import React, { useMemo } from 'react' import { ComposedChart, Line, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from 'recharts' import { OHLCVPoint } from '../api/stockApi' interface Props { data: OHLCVPoint[] } // Show every Nth date label to avoid crowding function tickFormatter(value: string, index: number, total: number): string { const step = Math.max(1, Math.floor(total / 10)) if (index % step === 0) return value return '' } const CustomTooltip = ({ active, payload, label }: any) => { if (!active || !payload?.length) return null const d: OHLCVPoint = payload[0]?.payload if (!d) return null return (

{label}

O: {d.open?.toFixed(2)}

H: {d.high?.toFixed(2)}

L: {d.low?.toFixed(2)}

C: {d.close?.toFixed(2)}

{d.ma5 != null &&

MA5: {d.ma5.toFixed(2)}

} {d.ma20 != null &&

MA20: {d.ma20.toFixed(2)}

} {d.ma60 != null &&

MA60: {d.ma60.toFixed(2)}

} {d.ma120 != null &&

MA120: {d.ma120.toFixed(2)}

} {d.ma240 != null &&

MA240: {d.ma240.toFixed(2)}

}
) } export const CandlestickChart: React.FC = ({ data }) => { const len = data.length // Build Bollinger band area data: pairs of [lower, upper] for Area fill const chartData = useMemo( () => data.map((d) => ({ ...d, bb_band: d.bb_upper != null && d.bb_lower != null ? [d.bb_lower, d.bb_upper] : [null, null], })), [data] ) const allPrices = data.flatMap((d) => [d.low, d.high, d.bb_upper, d.bb_lower].filter(Boolean) as number[]) const yMin = Math.floor(Math.min(...allPrices) * 0.995) const yMax = Math.ceil(Math.max(...allPrices) * 1.005) return ( tickFormatter(val, idx, len)} interval={0} /> v.toFixed(0)} width={55} /> } /> {value}} /> {/* Bollinger Band fill — rendered first so it's behind price lines */} {/* Close price as thick line */} {/* Moving averages */} ) }