risk¶
Functions for measuring portfolio risk from multiple perspectives. These metrics quantify volatility, downside risk, tail risk, drawdown severity, distribution shape, and benchmark-relative sensitivity to help evaluate the uncertainty and stability of investment returns.
risk ¶
Risk (dispersion / tail-risk) metrics.
rf, mar (minimum acceptable return), and similar rate parameters are annual rates throughout PortPy, de-annualized internally via geometric compounding (utils.validation.periodic_rate_from_annual). This differs from empyrical, whose equivalent parameters (risk_free, required_return) are already-periodic rates. See the validation tests for a worked comparison.
variance ¶
Sample variance of per-period returns (ddof=1, not annualized).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The sample variance, in squared-return units. |
Source code in src/portpy/metrics/risk.py
volatility ¶
volatility(
returns: Series,
annualized: bool = True,
periods_per_year: int = TRADING_DAYS_PER_YEAR,
as_result: bool = False,
) -> float | MetricResult
Standard deviation of returns (ddof=1), annualized by sqrt(periods_per_year) by default.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
annualized
|
bool
|
If True (default), scale by sqrt(periods_per_year). |
True
|
periods_per_year
|
int
|
Number of periods in a year, used to annualize. |
TRADING_DAYS_PER_YEAR
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The (optionally annualized) standard deviation of returns. |
Source code in src/portpy/metrics/risk.py
downside_deviation ¶
downside_deviation(
returns: Series,
mar: float = DEFAULT_MAR,
annualized: bool = True,
periods_per_year: int = TRADING_DAYS_PER_YEAR,
as_result: bool = False,
) -> float | MetricResult
Root-mean-square of returns falling short of mar, dividing by the full sample size N.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
mar
|
float
|
Minimum acceptable return, as an annual rate. Defaults to DEFAULT_MAR. |
DEFAULT_MAR
|
annualized
|
bool
|
If True (default), scale by sqrt(periods_per_year). |
True
|
periods_per_year
|
int
|
Number of periods in a year, used to de-annualize
|
TRADING_DAYS_PER_YEAR
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The (optionally annualized) downside deviation. |
Source code in src/portpy/metrics/risk.py
semi_variance ¶
semi_variance(
returns: Series,
mar: float = DEFAULT_MAR,
periods_per_year: int = TRADING_DAYS_PER_YEAR,
as_result: bool = False,
) -> float | MetricResult
Mean squared shortfall below mar, dividing by the full sample size N.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
mar
|
float
|
Minimum acceptable return, as an annual rate. Defaults to DEFAULT_MAR. |
DEFAULT_MAR
|
periods_per_year
|
int
|
Number of periods in a year, used to de-annualize |
TRADING_DAYS_PER_YEAR
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The mean squared shortfall, in squared-return units. |
Source code in src/portpy/metrics/risk.py
value_at_risk ¶
value_at_risk(
returns: Series,
method: str = "historical",
confidence: float = DEFAULT_CONFIDENCE_LEVEL,
as_result: bool = False,
) -> float | MetricResult
Per-period loss threshold exceeded only (1 - confidence) of the time.
Reported in return units and signed like a return (a negative number means a loss); e.g. -0.03 at 95% confidence means "there's a 5% chance of losing more than 3% in a single period."
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
method
|
str
|
One of "historical" (empirical percentile), "parametric" (assumes Normally-distributed returns), or "cornish_fisher" (parametric VaR adjusted for the sample's skewness and kurtosis). |
'historical'
|
confidence
|
float
|
Confidence level in (0, 1), e.g. 0.95. Defaults to DEFAULT_CONFIDENCE_LEVEL. |
DEFAULT_CONFIDENCE_LEVEL
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The per-period VaR at the given confidence level. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/portpy/metrics/risk.py
conditional_var ¶
conditional_var(
returns: Series,
confidence: float = DEFAULT_CONFIDENCE_LEVEL,
as_result: bool = False,
) -> float | MetricResult
Conditional VaR / Expected Shortfall: the average loss in the worst (1 - confidence) fraction of periods.
Always at least as severe as value_at_risk at the same confidence. It answers "given that we're in the bad tail, how bad is it on average?" rather than just where the tail starts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
confidence
|
float
|
Confidence level in (0, 1), e.g. 0.95. Defaults to DEFAULT_CONFIDENCE_LEVEL. |
DEFAULT_CONFIDENCE_LEVEL
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The average per-period loss in the tail beyond the VaR cutoff. |
Source code in src/portpy/metrics/risk.py
tail_ratio ¶
Ratio of the size of the right tail (95th percentile) to the left tail (5th percentile).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The tail ratio. |
Source code in src/portpy/metrics/risk.py
skewness ¶
Sample skewness (adjusted Fisher-Pearson) of the return distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The sample skewness. |
Source code in src/portpy/metrics/risk.py
kurtosis ¶
Sample excess kurtosis (0 = Normal-like tails; positive = fatter tails than Normal).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The sample excess kurtosis. |
Source code in src/portpy/metrics/risk.py
ulcer_index ¶
Root-mean-square of percentage drawdowns. Penalizes both deep and prolonged drawdowns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
Series
|
Price series. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The ulcer index. |
Source code in src/portpy/metrics/risk.py
pain_index ¶
Mean absolute drawdown across the whole period (a.k.a. average drawdown).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
Series
|
Price series. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The pain index. |
Source code in src/portpy/metrics/risk.py
beta ¶
Sensitivity of returns to a benchmark.
Formula: Cov(r, b) / Var(b)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
benchmark
|
Series
|
Per-period benchmark returns. |
required |
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The beta coefficient. |
Source code in src/portpy/metrics/risk.py
tracking_error ¶
tracking_error(
returns: Series,
benchmark: Series,
annualized: bool = True,
periods_per_year: int = TRADING_DAYS_PER_YEAR,
as_result: bool = False,
) -> float | MetricResult
Standard deviation of the return difference (returns - benchmark), i.e. active risk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Per-period returns. |
required |
benchmark
|
Series
|
Per-period benchmark returns. |
required |
annualized
|
bool
|
If True (default), scale by sqrt(periods_per_year). |
True
|
periods_per_year
|
int
|
Number of periods in a year, used to annualize. |
TRADING_DAYS_PER_YEAR
|
as_result
|
bool
|
If True, return a MetricResult instead of a plain float. |
False
|
Returns:
| Type | Description |
|---|---|
float | MetricResult
|
The (optionally annualized) tracking error. |