fix: weekend-aware cycle — IVGate, pandas MultiIndex, ticker aliases, day/session in AI prompt
- auto_cycle.py: detect weekend/market session, build cycle_meta with day_of_week/is_weekend/market_note; IVGate skips iv_rank>=99 on weekends to avoid artificial weekend option premium cascade; inject portfolio context (open trades + price moves + concentration) before AI scoring; pass portfolio_context_block + run_id to both AI scorer and suggester - ai_analyzer.py: _build_temporal_news_block injects market session banner (WEEKEND warning, pre/after-market note, or open session label) so AI knows markets are closed and defers execution to Monday - iv_engine.py: add WHEAT/EUR/USD ticker aliases; skip saving IV snapshots on weekends to protect history; resolve aliases before slash-format conversion in _resolve_ticker - technical_indicators.py: fix pandas MultiIndex from yfinance>=0.2 (droplevel+squeeze); use period proportional to lookback instead of fixed period=1d - database.py: asset_class ticker-based fallback (_asset_class_from_ticker); one-time backfill migration for all NULL asset_class rows; ai_call_logs table + save/get helpers; normalize_ticker public function Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -230,12 +230,41 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
else:
|
||||
_calib_label = f"Long terme ({_delta_minutes/60:.0f}h) — marchés ont eu le temps d'intégrer"
|
||||
|
||||
# ── Market calendar context ────────────────────────────────────────
|
||||
from datetime import timezone as _tz
|
||||
_now_utc = _now.replace(tzinfo=_tz.utc) if _now.tzinfo is None else _now.astimezone(_tz.utc)
|
||||
_weekday = _now_utc.weekday() # 0=Mon … 6=Sun
|
||||
_day_names = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
|
||||
_is_weekend = _weekday >= 5
|
||||
# CME equity/energy/metals futures: closed Fri 17:00 ET → Sun 18:00 ET
|
||||
# Rough ET offset (UTC-5 winter / UTC-4 summer) — good enough for intent
|
||||
_et_hour = (_now_utc.hour - 5) % 24
|
||||
if _is_weekend:
|
||||
_market_session = "closed"
|
||||
_market_note = f"{_day_names[_weekday]} — marchés US et CME fermés. Derniers prix disponibles: vendredi clôture."
|
||||
elif _et_hour < 4:
|
||||
_market_session = "overnight"
|
||||
_market_note = "Nuit US (overnight) — liquidité réduite, spreads larges."
|
||||
elif _et_hour < 9:
|
||||
_market_session = "pre_market"
|
||||
_market_note = "Pré-marché US — prix indicatifs, faible liquidité."
|
||||
elif _et_hour < 16:
|
||||
_market_session = "open"
|
||||
_market_note = "Marché US ouvert."
|
||||
else:
|
||||
_market_session = "after_hours"
|
||||
_market_note = "After-hours US — prix indicatifs, liquidité réduite."
|
||||
|
||||
cycle_meta = {
|
||||
"current_cycle_ts": _now.isoformat(),
|
||||
"last_cycle_ts": _last_cycle_ts_str,
|
||||
"delta_minutes": round(_delta_minutes, 1),
|
||||
"interval_hours": _interval_hours,
|
||||
"calibration_label": _calib_label,
|
||||
"day_of_week": _day_names[_weekday],
|
||||
"is_weekend": _is_weekend,
|
||||
"market_session": _market_session,
|
||||
"market_note": _market_note,
|
||||
}
|
||||
logger.info(
|
||||
f"[Cycle {run_id[:16]}] Cycle meta: delta={_delta_minutes:.0f}min depuis dernier cycle"
|
||||
@@ -1075,6 +1104,10 @@ def _apply_iv_gate(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Detect weekend: IVR=100 on weekends is artificial (weekend premium) — ignore rank
|
||||
from datetime import datetime as _dt_cls
|
||||
_weekend_now = _dt_cls.utcnow().weekday() >= 5
|
||||
|
||||
all_blocked: List[Dict] = []
|
||||
|
||||
for sp in scored:
|
||||
@@ -1093,6 +1126,11 @@ def _apply_iv_gate(
|
||||
continue
|
||||
|
||||
iv_rank = snap.get("iv_rank")
|
||||
# On weekends, ATM IV is inflated by weekend premium (market makers can't hedge).
|
||||
# IVR=99-100 on a weekend is almost always artificial — don't block on it.
|
||||
if _weekend_now and iv_rank is not None and iv_rank >= 99.0:
|
||||
iv_rank = None
|
||||
snap = {**snap, "iv_rank": None}
|
||||
iv_current_pct = snap.get("iv_current_pct")
|
||||
iv_min_52w = snap.get("iv_min_52w_pct")
|
||||
iv_max_52w = snap.get("iv_max_52w_pct")
|
||||
|
||||
Reference in New Issue
Block a user