Files
sitemente/components/mission-control/TradingChart.tsx
T

369 lines
24 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect, useRef } from 'react'
interface Trade {
id: string
date: string
pair: string
direction: 'long' | 'short'
entry: number
stopLoss: number
takeProfit: number
result?: 'win' | 'loss' | 'open'
pnl?: number
rr?: number
}
interface ChartData {
time: number
open: number
high: number
low: number
close: number
volume?: number
}
interface ThothView {
thought: string
trend: string
phase: string
key_level: number
bias: string
confidence: number
reason: string
next_action: string
updated_at: string
bias_history?: { time: number; bias: string; price: number }[]
support_zones?: { level: number; strength: number }[]
resistance_zones?: { level: number; strength: number }[]
}
interface PriceData {
price: number
change24h: number
}
interface IndicatorState {
ema20: boolean
ema50: boolean
ema200: boolean
bb: boolean
rsi: boolean
macd: boolean
thoth: boolean
volume: boolean
srZones: boolean
news: boolean
patterns: boolean
fib: boolean
countdown: boolean
calendar: boolean
correlation: boolean
funding: boolean
}
interface PatternMatch {
index: number
type: string
}
// Indicator calculations
const calculateEMA = (data: number[], period: number): (number | null)[] => {
const ema: (number | null)[] = []
const multiplier = 2 / (period + 1)
for (let i = 0; i < data.length; i++) {
if (i < period - 1) ema.push(null)
else if (i === period - 1) {
let sum = 0; for (let j = 0; j < period; j++) sum += data[i - j]
ema.push(sum / period)
} else {
ema.push(data[i] * multiplier + ema[i - 1]! * (1 - multiplier))
}
}
return ema
}
const calculateSMA = (data: number[], period: number): (number | null)[] => {
const sma: (number | null)[] = []
for (let i = 0; i < data.length; i++) {
if (i < period - 1) sma.push(null)
else { let sum = 0; for (let j = 0; j < period; j++) sum += data[i - j]; sma.push(sum / period) }
}
return sma
}
const calculateStdDev = (data: number[], period: number): (number | null)[] => {
const stdDev: (number | null)[] = []
for (let i = 0; i < data.length; i++) {
if (i < period - 1) stdDev.push(null)
else {
const slice = data.slice(i - period + 1, i + 1)
const mean = slice.reduce((a, b) => a + b, 0) / period
stdDev.push(Math.sqrt(slice.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / period))
}
}
return stdDev
}
const calculateBollingerBands = (data: number[], period: number = 20, stdDevMult: number = 2) => {
const sma = calculateSMA(data, period)
const stdDev = calculateStdDev(data, period)
const upper: (number | null)[] = [], lower: (number | null)[] = []
for (let i = 0; i < data.length; i++) {
if (sma[i] === null || stdDev[i] === null) { upper.push(null); lower.push(null) }
else { upper.push(sma[i]! + stdDev[i]! * stdDevMult); lower.push(sma[i]! - stdDev[i]! * stdDevMult) }
}
return { middle: sma, upper, lower }
}
const calculateRSI = (data: number[], period: number = 14): (number | null)[] => {
const rsi: (number | null)[] = []
let gains: number[] = [], losses: number[] = []
for (let i = 0; i < data.length; i++) {
if (i === 0) { rsi.push(null); continue }
const change = data[i] - data[i - 1]
gains.push(change > 0 ? change : 0)
losses.push(change < 0 ? Math.abs(change) : 0)
if (i < period) rsi.push(null)
else {
const avgGain = gains.slice(-period).reduce((a, b) => a + b, 0) / period
const avgLoss = losses.slice(-period).reduce((a, b) => a + b, 0) / period
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss
rsi.push(100 - (100 / (1 + rs)))
}
}
return rsi
}
const calculateMACD = (data: number[], fast: number = 12, slow: number = 26, signal: number = 9) => {
const emaFast = calculateEMA(data, fast)
const emaSlow = calculateEMA(data, slow)
const macdLine: (number | null)[] = []
for (let i = 0; i < data.length; i++) {
if (emaFast[i] === null || emaSlow[i] === null) macdLine.push(null)
else macdLine.push(emaFast[i]! - emaSlow[i]!)
}
const validMacd = macdLine.filter((v): v is number => v !== null)
const signalLine = calculateEMA(validMacd, signal)
const signalLineAligned: (number | null)[] = []
let signalIdx = 0
for (let i = 0; i < macdLine.length; i++) {
if (macdLine[i] === null) signalLineAligned.push(null)
else { signalLineAligned.push(signalLine[signalIdx] ?? null); signalIdx++ }
}
const histogram: (number | null)[] = []
for (let i = 0; i < macdLine.length; i++) {
if (macdLine[i] === null || signalLineAligned[i] === null) histogram.push(null)
else histogram.push(macdLine[i]! - signalLineAligned[i]!)
}
return { macd: macdLine, signal: signalLineAligned, histogram }
}
const detectPatterns = (data: ChartData[]): PatternMatch[] => {
const patterns: PatternMatch[] = []
const closes = data.map(d => d.close)
const highs = data.map(d => d.high)
const lows = data.map(d => d.low)
for (let i = 20; i < data.length - 5; i++) {
if (highs[i] > highs[i-1] && highs[i] > highs[i+1] && highs[i-5] > highs[i-4] && Math.abs(highs[i] - highs[i-5]) < highs[i] * 0.02)
patterns.push({ index: i, type: 'double_top' })
if (lows[i] < lows[i-1] && lows[i] < lows[i+1] && lows[i-5] < lows[i-4] && Math.abs(lows[i] - lows[i-5]) < lows[i] * 0.02)
patterns.push({ index: i, type: 'double_bottom' })
if (closes[i] > highs[i-5] && closes[i-1] < highs[i-5]) patterns.push({ index: i, type: 'breakout' })
if (closes[i] < lows[i-5] && closes[i-1] > lows[i-5]) patterns.push({ index: i, type: 'breakdown' })
}
return patterns
}
declare global {
interface Window {
Chart: any
}
}
export function TradingChart() {
const mainChartRef = useRef<HTMLCanvasElement>(null)
const rsiChartRef = useRef<HTMLCanvasElement>(null)
const macdChartRef = useRef<HTMLCanvasElement>(null)
const [selectedAsset, setSelectedAsset] = useState<'BTC' | 'SOL' | 'ETH'>('BTC')
const [selectedTimeframe, setSelectedTimeframe] = useState<'15m' | '1h' | '4h' | '1D'>('1h')
const [secondTimeframe, setSecondTimeframe] = useState<'15m' | '1h' | '4h' | '1D' | null>(null)
const [chartData, setChartData] = useState<ChartData[]>([])
const [secondChartData, setSecondChartData] = useState<ChartData[]>([])
const [trades, setTrades] = useState<Trade[]>([])
const [thothView, setThothView] = useState<Record<string, ThothView>>({})
const [priceData, setPriceData] = useState<PriceData>({ price: 0, change24h: 0 })
const [loading, setLoading] = useState(true)
const [patterns, setPatterns] = useState<PatternMatch[]>([])
const [indicators, setIndicators] = useState<IndicatorState>({
ema20: false, ema50: false, ema200: false, bb: false, rsi: false, macd: false, thoth: true, volume: true, srZones: true, news: false, patterns: true, fib: false, countdown: true, calendar: false, correlation: false, funding: false
})
const mainChartRefInstance = useRef<any>(null)
const rsiChartRefInstance = useRef<any>(null)
const macdChartRefInstance = useRef<any>(null)
const getCandleLimit = (tf: string) => ({ '15m': 80, '1h': 100, '4h': 60, '1D': 90 }[tf] || 100)
useEffect(() => { fetchChartData(); fetchSecondChartData(); fetchPriceData(); const i = setInterval(() => { fetchChartData(); fetchSecondChartData(); fetchPriceData(); }, 60000); return () => clearInterval(i) }, [selectedAsset, selectedTimeframe, secondTimeframe])
useEffect(() => { fetchTrades(); fetchThothView() }, [])
useEffect(() => {
if (chartData.length > 0) { setPatterns(detectPatterns(chartData)); renderCharts() }
return () => { if (mainChartRefInstance.current) mainChartRefInstance.current.destroy(); if (rsiChartRefInstance.current) rsiChartRefInstance.current.destroy(); if (macdChartRefInstance.current) macdChartRefInstance.current.destroy() }
}, [chartData, indicators])
const fetchPriceData = async () => {
try {
const idMap: Record<string, string> = { 'BTC': 'bitcoin', 'SOL': 'solana', 'ETH': 'ethereum' }
const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${idMap[selectedAsset]}&vs_currencies=usd&include_24hr_change=true`)
if (!res.ok) throw new Error('CoinGecko error')
const data = await res.json()
setPriceData({ price: data[idMap[selectedAsset]].usd, change24h: data[idMap[selectedAsset]].usd_24h_change })
} catch (e) {
console.warn("Price fetch failed, using fallback")
// Fallback prices
const fallbackPrices: Record<string, { price: number; change24h: number }> = {
'BTC': { price: 105000, change24h: 2.5 },
'SOL': { price: 180, change24h: -1.2 },
'ETH': { price: 3200, change24h: 1.8 }
}
setPriceData(fallbackPrices[selectedAsset] || { price: 0, change24h: 0 })
}
}
const fetchChartData = async () => {
setLoading(true)
try {
const symbol = selectedAsset === 'BTC' ? 'BTCUSDT' : selectedAsset === 'SOL' ? 'SOLUSDT' : 'ETHUSDT'
const interval = { '15m': '15m', '1h': '1h', '4h': '4h', '1D': '1d' }[selectedTimeframe] || '1h'
const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${getCandleLimit(selectedTimeframe)}`)
const data = await res.json()
setChartData(data.map((k: any[]) => ({ time: k[0], open: parseFloat(k[1]), high: parseFloat(k[2]), low: parseFloat(k[3]), close: parseFloat(k[4]), volume: parseFloat(k[5]) })))
} catch (e) { console.error("Chart fetch error:", e) }
finally { setLoading(false) }
}
const fetchSecondChartData = async () => {
if (!secondTimeframe) return
try {
const symbol = selectedAsset === 'BTC' ? 'BTCUSDT' : selectedAsset === 'SOL' ? 'SOLUSDT' : 'ETHUSDT'
const interval = { '15m': '15m', '1h': '1h', '4h': '4h', '1D': '1d' }[secondTimeframe] || '1h'
const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${getCandleLimit(secondTimeframe)}`)
const data = await res.json()
setSecondChartData(data.map((k: any[]) => ({ time: k[0], open: parseFloat(k[1]), high: parseFloat(k[2]), low: parseFloat(k[3]), close: parseFloat(k[4]) })))
} catch (e) { console.error("Second chart fetch error:", e) }
}
const fetchTrades = async () => { try { const res = await fetch('/api/trading/trades'); if (res.ok) setTrades((await res.json()).trades || []) } catch (e) { console.warn(e) } }
const fetchThothView = async () => { try { const res = await fetch('/thoth_view.json'); if (res.ok) setThothView(await res.json()) } catch (e) { console.warn(e) } }
const toggleIndicator = (key: keyof IndicatorState) => setIndicators(p => ({ ...p, [key]: !p[key] }))
const renderCharts = () => {
const script = document.createElement('script')
script.src = 'https://cdn.jsdelivr.net/npm/chart.js'
script.onload = () => { renderMainChart(); if (indicators.rsi) renderRSIChart(); if (indicators.macd) renderMACDChart() }
document.head.appendChild(script)
}
const renderMainChart = () => {
if (!mainChartRef.current) return
if (mainChartRefInstance.current) mainChartRefInstance.current.destroy()
const ctx = mainChartRef.current.getContext('2d')
if (!ctx) return
const closes = chartData.map(d => d.close)
const labels = chartData.map(d => new Date(d.time))
const ema20 = calculateEMA(closes, 20), ema50 = calculateEMA(closes, 50), ema200 = calculateEMA(closes, 200), bb = calculateBollingerBands(closes, 20, 2)
const minP = Math.min(...chartData.flatMap(d => [d.high, d.low])), maxP = Math.max(...chartData.flatMap(d => [d.high, d.low])), pad = (maxP - minP) * 0.15
const datasets: any[] = [{ type: 'bar', label: 'Price', data: chartData.map(d => [d.low, d.high]), backgroundColor: chartData.map(d => d.close >= d.open ? '#22c55e' : '#ef4444'), borderColor: chartData.map(d => d.close >= d.open ? '#22c55e' : '#ef4444'), borderWidth: 1, borderSkipped: false }]
if (indicators.volume && chartData[0]?.volume) datasets.push({ type: 'bar', label: 'Volume', data: chartData.map(d => d.volume || 0), backgroundColor: chartData.map(d => d.close >= d.open ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'), borderWidth: 0, yAxisID: 'y_vol' })
if (indicators.ema20) datasets.push({ type: 'line', data: ema20, borderColor: '#eab308', borderWidth: 2, pointRadius: 0, tension: 0.4, yAxisID: 'y' })
if (indicators.ema50) datasets.push({ type: 'line', data: ema50, borderColor: '#3b82f6', borderWidth: 2, pointRadius: 0, tension: 0.4, yAxisID: 'y' })
if (indicators.ema200) datasets.push({ type: 'line', data: ema200, borderColor: '#ffffff', borderWidth: 2, pointRadius: 0, tension: 0.4, yAxisID: 'y' })
if (indicators.bb) { datasets.push({ type: 'line', data: bb.upper, borderColor: '#a855f7', borderWidth: 1, pointRadius: 0, yAxisID: 'y' }); datasets.push({ type: 'line', data: bb.lower, borderColor: '#a855f7', borderWidth: 1, pointRadius: 0, backgroundColor: 'rgba(168,85,247,0.1)', fill: '-1', yAxisID: 'y' }) }
const currentView = thothView[selectedAsset]
if (indicators.srZones && currentView?.support_zones) currentView.support_zones.forEach(z => datasets.push({ type: 'line', data: chartData.map(() => z.level), borderColor: 'rgba(34,197,94,0.5)', borderWidth: 2, borderDash: [5,5], pointRadius: 0, yAxisID: 'y' }))
if (indicators.srZones && currentView?.resistance_zones) currentView.resistance_zones.forEach(z => datasets.push({ type: 'line', data: chartData.map(() => z.level), borderColor: 'rgba(239,68,68,0.5)', borderWidth: 2, borderDash: [5,5], pointRadius: 0, yAxisID: 'y' }))
if (indicators.patterns && patterns.length) datasets.push({ type: 'scatter', data: patterns.map(p => ({ x: labels[p.index], y: chartData[p.index]?.high || 0 })), backgroundColor: patterns.map(p => p.type === 'breakout' ? '#22c55e' : p.type === 'breakdown' ? '#ef4444' : '#fbbf24'), pointStyle: 'star', pointRadius: 10, yAxisID: 'y' })
if (indicators.thoth && currentView?.bias_history) {
const bc = currentView.bias_history.map(b => { let ci = 0, md = Infinity; chartData.forEach((d, i) => { const df = Math.abs(d.time - b.time); if (df < md) { md = df; ci = i } }); return { idx: ci, bias: b.bias } })
datasets.push({ type: 'scatter', data: bc.map(b => ({ x: labels[b.idx], y: chartData[b.idx]?.high || 0 })), backgroundColor: bc.map(b => b.bias === 'bullish' ? '#fbbf24' : b.bias === 'bearish' ? '#ef4444' : '#a0a0a0'), pointStyle: 'rectRot', pointRadius: 12, yAxisID: 'y' })
}
const scales: any = { x: { display: true, grid: { color: '#1a1a2e' }, ticks: { color: '#a0a0a0', maxTicksLimit: 12 } }, y: { position: 'right', min: minP - pad, max: maxP + pad, grid: { color: '#1a1a2e' }, ticks: { color: '#a0a0a0', callback: (v: any) => '$' + v.toFixed(0) } } }
if (indicators.volume && chartData[0]?.volume) scales.y_vol = { display: false, max: Math.max(...chartData.map(d => d.volume || 0)) * 3 }
mainChartRefInstance.current = new window.Chart(ctx, { type: 'bar', data: { labels, datasets }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, plugins: { legend: { display: false }, tooltip: { backgroundColor: '#1a1a2e', titleColor: '#fff', bodyColor: '#a0a0a0', borderColor: '#2a2a4e', borderWidth: 1 } }, scales } })
}
const renderRSIChart = () => {
if (!rsiChartRef.current) return
if (rsiChartRefInstance.current) rsiChartRefInstance.current.destroy()
const ctx = rsiChartRef.current.getContext('2d')
if (!ctx) return
const rsi = calculateRSI(chartData.map(d => d.close), 14)
rsiChartRefInstance.current = new window.Chart(ctx, { type: 'line', data: { labels: chartData.map(d => d.time), datasets: [{ data: rsi, borderColor: '#a0a0a0', borderWidth: 2, pointRadius: 0, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { min: 0, max: 100, position: 'right', grid: { color: '#1a1a2e' }, ticks: { color: '#a0a0a0' } } } } })
}
const renderMACDChart = () => {
if (!macdChartRef.current) return
if (macdChartRefInstance.current) macdChartRefInstance.current.destroy()
const ctx = macdChartRef.current.getContext('2d')
if (!ctx) return
const { macd, signal, histogram } = calculateMACD(chartData.map(d => d.close))
macdChartRefInstance.current = new window.Chart(ctx, { type: 'bar', data: { labels: chartData.map(d => d.time), datasets: [{ data: histogram, backgroundColor: histogram.map(v => v === null ? 'transparent' : v >= 0 ? '#22c55e' : '#ef4444'), borderWidth: 0 }, { type: 'line', data: macd, borderColor: '#3b82f6', borderWidth: 2, pointRadius: 0 }, { type: 'line', data: signal, borderColor: '#f59e0b', borderWidth: 2, pointRadius: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { position: 'right', grid: { color: '#1a1a2e' }, ticks: { color: '#a0a0a0' } } } } })
}
const closedTrades = trades.filter(t => t.result === 'win' || t.result === 'loss')
const wins = closedTrades.filter(t => t.result === 'win').length, winRate = closedTrades.length ? Math.round(wins / closedTrades.length * 100) : 0, totalPnl = closedTrades.reduce((s, t) => s + (t.pnl || 0), 0), avgRr = closedTrades.length ? closedTrades.reduce((s, t) => s + (t.rr || 0), 0) / closedTrades.length : 0
const cv = thothView[selectedAsset]
const getTE = (t: string) => t === 'uptrend' ? '🟢' : t === 'downtrend' ? '🔴' : '⚪️'
const getBC = (b: string) => b === 'bullish' ? 'text-green-400' : b === 'bearish' ? 'text-red-400' : 'text-yellow-400'
return (
<div className="space-y-4">
<div className="flex gap-4 justify-between flex-wrap">
<div className="flex gap-2">{(['BTC', 'SOL', 'ETH'] as const).map(a => <button key={a} onClick={() => setSelectedAsset(a)} className={`px-4 py-2 rounded-lg font-medium ${selectedAsset === a ? 'bg-brand-pink text-white' : 'bg-white/10 text-white/70'}`}>{a}</button>)}</div>
<div className="flex gap-2">{(['15m', '1h', '4h', '1D'] as const).map(tf => <button key={tf} onClick={() => setSelectedTimeframe(tf)} className={`px-3 py-1 rounded text-sm ${selectedTimeframe === tf ? 'bg-green-500/20 text-green-400 border border-green-500/30' : 'bg-white/10 text-white/50'}`}>{tf}</button>)}</div>
</div>
<div className="flex gap-2 items-center"><span className="text-white/50 text-sm">Compare:</span><button onClick={() => setSecondTimeframe(null)} className={`px-2 py-1 rounded text-xs ${!secondTimeframe ? 'bg-brand-pink text-white' : 'bg-white/10 text-white/50'}`}>None</button>{(['15m', '1h', '4h', '1D'] as const).map(tf => <button key={tf} onClick={() => setSecondTimeframe(tf)} className={`px-2 py-1 rounded text-xs ${secondTimeframe === tf ? 'bg-brand-pink text-white' : 'bg-white/10 text-white/50'}`}>{tf}</button>)}</div>
<div className="flex gap-2 flex-wrap">{(Object.keys(indicators) as (keyof IndicatorState)[]).map(k => <button key={k} onClick={() => toggleIndicator(k)} className={`px-3 py-1 rounded text-sm ${indicators[k] ? 'bg-brand-pink text-white' : 'bg-white/10 text-white/50'}`}>{k === 'thoth' ? '👁 THOTH' : k === 'srZones' ? 'S/R' : k === 'volume' ? 'VOL' : k === 'news' ? 'NEWS' : k === 'patterns' ? 'PATTERNS' : k === 'fib' ? 'FIB' : k === 'countdown' ? '⏱️' : k === 'calendar' ? '📅' : k === 'correlation' ? '📊 CORR' : k === 'funding' ? '💰 FUND' : k.toUpperCase()}</button>)}</div>
<div className="flex justify-between p-4 rounded-lg bg-black/50 border border-white/10"><div><span className="text-2xl font-bold">{selectedAsset}/USD</span></div><div className="text-right"><div className="text-2xl font-bold">${priceData.price.toLocaleString()}</div><div className={`text-sm ${priceData.change24h >= 0 ? 'text-green-400' : 'text-red-400'}`}>{priceData.change24h >= 0 ? '↑' : '↓'} {Math.abs(priceData.change24h).toFixed(2)}%</div></div></div>
{cv && <div className="rounded-lg border border-brand-pink/30 bg-brand-pink/5 p-4"><div className="flex items-center gap-2 mb-3"><span className="text-xl">👁</span><h3 className="font-bold text-brand-pink">THOTH'S VIEW</h3><span className="text-xs text-white/40 ml-auto">{new Date(cv.updated_at).toLocaleString()}</span></div><p className="text-white/90 mb-4 italic">"{cv.thought}"</p><div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm"><div><p className="text-white/50 text-xs">Trend</p><p className="font-medium">{getTE(cv.trend)} {cv.trend}</p></div><div><p className="text-white/50 text-xs">Phase</p><p className="font-medium">{cv.phase}</p></div><div><p className="text-white/50 text-xs">Key Level</p><p className="font-medium">${cv.key_level.toLocaleString()}</p></div><div><p className="text-white/50 text-xs">Bias</p><p className={`font-medium ${getBC(cv.bias)}`}>{cv.bias.toUpperCase()} ({cv.confidence}/10)</p></div></div></div>}
<div className="relative rounded-lg bg-black/50 border border-white/10" style={{ height: '400px' }}>{loading && <div className="absolute inset-0 flex items-center justify-center bg-black/50 z-10"><span className="text-white/50">Loading...</span></div>}<canvas ref={mainChartRef} /></div>
{secondTimeframe && secondChartData.length > 0 && <div className="rounded-lg bg-black/50 border border-white/10" style={{ height: '200px' }}><div className="px-4 py-2 border-b border-white/10 text-sm font-bold">{secondTimeframe} Chart</div><SecondChart data={secondChartData} /></div>}
{indicators.rsi && <div className="rounded-lg bg-black/50 border border-white/10" style={{ height: '150px' }}><canvas ref={rsiChartRef} /></div>}
{indicators.macd && <div className="rounded-lg bg-black/50 border border-white/10" style={{ height: '150px' }}><canvas ref={macdChartRef} /></div>}
<div className="grid grid-cols-5 gap-3">{[{ v: trades.filter(t => t.result === 'open').length, l: 'Open' }, { v: totalPnl, l: 'P&L', c: 'text-green-400' }, { v: winRate + '%', l: 'Win Rate' }, { v: closedTrades.length, l: 'Trades' }, { v: avgRr.toFixed(1) + ':1', l: 'Avg R:R' }].map((s, i) => <div key={i} className="p-3 rounded-lg bg-white/5 border border-white/10 text-center"><p className={`text-2xl font-bold ${s.c || ''}`}>{s.v}</p><p className="text-xs text-white/50">{s.l}</p></div>)}</div>
<div className="rounded-lg border border-white/10 bg-white/5 overflow-hidden"><div className="px-4 py-3 border-b border-white/10"><h3 className="font-bold">📊 Trade History</h3></div>{trades.length === 0 ? <div className="p-8 text-center text-white/50">No trades yet</div> : <div className="max-h-48 overflow-y-auto"><table className="w-full text-sm"><thead className="bg-white/5 text-white/70 sticky top-0"><tr><th className="px-4 py-2 text-left">Date</th><th className="px-4 py-2">Asset</th><th className="px-4 py-2">Dir</th><th className="px-4 py-2 text-right">Entry</th><th className="px-4 py-2 text-right">SL</th><th className="px-4 py-2 text-right">TP</th><th className="px-4 py-2 text-right">R:R</th><th className="px-4 py-2 text-right">Result</th></tr></thead><tbody>{trades.map((t, i) => <tr key={i} className={`border-t border-white/5 ${t.result === 'win' ? 'bg-green-500/10' : t.result === 'loss' ? 'bg-red-500/10' : 'bg-yellow-500/10'}`}><td className="px-4 py-2">{t.date}</td><td className="px-4 py-2 text-center">{t.pair}</td><td className="px-4 py-2 text-center"><span className={t.direction === 'long' ? 'text-green-400' : 'text-red-400'}>{t.direction.toUpperCase()}</span></td><td className="px-4 py-2 text-right">${t.entry.toLocaleString()}</td><td className="px-4 py-2 text-right text-red-400">${t.stopLoss.toLocaleString()}</td><td className="px-4 py-2 text-right text-green-400">${t.takeProfit.toLocaleString()}</td><td className="px-4 py-2 text-right">{t.rr?.toFixed(1)}:1</td><td className="px-4 py-2 text-right">{t.result === 'win' ? '' : t.result === 'loss' ? '' : ''}</td></tr>)}</tbody></table></div>}</div>
</div>
)
}
function SecondChart({ data }: { data: ChartData[] }) {
const ref = useRef<HTMLCanvasElement>(null)
useEffect(() => {
if (!ref.current || !data.length) return
const ctx = ref.current.getContext('2d')
if (!ctx) return
if (ref.current.chart) ref.current.chart.destroy()
const chart = new window.Chart(ctx, { type: 'line', data: { labels: data.map(d => new Date(d.time).toLocaleTimeString()), datasets: [{ data: data.map(d => d.close), borderColor: '#a0a0a0', borderWidth: 2, pointRadius: 0, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { position: 'right', grid: { color: '#1a1a2e' }, ticks: { color: '#a0a0a0' } } } } })
ref.current.chart = chart
}, [data])
return <canvas ref={ref} />
}