From 5e8c81e7385980c7e04c55ed439779f3cbe5e7d2 Mon Sep 17 00:00:00 2001 From: dtarkent2-sys Date: Mon, 9 Mar 2026 21:56:38 +0000 Subject: [PATCH] =?UTF-8?q?fix:=206=20audit=20issues=20=E2=80=94=20missing?= =?UTF-8?q?=20await,=20regime=20range,=20pct=5Fout=20scaling,=20ticker=20v?= =?UTF-8?q?alidation,=20dead=20code,=20flag=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. app.py: await _update_in_progress (coroutine was silently dropped) 2. models.py + tier1.py: regime_score_adjustment range ±2→±10 (was negligible on 0-100 scale) 3. y_finance.py: pct_out * 100 (was fraction, displayed as percent) 4. app.py: ticker validation accepts dots/hyphens (BRK.B, BF-B) 5. portfolio.py: wire _fetch_peer_basics into theme substitution (was dead code) 6. setup.py: accumulate global_flags across parallel agents (dict.update was dropping them) Co-Authored-By: Claude Opus 4.6 --- app.py | 5 +-- tradingagents/agents/structured/portfolio.py | 33 +++++++++++++++++--- tradingagents/agents/structured/tier1.py | 12 +++---- tradingagents/dataflows/y_finance.py | 2 +- tradingagents/graph/setup.py | 5 +++ tradingagents/models.py | 6 ++-- 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/app.py b/app.py index cae85a2b..45f9a34d 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ """FastAPI SSE backend for the structured equity ranking engine.""" import os +import re import time import uuid import asyncio @@ -231,7 +232,7 @@ async def _run_analysis_inner(analysis_id: str, ticker: str, trade_date: str): await q.put(evt) # Mark in-progress agents for upcoming stages - _update_in_progress(chunk, emitted_fields, prev_agent_statuses, state, q, start_time) + await _update_in_progress(chunk, emitted_fields, prev_agent_statuses, state, q, start_time) except Exception as e: print(f"[ANALYSIS] Stream error: {e}\n{_tb.format_exc()}", flush=True) @@ -396,7 +397,7 @@ async def _start_cleanup(): @app.post("/analyze", dependencies=[Depends(verify_api_key)]) async def start_analysis(req: AnalyzeRequest): ticker = req.ticker.upper().strip() - if not ticker or len(ticker) > 5 or not ticker.isalpha(): + if not ticker or not re.match(r'^[A-Z0-9.\-]{1,6}$', ticker): raise HTTPException(400, "Invalid ticker") trade_date = req.date or str(date.today()) analysis_id = str(uuid.uuid4()) diff --git a/tradingagents/agents/structured/portfolio.py b/tradingagents/agents/structured/portfolio.py index 0fc59d02..811a7d7a 100644 --- a/tradingagents/agents/structured/portfolio.py +++ b/tradingagents/agents/structured/portfolio.py @@ -104,14 +104,36 @@ def create_theme_substitution_node(llm): industry = card.get("industry", "") sector = card.get("sector", "") - # Fetch peer data for comparison - # First, ask LLM to identify theme peers, then we'll fetch their data + # Fetch competitor/peer data to ground the LLM's comparison + competitors = card.get("competitors") or [] + peer_data = _fetch_peer_basics(competitors) if competitors else [] + peer_summary = "" + if peer_data: + lines = [] + for p in peer_data: + if p.get("error"): + continue + rg = p.get("revenue_growth") + rg_str = f"{rg*100:.1f}%" if rg else "N/A" + pm = p.get("profit_margins") + pm_str = f"{pm*100:.1f}%" if pm else "N/A" + lines.append( + f" {p['ticker']}: P/E={p.get('trailing_pe', 'N/A')}, " + f"Fwd P/E={p.get('forward_pe', 'N/A')}, " + f"RevGrowth={rg_str}, " + f"Margins={pm_str}, " + f"52W={p.get('52w_range_pct', 'N/A')}%" + ) + peer_summary = "\n".join(lines) + theme_prompt = f"""You are a Theme Substitution Analyst. Your job: determine if {ticker} is the BEST expression of its investment theme, or if better alternatives exist. CANDIDATE STOCK: {summary} +{f'PEER FUNDAMENTALS (live data):{chr(10)}{peer_summary}' if peer_summary else 'No live peer data available — use your knowledge of these companies.'} + INSTRUCTIONS — do this in order: 1. IDENTIFY THE THEME: What macro/sector theme does {ticker} express? @@ -120,9 +142,10 @@ INSTRUCTIONS — do this in order: Name it clearly in theme_name. 2. LIST THEME PEERS: Name 3-6 other publicly traded stocks that express the SAME theme. - These should be the strongest competitors for capital allocation in this theme. - For each peer, estimate a master_score_estimate (0-10) based on your knowledge of - their fundamentals, momentum, and positioning vs {ticker}. + Use the peer data above if available. These should be the strongest competitors + for capital allocation in this theme. + For each peer, score master_score_estimate (0-10) based on fundamentals, momentum, + and positioning vs {ticker}. 3. RANK WITHIN THEME: Rank all stocks (including {ticker}) by investment quality. The stock with the best combination of: business quality, valuation, momentum, diff --git a/tradingagents/agents/structured/tier1.py b/tradingagents/agents/structured/tier1.py index bf43950e..f9cc2d66 100644 --- a/tradingagents/agents/structured/tier1.py +++ b/tradingagents/agents/structured/tier1.py @@ -188,13 +188,13 @@ INSTRUCTIONS: 2. Classify liquidity_regime: "expansion" / "contraction" / "neutral". - expansion: falling yields, dovish Fed, credit flowing, dollar weakening. - contraction: rising yields, hawkish Fed, tight credit, dollar strengthening. -3. Set regime_score_adjustment (-2 to +2): - - +2 = strong macro tailwind for this specific stock/sector. - - +1 = mild tailwind. +3. Set regime_score_adjustment (-10 to +10): + - +5 to +10 = strong macro tailwind for this specific stock/sector. + - +1 to +4 = mild tailwind. - 0 = neutral. - - -1 = mild headwind. - - -2 = severe macro headwind (risk-off + contraction + hostile sector). - This adjustment directly modifies the master score for ALL stocks. + - -1 to -4 = mild headwind. + - -5 to -10 = severe macro headwind (risk-off + contraction + hostile sector). + This adjustment directly modifies the 0-100 master score for ALL stocks. 4. Score macro_alignment_0_to_10: how well macro supports {ticker} specifically. 5. Also provide score_0_to_10 (overall macro health). 6. Set regime_label: descriptive label (e.g., "Late Cycle Risk-Off"). diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index f0062c5c..dd681fea 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -613,7 +613,7 @@ def get_institutional_flow(ticker): { "holder": str(r.get("Holder", "")), "shares": int(r["Shares"]) if r.get("Shares") else None, - "pct_out": float(r["% Out"]) if r.get("% Out") else None, + "pct_out": round(float(r["% Out"]) * 100, 2) if r.get("% Out") else None, "value": float(r["Value"]) if r.get("Value") else None, } for r in top diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index 24689756..aa539f1a 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -191,11 +191,16 @@ def _create_parallel_node(agent_fns: List[tuple], label: str): results = await asyncio.gather(*tasks, return_exceptions=True) merged: Dict[str, Any] = {} + all_flags: list = [] for (name, _), result in zip(agent_fns, results): if isinstance(result, Exception): logger.error("[%s] %s failed: %s", label, name, result) continue + flags = result.pop("global_flags", []) + all_flags.extend(flags) merged.update(result) + if all_flags: + merged["global_flags"] = all_flags logger.info("[%s] completed in %.1fs", label, time.time() - t0) return merged diff --git a/tradingagents/models.py b/tradingagents/models.py index eadd03ec..16311b9b 100644 --- a/tradingagents/models.py +++ b/tradingagents/models.py @@ -121,9 +121,9 @@ class MacroRegimeOutput(AgentBaseOutput): risk_appetite: str = "neutral" # risk-on / risk-off / transitional liquidity_regime: str = "neutral" # expansion / contraction / neutral regime_score_adjustment: float = Field( - default=0.0, ge=-2, le=2, - description="Adjustment applied to all downstream scores. " - "+2 = strong macro tailwind, -2 = severe macro headwind.", + default=0.0, ge=-10, le=10, + description="Adjustment applied to the 0-100 master score. " + "+10 = strong macro tailwind, -10 = severe macro headwind.", )