feat: causal lab
This commit is contained in:
@@ -3,7 +3,7 @@ Isolated cycle action: Check New Market Events.
|
|||||||
|
|
||||||
Scans 4 sources and creates market_events for significant findings:
|
Scans 4 sources and creates market_events for significant findings:
|
||||||
- news : geopolitical/macro news (RSS feeds, rule-scored)
|
- news : geopolitical/macro news (RSS feeds, rule-scored)
|
||||||
- eco : FRED economic releases with high surprise z-score
|
- eco : ff_calendar releases with high surprise % (toutes devises, USD inclus)
|
||||||
- technical: configurable signal catalog driven by Technical Desk
|
- technical: configurable signal catalog driven by Technical Desk
|
||||||
- reports : institutional reports (COT, EIA) with high importance
|
- reports : institutional reports (COT, EIA) with high importance
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ def _check_ff_calendar_surprises(
|
|||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Detect surprising releases in ff_calendar for the given currencies.
|
Detect surprising releases in ff_calendar for the given currencies.
|
||||||
Used when currencies other than USD are configured (FRED only covers USD).
|
Détecte les releases surprenantes dans ff_calendar pour toutes les devises données (USD inclus).
|
||||||
"""
|
"""
|
||||||
from services.database import get_conn
|
from services.database import get_conn
|
||||||
|
|
||||||
@@ -483,129 +483,20 @@ def _check_eco(desk_cfg: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|||||||
existing = _existing_event_keys()
|
existing = _existing_event_keys()
|
||||||
created: List[Dict] = []
|
created: List[Dict] = []
|
||||||
|
|
||||||
# ── Local economic_events table (replaces FRED API call) ─────────────────
|
# ── ff_calendar — source unique pour toutes les devises (USD inclus) ──────
|
||||||
if "USD" in currencies:
|
# Anciennement : USD → economic_events (FRED), autres → ff_calendar.
|
||||||
try:
|
# Désormais ff_calendar couvre toutes les devises avec forecast + actual,
|
||||||
conn = get_conn()
|
# donc on unifie sur une seule source cohérente.
|
||||||
rows = conn.execute(
|
created += _check_ff_calendar_surprises(
|
||||||
"""SELECT * FROM economic_events
|
currencies=currencies,
|
||||||
WHERE event_date >= ? AND event_date <= ?
|
min_impact=min_impact,
|
||||||
AND ABS(COALESCE(surprise_zscore, 0)) >= ?
|
date_from=date_from,
|
||||||
ORDER BY event_date DESC, ABS(COALESCE(surprise_zscore, 0)) DESC
|
date_to=date_to,
|
||||||
LIMIT 50""",
|
min_surprise_pct=ff_surprise_min,
|
||||||
(date_from, date_to, z_threshold),
|
lookback_releases=lookback_releases,
|
||||||
).fetchall()
|
create_evt=create_evt,
|
||||||
conn.close()
|
existing=existing,
|
||||||
releases = []
|
)
|
||||||
for r in rows:
|
|
||||||
d = dict(r)
|
|
||||||
try:
|
|
||||||
d["assets_impacted"] = json.loads(d.get("assets_impacted") or "[]")
|
|
||||||
except Exception:
|
|
||||||
d["assets_impacted"] = []
|
|
||||||
releases.append(d)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"[check_events/eco] local query failed: {e}")
|
|
||||||
releases = []
|
|
||||||
|
|
||||||
for rel in releases:
|
|
||||||
s_id = rel.get("series_id", "")
|
|
||||||
impact = _SERIES_IMPACT.get(s_id, "low")
|
|
||||||
if _IMPACT_RANKS.get(impact, 1) < min_rank:
|
|
||||||
continue
|
|
||||||
|
|
||||||
z = abs(rel.get("surprise_zscore") or 0)
|
|
||||||
s_pct = rel.get("surprise_pct") or 0
|
|
||||||
ev_date = (rel.get("event_date") or "")[:10]
|
|
||||||
ev_name_base = rel.get("event_name", s_id)
|
|
||||||
direction = rel.get("surprise_direction", "neutral")
|
|
||||||
|
|
||||||
sign = "+" if s_pct >= 0 else ""
|
|
||||||
ev_name = f"{ev_name_base} — Surprise {sign}{s_pct:.1f}% ({ev_date[:7]})"
|
|
||||||
|
|
||||||
if _is_dup(ev_name, existing):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Lookback context from FRED history
|
|
||||||
context_str = ""
|
|
||||||
if lookback_releases > 0 and s_id:
|
|
||||||
try:
|
|
||||||
conn = get_conn()
|
|
||||||
hist = conn.execute(
|
|
||||||
"""SELECT event_date, actual_value, forecast_value
|
|
||||||
FROM economic_events
|
|
||||||
WHERE series_id = ? AND event_date < ?
|
|
||||||
ORDER BY event_date DESC LIMIT ?""",
|
|
||||||
(s_id, ev_date, lookback_releases),
|
|
||||||
).fetchall()
|
|
||||||
conn.close()
|
|
||||||
if hist:
|
|
||||||
context_str = " Historique récent: " + ", ".join(
|
|
||||||
f"{r[0][:7]}: réel={r[1]} consensus={r[2]}" for r in hist
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
sub_type = SUBTYPE_FROM_SERIES.get(s_id, s_id[:10]) if s_id else ev_name_base[:10]
|
|
||||||
level = "long" if z >= 3 else ("medium" if z >= 2 else "short")
|
|
||||||
assets = rel.get("assets_impacted") or []
|
|
||||||
if isinstance(assets, str):
|
|
||||||
try:
|
|
||||||
assets = json.loads(assets)
|
|
||||||
except Exception:
|
|
||||||
assets = []
|
|
||||||
|
|
||||||
source_ref = {
|
|
||||||
"title": f"FRED release: {ev_name_base} ({ev_date})",
|
|
||||||
"source": "FRED",
|
|
||||||
"url": f"https://fred.stlouisfed.org/series/{s_id}" if s_id else "",
|
|
||||||
"date": ev_date,
|
|
||||||
"original_score": round(min(0.95, 0.35 + z * 0.15), 3),
|
|
||||||
}
|
|
||||||
|
|
||||||
ev = {
|
|
||||||
"name": ev_name,
|
|
||||||
"start_date": ev_date,
|
|
||||||
"level": level,
|
|
||||||
"category": "event_calendar",
|
|
||||||
"sub_type": sub_type,
|
|
||||||
"description": (
|
|
||||||
f"Surprise {direction} {sign}{s_pct:.1f}% vs baseline "
|
|
||||||
f"(z-score: {z:.1f}σ). "
|
|
||||||
f"Réel: {rel.get('actual_value', '?')} {rel.get('actual_unit', '')} "
|
|
||||||
f"/ Prévision: {rel.get('forecast_value', '?')}."
|
|
||||||
+ context_str
|
|
||||||
),
|
|
||||||
"market_impact": "",
|
|
||||||
"affected_assets": assets,
|
|
||||||
"impact_score": min(0.95, 0.35 + z * 0.15),
|
|
||||||
"actual_value": str(rel.get("actual_value", "")),
|
|
||||||
"expected_value": str(rel.get("forecast_value", "")),
|
|
||||||
"surprise_pct": float(s_pct),
|
|
||||||
"source_refs": [source_ref],
|
|
||||||
"origin": "detector_eco",
|
|
||||||
}
|
|
||||||
if create_evt:
|
|
||||||
result = _save_and_evaluate(ev, existing)
|
|
||||||
if result:
|
|
||||||
result["source"] = "eco"
|
|
||||||
created.append(result)
|
|
||||||
else:
|
|
||||||
logger.info(f"[check_events/eco] create_market_event=False — skipping: {ev_name}")
|
|
||||||
|
|
||||||
# ── ff_calendar path (non-USD currencies) ────────────────────────────────
|
|
||||||
non_usd = [c for c in currencies if c != "USD"]
|
|
||||||
if non_usd:
|
|
||||||
created += _check_ff_calendar_surprises(
|
|
||||||
currencies=non_usd,
|
|
||||||
min_impact=min_impact,
|
|
||||||
date_from=date_from,
|
|
||||||
date_to=date_to,
|
|
||||||
min_surprise_pct=ff_surprise_min,
|
|
||||||
lookback_releases=lookback_releases,
|
|
||||||
create_evt=create_evt,
|
|
||||||
existing=existing,
|
|
||||||
)
|
|
||||||
|
|
||||||
return created
|
return created
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user