feat: cycle
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user