QuantStats Report
Generate professional HTML tearsheets using QuantStats — full metrics, benchmark comparison, and rolling performance analysis.
Quantitative Performance Reporting with QuantStats
This notebook demonstrates the application of the QuantStats Python library for generating comprehensive performance reports of algorithmic trading strategies. It covers dependency installation, library imports, synthetic data generation, strategy definition, metric calculation, and interactive visualization.
1. Dependency Installation
This section outlines the installation of necessary Python packages required for executing the analyses presented in this notebook.
!pip install pandas numpy quantstats plotlyRequirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2) Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2) Collecting quantstats Downloading quantstats-0.0.81-py3-none-any.whl.metadata (10 kB) Requirement already satisfied: plotly in /usr/local/lib/python3.12/dist-packages (5.24.1) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.1) Requirement already satisfied: matplotlib>=3.7.0 in /usr/local/lib/python3.12/dist-packages (from quantstats) (3.10.0) Requirement already satisfied: scipy>=1.11.0 in /usr/local/lib/python3.12/dist-packages (from quantstats) (1.16.3) Requirement already satisfied: seaborn>=0.13.0 in /usr/local/lib/python3.12/dist-packages (from quantstats) (0.13.2) Requirement already satisfied: tabulate>=0.9.0 in /usr/local/lib/python3.12/dist-packages (from quantstats) (0.9.0) Requirement already satisfied: yfinance>=0.2.40 in /usr/local/lib/python3.12/dist-packages (from quantstats) (0.2.66) Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.12/dist-packages (from plotly) (9.1.4) Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from plotly) (26.1) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (1.3.3) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (4.62.1) Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (1.5.0) Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (11.3.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib>=3.7.0->quantstats) (3.3.2) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0) Requirement already satisfied: requests>=2.31 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (2.32.4) Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (0.0.13) Requirement already satisfied: platformdirs>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (4.9.6) Requirement already satisfied: frozendict>=2.3.4 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (2.4.7) Requirement already satisfied: peewee>=3.16.2 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (4.0.5) Requirement already satisfied: beautifulsoup4>=4.11.1 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (4.13.5) Requirement already satisfied: curl_cffi>=0.7 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (0.15.0) Requirement already satisfied: protobuf>=3.19.0 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (5.29.6) Requirement already satisfied: websockets>=13.0 in /usr/local/lib/python3.12/dist-packages (from yfinance>=0.2.40->quantstats) (15.0.1) Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4>=4.11.1->yfinance>=0.2.40->quantstats) (2.8.3) Requirement already satisfied: typing-extensions>=4.0.0 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4>=4.11.1->yfinance>=0.2.40->quantstats) (4.15.0) Requirement already satisfied: cffi>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (2.0.0) Requirement already satisfied: certifi>=2024.2.2 in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (2026.4.22) Requirement already satisfied: rich in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (13.9.4) Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance>=0.2.40->quantstats) (3.4.7) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance>=0.2.40->quantstats) (3.13) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance>=0.2.40->quantstats) (2.5.0) Requirement already satisfied: pycparser in /usr/local/lib/python3.12/dist-packages (from cffi>=2.0.0->curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (3.0) Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich->curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (4.0.0) Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich->curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (2.20.0) Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich->curl_cffi>=0.7->yfinance>=0.2.40->quantstats) (0.1.2) Downloading quantstats-0.0.81-py3-none-any.whl (90 kB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m90.1/90.1 kB[0m [31m2.0 MB/s[0m eta [36m0:00:00[0m [?25hInstalling collected packages: quantstats Successfully installed quantstats-0.0.81
2. Library Imports
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import quantstats as qs
import plotly.graph_objects as go
from plotly.subplots import make_subplots3. What Is QuantStats?
QuantStats is a Python library that generates comprehensive performance tearsheets — standardized multi-page reports covering return, risk, and drawdown metrics — from a return series in a single function call.
Key capabilities:
| Feature | Description |
|---|---|
qs.reports.metrics() | Prints 30+ performance metrics to console |
qs.reports.html() | Generates a full HTML tearsheet with charts and tables |
qs.reports.basic() | Prints a condensed metric summary |
qs.reports.full() | Full tearsheet output to console (matplotlib-based) |
qs.stats.* | Individual metric functions (Sharpe, Sortino, Calmar, VaR, etc.) |
qs.plots.* | Individual plot functions (drawdown, monthly returns heatmap, etc.) |
Why use QuantStats over manual computation: QuantStats implements risk metrics to professional fund management standards — including correct handling of annualization, risk-free rates, and benchmark comparison — eliminating the need to implement each metric individually. It also produces benchmark-relative statistics (alpha, beta, information ratio) when a benchmark return series is provided.
4. Data Generation
def generate_data(periods: int) -> pd.DataFrame:
start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")
price_data = []; last_close = 42000
volatility_scale = 0.005; wick_scale = 0.002
for _ in range(periods):
open_price = last_close + np.random.normal(0, last_close * volatility_scale * 0.1)
close_price = open_price + np.random.normal(0, last_close * volatility_scale)
body_high = max(open_price, close_price)
body_low = min(open_price, close_price)
high_price = max(body_high + abs(np.random.normal(0, last_close * wick_scale)),
open_price, close_price)
low_price = min(body_low - abs(np.random.normal(0, last_close * wick_scale)),
open_price, close_price)
if high_price < low_price:
high_price, low_price = low_price, high_price
price_data.append({
"open": max(1, int(open_price)),
"high": max(1, int(high_price)),
"low": max(1, int(low_price)),
"close": max(1, int(close_price)),
})
last_close = close_price
df = pd.DataFrame(price_data, index=datetime_index)
df.index.name = "datetime"
df["volume"] = np.random.uniform(100.0, 500.0, periods)
df["datetime"] = df.index.to_series()
return df.reset_index(drop=True)
df = generate_data(500)
# --- Build strategy and benchmark return series ---
df["fast_ma"] = df["close"].rolling(10).mean()
df["slow_ma"] = df["close"].rolling(30).mean()
df["position"] = np.where(df["fast_ma"] > df["slow_ma"], 1, 0)
df["trade"] = df["position"].diff().abs()
df["strategy_return"] = df["position"].shift(1).fillna(0) * df["close"].pct_change() - df["trade"] * 0.0005
df["market_return"] = df["close"].pct_change()
df = df.dropna()
# QuantStats requires a DatetimeIndex with timezone-naive timestamps
strategy_returns = pd.Series(
df["strategy_return"].values,
index=pd.DatetimeIndex(df["datetime"]).tz_localize(None),
name="Strategy",
)
benchmark_returns = pd.Series(
df["market_return"].values,
index=pd.DatetimeIndex(df["datetime"]).tz_localize(None),
name="Benchmark",
)
print(f"Return series length : {len(strategy_returns)}")
display(df[["datetime","strategy_return","market_return"]].head(10))Return series length : 471
| datetime | strategy_return | market_return | |
|---|---|---|---|
| 29 | 2024-01-01 00:29:00+00:00 | -0.000500 | -0.009036 |
| 30 | 2024-01-01 00:30:00+00:00 | 0.010247 | 0.010247 |
| 31 | 2024-01-01 00:31:00+00:00 | 0.004423 | 0.004423 |
| 32 | 2024-01-01 00:32:00+00:00 | 0.000245 | 0.000245 |
| 33 | 2024-01-01 00:33:00+00:00 | -0.002068 | -0.002068 |
| 34 | 2024-01-01 00:34:00+00:00 | -0.001159 | -0.001159 |
| 35 | 2024-01-01 00:35:00+00:00 | -0.010887 | -0.010887 |
| 36 | 2024-01-01 00:36:00+00:00 | -0.007691 | -0.007691 |
| 37 | 2024-01-01 00:37:00+00:00 | 0.007296 | 0.007296 |
| 38 | 2024-01-01 00:38:00+00:00 | -0.001038 | -0.001038 |
5. QuantStats Metrics
# --- Console metrics summary ---
print("--- QuantStats Performance Metrics ---")
qs.reports.metrics(strategy_returns, benchmark=benchmark_returns, mode="full")--- QuantStats Performance Metrics ---
Parameter Value
-------------- ---------
Benchmark Benchmark
Risk-Free Rate 0.0%
Periods/Year 252
Compounded Yes
Match Dates Yes
Benchmark Strategy
------------------------- ----------- ----------
Start Period 2024-01-01 2024-01-01
End Period 2024-01-01 2024-01-01
Risk-Free Rate 0.0% 0.0%
Time in Market 100.0% 56.0%
Cumulative Return -4.95% -3.14%
CAGR﹪ -2.68% -1.69%
Sharpe -0.31 -0.27
Prob. Sharpe Ratio 33.36% 35.44%
Smart Sharpe -0.29 -0.26
Sortino -0.43 -0.38
Smart Sortino -0.4 -0.35
Sortino/√2 -0.31 -0.27
Smart Sortino/√2 -0.29 -0.25
Omega 0.95 0.94
Max Drawdown -11.46% -7.77%
Max DD Date 2024-01-01 2024-01-01
Max DD Period Start 2024-01-01 2024-01-01
Max DD Period End 2024-01-01 2024-01-01
Longest DD Days 1 1
Volatility (ann.) 7.69% 5.66%
R^2 0.54 0.54
Information Ratio 0.01 0.01
Calmar -0.23 -0.22
Skew -0.09 -0.13
Kurtosis -0.18 2.46
Ulcer Performance Index -0.65 -0.78
Risk-Adjusted Return -2.68% -3.02%
Risk-Return Ratio -0.02 -0.02
Avg. Return -0.01% -0.01%
Avg. Win 0.38% 0.38%
Avg. Loss -0.4% -0.38%
Win/Loss Ratio 0.97 1.0
Profit Ratio 0.91 0.16
Expected Daily % -0.01% -0.01%
Expected Monthly % -4.95% -3.14%
Expected Yearly % -4.95% -3.14%
Kelly Criterion -0.87% -4.19%
Risk of Ruin 0.0% 0.0%
Daily Value-at-Risk -0.81% -0.59%
Expected Shortfall (cVaR) -1.0% -0.82%
Max Consecutive Wins 12 12
Max Consecutive Losses 7 6
Gain/Pain Ratio -1.0 -1.0
Gain/Pain (1M) -1.0 -1.0
Payoff Ratio 0.97 1.0
Profit Factor 0.95 0.94
Common Sense Ratio 0.87 0.88
CPC Index 0.46 0.45
Tail Ratio 0.91 0.94
Outlier Win Ratio 2.81 6.9
Outlier Loss Ratio 2.73 2.85
MTD -4.95% -3.14%
3M -4.95% -3.14%
6M -4.95% -3.14%
YTD -4.95% -3.14%
1Y -4.95% -3.14%
3Y (ann.) -2.68% -1.69%
5Y (ann.) -2.68% -1.69%
10Y (ann.) -2.68% -1.69%
All-time (ann.) -2.68% -1.69%
Best Day 1.31% 1.31%
Worst Day -1.41% -1.46%
Best Month -4.95% -3.14%
Worst Month -4.95% -3.14%
Best Year -4.95% -3.14%
Worst Year -4.95% -3.14%
Avg. Drawdown -3.04% -3.33%
Avg. Drawdown Days 1 1
Recovery Factor 0.39 0.37
Ulcer Index 0.08 0.04
Serenity Index -0.02 -0.04
Avg. Up Month - -
Avg. Down Month -4.95% -3.14%
Win Days % 50.32% 47.91%
Win Month % 0.0% 0.0%
Win Quarter % 0.0% 0.0%
Win Year % 0.0% 0.0%
Beta - 0.54
Alpha - -0.0
Correlation - 73.33%
Treynor Ratio - -5.81%
Explanation: qs.reports.metrics() computes and prints all standard performance metrics in a formatted table. The mode="full" argument includes the complete set of metrics — total return, CAGR, Sharpe, Sortino, Calmar, Omega, Skewness, Kurtosis, VaR, CVaR, and drawdown statistics. Providing benchmark enables benchmark-relative metrics including alpha, beta, and the information ratio.
6. Individual Metric Extraction
sharpe = qs.stats.sharpe(strategy_returns)
sortino = qs.stats.sortino(strategy_returns)
calmar = qs.stats.calmar(strategy_returns)
max_dd = qs.stats.max_drawdown(strategy_returns)
omega = qs.stats.omega(strategy_returns)
skew = qs.stats.skew(strategy_returns)
kurt = qs.stats.kurtosis(strategy_returns)
print("--- Individual QuantStats Metrics ---")
print(f" Sharpe Ratio : {sharpe:.4f}")
print(f" Sortino Ratio : {sortino:.4f}")
print(f" Calmar Ratio : {calmar:.4f}")
print(f" Max Drawdown : {max_dd*100:.4f}%")
print(f" Omega Ratio : {omega:.4f}")
print(f" Return Skewness : {skew:.4f}")
print(f" Return Kurtosis : {kurt:.4f}")--- Individual QuantStats Metrics --- Sharpe Ratio : -0.2733 Sortino Ratio : -0.3772 Calmar Ratio : -0.2176 Max Drawdown : -7.7696% Omega Ratio : 0.9435 Return Skewness : -0.1291 Return Kurtosis : 2.4600
Explanation — Additional metrics:
- Omega Ratio: The ratio of the probability-weighted gains above a threshold to the probability-weighted losses below it. A value above 1.0 indicates more weighted gain than weighted loss — a more complete measure than Sharpe because it uses the full return distribution, not just its mean and variance.
- Skewness: Measures the asymmetry of the return distribution. Positive skew means occasional large positive returns with frequent small losses — generally preferred. Negative skew means frequent small gains with occasional large losses — typical of option-selling strategies.
- Kurtosis: Measures the heaviness of the tails relative to a normal distribution. Excess kurtosis (leptokurtosis) indicates more extreme outliers than a normal distribution predicts — common in financial returns.
7. HTML Tearsheet Generation
qs.reports.html(
strategy_returns,
benchmark = benchmark_returns,
output = "strategy_tearsheet.html",
title = "MA Crossover Strategy — Performance Tearsheet",
)
print("Tearsheet saved to: strategy_tearsheet.html")WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found. WARNING:matplotlib.font_manager:findfont: Font family 'Arial' not found.
Tearsheet saved to: strategy_tearsheet.html
8. Visualization — Custom Plotly Summary
equity_strategy = (1 + strategy_returns).cumprod() * 10_000
equity_benchmark = (1 + benchmark_returns).cumprod() * 10_000
dd_strategy = (equity_strategy / equity_strategy.cummax() - 1) * 100
dd_benchmark = (equity_benchmark / equity_benchmark.cummax() - 1) * 100
fig = make_subplots(
rows=3, cols=1, shared_xaxes=True,
subplot_titles=[
"Equity Curve — Strategy vs Benchmark",
"Drawdown (%)",
"Return Distribution",
],
row_heights=[0.4, 0.3, 0.3],
)
fig.add_trace(go.Scatter(
x=equity_strategy.index, y=equity_strategy.values,
mode="lines", name="Strategy",
line=dict(color="green", width=2)), row=1, col=1)
fig.add_trace(go.Scatter(
x=equity_benchmark.index, y=equity_benchmark.values,
mode="lines", name="Benchmark",
line=dict(color="gray", width=1.5, dash="dash")), row=1, col=1)
fig.add_trace(go.Scatter(
x=dd_strategy.index, y=dd_strategy.values,
mode="lines", name="Strategy DD",
fill="tozeroy", line=dict(color="red", width=1)), row=2, col=1)
fig.add_trace(go.Scatter(
x=dd_benchmark.index, y=dd_benchmark.values,
mode="lines", name="Benchmark DD",
line=dict(color="orange", width=1, dash="dot")), row=2, col=1)
fig.add_trace(go.Histogram(
x=strategy_returns.values * 100,
nbinsx=50,
marker_color="steelblue",
name="Strategy Returns (%)"), row=3, col=1)
fig.add_vline(x=0, line_dash="dash", line_color="red", row=3, col=1)
fig.update_layout(
title_text="QuantStats Summary — MA Crossover Strategy",
xaxis_rangeslider_visible=False,
height=900,
xaxis3_title="Datetime",
yaxis_title="Portfolio Value (\$)",
yaxis2_title="Drawdown (%)",
yaxis3_title="Frequency",
)
fig.show()