---
title: "Plot Data"
description: "Extract chart-ready data from backtest results for any visualization library"
icon: "chart-area"
---

The plotting module processes raw backtest data into **frozen dataclasses** that matplotlib, plotly, or any charting library can consume directly. No external dependencies required.

<CardGroup cols={3}>
  <Card title="Bollinger Bands" icon="chart-line">
    Rolling mean with standard deviation envelopes
  </Card>
  <Card title="Distributions" icon="chart-bar">
    Return and PnL histograms with full statistics
  </Card>
  <Card title="Heatmaps" icon="table-cells">
    Monthly returns and correlation matrices
  </Card>
  <Card title="Underwater" icon="water">
    Per-point drawdown depth for area charts
  </Card>
  <Card title="Trade Scatter" icon="bullseye">
    Buy/sell markers with PnL attribution
  </Card>
  <Card title="PlotBundle" icon="layer-group">
    One-call extraction of all plot data
  </Card>
</CardGroup>

## Quick Start

```python
import horizon as hz
import matplotlib.pyplot as plt

result = hz.backtest(strategy, ...)
bundle = hz.from_backtest(result)

# Equity curve
fig, ax = plt.subplots()
ax.plot(bundle.equity.timestamps, bundle.equity.equity)
ax.set_title("Normalized Equity")

# Underwater chart
fig, ax = plt.subplots()
ax.fill_between(
    bundle.underwater.timestamps,
    [-d for d in bundle.underwater.drawdown_pct],
    alpha=0.4, color="red",
)
ax.set_title("Drawdown (%)")

# Return distribution
fig, ax = plt.subplots()
ax.bar(bundle.return_hist.bin_centers, bundle.return_hist.counts)
ax.set_title(f"Returns (skew={bundle.return_hist.skew:.2f})")
plt.show()
```

## One-Call Extraction

### `from_backtest`

Extract all plot data from a `BacktestResult` in a single call.

```python
bundle = hz.from_backtest(
    result,
    n_bins=30,
    rolling_window=30,
    include_bands=False,
    band_window=20,
    band_std=2.0,
    calibration_bins=10,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `result` | `BacktestResult` | required | Backtest result object |
| `n_bins` | `int` | `30` | Bins for histograms |
| `rolling_window` | `int` | `30` | Window for rolling Sharpe/Sortino |
| `include_bands` | `bool` | `False` | Add Bollinger bands to equity |
| `band_window` | `int` | `20` | Bollinger band window |
| `band_std` | `float` | `2.0` | Bollinger band std multiplier |
| `calibration_bins` | `int` | `10` | Bins for calibration plot |

Returns a `PlotBundle` containing all extracted data.

## Individual Functions

### `bollinger_bands`

```python
bands = hz.bollinger_bands(series, window=20, num_std=2.0)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `series` | `list[tuple[float, float]]` | required | (timestamp, value) pairs |
| `window` | `int` | `20` | Rolling window size |
| `num_std` | `float` | `2.0` | Standard deviations for bands |

Returns `BandData` with `timestamps`, `middle`, `upper`, `lower`, `bandwidth`.

### `underwater_curve`

```python
uw = hz.underwater_curve(equity_curve)
```

Returns `UnderwaterData` with per-point `drawdown_pct` and `peak` tracking.

### `histogram`

```python
h = hz.histogram(values, n_bins=30)
```

Returns `HistogramData` with `bin_edges`, `bin_centers`, `counts`, `frequencies`, plus `mean`, `median`, `std`, `skew`, `kurtosis`.

### `return_distribution`

```python
h = hz.return_distribution(equity_curve, n_bins=30)
```

Extracts period returns from equity curve, returns `HistogramData`.

### `pnl_distribution`

```python
h = hz.pnl_distribution(trades, n_bins=30)
```

FIFO-matches trades into round-trips, returns `HistogramData` of PnL values.

### `monthly_returns_heatmap`

```python
hm = hz.monthly_returns_heatmap(equity_curve)
```

Returns `HeatmapData` with years as rows and months (Jan-Dec) as columns.

### `correlation_heatmap`

```python
hm = hz.correlation_heatmap({"BTC": btc_returns, "ETH": eth_returns})
```

Returns `HeatmapData` with Pearson correlation coefficients.

### `rolling_stats`

```python
r = hz.rolling_stats(equity_curve, window=30)
```

Returns `RollingStatData` with `sharpe` and `sortino` as `CurveData`.

### `normalized_equity`

```python
ne = hz.normalized_equity(equity_curve, initial_capital=1000.0, include_bands=True)
```

Returns `NormalizedEquityData` with equity normalized to 1.0 and cumulative PnL.

### `trade_scatter`

```python
ts = hz.trade_scatter(trades)
```

Returns `TradeScatterData` with all `points`, `buys`, and `sells` as `TradeScatterPoint` tuples.

### `calibration_plot`

```python
cal = hz.calibration_plot(trades, outcomes, n_bins=10)
```

Returns `CalibrationPlotData` with `bin_centers`, `actual_freq`, `perfect_line`, `ece`, and `brier_score`. Uses Rust `calibration_curve()` when available, falls back to pure Python.

## Output Types

| Type | Fields |
|------|--------|
| `BandData` | `timestamps`, `middle`, `upper`, `lower`, `bandwidth` |
| `UnderwaterData` | `timestamps`, `drawdown_pct`, `peak` |
| `HistogramData` | `bin_edges`, `bin_centers`, `counts`, `frequencies`, `mean`, `median`, `std`, `skew`, `kurtosis` |
| `HeatmapData` | `values`, `row_labels`, `col_labels`, `title` |
| `CurveData` | `timestamps`, `values`, `label` |
| `NormalizedEquityData` | `timestamps`, `equity`, `cumulative_pnl`, `bands` |
| `TradeScatterPoint` | `timestamp`, `price`, `size`, `side`, `market_id`, `pnl` |
| `TradeScatterData` | `points`, `buys`, `sells` |
| `CalibrationPlotData` | `bin_centers`, `actual_freq`, `counts`, `perfect_line`, `ece`, `brier_score` |
| `RollingStatData` | `sharpe`, `sortino` |
| `PlotBundle` | `equity`, `underwater`, `return_hist`, `pnl_hist`, `monthly_heatmap`, `rolling`, `trades`, `calibration` |

All types are frozen dataclasses with tuple fields (immutable).
