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 {
|
||||
data: ChartData[];
|
||||
height?: number;
|
||||
height?: number | string;
|
||||
type?: 'candlestick' | 'line' | 'area';
|
||||
showVolume?: boolean;
|
||||
theme?: 'light' | 'dark';
|
||||
|
|
@ -399,7 +399,10 @@ export function Chart({
|
|||
<div className={`relative ${className}`}>
|
||||
<div
|
||||
ref={chartContainerRef}
|
||||
style={{ width: '100%', height: `${height}px` }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: typeof height === 'string' ? height : `${height}px`
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={resetZoom}
|
||||
|
|
|
|||
|
|
@ -262,11 +262,13 @@ export function BacktestDetailPageV2() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden">
|
||||
<BacktestPlayback
|
||||
key={currentRun?.id || 'no-run'} // Force remount on run change
|
||||
result={runResults}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'trades':
|
||||
return (
|
||||
|
|
@ -361,7 +363,7 @@ export function BacktestDetailPageV2() {
|
|||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
<div className="flex-1 overflow-hidden py-4">
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-error/10 border border-error/20 rounded-lg">
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { BacktestResult } from '../types/backtest.types';
|
||||
import { useState, useMemo, memo } from 'react';
|
||||
import { Chart } from '../../../components/charts/Chart';
|
||||
import { ChartContainer } from './ChartContainer';
|
||||
|
||||
interface BacktestChartProps {
|
||||
result: BacktestResult | null;
|
||||
|
|
@ -107,7 +108,9 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }:
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 min-h-0">
|
||||
<ChartContainer>
|
||||
{(height) => (
|
||||
<Chart
|
||||
data={chartData.ohlcData}
|
||||
overlayData={[
|
||||
|
|
@ -119,10 +122,12 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }:
|
|||
}
|
||||
]}
|
||||
tradeMarkers={chartData.tradeMarkers}
|
||||
height={500}
|
||||
height={height}
|
||||
chartId={`backtest-${result?.runId || 'default'}`}
|
||||
key={result?.runId || 'default'}
|
||||
/>
|
||||
)}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ export const BacktestPlayback = memo(function BacktestPlayback({ result, isLoadi
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col space-y-4">
|
||||
<div className="h-full flex flex-col gap-4">
|
||||
{/* Chart Section */}
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 min-h-0">
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* Performance Metrics */}
|
||||
<div className="flex-shrink-0">
|
||||
{/* Performance Metrics and Positions - Fixed height section */}
|
||||
<div className="flex-shrink-0 grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<CompactPerformanceMetrics result={result} isLoading={isLoading} />
|
||||
</div>
|
||||
|
||||
{/* Positions Summary */}
|
||||
<div className="flex-shrink-0">
|
||||
<PositionsSummary
|
||||
openPositions={openPositions}
|
||||
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">
|
||||
<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 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
|
|
@ -212,7 +212,7 @@ export function CompactPerformanceMetrics({ result, isLoading }: CompactPerforma
|
|||
{/* Advanced Metrics - Show only if available */}
|
||||
{(allMetrics.skewness !== undefined || allMetrics.kurtosis !== 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">
|
||||
{allMetrics.informationRatio !== undefined && (
|
||||
<div className="text-center">
|
||||
|
|
@ -254,7 +254,7 @@ export function CompactPerformanceMetrics({ result, isLoading }: CompactPerforma
|
|||
)}
|
||||
|
||||
{/* 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="text-center">
|
||||
<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;
|
||||
return sum + ((currentPrice - avgPrice) * quantity);
|
||||
}, 0);
|
||||
const totalPnL = totalRealizedPnL + totalUnrealizedPnL;
|
||||
|
||||
return (
|
||||
<div className="bg-surface-secondary rounded-lg border border-border p-4">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<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="bg-surface-secondary rounded-lg border border-border p-4 h-full flex flex-col">
|
||||
<h3 className="text-sm font-medium text-text-primary mb-3">Positions Summary</h3>
|
||||
|
||||
<div className="flex-1">
|
||||
{openPositions.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<h4 className="text-xs font-medium text-text-secondary mb-2">Open Positions ({openPositions.length})</h4>
|
||||
<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 avgPrice = position.averagePrice || position.avgPrice || 0;
|
||||
const currentPrice = position.currentPrice || position.lastPrice || avgPrice;
|
||||
|
|
@ -85,16 +71,16 @@ export function PositionsSummary({ openPositions, closedPositions, onExpand }: P
|
|||
</div>
|
||||
)}
|
||||
|
||||
{closedPositions.length > 0 && (
|
||||
<div>
|
||||
{closedPositions.length > 0 && openPositions.length === 0 && (
|
||||
<div className="mb-3">
|
||||
<h4 className="text-xs font-medium text-text-secondary mb-2">
|
||||
Closed Positions ({closedPositions.length})
|
||||
</h4>
|
||||
<div className="space-y-1">
|
||||
{closedPositions.slice(0, 3).map((position, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-1 px-2 text-xs">
|
||||
<span className="text-text-primary">{position.symbol}</span>
|
||||
<span className={`font-medium ${position.realizedPnl >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
<div key={index} className="flex items-center justify-between py-1.5 px-2 rounded hover:bg-surface-tertiary transition-colors">
|
||||
<span className="text-sm font-medium text-text-primary">{position.symbol}</span>
|
||||
<span className={`text-sm font-medium ${position.realizedPnl >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
${position.realizedPnl.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -103,18 +89,46 @@ export function PositionsSummary({ openPositions, closedPositions, onExpand }: P
|
|||
</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 && (
|
||||
<p className="text-sm text-text-secondary text-center">No positions to display</p>
|
||||
)}
|
||||
</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 { CompactPositionsTable } from './CompactPositionsTable';
|
||||
export { PositionsSummary } from './PositionsSummary';
|
||||
export { ChartContainer } from './ChartContainer';
|
||||
Loading…
Add table
Add a link
Reference in a new issue