diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000..324a1418 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,299 @@ +# Implementation Plan: Medium-Term Positioning Upgrade + +## Lead Architect Overview + +Four objectives to upgrade TradingAgents for medium-term (1–3 month) positioning: +1. **Agentic Debate** — Increase debate rounds to 2–3 +2. **Fundamental Data** — Extend look-back to 8 quarters with TTM trend computation +3. **Relative Performance** — Sector & peer comparison tools +4. **Macro Regime Flag** — Classify market as risk-on / risk-off / transition + +--- + +## Step 1: Agentic Debate — Increase Rounds (Architect + API Integrator) + +**Assigned to: API Integrator Agent** +**Risk: LOW** — Config-only change, conditional logic already supports arbitrary round counts. + +### Changes: +- **File:** `tradingagents/default_config.py` + - Change `"max_debate_rounds": 1` → `"max_debate_rounds": 2` + - Change `"max_risk_discuss_rounds": 1` → `"max_risk_discuss_rounds": 2` + +- **File:** `tradingagents/graph/trading_graph.py` (line 146) + - Pass config values to `ConditionalLogic`: + ```python + self.conditional_logic = ConditionalLogic( + max_debate_rounds=self.config.get("max_debate_rounds", 2), + max_risk_discuss_rounds=self.config.get("max_risk_discuss_rounds", 2), + ) + ``` + - **NOTE:** Currently `ConditionalLogic()` is called with no args, so it uses its own defaults of 1. The config values are never actually wired in. This is a bug fix. + +### Verification: +- Investment debate: count threshold = `2 * 2 = 4` → Bull speaks 2×, Bear speaks 2× before judge +- Risk debate: count threshold = `3 * 2 = 6` → Each of 3 analysts speaks 2× before judge +- `max_recur_limit` of 100 is sufficient (was 100, worst case ~20 graph steps) + +--- + +## Step 2: Fundamental Data — 8-Quarter TTM Trend (API Integrator + Economist) + +**Assigned to: API Integrator (data layer) + Economist (TTM computation logic)** +**Risk: MEDIUM** — Requires new data tool + prompt update + TTM computation module. + +### 2A: New TTM Computation Module (Economist Agent) + +- **New file:** `tradingagents/dataflows/ttm_analysis.py` + - `compute_ttm_metrics(income_df, balance_df, cashflow_df) -> dict` + - Sum last 4 quarters of income stmt for flow items (Revenue, Net Income, EBITDA, Operating Income, Gross Profit) + - Use latest quarter for balance sheet (stock items: Total Assets, Total Debt, Equity) + - Compute key ratios: Revenue Growth (QoQ and YoY), Margin trends (Gross, Operating, Net), ROE trend, Debt/Equity trend, FCF trend + - `format_ttm_report(metrics: dict, ticker: str) -> str` + - Markdown report with 8-quarter trend table + TTM summary + quarter-over-quarter trajectory + +### 2B: New Tool — `get_ttm_analysis` (API Integrator Agent) + +- **File:** `tradingagents/agents/utils/fundamental_data_tools.py` + - Add new `@tool` function `get_ttm_analysis(ticker, curr_date) -> str` + - Internally calls existing vendor-routed `get_income_statement`, `get_balance_sheet`, `get_cashflow` with `freq="quarterly"` + - Passes raw data to `compute_ttm_metrics()` and `format_ttm_report()` + +- **File:** `tradingagents/agents/utils/agent_utils.py` + - Export `get_ttm_analysis` tool + +### 2C: Update Fundamentals Analyst Prompt (Economist Agent) + +- **File:** `tradingagents/agents/analysts/fundamentals_analyst.py` + - Add `get_ttm_analysis` to tools list + - Update system prompt from "past week" to: + > "You are a researcher tasked with analyzing fundamental information covering the last 8 quarters (2 years) for a company. First call `get_ttm_analysis` to obtain a Trailing Twelve Months (TTM) trend report including revenue growth, margin trajectories, and key ratio trends. Then supplement with `get_fundamentals` for the latest snapshot. Write a comprehensive report covering multi-quarter trends, not just the most recent filing." + +- **File:** `tradingagents/graph/trading_graph.py` — `_create_tool_nodes()` + - Add `get_ttm_analysis` to the `"fundamentals"` ToolNode + +### 2D: Data Layer — Ensure 8 Quarters Available + +- **yfinance:** `ticker.quarterly_income_stmt` returns up to 5 quarters. To get 8, we need to combine quarterly + annual or make 2 calls. Actually, yfinance returns the last 4-5 quarters by default. We'll need to fetch 2+ years of data. + - Approach: Call `ticker.quarterly_income_stmt` which returns available quarters (typically 4-5). Also call `ticker.income_stmt` (annual) for older periods. Combine to reconstruct 8 quarters. + - **Alternative (preferred):** yfinance `ticker.get_income_stmt(freq="quarterly", as_dict=False)` can return more data. Test this. + - **Fallback:** Alpha Vantage INCOME_STATEMENT endpoint returns up to 20 quarterly reports — use this as the configured vendor for TTM. + +- **File:** `tradingagents/default_config.py` + - Add to `tool_vendors`: `"get_ttm_analysis": "alpha_vantage,yfinance"` to prefer Alpha Vantage for richer quarterly history + +### Data Source Assessment: +| Source | Quarters Available | Notes | +|--------|-------------------|-------| +| yfinance `quarterly_income_stmt` | 4-5 | Limited but free | +| Alpha Vantage `INCOME_STATEMENT` | Up to 20 quarterly | Best option, needs API key | +| Alpha Vantage `BALANCE_SHEET` | Up to 20 quarterly | Same | +| Alpha Vantage `CASH_FLOW` | Up to 20 quarterly | Same | + +--- + +## Step 3: Relative Performance — Sector & Peer Comparison (API Integrator + Economist) + +**Assigned to: API Integrator (tools) + Economist (comparison logic)** +**Risk: MEDIUM** — New tools leveraging existing scanner infrastructure. + +### 3A: New Peer Comparison Module (Economist Agent) + +- **New file:** `tradingagents/dataflows/peer_comparison.py` + - `get_sector_peers(ticker) -> list[str]` + - Use yfinance `Ticker.info["sector"]` to identify sector + - Return top 5-8 peers from same sector (use existing `_SECTOR_TICKERS` mapping from `alpha_vantage_scanner.py`, or yfinance Sector.top_companies) + - `compute_relative_performance(ticker, peers, period="6mo") -> str` + - Download price history for ticker + peers via `yf.download()` + - Compute: 1-week, 1-month, 3-month, 6-month returns for each + - Rank ticker among peers + - Compute ticker's alpha vs sector ETF + - Return markdown table with relative positioning + +### 3B: New Tools — `get_peer_comparison` and `get_sector_relative` (API Integrator) + +- **File:** `tradingagents/agents/utils/fundamental_data_tools.py` + - `get_peer_comparison(ticker, curr_date) -> str` — @tool + - Calls `get_sector_peers()` and `compute_relative_performance()` + - Returns ranked peer table with ticker highlighted + - `get_sector_relative(ticker, curr_date) -> str` — @tool + - Compares ticker vs its sector ETF over multiple time frames + - Returns outperformance/underperformance metrics + +- **File:** `tradingagents/agents/utils/agent_utils.py` + - Export both new tools + +### 3C: Wire Into Fundamentals Analyst (API Integrator) + +- **File:** `tradingagents/agents/analysts/fundamentals_analyst.py` + - Add `get_peer_comparison` and `get_sector_relative` to tools list + - Update prompt to instruct: "Also analyze how the company performs relative to sector peers and its sector ETF benchmark over 1-week, 1-month, 3-month, and 6-month periods." + +- **File:** `tradingagents/graph/trading_graph.py` — `_create_tool_nodes()` + - Add both tools to `"fundamentals"` ToolNode + +### 3D: Vendor Routing + +- These tools use yfinance directly (no Alpha Vantage endpoint for peer comparison) +- No vendor routing needed — direct yfinance calls inside the module +- Register in `TOOLS_CATEGORIES` under `"fundamental_data"` for consistency + +--- + +## Step 4: Macro Regime Flag (Economist Agent) + +**Assigned to: Economist Agent** +**Risk: MEDIUM** — New module + new state field + integration into Research Manager. + +### 4A: Macro Regime Classifier Module (Economist) + +- **New file:** `tradingagents/dataflows/macro_regime.py` + - `classify_macro_regime(curr_date: str = None) -> dict` + - Returns: `{"regime": "risk-on"|"risk-off"|"transition", "confidence": float, "signals": dict, "summary": str}` + - Signal sources (all via yfinance — free, no API key needed): + 1. **VIX level**: `yf.Ticker("^VIX")` → <16 risk-on, 16-25 transition, >25 risk-off + 2. **VIX trend**: 5-day vs 20-day SMA — rising = risk-off signal + 3. **Credit spread proxy**: `yf.Ticker("HYG")` vs `yf.Ticker("LQD")` — HYG/LQD ratio declining = risk-off + 4. **Yield curve proxy**: `yf.Ticker("TLT")` (20yr) vs `yf.Ticker("SHY")` (1-3yr) — TLT outperforming = risk-off (flight to safety) + 5. **Market breadth**: S&P 500 (`^GSPC`) above/below 200-SMA + 6. **Sector rotation signal**: Defensive sectors (XLU, XLP, XLV) outperforming cyclicals (XLY, XLK, XLI) = risk-off + - Scoring: Each signal contributes -1 (risk-off), 0 (neutral), or +1 (risk-on). Aggregate: + - Sum >= 3: "risk-on" + - Sum <= -3: "risk-off" + - Otherwise: "transition" + - `format_macro_report(regime_data: dict) -> str` + - Markdown report with signal breakdown, regime classification, and confidence level + +### 4B: New Tool — `get_macro_regime` (API Integrator) + +- **File:** `tradingagents/agents/utils/fundamental_data_tools.py` (or new `macro_tools.py`) + - `get_macro_regime(curr_date) -> str` — @tool + - Calls `classify_macro_regime()` and `format_macro_report()` + +- **File:** `tradingagents/agents/utils/agent_utils.py` + - Export `get_macro_regime` + +### 4C: Add Macro Regime to Agent State + +- **File:** `tradingagents/agents/utils/agent_states.py` + - Add to `AgentState`: + ```python + macro_regime_report: Annotated[str, "Macro regime classification (risk-on/risk-off/transition)"] + ``` + +### 4D: Wire Into Market Analyst (API Integrator) + +- **File:** `tradingagents/agents/analysts/market_analyst.py` + - Add `get_macro_regime` to tools list + - Update prompt to include: "Before analyzing individual stock technicals, call `get_macro_regime` to determine the current market environment (risk-on, risk-off, or transition). Interpret all subsequent technical signals through this macro lens." + - Return `macro_regime_report` in output dict + +- **File:** `tradingagents/graph/trading_graph.py` — `_create_tool_nodes()` + - Add `get_macro_regime` to `"market"` ToolNode + +### 4E: Feed Macro Regime Into Downstream Agents + +- **File:** `tradingagents/agents/managers/research_manager.py` + - Add `macro_regime_report` to the `curr_situation` string that gets passed to the judge + - Update prompt to reference macro regime in decision-making + +- **File:** `tradingagents/agents/managers/risk_manager.py` + - Include macro regime context in risk assessment prompt + +- **File:** `tradingagents/graph/trading_graph.py` — `_log_state()` + - Add `macro_regime_report` to logged state + +--- + +## Step 5: Integration Tests (Tester Agent) + +**Assigned to: Tester Agent** + +### 5A: Test Debate Rounds — `tests/test_debate_rounds.py` +- Test `ConditionalLogic` with `max_debate_rounds=2`: + - Verify bull/bear alternate correctly for 4 turns + - Verify routing to "Research Manager" after count >= 4 +- Test `ConditionalLogic` with `max_risk_discuss_rounds=2`: + - Verify aggressive→conservative→neutral rotation for 6 turns + - Verify routing to "Risk Judge" after count >= 6 +- Test config values are properly wired from `TradingAgentsGraph` config to `ConditionalLogic` + +### 5B: Test TTM Analysis — `tests/test_ttm_analysis.py` +- Unit test `compute_ttm_metrics()` with mock 8-quarter DataFrames + - Verify TTM revenue = sum of last 4 quarters + - Verify margin calculations + - Verify QoQ and YoY growth rates +- Unit test `format_ttm_report()` output contains expected sections +- Integration test `get_ttm_analysis` tool with real ticker (mark `@pytest.mark.integration`) + +### 5C: Test Peer Comparison — `tests/test_peer_comparison.py` +- Unit test `get_sector_peers()` returns valid tickers for known sectors +- Unit test `compute_relative_performance()` with mock price data + - Verify correct return calculations + - Verify ranking logic +- Integration test with real ticker (mark `@pytest.mark.integration`) + +### 5D: Test Macro Regime — `tests/test_macro_regime.py` +- Unit test `classify_macro_regime()` with mocked yfinance data: + - All risk-on signals → "risk-on" + - All risk-off signals → "risk-off" + - Mixed signals → "transition" +- Unit test `format_macro_report()` output format +- Unit test scoring edge cases (VIX at boundaries, missing data gracefully handled) +- Integration test with real market data (mark `@pytest.mark.integration`) + +### 5E: Test Config Wiring — `tests/test_config_wiring.py` +- Test that `TradingAgentsGraph(config={...})` properly passes debate rounds to `ConditionalLogic` +- Test that new tools appear in the correct ToolNodes +- Test that new state fields exist in `AgentState` + +--- + +## File Change Summary + +| File | Action | Objective | +|------|--------|-----------| +| `tradingagents/default_config.py` | EDIT | #1 debate rounds, #2 tool vendor | +| `tradingagents/graph/trading_graph.py` | EDIT | #1 wire config, #2/#3/#4 add tools to ToolNodes, #4 log macro | +| `tradingagents/graph/conditional_logic.py` | NO CHANGE | Already supports arbitrary rounds | +| `tradingagents/agents/utils/agent_states.py` | EDIT | #4 add macro_regime_report | +| `tradingagents/agents/analysts/fundamentals_analyst.py` | EDIT | #2/#3 new tools + prompt | +| `tradingagents/agents/analysts/market_analyst.py` | EDIT | #4 macro regime tool + prompt | +| `tradingagents/agents/managers/research_manager.py` | EDIT | #4 include macro in decision | +| `tradingagents/agents/managers/risk_manager.py` | EDIT | #4 include macro in risk assessment | +| `tradingagents/agents/utils/fundamental_data_tools.py` | EDIT | #2/#3 new tool functions | +| `tradingagents/agents/utils/agent_utils.py` | EDIT | #2/#3/#4 export new tools | +| `tradingagents/agents/__init__.py` | NO CHANGE | Tools don't need agent-level export | +| `tradingagents/dataflows/ttm_analysis.py` | NEW | #2 TTM computation | +| `tradingagents/dataflows/peer_comparison.py` | NEW | #3 peer comparison logic | +| `tradingagents/dataflows/macro_regime.py` | NEW | #4 macro regime classifier | +| `tradingagents/dataflows/interface.py` | EDIT | #2/#3 register new tools in TOOLS_CATEGORIES | +| `tests/test_debate_rounds.py` | NEW | #1 tests | +| `tests/test_ttm_analysis.py` | NEW | #2 tests | +| `tests/test_peer_comparison.py` | NEW | #3 tests | +| `tests/test_macro_regime.py` | NEW | #4 tests | +| `tests/test_config_wiring.py` | NEW | Integration wiring tests | + +--- + +## Execution Order + +1. **Step 1** (Debate Rounds) — Independent, can start immediately +2. **Step 4A** (Macro Regime Module) — Independent, can start in parallel +3. **Step 2A** (TTM Module) — Independent, can start in parallel +4. **Step 3A** (Peer Comparison Module) — Independent, can start in parallel +5. **Step 2B–2D** (TTM Integration) — Depends on 2A +6. **Step 3B–3D** (Peer Integration) — Depends on 3A +7. **Step 4B–4E** (Macro Integration) — Depends on 4A +8. **Step 5** (All Tests) — Depends on all above + +Steps 1, 2A, 3A, 4A can all run in parallel. + +--- + +## Risk Mitigation + +- **yfinance quarterly data limit:** If yfinance returns <8 quarters, TTM module gracefully computes with available data and notes the gap. Alpha Vantage fallback provides full 20 quarters. +- **New state field (macro_regime_report):** Default empty string. All existing agents that don't produce it will leave it empty — no reducer conflicts. +- **Rate limits:** Macro regime and peer comparison both call yfinance which has no rate limit. Sector performance via Alpha Vantage is already rate-limited at 75/min. +- **Backward compatibility:** All changes are additive. `max_debate_rounds=1` still works. New tools are optional in prompts. `macro_regime_report` defaults to empty.