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:
@@ -4,6 +4,7 @@ import clsx from 'clsx'
|
||||
import {
|
||||
Brain, TrendingUp, TrendingDown, RefreshCw, ShieldAlert,
|
||||
GitCompare, Layers, Zap, BookOpen, Clock, ChevronRight,
|
||||
CheckCircle2, AlertTriangle, XCircle, BarChart2,
|
||||
} from 'lucide-react'
|
||||
|
||||
const SCENARIO_META: Record<string, { label: string; color: string; emoji: string }> = {
|
||||
@@ -43,6 +44,37 @@ function Section({ icon, title, children }: { icon: React.ReactNode; title: stri
|
||||
)
|
||||
}
|
||||
|
||||
const VERDICT_META = {
|
||||
OK: { color: 'text-emerald-400', bg: 'bg-emerald-900/10 border-emerald-700/30', icon: CheckCircle2 },
|
||||
WARN: { color: 'text-yellow-400', bg: 'bg-yellow-900/10 border-yellow-700/30', icon: AlertTriangle },
|
||||
ALERT: { color: 'text-red-400', bg: 'bg-red-900/10 border-red-700/30', icon: XCircle },
|
||||
}
|
||||
|
||||
function VerdictBadge({ verdict }: { verdict: 'OK' | 'WARN' | 'ALERT' }) {
|
||||
const m = VERDICT_META[verdict] ?? VERDICT_META.WARN
|
||||
const Icon = m.icon
|
||||
return (
|
||||
<span className={clsx('inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-bold border', m.bg, m.color)}>
|
||||
<Icon className="w-2.5 h-2.5" /> {verdict}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function IVBar({ ivRank }: { ivRank?: number | null }) {
|
||||
if (ivRank == null) return <span className="text-slate-600 text-[10px]">IVR —</span>
|
||||
const color = ivRank >= 60 ? 'bg-red-500' : ivRank >= 30 ? 'bg-yellow-500' : 'bg-emerald-500'
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-16 h-1 bg-slate-700 rounded-full overflow-hidden">
|
||||
<div className={clsx('h-full rounded-full', color)} style={{ width: `${Math.min(ivRank, 100)}%` }} />
|
||||
</div>
|
||||
<span className={clsx('text-[10px] font-mono', ivRank >= 60 ? 'text-red-400' : ivRank >= 30 ? 'text-yellow-400' : 'text-emerald-400')}>
|
||||
IVR {ivRank.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DeltaRow({ emoji, label, count, items, colorClass }: {
|
||||
emoji: string; label: string; count: number;
|
||||
items: any[]; colorClass: string;
|
||||
@@ -412,6 +444,123 @@ export default function RapportCycle() {
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{/* Options Technical Agent */}
|
||||
{report.options_technical && (report.options_technical.assessments?.length > 0 || report.options_technical.global_assessment) && (
|
||||
<Section icon={<BarChart2 className="w-4 h-4" />} title="Validation Technique Options">
|
||||
{/* Global score + summary */}
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
{report.options_technical.global_score != null && (
|
||||
<div className="shrink-0 text-center">
|
||||
<div className={clsx('text-2xl font-bold font-mono',
|
||||
report.options_technical.global_score >= 70 ? 'text-emerald-400'
|
||||
: report.options_technical.global_score >= 45 ? 'text-yellow-400'
|
||||
: 'text-red-400'
|
||||
)}>
|
||||
{report.options_technical.global_score}
|
||||
</div>
|
||||
<div className="text-[9px] text-slate-600">Score vol</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
{report.options_technical.global_assessment && (
|
||||
<p className="text-xs text-slate-300 leading-relaxed">
|
||||
{report.options_technical.global_assessment}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex gap-3 mt-2 text-[10px]">
|
||||
{report.options_technical.n_ok > 0 && (
|
||||
<span className="text-emerald-400 flex items-center gap-0.5">
|
||||
<CheckCircle2 className="w-3 h-3" /> {report.options_technical.n_ok} OK
|
||||
</span>
|
||||
)}
|
||||
{report.options_technical.n_warn > 0 && (
|
||||
<span className="text-yellow-400 flex items-center gap-0.5">
|
||||
<AlertTriangle className="w-3 h-3" /> {report.options_technical.n_warn} WARN
|
||||
</span>
|
||||
)}
|
||||
{report.options_technical.n_alert > 0 && (
|
||||
<span className="text-red-400 flex items-center gap-0.5">
|
||||
<XCircle className="w-3 h-3" /> {report.options_technical.n_alert} ALERT
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Per-trade assessments */}
|
||||
<div className="space-y-3">
|
||||
{(report.options_technical.assessments as any[]).map((a: any, i: number) => (
|
||||
<div key={i} className={clsx(
|
||||
'rounded border px-3 py-2.5',
|
||||
VERDICT_META[a.verdict as 'OK' | 'WARN' | 'ALERT']?.bg ?? 'border-slate-700/30 bg-dark-700/40'
|
||||
)}>
|
||||
{/* Trade header */}
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<VerdictBadge verdict={a.verdict} />
|
||||
<span className="text-xs font-semibold text-slate-200">{a.ticker}</span>
|
||||
<span className="text-[10px] text-slate-500">{a.strategy}</span>
|
||||
<div className="ml-auto">
|
||||
<IVBar ivRank={a.iv_rank} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* IV data row */}
|
||||
<div className="flex gap-3 text-[10px] text-slate-500 mb-2">
|
||||
{a.skew_pct != null && (
|
||||
<span>Skew: <span className={clsx('font-mono', a.skew_pct > 5 ? 'text-orange-400' : 'text-slate-400')}>
|
||||
{a.skew_pct > 0 ? '+' : ''}{a.skew_pct.toFixed(1)}pts
|
||||
</span></span>
|
||||
)}
|
||||
{a.term_structure && (
|
||||
<span>Structure: <span className={clsx('font-mono',
|
||||
a.term_structure === 'backwardation' ? 'text-red-400'
|
||||
: a.term_structure === 'contango' ? 'text-emerald-400' : 'text-slate-400'
|
||||
)}>{a.term_structure}</span></span>
|
||||
)}
|
||||
{a.flow_bias && (
|
||||
<span>Flow: <span className="font-mono text-slate-400">{a.flow_bias}</span></span>
|
||||
)}
|
||||
<span className="ml-auto text-slate-600">Score: <span className="font-mono text-slate-400">{a.fit_score}</span></span>
|
||||
</div>
|
||||
|
||||
{/* Issues + positives */}
|
||||
{((a.issues ?? []).length > 0 || (a.positives ?? []).length > 0) && (
|
||||
<div className="space-y-0.5 mb-2">
|
||||
{(a.issues as string[]).map((issue: string, j: number) => (
|
||||
<div key={j} className="flex items-start gap-1.5 text-[10px] text-orange-300">
|
||||
<span className="shrink-0 mt-0.5">⚠</span> {issue}
|
||||
</div>
|
||||
))}
|
||||
{(a.positives as string[]).map((pos: string, j: number) => (
|
||||
<div key={j} className="flex items-start gap-1.5 text-[10px] text-emerald-400">
|
||||
<span className="shrink-0 mt-0.5">✓</span> {pos}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* GPT-4o analysis */}
|
||||
{a.analysis && (
|
||||
<p className="text-[11px] text-slate-400 leading-relaxed mb-1">{a.analysis}</p>
|
||||
)}
|
||||
|
||||
{/* Optimal strategy + when to enter */}
|
||||
<div className="flex items-start gap-3 mt-1.5 pt-1.5 border-t border-slate-700/20 text-[10px]">
|
||||
{a.optimal_strategy && a.optimal_strategy !== a.strategy && (
|
||||
<span className="text-slate-500">
|
||||
Stratégie optimale: <span className="text-blue-400 font-medium">{a.optimal_strategy}</span>
|
||||
</span>
|
||||
)}
|
||||
{a.when_to_enter && (
|
||||
<span className="text-slate-600 italic">{a.when_to_enter}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{/* Risk / portfolio monitor */}
|
||||
{report.portfolio_monitor && (
|
||||
<Section icon={<ShieldAlert className="w-4 h-4" />} title="Risque & Alertes portefeuille">
|
||||
|
||||
Reference in New Issue
Block a user