fix: 3 bugs — synthesis crash, stale running cycle, invalid tickers
- knowledge.py: trade_line crash when pnl_pct is None in dict
(t.get('pnl_pct',0) returns None if key exists with None value — use 'or 0')
- database.py: cleanup_stale_running_cycles() marks any 'running' cycle
as 'error' on startup (uvicorn reload mid-cycle left status stuck)
- main.py: call cleanup_stale_running_cycles() at startup with warning log
- database.py: _normalize_ticker() converts GPT-4o exchange:symbol format
(NSE:RELIANCE → RELIANCE.NS, BSE:X → X.BO, etc.) so yfinance stops
spamming 404 errors for every MTM request
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from routers import market_data, geopolitical, options, backtest, ai, portfolio, config, patterns, journal, cycle as cycle_router, profiles as profiles_router, reasoning as reasoning_router, knowledge as knowledge_router
|
||||
from services.database import init_db, get_config
|
||||
from services.database import init_db, get_config, cleanup_stale_running_cycles
|
||||
import os
|
||||
import uvicorn
|
||||
|
||||
@@ -23,6 +23,13 @@ app.add_middleware(
|
||||
@app.on_event("startup")
|
||||
def startup():
|
||||
init_db()
|
||||
# Clean up cycles that were left in 'running' state by a previous crashed process
|
||||
stale = cleanup_stale_running_cycles()
|
||||
if stale:
|
||||
import logging
|
||||
logging.getLogger(__name__).warning(
|
||||
f"[Startup] Cleaned up {stale} stale 'running' cycle(s) — server likely reloaded mid-cycle"
|
||||
)
|
||||
key = get_config("openai_api_key") or ""
|
||||
if key:
|
||||
os.environ["OPENAI_API_KEY"] = key
|
||||
|
||||
@@ -64,8 +64,9 @@ Note timing: {timing[:150]}
|
||||
|
||||
def trade_line(t):
|
||||
mat = t.get("maturity", {})
|
||||
pnl = t.get("pnl_pct") or 0
|
||||
return (f"{t.get('underlying','?')} {t.get('strategy','?')} "
|
||||
f"P&L={t.get('pnl_pct',0):+.1f}% score={t.get('latest_score','?')} "
|
||||
f"P&L={pnl:+.1f}% score={t.get('latest_score','?')} "
|
||||
f"regime={t.get('macro_regime','?')} [{mat.get('readable','')}]")
|
||||
|
||||
trades_block = f"""
|
||||
|
||||
@@ -950,6 +950,17 @@ def add_cycle_run(run_id: str, trigger: str = "auto") -> None:
|
||||
conn.close()
|
||||
|
||||
|
||||
def cleanup_stale_running_cycles() -> int:
|
||||
"""Mark any cycle_runs still in 'running' state as 'error' (stale from a crashed process)."""
|
||||
conn = get_conn()
|
||||
cur = conn.execute(
|
||||
"UPDATE cycle_runs SET status='error', completed_at=datetime('now') WHERE status='running'"
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return cur.rowcount
|
||||
|
||||
|
||||
def update_cycle_run(run_id: str, **fields) -> None:
|
||||
if not fields:
|
||||
return
|
||||
@@ -1125,6 +1136,20 @@ def get_pattern_scoring_history(pattern_id: str, limit: int = 10) -> List[Dict[s
|
||||
_BEARISH_KEYWORDS = {"bear", "put", "short", "sell", "vente", "baissier"}
|
||||
|
||||
|
||||
_EXCHANGE_PREFIX_MAP = {
|
||||
"NSE": ".NS", "BSE": ".BO", "TSX": ".TO", "LSE": ".L",
|
||||
"HKG": ".HK", "SHA": ".SS", "SHE": ".SZ", "TYO": ".T",
|
||||
}
|
||||
|
||||
def _normalize_ticker(ticker: str) -> str:
|
||||
"""Convert exchange:symbol formats (from GPT-4o) to yfinance-compatible tickers."""
|
||||
if ":" in ticker:
|
||||
exchange, symbol = ticker.split(":", 1)
|
||||
suffix = _EXCHANGE_PREFIX_MAP.get(exchange.upper(), "")
|
||||
return symbol + suffix if suffix else symbol
|
||||
return ticker
|
||||
|
||||
|
||||
def _fetch_live_prices(tickers: List[str], timeout: int = 20) -> Dict[str, Optional[float]]:
|
||||
"""
|
||||
Fetch current prices for a list of tickers using yf.download().
|
||||
@@ -1139,12 +1164,13 @@ def _fetch_live_prices(tickers: List[str], timeout: int = 20) -> Dict[str, Optio
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
def _one(ticker: str) -> tuple:
|
||||
yf_ticker = _normalize_ticker(ticker)
|
||||
for kwargs in [
|
||||
{"period": "1d", "interval": "5m"},
|
||||
{"period": "5d", "interval": "1d"},
|
||||
]:
|
||||
try:
|
||||
df = yf.download(ticker, progress=False, auto_adjust=True, **kwargs)
|
||||
df = yf.download(yf_ticker, progress=False, auto_adjust=True, **kwargs)
|
||||
if df.empty:
|
||||
continue
|
||||
if isinstance(df.columns, pd.MultiIndex):
|
||||
|
||||
Reference in New Issue
Block a user