docs/concepts/visualization.md
NautilusTrader provides interactive HTML tearsheets for analyzing backtest results through an extensible visualization system built on Plotly. You can generate reports with minimal code and add custom charts and themes.
The visualization system has three parts:
All visualization outputs are self-contained HTML files that can be viewed in any modern browser, shared with stakeholders, or archived for future reference.
:::note
The visualization system requires the visualization extra. It installs Pandas for
DataFrame handling, Plotly for interactive figures, and Kaleido for static image export:
uv pip install "nautilus_trader[visualization]"
:::
A tearsheet is a performance report that combines multiple charts and statistics into a single interactive visualization. Tearsheets are generated after completing a backtest run and provide immediate visual feedback on strategy performance.
Generate a tearsheet with default settings:
from nautilus_trader.analysis import create_tearsheet
from nautilus_trader.backtest.engine import BacktestEngine
# After running your backtest
engine.run()
# Generate tearsheet
create_tearsheet(
engine=engine,
output_path="backtest_results.html",
)
This produces an HTML file with all default charts, using the light theme and automatic
layout. Open backtest_results.html in your browser to view the interactive tearsheet.
Control which charts appear and how they're styled:
from nautilus_trader.analysis import TearsheetConfig
from nautilus_trader.analysis import TearsheetDrawdownChart
from nautilus_trader.analysis import TearsheetEquityChart
from nautilus_trader.analysis import TearsheetRunInfoChart
from nautilus_trader.analysis import TearsheetStatsTableChart
config = TearsheetConfig(
charts=[
TearsheetRunInfoChart(),
TearsheetStatsTableChart(),
TearsheetEquityChart(),
TearsheetDrawdownChart(),
],
theme="nautilus_dark",
height=2000,
)
create_tearsheet(
engine=engine,
output_path="custom_tearsheet.html",
config=config,
)
For multi-currency backtests, filter statistics to a specific currency:
from nautilus_trader.model.currencies import USD
create_tearsheet(
engine=engine,
output_path="usd_only.html",
currency=USD, # Currency object, shows only USD statistics
)
When currency is None (default), statistics for all currencies are displayed
separately in the tearsheet. Return-based charts are reconstructed from account
reports only when the accounts share one currency; pass currency for multi-currency
backtests so return charts use the selected currency.
The tearsheet can include any combination of the following built-in charts:
| Chart Name | Type | Description |
|---|---|---|
run_info | Table | Run metadata and account balances. |
stats_table | Table | Performance statistics (PnL, returns, general metrics). |
equity | Line | Cumulative returns over time with optional benchmark. |
drawdown | Area | Drawdown percentage from peak equity. |
monthly_returns | Heatmap | Monthly portfolio return percentages organized by year. |
distribution | Histogram | Distribution of individual return values. |
rolling_sharpe | Line | 60-day rolling Sharpe ratio. |
yearly_returns | Bar | Annual return percentages. |
bars_with_fills | Candlestick | Price bars (OHLC) with order fills overlaid as markers. |
All charts are registered in the chart registry and are configured via chart objects in
TearsheetConfig.charts (each chart object maps to a built-in chart name).
The run_info chart displays key metadata about the backtest run:
This table appears in the top-left position by default.
The stats_table chart displays performance metrics organized into sections:
This table appears in the top-right position by default.
The equity chart plots cumulative returns over the backtest period. When benchmark_returns
is provided to create_tearsheet(), the benchmark is overlaid for comparison.
import pandas as pd
# Load benchmark returns (e.g., from a market index)
# Index should be datetime, aligned with strategy returns timeframe
benchmark_returns = pd.read_csv("sp500_returns.csv", index_col=0, parse_dates=True)["return"]
create_tearsheet(
engine=engine,
output_path="with_benchmark.html",
benchmark_returns=benchmark_returns,
benchmark_name="S&P 500",
)
The benchmark series is plotted as-is; ensure the index aligns with your strategy's return dates for accurate comparison.
The monthly_returns and yearly_returns charts default to compounded (time-weighted)
returns: each cell measures the period's gain against the running start-of-period balance,
and the periods compound to the total return.
Set compounding=False to report simple, non-compounding returns measured against fixed
initial capital. Each cell then measures the period's gain as a percentage of the starting
capital, so the periods sum to the total return instead of compounding to it. This is the
nominal rate of return, the convention used for constant-capital strategies that trade fixed
size and withdraw profits.
config = TearsheetConfig(
charts=[
TearsheetMonthlyReturnsChart(compounding=False),
TearsheetYearlyReturnsChart(compounding=False),
],
)
create_tearsheet(engine=engine, config=config)
The standalone create_monthly_returns_heatmap() and create_yearly_returns() functions
accept the same compounding argument. For the non-compounding figures to faithfully
represent constant capital, size positions at a fixed quantity rather than as a fraction of
current equity; otherwise later periods inflate as the running balance grows.
Themes control the visual styling of charts including colors, fonts, and backgrounds. NautilusTrader provides four built-in themes:
| Theme Name | Description | Use Case |
|---|---|---|
plotly_white | Clean light theme with dark gray headers. | Default, professional reports. |
plotly_dark | Dark background with standard Plotly colors. | Low‑light environments. |
nautilus | Light theme with NautilusTrader brand colors. | Official light mode. |
nautilus_dark | Dark theme with teal/cyan signature colors. | Official dark mode. |
Specify the theme in TearsheetConfig:
config = TearsheetConfig(theme="nautilus_dark")
create_tearsheet(engine=engine, config=config)
Register a custom theme for consistent branding across all visualizations:
from nautilus_trader.analysis import register_theme
register_theme(
name="corporate",
template="plotly_white", # Base Plotly template
colors={
"primary": "#003366", # Navy blue
"positive": "#2e8b57", # Sea green
"negative": "#c41e3a", # Cardinal red
"neutral": "#808080", # Gray
"background": "#ffffff", # White
"grid": "#e5e5e5", # Light gray
# Optional table colors (defaults will be provided if omitted)
"table_section": "#e5e5e5",
"table_row_odd": "#f8f8f8",
"table_row_even": "#ffffff",
"table_text": "#000000",
}
)
# Use the custom theme
config = TearsheetConfig(theme="corporate")
The theme system automatically provides sensible defaults for table_* colors based on
the background and grid colors, ensuring backward compatibility with themes registered
before table-specific colors were introduced.
The TearsheetConfig class provides declarative control over tearsheet generation:
from nautilus_trader.analysis import GridLayout
from nautilus_trader.analysis import TearsheetConfig
from nautilus_trader.analysis import TearsheetDrawdownChart
from nautilus_trader.analysis import TearsheetEquityChart
from nautilus_trader.analysis import TearsheetStatsTableChart
config = TearsheetConfig(
charts=[
TearsheetEquityChart(),
TearsheetDrawdownChart(),
TearsheetStatsTableChart(),
],
theme="nautilus_dark",
title="Q4 2024 Strategy Performance",
height=1800,
include_benchmark=True,
benchmark_name="SPY",
layout=GridLayout(
rows=2,
cols=2,
heights=[0.60, 0.40],
vertical_spacing=0.08,
horizontal_spacing=0.12,
),
)
| Parameter | Type | Default | Description |
|---|---|---|---|
charts | list[TearsheetChart] | Built‑ins | Charts to include, in order. |
theme | str | "plotly_white" | Theme name for styling. |
layout | GridLayout | None | Custom subplot grid layout. |
title | str | Auto‑generated | Tearsheet title. |
include_benchmark | bool | True | Show benchmark when provided. |
benchmark_name | str | "Benchmark" | Display name for benchmark. |
height | int | 1500 | Total height in pixels. |
show_logo | bool | True | Reserved for future logo rendering. |
When layout is None, the grid dimensions and row heights are automatically calculated
based on the number of charts. For 8 charts (the default), a 4x2 grid is used with
heights [0.50, 0.22, 0.16, 0.12] to give more space to the top row tables.
The registry pattern lets you add custom charts. Charts are functions that render traces onto a Plotly figure object.
from nautilus_trader.analysis.tearsheet import register_chart
import plotly.graph_objects as go
def my_custom_chart(returns, output_path=None, title="Custom Chart", theme="plotly_white"):
"""
Create a custom visualization.
This function signature matches the built-in chart functions for consistency.
"""
from nautilus_trader.analysis.themes import get_theme
theme_config = get_theme(theme)
# Create your visualization
fig = go.Figure()
fig.add_trace(go.Scatter(
x=returns.index,
y=returns.cumsum(),
mode="lines",
name="Custom Metric",
line={"color": theme_config["colors"]["primary"]},
))
fig.update_layout(
title=title,
template=theme_config["template"],
xaxis_title="Date",
yaxis_title="Value",
)
if output_path:
fig.write_html(output_path)
return fig
# Register the chart for standalone use (via `get_chart()` / `list_charts()`)
register_chart("my_custom", my_custom_chart)
For tearsheet integration with proper grid placement, use register_tearsheet_chart. Unlike
register_chart (which registers a standalone function that returns its own figure), a tearsheet
renderer draws traces directly onto a shared subplot grid cell, so its signature takes the target
fig plus the row and col to render into.
from nautilus_trader.analysis import TearsheetConfig
from nautilus_trader.analysis import TearsheetCustomChart
from nautilus_trader.analysis import TearsheetEquityChart
from nautilus_trader.analysis import TearsheetStatsTableChart
from nautilus_trader.analysis import register_tearsheet_chart
def _render_my_metric(fig, row, col, returns, theme_config, **kwargs):
"""
Render custom metric directly onto a subplot.
Parameters
----------
fig : go.Figure
The figure to add traces to.
row : int
Subplot row position.
col : int
Subplot column position.
returns : pd.Series
Strategy returns series supplied to the renderer.
theme_config : dict
Theme configuration dictionary.
**kwargs : dict
Additional parameters (stats_pnls, stats_returns, benchmark_returns, etc.).
"""
metric_values = returns.rolling(30).std() * 100 # Example metric
fig.add_trace(
go.Scatter(
x=returns.index,
y=metric_values,
mode="lines",
name="30-Day Volatility",
line={"color": theme_config["colors"]["neutral"]},
),
row=row,
col=col,
)
fig.update_xaxes(title_text="Date", row=row, col=col)
fig.update_yaxes(title_text="Volatility (%)", row=row, col=col)
# Register for tearsheet use
register_tearsheet_chart(
name="volatility",
subplot_type="scatter",
title="Rolling Volatility (30-day)",
renderer=_render_my_metric,
)
# Now "volatility" can be used in TearsheetConfig.charts:
config = TearsheetConfig(
charts=[
TearsheetStatsTableChart(),
TearsheetEquityChart(),
TearsheetCustomChart(chart="volatility"),
],
)
The renderer function receives all necessary data (returns, statistics, theme configuration) and renders directly onto the specified subplot position.
For situations where you have precomputed statistics but not a BacktestEngine instance,
use the lower-level API:
import pandas as pd
from nautilus_trader.analysis.tearsheet import create_tearsheet_from_stats
# Load precomputed data. The structure matches BacktestResult stats fields.
stats_pnls = {"USD": {"PnL (total)": 1500.0, "Win Rate": 0.55, ...}} # Per-currency
stats_returns = {"Sharpe Ratio (252 days)": 1.2, "Max Drawdown": -0.15, ...}
stats_general = {"Avg Winner": 100.0, "Avg Loser": -50.0, ...}
returns = pd.Series(...) # Daily returns with datetime index
create_tearsheet_from_stats(
stats_pnls=stats_pnls,
stats_returns=stats_returns,
stats_general=stats_general,
returns=returns,
output_path="offline_analysis.html",
)
The dictionary keys should match those returned by engine.get_result().stats_pnls,
engine.get_result().stats_returns, and engine.get_result().stats_general.
This approach is useful for:
plotly_white for professional reports and presentations.nautilus_dark for official materials or low-light viewing.Custom charts work best when paired with statistics supplied through the same
stats_pnls, stats_returns, and stats_general dictionaries used by the built-in
tearsheet charts. For live BacktestEngine usage these values come from
engine.get_result(); for offline analysis, pass compatible dictionaries directly to
create_tearsheet_from_stats():
stats_returns = {
"Sharpe Ratio (252 days)": 1.2,
"Custom Volatility Score": 0.42,
}
The visualization system provides two API levels:
Recommended for most use cases:
create_tearsheet(engine=engine, config=config)
Automatically extracts data from the BacktestEngine, generates all configured charts,
and produces a complete HTML tearsheet.
For advanced customization or offline analysis:
create_tearsheet_from_stats(
stats_pnls=stats_pnls,
stats_returns=stats_returns,
stats_general=stats_general,
returns=returns,
run_info=run_info,
account_info=account_info,
config=config,
)
Provides fine-grained control over data inputs and allows analysis of precomputed statistics.
Individual chart functions can be used independently to generate single-purpose HTML visualizations or Plotly figures for custom analysis workflows.
The create_bars_with_fills function generates a candlestick chart with order fills overlaid,
useful for visually analyzing strategy execution within price action. It can be used standalone
or included in tearsheets:
from nautilus_trader.analysis import create_bars_with_fills
from nautilus_trader.analysis import create_tearsheet
from nautilus_trader.analysis import TearsheetBarsWithFillsChart
from nautilus_trader.analysis import TearsheetConfig
from nautilus_trader.analysis import TearsheetEquityChart
from nautilus_trader.analysis import TearsheetStatsTableChart
from nautilus_trader.model.data import BarType
# Standalone usage
bar_type = BarType.from_str("ESM4.XCME-1-MINUTE-LAST-EXTERNAL")
fig = create_bars_with_fills(
engine=engine,
bar_type=bar_type,
title="ES Futures - Entry/Exit Analysis",
)
fig.show() # Display in Jupyter
fig.write_html("bars_with_fills.html") # Or save to file
# Include in tearsheet
config = TearsheetConfig(
charts=[
TearsheetStatsTableChart(),
TearsheetEquityChart(),
TearsheetBarsWithFillsChart(
bar_type="ESM4.XCME-1-MINUTE-LAST-EXTERNAL",
title="Bars with Fills",
),
],
)
create_tearsheet(engine=engine, config=config)
# Multiple bars-with-fills charts in one tearsheet
config = TearsheetConfig(
charts=[
TearsheetStatsTableChart(),
TearsheetEquityChart(),
TearsheetBarsWithFillsChart(
bar_type=f"{instrument.id}-5-MINUTE-MID-INTERNAL",
title=f"Bars with Order Fills - {instrument.id}",
),
TearsheetBarsWithFillsChart(
bar_type=f"{other_instrument.id}-5-MINUTE-MID-INTERNAL",
title=f"Bars with Order Fills - {other_instrument.id}",
),
],
)
create_tearsheet(engine=engine, config=config)
The visualization shows candlesticks for OHLC price action with triangle markers representing order
fills (green up-triangles for buys, red down-triangles for sells). Charts that need extra
configuration (like bar_type) take those parameters directly on the chart object
(e.g. TearsheetBarsWithFillsChart(bar_type=...)).
Other individual chart functions include create_equity_curve, create_drawdown_chart,
create_monthly_returns_heatmap, and more. See the API reference for the complete list.