feat: Phase 4+5 — price discovery status + replay historique

Phase 4 — Price Discovery Status (la pièce maîtresse) :
- price_discovery.py (nouveau) : capture_price_snapshots() sauve les prix des tickers
  liés à chaque news scorée (energy→BZ=F/NG=F, metals→GC=F/HG=F, indices→^GSPC/IWM)
- compute_absorptions() mesure combien du mouvement attendu s'est déjà produit
  (status: not_yet_priced <30% / partially_priced 30-80% / fully_priced >80%)
- build_price_discovery_block() → bloc prompt avec opportunités classées
- database.py : table news_price_snapshots + save/get/purge fonctions
- auto_cycle.py : capture après ai_score_news_batch, compute avant suggestions,
  block injecté dans suggestion + scoring prompts + context snapshot
- ai_analyzer.py : param price_discovery_block dans suggest + score

Phase 5 — Replay historique :
- cycle.py : POST /api/cycle/contexts/{run_id}/replay — recharge le snapshot historique
  et relance suggest_patterns_from_market_context avec le contexte original
- useApi.ts : hook useReplayCycle
- SystemLogs.tsx : bouton "Rejouer ce cycle" dans onglet Contexte IA avec champ
  notes, résultats inline (liste des patterns générés), section price_discovery
  ouverte par défaut en rouge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-20 16:58:57 +02:00
parent 9c0ebbd138
commit a21699805b
7 changed files with 499 additions and 15 deletions

View File

@@ -385,6 +385,19 @@ def init_db():
context_json TEXT NOT NULL
)""")
c.execute("""CREATE TABLE IF NOT EXISTS news_price_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
article_hash TEXT NOT NULL,
article_title TEXT,
ticker TEXT NOT NULL,
expected_direction TEXT,
expected_impact_score REAL,
price_at_capture REAL,
captured_at TEXT NOT NULL DEFAULT (datetime('now')),
capture_cycle_id TEXT,
UNIQUE(article_hash, ticker)
)""")
c.execute("""CREATE TABLE IF NOT EXISTS skipped_trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT,
@@ -2130,6 +2143,57 @@ def list_cycle_context_snapshots(limit: int = 30) -> list:
return [{"run_id": r["run_id"], "ts": r["ts"]} for r in rows]
# ── News Price Snapshots (Phase 4 — Price Discovery) ─────────────────────────
def save_news_price_snapshot(
article_hash: str,
article_title: str,
ticker: str,
expected_direction: str,
expected_impact_score: float,
price_at_capture: float,
cycle_id: str,
) -> None:
conn = get_conn()
conn.execute(
"""INSERT OR IGNORE INTO news_price_snapshots
(article_hash, article_title, ticker, expected_direction,
expected_impact_score, price_at_capture, capture_cycle_id)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(article_hash, article_title, ticker, expected_direction,
expected_impact_score, price_at_capture, cycle_id),
)
conn.commit()
conn.close()
def get_news_price_snapshots(max_age_days: int = 7, min_age_minutes: float = 30.0) -> list:
conn = get_conn()
rows = conn.execute(
"""SELECT article_hash, article_title, ticker, expected_direction,
expected_impact_score, price_at_capture, captured_at, capture_cycle_id
FROM news_price_snapshots
WHERE captured_at >= datetime('now', ? || ' days')
AND captured_at <= datetime('now', ? || ' minutes')
ORDER BY captured_at DESC""",
(f"-{max_age_days}", f"-{int(min_age_minutes)}"),
).fetchall()
conn.close()
return [dict(r) for r in rows]
def purge_old_price_snapshots(older_than_days: int = 14) -> int:
conn = get_conn()
conn.execute(
"DELETE FROM news_price_snapshots WHERE captured_at < datetime('now', ? || ' days')",
(f"-{older_than_days}",),
)
deleted = conn.total_changes
conn.commit()
conn.close()
return deleted
# ── Knowledge Base Decay ──────────────────────────────────────────────────────
def decay_kb_confidence() -> int: