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

View file

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

View file

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

View file

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

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

View file

@ -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>
{openPositions.length > 0 && (
<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>
{openPositions.length === 0 && closedPositions.length === 0 && (
<p className="text-sm text-text-secondary text-center">No positions to display</p>
)}
{/* 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>
);
}

View file

@ -8,4 +8,5 @@ export { RunsList } from './RunsList';
export { RunsListWithMetrics } from './RunsListWithMetrics';
export { CompactPerformanceMetrics } from './CompactPerformanceMetrics';
export { CompactPositionsTable } from './CompactPositionsTable';
export { PositionsSummary } from './PositionsSummary';
export { PositionsSummary } from './PositionsSummary';
export { ChartContainer } from './ChartContainer';