stock-bot/apps/stock/web-app/src/features/charts/hooks/useChartData.ts
2025-07-02 19:58:43 -04:00

145 lines
No EOL
4.1 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { ChartDataService } from '../services/chartDataService';
import type { CandlestickData, MarketData, ChartInterval } from '../types';
interface UseChartDataOptions {
symbol: string;
interval: ChartInterval;
enableRealtime?: boolean;
}
export function useChartData({ symbol, interval, enableRealtime = true }: UseChartDataOptions) {
const [data, setData] = useState<CandlestickData[]>([]);
const [marketData, setMarketData] = useState<MarketData | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const unsubscribeRef = useRef<(() => void) | null>(null);
// Fetch historical data
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
setIsLoading(true);
setError(null);
const { start, end } = ChartDataService.getDefaultDateRange(interval);
const historicalData = await ChartDataService.getHistoricalData(
symbol,
interval,
start,
end
);
if (!cancelled) {
setData(historicalData);
}
// Fetch current market data
const currentData = await ChartDataService.getRealtimeData(symbol);
if (!cancelled) {
setMarketData(currentData);
}
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err.message : 'Failed to load chart data');
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [symbol, interval]);
// Subscribe to real-time updates
useEffect(() => {
if (!enableRealtime || isLoading || error) return;
const handleRealtimeUpdate = (newCandle: CandlestickData) => {
setData(prevData => {
const lastCandle = prevData[prevData.length - 1];
// Check if this is an update to the current candle or a new one
if (lastCandle && lastCandle.time === newCandle.time) {
// Update existing candle
return [...prevData.slice(0, -1), newCandle];
} else {
// Add new candle
return [...prevData, newCandle];
}
});
// Update market data
setMarketData(prev => {
if (!prev) return prev;
const change = newCandle.close - (prev.previousClose || prev.price);
const changePercent = (change / (prev.previousClose || prev.price)) * 100;
return {
...prev,
price: newCandle.close,
change,
changePercent,
high: Math.max(prev.high, newCandle.high),
low: Math.min(prev.low, newCandle.low),
volume: prev.volume + (newCandle.volume || 0),
timestamp: new Date().toISOString(),
};
});
};
const handleError = (error: Error) => {
console.error('Real-time data error:', error);
};
unsubscribeRef.current = ChartDataService.subscribeToRealtime(
symbol,
handleRealtimeUpdate,
handleError
);
return () => {
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
};
}, [symbol, enableRealtime, isLoading, error]);
const refresh = async () => {
const { start, end } = ChartDataService.getDefaultDateRange(interval);
try {
setIsLoading(true);
const newData = await ChartDataService.getHistoricalData(symbol, interval, start, end);
setData(newData);
const currentData = await ChartDataService.getRealtimeData(symbol);
setMarketData(currentData);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to refresh data');
} finally {
setIsLoading(false);
}
};
return {
data,
marketData,
isLoading,
error,
refresh,
};
}