> ## Documentation Index
> Fetch the complete documentation index at: https://mathematicalcompany.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Plot Data

> Extract chart-ready data from backtest results for any visualization library

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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
uw = hz.underwater_curve(equity_curve)
```

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

### `histogram`

```python theme={null}
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 theme={null}
h = hz.return_distribution(equity_curve, n_bins=30)
```

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

### `pnl_distribution`

```python theme={null}
h = hz.pnl_distribution(trades, n_bins=30)
```

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

### `monthly_returns_heatmap`

```python theme={null}
hm = hz.monthly_returns_heatmap(equity_curve)
```

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

### `correlation_heatmap`

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

Returns `HeatmapData` with Pearson correlation coefficients.

### `rolling_stats`

```python theme={null}
r = hz.rolling_stats(equity_curve, window=30)
```

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

### `normalized_equity`

```python theme={null}
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 theme={null}
ts = hz.trade_scatter(trades)
```

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

### `calibration_plot`

```python theme={null}
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).
