fixed up look
This commit is contained in:
parent
d15e542f20
commit
805ce0ebf1
8 changed files with 135 additions and 72 deletions
|
|
@ -24,7 +24,7 @@ export interface TradeMarker {
|
||||||
|
|
||||||
export interface ChartProps {
|
export interface ChartProps {
|
||||||
data: ChartData[];
|
data: ChartData[];
|
||||||
height?: number;
|
height?: number | string;
|
||||||
type?: 'candlestick' | 'line' | 'area';
|
type?: 'candlestick' | 'line' | 'area';
|
||||||
showVolume?: boolean;
|
showVolume?: boolean;
|
||||||
theme?: 'light' | 'dark';
|
theme?: 'light' | 'dark';
|
||||||
|
|
@ -399,7 +399,10 @@ export function Chart({
|
||||||
<div className={`relative ${className}`}>
|
<div className={`relative ${className}`}>
|
||||||
<div
|
<div
|
||||||
ref={chartContainerRef}
|
ref={chartContainerRef}
|
||||||
style={{ width: '100%', height: `${height}px` }}
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: typeof height === 'string' ? height : `${height}px`
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={resetZoom}
|
onClick={resetZoom}
|
||||||
|
|
|
||||||
|
|
@ -262,11 +262,13 @@ export function BacktestDetailPageV2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="h-full overflow-hidden">
|
||||||
<BacktestPlayback
|
<BacktestPlayback
|
||||||
key={currentRun?.id || 'no-run'} // Force remount on run change
|
key={currentRun?.id || 'no-run'} // Force remount on run change
|
||||||
result={runResults}
|
result={runResults}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
case 'trades':
|
case 'trades':
|
||||||
return (
|
return (
|
||||||
|
|
@ -361,7 +363,7 @@ export function BacktestDetailPageV2() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
<div className="flex-1 overflow-auto p-4">
|
<div className="flex-1 overflow-hidden py-4">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-4 bg-error/10 border border-error/20 rounded-lg">
|
<div className="mb-4 p-4 bg-error/10 border border-error/20 rounded-lg">
|
||||||
<p className="text-sm text-error">{error}</p>
|
<p className="text-sm text-error">{error}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { BacktestResult } from '../types/backtest.types';
|
import type { BacktestResult } from '../types/backtest.types';
|
||||||
import { useState, useMemo, memo } from 'react';
|
import { useState, useMemo, memo } from 'react';
|
||||||
import { Chart } from '../../../components/charts/Chart';
|
import { Chart } from '../../../components/charts/Chart';
|
||||||
|
import { ChartContainer } from './ChartContainer';
|
||||||
|
|
||||||
interface BacktestChartProps {
|
interface BacktestChartProps {
|
||||||
result: BacktestResult | null;
|
result: BacktestResult | null;
|
||||||
|
|
@ -107,7 +108,9 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }:
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-h-0">
|
||||||
|
<ChartContainer>
|
||||||
|
{(height) => (
|
||||||
<Chart
|
<Chart
|
||||||
data={chartData.ohlcData}
|
data={chartData.ohlcData}
|
||||||
overlayData={[
|
overlayData={[
|
||||||
|
|
@ -119,10 +122,12 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }:
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
tradeMarkers={chartData.tradeMarkers}
|
tradeMarkers={chartData.tradeMarkers}
|
||||||
height={500}
|
height={height}
|
||||||
chartId={`backtest-${result?.runId || 'default'}`}
|
chartId={`backtest-${result?.runId || 'default'}`}
|
||||||
key={result?.runId || 'default'}
|
key={result?.runId || 'default'}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</ChartContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,9 @@ export const BacktestPlayback = memo(function BacktestPlayback({ result, isLoadi
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col space-y-4">
|
<div className="h-full flex flex-col gap-4">
|
||||||
{/* Chart Section */}
|
{/* Chart Section */}
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-h-0">
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={
|
fallback={
|
||||||
<div className="h-full flex items-center justify-center bg-surface-secondary rounded-lg border border-border">
|
<div className="h-full flex items-center justify-center bg-surface-secondary rounded-lg border border-border">
|
||||||
|
|
@ -80,13 +80,9 @@ export const BacktestPlayback = memo(function BacktestPlayback({ result, isLoadi
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Performance Metrics */}
|
{/* Performance Metrics and Positions - Fixed height section */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0 grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<CompactPerformanceMetrics result={result} isLoading={isLoading} />
|
<CompactPerformanceMetrics result={result} isLoading={isLoading} />
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Positions Summary */}
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
<PositionsSummary
|
<PositionsSummary
|
||||||
openPositions={openPositions}
|
openPositions={openPositions}
|
||||||
closedPositions={closedPositions}
|
closedPositions={closedPositions}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useRef, useEffect, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface ChartContainerProps {
|
||||||
|
children: (height: number) => ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChartContainer({ children, className = '' }: ChartContainerProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [height, setHeight] = useState(500); // Default height
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { height } = entry.contentRect;
|
||||||
|
if (height > 0) {
|
||||||
|
setHeight(Math.floor(height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
// Initial measurement
|
||||||
|
const initialHeight = containerRef.current.clientHeight;
|
||||||
|
if (initialHeight > 0) {
|
||||||
|
setHeight(initialHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className={`h-full w-full ${className}`}>
|
||||||
|
{children(height)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ export function CompactPerformanceMetrics({ result, isLoading }: CompactPerforma
|
||||||
<div className="bg-surface-secondary rounded-lg border border-border p-4">
|
<div className="bg-surface-secondary rounded-lg border border-border p-4">
|
||||||
<h3 className="text-sm font-medium text-text-primary mb-3">Performance Analytics</h3>
|
<h3 className="text-sm font-medium text-text-primary mb-3">Performance Analytics</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{/* Column 1 - Return Metrics */}
|
{/* Column 1 - Return Metrics */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
|
|
@ -212,7 +212,7 @@ export function CompactPerformanceMetrics({ result, isLoading }: CompactPerforma
|
||||||
{/* Advanced Metrics - Show only if available */}
|
{/* Advanced Metrics - Show only if available */}
|
||||||
{(allMetrics.skewness !== undefined || allMetrics.kurtosis !== undefined ||
|
{(allMetrics.skewness !== undefined || allMetrics.kurtosis !== undefined ||
|
||||||
allMetrics.informationRatio !== undefined || allMetrics.kellyFraction !== undefined) && (
|
allMetrics.informationRatio !== undefined || allMetrics.kellyFraction !== undefined) && (
|
||||||
<div className="mt-3 pt-3 border-t border-border">
|
<div className="mt-2 pt-2 border-t border-border">
|
||||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||||
{allMetrics.informationRatio !== undefined && (
|
{allMetrics.informationRatio !== undefined && (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -254,7 +254,7 @@ export function CompactPerformanceMetrics({ result, isLoading }: CompactPerforma
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Additional Metrics Row */}
|
{/* Additional Metrics Row */}
|
||||||
<div className="mt-3 pt-3 border-t border-border">
|
<div className="mt-2 pt-2 border-t border-border">
|
||||||
<div className="grid grid-cols-5 gap-2 text-xs">
|
<div className="grid grid-cols-5 gap-2 text-xs">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<span className="text-text-secondary">Calmar</span>
|
<span className="text-text-secondary">Calmar</span>
|
||||||
|
|
|
||||||
|
|
@ -23,32 +23,18 @@ export function PositionsSummary({ openPositions, closedPositions, onExpand }: P
|
||||||
const currentPrice = p.currentPrice || p.lastPrice || avgPrice;
|
const currentPrice = p.currentPrice || p.lastPrice || avgPrice;
|
||||||
return sum + ((currentPrice - avgPrice) * quantity);
|
return sum + ((currentPrice - avgPrice) * quantity);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
const totalPnL = totalRealizedPnL + totalUnrealizedPnL;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-surface-secondary rounded-lg border border-border p-4">
|
<div className="bg-surface-secondary rounded-lg border border-border p-4 h-full flex flex-col">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<h3 className="text-sm font-medium text-text-primary mb-3">Positions Summary</h3>
|
||||||
<h3 className="text-sm font-medium text-text-primary">Positions Summary</h3>
|
|
||||||
<div className="flex items-center space-x-4 text-sm">
|
|
||||||
<div>
|
|
||||||
<span className="text-text-secondary">Realized:</span>
|
|
||||||
<span className={`ml-1 font-medium ${totalRealizedPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
|
||||||
${totalRealizedPnL.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-text-secondary">Unrealized:</span>
|
|
||||||
<span className={`ml-1 font-medium ${totalUnrealizedPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
|
||||||
${totalUnrealizedPnL.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
{openPositions.length > 0 && (
|
{openPositions.length > 0 && (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<h4 className="text-xs font-medium text-text-secondary mb-2">Open Positions ({openPositions.length})</h4>
|
<h4 className="text-xs font-medium text-text-secondary mb-2">Open Positions ({openPositions.length})</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{openPositions.slice(0, 5).map((position, index) => {
|
{openPositions.slice(0, 3).map((position, index) => {
|
||||||
const quantity = position.quantity || 0;
|
const quantity = position.quantity || 0;
|
||||||
const avgPrice = position.averagePrice || position.avgPrice || 0;
|
const avgPrice = position.averagePrice || position.avgPrice || 0;
|
||||||
const currentPrice = position.currentPrice || position.lastPrice || avgPrice;
|
const currentPrice = position.currentPrice || position.lastPrice || avgPrice;
|
||||||
|
|
@ -85,16 +71,16 @@ export function PositionsSummary({ openPositions, closedPositions, onExpand }: P
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{closedPositions.length > 0 && (
|
{closedPositions.length > 0 && openPositions.length === 0 && (
|
||||||
<div>
|
<div className="mb-3">
|
||||||
<h4 className="text-xs font-medium text-text-secondary mb-2">
|
<h4 className="text-xs font-medium text-text-secondary mb-2">
|
||||||
Closed Positions ({closedPositions.length})
|
Closed Positions ({closedPositions.length})
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{closedPositions.slice(0, 3).map((position, index) => (
|
{closedPositions.slice(0, 3).map((position, index) => (
|
||||||
<div key={index} className="flex items-center justify-between py-1 px-2 text-xs">
|
<div key={index} className="flex items-center justify-between py-1.5 px-2 rounded hover:bg-surface-tertiary transition-colors">
|
||||||
<span className="text-text-primary">{position.symbol}</span>
|
<span className="text-sm font-medium text-text-primary">{position.symbol}</span>
|
||||||
<span className={`font-medium ${position.realizedPnl >= 0 ? 'text-success' : 'text-error'}`}>
|
<span className={`text-sm font-medium ${position.realizedPnl >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
${position.realizedPnl.toFixed(2)}
|
${position.realizedPnl.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -103,18 +89,46 @@ export function PositionsSummary({ openPositions, closedPositions, onExpand }: P
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(openPositions.length > 5 || closedPositions.length > 3) && onExpand && (
|
|
||||||
<button
|
|
||||||
onClick={onExpand}
|
|
||||||
className="w-full text-xs text-primary-500 hover:text-primary-600 text-center pt-2 font-medium"
|
|
||||||
>
|
|
||||||
View all positions
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{openPositions.length === 0 && closedPositions.length === 0 && (
|
{openPositions.length === 0 && closedPositions.length === 0 && (
|
||||||
<p className="text-sm text-text-secondary text-center">No positions to display</p>
|
<p className="text-sm text-text-secondary text-center">No positions to display</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Summary metrics at bottom - similar to performance metrics */}
|
||||||
|
<div className="mt-3 pt-3 border-t border-border">
|
||||||
|
<div className="grid grid-cols-3 gap-2 text-xs">
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="text-text-secondary">Unrealized</span>
|
||||||
|
<div className={`font-medium ${totalUnrealizedPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
${totalUnrealizedPnL.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="text-text-secondary">Realized</span>
|
||||||
|
<div className={`font-medium ${totalRealizedPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
${totalRealizedPnL.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="text-text-secondary">Total P&L</span>
|
||||||
|
<div className={`font-medium ${totalPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
${totalPnL.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{((openPositions.length > 3) ||
|
||||||
|
(closedPositions.length > 3 && openPositions.length === 0)) && onExpand && (
|
||||||
|
<button
|
||||||
|
onClick={onExpand}
|
||||||
|
className="w-full text-xs text-primary-500 hover:text-primary-600 text-center pt-2 font-medium"
|
||||||
|
>
|
||||||
|
View all {openPositions.length + closedPositions.length} positions
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -9,3 +9,4 @@ export { RunsListWithMetrics } from './RunsListWithMetrics';
|
||||||
export { CompactPerformanceMetrics } from './CompactPerformanceMetrics';
|
export { CompactPerformanceMetrics } from './CompactPerformanceMetrics';
|
||||||
export { CompactPositionsTable } from './CompactPositionsTable';
|
export { CompactPositionsTable } from './CompactPositionsTable';
|
||||||
export { PositionsSummary } from './PositionsSummary';
|
export { PositionsSummary } from './PositionsSummary';
|
||||||
|
export { ChartContainer } from './ChartContainer';
|
||||||
Loading…
Add table
Add a link
Reference in a new issue