Skip to content

Commit

Permalink
add proxy tools use case
Browse files Browse the repository at this point in the history
  • Loading branch information
qdriven committed Jan 6, 2025
1 parent f0d4868 commit bdba14e
Show file tree
Hide file tree
Showing 313 changed files with 329,084 additions and 5 deletions.
Empty file added agents/maigctester/.evn.example
Empty file.
Empty file added agents/maigctester/more.md
Empty file.
Empty file.
Empty file.
167 changes: 167 additions & 0 deletions agents/maigctester/src/maigctester/fi/agents/fundamentals.py
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 agents/maigctester/src/maigctester/fi/agents/market_data.py
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 agents/maigctester/src/maigctester/fi/agents/portfolio_manager.py
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]}
Loading

0 comments on commit bdba14e

Please sign in to comment.