Spaces:
Sleeping
Sleeping
| """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)""" | |
| 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 | |
| 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""" | |
| 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 | |
| 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 ("-", "--", ":") | |
| 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 | |
| class GridConfig: | |
| """Grid dimensions for a specific screen size.""" | |
| columns: int | |
| rows: int | |
| min_width: Optional[int] = None # Minimum column width in pixels | |
| 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) | |
| ) | |
| 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" | |
| 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 | |
| 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 | |
| class GrowthMetrics: | |
| """Year-over-year growth rates.""" | |
| revenue_growth_yoy: Optional[float] = None # Percentage | |
| earnings_growth_yoy: Optional[float] = None # Percentage | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| class FinancialStatements: | |
| """Complete financial statement data.""" | |
| balance_sheet: BalanceSheet | |
| income_statement: IncomeStatement | |
| cash_flow_statement: CashFlowStatement | |
| 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 | |
| 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) | |
| 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 | |
| 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 | |
| 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) | |