import { useQuery } from '@tanstack/react-query'
import {
Box,
Paper,
Typography,
Grid,
Chip,
Tooltip,
CircularProgress,
} from '@mui/material'
import {
TrendingUp,
TrendingDown,
SwapHoriz,
} from '@mui/icons-material'
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip as RechartsTooltip,
ResponsiveContainer,
ReferenceLine,
} from 'recharts'
import { marketDataApi } from '../api/marketData'
interface SpreadChartProps {
primarySymbol: string
secondarySymbol: string
lookbackPeriod?: number
zScoreThreshold?: number
}
export default function SpreadChart({
primarySymbol,
secondarySymbol,
lookbackPeriod = 20,
zScoreThreshold = 2.0,
}: SpreadChartProps) {
// Fetch spread data from backend
const { data: spreadResponse, isLoading, error } = useQuery({
queryKey: ['spread-data', primarySymbol, secondarySymbol, lookbackPeriod],
queryFn: () => marketDataApi.getSpreadData(
primarySymbol,
secondarySymbol,
'1h',
lookbackPeriod + 30
),
refetchInterval: 60000, // Refresh every minute
staleTime: 30000,
})
const spreadData = spreadResponse?.data ?? []
const currentZScore = spreadResponse?.currentZScore ?? 0
const currentSpread = spreadResponse?.currentSpread ?? 0
// Determine signal state
const getSignalState = () => {
if (currentZScore > zScoreThreshold) {
return { label: `Short Spread (Sell ${primarySymbol})`, color: 'error' as const, icon: }
} else if (currentZScore < -zScoreThreshold) {
return { label: `Long Spread (Buy ${primarySymbol})`, color: 'success' as const, icon: }
}
return { label: 'Neutral (No Signal)', color: 'default' as const, icon: }
}
const signalState = getSignalState()
if (isLoading) {
return (
Loading spread data...
)
}
if (error) {
return (
Failed to load spread data: {(error as Error).message}
)
}
return (
Pairs Trading: {primarySymbol} / {secondarySymbol}
Statistical Arbitrage - Spread Analysis
{/* Key Metrics */}
Current Spread
{currentSpread?.toFixed(4) ?? 'N/A'}
zScoreThreshold
? (currentZScore > 0 ? 'error.dark' : 'success.dark')
: 'background.default',
textAlign: 'center',
transition: 'background-color 0.3s',
}}
>
Z-Score
zScoreThreshold ? 'white' : 'inherit'
}}
>
{currentZScore?.toFixed(2) ?? 'N/A'}
Threshold
±{zScoreThreshold}
{/* Z-Score Visual Gauge */}
-{zScoreThreshold} (Buy)
0 (Neutral)
+{zScoreThreshold} (Sell)
{/* Threshold zones */}
{/* Current position indicator */}
zScoreThreshold
? (currentZScore > 0 ? 'error.main' : 'success.main')
: 'primary.main',
borderRadius: 1,
}} />
{/* Spread Chart */}
Spread History (Ratio: {primarySymbol} / {secondarySymbol})
new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
stroke="rgba(255,255,255,0.5)"
/>
new Date(t).toLocaleString()}
/>
{/* Z-Score Chart */}
Z-Score History
new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
stroke="rgba(255,255,255,0.5)"
/>
new Date(t).toLocaleString()}
/>
{/* Threshold lines */}
)
}