feat: causal lab

This commit is contained in:
OpenSquared
2026-06-29 10:32:18 +02:00
parent 67bfd8e777
commit bcdf1fbf2e
2 changed files with 66 additions and 30 deletions

View File

@@ -134,12 +134,12 @@ def _fetch_prices(event_date_str: str, instruments: list[str]) -> dict:
return out
def _drift_metrics(prices: dict, event_date_str: str, inst: str, lag_min: int = 0) -> dict:
def _drift_metrics(prices: dict, event_date_str: str, inst: str, lag_min: int = 0, lag_days: int = 0) -> dict:
series = prices.get(inst, [])
mode = prices.get("mode", "none")
edate = event_date_str[:10]
empty = {"pre_pips": None, "post_pips": None, "drift_ratio": None, "leak": "unknown", "lag_min": lag_min}
empty = {"pre_pips": None, "post_pips": None, "drift_ratio": None, "leak": "unknown", "lag_min": lag_min, "lag_days": lag_days}
if not series:
return empty
@@ -154,13 +154,18 @@ def _drift_metrics(prices: dict, event_date_str: str, inst: str, lag_min: int =
pre_pips = round((series[mid - 1]["c"] - series[0]["c"]) * mult)
post_pips = round((series[-1]["c"] - series[mid]["c"]) * mult)
else:
pre = [b for b in series if b["t"] < edate]
same = [b for b in series if b["t"] == edate]
if not pre or not same:
# En mode journalier, lag_days décale la date de référence "post"
if lag_days > 0:
post_date = (datetime.strptime(edate, "%Y-%m-%d") + timedelta(days=lag_days)).strftime("%Y-%m-%d")
pre = [b for b in series if b["t"] <= edate]
post = [b for b in series if b["t"] >= post_date]
else:
pre = [b for b in series if b["t"] < edate]
post = [b for b in series if b["t"] == edate]
if not pre or not post:
return empty
mult = 10000 if inst in ("EURUSD",) else 10
pre_pips = None
post_pips = round((same[0]["c"] - pre[-1]["c"]) * mult)
post_pips = round((post[0]["c"] - pre[-1]["c"]) * mult)
ratio: Optional[float] = None
if pre_pips is not None and post_pips and post_pips != 0:
@@ -866,7 +871,7 @@ def analyze_event(body: AnalyzeRequest):
# Évaluation du graphe
node_values = evaluate_graph(graph, inputs, body.coef_overrides or {})
# Lag effectif : max des lag_min sur toutes les arêtes menant à un market_asset
# Lag effectif : max des lag_min/lag_days sur toutes les arêtes menant à un market_asset
edges = graph.get("edges", [])
nodes_map = {n["id"]: n for n in graph.get("nodes", [])}
output_ids = {n["id"] for n in graph.get("nodes", []) if n.get("type") in ("market_asset", "output")}
@@ -874,6 +879,10 @@ def analyze_event(body: AnalyzeRequest):
(e.get("lag_min", 0) or 0 for e in edges if e.get("to") in output_ids),
default=0,
)
effective_lag_days = max(
(e.get("lag_days", 0) or 0 for e in edges if e.get("to") in output_ids),
default=0,
)
# Instruments : utiliser tous ceux du template si aucun spécifié
if body.instrument:
@@ -888,7 +897,7 @@ def analyze_event(body: AnalyzeRequest):
actual_moves: dict = {}
drift_by_inst: dict = {}
for inst in instruments:
drift = _drift_metrics(prices, event["start_date"], inst, lag_min=effective_lag)
drift = _drift_metrics(prices, event["start_date"], inst, lag_min=effective_lag, lag_days=effective_lag_days)
drift_by_inst[inst] = drift
if drift.get("post_pips") is not None:
actual_moves[inst] = drift["post_pips"]
@@ -919,6 +928,7 @@ def analyze_event(body: AnalyzeRequest):
"drift": drift_by_inst.get(primary_inst, {}),
"prices_mode": prices.get("mode", "none"),
"effective_lag_min": effective_lag,
"effective_lag_days": effective_lag_days,
"analyzed_at": analyzed_at,
"graph_json": graph, # structure complète pour visualisation frontend
}