Backtest Analytics Dashboard Development
Typical problem: a strategist runs a backtest in Python (backtrader, bt, vectorbt), gets JSON with thousands of rows of OHLCV and signals, and sends it to Excel. A proper backtest dashboard isn't about "pretty charts"—it's a tool for quickly identifying problems: overfitting, survivorship bias, incorrect slippage. Visualization should make problems obvious.
Key Metrics and Their Display
Equity Curve
Main chart—equity curve with drawdown panel underneath. Not just a balance line, but:
- Equity line (portfolio value in USD/BTC)
- Benchmark line (buy & hold of the same asset)
- Underwater plot (current drawdown depth in %)
If the equity curve is "smooth" relative to the benchmark—possible sign of overfitting or look-ahead bias in data.
Strategy Statistics
interface BacktestStats {
totalReturn: number; // %
annualizedReturn: number; // %
sharpeRatio: number; // > 1 acceptable, > 2 good
sortinoRatio: number; // downside volatility only
maxDrawdown: number; // % from peak
maxDrawdownDuration: number; // days
winRate: number; // % of profitable trades
profitFactor: number; // gross profit / gross loss
avgWin: number; // average winning trade
avgLoss: number; // average losing trade
totalTrades: number;
calmarRatio: number; // annualizedReturn / maxDrawdown
}
Display these metrics in a summary panel with large font, color-coded (red/green relative to thresholds).
Technical Stack
Frontend
React + Recharts or TradingView Lightweight Charts for financial data. TradingView Lightweight Charts—specialized library with candlestick, volume, and overlay indicator support out of the box. Recharts is more flexible for custom non-standard visualizations.
import { createChart, ColorType, LineStyle } from 'lightweight-charts';
function EquityCurve({ equityData, benchmarkData }: Props) {
const chartContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const chart = createChart(chartContainerRef.current!, {
width: chartContainerRef.current!.clientWidth,
height: 400,
layout: { background: { type: ColorType.Solid, color: '#1a1a2e' }, textColor: '#d1d5db' },
grid: { vertLines: { color: '#2d3748' }, horzLines: { color: '#2d3748' } },
timeScale: { timeVisible: true },
});
const equitySeries = chart.addLineSeries({ color: '#10b981', lineWidth: 2 });
const benchmarkSeries = chart.addLineSeries({ color: '#6366f1', lineWidth: 1, lineStyle: LineStyle.Dashed });
equitySeries.setData(equityData);
benchmarkSeries.setData(benchmarkData);
chart.timeScale().fitContent();
return () => chart.remove();
}, [equityData, benchmarkData]);
return <div ref={chartContainerRef} />;
}
Trades Table
Trades table with sorting and filtering—TanStack Table (react-table v8). For thousands of rows—mandatory virtualization via @tanstack/react-virtual:
import { useVirtualizer } from '@tanstack/react-virtual';
// Render only visible rows
const rowVirtualizer = useVirtualizer({
count: trades.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40, // row height
overscan: 10,
});
Without virtualization, a 10,000-row table will freeze the UI for several seconds on render.
Data Storage and Loading
Backtest results—structured JSON. For large backtests (1m data over multiple years = tens of MB), loading everything at once isn't practical. Schema:
interface BacktestResult {
id: string;
metadata: BacktestMetadata; // strategy configuration
stats: BacktestStats; // aggregated statistics
equityCurve: TimeseriesPoint[]; // daily points (usually < 5000)
trades: Trade[]; // list of trades
// Don't store OHLCV data—load separately on demand
}
API endpoint with pagination for trades + separate endpoint for equity curve. First render—show stats + equity curve (cheap), load trades on request.
Comparing Multiple Strategies
Powerful feature—overlay equity curves of multiple backtests on one chart. User selects 2-5 strategies from a list, sees their comparison visually and in a parameters table:
interface ComparisonView {
strategies: Array<{
id: string;
name: string;
color: string;
stats: BacktestStats;
equityCurve: TimeseriesPoint[];
}>;
}
Correlation matrix between strategies—important for portfolio construction. If two strategies are highly correlated, there's no point holding both.
Monte Carlo Visualization
Monte Carlo simulation (shuffling trade order N times) shows the range of possible outcomes. Displayed as a fan chart—center line of median outcome + bands for 10th/90th percentiles:
// Generate fan chart data
const percentiles = monteCarloRuns.reduce((acc, run, idx) => {
run.forEach((point, t) => {
if (!acc[t]) acc[t] = [];
acc[t].push(point.equity);
});
return acc;
}, [] as number[][]).map(values => ({
p10: percentile(values, 10),
p50: percentile(values, 50),
p90: percentile(values, 90),
}));
Development Timeline
Equity curve + stats + trades table with virtualization — 2 days. Add strategy comparison + Monte Carlo fan chart + period filtering — 3-5 days total.







