import { useState, useMemo } from 'react' import { useAllPatterns, useSavePattern, useDeletePattern, useEvaluatePattern, useSuggestPattern, useAiStatus, useSuggestNewPatterns, useTogglePattern, usePatternSimilarity, useLastScores, usePatternReliability } from '../hooks/useApi' import clsx from 'clsx' import { Zap, Plus, Trash2, Edit3, Brain, Save, RotateCcw, Sparkles, X, Check, Eye, EyeOff, ShieldAlert, LayoutGrid, List, SlidersHorizontal } from 'lucide-react' function jaccard(a: string[], b: string[]): number { if (!a.length && !b.length) return 0 const setA = new Set(a.map(x => x.toLowerCase())) const setB = new Set(b.map(x => x.toLowerCase())) let intersection = 0 for (const x of setA) if (setB.has(x)) intersection++ const union = setA.size + setB.size - intersection return union === 0 ? 0 : intersection / union } const TRIGGERS = ['military', 'sanctions', 'elections', 'natural_disaster', 'health_crisis', 'resource_scarcity', 'trade_war', 'energy', 'political_speech', 'financial_crisis'] const ASSET_CLASSES = ['energy', 'metals', 'agriculture', 'equities', 'indices', 'forex', 'rates'] const ASSET_CLASS_LABELS: Record = { energy: '⚑ Energy', metals: 'πŸ₯‡ Metals', agriculture: '🌾 Agri', equities: 'πŸ“ˆ Equities', indices: 'πŸ“Š Indices', forex: 'πŸ’± Forex', rates: 'πŸ“‰ Rates', } const TRIGGER_LABELS: Record = { military: 'βš”οΈ Military', sanctions: '🚫 Sanctions', elections: 'πŸ—³οΈ Elections', natural_disaster: 'πŸŒͺ️ Disaster', health_crisis: 'πŸ₯ Health', resource_scarcity: '⚠️ Resources', trade_war: '🀝 Trade', energy: '⚑ Energy', political_speech: 'πŸŽ™οΈ Speech', financial_crisis: 'πŸ’Έ Finance', } const EMPTY_PATTERN = { name: '', description: '', triggers: [] as string[], keywords: [] as string[], historical_instances: [] as any[], suggested_trades: [] as any[], asset_class: 'energy', expected_move_pct: 10, probability: 0.6, horizon_days: 30, counter_thesis: '', invalidation_trigger: '', invalidation_probability: undefined as number | undefined, } function QualityBadge({ score }: { score: number }) { const color = score >= 75 ? 'badge-green' : score >= 50 ? 'badge-yellow' : 'badge-red' const label = score >= 75 ? 'Excellent' : score >= 50 ? 'Good' : 'Weak' return {score}/100 β€” {label} } function ReliabilityBadge({ rel }: { rel: any }) { if (!rel || rel.trade_count < 3) return null const wr = rel.win_rate_pct const color = wr >= 60 ? 'text-emerald-400 border-emerald-700/40 bg-emerald-900/20' : wr >= 40 ? 'text-amber-400 border-amber-700/40 bg-amber-900/20' : 'text-red-400 border-red-700/40 bg-red-900/20' return ( πŸ“Š {wr}% WR ({rel.trade_count}t Β· βŒ€{rel.avg_pnl_pct > 0 ? '+' : ''}{rel.avg_pnl_pct}%) ) } function PatternCard({ p, onEdit, onDelete, onToggle, similarTo, aiScore, reliability }: { p: any; onEdit: () => void; onDelete: () => void; onToggle: () => void similarTo?: Array<{ name: string; similarity: number }> aiScore?: number | null reliability?: any }) { const [expanded, setExpanded] = useState(false) const isCustom = p.source === 'custom' const isActive = p.is_active !== 0 return (
{p.name}
{isCustom && Custom} {!isActive && Disabled} {p.ai_quality_score && }
{p.description}
0 ? 'positive' : 'negative')}> {p.expected_move_pct > 0 ? '+' : ''}{p.expected_move_pct}%
{p.horizon_days}d
{similarTo && similarTo.length > 0 && (
{similarTo.map((s, i) => ( ⚠ Similar to {s.name} {Math.round(s.similarity * 100)}% ))}
)}
{(p.triggers || []).map((t: string) => ( {TRIGGER_LABELS[t] ?? t} ))} {p.asset_class} {aiScore != null ? ( = 50 ? 'badge-green' : aiScore >= 25 ? 'badge-yellow' : 'badge-red')}> {aiScore}/100 AI ) : ( {Math.round(p.probability * 100)}% prob. )}
{expanded && (
{p.keywords?.length > 0 && (
Detection keywords
{p.keywords.map((kw: string) => ( #{kw} ))}
)} {p.historical_instances?.length > 0 && (
Historical instances
{p.historical_instances.map((h: any, i: number) => (
{h.date} {h.event || h.outcome}
))}
)} {p.suggested_trades?.length > 0 && (
Suggested trades
{p.suggested_trades.map((t: any, i: number) => (
{t.strategy} {t.underlying} {t.asset_class && {t.asset_class}} β€” {t.rationale}
))}
)} {(p.counter_thesis || p.invalidation_trigger) && (
Counter-thesis & Invalidation
{p.counter_thesis && (
Counter-thesis: {p.counter_thesis}
)} {p.invalidation_trigger && (
Trigger : {p.invalidation_trigger} {p.invalidation_probability != null && ( ({Math.round(p.invalidation_probability * 100)}% prob.) )}
)}
)} {p.ai_evaluation && Object.keys(p.ai_evaluation).length > 0 && (
AI Evaluation
{p.ai_evaluation.strengths?.length > 0 && (
βœ“ {p.ai_evaluation.strengths[0]}
)} {p.ai_evaluation.weaknesses?.length > 0 && (
⚠ {p.ai_evaluation.weaknesses[0]}
)} {p.ai_evaluation.overall_recommendation && (
{p.ai_evaluation.overall_recommendation}
)}
)}
)}
) } function AiSuggestModal({ onClose, onSaveAll, allPatterns }: { onClose: () => void; onSaveAll: (patterns: any[]) => void; allPatterns: any[] }) { const { mutate: suggest, isPending, data } = useSuggestNewPatterns() const [saved, setSaved] = useState>(new Set()) const { mutate: savePattern } = useSavePattern() const suggestions: any[] = (data as any)?.suggested_patterns ?? [] const handleSave = (p: any, idx: number) => { savePattern(p, { onSuccess: () => setSaved(prev => new Set(prev).add(idx)) }) } // For each suggestion, find existing patterns with keyword overlap >= 25% const suggestionSimilarities = useMemo(() => suggestions.map(s => { const kws = s.keywords ?? [] return allPatterns .map(ep => ({ name: ep.name, similarity: jaccard(kws, ep.keywords ?? []) })) .filter(x => x.similarity >= 0.25) .sort((a, b) => b.similarity - a.similarity) .slice(0, 3) }), [suggestions, allPatterns] ) return (
{/* Header */}

Suggest patterns with AI

GPT-4o analyzes geo news, prices, calendar and the current macro regime to propose patterns aligned with the dominant scenario

{/* Body */}
{!data && !isPending && (

The AI will analyze current geopolitical news, price movements, and the economic calendar to propose patterns relevant today.

)} {isPending && (

GPT-4o analyzing current context…

)} {suggestions.map((p: any, idx: number) => { const score = Math.round((p.probability ?? 0.5) * 100) const similars = suggestionSimilarities[idx] ?? [] return (
0 ? 'border-amber-700/30' : 'border-blue-700/20')}>
{p.name}
{p.asset_class} = 50 ? 'badge-green' : score >= 25 ? 'badge-yellow' : 'badge-red')}> {score}/100 IA {p.horizon_days}d Β· {p.expected_move_pct > 0 ? '+' : ''}{p.expected_move_pct}%
{similars.length > 0 && (
{similars.map((s, i) => ( ⚠ Similar to {s.name} {Math.round(s.similarity * 100)}% ))}
)}

{p.description}

{p.macro_fit && (
🌐 {p.macro_fit}
)} {(p.counter_thesis || p.invalidation_trigger) && (
{p.counter_thesis && (
⚠ Counter-thesis: {p.counter_thesis}
)} {p.invalidation_trigger && (
Trigger : {p.invalidation_trigger}{p.invalidation_probability != null ? ` (${Math.round(p.invalidation_probability * 100)}%)` : ''}
)}
)} {p.suggested_trades?.length > 0 && (
{p.suggested_trades.map((t: any, ti: number) => (
{t.strategy} {t.underlying} β€” {t.rationale}
))}
)}
) })} {data && suggestions.length === 0 && (
No patterns suggested. Try again.
)}
{/* Footer */}
{suggestions.length > 0 && ( {saved.size}/{suggestions.length} saved )}
{data && suggestions.length > 0 && ( )}
) } function PatternForm({ initial, onSave, onCancel }: { initial?: any; onSave: (p: any) => void; onCancel: () => void }) { const [form, setForm] = useState(initial ? { ...initial, triggers: Array.isArray(initial.triggers) ? initial.triggers : [], keywords: Array.isArray(initial.keywords) ? initial.keywords : [], } : { ...EMPTY_PATTERN }) const [kwInput, setKwInput] = useState('') const [aiResult, setAiResult] = useState(null) const [aiLoading, setAiLoading] = useState(false) const [suggestInput, setSuggestInput] = useState('') const [suggestLoading, setSuggestLoading] = useState(false) const { mutateAsync: evaluate } = useEvaluatePattern() const { mutateAsync: suggest } = useSuggestPattern() const { data: aiStatus } = useAiStatus() const set = (k: string, v: unknown) => setForm((f: any) => ({ ...f, [k]: v })) const toggleTrigger = (t: string) => { set('triggers', form.triggers.includes(t) ? form.triggers.filter((x: string) => x !== t) : [...form.triggers, t]) } const addKeyword = () => { if (kwInput.trim() && !form.keywords.includes(kwInput.trim())) { set('keywords', [...form.keywords, kwInput.trim()]) setKwInput('') } } const evaluateWithAI = async () => { setAiLoading(true) try { const result = await evaluate(form) setAiResult(result) if (result.quality_score) set('ai_quality_score', result.quality_score) set('ai_evaluation', result) } catch (e) { console.error(e) } setAiLoading(false) } const suggestFromContext = async () => { setSuggestLoading(true) try { const result = await suggest(suggestInput) if (result && !result.error) { setForm((f: any) => ({ ...f, name: result.name || f.name, description: result.description || f.description, triggers: result.triggers || f.triggers, keywords: result.keywords || f.keywords, historical_instances: result.historical_instances || f.historical_instances, suggested_trades: result.suggested_trades || f.suggested_trades, asset_class: result.asset_class || f.asset_class, expected_move_pct: result.expected_move_pct ?? f.expected_move_pct, probability: result.probability ?? f.probability, horizon_days: result.horizon_days ?? f.horizon_days, })) } } catch (e) { console.error(e) } setSuggestLoading(false) } return (
{/* AI Suggest from context */} {aiStatus?.enabled && (
Generate from context (AI)