Performance·Reports·Beginner

QuantStats Report

Generate professional HTML tearsheets using QuantStats — full metrics, benchmark comparison, and rolling performance analysis.

quantstatstearsheetbenchmark

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.

[1]
!pip install pandas numpy quantstats plotly
Requirement 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)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 90.1/90.1 kB 2.0 MB/s eta 0:00:00
[?25hInstalling collected packages: quantstats
Successfully installed quantstats-0.0.81

2. Library Imports

[2]
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_subplots

3. 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:

FeatureDescription
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

[3]
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

[4]
# --- 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

[5]
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

[6]
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

[8]
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()
[ ]