Skip to content

Commit bdba14e

Browse files
committed
add proxy tools use case
1 parent f0d4868 commit bdba14e

File tree

313 files changed

+329084
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

313 files changed

+329084
-5
lines changed

Diff for: agents/maigctester/.evn.example

Whitespace-only changes.

Diff for: agents/maigctester/more.md

Whitespace-only changes.

Diff for: agents/maigctester/src/maigctester/fi/README.md

Whitespace-only changes.

Diff for: agents/maigctester/src/maigctester/fi/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
2+
from langchain_core.messages import HumanMessage
3+
4+
from agents.state import AgentState, show_agent_reasoning
5+
6+
import json
7+
8+
##### Fundamental Agent #####
9+
def fundamentals_agent(state: AgentState):
10+
"""Analyzes fundamental data and generates trading signals."""
11+
show_reasoning = state["metadata"]["show_reasoning"]
12+
data = state["data"]
13+
metrics = data["financial_metrics"][0]
14+
financial_line_item = data["financial_line_items"][0]
15+
market_cap = data["market_cap"]
16+
17+
# Initialize signals list for different fundamental aspects
18+
signals = []
19+
reasoning = {}
20+
21+
# 1. Profitability Analysis
22+
profitability_score = 0
23+
if metrics["return_on_equity"] > 0.15: # Strong ROE above 15%
24+
profitability_score += 1
25+
if metrics["net_margin"] > 0.20: # Healthy profit margins
26+
profitability_score += 1
27+
if metrics["operating_margin"] > 0.15: # Strong operating efficiency
28+
profitability_score += 1
29+
30+
signals.append('bullish' if profitability_score >= 2 else 'bearish' if profitability_score == 0 else 'neutral')
31+
reasoning["Profitability"] = {
32+
"signal": signals[0],
33+
"details": f"ROE: {metrics['return_on_equity']:.2%}, Net Margin: {metrics['net_margin']:.2%}, Op Margin: {metrics['operating_margin']:.2%}"
34+
}
35+
36+
# 2. Growth Analysis
37+
growth_score = 0
38+
if metrics["revenue_growth"] > 0.10: # 10% revenue growth
39+
growth_score += 1
40+
if metrics["earnings_growth"] > 0.10: # 10% earnings growth
41+
growth_score += 1
42+
if metrics["book_value_growth"] > 0.10: # 10% book value growth
43+
growth_score += 1
44+
45+
signals.append('bullish' if growth_score >= 2 else 'bearish' if growth_score == 0 else 'neutral')
46+
reasoning["Growth"] = {
47+
"signal": signals[1],
48+
"details": f"Revenue Growth: {metrics['revenue_growth']:.2%}, Earnings Growth: {metrics['earnings_growth']:.2%}"
49+
}
50+
51+
# 3. Financial Health
52+
health_score = 0
53+
if metrics["current_ratio"] > 1.5: # Strong liquidity
54+
health_score += 1
55+
if metrics["debt_to_equity"] < 0.5: # Conservative debt levels
56+
health_score += 1
57+
if metrics["free_cash_flow_per_share"] > metrics["earnings_per_share"] * 0.8: # Strong FCF conversion
58+
health_score += 1
59+
60+
signals.append('bullish' if health_score >= 2 else 'bearish' if health_score == 0 else 'neutral')
61+
reasoning["Financial_Health"] = {
62+
"signal": signals[2],
63+
"details": f"Current Ratio: {metrics['current_ratio']:.2f}, D/E: {metrics['debt_to_equity']:.2f}"
64+
}
65+
66+
# 4. Price to X ratios
67+
pe_ratio = metrics["price_to_earnings_ratio"]
68+
pb_ratio = metrics["price_to_book_ratio"]
69+
ps_ratio = metrics["price_to_sales_ratio"]
70+
71+
price_ratio_score = 0
72+
if pe_ratio < 25: # Reasonable P/E ratio
73+
price_ratio_score += 1
74+
if pb_ratio < 3: # Reasonable P/B ratio
75+
price_ratio_score += 1
76+
if ps_ratio < 5: # Reasonable P/S ratio
77+
price_ratio_score += 1
78+
79+
signals.append('bullish' if price_ratio_score >= 2 else 'bearish' if price_ratio_score == 0 else 'neutral')
80+
reasoning["Price_Ratios"] = {
81+
"signal": signals[3],
82+
"details": f"P/E: {pe_ratio:.2f}, P/B: {pb_ratio:.2f}, P/S: {ps_ratio:.2f}"
83+
}
84+
85+
# 5. Calculate intrinsic value and compare to market cap
86+
free_cash_flow = financial_line_item.get('free_cash_flow')
87+
intrinsic_value = calculate_intrinsic_value(
88+
free_cash_flow=free_cash_flow,
89+
growth_rate=metrics["earnings_growth"],
90+
discount_rate=0.10,
91+
terminal_growth_rate=0.03,
92+
num_years=5,
93+
)
94+
if market_cap < intrinsic_value:
95+
signals.append('bullish')
96+
else:
97+
signals.append('bearish')
98+
99+
reasoning["Intrinsic_Value"] = {
100+
"signal": signals[4],
101+
"details": f"Intrinsic Value: ${intrinsic_value:,.2f}, Market Cap: ${market_cap:,.2f}"
102+
}
103+
104+
# Determine overall signal
105+
bullish_signals = signals.count('bullish')
106+
bearish_signals = signals.count('bearish')
107+
108+
if bullish_signals > bearish_signals:
109+
overall_signal = 'bullish'
110+
elif bearish_signals > bullish_signals:
111+
overall_signal = 'bearish'
112+
else:
113+
overall_signal = 'neutral'
114+
115+
# Calculate confidence level
116+
total_signals = len(signals)
117+
confidence = max(bullish_signals, bearish_signals) / total_signals
118+
119+
message_content = {
120+
"signal": overall_signal,
121+
"confidence": f"{round(confidence * 100)}%",
122+
"reasoning": reasoning
123+
}
124+
125+
# Create the fundamental analysis message
126+
message = HumanMessage(
127+
content=json.dumps(message_content),
128+
name="fundamentals_agent",
129+
)
130+
131+
# Print the reasoning if the flag is set
132+
if show_reasoning:
133+
show_agent_reasoning(message_content, "Fundamental Analysis Agent")
134+
135+
return {
136+
"messages": [message],
137+
"data": data,
138+
}
139+
140+
def calculate_intrinsic_value(
141+
free_cash_flow: float,
142+
growth_rate: float = 0.05,
143+
discount_rate: float = 0.10,
144+
terminal_growth_rate: float = 0.02,
145+
num_years: int = 5,
146+
) -> float:
147+
"""
148+
Computes the discounted cash flow (DCF) for a given company based on the current free cash flow.
149+
Use this function to calculate the intrinsic value of a stock.
150+
"""
151+
# Estimate the future cash flows based on the growth rate
152+
cash_flows = [free_cash_flow * (1 + growth_rate) ** i for i in range(num_years)]
153+
154+
# Calculate the present value of projected cash flows
155+
present_values = []
156+
for i in range(num_years):
157+
present_value = cash_flows[i] / (1 + discount_rate) ** (i + 1)
158+
present_values.append(present_value)
159+
160+
# Calculate the terminal value
161+
terminal_value = cash_flows[-1] * (1 + terminal_growth_rate) / (discount_rate - terminal_growth_rate)
162+
terminal_present_value = terminal_value / (1 + discount_rate) ** num_years
163+
164+
# Sum up the present values and terminal value
165+
dcf_value = sum(present_values) + terminal_present_value
166+
167+
return dcf_value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
from langchain_openai.chat_models import ChatOpenAI
3+
4+
from agents.state import AgentState
5+
from tools.api import search_line_items, get_financial_metrics, get_insider_trades, get_market_cap, get_prices
6+
7+
from datetime import datetime
8+
9+
llm = ChatOpenAI(model="gpt-4o")
10+
11+
def market_data_agent(state: AgentState):
12+
"""Responsible for gathering and preprocessing market data"""
13+
messages = state["messages"]
14+
data = state["data"]
15+
16+
# Set default dates
17+
end_date = data["end_date"] or datetime.now().strftime('%Y-%m-%d')
18+
if not data["start_date"]:
19+
# Calculate 3 months before end_date
20+
end_date_obj = datetime.strptime(end_date, '%Y-%m-%d')
21+
start_date = end_date_obj.replace(month=end_date_obj.month - 3) if end_date_obj.month > 3 else \
22+
end_date_obj.replace(year=end_date_obj.year - 1, month=end_date_obj.month + 9)
23+
start_date = start_date.strftime('%Y-%m-%d')
24+
else:
25+
start_date = data["start_date"]
26+
27+
# Get the historical price data
28+
prices = get_prices(
29+
ticker=data["ticker"],
30+
start_date=start_date,
31+
end_date=end_date,
32+
)
33+
34+
# Get the financial metrics
35+
financial_metrics = get_financial_metrics(
36+
ticker=data["ticker"],
37+
report_period=end_date,
38+
period='ttm',
39+
limit=1,
40+
)
41+
42+
# Get the insider trades
43+
insider_trades = get_insider_trades(
44+
ticker=data["ticker"],
45+
end_date=end_date,
46+
limit=5,
47+
)
48+
49+
# Get the market cap
50+
market_cap = get_market_cap(
51+
ticker=data["ticker"],
52+
)
53+
54+
# Get the line_items
55+
financial_line_items = search_line_items(
56+
ticker=data["ticker"],
57+
line_items=["free_cash_flow"],
58+
period='ttm',
59+
limit=1,
60+
)
61+
62+
return {
63+
"messages": messages,
64+
"data": {
65+
**data,
66+
"prices": prices,
67+
"start_date": start_date,
68+
"end_date": end_date,
69+
"financial_metrics": financial_metrics,
70+
"insider_trades": insider_trades,
71+
"market_cap": market_cap,
72+
"financial_line_items": financial_line_items,
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
2+
from langchain_core.messages import HumanMessage
3+
from langchain_core.prompts import ChatPromptTemplate
4+
from langchain_openai.chat_models import ChatOpenAI
5+
6+
from agents.state import AgentState, show_agent_reasoning
7+
8+
9+
##### Portfolio Management Agent #####
10+
def portfolio_management_agent(state: AgentState):
11+
"""Makes final trading decisions and generates orders"""
12+
show_reasoning = state["metadata"]["show_reasoning"]
13+
portfolio = state["data"]["portfolio"]
14+
15+
# Get the technical analyst, fundamentals agent, and risk management agent messages
16+
technical_message = next(msg for msg in state["messages"] if msg.name == "technical_analyst_agent")
17+
fundamentals_message = next(msg for msg in state["messages"] if msg.name == "fundamentals_agent")
18+
sentiment_message = next(msg for msg in state["messages"] if msg.name == "sentiment_agent")
19+
risk_message = next(msg for msg in state["messages"] if msg.name == "risk_management_agent")
20+
21+
# Create the prompt template
22+
template = ChatPromptTemplate.from_messages(
23+
[
24+
(
25+
"system",
26+
"""You are a portfolio manager making final trading decisions.
27+
Your job is to make a trading decision based on the team's analysis while strictly adhering
28+
to risk management constraints.
29+
30+
RISK MANAGEMENT CONSTRAINTS:
31+
- You MUST NOT exceed the max_position_size specified by the risk manager
32+
- You MUST follow the trading_action (buy/sell/hold) recommended by risk management
33+
- These are hard constraints that cannot be overridden by other signals
34+
35+
When weighing the different signals for direction and timing:
36+
1. Fundamental Analysis (50% weight)
37+
- Primary driver of trading decisions
38+
- Should determine overall direction
39+
40+
2. Technical Analysis (35% weight)
41+
- Secondary confirmation
42+
- Helps with entry/exit timing
43+
44+
3. Sentiment Analysis (15% weight)
45+
- Final consideration
46+
- Can influence sizing within risk limits
47+
48+
The decision process should be:
49+
1. First check risk management constraints
50+
2. Then evaluate fundamental outlook
51+
3. Use technical analysis for timing
52+
4. Consider sentiment for final adjustment
53+
54+
Provide the following in your output:
55+
- "action": "buy" | "sell" | "hold",
56+
- "quantity": <positive integer>
57+
- "confidence": <float between 0 and 1>
58+
- "agent_signals": <list of agent signals including agent name, signal (bullish | bearish | neutral), and their confidence>
59+
- "reasoning": <concise explanation of the decision including how you weighted the signals>
60+
61+
Trading Rules:
62+
- Never exceed risk management position limits
63+
- Only buy if you have available cash
64+
- Only sell if you have shares to sell
65+
- Quantity must be ≤ current position for sells
66+
- Quantity must be ≤ max_position_size from risk management"""
67+
),
68+
(
69+
"human",
70+
"""Based on the team's analysis below, make your trading decision.
71+
72+
Technical Analysis Trading Signal: {technical_message}
73+
Fundamental Analysis Trading Signal: {fundamentals_message}
74+
Sentiment Analysis Trading Signal: {sentiment_message}
75+
Risk Management Trading Signal: {risk_message}
76+
77+
Here is the current portfolio:
78+
Portfolio:
79+
Cash: {portfolio_cash}
80+
Current Position: {portfolio_stock} shares
81+
82+
Only include the action, quantity, reasoning, confidence, and agent_signals in your output as JSON. Do not include any JSON markdown.
83+
84+
Remember, the action must be either buy, sell, or hold.
85+
You can only buy if you have available cash.
86+
You can only sell if you have shares in the portfolio to sell.
87+
"""
88+
),
89+
]
90+
)
91+
92+
# Generate the prompt
93+
prompt = template.invoke(
94+
{
95+
"technical_message": technical_message.content,
96+
"fundamentals_message": fundamentals_message.content,
97+
"sentiment_message": sentiment_message.content,
98+
"risk_message": risk_message.content,
99+
"portfolio_cash": f"{portfolio['cash']:.2f}",
100+
"portfolio_stock": portfolio["stock"]
101+
}
102+
)
103+
# Invoke the LLM
104+
llm = ChatOpenAI(model="gpt-4o")
105+
result = llm.invoke(prompt)
106+
107+
# Create the portfolio management message
108+
message = HumanMessage(
109+
content=result.content,
110+
name="portfolio_management",
111+
)
112+
113+
# Print the decision if the flag is set
114+
if show_reasoning:
115+
show_agent_reasoning(message.content, "Portfolio Management Agent")
116+
117+
return {"messages": state["messages"] + [message]}

0 commit comments

Comments
 (0)