145 lines
No EOL
4.1 KiB
TypeScript
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,
|
|
};
|
|
} |