stats

stats

Statistical analytics for return streams: performance, risk, and benchmark-relative metrics.

Use :func:returns to bind a return stream (and optional benchmark) for chained stats and plotting, e.g. q.stats.returns(returns, benchmark=spy).alpha(). Plain functions (e.g. q.stats.performance(returns)) are stateless and take the series directly. See :mod:qrt.plot for rendering return streams.

Classes

Name Description
Returns Bind a return stream (and optional benchmark) for chained stats and plotting.

Returns

stats.Returns(
    data,
    benchmark=None,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Bind a return stream (and optional benchmark) for chained stats and plotting.

Created via :func:returns; not intended to be instantiated directly.

Methods

Name Description
alpha See :func:alpha.
benchmark_stats See :func:benchmark_stats.
beta See :func:beta.
metrics See :func:metrics.
monthly_returns See :func:monthly_returns.
performance See :func:performance.
period_returns See :func:period_returns.
plot Render this return stream with :mod:qrt.plot.
rolling_alpha See :func:rolling_alpha.
rolling_beta See :func:rolling_beta.
rolling_sharpe See :func:rolling_sharpe.
rolling_sortino See :func:rolling_sortino.
rolling_volatility See :func:rolling_volatility.
alpha
stats.Returns.alpha()

See :func:alpha.

benchmark_stats
stats.Returns.benchmark_stats()

See :func:benchmark_stats.

beta
stats.Returns.beta()

See :func:beta.

metrics
stats.Returns.metrics(mode='full')

See :func:metrics.

monthly_returns
stats.Returns.monthly_returns(eoy=True)

See :func:monthly_returns.

performance
stats.Returns.performance()

See :func:performance.

period_returns
stats.Returns.period_returns()

See :func:period_returns.

plot
stats.Returns.plot(kind='performance', **kwargs)

Render this return stream with :mod:qrt.plot.

Parameters
Name Type Description Default
kind str One of "performance"/"tearsheet", "equity", "drawdown", or "monthly_heatmap". 'performance'
**kwargs Any Forwarded to the underlying qrt.plot function. {}
Returns
Name Type Description
Figure A Plotly Figure.
rolling_alpha
stats.Returns.rolling_alpha(window=63)

See :func:rolling_alpha.

rolling_beta
stats.Returns.rolling_beta(window=63)

See :func:rolling_beta.

rolling_sharpe
stats.Returns.rolling_sharpe(window=63)

See :func:rolling_sharpe.

rolling_sortino
stats.Returns.rolling_sortino(window=63)

See :func:rolling_sortino.

rolling_volatility
stats.Returns.rolling_volatility(window=63)

See :func:rolling_volatility.

Functions

Name Description
adjusted_sortino Calculate Jack Schwager’s adjusted Sortino ratio.
aggregate_returns Compound a return series into coarser calendar periods.
alpha Calculate the annualized Jensen alpha of a return stream relative to a benchmark.
autocorr_penalty Calculate a penalty factor for return autocorrelation.
avg_loss Calculate the average return across losing (negative-return) periods.
avg_return Calculate the average return across non-zero periods.
avg_win Calculate the average return across winning (positive-return) periods.
benchmark_stats Calculate relative performance, risk, and attribution versus a benchmark.
best Return the single best (highest) period return.
beta Calculate the market beta of a return stream relative to a benchmark.
calmar Calculate the Calmar ratio.
common_sense_ratio Calculate the Common Sense Ratio: Profit Factor x Tail Ratio.
compare Compare strategy and benchmark returns period-by-period.
conditional_value_at_risk Calculate the historical Conditional Value at Risk (Expected Shortfall).
consecutive_losses Return the longest streak of consecutive negative-return periods.
consecutive_wins Return the longest streak of consecutive positive-return periods.
cpc_index Calculate the CPC Index: Profit Factor x Win Rate x Payoff Ratio.
distribution Break down compounded returns by period, flagging IQR outliers within each.
drawdown_details Break a return stream’s drawdown series into individual peak-to-recovery episodes.
excess_returns Calculate per-period excess returns over a deannualized risk-free rate.
expected_return Calculate the geometric-mean expected return per period, optionally per calendar period.
exponential_volatility Calculate exponentially-weighted volatility, giving more weight to recent returns.
exposure Calculate the fraction of periods with a non-zero return (time in market).
factor_contributions Decompose each period’s excess return into per-factor contributions from a static fit.
factor_regression Fit a full-period multi-factor OLS regression (e.g. the Fama-French five-factor model).
factor_regression_stats Calculate regression-level diagnostics for a full-period multi-factor OLS fit.
gain_to_pain_ratio Calculate Jack Schwager’s Gain-to-Pain ratio.
geometric_mean Calculate the geometric mean return per period.
infer_periods_per_year Infer a conventional annualization frequency from a datetime index.
information_ratio Calculate the Information ratio.
kelly_criterion Calculate the Kelly Criterion fraction.
kurtosis Calculate the excess kurtosis of returns.
make_portfolio Turn a return series into a portfolio equity curve.
max_drawdown Calculate the maximum drawdown.
metrics Build the full quantstats-style metrics table for a return stream.
montecarlo Simulate bootstrap-resampled return paths to gauge the range of plausible outcomes.
monthly_returns Compound returns by calendar month into a year-by-month table.
multiclass_precision_recall_curve Calculate one-vs-rest multiclass precision-recall curves and averages.
multiclass_roc_curve Calculate one-vs-rest multiclass ROC curves and micro/macro averages.
noise_test Simulate noise-perturbed return paths to gauge sensitivity to day-to-day noise/volatility.
omega Calculate the Omega ratio of a return stream.
outlier_loss_ratio Calculate the outlier loss ratio: a low quantile of returns vs. the average loss.
outlier_win_ratio Calculate the outlier win ratio: a high quantile of returns vs. the average win.
outliers Return the subset of returns above a given quantile threshold.
payoff_ratio Calculate the payoff ratio: average win divided by absolute average loss.
performance Calculate standard performance statistics for a periodic return stream.
period_returns Compound returns over standard trailing and calendar windows (MTD, 3M, …, all-time).
probabilistic_ratio Calculate the probability that a risk-adjusted ratio is truly positive.
profit_factor Calculate the profit factor: total gains divided by total losses.
profit_ratio Calculate the profit ratio: win frequency divided by loss frequency.
r_squared Calculate R² (coefficient of determination) between strategy and benchmark returns.
rar Calculate the Risk-Adjusted Return (CAGR divided by exposure).
rebase Rescale a price series to a common starting value.
recovery_factor Calculate the recovery factor: total return divided by Max Drawdown.
remove_outliers Return the subset of returns below a given quantile threshold, discarding upper-tail outliers.
returns Bind a return stream (and optional benchmark) for chained stats and plotting.
risk_of_ruin Estimate the risk of ruin.
risk_return_ratio Calculate the risk-return ratio: mean return divided by its standard deviation.
rolling_alpha Return annualized rolling Jensen alpha relative to a benchmark.
rolling_beta Return rolling beta of strategy returns relative to a benchmark.
rolling_factor_regression Calculate rolling multi-factor OLS betas over a moving window.
rolling_sharpe Return annualized rolling Sharpe ratios over a moving window.
rolling_sortino Return annualized rolling Sortino ratios over a moving window.
rolling_volatility Return annualized rolling volatility for a periodic return stream.
serenity_index Calculate the Serenity Index.
sharpe Calculate the annualized Sharpe ratio of excess returns.
skew Calculate the skewness of returns.
sortino Calculate the annualized Sortino ratio of excess returns.
tail_ratio Calculate the tail ratio between the right and left tails of the return distribution.
to_drawdown_series Convert a return series into its running drawdown series.
to_log_returns Convert a return series to log (continuously compounded) returns.
to_prices Convert a return series into a cumulative price/equity curve.
trade_stats Calculate trade-level statistics from a canonical trades-format log.
trades_to_positions Convert a trade log into a signed position series over index.
trades_to_returns Convert a trade log into a periodic return stream.
treynor_ratio Calculate the annualized Treynor ratio relative to a benchmark.
ulcer_index Calculate the Ulcer Index.
ulcer_performance_index Calculate the Ulcer Performance Index (Martin ratio).
value_at_risk Calculate the parametric (variance-covariance) Value at Risk.
variance_test Simulate forward trades with a randomly varied win rate to stress-test a strategy’s edge.
volatility Calculate annualized volatility.
win_rate Calculate the win rate: the share of non-zero-return periods that were positive.
worst Return the single worst (lowest) period return.

adjusted_sortino

stats.adjusted_sortino(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    smart=False,
)

Calculate Jack Schwager’s adjusted Sortino ratio.

Divides the Sortino ratio by \(\sqrt{2}\) so it can be compared directly against the Sharpe ratio, which otherwise tends to run higher since it divides by an unconditional rather than downside-only deviation.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0
smart bool Whether to penalize for return autocorrelation, see :func:sortino. False

Returns

Name Type Description
float Adjusted Sortino ratio.

aggregate_returns

stats.aggregate_returns(returns, period, *, return_type='simple')

Compound a return series into coarser calendar periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series with a DatetimeIndex. required
period Literal['W', 'M', 'Q', 'Y'] Target period: "W" (weekly), "M" (monthly), "Q" (quarterly), or "Y" (yearly). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Series of compounded returns, one value per period, indexed by period end date.

alpha

stats.alpha(returns, benchmark, *, return_type='simple', periods_per_year=None)

Calculate the annualized Jensen alpha of a return stream relative to a benchmark.

Jensen’s alpha is the annualized return left over after accounting for the return explained by beta exposure to the benchmark — the excess return generated (or lost) beyond what market risk alone would predict.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
float Annualized alpha (excess return not explained by benchmark beta exposure).

autocorr_penalty

stats.autocorr_penalty(returns, *, return_type='simple')

Calculate a penalty factor for return autocorrelation.

Serially correlated returns (e.g. from stale pricing, smoothing, or illiquid assets) understate true volatility and can artificially inflate Sharpe- and Sortino-style ratios. This factor (>= 1) is meant to multiply the ratio’s denominator to correct for that; see smart=True on :func:sharpe/:func:sortino.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Autocorrelation penalty factor (1.0 when lag-1 autocorrelation is zero or undefined).

avg_loss

stats.avg_loss(returns, *, return_type='simple', aggregate=None)

Calculate the average return across losing (negative-return) periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the average down month. None

Returns

Name Type Description
float Mean of negative periodic returns.

avg_return

stats.avg_return(returns, *, return_type='simple')

Calculate the average return across non-zero periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Mean of non-zero periodic returns.

avg_win

stats.avg_win(returns, *, return_type='simple', aggregate=None)

Calculate the average return across winning (positive-return) periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the average up month. None

Returns

Name Type Description
float Mean of positive periodic returns.

benchmark_stats

stats.benchmark_stats(
    returns,
    benchmark,
    *,
    return_type='simple',
    periods_per_year=None,
)

Calculate relative performance, risk, and attribution versus a benchmark.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Series indexed by metric name:
pd.Series - Strategy Total Return / Benchmark Total Return: Simple compounded returns over the full period.
pd.Series - Active Return: Geometric excess growth of the strategy over the benchmark (final value of the strategy/benchmark equity ratio, minus 1).
pd.Series - Beta: Beta — sensitivity to benchmark movements.
pd.Series - Alpha: Jensen’s alpha — annualized excess return not explained by beta exposure.
pd.Series - Correlation: Correlation — Pearson correlation between strategy and benchmark returns.
pd.Series - Tracking Error: Tracking error — annualized standard deviation of active returns (strategy minus benchmark), i.e. how consistently the strategy diverges from the benchmark.
pd.Series - Information Ratio: Information ratio — annualized active return divided by tracking error, i.e. consistency of benchmark-beating performance.
pd.Series - Periods: Number of aligned return observations used.

best

stats.best(returns, *, return_type='simple', aggregate=None)

Return the single best (highest) period return.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the best month. None

Returns

Name Type Description
float Maximum periodic return.

beta

stats.beta(returns, benchmark, *, return_type='simple')

Calculate the market beta of a return stream relative to a benchmark.

Beta is the covariance of strategy and benchmark returns divided by the benchmark’s variance — a measure of sensitivity to benchmark (market) movements, i.e. systematic risk.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float The beta coefficient (covariance with the benchmark over benchmark variance).

calmar

stats.calmar(returns, *, return_type='simple', periods_per_year=None, rf=0.0)

Calculate the Calmar ratio.

CAGR divided by the absolute Max Drawdown, i.e. return per unit of worst-case pain endured.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing CAGR. Defaults to 0.0. 0.0

Returns

Name Type Description
float Calmar ratio.

common_sense_ratio

stats.common_sense_ratio(returns, *, return_type='simple')

Calculate the Common Sense Ratio: Profit Factor x Tail Ratio.

Combines overall profitability (:func:profit_factor) with the shape of extreme returns (:func:tail_ratio), so it penalizes strategies with a poor tail-risk profile even if their average profit factor looks good.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Common Sense Ratio.

compare

stats.compare(returns, benchmark, *, return_type='simple')

Compare strategy and benchmark returns period-by-period.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.DataFrame DataFrame indexed like the aligned returns, with columns:
pd.DataFrame - Strategy: Strategy return for the period.
pd.DataFrame - Benchmark: Benchmark return for the period.
pd.DataFrame - Multiplier: Strategy / Benchmark for the period.
pd.DataFrame - Won: True where the strategy return met or beat the benchmark.

conditional_value_at_risk

stats.conditional_value_at_risk(
    returns,
    *,
    return_type='simple',
    confidence=0.95,
)

Calculate the historical Conditional Value at Risk (Expected Shortfall).

Expected Shortfall is the average return among periods that fell below the :func:value_at_risk threshold — the expected loss given that a tail event occurred, which VaR alone doesn’t capture.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
confidence float Confidence level, e.g. 0.95 for a 95% CVaR. Defaults to 0.95. 0.95

Returns

Name Type Description
float Conditional Value at Risk (a negative number representing an expected loss).

consecutive_losses

stats.consecutive_losses(returns, *, return_type='simple')

Return the longest streak of consecutive negative-return periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
int Length of the longest losing streak.

consecutive_wins

stats.consecutive_wins(returns, *, return_type='simple')

Return the longest streak of consecutive positive-return periods.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
int Length of the longest winning streak.

cpc_index

stats.cpc_index(returns, *, return_type='simple')

Calculate the CPC Index: Profit Factor x Win Rate x Payoff Ratio.

A composite score combining how much is won per unit lost (:func:profit_factor), how often periods win (win rate), and the relative size of wins vs. losses (:func:payoff_ratio).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float CPC Index.

distribution

stats.distribution(returns, *, return_type='simple')

Break down compounded returns by period, flagging IQR outliers within each.

Compounds returns into Daily (as given), Weekly, Monthly, Quarterly, and Yearly buckets, then splits each bucket’s values into “normal” and “outlier” using the standard 1.5x interquartile-range rule.

Parameters

Name Type Description Default
returns pd.Series Periodic return series with a DatetimeIndex. required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
dict[str, dict[str, list[float]]] Dict keyed by period name ("Daily", "Weekly", "Monthly", "Quarterly",
dict[str, dict[str, list[float]]] "Yearly"), each mapping to {"values": [...], "outliers": [...]}.

drawdown_details

stats.drawdown_details(returns, *, return_type='simple', sort_by='depth')

Break a return stream’s drawdown series into individual peak-to-recovery episodes.

A prerequisite for q.plot’s longest-drawdown highlighting. See :func:to_drawdown_series for the underlying running drawdown series.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
sort_by Literal['depth', 'duration'] Order episodes by "depth" (deepest drawdown first, default) or "duration" (longest first). 'depth'

Returns

Name Type Description
pd.DataFrame DataFrame with one row per drawdown episode and columns:
pd.DataFrame - Start: Last date at a new high before the decline began.
pd.DataFrame - Valley: Date of the deepest point of the drawdown.
pd.DataFrame - End: Last date the drawdown was still active (before recovering to a new high, or the final date in returns if the drawdown never recovered).
pd.DataFrame - Days: Number of calendar days from Start to End.
pd.DataFrame - Max Drawdown: The episode’s trough value (a non-positive number).
pd.DataFrame Empty (no rows) if returns never drew down.

excess_returns

stats.excess_returns(
    returns,
    rf=0.0,
    *,
    return_type='simple',
    periods_per_year=None,
)

Calculate per-period excess returns over a deannualized risk-free rate.

rf is an annualized rate; it is converted to a per-period rate before being subtracted from each period’s return (see :func:performance’s rf parameter for the same convention).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
rf float Annualized risk-free rate. Defaults to 0.0 (returns unchanged). 0.0
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency used to deannualize rf. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Excess return series.

expected_return

stats.expected_return(returns, *, aggregate=None, return_type='simple')

Calculate the geometric-mean expected return per period, optionally per calendar period.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the expected monthly return. None (default) uses the series’ native frequency. None
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Geometric mean return per (aggregated) period.

exponential_volatility

stats.exponential_volatility(
    returns,
    span=30,
    *,
    return_type='simple',
    periods_per_year=None,
    annualize=True,
)

Calculate exponentially-weighted volatility, giving more weight to recent returns.

Unlike :func:rolling_volatility’s fixed window with equal weighting, each observation’s influence decays geometrically with age, controlled by span.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
span int Decay span (same semantics as pandas.Series.ewm); roughly the number of recent periods that dominate the estimate. 30
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
annualize bool Whether to annualize the result. Defaults to True. True

Returns

Name Type Description
pd.Series Series of (optionally annualized) exponentially-weighted volatility.

exposure

stats.exposure(returns, *, return_type='simple')

Calculate the fraction of periods with a non-zero return (time in market).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Share of periods with a non-zero return, in [0, 1].

factor_contributions

stats.factor_contributions(
    returns,
    factors,
    *,
    return_type='simple',
    rf='RF',
    covariance='nonrobust',
)

Decompose each period’s excess return into per-factor contributions from a static fit.

A factor loading (:func:factor_regression) answers “how sensitive is the strategy to this factor?”; a factor contribution answers “how much return did that exposure generate?”. Fits :func:factor_regression once over the full period, then attributes every period’s excess return to Alpha (the fitted intercept), one column per factor (coefficient * factor return), and Residual (left unexplained). Columns sum, period by period, to the excess return (returns minus rf).

Note

Cumulative arithmetic sums of these columns reconstruct the cumulative excess return exactly; compounding each column separately (e.g. (1 + contribution).cumprod()) would not, since return compounding creates cross-factor interaction terms. Use arithmetic cumulative sums for additive attribution (see :func:qrt.plot.factor_contributions).

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
factors pd.DataFrame DataFrame of periodic factor return columns, aligned to returns. required
return_type ReturnType Whether returns is "simple" or "log" returns. 'simple'
rf str | pd.Series | None See :func:factor_regression. 'RF'
covariance CovarianceType See :func:factor_regression (only affects standard errors, not the contributions themselves, which depend only on the fitted coefficients). 'nonrobust'

Returns

Name Type Description
pd.DataFrame DataFrame indexed like the aligned returns/factors data, with columns Alpha,
pd.DataFrame one per factor (in factors’ order), and Residual.

factor_regression

stats.factor_regression(
    returns,
    factors,
    *,
    return_type='simple',
    rf='RF',
    covariance='nonrobust',
    confidence_level=0.95,
)

Fit a full-period multi-factor OLS regression (e.g. the Fama-French five-factor model).

Decomposes returns’ excess return into a constant (alpha) plus a linear exposure (beta) to each column of factors: R - Rf = alpha + beta_1 * factor_1 + ... + beta_k * factor_k + residual. This is a return-attribution regression, not a forecasting model – it explains what already happened, using every observation in the sample. See Fama-French three/five-factor model and the Kenneth French Data Library for the standard Mkt-RF/SMB/HML/RMW/CMA/RF factor set (any factor set works, not only the Fama-French five). For time-varying exposures, see :func:rolling_factor_regression.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
factors pd.DataFrame DataFrame of periodic factor return columns (e.g. Mkt-RF, SMB, HML, RMW, CMA, and optionally RF), aligned to returns on shared dates. Must be in the same decimal-return units as returns. required
return_type ReturnType Whether returns is "simple" or "log" returns. 'simple'
rf str | pd.Series | None Risk-free rate used to convert returns into the regression’s excess-return target. Either a column name already present in factors (removed from the regressors, e.g. Ken French’s "RF") or a separate periodic risk-free return series aligned like factors. Pass None if returns is already an excess return. 'RF'
covariance CovarianceType Standard-error estimator: "nonrobust" (classic OLS, default) or "HC3" (heteroskedasticity-robust). Coefficients are identical either way; only standard errors, t-statistics, p-values, and confidence intervals differ. 'nonrobust'
confidence_level float Confidence level for CI Lower/CI Upper. Defaults to 0.95. 0.95

Returns

Name Type Description
pd.DataFrame DataFrame indexed by "Alpha" followed by each factor column (in factors
pd.DataFrame order, excluding rf when it names a column), with columns Coefficient,
pd.DataFrame Std Error, t-Statistic, p-Value, CI Lower, CI Upper. See
pd.DataFrame func:factor_regression_stats for regression-level diagnostics (R², annualized
pd.DataFrame alpha, residual volatility) and :func:factor_contributions for the per-period
pd.DataFrame return attribution this fit implies.

factor_regression_stats

stats.factor_regression_stats(
    returns,
    factors,
    *,
    return_type='simple',
    rf='RF',
    covariance='nonrobust',
    periods_per_year=None,
)

Calculate regression-level diagnostics for a full-period multi-factor OLS fit.

Companion to :func:factor_regression (which returns the per-factor coefficient table); this returns the fit’s overall explanatory power and alpha, annualized.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
factors pd.DataFrame DataFrame of periodic factor return columns, aligned to returns. required
return_type ReturnType Whether returns is "simple" or "log" returns. 'simple'
rf str | pd.Series | None See :func:factor_regression. 'RF'
covariance CovarianceType See :func:factor_regression (only affects Alpha’s statistical significance, not shown here – see :func:factor_regression for alpha’s standard error/t-statistic/p-value). 'nonrobust'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Series indexed by metric name:
pd.Series - Alpha: Per-period (e.g. daily) intercept.
pd.Series - Alpha (ann.): Simple annualization (periods_per_year * Alpha); prefer this over compounding a regression intercept, which implies more economic precision than is justified.
pd.Series - : Fraction of excess-return variance explained by the factors.
pd.Series - Adj. R²: R² penalized for the number of factors, comparable across models with a different factor count.
pd.Series - Residual Volatility: Annualized standard deviation of the fit’s residuals – variation left unexplained by the factors.
pd.Series - Periods: Number of aligned observations used.

gain_to_pain_ratio

stats.gain_to_pain_ratio(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    aggregate=None,
)

Calculate Jack Schwager’s Gain-to-Pain ratio.

Sum of all (excess) returns divided by the absolute sum of losing periods — a simple measure of total profit generated per unit of total loss endured, without regard to distribution shape or timing.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency, used to deannualize rf. Inferred from the index (after any aggregate compounding) when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns. Defaults to 0.0. 0.0
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the monthly-resolution ratio. None

Returns

Name Type Description
float Gain-to-Pain ratio.

geometric_mean

stats.geometric_mean(returns, *, return_type='simple')

Calculate the geometric mean return per period.

The constant per-period return that would produce the same compounded growth as the actual return sequence (the un-annualized building block of :func:_cagr).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Geometric mean return per period.

infer_periods_per_year

stats.infer_periods_per_year(index)

Infer a conventional annualization frequency from a datetime index.

Daily and intraday data use 252 trading periods per year. Weekly, monthly, quarterly, and yearly data use 52, 12, 4, and 1 periods respectively. Non-datetime or single-observation indexes default to 252.

Parameters

Name Type Description Default
index pd.Index Datetime index whose median spacing is used to infer frequency. required

Returns

Name Type Description
int Periods per year (e.g. 252, 52, 12, 4, or 1).

information_ratio

stats.information_ratio(
    returns,
    benchmark,
    *,
    return_type='simple',
    periods_per_year=None,
)

Calculate the Information ratio.

Annualized active return (strategy minus benchmark) divided by tracking error, i.e. consistency of benchmark-beating performance.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
float Information Ratio.

kelly_criterion

stats.kelly_criterion(returns, *, return_type='simple')

Calculate the Kelly Criterion fraction.

The Kelly Criterion is the recommended fraction of capital to allocate to a strategy given its win rate and :func:payoff_ratio, to maximize long-run geometric growth without risking ruin.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Kelly fraction (can be negative, indicating the strategy has negative expectancy).

kurtosis

stats.kurtosis(returns, *, return_type='simple')

Calculate the excess kurtosis of returns.

Kurtosis measures how fat-tailed the return distribution is relative to a normal distribution (which has an excess kurtosis of 0). Higher values mean more frequent extreme returns than a normal distribution would predict.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Sample excess kurtosis.

make_portfolio

stats.make_portfolio(
    returns,
    *,
    return_type='simple',
    start_balance=100000.0,
    mode='compound',
)

Turn a return series into a portfolio equity curve.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
start_balance float Starting portfolio balance. Defaults to 100,000. 100000.0
mode Literal['compound', 'linear'] "compound" (default) reinvests gains/losses each period (equivalent to :func:to_prices, rebased to start_balance); "linear" applies each period’s return to the original start_balance only, without reinvestment. 'compound'

Returns

Name Type Description
pd.Series Portfolio balance series, same index as returns.

max_drawdown

stats.max_drawdown(returns, *, return_type='simple')

Calculate the maximum drawdown.

The largest peak-to-trough decline in cumulative value over the period. See :func:to_drawdown_series for the full running series.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Max Drawdown (a non-positive number, e.g. -0.25 for a 25% decline).

metrics

stats.metrics(
    returns,
    benchmark=None,
    *,
    mode='full',
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Build the full quantstats-style metrics table for a return stream.

Composes the individual q.stats metric functions into one grouped DataFrame — the table behind :func:qrt.plot.metrics_table and the tearsheet’s metrics panel. Values are raw (floats, ints, timestamps), not formatted strings; rendering/formatting is left to the caller.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series with a DatetimeIndex. required
benchmark pd.Series | None Optional benchmark periodic return series, aligned to returns on shared dates. Adds a benchmark column plus a vs. Benchmark section (Beta, Alpha, Correlation, R², Information Ratio, Tracking Error, Treynor Ratio) filled only for the strategy column. None
mode Literal['basic', 'full'] "full" (default) for every metric, "basic" for a compact headline subset. 'full'
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, used by excess-return metrics and reported in the Risk-Free Rate row. Defaults to 0.0. 0.0

Returns

Name Type Description
pd.DataFrame DataFrame with a (Section, Metric) MultiIndex and one column
pd.DataFrame per series (benchmark first when given). Sections: Overview,
pd.DataFrame Returns, Risk-Adjusted, Risk, Drawdown, Trading,
pd.DataFrame Period Returns, Best / Worst, Win Rates, and
pd.DataFrame vs. Benchmark (benchmark only).

montecarlo

stats.montecarlo(
    returns,
    sims=1000,
    *,
    return_type='simple',
    bust=None,
    goal=None,
    confidence=0.95,
    seed=None,
    block_size=None,
    periods=None,
)

Simulate bootstrap-resampled return paths to gauge the range of plausible outcomes.

Each simulated path is built by resampling the historical returns with replacement (a bootstrap), then compounding them into a cumulative growth path. Useful for estimating how much of a strategy’s realized performance could plausibly be attributed to chance, and what range of terminal outcomes and drawdowns a similar return distribution could plausibly produce.

Note

This deliberately resamples with replacement rather than merely permuting (shuffling) the historical returns. A plain permutation only reorders the same fixed set of returns, and since compounding is a product, its terminal value prod(1 + r) is invariant to the order of the factors — every permuted path compounds to exactly the same terminal return as the original. That makes a permutation-only simulation’s terminal-value spread, goal_probability, degenerate (every path either meets the goal or none do) even though its path shape (and therefore Max Drawdown) still varies meaningfully. Bootstrap resampling avoids this: some returns are drawn more than once and others not at all, so the terminal value genuinely varies across simulations, making terminal_stats/goal_probability statistically meaningful.

Note

By default (block_size=None) each period is resampled independently (i.i.d.), which implicitly assumes returns have no serial dependence. Real return series routinely violate that: volatility clustering (calm/turbulent periods cluster together), short-term autocorrelation, and macro-regime persistence are all well documented. i.i.d. resampling destroys these dependencies, which tends to understate tail risk (it can’t produce a realistic run of consecutive bad days beyond what chance alone would predict). Pass block_size (e.g. 20 for roughly a trading month) to instead draw contiguous, circularly-wrapped blocks of historical returns with Geometrically-distributed lengths (mean block_size) — a stationary block bootstrap — which preserves that local structure. Larger block_size preserves longer-range dependence at the cost of fewer effectively-independent blocks (and thus less path diversity) per simulation; it should typically be set to roughly the horizon over which returns are believed to be autocorrelated (e.g. the length of a typical volatility cluster). For explicitly modeling time-varying volatility/regime shifts, a fitted parametric model (ARMA-GARCH) is a more appropriate but heavier tool than resampling; that’s out of scope here.

Note

Compounding is multiplicative, so its variance accumulates with the number of resampled periods: simulating as many periods as a long history contains (the default, periods=None) makes the terminal-value spread balloon (easily spanning multiple orders of magnitude for a volatile asset) and pushes any fixed bust/goal threshold’s probability toward 0% or 100%, since a long enough random path is nearly guaranteed to cross it at some point — both effects are real, not bugs, but make for a poorly-differentiated simulation. Pass periods to decouple the simulation horizon from the length of returns: the full history is still used as the resampling pool (so a long, multi-regime history avoids biasing what gets resampled — see the module-level guidance on avoiding a single-regime sample), while each simulated path only covers periods steps forward, matching whatever horizon bust/goal are actually meant to evaluate (e.g. periods=252 for one trading year). Must satisfy 1 <= periods <= len(returns). When given, sim_0 (the “original” reference path) is the most recent periods-length window of returns rather than the entire history.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). Also serves as the resampling pool when periods is narrower than returns itself. required
sims int Number of simulated paths, including the original (unresampled) path as the first column (sim_0). Must be at least 1. 1000
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
bust float | None Optional Max Drawdown threshold (e.g. -0.2 for -20%). When given, bust_probability reports the fraction of simulated paths whose max drawdown breached it. None
goal float | None Optional cumulative-return threshold (e.g. 1.0 for +100%). When given, goal_probability reports the fraction of simulated paths that reached it. None
confidence float Confidence level for the per-period confidence_band. Defaults to 0.95 (a 95% band). 0.95
seed int | None Optional random seed for reproducibility. None
block_size float | None Optional mean block length (in periods) for a stationary block bootstrap (see Note above). Defaults to None, resampling each period independently (i.i.d.). None
periods int | None Optional simulation horizon in periods, decoupled from len(returns) (see Note above). Defaults to None (simulate as many periods as returns contains). None

Returns

Name Type Description
dict[str, object] Dict with keys:
dict[str, object] - paths: DataFrame of cumulative simulated returns, one column per simulation, indexed like returns.
dict[str, object] - terminal_stats: Summary statistics (mean, std, min/25%/50%/75%/max) of the final cumulative return across simulations.
dict[str, object] - max_drawdown_stats: Same summary statistics for each simulation’s Max Drawdown.
dict[str, object] - confidence_band: DataFrame with Lower/Upper columns holding the confidence-level percentile range of cumulative return at each period.
dict[str, object] - bust_probability: Fraction of simulations at or below bust (None if bust wasn’t given).
dict[str, object] - goal_probability: Fraction of simulations at or above goal (None if goal wasn’t given).

monthly_returns

stats.monthly_returns(returns, *, return_type='simple', eoy=True)

Compound returns by calendar month into a year-by-month table.

Useful for spotting seasonal patterns and feeding a monthly-returns heatmap (see :func:qrt.plot.monthly_heatmap).

Parameters

Name Type Description Default
returns pd.Series Periodic return series with a DatetimeIndex. required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
eoy bool Whether to append an EOY (end-of-year) column with each year’s compounded total return. Defaults to True. True

Returns

Name Type Description
pd.DataFrame DataFrame indexed by year with one column per calendar month (1-12),
pd.DataFrame plus an EOY column when eoy=True.

multiclass_precision_recall_curve

stats.multiclass_precision_recall_curve(y_true, y_score, *, classes=None)

Calculate one-vs-rest multiclass precision-recall curves and averages.

Parameters

Name Type Description Default
y_true Sequence[Any] | pd.Series | np.ndarray True class labels with shape (n_samples,). required
y_score pd.DataFrame | np.ndarray Class scores with shape (n_samples, n_classes). DataFrame column names are used as class labels when classes is omitted. required
classes Iterable[Any] | None Class labels in the same order as the score columns. None

Returns

Name Type Description
pd.DataFrame A tidy DataFrame with curve, class, recall, precision,
pd.DataFrame threshold, and average_precision columns.

multiclass_roc_curve

stats.multiclass_roc_curve(y_true, y_score, *, classes=None)

Calculate one-vs-rest multiclass ROC curves and micro/macro averages.

Parameters

Name Type Description Default
y_true Sequence[Any] | pd.Series | np.ndarray True class labels with shape (n_samples,). required
y_score pd.DataFrame | np.ndarray Class scores with shape (n_samples, n_classes). DataFrame column names are used as class labels when classes is omitted. required
classes Iterable[Any] | None Class labels in the same order as the score columns. None

Returns

Name Type Description
pd.DataFrame A tidy DataFrame with curve, class, fpr, tpr,
pd.DataFrame threshold, and auc columns.

noise_test

stats.noise_test(
    returns,
    sims=1000,
    *,
    return_type='simple',
    noise=0.1,
    bust=None,
    goal=None,
    confidence=0.95,
    seed=None,
)

Simulate noise-perturbed return paths to gauge sensitivity to day-to-day noise/volatility.

Each simulated path keeps the exact historical sequence of returns (same order, same length, same index) but scales every period’s return by 1 + eps where eps ~ Normal(0, noise) — i.e. jitters the magnitude of each period’s move by a random relative fraction without flipping its sign. This asks “would this exact strategy still perform similarly if the day-to-day noise/volatility in prices had been slightly different, but its underlying trend/edge (the sequence of up/down calls) was unchanged?” — a different question from :func:montecarlo (which varies the order/composition of returns via bootstrap resampling) or :func:variance_test (which varies the win rate itself over a forward horizon).

Note

Unlike :func:montecarlo, noise_test never reorders or resamples returns — every simulated path is the same length as returns and stays aligned to its original index, so no separate horizon/resampling-pool decoupling is needed here.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
sims int Number of simulated paths, including the original (unperturbed) path as the first column (sim_0). Must be at least 1. 1000
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
noise float Standard deviation of the multiplicative noise applied to each period’s return, e.g. 0.1 randomly scales each return’s magnitude by roughly ±10%. Must be non-negative. 0.1
bust float | None Optional Max Drawdown threshold (e.g. -0.2 for -20%). When given, bust_probability reports the fraction of simulated paths whose max drawdown breached it. None
goal float | None Optional cumulative-return threshold (e.g. 1.0 for +100%). When given, goal_probability reports the fraction of simulated paths that reached it. None
confidence float Confidence level for the per-period confidence_band. Defaults to 0.95 (a 95% band). 0.95
seed int | None Optional random seed for reproducibility. None

Returns

Name Type Description
dict[str, object] Dict with keys:
dict[str, object] - paths: DataFrame of cumulative simulated returns, one column per simulation (sim_0 is the original, unperturbed path), indexed like returns.
dict[str, object] - terminal_stats: Summary statistics (mean, std, min/25%/50%/75%/max) of the final cumulative return across simulations.
dict[str, object] - max_drawdown_stats: Same summary statistics for each simulation’s Max Drawdown.
dict[str, object] - confidence_band: DataFrame with Lower/Upper columns holding the confidence-level percentile range of cumulative return at each period.
dict[str, object] - bust_probability: Fraction of simulations at or below bust (None if bust wasn’t given).
dict[str, object] - goal_probability: Fraction of simulations at or above goal (None if goal wasn’t given).

omega

stats.omega(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    required_return=0.0,
)

Calculate the Omega ratio of a return stream.

The Omega ratio is the probability-weighted ratio of gains above a required-return threshold to losses below it, using the full return distribution rather than just its mean and variance (unlike Sharpe/Sortino).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0
required_return float Annualized minimum acceptable return threshold, deannualized the same way as rf. Defaults to 0.0. 0.0

Returns

Name Type Description
float Omega ratio.

outlier_loss_ratio

stats.outlier_loss_ratio(returns, *, return_type='simple', quantile=0.01)

Calculate the outlier loss ratio: a low quantile of returns vs. the average loss.

Ratio of the quantile (e.g. 1st percentile) return to the average losing return — how much a strategy’s worst outcomes are driven by rare, outsized losses rather than typical ones.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
quantile float Lower quantile used for the outlier threshold. Defaults to 0.01. 0.01

Returns

Name Type Description
float Outlier loss ratio.

outlier_win_ratio

stats.outlier_win_ratio(returns, *, return_type='simple', quantile=0.99)

Calculate the outlier win ratio: a high quantile of returns vs. the average win.

Ratio of the quantile (e.g. 99th percentile) return to the average winning return — how much a strategy’s best outcomes are driven by rare, outsized wins rather than typical ones.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
quantile float Upper quantile used for the outlier threshold. Defaults to 0.99. 0.99

Returns

Name Type Description
float Outlier win ratio.

outliers

stats.outliers(returns, quantile=0.95, *, return_type='simple')

Return the subset of returns above a given quantile threshold.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
quantile float Quantile threshold, e.g. 0.95 for the top 5% of returns. 0.95
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Returns above the quantile threshold.

payoff_ratio

stats.payoff_ratio(returns, *, return_type='simple')

Calculate the payoff ratio: average win divided by absolute average loss.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Payoff ratio.

performance

stats.performance(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    smart=False,
)

Calculate standard performance statistics for a periodic return stream.

Annualized metrics use periods_per_year when supplied; otherwise, a conventional frequency is inferred from a datetime index. rf (an annualized risk-free rate) is deannualized to match periods and subtracted from returns before computing CAGR, Sharpe, and Sortino (and, through CAGR, Calmar); Total Return, Volatility, Max Drawdown, and Win Rate always use raw returns.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing excess-return ratios. Defaults to 0.0. 0.0
smart bool Whether to penalize Sharpe/Sortino for return autocorrelation, see :func:sharpe/:func:sortino. Defaults to False. False

Returns

Name Type Description
pd.Series Series indexed by metric name:
pd.Series - Total Return: Simple compounded return over the full period.
pd.Series - CAGR: Compound Annual Growth Rate — the constant annual rate of return that would produce the same total growth.
pd.Series - Volatility: Volatility — annualized standard deviation of returns, the most common measure of risk.
pd.Series - Sharpe: Sharpe ratio — annualized excess return per unit of volatility, i.e. return earned per unit of total risk taken.
pd.Series - Sortino: Sortino ratio — like Sharpe, but only penalizes downside volatility, since investors rarely mind upside swings.
pd.Series - Calmar: Calmar ratio — CAGR divided by the absolute Max Drawdown, i.e. return per unit of worst-case pain endured.
pd.Series - Max Drawdown: Drawdown — largest peak-to-trough decline in cumulative value over the period.
pd.Series - Win Rate: Share of non-zero-return periods that were positive (periods with exactly zero return are excluded from both the count of wins and the total).
pd.Series - Periods: Number of return observations used.

period_returns

stats.period_returns(returns, *, return_type='simple', periods_per_year=None)

Compound returns over standard trailing and calendar windows (MTD, 3M, …, all-time).

Windows shorter than one year report the plain compounded return; multi-year windows report the annualized (CAGR) rate. Windows extending before the first observation use whatever data is available.

Parameters

Name Type Description Default
returns pd.Series Periodic return series with a DatetimeIndex. required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency for the multi-year windows. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Series indexed by window label: MTD, 3M, 6M, YTD, 1Y,
pd.Series 3Y (ann.), 5Y (ann.), 10Y (ann.), and All-time (ann.).

probabilistic_ratio

stats.probabilistic_ratio(
    returns,
    *,
    base='sharpe',
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    smart=False,
)

Calculate the probability that a risk-adjusted ratio is truly positive.

The Probabilistic Sharpe Ratio (Bailey & Lopez de Prado, 2012) converts a point-estimate ratio into a confidence level by accounting for sample size, skew, and kurtosis, all of which affect how noisy the ratio estimate is. The same correction is applied here to the Sortino and adjusted-Sortino ratios.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
base Literal['sharpe', 'sortino', 'adjusted_sortino'] Which ratio to convert: "sharpe", "sortino", or "adjusted_sortino". 'sharpe'
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the base ratio. Defaults to 0.0. 0.0
smart bool Whether to penalize the base ratio for return autocorrelation, see :func:sharpe/:func:sortino. False

Returns

Name Type Description
float Probability (0-1) that the true ratio is greater than zero.

profit_factor

stats.profit_factor(returns, *, return_type='simple')

Calculate the profit factor: total gains divided by total losses.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Profit factor.

profit_ratio

stats.profit_ratio(returns, *, return_type='simple')

Calculate the profit ratio: win frequency divided by loss frequency.

Distinct from :func:payoff_ratio (which compares win/loss size) and :func:profit_factor (which compares total gains vs. total losses) — this compares how often the strategy wins vs. how often it loses.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Profit ratio.

r_squared

stats.r_squared(returns, benchmark, *, return_type='simple')

Calculate R² (coefficient of determination) between strategy and benchmark returns.

is the square of the Pearson correlation between strategy and benchmark returns — how much of the strategy’s variance is explained by benchmark movements. Closer to 1 means the strategy moves in lockstep with the benchmark; closer to 0 means it’s largely independent.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float R², in [0, 1].

rar

stats.rar(returns, *, return_type='simple', periods_per_year=None, rf=0.0)

Calculate the Risk-Adjusted Return (CAGR divided by exposure).

Divides CAGR by :func:exposure (the fraction of periods actually invested, i.e. with non-zero returns), so a strategy that earns its return while sitting in cash most of the time scores higher than one that needed to be fully invested throughout.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing CAGR. Defaults to 0.0. 0.0

Returns

Name Type Description
float Risk-adjusted return.

rebase

stats.rebase(prices, base=100.0)

Rescale a price series to a common starting value.

Useful for plotting or comparing price series that start on different scales (e.g. a strategy’s equity curve against a benchmark index).

Parameters

Name Type Description Default
prices pd.Series Price series. Must contain at least one non-null, non-zero value. required
base float Starting value after rebasing. Defaults to 100.0. 100.0

Returns

Name Type Description
pd.Series Rebased price series, same index as prices.

recovery_factor

stats.recovery_factor(returns, *, return_type='simple', rf=0.0)

Calculate the recovery factor: total return divided by Max Drawdown.

How many multiples of the worst peak-to-trough decline the strategy ultimately earned back — higher values mean faster, more complete recovery from drawdowns.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
rf float Amount subtracted from total return before dividing. Defaults to 0.0. 0.0

Returns

Name Type Description
float Recovery factor.

remove_outliers

stats.remove_outliers(returns, quantile=0.95, *, return_type='simple')

Return the subset of returns below a given quantile threshold, discarding upper-tail outliers.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
quantile float Quantile threshold, e.g. 0.95 to discard the top 5% of returns. 0.95
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Returns below the quantile threshold.

returns

stats.returns(
    data,
    benchmark=None,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Bind a return stream (and optional benchmark) for chained stats and plotting.

Parameters

Name Type Description Default
data pd.Series Periodic return series (simple or log, per return_type). required
benchmark pd.Series | None Optional benchmark periodic return series, aligned to data on shared dates. Required for relative stats such as .alpha()/.beta(). None
return_type ReturnType Whether data/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, used by .performance() and .rolling_sharpe(). Defaults to 0.0. 0.0

Returns

Name Type Description
A Returns class:Returns object exposing bound stats (.performance(),
Returns .alpha(), .beta(), .rolling_beta(), …) and .plot(kind=...).

risk_of_ruin

stats.risk_of_ruin(returns, *, return_type='simple')

Estimate the risk of ruin.

The risk of ruin is the probability of losing the entire trading account, from the classic gambler’s-ruin formula applied to the strategy’s per-period win rate.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Probability of ruin, in [0, 1].

risk_return_ratio

stats.risk_return_ratio(returns, *, return_type='simple')

Calculate the risk-return ratio: mean return divided by its standard deviation.

Like the Sharpe ratio, but un-annualized and without a risk-free rate — the simplest possible return-per-unit-of-risk measure.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Risk-return ratio.

rolling_alpha

stats.rolling_alpha(
    returns,
    benchmark,
    window=63,
    *,
    return_type='simple',
    periods_per_year=None,
)

Return annualized rolling Jensen alpha relative to a benchmark.

Jensen’s alpha is the annualized return left over after accounting for the return explained by beta exposure to the benchmark — the excess return generated (or lost) beyond what market risk alone would predict.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
window int Rolling window size, in periods. 63
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Series of annualized rolling Jensen alpha.

rolling_beta

stats.rolling_beta(returns, benchmark, window=63, *, return_type='simple')

Return rolling beta of strategy returns relative to a benchmark.

Beta measures sensitivity to benchmark (market) movements: the covariance of strategy and benchmark returns divided by the benchmark’s variance. A beta of 1 tracks the benchmark 1:1; above 1 amplifies its moves; below 1 dampens them. See also the Capital Asset Pricing Model, which uses beta to model expected return as a function of systematic risk.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
window int Rolling window size, in periods. 63
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Series of rolling beta values.

rolling_factor_regression

stats.rolling_factor_regression(
    returns,
    factors,
    window=63,
    *,
    return_type='simple',
    rf='RF',
    min_observations=None,
)

Calculate rolling multi-factor OLS betas over a moving window.

Refits :func:factor_regression’s OLS on each trailing window of aligned observations, e.g. the rolling Fama-French five-factor betas that reveal time-varying exposure a single full-period fit cannot. Following statsmodels.regression.rolling.RollingOLS’s convention, each window’s estimate is stored at the date of its last observation, so the window is a trailing observation window (not a calendar-day window) – with daily data, window=63 is roughly 3 trading months.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
factors pd.DataFrame DataFrame of periodic factor return columns, aligned to returns. required
window int Trailing window size, in periods. Defaults to 63. 63
return_type ReturnType Whether returns is "simple" or "log" returns. 'simple'
rf str | pd.Series | None See :func:factor_regression. 'RF'
min_observations int | None Minimum aligned observations required before the first (noisier, shorter-window) estimate is produced. Defaults to window (a full window); every estimate still reports its actual observation count in N Obs. None

Returns

Name Type Description
pd.DataFrame DataFrame indexed like the aligned returns/factors data, with columns
pd.DataFrame Alpha, one per factor (in factors’ order), , and N Obs.
pd.DataFrame Rows before the first min_observations aligned dates are NaN
pd.DataFrame (there is no complete window yet).

rolling_sharpe

stats.rolling_sharpe(
    returns,
    window=63,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Return annualized rolling Sharpe ratios over a moving window.

The Sharpe ratio is the annualized mean excess return divided by the annualized volatility of returns — return earned per unit of total risk taken.

Parameters

Name Type Description Default
returns pd.Series Periodic return series. required
window int Rolling window size, in periods. 63
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0

Returns

Name Type Description
pd.Series Series of annualized rolling Sharpe ratios.

rolling_sortino

stats.rolling_sortino(
    returns,
    window=63,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Return annualized rolling Sortino ratios over a moving window.

The Sortino ratio is like the Sharpe ratio, but only penalizes downside volatility, since investors rarely mind upside swings.

Parameters

Name Type Description Default
returns pd.Series Periodic return series. required
window int Rolling window size, in periods. 63
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0

Returns

Name Type Description
pd.Series Series of annualized rolling Sortino ratios.

rolling_volatility

stats.rolling_volatility(
    returns,
    window=63,
    *,
    return_type='simple',
    periods_per_year=None,
)

Return annualized rolling volatility for a periodic return stream.

Volatility is the standard deviation of returns — the most common measure of risk. Unlike Sortino-style measures, it penalizes upside and downside swings equally.

Parameters

Name Type Description Default
returns pd.Series Periodic return series. required
window int Rolling window size, in periods. 63
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
pd.Series Series of annualized rolling volatility.

serenity_index

stats.serenity_index(returns, *, return_type='simple', rf=0.0)

Calculate the Serenity Index.

A composite drawdown-aware risk-adjusted return measure (KeyQuant, 2011): total excess return divided by the :func:ulcer_index scaled by a “pitfall” factor — the tail risk of drawdowns (their Conditional VaR) relative to overall return volatility. Penalizes strategies whose drawdowns are both deep/prolonged (high Ulcer Index) and concentrated in a few severe episodes (high pitfall).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
rf float Amount subtracted from total return before dividing. Defaults to 0.0. 0.0

Returns

Name Type Description
float Serenity Index.

sharpe

stats.sharpe(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    smart=False,
)

Calculate the annualized Sharpe ratio of excess returns.

The Sharpe ratio is the annualized mean excess return divided by the annualized volatility of returns — return earned per unit of total risk taken.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0
smart bool Whether to penalize the denominator for return autocorrelation via :func:autocorr_penalty, which otherwise inflates the ratio for serially correlated (e.g. smoothed or illiquid) return streams. Defaults to False. False

Returns

Name Type Description
float Annualized Sharpe ratio.

skew

stats.skew(returns, *, return_type='simple')

Calculate the skewness of returns.

Skewness measures the degree of asymmetry of the return distribution around its mean. Positive skew means a longer right tail (occasional large gains); negative skew means a longer left tail (occasional large losses).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Sample skewness.

sortino

stats.sortino(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
    smart=False,
)

Calculate the annualized Sortino ratio of excess returns.

The Sortino ratio is like the Sharpe ratio, but only penalizes downside volatility, since investors rarely mind upside swings.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0
smart bool Whether to penalize the denominator for return autocorrelation, see :func:autocorr_penalty. Defaults to False. False

Returns

Name Type Description
float Annualized Sortino ratio.

tail_ratio

stats.tail_ratio(returns, *, return_type='simple', cutoff=0.95)

Calculate the tail ratio between the right and left tails of the return distribution.

Ratio of the cutoff percentile (e.g. 95th) to the 1 - cutoff percentile (e.g. 5th) of returns — how large favorable outliers are relative to unfavorable ones.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
cutoff float Upper percentile used for the right tail; 1 - cutoff is used for the left tail. Defaults to 0.95. 0.95

Returns

Name Type Description
float Tail ratio.

to_drawdown_series

stats.to_drawdown_series(returns, *, return_type='simple')

Convert a return series into its running drawdown series.

Each value is the percentage decline from the running peak of cumulative growth (0 at new highs, negative during drawdowns).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Drawdown series (non-positive values), same index as returns.

to_log_returns

stats.to_log_returns(returns, *, return_type='simple')

Convert a return series to log (continuously compounded) returns.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
pd.Series Log return series (ln(1 + returns)).

to_prices

stats.to_prices(returns, *, return_type='simple', base=1.0)

Convert a return series into a cumulative price/equity curve.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
base float Starting value of the curve. Defaults to 1.0. 1.0

Returns

Name Type Description
pd.Series Cumulative price series starting at base, same index as returns.

trade_stats

stats.trade_stats(trades, by=None)

Calculate trade-level statistics from a canonical trades-format log.

Unlike the return-stream metrics (:func:performance, :func:win_rate, …), these operate on whole round-trip trades – so Win Rate is the share of winning trades, Avg Days the mean holding period, and Exposure the fraction of calendar time (first entry to last exit) with an open position, computed from the union of actual trade spans.

Parameters

Name Type Description Default
trades pd.DataFrame Canonical trades-format DataFrame (see :mod:qrt.data.datasets). required
by str | None Optional column to group by (e.g. "exit_reason", "direction", or any feature-snapshot column). When given, the result has one column per group next to the All column. None

Returns

Name Type Description
pd.DataFrame DataFrame of metrics (rows) by group (columns):
pd.DataFrame - Trades: Number of round-trip trades.
pd.DataFrame - Win Rate: Share of non-zero-return trades that were profitable.
pd.DataFrame - Total Return: Compounded return across the group’s trades.
pd.DataFrame - Avg Return / Avg Win / Avg Loss: Mean trade returns (overall, winners only, losers only).
pd.DataFrame - Payoff Ratio: Avg Win / \
pd.DataFrame - Profit Factor: Gross wins / gross losses.
pd.DataFrame - Best / Worst: Extreme single-trade returns.
pd.DataFrame - Avg MAE / Avg MFE: Mean max adverse/favorable excursion (NaN when the columns are absent).
pd.DataFrame - Avg Days / Median Days: Holding period in calendar days.
pd.DataFrame - Exposure: Fraction of the group’s calendar span with an open position.

Raises

Name Type Description
ValueError If trades lacks canonical columns, or by is not a column.

trades_to_positions

stats.trades_to_positions(trades, index)

Convert a trade log into a signed position series over index.

Each trade contributes +1 (long) or -1 (short) on every bar from its entry_time through its exit_time (inclusive); overlapping trades sum. Bars with no open trade are 0 – the basis for exposure timelines and time-in-market analysis.

Parameters

Name Type Description Default
trades pd.DataFrame Canonical trades-format DataFrame. required
index pd.Index Target index (e.g. prices.index) to evaluate positions on. required

Returns

Name Type Description
pd.Series Integer position series named "position" over index.

trades_to_returns

stats.trades_to_returns(trades, prices=None)

Convert a trade log into a periodic return stream.

The bridge from qrt’s canonical trades format (one row per round-trip trade, see :mod:qrt.data.datasets) to the return-stream form every other :mod:qrt.stats/:mod:qrt.plot function consumes.

Without prices, each trade’s whole return is attributed at its exit_time (compounded when several trades exit on the same bar) – fine for aggregate stats, but intra-trade drawdown and volatility are invisible. With prices (a close series covering the trade span), trades are marked to market: each bar in a trade contributes its close-to-close return (entry bar: close vs entry_price; exit bar: exit_price vs previous close), direction-adjusted, and bars with no open trade contribute 0.0 – giving honest drawdown/volatility/ exposure statistics.

Note

For long trades, marked-to-market bar returns compound exactly to the trade’s stored return. For shorts they model a daily rebalanced short position, which compounds slightly differently than the unrebalanced trade-level return – both are correct, they answer different questions.

Parameters

Name Type Description Default
trades pd.DataFrame Canonical trades-format DataFrame. required
prices pd.Series | None Optional close series with a DatetimeIndex covering the trade span (e.g. q.data.datasets.load("spy")["close"]). None

Returns

Name Type Description
pd.Series Periodic simple-return series named "return", indexed by
pd.Series exit_time (without prices) or by the full prices index
pd.Series (with prices).

treynor_ratio

stats.treynor_ratio(
    returns,
    benchmark,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Calculate the annualized Treynor ratio relative to a benchmark.

The Treynor ratio is the annualized mean excess return divided by beta — like the Sharpe ratio, but measuring return earned per unit of systematic (market) risk rather than total risk.

Parameters

Name Type Description Default
returns pd.Series Strategy periodic return series. required
benchmark pd.Series Benchmark periodic return series, aligned on shared dates. required
return_type ReturnType Whether returns/benchmark are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing the ratio. Defaults to 0.0. 0.0

Returns

Name Type Description
float Annualized Treynor ratio.

ulcer_index

stats.ulcer_index(returns, *, return_type='simple')

Calculate the Ulcer Index.

The Ulcer Index is the root-mean-square of drawdowns from the running peak, capturing both the depth and duration of drawdowns rather than just their worst point (unlike Max Drawdown).

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'

Returns

Name Type Description
float Ulcer Index.

ulcer_performance_index

stats.ulcer_performance_index(
    returns,
    *,
    return_type='simple',
    periods_per_year=None,
    rf=0.0,
)

Calculate the Ulcer Performance Index (Martin ratio).

Annualized excess return divided by the :func:ulcer_index — like Calmar, but risk is measured by the depth and duration of drawdowns rather than just the single worst one.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None
rf float Annualized risk-free rate, subtracted (after deannualizing) from returns before computing CAGR. Defaults to 0.0. 0.0

Returns

Name Type Description
float Ulcer Performance Index.

value_at_risk

stats.value_at_risk(returns, *, return_type='simple', confidence=0.95)

Calculate the parametric (variance-covariance) Value at Risk.

Value at Risk assumes normally distributed returns and estimates the maximum expected loss over one period at the given confidence level from the sample mean and standard deviation.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
confidence float Confidence level, e.g. 0.95 for a 95% VaR. Defaults to 0.95. 0.95

Returns

Name Type Description
float Value at Risk (a negative number representing a loss).

variance_test

stats.variance_test(
    trades,
    periods,
    sims=1000,
    *,
    return_type='simple',
    win_rate_variance=0.1,
    ruin=-0.9,
    confidence=0.95,
    seed=None,
)

Simulate forward trades with a randomly varied win rate to stress-test a strategy’s edge.

Unlike :func:montecarlo (which resamples periodic returns to explore plausible histories), this simulates forward, trade-by-trade: each simulated path draws periods future trades by (1) picking a per-simulation win rate randomly perturbed by up to win_rate_variance from the historical win rate, then (2) for each trade, flipping a coin at that win rate and sampling a return with replacement from the historical winning or losing trades accordingly. This directly stresses whether a strategy’s edge holds up if its win rate drifts, rather than just reordering/bootstrapping the exact historical return sequence.

Note

Like the rest of this package, results are expressed as compounded (multiplicative) cumulative returns rather than account-currency dollar P&L — pass per-trade returns in trades, not dollar amounts.

Note

When trades has a DatetimeIndex, paths is indexed by periods timestamps extrapolated forward from the last historical trade date (step size inferred via :func:pandas.infer_freq, falling back to the median spacing for irregular series), continuing the timeline where real_path leaves off, rather than a bare trade count — consistent with the date axes used throughout :mod:qrt.plot. Falls back to a plain 0..periods - 1 trade-count index otherwise.

Parameters

Name Type Description Default
trades pd.Series Per-trade return series (simple or log, per return_type) — one entry per historical trade, not necessarily a periodic/calendar series, though a DatetimeIndex is used to date the simulated horizon (see Note above) when present. Must contain at least one winning and one losing (or breakeven) trade. required
periods int Number of future trades to simulate per path. Must be at least 1. required
sims int Number of simulated paths. Must be at least 1. 1000
return_type ReturnType Whether trades are "simple" or "log" returns. 'simple'
win_rate_variance float Amount by which each simulation’s win rate is randomly perturbed (uniformly, in both directions) from the historical win rate — e.g. 0.1 varies a 55% historical win rate anywhere from 45% to 65% per simulation. Clipped to [0, 1]. Defaults to 0.1. 0.1
ruin float Max Drawdown threshold (e.g. -0.9 for -90%) below which a path is considered “ruined” (having lost most or all of its capital). ruin_probability reports the fraction of paths that breached it. Defaults to -0.9. -0.9
confidence float Confidence level for the per-trade confidence_band. Defaults to 0.95. 0.95
seed int | None Optional random seed for reproducibility. None

Returns

Name Type Description
dict[str, object] Dict with keys:
dict[str, object] - paths: DataFrame of cumulative simulated returns, one column per simulation, indexed either by periods extrapolated future timestamps (when trades has a DatetimeIndex) or a plain 0..periods - 1 trade count otherwise — see the Note above.
dict[str, object] - real_path: Series of the actual historical cumulative compounded return over the most recent min(periods, len(trades)) trades, for reference/comparison — not part of the simulation. Truncated to periods (rather than the full trades history) so its scale and length stay comparable to the simulated paths, and immediately precedes paths on the timeline when dated.
dict[str, object] - win_rates: Array of the randomly perturbed win rate used by each simulation.
dict[str, object] - terminal_stats: Summary statistics (mean, std, min/25%/50%/75%/max) of the final cumulative return across simulations.
dict[str, object] - max_drawdown_stats: Same summary statistics for each simulation’s Max Drawdown.
dict[str, object] - confidence_band: DataFrame with Lower/Upper columns holding the confidence-level percentile range of cumulative return at each trade.
dict[str, object] - make_money_probability: Fraction of simulations with a positive terminal return.
dict[str, object] - ruin_probability: Fraction of simulations whose Max Drawdown breached ruin.
dict[str, object] - average_profit/average_drawdown: Mean terminal return / mean Max Drawdown (signed) across simulations.
dict[str, object] - pnldd_ratio: average_profit / abs(average_drawdown) — average return earned per unit of average drawdown risked.

volatility

stats.volatility(returns, *, return_type='simple', periods_per_year=None)

Calculate annualized volatility.

The standard deviation of returns — the most common measure of risk. See :func:rolling_volatility for a moving-window variant.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
periods_per_year int | None Annualization frequency. Inferred from the index when not given. None

Returns

Name Type Description
float Annualized volatility.

win_rate

stats.win_rate(returns, *, return_type='simple', aggregate=None)

Calculate the win rate: the share of non-zero-return periods that were positive.

Periods with exactly zero return are excluded from both the count of wins and the total.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the share of winning months. None

Returns

Name Type Description
float Win rate (0-1 scale).

worst

stats.worst(returns, *, return_type='simple', aggregate=None)

Return the single worst (lowest) period return.

Parameters

Name Type Description Default
returns pd.Series Periodic return series (simple or log, per return_type). required
return_type ReturnType Whether returns are "simple" or "log" returns. 'simple'
aggregate Literal['W', 'M', 'Q', 'Y'] | None Optional calendar period to compound into first (see :func:aggregate_returns), e.g. "M" for the worst month. None

Returns

Name Type Description
float Minimum periodic return.
Back to top