quanthedge / frontend /src /pages /PatternIntelligence.tsx
jashdoshi77's picture
more changes
f30fe4c
import { useState, useEffect } from 'react';
import { patternsAPI } from '../api/client';
import { formatCurrency, detectCurrencyFromTicker } from '../utils/currencyUtils';
import TickerSearch from '../components/TickerSearch';
/* ── SVG Icons (no emojis) ─────────────────────────────────────────────── */
const ScanIcon = () => <svg viewBox="0 0 20 20" fill="currentColor" width="18" height="18"><path fillRule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clipRule="evenodd" /></svg>;
const ChartIcon = () => <svg viewBox="0 0 20 20" fill="currentColor" width="18" height="18"><path fillRule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 0l-2 2a1 1 0 101.414 1.414L8 10.414l1.293 1.293a1 1 0 001.414 0l4-4z" clipRule="evenodd" /></svg>;
const CatalogIcon = () => <svg viewBox="0 0 20 20" fill="currentColor" width="18" height="18"><path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z" /></svg>;
const AccuracyIcon = () => <svg viewBox="0 0 20 20" fill="currentColor" width="18" height="18"><path fillRule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /></svg>;
const ArrowUp = () => <svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path fillRule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" clipRule="evenodd" /></svg>;
const ArrowDown = () => <svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path fillRule="evenodd" d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z" clipRule="evenodd" /></svg>;
const TABS = [
{ id: 'scanner', label: 'Pattern Scanner', icon: <ScanIcon /> },
{ id: 'catalog', label: 'Pattern Catalog', icon: <CatalogIcon /> },
{ id: 'accuracy', label: 'Accuracy Report', icon: <AccuracyIcon /> },
];
export default function PatternIntelligence() {
const [activeTab, setActiveTab] = useState('scanner');
const [ticker, setTicker] = useState('');
const [period, setPeriod] = useState('2y');
const [horizon, setHorizon] = useState(5);
const [loading, setLoading] = useState(false);
const [analysis, setAnalysis] = useState<any>(null);
const [catalog, setCatalog] = useState<any>(null);
const [accuracy, setAccuracy] = useState<any>(null);
const [error, setError] = useState('');
const handleAnalyze = async () => {
if (!ticker.trim()) return;
setLoading(true);
setError('');
try {
const { data } = await patternsAPI.analyze({ ticker: ticker.toUpperCase(), period, horizon });
setAnalysis(data);
} catch (err: any) {
setError(err.response?.data?.detail || 'Analysis failed');
} finally {
setLoading(false);
}
};
const loadCatalog = async () => {
try {
const { data } = await patternsAPI.catalog();
setCatalog(data);
} catch (err) {
setError('Failed to load catalog');
}
};
const handleBacktestAccuracy = async () => {
if (!ticker.trim()) return;
setLoading(true);
setError('');
try {
const { data } = await patternsAPI.backtestAccuracy({ ticker: ticker.toUpperCase(), period: '5y', horizon });
setAccuracy(data);
} catch (err: any) {
setError(err.response?.data?.detail || 'Backtest failed');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (activeTab === 'catalog' && !catalog) loadCatalog();
}, [activeTab]);
const directionColor = (d: string) => {
if (d === 'bullish' || d === 'strong_up') return 'var(--accent-green)';
if (d === 'bearish' || d === 'strong_down') return 'var(--accent-red, #ef4444)';
return 'var(--text-muted)';
};
const confidenceColor = (c: number) => {
if (c >= 0.65) return 'var(--accent-green)';
if (c >= 0.45) return 'var(--accent-yellow, #f59e0b)';
return 'var(--accent-red, #ef4444)';
};
return (
<div className="page-container" style={{ padding: '1.5rem 2rem' }}>
{/* Header */}
<div style={{ marginBottom: '1.5rem' }}>
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, margin: 0, display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<ChartIcon /> Pattern Intelligence
</h1>
<p style={{ color: 'var(--text-muted)', margin: '0.25rem 0 0', fontSize: '0.85rem' }}>
AI-powered candlestick pattern recognition with LightGBM ensemble prediction
</p>
</div>
{/* Tabs */}
<div className="qh-tabs" style={{ display: 'flex', gap: '0.25rem', marginBottom: '1.25rem', borderBottom: '1px solid var(--border-subtle)', paddingBottom: '0' }}>
{TABS.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
display: 'flex', alignItems: 'center', gap: '0.4rem', padding: '0.6rem 1rem',
border: 'none', background: 'none', cursor: 'pointer', fontSize: '0.8rem', fontWeight: 600,
color: activeTab === tab.id ? 'var(--accent-green)' : 'var(--text-muted)',
borderBottom: activeTab === tab.id ? '2px solid var(--accent-green)' : '2px solid transparent',
transition: 'all 0.2s ease',
}}
>
{tab.icon} {tab.label}
</button>
))}
</div>
{/* Search Bar */}
{(activeTab === 'scanner' || activeTab === 'accuracy') && (
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.25rem', flexWrap: 'wrap' }}>
<TickerSearch
value={ticker}
onChange={setTicker}
onSelect={() => activeTab === 'scanner' ? handleAnalyze() : handleBacktestAccuracy()}
placeholder="Search stocks, ETFs, crypto..."
style={{ flex: '1 1 200px', minWidth: 180 }}
/>
<select value={period} onChange={e => setPeriod(e.target.value)} style={{
padding: '0.55rem 0.75rem', border: '1px solid var(--border-subtle)', borderRadius: '0.5rem',
background: 'var(--bg-secondary)', color: 'var(--text-primary)', fontSize: '0.85rem',
}}>
<option value="1y">1 Year</option>
<option value="2y">2 Years</option>
<option value="3y">3 Years</option>
<option value="5y">5 Years</option>
</select>
<select value={horizon} onChange={e => setHorizon(Number(e.target.value))} style={{
padding: '0.55rem 0.75rem', border: '1px solid var(--border-subtle)', borderRadius: '0.5rem',
background: 'var(--bg-secondary)', color: 'var(--text-primary)', fontSize: '0.85rem',
}}>
<option value={1}>1-Day Horizon</option>
<option value={3}>3-Day Horizon</option>
<option value={5}>5-Day Horizon</option>
<option value={10}>10-Day Horizon</option>
<option value={20}>20-Day Horizon</option>
</select>
<button
onClick={activeTab === 'scanner' ? handleAnalyze : handleBacktestAccuracy}
disabled={loading || !ticker.trim()}
style={{
padding: '0.55rem 1.2rem', borderRadius: '0.5rem', border: 'none',
background: 'var(--accent-green)', color: '#fff', fontWeight: 600,
fontSize: '0.85rem', cursor: loading ? 'wait' : 'pointer',
opacity: loading || !ticker.trim() ? 0.6 : 1,
}}
>
{loading ? 'Analyzing...' : activeTab === 'scanner' ? 'Analyze' : 'Run Accuracy Test'}
</button>
</div>
)}
{error && (
<div style={{ padding: '0.75rem 1rem', borderRadius: '0.5rem', background: 'rgba(239,68,68,0.1)', color: '#ef4444', fontSize: '0.8rem', marginBottom: '1rem', border: '1px solid rgba(239,68,68,0.2)' }}>
{error}
</div>
)}
{/* ── Scanner Tab ──────────────────────────────────────────────── */}
{activeTab === 'scanner' && analysis && (
<div style={{ display: 'grid', gap: '1rem', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))' }}>
{/* Prediction Card */}
<div className="card" style={{ padding: '1.25rem', gridColumn: 'span 2' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div>
<div style={{ fontSize: '0.7rem', textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)', fontWeight: 600 }}>Prediction</div>
<div style={{ fontSize: '1.5rem', fontWeight: 700, color: directionColor(analysis.prediction), display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{analysis.prediction === 'strong_up' && <ArrowUp />}
{analysis.prediction === 'strong_down' && <ArrowDown />}
{analysis.prediction_label}
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)', fontWeight: 600 }}>Confidence</div>
<div style={{ fontSize: '1.5rem', fontWeight: 700, color: confidenceColor(analysis.confidence) }}>
{(analysis.confidence * 100).toFixed(1)}%
</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>{analysis.confidence_level} confidence</div>
</div>
</div>
{/* Probability Bar */}
<div style={{ marginBottom: '0.75rem' }}>
<div style={{ display: 'flex', fontSize: '0.7rem', color: 'var(--text-muted)', marginBottom: '0.25rem' }}>
<span>Bearish</span><span style={{ marginLeft: 'auto' }}>Bullish</span>
</div>
<div style={{ display: 'flex', height: 8, borderRadius: 4, overflow: 'hidden', gap: 1 }}>
<div style={{ width: `${analysis.probabilities.strong_down * 100}%`, background: '#ef4444' }} />
<div style={{ width: `${analysis.probabilities.neutral * 100}%`, background: '#6b7280' }} />
<div style={{ width: `${analysis.probabilities.strong_up * 100}%`, background: 'var(--accent-green)' }} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.65rem', color: 'var(--text-muted)', marginTop: '0.2rem' }}>
<span>{(analysis.probabilities.strong_down * 100).toFixed(1)}%</span>
<span>{(analysis.probabilities.neutral * 100).toFixed(1)}%</span>
<span>{(analysis.probabilities.strong_up * 100).toFixed(1)}%</span>
</div>
</div>
{/* Stats Row */}
<div style={{ display: 'flex', gap: '1.5rem', fontSize: '0.8rem', flexWrap: 'wrap' }}>
<div><span style={{ color: 'var(--text-muted)' }}>Price: </span><strong>{formatCurrency(analysis.current_price, detectCurrencyFromTicker(ticker))}</strong></div>
<div><span style={{ color: 'var(--text-muted)' }}>Expected Return: </span><strong style={{ color: directionColor(analysis.prediction) }}>{analysis.expected_return_pct > 0 ? '+' : ''}{analysis.expected_return_pct}%</strong></div>
<div><span style={{ color: 'var(--text-muted)' }}>Horizon: </span><strong>{analysis.horizon_days}D</strong></div>
<div><span style={{ color: 'var(--text-muted)' }}>Accuracy: </span><strong>{(analysis.model_metrics?.accuracy * 100).toFixed(1)}%</strong></div>
<div><span style={{ color: 'var(--text-muted)' }}>F1: </span><strong>{analysis.model_metrics?.f1?.toFixed(4)}</strong></div>
</div>
</div>
{/* Hedge Recommendation Card */}
{analysis.hedge_recommendation && (
<div className="card" style={{ padding: '1.25rem', gridColumn: 'span 2', borderLeft: `4px solid ${analysis.hedge_recommendation.urgency === 'high' ? '#ef4444' : analysis.hedge_recommendation.urgency === 'medium' ? '#f59e0b' : 'var(--accent-green)'}` }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)' }}>Hedge Recommendation</div>
<span style={{
fontSize: '0.65rem', padding: '0.15rem 0.5rem', borderRadius: 20, fontWeight: 700,
background: analysis.hedge_recommendation.urgency === 'high' ? 'rgba(239,68,68,0.15)' : analysis.hedge_recommendation.urgency === 'medium' ? 'rgba(245,158,11,0.15)' : 'rgba(16,185,129,0.15)',
color: analysis.hedge_recommendation.urgency === 'high' ? '#ef4444' : analysis.hedge_recommendation.urgency === 'medium' ? '#f59e0b' : 'var(--accent-green)',
}}>{analysis.hedge_recommendation.action?.replace(/_/g, ' ').toUpperCase()}</span>
</div>
<div style={{ display: 'grid', gap: '0.75rem', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))', marginBottom: '0.75rem' }}>
<div style={{ padding: '0.5rem 0.75rem', borderRadius: '0.4rem', background: 'var(--bg-tertiary, var(--bg-secondary))' }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Suggested Hedge</div>
<div style={{ fontSize: '1.1rem', fontWeight: 700 }}>{analysis.hedge_recommendation.suggested_hedge_pct}%</div>
</div>
<div style={{ padding: '0.5rem 0.75rem', borderRadius: '0.4rem', background: 'var(--bg-tertiary, var(--bg-secondary))' }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Pattern Consensus</div>
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: directionColor(analysis.hedge_recommendation.pattern_consensus === 'bearish' ? 'bearish' : analysis.hedge_recommendation.pattern_consensus === 'bullish' ? 'bullish' : 'neutral') }}>
{analysis.hedge_recommendation.pattern_consensus?.charAt(0).toUpperCase() + analysis.hedge_recommendation.pattern_consensus?.slice(1)}
</div>
</div>
<div style={{ padding: '0.5rem 0.75rem', borderRadius: '0.4rem', background: 'var(--bg-tertiary, var(--bg-secondary))' }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Bearish Signals</div>
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: '#ef4444' }}>{analysis.hedge_recommendation.bearish_pattern_count}</div>
</div>
<div style={{ padding: '0.5rem 0.75rem', borderRadius: '0.4rem', background: 'var(--bg-tertiary, var(--bg-secondary))' }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Bullish Signals</div>
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: 'var(--accent-green)' }}>{analysis.hedge_recommendation.bullish_pattern_count}</div>
</div>
</div>
<div style={{ padding: '0.6rem 0.85rem', borderRadius: '0.4rem', background: 'var(--bg-secondary)', fontSize: '0.78rem', color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: analysis.hedge_recommendation.instruments?.length ? '0.75rem' : 0 }}>
{analysis.hedge_recommendation.rationale}
</div>
{analysis.hedge_recommendation.instruments?.length > 0 && (
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{analysis.hedge_recommendation.instruments.map((inst: string, i: number) => (
<span key={i} style={{ fontSize: '0.7rem', padding: '0.2rem 0.5rem', borderRadius: 4, background: 'rgba(99,102,241,0.1)', color: '#6366f1', fontWeight: 600 }}>{inst}</span>
))}
</div>
)}
</div>
)}
{/* Detected Patterns */}
<div className="card" style={{ padding: '1.25rem' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)', marginBottom: '0.75rem' }}>
Detected Patterns ({analysis.detected_patterns?.length || 0})
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxHeight: 350, overflow: 'auto' }}>
{(analysis.detected_patterns || []).map((p: any, i: number) => (
<div key={i} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '0.5rem 0.75rem', borderRadius: '0.4rem',
background: 'var(--bg-tertiary, var(--bg-secondary))',
border: '1px solid var(--border-subtle)',
}}>
<div>
<div style={{ fontSize: '0.8rem', fontWeight: 600 }}>{p.name}</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-muted)' }}>{p.category}</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 600, color: directionColor(p.direction) }}>
{p.direction === 'bullish' ? 'Bullish' : p.direction === 'bearish' ? 'Bearish' : 'Neutral'}
</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-muted)' }}>
{(p.reliability * 100).toFixed(0)}% reliability
</div>
</div>
</div>
))}
{(!analysis.detected_patterns || analysis.detected_patterns.length === 0) && (
<div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', textAlign: 'center', padding: '1rem' }}>
No patterns detected in recent candles
</div>
)}
</div>
</div>
{/* Top Features */}
<div className="card" style={{ padding: '1.25rem' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)', marginBottom: '0.75rem' }}>
Top Feature Importances
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
{(analysis.top_features || []).slice(0, 10).map((f: any, i: number) => {
const maxImp = analysis.top_features[0]?.importance || 1;
return (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem' }}>
<span style={{ width: 130, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--text-secondary)' }}>
{f.name}
</span>
<div style={{ flex: 1, height: 6, borderRadius: 3, background: 'var(--bg-tertiary, var(--bg-secondary))' }}>
<div style={{ width: `${(f.importance / maxImp) * 100}%`, height: '100%', borderRadius: 3, background: 'var(--accent-green)' }} />
</div>
<span style={{ width: 45, textAlign: 'right', fontSize: '0.65rem', color: 'var(--text-muted)' }}>
{f.importance.toFixed(1)}
</span>
</div>
);
})}
</div>
</div>
{/* Advanced Features */}
<div className="card" style={{ padding: '1.25rem', gridColumn: 'span 2' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)', marginBottom: '0.75rem' }}>
Advanced Mathematical Features
</div>
<div style={{ display: 'grid', gap: '0.75rem', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))' }}>
{analysis.advanced_features && Object.entries(analysis.advanced_features).filter(([_, v]) => typeof v === 'number').map(([key, val]: [string, any]) => (
<div key={key} style={{ padding: '0.5rem 0.75rem', borderRadius: '0.4rem', background: 'var(--bg-tertiary, var(--bg-secondary))', border: '1px solid var(--border-subtle)' }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)', letterSpacing: '0.03em' }}>
{key.replace(/_/g, ' ')}
</div>
<div style={{ fontSize: '1rem', fontWeight: 700 }}>{typeof val === 'number' ? val.toFixed(4) : val}</div>
</div>
))}
</div>
</div>
</div>
)}
{/* ── Catalog Tab ──────────────────────────────────────────────── */}
{activeTab === 'catalog' && catalog && (
<div style={{ display: 'grid', gap: '0.5rem', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
{(catalog.patterns || []).map((p: any, i: number) => (
<div key={i} className="card" style={{ padding: '0.75rem 1rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.2rem' }}>
<span style={{ fontSize: '0.85rem', fontWeight: 600 }}>{p.name}</span>
<span style={{
fontSize: '0.6rem', padding: '0.1rem 0.4rem', borderRadius: 20,
background: p.category === 'single' ? 'rgba(59,130,246,0.1)' : p.category === 'multi' ? 'rgba(168,85,247,0.1)' : 'rgba(251,146,60,0.1)',
color: p.category === 'single' ? '#3b82f6' : p.category === 'multi' ? '#a855f7' : '#fb923c',
fontWeight: 600,
}}>{p.category}</span>
</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', lineHeight: 1.4 }}>{p.description}</div>
</div>
<div style={{ textAlign: 'right', minWidth: 80, marginLeft: '0.75rem' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 600, color: directionColor(p.direction) }}>
{p.direction === 'bullish' ? 'Bullish' : p.direction === 'bearish' ? 'Bearish' : 'Neutral'}
</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-muted)' }}>{(p.reliability * 100).toFixed(0)}% reliable</div>
</div>
</div>
))}
</div>
)}
{/* ── Accuracy Tab ─────────────────────────────────────────────── */}
{activeTab === 'accuracy' && accuracy && (
<div>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem', flexWrap: 'wrap' }}>
<div className="card" style={{ padding: '0.75rem 1rem', flex: 1, minWidth: 120 }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Bars Analyzed</div>
<div style={{ fontSize: '1.25rem', fontWeight: 700 }}>{accuracy.total_bars_analyzed}</div>
</div>
<div className="card" style={{ padding: '0.75rem 1rem', flex: 1, minWidth: 120 }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Patterns Found</div>
<div style={{ fontSize: '1.25rem', fontWeight: 700 }}>{accuracy.patterns_found}</div>
</div>
<div className="card" style={{ padding: '0.75rem 1rem', flex: 1, minWidth: 120 }}>
<div style={{ fontSize: '0.6rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Horizon</div>
<div style={{ fontSize: '1.25rem', fontWeight: 700 }}>{accuracy.horizon_days}D</div>
</div>
</div>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.8rem' }}>
<thead>
<tr style={{ background: 'var(--bg-secondary)', borderBottom: '1px solid var(--border-subtle)' }}>
<th style={{ padding: '0.6rem 1rem', textAlign: 'left', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Pattern</th>
<th style={{ padding: '0.6rem 0.75rem', textAlign: 'center', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Direction</th>
<th style={{ padding: '0.6rem 0.75rem', textAlign: 'center', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Occurrences</th>
<th style={{ padding: '0.6rem 0.75rem', textAlign: 'center', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Win Rate</th>
<th style={{ padding: '0.6rem 0.75rem', textAlign: 'center', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Avg Return</th>
<th style={{ padding: '0.6rem 0.75rem', textAlign: 'center', fontWeight: 600, fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Actual vs Theoretical</th>
</tr>
</thead>
<tbody>
{(accuracy.accuracy_report || []).map((r: any, i: number) => (
<tr key={i} style={{ borderBottom: '1px solid var(--border-subtle)' }}>
<td style={{ padding: '0.5rem 1rem', fontWeight: 600 }}>{r.pattern}</td>
<td style={{ padding: '0.5rem 0.75rem', textAlign: 'center', color: directionColor(r.direction) }}>{r.direction}</td>
<td style={{ padding: '0.5rem 0.75rem', textAlign: 'center' }}>{r.occurrences}</td>
<td style={{ padding: '0.5rem 0.75rem', textAlign: 'center', fontWeight: 600, color: r.win_rate >= 0.5 ? 'var(--accent-green)' : '#ef4444' }}>
{(r.win_rate * 100).toFixed(1)}%
</td>
<td style={{ padding: '0.5rem 0.75rem', textAlign: 'center', color: r.avg_return_pct >= 0 ? 'var(--accent-green)' : '#ef4444' }}>
{r.avg_return_pct >= 0 ? '+' : ''}{r.avg_return_pct.toFixed(2)}%
</td>
<td style={{ padding: '0.5rem 0.75rem', textAlign: 'center', color: r.actual_vs_theoretical >= 0 ? 'var(--accent-green)' : '#ef4444' }}>
{r.actual_vs_theoretical >= 0 ? '+' : ''}{(r.actual_vs_theoretical * 100).toFixed(1)}%
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Empty state */}
{activeTab === 'scanner' && !analysis && !loading && (
<div style={{ textAlign: 'center', padding: '4rem 2rem', color: 'var(--text-muted)' }}>
<ChartIcon />
<p style={{ fontSize: '0.9rem', marginTop: '0.75rem' }}>Enter a ticker symbol and click Analyze to detect candlestick patterns and get AI predictions</p>
<p style={{ fontSize: '0.75rem' }}>Supports all markets: US equities, Indian stocks (.NS), crypto, and more</p>
</div>
)}
</div>
);
}