trading-tools / config /models.py
Deploy Bot
Deploy Trading Analysis Platform to HuggingFace Spaces
a1bf219
"""LLM model configurations and workflow enums for enhanced reporting."""
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
from langchain_core.language_models import BaseChatModel
from utils.llm.provider_factory import LLMProviderFactory
# ============================================================================
# Investment Style and Phase Configuration (Feature 002)
# ============================================================================
class InvestmentStyle(str, Enum):
"""Investment style profiles that configure analysis emphasis and defaults."""
LONG_TERM = "long_term"
"""Long-term investment focus (quarters to years).
- Default timeframe: Weekly (1w)
- Analysis priority: Fundamental > Technical
- Review cycle: Quarterly
- Suitable timeframes: 1w, 1mo, 3mo, 1y, 5y
"""
SWING_TRADING = "swing_trading"
"""Swing trading focus (weeks to months).
- Default timeframe: Daily (1d)
- Analysis priority: Technical + Fundamental (balanced)
- Review cycle: Weekly
- Suitable timeframes: 1d, 1w, 1mo
"""
class AnalysisPhase(str, Enum):
"""Workflow phases that can be selectively executed."""
TECHNICAL = "technical"
"""Technical Analysis Phase - Indicators, patterns, trends (~15-20s)"""
FUNDAMENTAL = "fundamental"
"""Fundamental Analysis Phase - Financial metrics, valuation (~30-40s)"""
SENTIMENT = "sentiment"
"""Sentiment Analysis Phase - Social & news sentiment (~20-30s)"""
RESEARCH_SYNTHESIS = "research_synthesis"
"""Research Synthesis Phase - Integrated perspective (~15-20s)"""
RISK = "risk"
"""Risk Assessment Phase - Risk scores, volatility (~15-20s)"""
DECISION = "decision"
"""Portfolio Decision Phase - Final BUY/SELL/HOLD (~15-20s)"""
@dataclass
class PhaseConfiguration:
"""Configuration for phase-based workflow execution."""
investment_style: InvestmentStyle = InvestmentStyle.LONG_TERM
"""User's investment approach (long-term vs swing trading)."""
enabled_phases: List[AnalysisPhase] = field(
default_factory=lambda: [
AnalysisPhase.TECHNICAL,
AnalysisPhase.FUNDAMENTAL,
AnalysisPhase.SENTIMENT,
AnalysisPhase.RESEARCH_SYNTHESIS,
AnalysisPhase.RISK,
AnalysisPhase.DECISION,
]
)
"""List of workflow phases to execute."""
timeframe: str = "1w"
"""Selected analysis timeframe (1d, 1w, 1mo, 3mo, 1y, 5y)."""
chart_period_days: int = 180
"""Number of days of historical data to fetch for charts."""
educational_mode: bool = True
"""Whether to include plain-English explanations and tooltips."""
def validate(self) -> List[str]:
"""Validate configuration and return list of errors.
Returns:
List of validation error messages (empty if valid)
"""
errors = []
# At least one phase must be enabled
if not self.enabled_phases:
errors.append("At least one analysis phase must be enabled")
return errors
phases_set = set(self.enabled_phases)
analysis_phases = {
AnalysisPhase.TECHNICAL,
AnalysisPhase.FUNDAMENTAL,
AnalysisPhase.SENTIMENT,
}
# RESEARCH_SYNTHESIS requires at least one analysis phase
if AnalysisPhase.RESEARCH_SYNTHESIS in phases_set:
if not (analysis_phases & phases_set):
errors.append(
"Research Synthesis phase requires at least one analysis phase "
"(Technical, Fundamental, or Sentiment) to be enabled"
)
# RISK requires at least one analysis phase
if AnalysisPhase.RISK in phases_set:
if not (analysis_phases & phases_set):
errors.append(
"Risk Assessment phase requires at least one analysis phase "
"(Technical, Fundamental, or Sentiment) to be enabled"
)
# DECISION requires RESEARCH_SYNTHESIS
if AnalysisPhase.DECISION in phases_set:
if AnalysisPhase.RESEARCH_SYNTHESIS not in phases_set:
errors.append(
"Portfolio Decision phase requires Research Synthesis phase to be enabled"
)
return errors
@classmethod
def from_investment_style(cls, style: InvestmentStyle) -> "PhaseConfiguration":
"""Create configuration with defaults for investment style.
Args:
style: Investment style to configure for
Returns:
PhaseConfiguration with style-appropriate defaults
"""
if style == InvestmentStyle.LONG_TERM:
return cls(
investment_style=style,
timeframe="1w",
chart_period_days=365, # 1 year of data
enabled_phases=[
AnalysisPhase.TECHNICAL,
AnalysisPhase.FUNDAMENTAL,
AnalysisPhase.SENTIMENT,
AnalysisPhase.RESEARCH_SYNTHESIS,
AnalysisPhase.RISK,
AnalysisPhase.DECISION,
],
)
else: # SWING_TRADING
return cls(
investment_style=style,
timeframe="1d",
chart_period_days=90, # 3 months of data
enabled_phases=[
AnalysisPhase.TECHNICAL,
AnalysisPhase.FUNDAMENTAL,
AnalysisPhase.RESEARCH_SYNTHESIS,
AnalysisPhase.DECISION,
], # Skip SENTIMENT and RISK for faster execution
)
# ============================================================================
# LLM Model Configurations
# ============================================================================
# Per-agent LLM model configurations
# Format: {"provider": str, "model": str, "temperature": float}
AGENT_MODELS = {
# Technical Analysis Agents (User Story 1)
"indicator_agent": {
"provider": "openai",
"model": "gpt-4o-mini",
"temperature": 0.1,
},
"pattern_agent": {
"provider": "openai",
"model": "gpt-4o", # Needs vision for chart analysis
"temperature": 0.1,
},
"trend_agent": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.1,
},
"decision_agent": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.1,
},
# Fundamental Analysis Agents (User Story 2)
"fundamentals_agent": {
"provider": "openai",
"model": "gpt-4o-mini",
"temperature": 0.1,
},
"sentiment_agent": {
"provider": "openai",
"model": "gpt-4o-mini",
"temperature": 0.2,
},
"news_agent": {
"provider": "openai",
"model": "gpt-4o-mini",
"temperature": 0.1,
},
"technical_analyst": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.1,
},
# Management Agents (User Story 2)
"researcher_team": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.3, # Higher for debate/creativity
},
"risk_manager": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.1,
},
"portfolio_manager": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.1,
},
}
# Default models for each provider (used when provider is overridden but model is not)
DEFAULT_MODELS_BY_PROVIDER = {
"openai": "gpt-4o-mini",
"anthropic": "claude-3-5-sonnet-20241022",
"huggingface": "meta-llama/Llama-3.3-70B-Instruct", # Llama 3.3 available via HF Inference Providers
"qwen": "qwen2.5-72b-instruct",
}
def get_model(
agent_name: str,
routing_policy: Optional[str] = None,
runtime_config: Optional[dict] = None,
) -> BaseChatModel:
"""
Get configured LLM model for a specific agent.
Args:
agent_name: Name of the agent (e.g., "indicator_agent", "portfolio_manager")
routing_policy: Optional routing policy override for HuggingFace
(auto, fastest, cheapest, or provider name)
runtime_config: Optional runtime configuration with llm_provider/llm_model overrides
Returns:
Configured LangChain chat model
Raises:
ValueError: If agent_name is not found in configuration
"""
if agent_name not in AGENT_MODELS:
raise ValueError(
f"Agent '{agent_name}' not found in model configuration. "
f"Available agents: {', '.join(AGENT_MODELS.keys())}"
)
config = AGENT_MODELS[agent_name]
# Apply runtime overrides if provided
if runtime_config:
provider = runtime_config.get("llm_provider", config["provider"])
# If provider is overridden but model is not specified, use default model for that provider
if "llm_provider" in runtime_config and "llm_model" not in runtime_config:
model = DEFAULT_MODELS_BY_PROVIDER.get(provider, config["model"])
else:
model = runtime_config.get("llm_model", config["model"])
else:
provider = config["provider"]
model = config["model"]
# Use routing_policy from config if not overridden
if routing_policy is None:
routing_policy = config.get("routing_policy")
return LLMProviderFactory.create(
provider=provider,
model=model,
temperature=config["temperature"],
routing_policy=routing_policy,
)
# ============================================================================
# Valuation Dashboard Models (Feature 004)
# ============================================================================
class ChartType(str, Enum):
"""Type of fundamental chart in the valuation dashboard."""
PE_RATIO = "PE_RATIO"
"""Price-to-Earnings ratio over time"""
PB_RATIO = "PB_RATIO"
"""Price-to-Book ratio over time"""
PS_RATIO = "PS_RATIO"
"""Price-to-Sales ratio over time"""
EV_EBITDA = "EV_EBITDA"
"""EV/EBITDA ratio over time"""
PROFIT_MARGINS = "PROFIT_MARGINS"
"""Multi-line chart (Gross, Operating, Net margins)"""
ROE = "ROE"
"""Return on Equity over time"""
REVENUE_EARNINGS_GROWTH = "REVENUE_EARNINGS_GROWTH"
"""YoY growth rates for revenue and earnings"""
FREE_CASH_FLOW = "FREE_CASH_FLOW"
"""Free cash flow over time"""
DEBT_TO_EQUITY = "DEBT_TO_EQUITY"
"""Debt-to-Equity ratio over time"""
class DataAvailability(str, Enum):
"""Status of data completeness for a chart."""
COMPLETE = "complete"
"""All data available, chart rendered successfully"""
PARTIAL = "partial"
"""Some data missing, chart rendered with gaps"""
UNAVAILABLE = "unavailable"
"""No data available, placeholder chart shown"""
ERROR = "error"
"""Error occurred during data fetch or calculation"""
@dataclass
class DataPoint:
"""Single (timestamp, value) pair in a time series."""
timestamp: str # ISO format datetime string
value: Optional[float] # None if unavailable for this period
@dataclass
class DataSeries:
"""Time-series data for a single line on a chart."""
name: str # Series name (e.g., "Gross Margin")
data_points: List[DataPoint]
data_frequency: str # "quarterly", "annual", "monthly"
color: Optional[str] = None # Hex color code (e.g., "#2962FF")
line_style: Optional[str] = None # Line style ("-", "--", ":")
@dataclass
class ChartData:
"""Represents a single fundamental metric chart."""
chart_type: ChartType
title: str
data_series: List[DataSeries]
x_label: str
y_label: str
unit: str # "ratio", "%", "USD millions"
data_availability: DataAvailability
file_path: Optional[str] = None # Path to generated chart PNG
legend_enabled: bool = True
error_message: Optional[str] = None
rendering_hint: Optional[str] = None
@dataclass
class GridConfig:
"""Grid dimensions for a specific screen size."""
columns: int
rows: int
min_width: Optional[int] = None # Minimum column width in pixels
@dataclass
class DashboardLayout:
"""Configuration for responsive grid layout."""
desktop_config: GridConfig = field(
default_factory=lambda: GridConfig(columns=2, rows=4, min_width=300)
)
tablet_config: GridConfig = field(
default_factory=lambda: GridConfig(columns=2, rows=4, min_width=300)
)
mobile_config: GridConfig = field(
default_factory=lambda: GridConfig(columns=1, rows=8)
)
@dataclass
class ValuationDashboard:
"""Container for the complete set of fundamental analysis charts."""
ticker: str
start_date: str # ISO format datetime string
end_date: str # ISO format datetime string
charts: List[ChartData]
layout_config: DashboardLayout
generation_timestamp: str # ISO format datetime string
data_source: str # "yfinance", "alpha_vantage", "hybrid"
@dataclass
class ValuationMetrics:
"""Valuation ratio data for charts and analysis."""
pe_ratio: Optional[float] = None # None if negative earnings
pb_ratio: Optional[float] = None # None if negative equity
ps_ratio: Optional[float] = None
ev_ebitda: Optional[float] = None # None if negative EBITDA
peg_ratio: Optional[float] = None
market_cap: Optional[float] = None # USD millions
@dataclass
class ProfitabilityMetrics:
"""Profitability and efficiency data."""
gross_margin: Optional[float] = None # Percentage (0-100)
operating_margin: Optional[float] = None # Percentage (0-100)
net_margin: Optional[float] = None # Percentage (0-100)
roe: Optional[float] = None # Percentage, None if equity <= 0
@dataclass
class GrowthMetrics:
"""Year-over-year growth rates."""
revenue_growth_yoy: Optional[float] = None # Percentage
earnings_growth_yoy: Optional[float] = None # Percentage
@dataclass
class CashFlowMetrics:
"""Cash flow data."""
free_cash_flow: Optional[float] = None # USD millions
operating_cash_flow: Optional[float] = None # USD millions
capex: Optional[float] = None # USD millions
@dataclass
class LeverageMetrics:
"""Financial leverage and debt data."""
debt_to_equity: Optional[float] = None # None if equity <= 0
total_debt: Optional[float] = None # USD millions
total_equity: Optional[float] = None # USD millions
@dataclass
class BalanceSheet:
"""Balance sheet data for agent tables."""
total_assets: Optional[float] = None # USD millions
total_liabilities: Optional[float] = None # USD millions
total_equity: Optional[float] = None # USD millions
working_capital: Optional[float] = None # USD millions
cash_and_equivalents: Optional[float] = None # USD millions
retained_earnings: Optional[float] = None # USD millions
@dataclass
class IncomeStatement:
"""Income statement data for agent tables."""
total_revenue: Optional[float] = None # USD millions
gross_profit: Optional[float] = None # USD millions
net_income: Optional[float] = None # USD millions
ebitda: Optional[float] = None # USD millions
basic_eps: Optional[float] = None # USD
@dataclass
class CashFlowStatement:
"""Cash flow statement data for agent tables."""
operating_cash_flow: Optional[float] = None # USD millions
free_cash_flow: Optional[float] = None # USD millions
capex: Optional[float] = None # USD millions
@dataclass
class FinancialStatements:
"""Complete financial statement data."""
balance_sheet: BalanceSheet
income_statement: IncomeStatement
cash_flow_statement: CashFlowStatement
@dataclass
class FundamentalMetrics:
"""Comprehensive collection of fundamental data."""
ticker: str
as_of_date: str # ISO format datetime string
valuation: ValuationMetrics
profitability: ProfitabilityMetrics
growth: GrowthMetrics
cash_flow: CashFlowMetrics
leverage: LeverageMetrics
financial_statements: FinancialStatements
data_sources: dict # Mapping of metric → data source used
@dataclass
class ContentSection:
"""Hierarchical section in agent response."""
heading: str # Markdown heading (e.g., "## Company Overview")
level: int # Heading level (1-4)
content: str # Markdown-formatted content
subsections: List["ContentSection"] = field(default_factory=list)
@dataclass
class DataTable:
"""Formatted table presenting data."""
headers: List[str]
rows: List[List[str]] # Each row is list of cell values
title: Optional[str] = None
format_hints: Optional[dict] = None # Column formatting hints
@dataclass
class ConclusionBlock:
"""Final recommendation or synthesis."""
heading: str # e.g., "Transaction Proposal"
recommendation: str # Clear actionable recommendation
rationale: str # Explanation for the recommendation
supporting_data: Optional[DataTable] = None
@dataclass
class AgentResponseFormat:
"""Structured format for agent responses."""
sections: List[ContentSection]
key_insights: List[str] # Bullet-pointed insights
summary: List[str] # Numbered summary points
conclusion: ConclusionBlock
data_tables: List[DataTable] = field(default_factory=list)