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:
@@ -175,6 +175,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
add_cycle_run, update_cycle_run, save_reasoning_trace,
|
||||
get_latest_portfolio_lessons, log_system_event,
|
||||
get_last_completed_cycle_ts, save_cycle_context_snapshot,
|
||||
purge_old_price_snapshots,
|
||||
)
|
||||
from services.data_fetcher import fetch_geo_news, get_all_quotes, get_macro_gauges, score_macro_scenarios
|
||||
from services.geo_analyzer import compute_geo_risk_score
|
||||
@@ -326,6 +327,21 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
|
||||
quotes = get_all_quotes()
|
||||
|
||||
# ── Phase 4A: capture price snapshots for scored news ─────────────────
|
||||
try:
|
||||
from services.price_discovery import capture_price_snapshots as _cap_snap
|
||||
_quotes_flat: Dict[str, float] = {}
|
||||
for _qs in quotes.values():
|
||||
for _q in _qs:
|
||||
if _q.get("symbol") and _q.get("price"):
|
||||
_quotes_flat[_q["symbol"]] = float(_q["price"])
|
||||
_n_snaps = _cap_snap(news, _quotes_flat, run_id)
|
||||
if _n_snaps:
|
||||
logger.info(f"[Cycle {run_id[:16]}] Phase 4: {_n_snaps} price snapshots captured")
|
||||
purge_old_price_snapshots(older_than_days=14)
|
||||
except Exception as _pd_e:
|
||||
logger.warning(f"[Cycle] Price snapshot capture failed (non-blocking): {_pd_e}")
|
||||
|
||||
gauges = get_macro_gauges()
|
||||
scenarios = score_macro_scenarios(gauges)
|
||||
macro_regime = {"gauges": gauges, "scenarios": scenarios}
|
||||
@@ -370,6 +386,19 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
logger.info(f"[Cycle {run_id[:16]}] Reliability map: {len(_reliability_map)} patterns")
|
||||
except Exception as _re:
|
||||
logger.warning(f"[Cycle] Reliability map failed (non-blocking): {_re}")
|
||||
# ── Phase 4B: compute price absorption from previous snapshots ───────
|
||||
_absorptions = []
|
||||
_price_discovery_block = ""
|
||||
try:
|
||||
from services.price_discovery import compute_absorptions, build_price_discovery_block
|
||||
_absorptions = compute_absorptions(min_age_minutes=30.0, max_age_days=7)
|
||||
_price_discovery_block = build_price_discovery_block(_absorptions)
|
||||
opps = sum(1 for a in _absorptions if a["opportunity"])
|
||||
if _absorptions:
|
||||
logger.info(f"[Cycle {run_id[:16]}] Phase 4: {len(_absorptions)} absorptions, {opps} opportunities")
|
||||
except Exception as _abs_e:
|
||||
logger.warning(f"[Cycle] Price absorption failed (non-blocking): {_abs_e}")
|
||||
|
||||
# ── Phase 2: FRED recent macro releases ──────────────────────────────
|
||||
_fred_releases = []
|
||||
_fred_block = ""
|
||||
@@ -436,6 +465,10 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
"older": [{"title": n.get("title"), "source": n.get("source")} for n in _news_snap["older"][:5]],
|
||||
},
|
||||
"fred_releases": _fred_releases,
|
||||
"price_discovery": [
|
||||
{k: v for k, v in a.items() if k != "article_hash"}
|
||||
for a in _absorptions[:10]
|
||||
],
|
||||
"tech_indicators_block": _tech_block,
|
||||
"iv_context_preview": iv_context[:500] if iv_context else "",
|
||||
"calendar": calendar[:8] if calendar else [],
|
||||
@@ -454,6 +487,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
cycle_meta=cycle_meta,
|
||||
tech_indicators_block=_tech_block,
|
||||
fred_block=_fred_block,
|
||||
price_discovery_block=_price_discovery_block,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"[Cycle] Suggestion step failed: {e}")
|
||||
@@ -579,6 +613,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
cycle_meta=cycle_meta,
|
||||
tech_indicators_block=_tech_block,
|
||||
fred_block=_fred_block,
|
||||
price_discovery_block=_price_discovery_block,
|
||||
)
|
||||
scored_with_id = [s for s in scored if s.get("pattern_id")]
|
||||
scored_without_id = [s for s in scored if not s.get("pattern_id")]
|
||||
|
||||
Reference in New Issue
Block a user