Backtest Results Visualization Development
A backtest itself is just numbers: equity curve, drawdown, Sharpe, win rate, list of trades. Without proper visualization, it's unusable for analysis. Developers of algorithmic trading platforms often underestimate this part and end up with a table of thousands of rows where it's impossible to understand why the strategy failed in a specific period.
Key Visualization Components
Equity Curve—the primary chart showing capital growth over time. Built as a line chart using TradingView Lightweight Charts or Recharts. Always show the benchmark (BTC/ETH hold or index) on the same time scale. Without a benchmark, the equity curve is meaningless—the strategy could have grown 40% while the market grew 200%.
Drawdown Chart—separate graph below the equity curve. Shows the current drawdown from the last historical peak as a percentage. Calculated as:
const computeDrawdown = (equity: number[]): number[] => {
let peak = equity[0];
return equity.map((value) => {
if (value > peak) peak = value;
return ((value - peak) / peak) * 100;
});
};
It's critical to synchronize the time scale between equity curve and drawdown—the user must see simultaneously: "there was a -35% drawdown here, and here's what happened with price at that moment."
Trades Table—virtualized list (react-virtual or TanStack Virtual) with sorting and filtering. With thousands of trades, normal DOM rendering kills performance. Columns: entry/exit date, direction (long/short), position size, entry/exit price, P&L in absolute terms and percentage, commissions.
Aggregated Statistics
Break metrics into several groups:
| Group | Metrics |
|---|---|
| Returns | Total Return, CAGR, Monthly Returns heatmap |
| Risk | Max Drawdown, Avg Drawdown Duration, VaR 95% |
| Quality | Sharpe Ratio, Sortino Ratio, Calmar Ratio |
| Trading | Win Rate, Profit Factor, Avg Win/Loss, Max Consecutive Losses |
Monthly Returns Heatmap is especially useful. A year × month matrix where each cell is colored from red (loss) to green (profit). Instantly reveals seasonality and problem periods.
// Data structure for heatmap
type MonthlyReturn = {
year: number;
month: number; // 0-11
return: number; // percentage
};
Implemented as a custom SVG component or library like @nivo/heatmap.
Interactivity
Critical feature: clicking any point on the equity curve should reveal the list of trades from that period. Clicking a trade row—highlights it on the price chart with entry and exit markers.
This requires bidirectional binding between components: selection state in a shared store (Zustand or Recoil), subscription to hover/click events on the chart via LWC API, dynamic table filtering.
Export and Sharing
Users want to share results. Minimal set: CSV export (trades table), PDF export with key charts and metrics (html2canvas + jsPDF), permalink to a specific backtest via hash-based state in URL.
Performance with Large Datasets
With 50k+ data points on the chart, native rendering starts to lag. Strategies:
- Decimation—on zoom-out show aggregated data (OHLC per day instead of per minute), on zoom-in—full detail
- Web Worker—compute metrics and transform data in a worker to avoid blocking main thread
-
Incremental updates—if backtest runs in real-time with streaming data, update the last point via
series.update()instead of fullsetData







