backtest work
This commit is contained in:
parent
143e2e1678
commit
55b4ca78c9
6 changed files with 427 additions and 129 deletions
|
|
@ -11,6 +11,16 @@ export interface ChartData {
|
|||
volume?: number;
|
||||
}
|
||||
|
||||
export interface TradeMarker {
|
||||
time: number;
|
||||
position: 'aboveBar' | 'belowBar';
|
||||
color: string;
|
||||
shape: 'arrowUp' | 'arrowDown';
|
||||
text: string;
|
||||
id?: string;
|
||||
price?: number;
|
||||
}
|
||||
|
||||
export interface ChartProps {
|
||||
data: ChartData[];
|
||||
height?: number;
|
||||
|
|
@ -23,6 +33,7 @@ export interface ChartProps {
|
|||
color?: string;
|
||||
lineWidth?: number;
|
||||
}>;
|
||||
tradeMarkers?: TradeMarker[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +44,7 @@ export function Chart({
|
|||
showVolume = true,
|
||||
theme = 'dark',
|
||||
overlayData = [],
|
||||
tradeMarkers = [],
|
||||
className = '',
|
||||
}: ChartProps) {
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -94,16 +106,18 @@ export function Chart({
|
|||
|
||||
chartRef.current = chart;
|
||||
|
||||
// Filter and validate data
|
||||
// Filter, validate and sort 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;
|
||||
});
|
||||
return rawData
|
||||
.filter((item, index) => {
|
||||
if (seen.has(item.time)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(item.time);
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => a.time - b.time); // Ensure ascending time order
|
||||
};
|
||||
|
||||
// Create main series
|
||||
|
|
@ -193,7 +207,8 @@ export function Chart({
|
|||
}
|
||||
|
||||
// Filter out duplicate timestamps and ensure ascending order
|
||||
const uniqueData = overlay.data.reduce((acc: any[], curr) => {
|
||||
const sortedData = [...overlay.data].sort((a, b) => a.time - b.time);
|
||||
const uniqueData = sortedData.reduce((acc: any[], curr) => {
|
||||
if (!acc.length || curr.time > acc[acc.length - 1].time) {
|
||||
acc.push(curr);
|
||||
}
|
||||
|
|
@ -203,6 +218,22 @@ export function Chart({
|
|||
overlaySeriesRef.current.set(overlay.name, series);
|
||||
});
|
||||
|
||||
// Add trade markers
|
||||
if (tradeMarkers.length > 0 && mainSeriesRef.current) {
|
||||
// Sort markers by time to ensure they're in ascending order
|
||||
const sortedMarkers = [...tradeMarkers].sort((a, b) => a.time - b.time);
|
||||
const markers: LightweightCharts.SeriesMarker<LightweightCharts.Time>[] = sortedMarkers.map(marker => ({
|
||||
time: marker.time as LightweightCharts.Time,
|
||||
position: marker.position,
|
||||
color: marker.color,
|
||||
shape: marker.shape as LightweightCharts.SeriesMarkerShape,
|
||||
text: marker.text,
|
||||
id: marker.id,
|
||||
size: 1
|
||||
}));
|
||||
mainSeriesRef.current.setMarkers(markers);
|
||||
}
|
||||
|
||||
// Fit content with a slight delay to ensure all series are loaded
|
||||
setTimeout(() => {
|
||||
chart.timeScale().fitContent();
|
||||
|
|
@ -251,7 +282,7 @@ export function Chart({
|
|||
chart.remove();
|
||||
}
|
||||
};
|
||||
}, [data, height, type, showVolume, theme, overlayData]);
|
||||
}, [data, height, type, showVolume, theme, overlayData, tradeMarkers]);
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ interface BacktestResultsProps {
|
|||
}
|
||||
|
||||
export function BacktestResults({ status, results, currentTime }: BacktestResultsProps) {
|
||||
const [selectedSymbol, setSelectedSymbol] = useState<string>('');
|
||||
|
||||
if (status === 'idle') {
|
||||
return (
|
||||
<div className="bg-surface-secondary p-8 rounded-lg border border-border h-full flex items-center justify-center">
|
||||
|
|
@ -112,16 +114,55 @@ export function BacktestResults({ status, results, currentTime }: BacktestResult
|
|||
|
||||
{/* Performance Chart */}
|
||||
<div className="bg-surface-secondary p-4 rounded-lg border border-border">
|
||||
<h3 className="text-base font-medium text-text-primary mb-4">
|
||||
Portfolio Performance
|
||||
</h3>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-base font-medium text-text-primary">
|
||||
Portfolio Performance
|
||||
</h3>
|
||||
{results.ohlcData && Object.keys(results.ohlcData).length > 1 && (
|
||||
<select
|
||||
value={selectedSymbol || Object.keys(results.ohlcData)[0]}
|
||||
onChange={(e) => setSelectedSymbol(e.target.value)}
|
||||
className="px-3 py-1 text-sm bg-background border border-border rounded-md text-text-primary focus:outline-none focus:ring-1 focus:ring-primary-500"
|
||||
>
|
||||
{Object.keys(results.ohlcData).map(symbol => (
|
||||
<option key={symbol} value={symbol}>{symbol}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
{(() => {
|
||||
const hasOhlcData = results.ohlcData && Object.keys(results.ohlcData).length > 0;
|
||||
const hasEquityData = results.equity && results.equity.length > 0;
|
||||
|
||||
if (hasOhlcData) {
|
||||
const firstSymbol = Object.keys(results.ohlcData)[0];
|
||||
const ohlcData = results.ohlcData[firstSymbol];
|
||||
const activeSymbol = selectedSymbol || Object.keys(results.ohlcData)[0];
|
||||
const ohlcData = results.ohlcData[activeSymbol];
|
||||
|
||||
// Create trade markers for the selected symbol
|
||||
const tradeMarkers = results.trades
|
||||
.filter(trade => trade.symbol === activeSymbol)
|
||||
.map(trade => ({
|
||||
time: Math.floor(new Date(trade.entryDate).getTime() / 1000),
|
||||
position: 'belowBar' as const,
|
||||
color: '#10b981',
|
||||
shape: 'arrowUp' as const,
|
||||
text: `Buy ${trade.quantity}@${trade.entryPrice.toFixed(2)}`,
|
||||
id: `${trade.id}-entry`,
|
||||
price: trade.entryPrice
|
||||
}))
|
||||
.concat(
|
||||
results.trades
|
||||
.filter(trade => trade.symbol === activeSymbol && trade.exitDate)
|
||||
.map(trade => ({
|
||||
time: Math.floor(new Date(trade.exitDate!).getTime() / 1000),
|
||||
position: 'aboveBar' as const,
|
||||
color: '#ef4444',
|
||||
shape: 'arrowDown' as const,
|
||||
text: `Sell ${trade.quantity}@${trade.exitPrice.toFixed(2)} (${trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)})`,
|
||||
id: `${trade.id}-exit`,
|
||||
price: trade.exitPrice
|
||||
}))
|
||||
);
|
||||
|
||||
return (
|
||||
<Chart
|
||||
|
|
@ -141,6 +182,7 @@ export function BacktestResults({ status, results, currentTime }: BacktestResult
|
|||
lineWidth: 3
|
||||
}
|
||||
] : []}
|
||||
tradeMarkers={tradeMarkers}
|
||||
className="rounded"
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue