fix: 6 audit issues — missing await, regime range, pct_out scaling, ticker validation, dead code, flag merge
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 <noreply@anthropic.com>
This commit is contained in:
parent
ee80a42971
commit
5e8c81e738
5
app.py
5
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())
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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").
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue