feat: options technical agent — IV/skew/term structure validation per trade
- New options_technical_agent.py: rule engine (IVR, skew, term structure, flow)
+ GPT-4o narrative per trade; verdict OK/WARN/ALERT + fit_score
- options_trade_assessments table in DB for Journal badge persistence
- auto_cycle.py step 5.2: assess newly logged trades after log_trade_entries;
results embedded in cycle report
- suggest_patterns_from_market_context: +iv_context param + explicit IV→strategy
rules in prompt (IVR<30%→Long, 30-60%→Spread, >60%→no naked long, >80%→short)
- Pre-fetch iv_context at step 1.9 so suggestion step gets strategy rules
- reports.py: /api/reports/assessments/latest + /assessments/{run_id} endpoints
- RapportIA.tsx: "Validation Technique Options" section with per-trade IVBar,
VerdictBadge, issues list, GPT-4o analysis, optimal strategy suggestion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,24 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
dominant = scenarios.get("dominant", "incertain")
|
||||
summary["dominant_regime"] = dominant
|
||||
|
||||
# ── Step 1.9: Pre-fetch IV context for strategy suggestion rules ──────
|
||||
iv_context = ""
|
||||
try:
|
||||
from services.iv_engine import get_iv_context_for_prompt, IV_WATCHLIST
|
||||
from services.database import get_mtm_trades_with_traces
|
||||
_mtm_pre = get_mtm_trades_with_traces(days=90)
|
||||
_trade_tickers_pre = list({
|
||||
(t.get("underlying") or "").upper()
|
||||
for t in _mtm_pre.get("all_trades", [])
|
||||
if t.get("underlying")
|
||||
})
|
||||
_iv_tickers_pre = (_trade_tickers_pre + IV_WATCHLIST[:6])[:10]
|
||||
iv_context = get_iv_context_for_prompt(_iv_tickers_pre)
|
||||
if iv_context:
|
||||
logger.info(f"[Cycle {run_id[:16]}] IV context pre-fetched for {len(_iv_tickers_pre)} tickers (suggestion step)")
|
||||
except Exception as _e:
|
||||
logger.warning(f"[Cycle] IV context pre-fetch failed (non-blocking): {_e}")
|
||||
|
||||
# ── Step 2: Suggest new patterns ──────────────────────────────────────
|
||||
logger.info(f"[Cycle {run_id[:16]}] Step 2: suggesting patterns")
|
||||
_reliability_map = {}
|
||||
@@ -317,6 +335,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
news, quotes, calendar, macro_regime=macro_regime, geo_score=geo_score_obj,
|
||||
portfolio_lessons=portfolio_lessons,
|
||||
reliability_map=_reliability_map or None,
|
||||
iv_context=iv_context,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"[Cycle] Suggestion step failed: {e}")
|
||||
@@ -389,8 +408,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
except Exception as _re:
|
||||
logger.warning(f"[Cycle] Risk cluster context failed (non-blocking): {_re}")
|
||||
|
||||
# ── Step 3.6: Collect IV context ─────────────────────────────────────
|
||||
iv_context = ""
|
||||
# ── Step 3.6: Refresh IV context (with full trade+watchlist scope) ──────
|
||||
try:
|
||||
from services.iv_engine import get_iv_context_for_prompt, IV_WATCHLIST
|
||||
# Collect underlyings from current trade journal + default watchlist
|
||||
@@ -521,8 +539,32 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
log_geo_alert(geo_score=geo_score_val, top_patterns=top_patterns_log,
|
||||
news_count=len(news), run_id=scoring_run_id)
|
||||
|
||||
_options_assessment = None
|
||||
log_trade_entries(run_id=scoring_run_id, scored_patterns=scored, quotes=quotes)
|
||||
|
||||
# ── Step 5.2: Options Technical Agent — validate newly logged trades ──
|
||||
try:
|
||||
from services.options_technical_agent import assess_logged_trades, save_assessments_to_db
|
||||
_options_assessment = assess_logged_trades(
|
||||
scoring_run_id=scoring_run_id,
|
||||
scored=scored,
|
||||
ai_key=ai_key,
|
||||
)
|
||||
if _options_assessment and _options_assessment.get("assessments"):
|
||||
save_assessments_to_db(_options_assessment["assessments"], scoring_run_id)
|
||||
summary["options_assessment"] = {
|
||||
"n_ok": _options_assessment.get("n_ok", 0),
|
||||
"n_warn": _options_assessment.get("n_warn", 0),
|
||||
"n_alert": _options_assessment.get("n_alert", 0),
|
||||
"global_score": _options_assessment.get("global_score"),
|
||||
}
|
||||
logger.info(
|
||||
f"[OptionsTech] Assessment done — OK={_options_assessment.get('n_ok')} "
|
||||
f"WARN={_options_assessment.get('n_warn')} ALERT={_options_assessment.get('n_alert')}"
|
||||
)
|
||||
except Exception as _ota:
|
||||
logger.warning(f"[OptionsTech] Agent failed (non-blocking): {_ota}")
|
||||
|
||||
# ── Step 5.1: Portfolio monitor — conflict & concentration check ──────
|
||||
try:
|
||||
from services.portfolio_risk import analyze_simulation_portfolio
|
||||
@@ -651,6 +693,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
scoring_run_id=scoring_run_id,
|
||||
portfolio_monitor=summary.get("portfolio_monitor"),
|
||||
commentary=commentary,
|
||||
options_assessment=_options_assessment,
|
||||
)
|
||||
if _cycle_report:
|
||||
from services.database import save_cycle_report
|
||||
@@ -780,6 +823,7 @@ def _generate_cycle_report(
|
||||
scoring_run_id: str,
|
||||
portfolio_monitor: Optional[Dict],
|
||||
commentary: Optional[str],
|
||||
options_assessment: Optional[Dict] = None,
|
||||
) -> Optional[Dict]:
|
||||
"""
|
||||
Build the full cycle report dict:
|
||||
@@ -987,6 +1031,15 @@ Réponds en JSON avec ce schéma EXACT:
|
||||
"summary": (s.get("summary") or "")[:120], "key_catalyst": s.get("key_catalyst", "")}
|
||||
for s in sorted(scored, key=lambda x: -(x.get("score") or 0))[:5]
|
||||
],
|
||||
# Options Technical Agent assessment
|
||||
"options_technical": {
|
||||
"global_assessment": (options_assessment or {}).get("global_assessment", ""),
|
||||
"global_score": (options_assessment or {}).get("global_score"),
|
||||
"n_ok": (options_assessment or {}).get("n_ok", 0),
|
||||
"n_warn": (options_assessment or {}).get("n_warn", 0),
|
||||
"n_alert": (options_assessment or {}).get("n_alert", 0),
|
||||
"assessments": (options_assessment or {}).get("assessments", []),
|
||||
} if options_assessment is not None else None,
|
||||
}
|
||||
return report
|
||||
|
||||
|
||||
Reference in New Issue
Block a user