initial backtests
This commit is contained in:
parent
fa70ada2bb
commit
5a3a23a2ba
6 changed files with 400 additions and 129 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import * as LightweightCharts from 'lightweight-charts';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
export interface ChartData {
|
||||
time: number;
|
||||
|
|
@ -40,20 +40,16 @@ export function Chart({
|
|||
const mainSeriesRef = useRef<LightweightCharts.ISeriesApi<any> | null>(null);
|
||||
const volumeSeriesRef = useRef<LightweightCharts.ISeriesApi<any> | null>(null);
|
||||
const overlaySeriesRef = useRef<Map<string, LightweightCharts.ISeriesApi<any>>>(new Map());
|
||||
|
||||
// Debug logging
|
||||
console.log('Chart - data received:', data);
|
||||
console.log('Chart - data length:', data?.length);
|
||||
console.log('Chart - data type:', Array.isArray(data) ? 'array' : typeof data);
|
||||
console.log('Chart - first data point:', data?.[0]);
|
||||
|
||||
// Reset zoom handler
|
||||
const resetZoom = useCallback(() => {
|
||||
if (chartRef.current) {
|
||||
chartRef.current.timeScale().fitContent();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartContainerRef.current || !data || !data.length) {
|
||||
console.log('Chart - early return:', {
|
||||
hasContainer: !!chartContainerRef.current,
|
||||
hasData: !!data,
|
||||
dataLength: data?.length
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -89,11 +85,27 @@ export function Chart({
|
|||
borderColor: theme === 'dark' ? '#1f2937' : '#e5e7eb',
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
rightOffset: 12,
|
||||
barSpacing: 3,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
},
|
||||
});
|
||||
|
||||
chartRef.current = chart;
|
||||
|
||||
// Filter and validate data
|
||||
const validateAndFilterData = (rawData: any[]) => {
|
||||
const seen = new Set<number>();
|
||||
return rawData.filter((item, index) => {
|
||||
if (seen.has(item.time)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(item.time);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// Create main series
|
||||
if (type === 'candlestick' && data[0].open !== undefined) {
|
||||
mainSeriesRef.current = chart.addCandlestickSeries({
|
||||
|
|
@ -104,7 +116,8 @@ export function Chart({
|
|||
wickUpColor: '#10b981',
|
||||
wickDownColor: '#ef4444',
|
||||
});
|
||||
mainSeriesRef.current.setData(data as LightweightCharts.CandlestickData[]);
|
||||
const validData = validateAndFilterData(data);
|
||||
mainSeriesRef.current.setData(validData as LightweightCharts.CandlestickData[]);
|
||||
} else if (type === 'line' || (type === 'candlestick' && data[0].value !== undefined)) {
|
||||
mainSeriesRef.current = chart.addLineSeries({
|
||||
color: '#3b82f6',
|
||||
|
|
@ -114,7 +127,8 @@ export function Chart({
|
|||
time: d.time,
|
||||
value: d.value ?? d.close ?? 0
|
||||
}));
|
||||
mainSeriesRef.current.setData(lineData);
|
||||
const validData = validateAndFilterData(lineData);
|
||||
mainSeriesRef.current.setData(validData);
|
||||
} else if (type === 'area') {
|
||||
mainSeriesRef.current = chart.addAreaSeries({
|
||||
lineColor: '#3b82f6',
|
||||
|
|
@ -126,7 +140,8 @@ export function Chart({
|
|||
time: d.time,
|
||||
value: d.value ?? d.close ?? 0
|
||||
}));
|
||||
mainSeriesRef.current.setData(areaData);
|
||||
const validData = validateAndFilterData(areaData);
|
||||
mainSeriesRef.current.setData(validData);
|
||||
}
|
||||
|
||||
// Add volume if available
|
||||
|
|
@ -177,12 +192,46 @@ export function Chart({
|
|||
});
|
||||
}
|
||||
|
||||
series.setData(overlay.data);
|
||||
// Filter out duplicate timestamps and ensure ascending order
|
||||
const uniqueData = overlay.data.reduce((acc: any[], curr) => {
|
||||
if (!acc.length || curr.time > acc[acc.length - 1].time) {
|
||||
acc.push(curr);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
series.setData(uniqueData);
|
||||
overlaySeriesRef.current.set(overlay.name, series);
|
||||
});
|
||||
|
||||
// Fit content
|
||||
chart.timeScale().fitContent();
|
||||
// Fit content with a slight delay to ensure all series are loaded
|
||||
setTimeout(() => {
|
||||
chart.timeScale().fitContent();
|
||||
|
||||
// Also set the visible range to ensure all data is shown
|
||||
if (data.length > 0) {
|
||||
const firstTime = data[0].time;
|
||||
const lastTime = data[data.length - 1].time;
|
||||
chart.timeScale().setVisibleRange({
|
||||
from: firstTime as any,
|
||||
to: lastTime as any,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Enable mouse wheel zoom and touch gestures
|
||||
chart.applyOptions({
|
||||
handleScroll: {
|
||||
mouseWheel: true,
|
||||
pressedMouseMove: true,
|
||||
horzTouchDrag: true,
|
||||
vertTouchDrag: true,
|
||||
},
|
||||
handleScale: {
|
||||
mouseWheel: true,
|
||||
pinch: true,
|
||||
axisPressedMouseMove: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Handle resize
|
||||
const handleResize = () => {
|
||||
|
|
@ -210,6 +259,13 @@ export function Chart({
|
|||
ref={chartContainerRef}
|
||||
style={{ width: '100%', height: `${height}px` }}
|
||||
/>
|
||||
<button
|
||||
onClick={resetZoom}
|
||||
className="absolute top-2 right-2 px-2 py-1 text-xs bg-surface-primary border border-border rounded hover:bg-surface-secondary transition-colors"
|
||||
title="Reset zoom"
|
||||
>
|
||||
Reset Zoom
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue