NUMERICAL AUDIT · 1 NOVEMBER 2026

pyfolio-reloaded: the drawdown table ignores the starting capital as a peak

When a return series starts with a loss, pyfolio-reloaded's worst-drawdown table and underwater plot understate the drawdown and date the recovery too early, contradicting Max drawdown in the same tear sheet.

Xamit Kadirbekov, Daniyal Kadirbekov · AI-assisted investigation and writing

Checked 1 November 2026: issue #113 and PR #114 are open, with no review yet; not merged or released. Local regression checks and limitations documented.

Upstream report #114 · Upstream issue #113

Authors: Xamit Kadirbekov and Daniyal Kadirbekov (GERO). Published 1 November 2026.

Status: Checked 1 November 2026: issue #113 and PR #114 are open, with no review yet; not merged or released. Local regression checks and limitations documented.

When a return series starts with a loss, pyfolio-reloaded's worst-drawdown table and underwater plot understate the drawdown and date the recovery too early, contradicting Max drawdown in the same tear sheet.

Published proposal

What the functions do

pyfolio-reloaded is the maintained fork of Quantopian's pyfolio, used to produce performance "tear sheets" for trading strategies and portfolios. A drawdown is the fall of capital from its previous peak. The tear sheet shows a "Max drawdown" statistic (from empyrical), a "Worst drawdown periods" table (gen_drawdown_table, get_top_drawdowns) and an underwater plot (plot_drawdown_underwater). The first peak is the starting capital, 1.0 before the first day.

The discrepancy

The table and the plot compute the running peak from the cumulative returns only, which start at the end of day one (0.9 after a −10% day, not 1.0). The first day's loss falls out of every drawdown, and "recovery" means getting back to 0.9 rather than to 1.0.

import pandas as pd
from pyfolio import timeseries as ts
idx = pd.bdate_range("2024-01-01", periods=6)
r = pd.Series([-0.10, -0.10, 0.05, 0.05, 0.10, 0.02], index=idx)
ts.gen_drawdown_table(r, top=1)
#   Net drawdown in %  Peak date   Valley date  Recovery date
#   10.0               2024-01-01  2024-01-02   2024-01-05
ts.perf_stats(r)["Max drawdown"]   # -0.19
pyfolio tableFrom the starting capital
Depth10%19% (1.0 to 0.81)
Peak2024-01-01the starting capital, before 2024-01-01
Recovery2024-01-05 (0.9823: above 0.9, below 1.0)2024-01-08 (1.002)

The equity curve from 1.0 is 0.90, 0.81, 0.8505, 0.893, 0.9823, 1.002. The repository's own test case (prices 10 → 9 → 7.5) shows the same with the common .pct_change().dropna() returns: the table gives 16.67%, while the drawdown from the start and empyrical.max_drawdown give 25%. Without .dropna() the first element is NaN, empyrical treats it as zero, and the existing test passes, which is why it does not catch the case.

In a fuzz test of 400 random 120-day series, 27 disagree with max_drawdown; among those, the depth is understated by a median of 0.54 and at most 1.8 percentage points. Recovery dates can move further, depending on the path.

Reference and why this is not a convention

The reference is the direct calculation from the definition and empyrical.max_drawdown, a separate package that starts from 1.0. The same tear sheet reports both numbers for the same concept, so the report contradicts itself. The same root cause was reported for the max-drawdown statistic in quantopian/pyfolio#179 (2016) and fixed by moving that statistic to empyrical; the table and the underwater plot kept the old behaviour. We present this as an unfixed remainder of a known issue, not as a new class of error.

Fix and tests

Upstream status

As of 1 November 2026, issue #113 and PR #114 are open, with no review or comment. Nothing has been merged or released.

Limits

The depth error is limited to the first day's loss, usually one to two percentage points; recovery dates and durations are affected more. Returns produced by zipline usually have zero on day one, in which case nothing changes. tox across Python versions and the documentation build were not run. Forks were not checked. We do not claim any measured investment loss.

The example in the issue was re-computed in a separate session on 1 November before submission. No reproduction archive is attached to this page yet.

Attribution and license

AI tools (Claude) helped find the cases, build the reference calculations and draft the patches and tests; the examples were re-run independently before submission. Xamit Kadirbekov and Daniyal Kadirbekov are the report authors and reviewed the submitted diff; upstream software remains credited to its developers. Report: CC BY 4.0. Upstream patches retain their upstream licenses.