fixed up look

This commit is contained in:
Boki 2025-07-04 18:23:59 -04:00
parent d15e542f20
commit 805ce0ebf1
8 changed files with 135 additions and 72 deletions

View file

@ -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}

View file

@ -262,11 +262,13 @@ export function BacktestDetailPageV2() {
} }
return ( return (
<BacktestPlayback <div className="h-full overflow-hidden">
key={currentRun?.id || 'no-run'} // Force remount on run change <BacktestPlayback
result={runResults} key={currentRun?.id || 'no-run'} // Force remount on run change
isLoading={isLoading} result={runResults}
/> 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>

View file

@ -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,22 +108,26 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }:
</div> </div>
)} )}
<div className="flex-1"> <div className="flex-1 min-h-0">
<Chart <ChartContainer>
data={chartData.ohlcData} {(height) => (
overlayData={[ <Chart
{ data={chartData.ohlcData}
name: 'Equity', overlayData={[
data: chartData.equityData, {
color: '#3b82f6', name: 'Equity',
lineWidth: 2 data: chartData.equityData,
} color: '#3b82f6',
]} lineWidth: 2
tradeMarkers={chartData.tradeMarkers} }
height={500} ]}
chartId={`backtest-${result?.runId || 'default'}`} tradeMarkers={chartData.tradeMarkers}
key={result?.runId || 'default'} height={height}
/> chartId={`backtest-${result?.runId || 'default'}`}
key={result?.runId || 'default'}
/>
)}
</ChartContainer>
</div> </div>
</div> </div>
); );

View file

@ -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}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>
{openPositions.length > 0 && ( <div className="flex-1">
{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 && ( {openPositions.length === 0 && closedPositions.length === 0 && (
<button <p className="text-sm text-text-secondary text-center">No positions to display</p>
onClick={onExpand} )}
className="w-full text-xs text-primary-500 hover:text-primary-600 text-center pt-2 font-medium" </div>
>
View all positions
</button>
)}
{openPositions.length === 0 && closedPositions.length === 0 && ( {/* Summary metrics at bottom - similar to performance metrics */}
<p className="text-sm text-text-secondary text-center">No positions to display</p> <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> </div>
); );
} }

View file

@ -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';