-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
313 changed files
with
329,084 additions
and
5 deletions.
There are no files selected for viewing
Empty file.
Empty file.
Empty file.
Empty file.
167 changes: 167 additions & 0 deletions
167
agents/maigctester/src/maigctester/fi/agents/fundamentals.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
|
||
from langchain_core.messages import HumanMessage | ||
|
||
from agents.state import AgentState, show_agent_reasoning | ||
|
||
import json | ||
|
||
##### Fundamental Agent ##### | ||
def fundamentals_agent(state: AgentState): | ||
"""Analyzes fundamental data and generates trading signals.""" | ||
show_reasoning = state["metadata"]["show_reasoning"] | ||
data = state["data"] | ||
metrics = data["financial_metrics"][0] | ||
financial_line_item = data["financial_line_items"][0] | ||
market_cap = data["market_cap"] | ||
|
||
# Initialize signals list for different fundamental aspects | ||
signals = [] | ||
reasoning = {} | ||
|
||
# 1. Profitability Analysis | ||
profitability_score = 0 | ||
if metrics["return_on_equity"] > 0.15: # Strong ROE above 15% | ||
profitability_score += 1 | ||
if metrics["net_margin"] > 0.20: # Healthy profit margins | ||
profitability_score += 1 | ||
if metrics["operating_margin"] > 0.15: # Strong operating efficiency | ||
profitability_score += 1 | ||
|
||
signals.append('bullish' if profitability_score >= 2 else 'bearish' if profitability_score == 0 else 'neutral') | ||
reasoning["Profitability"] = { | ||
"signal": signals[0], | ||
"details": f"ROE: {metrics['return_on_equity']:.2%}, Net Margin: {metrics['net_margin']:.2%}, Op Margin: {metrics['operating_margin']:.2%}" | ||
} | ||
|
||
# 2. Growth Analysis | ||
growth_score = 0 | ||
if metrics["revenue_growth"] > 0.10: # 10% revenue growth | ||
growth_score += 1 | ||
if metrics["earnings_growth"] > 0.10: # 10% earnings growth | ||
growth_score += 1 | ||
if metrics["book_value_growth"] > 0.10: # 10% book value growth | ||
growth_score += 1 | ||
|
||
signals.append('bullish' if growth_score >= 2 else 'bearish' if growth_score == 0 else 'neutral') | ||
reasoning["Growth"] = { | ||
"signal": signals[1], | ||
"details": f"Revenue Growth: {metrics['revenue_growth']:.2%}, Earnings Growth: {metrics['earnings_growth']:.2%}" | ||
} | ||
|
||
# 3. Financial Health | ||
health_score = 0 | ||
if metrics["current_ratio"] > 1.5: # Strong liquidity | ||
health_score += 1 | ||
if metrics["debt_to_equity"] < 0.5: # Conservative debt levels | ||
health_score += 1 | ||
if metrics["free_cash_flow_per_share"] > metrics["earnings_per_share"] * 0.8: # Strong FCF conversion | ||
health_score += 1 | ||
|
||
signals.append('bullish' if health_score >= 2 else 'bearish' if health_score == 0 else 'neutral') | ||
reasoning["Financial_Health"] = { | ||
"signal": signals[2], | ||
"details": f"Current Ratio: {metrics['current_ratio']:.2f}, D/E: {metrics['debt_to_equity']:.2f}" | ||
} | ||
|
||
# 4. Price to X ratios | ||
pe_ratio = metrics["price_to_earnings_ratio"] | ||
pb_ratio = metrics["price_to_book_ratio"] | ||
ps_ratio = metrics["price_to_sales_ratio"] | ||
|
||
price_ratio_score = 0 | ||
if pe_ratio < 25: # Reasonable P/E ratio | ||
price_ratio_score += 1 | ||
if pb_ratio < 3: # Reasonable P/B ratio | ||
price_ratio_score += 1 | ||
if ps_ratio < 5: # Reasonable P/S ratio | ||
price_ratio_score += 1 | ||
|
||
signals.append('bullish' if price_ratio_score >= 2 else 'bearish' if price_ratio_score == 0 else 'neutral') | ||
reasoning["Price_Ratios"] = { | ||
"signal": signals[3], | ||
"details": f"P/E: {pe_ratio:.2f}, P/B: {pb_ratio:.2f}, P/S: {ps_ratio:.2f}" | ||
} | ||
|
||
# 5. Calculate intrinsic value and compare to market cap | ||
free_cash_flow = financial_line_item.get('free_cash_flow') | ||
intrinsic_value = calculate_intrinsic_value( | ||
free_cash_flow=free_cash_flow, | ||
growth_rate=metrics["earnings_growth"], | ||
discount_rate=0.10, | ||
terminal_growth_rate=0.03, | ||
num_years=5, | ||
) | ||
if market_cap < intrinsic_value: | ||
signals.append('bullish') | ||
else: | ||
signals.append('bearish') | ||
|
||
reasoning["Intrinsic_Value"] = { | ||
"signal": signals[4], | ||
"details": f"Intrinsic Value: ${intrinsic_value:,.2f}, Market Cap: ${market_cap:,.2f}" | ||
} | ||
|
||
# Determine overall signal | ||
bullish_signals = signals.count('bullish') | ||
bearish_signals = signals.count('bearish') | ||
|
||
if bullish_signals > bearish_signals: | ||
overall_signal = 'bullish' | ||
elif bearish_signals > bullish_signals: | ||
overall_signal = 'bearish' | ||
else: | ||
overall_signal = 'neutral' | ||
|
||
# Calculate confidence level | ||
total_signals = len(signals) | ||
confidence = max(bullish_signals, bearish_signals) / total_signals | ||
|
||
message_content = { | ||
"signal": overall_signal, | ||
"confidence": f"{round(confidence * 100)}%", | ||
"reasoning": reasoning | ||
} | ||
|
||
# Create the fundamental analysis message | ||
message = HumanMessage( | ||
content=json.dumps(message_content), | ||
name="fundamentals_agent", | ||
) | ||
|
||
# Print the reasoning if the flag is set | ||
if show_reasoning: | ||
show_agent_reasoning(message_content, "Fundamental Analysis Agent") | ||
|
||
return { | ||
"messages": [message], | ||
"data": data, | ||
} | ||
|
||
def calculate_intrinsic_value( | ||
free_cash_flow: float, | ||
growth_rate: float = 0.05, | ||
discount_rate: float = 0.10, | ||
terminal_growth_rate: float = 0.02, | ||
num_years: int = 5, | ||
) -> float: | ||
""" | ||
Computes the discounted cash flow (DCF) for a given company based on the current free cash flow. | ||
Use this function to calculate the intrinsic value of a stock. | ||
""" | ||
# Estimate the future cash flows based on the growth rate | ||
cash_flows = [free_cash_flow * (1 + growth_rate) ** i for i in range(num_years)] | ||
|
||
# Calculate the present value of projected cash flows | ||
present_values = [] | ||
for i in range(num_years): | ||
present_value = cash_flows[i] / (1 + discount_rate) ** (i + 1) | ||
present_values.append(present_value) | ||
|
||
# Calculate the terminal value | ||
terminal_value = cash_flows[-1] * (1 + terminal_growth_rate) / (discount_rate - terminal_growth_rate) | ||
terminal_present_value = terminal_value / (1 + discount_rate) ** num_years | ||
|
||
# Sum up the present values and terminal value | ||
dcf_value = sum(present_values) + terminal_present_value | ||
|
||
return dcf_value |
74 changes: 74 additions & 0 deletions
74
agents/maigctester/src/maigctester/fi/agents/market_data.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
|
||
from langchain_openai.chat_models import ChatOpenAI | ||
|
||
from agents.state import AgentState | ||
from tools.api import search_line_items, get_financial_metrics, get_insider_trades, get_market_cap, get_prices | ||
|
||
from datetime import datetime | ||
|
||
llm = ChatOpenAI(model="gpt-4o") | ||
|
||
def market_data_agent(state: AgentState): | ||
"""Responsible for gathering and preprocessing market data""" | ||
messages = state["messages"] | ||
data = state["data"] | ||
|
||
# Set default dates | ||
end_date = data["end_date"] or datetime.now().strftime('%Y-%m-%d') | ||
if not data["start_date"]: | ||
# Calculate 3 months before end_date | ||
end_date_obj = datetime.strptime(end_date, '%Y-%m-%d') | ||
start_date = end_date_obj.replace(month=end_date_obj.month - 3) if end_date_obj.month > 3 else \ | ||
end_date_obj.replace(year=end_date_obj.year - 1, month=end_date_obj.month + 9) | ||
start_date = start_date.strftime('%Y-%m-%d') | ||
else: | ||
start_date = data["start_date"] | ||
|
||
# Get the historical price data | ||
prices = get_prices( | ||
ticker=data["ticker"], | ||
start_date=start_date, | ||
end_date=end_date, | ||
) | ||
|
||
# Get the financial metrics | ||
financial_metrics = get_financial_metrics( | ||
ticker=data["ticker"], | ||
report_period=end_date, | ||
period='ttm', | ||
limit=1, | ||
) | ||
|
||
# Get the insider trades | ||
insider_trades = get_insider_trades( | ||
ticker=data["ticker"], | ||
end_date=end_date, | ||
limit=5, | ||
) | ||
|
||
# Get the market cap | ||
market_cap = get_market_cap( | ||
ticker=data["ticker"], | ||
) | ||
|
||
# Get the line_items | ||
financial_line_items = search_line_items( | ||
ticker=data["ticker"], | ||
line_items=["free_cash_flow"], | ||
period='ttm', | ||
limit=1, | ||
) | ||
|
||
return { | ||
"messages": messages, | ||
"data": { | ||
**data, | ||
"prices": prices, | ||
"start_date": start_date, | ||
"end_date": end_date, | ||
"financial_metrics": financial_metrics, | ||
"insider_trades": insider_trades, | ||
"market_cap": market_cap, | ||
"financial_line_items": financial_line_items, | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
agents/maigctester/src/maigctester/fi/agents/portfolio_manager.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
|
||
from langchain_core.messages import HumanMessage | ||
from langchain_core.prompts import ChatPromptTemplate | ||
from langchain_openai.chat_models import ChatOpenAI | ||
|
||
from agents.state import AgentState, show_agent_reasoning | ||
|
||
|
||
##### Portfolio Management Agent ##### | ||
def portfolio_management_agent(state: AgentState): | ||
"""Makes final trading decisions and generates orders""" | ||
show_reasoning = state["metadata"]["show_reasoning"] | ||
portfolio = state["data"]["portfolio"] | ||
|
||
# Get the technical analyst, fundamentals agent, and risk management agent messages | ||
technical_message = next(msg for msg in state["messages"] if msg.name == "technical_analyst_agent") | ||
fundamentals_message = next(msg for msg in state["messages"] if msg.name == "fundamentals_agent") | ||
sentiment_message = next(msg for msg in state["messages"] if msg.name == "sentiment_agent") | ||
risk_message = next(msg for msg in state["messages"] if msg.name == "risk_management_agent") | ||
|
||
# Create the prompt template | ||
template = ChatPromptTemplate.from_messages( | ||
[ | ||
( | ||
"system", | ||
"""You are a portfolio manager making final trading decisions. | ||
Your job is to make a trading decision based on the team's analysis while strictly adhering | ||
to risk management constraints. | ||
RISK MANAGEMENT CONSTRAINTS: | ||
- You MUST NOT exceed the max_position_size specified by the risk manager | ||
- You MUST follow the trading_action (buy/sell/hold) recommended by risk management | ||
- These are hard constraints that cannot be overridden by other signals | ||
When weighing the different signals for direction and timing: | ||
1. Fundamental Analysis (50% weight) | ||
- Primary driver of trading decisions | ||
- Should determine overall direction | ||
2. Technical Analysis (35% weight) | ||
- Secondary confirmation | ||
- Helps with entry/exit timing | ||
3. Sentiment Analysis (15% weight) | ||
- Final consideration | ||
- Can influence sizing within risk limits | ||
The decision process should be: | ||
1. First check risk management constraints | ||
2. Then evaluate fundamental outlook | ||
3. Use technical analysis for timing | ||
4. Consider sentiment for final adjustment | ||
Provide the following in your output: | ||
- "action": "buy" | "sell" | "hold", | ||
- "quantity": <positive integer> | ||
- "confidence": <float between 0 and 1> | ||
- "agent_signals": <list of agent signals including agent name, signal (bullish | bearish | neutral), and their confidence> | ||
- "reasoning": <concise explanation of the decision including how you weighted the signals> | ||
Trading Rules: | ||
- Never exceed risk management position limits | ||
- Only buy if you have available cash | ||
- Only sell if you have shares to sell | ||
- Quantity must be ≤ current position for sells | ||
- Quantity must be ≤ max_position_size from risk management""" | ||
), | ||
( | ||
"human", | ||
"""Based on the team's analysis below, make your trading decision. | ||
Technical Analysis Trading Signal: {technical_message} | ||
Fundamental Analysis Trading Signal: {fundamentals_message} | ||
Sentiment Analysis Trading Signal: {sentiment_message} | ||
Risk Management Trading Signal: {risk_message} | ||
Here is the current portfolio: | ||
Portfolio: | ||
Cash: {portfolio_cash} | ||
Current Position: {portfolio_stock} shares | ||
Only include the action, quantity, reasoning, confidence, and agent_signals in your output as JSON. Do not include any JSON markdown. | ||
Remember, the action must be either buy, sell, or hold. | ||
You can only buy if you have available cash. | ||
You can only sell if you have shares in the portfolio to sell. | ||
""" | ||
), | ||
] | ||
) | ||
|
||
# Generate the prompt | ||
prompt = template.invoke( | ||
{ | ||
"technical_message": technical_message.content, | ||
"fundamentals_message": fundamentals_message.content, | ||
"sentiment_message": sentiment_message.content, | ||
"risk_message": risk_message.content, | ||
"portfolio_cash": f"{portfolio['cash']:.2f}", | ||
"portfolio_stock": portfolio["stock"] | ||
} | ||
) | ||
# Invoke the LLM | ||
llm = ChatOpenAI(model="gpt-4o") | ||
result = llm.invoke(prompt) | ||
|
||
# Create the portfolio management message | ||
message = HumanMessage( | ||
content=result.content, | ||
name="portfolio_management", | ||
) | ||
|
||
# Print the decision if the flag is set | ||
if show_reasoning: | ||
show_agent_reasoning(message.content, "Portfolio Management Agent") | ||
|
||
return {"messages": state["messages"] + [message]} |
Oops, something went wrong.