Financial Analysis Agent
What is a Financial Advisor Agent?
Financial advisor agents analyze market data, portfolio holdings, and economic indicators to provide personalized investment guidance. They combine quantitative analysis with natural language generation to explain complex financial concepts in accessible terms.
The key components are market data retrieval (prices, volumes, fundamentals), portfolio analysis (allocation, diversification, performance), risk assessment (volatility, drawdown, VaR), and advice generation (buy/hold/sell recommendations with rationale).
These agents democratize financial advice, providing institutional-quality analysis to individual investors while maintaining appropriate disclaimers about investment risk.
Project Overview
We will build a financial analysis agent that:
- Fetches real-time market data via APIs
- Analyzes portfolio composition and performance
- Calculates risk metrics (Sharpe ratio, VaR, beta)
- Generates personalized investment advice
- Creates performance reports and visualizations
- Monitors market news for impact analysis
Expected outcome: An agent that provides data-driven financial analysis and advice.
Difficulty: Advanced (requires understanding of finance, statistics, and data visualization)
Architecture
Tools & Setup
| Tool | Version | Purpose |
|---|---|---|
| Python | 3.11+ | Core language |
| yfinance | 0.2+ | Market data |
| pandas | 2.0+ | Data manipulation |
| numpy | 1.24+ | Numerical computing |
| matplotlib | 3.8+ | Visualization |
| openai | 1.0+ | LLM backbone |
Step 1: Environment Setup
python -m venv venv
source venv/bin/activate
pip install yfinance pandas numpy matplotlib openai
export OPENAI_API_KEY="sk-your-key"
Step 2: Project Structure
finance-agent/
βββ data/
β βββ __init__.py
β βββ market_data.py
βββ analysis/
β βββ __init__.py
β βββ portfolio.py
β βββ risk.py
βββ reporting/
β βββ __init__.py
β βββ report_generator.py
βββ agent.py
βββ main.py
Step 3: Market Data Fetcher
# data/market_data.py
import yfinance as yf
import pandas as pd
from typing import Dict, List
from datetime import datetime, timedelta
class MarketDataFetcher:
def get_stock_info(self, symbol: str) -> Dict:
stock = yf.Ticker(symbol)
info = stock.info
return {
"symbol": symbol,
"name": info.get("longName", symbol),
"price": info.get("currentPrice", 0),
"change": info.get("regularMarketChangePercent", 0),
"market_cap": info.get("marketCap", 0),
"pe_ratio": info.get("trailingPE"),
"dividend_yield": info.get("dividendYield"),
"52w_high": info.get("fiftyTwoWeekHigh"),
"52w_low": info.get("fiftyTwoWeekLow"),
"volume": info.get("volume", 0),
}
def get_historical(
self, symbol: str, period: str = "1y"
) -> pd.DataFrame:
stock = yf.Ticker(symbol)
return stock.history(period=period)
def get_multiple(
self, symbols: List[str], period: str = "1y"
) -> Dict[str, pd.DataFrame]:
return {s: self.get_historical(s, period) for s in symbols}
def get_market_overview(self) -> Dict:
indices = {
"^GSPC": "S&P 500",
"^DJI": "Dow Jones",
"^IXIC": "NASDAQ",
}
overview = {}
for symbol, name in indices.items():
stock = yf.Ticker(symbol)
info = stock.info
overview[name] = {
"price": info.get("regularMarketPrice", 0),
"change": info.get("regularMarketChangePercent", 0),
}
return overview
Step 4: Portfolio and Risk Analysis
# analysis/portfolio.py
import pandas as pd
import numpy as np
from typing import Dict, List
class PortfolioAnalyzer:
def analyze(
self,
holdings: Dict[str, float],
prices: Dict[str, pd.Series],
) -> Dict:
total_value = sum(
holdings[symbol] * prices[symbol].iloc[-1]
for symbol in holdings
)
weights = {
s: (holdings[s] * prices[s].iloc[-1]) / total_value
for s in holdings
}
returns = pd.DataFrame({
s: prices[s].pct_change() for s in holdings
}).dropna()
portfolio_returns = sum(
returns[s] * weights[s] for s in holdings
)
cumulative = (1 + portfolio_returns).cumprod()
total_return = (cumulative.iloc[-1] - 1) * 100
annualized = (
(1 + total_return / 100) ** (252 / len(returns)) - 1
) * 100
return {
"total_value": total_value,
"weights": weights,
"total_return_pct": round(total_return, 2),
"annualized_return_pct": round(annualized, 2),
"daily_returns": portfolio_returns,
"holdings": {
s: {
"value": holdings[s] * prices[s].iloc[-1],
"weight": weights[s],
}
for s in holdings
},
}
def diversification_score(self, weights: Dict[str, float]) -> float:
hhi = sum(w**2 for w in weights.values())
return 1 - hhi
# analysis/risk.py
import pandas as pd
import numpy as np
from typing import Dict
class RiskAnalyzer:
def calculate_metrics(
self,
returns: pd.Series,
risk_free_rate: float = 0.05,
) -> Dict:
daily_rf = risk_free_rate / 252
excess = returns - daily_rf
sharpe = np.sqrt(252) * excess.mean() / excess.std() if excess.std() > 0 else 0
var_95 = np.percentile(returns, 5)
cvar_95 = returns[returns <= var_95].mean()
cumulative = (1 + returns).cumprod()
running_max = cumulative.cummax()
drawdown = (cumulative - running_max) / running_max
max_drawdown = drawdown.min()
volatility = returns.std() * np.sqrt(252)
return {
"sharpe_ratio": round(sharpe, 3),
"var_95_daily": round(var_95 * 100, 2),
"cvar_95_daily": round(cvar_95 * 100, 2),
"max_drawdown_pct": round(max_drawdown * 100, 2),
"annualized_volatility_pct": round(volatility * 100, 2),
"total_risk_score": self._risk_score(sharpe, max_drawdown, volatility),
}
def _risk_score(
self, sharpe: float, max_dd: float, vol: float
) -> str:
score = 0
if sharpe > 1:
score += 3
elif sharpe > 0.5:
score += 2
elif sharpe > 0:
score += 1
if abs(max_dd) < 0.1:
score += 3
elif abs(max_dd) < 0.2:
score += 2
elif abs(max_dd) < 0.3:
score += 1
if vol < 0.15:
score += 2
elif vol < 0.25:
score += 1
labels = {0: "Very High", 1: "High", 2: "Moderate-High", 3: "Moderate", 4: "Moderate-Low", 5: "Low", 6: "Very Low", 8: "Conservative"}
return labels.get(score, "Moderate")
def beta(
self,
stock_returns: pd.Series,
market_returns: pd.Series,
) -> float:
covariance = np.cov(stock_returns, market_returns)[0][1]
market_variance = np.var(market_returns)
return covariance / market_variance if market_variance > 0 else 0
Step 5: Report Generator and Agent
# reporting/report_generator.py
from openai import OpenAI
from typing import Dict
class ReportGenerator:
def __init__(self, model: str = "gpt-4-turbo-preview"):
self.client = OpenAI()
self.model = model
def generate_report(
self,
portfolio: Dict,
risk: Dict,
market: Dict,
) -> str:
prompt = f"""Generate a professional investment report based on:
Portfolio Performance:
- Total Value: ${portfolio['total_value']:,.2f}
- Total Return: {portfolio['total_return_pct']}%
- Annualized Return: {portfolio['annualized_return_pct']}%
Risk Metrics:
- Sharpe Ratio: {risk['sharpe_ratio']}
- Max Drawdown: {risk['max_drawdown_pct']}%
- Volatility: {risk['annualized_volatility_pct']}%
- Risk Score: {risk['total_risk_score']}
Market Overview:
{market}
Provide:
1. Executive Summary
2. Performance Analysis
3. Risk Assessment
4. Market Context
5. Recommendations
Use professional financial language with appropriate disclaimers."""
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "You are a CFA charterholder writing investment reports."},
{"role": "user", "content": prompt},
],
temperature=0.3,
)
return response.choices[0].message.content
# agent.py
from data.market_data import MarketDataFetcher
from analysis.portfolio import PortfolioAnalyzer
from analysis.risk import RiskAnalyzer
from reporting.report_generator import ReportGenerator
from typing import Dict
class FinancialAdvisorAgent:
def __init__(self, model: str = "gpt-4-turbo-preview"):
self.market = MarketDataFetcher()
self.portfolio_analyzer = PortfolioAnalyzer()
self.risk_analyzer = RiskAnalyzer()
self.report_gen = ReportGenerator(model)
def analyze_portfolio(self, holdings: Dict[str, float]) -> Dict:
symbols = list(holdings.keys())
prices = self.market.get_multiple(symbols)
port = self.portfolio_analyzer.analyze(holdings, prices)
risk = self.risk_analyzer.calculate_metrics(port["daily_returns"])
market = self.market.get_market_overview()
return {
"portfolio": port,
"risk": risk,
"market": market,
}
def get_advice(
self, holdings: Dict[str, float], question: str
) -> str:
analysis = self.analyze_portfolio(holdings)
prompt = f"""Based on this portfolio analysis:
Portfolio: ${analysis['portfolio']['total_value']:,.2f}
Return: {analysis['portfolio']['total_return_pct']}%
Sharpe: {analysis['risk']['sharpe_ratio']}
Risk Score: {analysis['risk']['total_risk_score']}
User question: {question}
Provide specific, actionable advice with rationale. Include appropriate disclaimers."""
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "system", "content": "You are a financial advisor. Provide educational analysis, not personalized investment advice."},
{"role": "user", "content": prompt},
],
temperature=0.5,
)
return response.choices[0].message.content
Mathematical Foundation
Sharpe Ratio:
Where each parameter means:
- β portfolio return
- β risk-free rate
- β portfolio volatility
Intuition: Measures excess return per unit of risk. Higher Sharpe indicates better risk-adjusted returns.
Value at Risk (VaR):
Where is the z-score for confidence level .
Intuition: Maximum expected loss over a given time period at a confidence level.
Maximum Drawdown:
Intuition: Largest peak-to-trough decline, measuring worst-case loss.
Testing & Evaluation
import pytest
from analysis.risk import RiskAnalyzer
from analysis.portfolio import PortfolioAnalyzer
import pandas as pd
import numpy as np
def test_sharpe_ratio():
analyzer = RiskAnalyzer()
returns = pd.Series(np.random.normal(0.001, 0.02, 252))
metrics = analyzer.calculate_metrics(returns)
assert "sharpe_ratio" in metrics
def test_diversification():
analyzer = PortfolioAnalyzer()
score = analyzer.diversification_score({"A": 0.5, "B": 0.5})
assert score == 0.5
score = analyzer.diversification_score({"A": 1.0})
assert score == 0.0
Performance Metrics
| Metric | Value | Notes |
|---|---|---|
| Data Fetch Time | 1-3s | Per stock symbol |
| Portfolio Analysis | 100-500ms | Depends on holdings |
| Report Generation | 5-10s | GPT-4 with context |
| Market Coverage | 5000+ | US stocks |
| Historical Data | 10 years | Daily granularity |
Deployment
# main.py
from agent import FinancialAdvisorAgent
import json
def main():
agent = FinancialAdvisorAgent()
holdings = {"AAPL": 100, "GOOGL": 50, "MSFT": 75}
print("Financial Advisor Agent\n")
cmd = input("Command (analyze/advice/report): ").strip()
if cmd == "analyze":
result = agent.analyze_portfolio(holdings)
print(json.dumps(result["risk"], indent=2))
if __name__ == "__main__":
main()
Real-World Use Cases
- Personal Finance: Portfolio tracking and rebalancing advice
- Wealth Management: Client portfolio analysis and reporting
- Robo-Advisory: Automated investment recommendations
- Risk Management: Institutional portfolio risk monitoring
- Financial Planning: Retirement and goal-based planning
Common Pitfalls & Solutions
| Pitfall | Solution |
|---|---|
| Data quality issues | Validate data sources, handle missing values |
| Survivorship bias | Include delisted stocks in historical analysis |
| Look-ahead bias | Use only data available at decision time |
| Overfitting | Out-of-sample testing, regularization |
| Market regime changes | Adaptive models, regime detection |
Summary with Key Takeaways
- Market data integration enables real-time analysis
- Portfolio analysis provides quantified performance metrics
- Risk metrics (Sharpe, VaR, drawdown) quantify investment risk
- LLM-generated reports make complex analysis accessible
- Always include appropriate investment disclaimers