quantlab.runs.backtest_stats.return_stats#

quantlab.runs.backtest_stats.return_stats(returns, *, bar_interval, year_freq, ranges=None)[source]#

Return the return statistics of a per-bar return series, keyed by row name.

These are the rows of the in_sample, out_of_sample and benchmark blocks of metrics.json, equal to the bit to vectorbt’s ReturnsAccessor.stats with freq=bar_interval and year_freq. ann below is year_freq / bar_interval, the bars per year, and a NaN return is skipped by every row except Period and the annualization exponent, which count every bar.

  • Start, End: the first and last bar label (pd.Timestamp); Period: the number of bars times bar_interval.

  • Total Return [%]: compounded return; Annualized Return [%]: (1 + total) ** (ann / bars) - 1; Annualized Volatility [%]: standard deviation (ddof 1) times sqrt(ann).

  • Max Drawdown [%]: the deepest drawdown of the compounded curve, a positive percentage, NaN without any drawdown; Max Drawdown Duration: the longest one in bars times bar_interval (NaT without any).

  • Sharpe Ratio (mean over standard deviation times sqrt(ann)), Calmar Ratio, Omega Ratio and Sortino Ratio with a zero risk-free rate and required return; inf when the denominator is 0.

  • Skew and Kurtosis (pandas, bias-corrected), Tail Ratio (95th over 5th percentile, absolute), Common Sense Ratio (tail ratio times one plus the annualized return) and Value at Risk (the 5th percentile return, a fraction).

Parameters:
returnsxarray.DataArray

Per-bar returns on a sorted timestamp axis.

bar_interval

The bar spacing, anything pd.Timedelta accepts.

year_freqpd.Timedelta

One year, as year_freq returns it.

rangessequence of (str, str), optional

Inclusive bar-label ranges to cut the series to, concatenated in order; the whole series when omitted.

Returns:
dict

Seventeen rows, in the order listed above.

Raises:
ValueError

If no return falls inside ranges.

Examples

>>> returns = xr.DataArray(
...     [0.01, -0.02, 0.015, 0.005], dims=("timestamp",),
...     coords={"timestamp": pd.bdate_range("2024-01-01", periods=4)},
... )
>>> stats = return_stats(
...     returns, bar_interval="1D", year_freq=year_freq("1D", 252, 390)
... )
>>> round(stats["Total Return [%]"], 6), stats["Period"]
(0.967023, Timedelta('4 days 00:00:00'))
>>> round(stats["Max Drawdown [%]"], 6), stats["Max Drawdown Duration"]
(2.0, Timedelta('3 days 00:00:00'))
>>> sliced = return_stats(
...     returns, bar_interval="1D", year_freq=year_freq("1D", 252, 390),
...     ranges=[("2024-01-02", "2024-01-03")],
... )
>>> round(sliced["Total Return [%]"], 6), sliced["Period"]
(-0.53, Timedelta('2 days 00:00:00'))