Skip to content

Commit

Permalink
feat: add Anthropic Claude support and implement valuation safety che…
Browse files Browse the repository at this point in the history
…cks across agents
  • Loading branch information
rkacenski committed Jan 29, 2025
1 parent 0eee436 commit f253c21
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 30 deletions.
12 changes: 10 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# openai or anthropic
LLM_PROVIDER=openai

# Get your OpenAI API key from https://platform.openai.com/
OPENAI_API_KEY=your-openai-api-key
OPENAI_API_KEY=your-openai-key-here
OPENAI_MODEL=gpt-4 # or gpt-4-turbo-preview, gpt-3.5-turbo, etc.

# Get your Anthropic API key from https://console.anthropic.com/settings/keys
ANTHROPIC_API_KEY=your-anthropic-key-here
ANTHROPIC_MODEL=claude-3-sonnet-20240307 # or claude-3-opus-20240229, etc.

# Get your Financial Datasets API key from https://financialdatasets.ai/
FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key
FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key
64 changes: 58 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tabulate = "^0.9.0"
colorama = "^0.4.6"
questionary = "^2.1.0"
rich = "^13.9.4"
langchain-anthropic = "^0.3.4"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
Expand Down
26 changes: 22 additions & 4 deletions src/agents/portfolio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai.chat_models import ChatOpenAI
from langchain_anthropic import ChatAnthropic
import os

from graph.state import AgentState, show_agent_reasoning
from pydantic import BaseModel, Field
Expand Down Expand Up @@ -156,10 +158,26 @@ def portfolio_management_agent(state: AgentState):

def make_decision(prompt, tickers):
"""Attempts to get a decision from the LLM with retry logic"""
llm = ChatOpenAI(model="gpt-4o").with_structured_output(
PortfolioManagerOutput,
method="function_calling",
)
# Allow configurable model choice
model_provider = os.getenv("LLM_PROVIDER", "openai").lower()

if model_provider == "anthropic":
model_name = os.getenv("ANTHROPIC_MODEL", "claude-3-sonnet-20240307")
llm = ChatAnthropic(
model=model_name
).with_structured_output(
PortfolioManagerOutput,
method="function_calling",
)
else: # default to OpenAI
model_name = os.getenv("OPENAI_MODEL", "gpt-4")
llm = ChatOpenAI(
model=model_name
).with_structured_output(
PortfolioManagerOutput,
method="function_calling",
)

max_retries = 3
for attempt in range(max_retries):
try:
Expand Down
34 changes: 24 additions & 10 deletions src/agents/valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@ def valuation_agent(state: AgentState):
previous_financial_line_item = financial_line_items[1]

progress.update_status("valuation_agent", ticker, "Calculating owner earnings")
# Calculate working capital change
working_capital_change = current_financial_line_item.working_capital - previous_financial_line_item.working_capital
# Calculate working capital change with safety checks
working_capital_change = 0 # Default to 0 if we can't calculate
if (hasattr(current_financial_line_item, 'working_capital') and
hasattr(previous_financial_line_item, 'working_capital') and
current_financial_line_item.working_capital is not None and
previous_financial_line_item.working_capital is not None):
working_capital_change = current_financial_line_item.working_capital - previous_financial_line_item.working_capital

# Owner Earnings Valuation (Buffett Method)
owner_earnings_value = calculate_owner_earnings_value(
Expand All @@ -74,14 +79,18 @@ def valuation_agent(state: AgentState):
)

progress.update_status("valuation_agent", ticker, "Calculating DCF value")
# DCF Valuation
dcf_value = calculate_intrinsic_value(
free_cash_flow=current_financial_line_item.free_cash_flow,
growth_rate=metrics.earnings_growth,
discount_rate=0.10,
terminal_growth_rate=0.03,
num_years=5,
)
# DCF Valuation with safety check for free cash flow
if (hasattr(current_financial_line_item, 'free_cash_flow') and
current_financial_line_item.free_cash_flow is not None):
dcf_value = calculate_intrinsic_value(
free_cash_flow=current_financial_line_item.free_cash_flow,
growth_rate=metrics.earnings_growth,
discount_rate=0.10,
terminal_growth_rate=0.03,
num_years=5,
)
else:
dcf_value = 0

progress.update_status("valuation_agent", ticker, "Comparing to market value")
# Get the market cap
Expand Down Expand Up @@ -208,6 +217,11 @@ def calculate_intrinsic_value(
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.
"""
# Add safety check for None or invalid values
if free_cash_flow is None or not isinstance(free_cash_flow, (int, float)) or free_cash_flow <= 0:
return 0


# 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)]

Expand Down
32 changes: 24 additions & 8 deletions src/agents/warren_buffett.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import json
from typing_extensions import Literal
from utils.progress import progress
from langchain_anthropic import ChatAnthropic
import os


class BuffettSignal(BaseModel):
Expand Down Expand Up @@ -251,12 +253,12 @@ def calculate_owner_earnings(financial_line_items: list) -> dict[str, any]:
def calculate_intrinsic_value(financial_line_items: list) -> dict[str, any]:
"""Calculate intrinsic value using DCF with owner earnings."""
if not financial_line_items:
return {"value": None, "details": ["Insufficient data for valuation"]}
return {"intrinsic_value": None, "details": ["Insufficient data for valuation"]}

# Calculate owner earnings
earnings_data = calculate_owner_earnings(financial_line_items)
if not earnings_data["owner_earnings"]:
return {"value": None, "details": earnings_data["details"]}
return {"intrinsic_value": None, "details": earnings_data["details"]}

owner_earnings = earnings_data["owner_earnings"]

Expand All @@ -265,7 +267,7 @@ def calculate_intrinsic_value(financial_line_items: list) -> dict[str, any]:
shares_outstanding = latest_financial_line_items.outstanding_shares

if not shares_outstanding:
return {"value": None, "details": ["Missing shares outstanding data"]}
return {"intrinsic_value": None, "details": ["Missing shares outstanding data"]}

# Buffett's DCF assumptions
growth_rate = 0.05 # Conservative 5% growth
Expand Down Expand Up @@ -340,11 +342,25 @@ def generate_buffett_output(ticker: str, analysis_data: dict[str, any]) -> Buffe
"ticker": ticker
})


llm = ChatOpenAI(model="gpt-4o").with_structured_output(
BuffettSignal,
method="function_calling",
)
# Allow configurable model choice
model_provider = os.getenv("LLM_PROVIDER", "openai").lower()

if model_provider == "anthropic":
model_name = os.getenv("ANTHROPIC_MODEL", "claude-3-sonnet-20240307")
llm = ChatAnthropic(
model=model_name
).with_structured_output(
BuffettSignal,
method="function_calling",
)
else: # default to OpenAI
model_name = os.getenv("OPENAI_MODEL", "gpt-4")
llm = ChatOpenAI(
model=model_name
).with_structured_output(
BuffettSignal,
method="function_calling",
)

max_retries = 3
for attempt in range(max_retries):
Expand Down

0 comments on commit f253c21

Please sign in to comment.