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:
@@ -98,3 +98,96 @@ def get_context_snapshot(run_id: str):
|
||||
if not snap:
|
||||
raise HTTPException(404, "Snapshot non trouvé pour ce cycle")
|
||||
return snap
|
||||
|
||||
|
||||
class ReplayRequest(BaseModel):
|
||||
override_notes: Optional[str] = None # optional annotation added to the replay
|
||||
|
||||
|
||||
@router.post("/contexts/{run_id}/replay")
|
||||
def replay_cycle(run_id: str, req: ReplayRequest):
|
||||
"""
|
||||
Phase 5 — Re-run AI suggestion with the historical context from a saved snapshot.
|
||||
Returns new pattern suggestions based on the original context data.
|
||||
"""
|
||||
snap = get_cycle_context_snapshot(run_id)
|
||||
if not snap:
|
||||
raise HTTPException(404, "Snapshot non trouvé pour ce cycle")
|
||||
|
||||
ctx = snap.get("context", {})
|
||||
if not ctx:
|
||||
raise HTTPException(400, "Snapshot vide — impossible de rejouer")
|
||||
|
||||
try:
|
||||
from services.ai_analyzer import suggest_patterns_from_market_context, apply_news_decay, partition_news_by_age
|
||||
import json
|
||||
|
||||
# Reconstruct minimal inputs from the snapshot
|
||||
# news: merge inter_cycle + recent_24h + older from partitioned snapshot
|
||||
news_part = ctx.get("news_partitioned", {})
|
||||
news_flat = (
|
||||
news_part.get("inter_cycle", [])
|
||||
+ news_part.get("recent_24h", [])
|
||||
+ news_part.get("older", [])
|
||||
)
|
||||
|
||||
# quotes: reconstruct from quotes_summary
|
||||
quotes_by_class = ctx.get("quotes_summary", {})
|
||||
|
||||
# calendar
|
||||
calendar = ctx.get("calendar", [])
|
||||
|
||||
# macro regime (simplified for replay)
|
||||
macro_regime = None
|
||||
if ctx.get("macro_regime"):
|
||||
macro_regime = {"scenarios": {"dominant": ctx["macro_regime"].get("dominant"), "scores": ctx["macro_regime"].get("scores"), "asset_bias": {}, "reasons": []}, "gauges": {}}
|
||||
|
||||
# geo score
|
||||
geo_score = ctx.get("geo_score", {"score": 50, "level": "medium", "top_risks": []})
|
||||
|
||||
# preserved blocks
|
||||
tech_block = ctx.get("tech_indicators_block", "")
|
||||
iv_context = ctx.get("iv_context_preview", "")
|
||||
cycle_meta = ctx.get("cycle_meta", {})
|
||||
|
||||
# Rebuild FRED block from saved releases
|
||||
fred_block = ""
|
||||
if ctx.get("fred_releases"):
|
||||
from services.fred_fetcher import build_fred_context_block
|
||||
fred_block = build_fred_context_block(ctx["fred_releases"])
|
||||
|
||||
# Rebuild price discovery block from saved absorptions
|
||||
pd_block = ""
|
||||
if ctx.get("price_discovery"):
|
||||
from services.price_discovery import build_price_discovery_block
|
||||
pd_block = build_price_discovery_block(ctx["price_discovery"])
|
||||
|
||||
# Add replay note to cycle_meta
|
||||
if req.override_notes:
|
||||
cycle_meta = {**cycle_meta, "replay_notes": req.override_notes}
|
||||
cycle_meta["is_replay"] = True
|
||||
cycle_meta["replayed_at"] = __import__("datetime").datetime.utcnow().isoformat()
|
||||
|
||||
suggestions = suggest_patterns_from_market_context(
|
||||
news=news_flat,
|
||||
quotes_by_class=quotes_by_class,
|
||||
calendar=calendar,
|
||||
macro_regime=macro_regime,
|
||||
geo_score=geo_score,
|
||||
iv_context=iv_context,
|
||||
cycle_meta=cycle_meta,
|
||||
tech_indicators_block=tech_block,
|
||||
fred_block=fred_block,
|
||||
price_discovery_block=pd_block,
|
||||
)
|
||||
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"original_ts": snap["ts"],
|
||||
"replayed_at": cycle_meta["replayed_at"],
|
||||
"override_notes": req.override_notes,
|
||||
"suggestions_count": len(suggestions),
|
||||
"suggestions": suggestions,
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"Replay failed: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user