VaR
VaR copied to clipboard
Passed in daily PNL while cumulative PNL is expected.
def compute_cdar(pnl: array_like, var: array_like) -> np.ndarray:
"""Compute the Drawdown of a portfolio
Parameters
----------
pnl : array_like
Profit and Loss values.
Returns
-------
np.ndarray
Drawdowns
"""
# Compute the drawdowns
running_max = np.maximum.accumulate(pnl)
drawdowns = running_max - pnl
drawdown_values = np.zeros_like(var)
for i, item in enumerate(var):
drawdown_values[i] = np.nanmean(drawdowns[drawdowns > item])
drawdown_values = np.nan_to_num(drawdown_values)
return drawdown_values
The above implementation of compute_cdar expects pnl argument to the cumulative pnl, while the the pnl passed in is daily pnl.
Reference: https://scikit-portfolio.github.io/scikit-portfolio/efficient_cdar/
Sorry for the late reply and thank you very much for the great catch! I will fix this on Monday. You can fix this too if you want and then generate a PR.
How about this:
import numpy as np
from typing import Union, Sequence
def compute_drawdown(
pnl: array_like,
var: array_like,
axis: int = 1,
is_cumulative: bool = False
) -> np.ndarray:
"""
Compute the Conditional Drawdown at Risk (CDaR) of a portfolio.
Parameters
----------
pnl : array_like
Daily or cumulative Profit and Loss values (NAV time series).
Shape: (n_portfolios, n_timesteps)
var : array_like
VaR threshold values (e.g., percentiles of drawdowns).
Shape: (n_portfolios, n_quantiles)
axis : int, default = 0
Axis along which time progresses.
is_cumulative : bool, default = False
If False, the function will accumulate the PnL using np.cumsum along the given axis.
Returns
-------
np.ndarray
CDaR values, shape (n_portfolios, n_quantiles)
"""
pnl = np.asarray(pnl)
var = np.asarray(var)
if not is_cumulative:
pnl = np.cumsum(pnl, axis=axis)
running_max = np.maximum.accumulate(pnl, axis=axis)
drawdowns = running_max - pnl # drawdowns are positive
# Compute tail drawdowns that exceed the threshold in `var`
masks = [drawdowns > var[:, [i]] for i in range(var.shape[-1])]
tails = np.where(masks, drawdowns, np.nan)
dd_values = np.nanmean(tails, axis=-1)
return -dd_values.T # Flip sign to make CDaR a risk measure (negative expected drawdown)
I started a new branch where I optimise most of the code, which will lead to a major release. I will resolve this bug in this branch, since the function has more issues then this.