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 */} ) }