Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor FinancialDatasetAPI integration and the memory cache #42

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/agents/bill_ackman.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from langchain_openai import ChatOpenAI
from graph.state import AgentState, show_agent_reasoning
from tools.api import get_financial_metrics, get_market_cap, search_line_items
from tools.api import FinancialDatasetAPI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage
from pydantic import BaseModel
Expand All @@ -26,15 +26,17 @@ def bill_ackman_agent(state: AgentState):

analysis_data = {}
ackman_analysis = {}

financial_api = FinancialDatasetAPI()

for ticker in tickers:
progress.update_status("bill_ackman_agent", ticker, "Fetching financial metrics")
# You can adjust these parameters (period="annual"/"ttm", limit=5/10, etc.)
metrics = get_financial_metrics(ticker, end_date, period="annual", limit=5)
metrics = financial_api.get_financial_metrics(ticker, end_date, period="annual", limit=5)

progress.update_status("bill_ackman_agent", ticker, "Gathering financial line items")
# Request multiple periods of data (annual or TTM) for a more robust long-term view.
financial_line_items = search_line_items(
financial_line_items = financial_api.search_line_items(
ticker,
[
"revenue",
Expand All @@ -52,7 +54,7 @@ def bill_ackman_agent(state: AgentState):
)

progress.update_status("bill_ackman_agent", ticker, "Getting market cap")
market_cap = get_market_cap(ticker, end_date)
market_cap = financial_api.get_market_cap(ticker, end_date)

progress.update_status("bill_ackman_agent", ticker, "Analyzing business quality")
quality_analysis = analyze_business_quality(metrics, financial_line_items)
Expand Down
122 changes: 100 additions & 22 deletions src/agents/fundamentals.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json

from langchain_core.messages import HumanMessage

from graph.state import AgentState, show_agent_reasoning
from tools.api import FinancialDatasetAPI
from utils.progress import progress
import json

from tools.api import get_financial_metrics


##### Fundamental Agent #####
Expand All @@ -17,18 +18,22 @@ def fundamentals_agent(state: AgentState):
fundamental_analysis = {}

for ticker in tickers:
progress.update_status("fundamentals_agent", ticker, "Fetching financial metrics")
progress.update_status(
"fundamentals_agent", ticker, "Fetching financial metrics"
)

# Get the financial metrics
financial_metrics = get_financial_metrics(
financial_metrics = FinancialDatasetAPI().get_financial_metrics(
ticker=ticker,
end_date=end_date,
report_period=end_date,
period="ttm",
limit=10,
)

if not financial_metrics:
progress.update_status("fundamentals_agent", ticker, "Failed: No financial metrics found")
progress.update_status(
"fundamentals_agent", ticker, "Failed: No financial metrics found"
)
continue

# Pull the most recent financial metrics
Expand All @@ -49,12 +54,31 @@ def fundamentals_agent(state: AgentState):
(net_margin, 0.20), # Healthy profit margins
(operating_margin, 0.15), # Strong operating efficiency
]
profitability_score = sum(metric is not None and metric > threshold for metric, threshold in thresholds)
profitability_score = sum(
metric is not None and metric > threshold
for metric, threshold in thresholds
)

signals.append("bullish" if profitability_score >= 2 else "bearish" if profitability_score == 0 else "neutral")
signals.append(
"bullish"
if profitability_score >= 2
else "bearish"
if profitability_score == 0
else "neutral"
)
reasoning["profitability_signal"] = {
"signal": signals[0],
"details": (f"ROE: {return_on_equity:.2%}" if return_on_equity else "ROE: N/A") + ", " + (f"Net Margin: {net_margin:.2%}" if net_margin else "Net Margin: N/A") + ", " + (f"Op Margin: {operating_margin:.2%}" if operating_margin else "Op Margin: N/A"),
"details": (
f"ROE: {return_on_equity:.2%}" if return_on_equity else "ROE: N/A"
)
+ ", "
+ (f"Net Margin: {net_margin:.2%}" if net_margin else "Net Margin: N/A")
+ ", "
+ (
f"Op Margin: {operating_margin:.2%}"
if operating_margin
else "Op Margin: N/A"
),
}

progress.update_status("fundamentals_agent", ticker, "Analyzing growth")
Expand All @@ -68,15 +92,36 @@ def fundamentals_agent(state: AgentState):
(earnings_growth, 0.10), # 10% earnings growth
(book_value_growth, 0.10), # 10% book value growth
]
growth_score = sum(metric is not None and metric > threshold for metric, threshold in thresholds)
growth_score = sum(
metric is not None and metric > threshold
for metric, threshold in thresholds
)

signals.append("bullish" if growth_score >= 2 else "bearish" if growth_score == 0 else "neutral")
signals.append(
"bullish"
if growth_score >= 2
else "bearish"
if growth_score == 0
else "neutral"
)
reasoning["growth_signal"] = {
"signal": signals[1],
"details": (f"Revenue Growth: {revenue_growth:.2%}" if revenue_growth else "Revenue Growth: N/A") + ", " + (f"Earnings Growth: {earnings_growth:.2%}" if earnings_growth else "Earnings Growth: N/A"),
"details": (
f"Revenue Growth: {revenue_growth:.2%}"
if revenue_growth
else "Revenue Growth: N/A"
)
+ ", "
+ (
f"Earnings Growth: {earnings_growth:.2%}"
if earnings_growth
else "Earnings Growth: N/A"
),
}

progress.update_status("fundamentals_agent", ticker, "Analyzing financial health")
progress.update_status(
"fundamentals_agent", ticker, "Analyzing financial health"
)
# 3. Financial Health
current_ratio = metrics.current_ratio
debt_to_equity = metrics.debt_to_equity
Expand All @@ -88,16 +133,34 @@ def fundamentals_agent(state: AgentState):
health_score += 1
if debt_to_equity and debt_to_equity < 0.5: # Conservative debt levels
health_score += 1
if free_cash_flow_per_share and earnings_per_share and free_cash_flow_per_share > earnings_per_share * 0.8: # Strong FCF conversion
if (
free_cash_flow_per_share
and earnings_per_share
and free_cash_flow_per_share > 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")
signals.append(
"bullish"
if health_score >= 2
else "bearish"
if health_score == 0
else "neutral"
)
reasoning["financial_health_signal"] = {
"signal": signals[2],
"details": (f"Current Ratio: {current_ratio:.2f}" if current_ratio else "Current Ratio: N/A") + ", " + (f"D/E: {debt_to_equity:.2f}" if debt_to_equity else "D/E: N/A"),
"details": (
f"Current Ratio: {current_ratio:.2f}"
if current_ratio
else "Current Ratio: N/A"
)
+ ", "
+ (f"D/E: {debt_to_equity:.2f}" if debt_to_equity else "D/E: N/A"),
}

progress.update_status("fundamentals_agent", ticker, "Analyzing valuation ratios")
progress.update_status(
"fundamentals_agent", ticker, "Analyzing valuation ratios"
)
# 4. Price to X ratios
pe_ratio = metrics.price_to_earnings_ratio
pb_ratio = metrics.price_to_book_ratio
Expand All @@ -108,12 +171,25 @@ def fundamentals_agent(state: AgentState):
(pb_ratio, 3), # Reasonable P/B ratio
(ps_ratio, 5), # Reasonable P/S ratio
]
price_ratio_score = sum(metric is not None and metric > threshold for metric, threshold in thresholds)
price_ratio_score = sum(
metric is not None and metric > threshold
for metric, threshold in thresholds
)

signals.append("bullish" if price_ratio_score >= 2 else "bearish" if price_ratio_score == 0 else "neutral")
signals.append(
"bullish"
if price_ratio_score >= 2
else "bearish"
if price_ratio_score == 0
else "neutral"
)
reasoning["price_ratios_signal"] = {
"signal": signals[3],
"details": (f"P/E: {pe_ratio:.2f}" if pe_ratio else "P/E: N/A") + ", " + (f"P/B: {pb_ratio:.2f}" if pb_ratio else "P/B: N/A") + ", " + (f"P/S: {ps_ratio:.2f}" if ps_ratio else "P/S: N/A"),
"details": (f"P/E: {pe_ratio:.2f}" if pe_ratio else "P/E: N/A")
+ ", "
+ (f"P/B: {pb_ratio:.2f}" if pb_ratio else "P/B: N/A")
+ ", "
+ (f"P/S: {ps_ratio:.2f}" if ps_ratio else "P/S: N/A"),
}

progress.update_status("fundamentals_agent", ticker, "Calculating final signal")
Expand All @@ -130,7 +206,9 @@ def fundamentals_agent(state: AgentState):

# Calculate confidence level
total_signals = len(signals)
confidence = round(max(bullish_signals, bearish_signals) / total_signals, 2) * 100
confidence = (
round(max(bullish_signals, bearish_signals) / total_signals, 2) * 100
)

fundamental_analysis[ticker] = {
"signal": overall_signal,
Expand Down
13 changes: 8 additions & 5 deletions src/agents/risk_manager.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json

from langchain_core.messages import HumanMessage

from graph.state import AgentState, show_agent_reasoning
from tools.api import FinancialDatasetAPI
from utils.progress import progress
from tools.api import get_prices, prices_to_df
import json


##### Risk Management Agent #####
Expand All @@ -16,20 +18,21 @@ def risk_management_agent(state: AgentState):
risk_analysis = {}
current_prices = {} # Store prices here to avoid redundant API calls

financial_api = FinancialDatasetAPI()

for ticker in tickers:
progress.update_status("risk_management_agent", ticker, "Analyzing price data")

prices = get_prices(
prices_df = financial_api.get_prices(
ticker=ticker,
start_date=data["start_date"],
end_date=data["end_date"],
)

if not prices:
if prices_df.empty:
progress.update_status("risk_management_agent", ticker, "Failed: No price data found")
continue

prices_df = prices_to_df(prices)

progress.update_status("risk_management_agent", ticker, "Calculating position limits")

Expand Down
53 changes: 34 additions & 19 deletions src/agents/sentiment.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json

import numpy as np
import pandas as pd
from langchain_core.messages import HumanMessage

from graph.state import AgentState, show_agent_reasoning
from tools.api import FinancialDatasetAPI
from utils.progress import progress
import pandas as pd
import numpy as np
import json

from tools.api import get_insider_trades, get_company_news


##### Sentiment Agent #####
Expand All @@ -15,14 +16,16 @@ def sentiment_agent(state: AgentState):
end_date = data.get("end_date")
tickers = data.get("tickers")

financial_api = FinancialDatasetAPI()

# Initialize sentiment analysis for each ticker
sentiment_analysis = {}

for ticker in tickers:
progress.update_status("sentiment_agent", ticker, "Fetching insider trades")

# Get the insider trades
insider_trades = get_insider_trades(
insider_trades = financial_api.get_insider_trades(
ticker=ticker,
end_date=end_date,
limit=1000,
Expand All @@ -31,32 +34,39 @@ def sentiment_agent(state: AgentState):
progress.update_status("sentiment_agent", ticker, "Analyzing trading patterns")

# Get the signals from the insider trades
transaction_shares = pd.Series([t.transaction_shares for t in insider_trades]).dropna()
insider_signals = np.where(transaction_shares < 0, "bearish", "bullish").tolist()
transaction_shares = pd.Series(
[t.transaction_shares for t in insider_trades]
).dropna()
insider_signals = np.where(
transaction_shares < 0, "bearish", "bullish"
).tolist()

progress.update_status("sentiment_agent", ticker, "Fetching company news")

# Get the company news
company_news = get_company_news(ticker, end_date, limit=100)
company_news = financial_api.get_company_news(ticker, end_date, limit=100)

# Get the sentiment from the company news
sentiment = pd.Series([n.sentiment for n in company_news]).dropna()
news_signals = np.where(sentiment == "negative", "bearish",
np.where(sentiment == "positive", "bullish", "neutral")).tolist()

news_signals = np.where(
sentiment == "negative",
"bearish",
np.where(sentiment == "positive", "bullish", "neutral"),
).tolist()

progress.update_status("sentiment_agent", ticker, "Combining signals")
# Combine signals from both sources with weights
insider_weight = 0.3
news_weight = 0.7

# Calculate weighted signal counts
bullish_signals = (
insider_signals.count("bullish") * insider_weight +
news_signals.count("bullish") * news_weight
insider_signals.count("bullish") * insider_weight
+ news_signals.count("bullish") * news_weight
)
bearish_signals = (
insider_signals.count("bearish") * insider_weight +
news_signals.count("bearish") * news_weight
insider_signals.count("bearish") * insider_weight
+ news_signals.count("bearish") * news_weight
)

if bullish_signals > bearish_signals:
Expand All @@ -67,10 +77,15 @@ def sentiment_agent(state: AgentState):
overall_signal = "neutral"

# Calculate confidence level based on the weighted proportion
total_weighted_signals = len(insider_signals) * insider_weight + len(news_signals) * news_weight
total_weighted_signals = (
len(insider_signals) * insider_weight + len(news_signals) * news_weight
)
confidence = 0 # Default confidence when there are no signals
if total_weighted_signals > 0:
confidence = round(max(bullish_signals, bearish_signals) / total_weighted_signals, 2) * 100
confidence = (
round(max(bullish_signals, bearish_signals) / total_weighted_signals, 2)
* 100
)
reasoning = f"Weighted Bullish signals: {bullish_signals:.1f}, Weighted Bearish signals: {bearish_signals:.1f}"

sentiment_analysis[ticker] = {
Expand Down
Loading