feat: cycle

This commit is contained in:
OpenSquared
2026-07-15 12:03:02 +02:00
parent ce9c0b53a9
commit 2d474c9194
9 changed files with 471 additions and 67 deletions

View File

@@ -341,6 +341,20 @@ def init_db():
top_patterns_json TEXT NOT NULL DEFAULT '[]',
news_count INTEGER DEFAULT 0
)""")
# Geo risk score — one AI-judged snapshot per cycle run, insert-only, never
# mutated. Frontend reads only the latest row instead of recomputing live.
c.execute("""CREATE TABLE IF NOT EXISTS geo_risk_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL,
computed_at TEXT DEFAULT (datetime('now')),
score REAL NOT NULL,
level TEXT NOT NULL,
breakdown_json TEXT DEFAULT '{}',
top_risks_json TEXT DEFAULT '[]',
ai_rationale TEXT DEFAULT '',
source TEXT DEFAULT 'ai'
)""")
try:
c.execute("CREATE INDEX IF NOT EXISTS idx_gah_ts ON geo_alert_history(timestamp DESC)")
except Exception:
@@ -1908,6 +1922,33 @@ def get_geo_alert_history(days: int = 30) -> List[Dict[str, Any]]:
return result
def save_geo_risk_snapshot(run_id: str, score: float, level: str, breakdown: Dict[str, Any],
top_risks: List[Any], rationale: str, source: str = "ai") -> None:
conn = get_conn()
conn.execute(
"""INSERT INTO geo_risk_snapshots (run_id, score, level, breakdown_json, top_risks_json, ai_rationale, source)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(run_id, score, level, json.dumps(breakdown or {}), json.dumps(top_risks or []), rationale or "", source),
)
conn.commit()
conn.close()
def get_latest_geo_risk_snapshot() -> Optional[Dict[str, Any]]:
conn = get_conn()
row = conn.execute(
"SELECT * FROM geo_risk_snapshots ORDER BY computed_at DESC LIMIT 1"
).fetchone()
conn.close()
if not row:
return None
d = dict(row)
d["breakdown"] = json.loads(d.pop("breakdown_json", "{}") or "{}")
d["top_risks"] = json.loads(d.pop("top_risks_json", "[]") or "[]")
d["rationale"] = d.pop("ai_rationale", "")
return d
def _normalize_yf_ticker(ticker: str) -> str:
"""Normalize ticker for yfinance.
- USD/KRW → USDKRW=X (slash-format forex pairs from GPT-4o)
@@ -2047,7 +2088,10 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes
import logging as _logging
_log = _logging.getLogger(__name__)
profiles = get_risk_profiles(enabled_only=True)
_log.info(f"[TradeLog] run_id={run_id} scored_patterns={len(scored_patterns)} profiles={len(profiles)}")
min_score_threshold = int(get_config("min_score_threshold") or 0)
min_ev_threshold = float(get_config("min_ev_threshold") or 0.0)
_log.info(f"[TradeLog] run_id={run_id} scored_patterns={len(scored_patterns)} profiles={len(profiles)} "
f"min_score={min_score_threshold} min_ev={min_ev_threshold}")
# Load original patterns as fallback for expected_move_pct
# (GPT-4o scored output doesn't include this field)
@@ -2174,6 +2218,26 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes
ev_gross, ev_net, trade_score = _compute_trade_score(eff_score, exp_move)
# Global floor — applies on top of the per-profile score/gain match above.
if eff_score < min_score_threshold or ev_net < min_ev_threshold:
skipped_no_profile += 1
_log.debug(
f"[TradeLog] SKIP {underlying} score={eff_score} ev_net={ev_net:.2f}"
f"below global floor (min_score={min_score_threshold}, min_ev={min_ev_threshold})"
)
_trade_ac = trade.get("asset_class") or sp.get("asset_class") or _orig.get("asset_class") or ""
try:
log_skipped_trade(
run_id=run_id, pattern_id=pid, pattern_name=pattern_name,
underlying=underlying, strategy=strategy, score=eff_score,
expected_move_pct=exp_move,
skip_detail=f"below global floor: score={eff_score}<{min_score_threshold} or ev_net={ev_net:.2f}<{min_ev_threshold}",
asset_class=_trade_ac,
)
except Exception:
pass
continue
ticker_key = _normalize_ticker(underlying.upper())
entry_price = price_map.get(ticker_key)
horizon = int(