Risk Measures for Time Series

In [20]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import PortfolioAnalytics as PortfolioAnalytics
import PortfolioLoadData as PortfolioLoadData
import PortfolioRisk as PortfolioRisk
import PortfolioStatistics as PortfolioStatistics

%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

Downside Measures: SemiDeviation, VaR and cVaR

We're going to look at a few measures of downside risk. We've already seen how to compute drawdowns, but we're going to look at 3 popular measures.

The first measure is the simplest, which is the semideviation, which is nothing more than the volatility of the subset of returns that are negative or below a certain threshold.

In [21]:
hfi = PortfolioLoadData.get_hfi_returns()
In [22]:
PortfolioRisk.semideviation(hfi).sort_values()
Out[22]:
Global Macro              0.006579
Merger Arbitrage          0.008875
Equity Market Neutral     0.009566
Funds Of Funds            0.012122
Relative Value            0.012244
CTA Global                0.012443
Long/Short Equity         0.014051
Distressed Securities     0.015185
Event Driven              0.015429
Fixed Income Arbitrage    0.017763
Convertible Arbitrage     0.019540
Short Selling             0.027283
Emerging Markets          0.028039
dtype: float64

VaR and cVaR

We'll look at four different ways to compute Value At Risk

  1. Historic VaR
  2. Parametric Gaussian VaR
  3. Modified (Cornish-Fisher) VaR
  4. Conditional VaR

To compute the historic VaR at a certain level, say 5%, all we have to do is to find the number such that 5% of the returns fall below that number and 95% of the returns fall above that number. In other words, we want the 5 percentile return.

Note that for reporting purposes, it is common to invert the sign so we report a positive number to represent the loss i.e. the amount that is at risk.

Historical VaR

In [23]:
PortfolioRisk.var_historic(hfi, level=1).sort_values()
Out[23]:
Equity Market Neutral     0.018000
Global Macro              0.024316
Merger Arbitrage          0.025336
Relative Value            0.026660
Convertible Arbitrage     0.031776
Funds Of Funds            0.039664
Fixed Income Arbitrage    0.041672
Distressed Securities     0.046654
Event Driven              0.048612
CTA Global                0.049542
Long/Short Equity         0.049558
Emerging Markets          0.088466
Short Selling             0.113576
dtype: float64

Conditional VaR a.k.a. Beyond VaR

Now that we have the VaR, the CVaR is very easy. All we need is to find the mean of the numbers that fell below the VaR!

In [24]:
PortfolioRisk.cvar_historic(hfi, level=1).sort_values()
Out[24]:
Global Macro              0.029333
Equity Market Neutral     0.036100
Merger Arbitrage          0.036233
Relative Value            0.052367
CTA Global                0.054767
Funds Of Funds            0.061133
Long/Short Equity         0.061867
Distressed Securities     0.070967
Event Driven              0.071267
Fixed Income Arbitrage    0.072467
Convertible Arbitrage     0.086100
Short Selling             0.123867
Emerging Markets          0.141167
dtype: float64

Parametric Gaussian VaR

The idea behind this is very simple. If a set of returns is normally distributed, we know, for instance, that 50% of the returns are below the mean and 50% are above.

We also know that approx two thirds of the returns lie within 1 standard deviation. That means one third lie beyond one standard deviation from the mean. Since the normal distribution is symmetric, approximately one sixth (approx 16%) lie below one standard deviation away from the mean. Therefore, if we know the mean and standard deviation and if we assume that the returns are normally distributed, the 16% VaR would be the mean minus one standard deviation.

In general we can always convert a percentile point to a z-score (which is the number of standard deviations away from the mean that a number is). Therefore, if we can convert the VaR level (such as 1% or 5%) to a z-score, we can calculate the return level where that percent of returns lie below it.

scipy.stat.norm contains a function ppf() which does exactly that. It takes a percentile such as 0.05 or 0.01 and gives you the z-score corresponding to that in the normal distribution.

Therefore, all we need to do to estimate the VaR using this method is to find the z-score corresponding to percentile level, and then add that many standard deviations to the mean, to obtain the VaR.

In [25]:
PortfolioRisk.var_gaussian(hfi).sort_values()
Out[25]:
Equity Market Neutral     0.008850
Merger Arbitrage          0.010435
Relative Value            0.013061
Fixed Income Arbitrage    0.014579
Global Macro              0.018766
Distressed Securities     0.021032
Event Driven              0.021144
Funds Of Funds            0.021292
Convertible Arbitrage     0.021691
Long/Short Equity         0.026397
CTA Global                0.034235
Emerging Markets          0.047164
Short Selling             0.080086
dtype: float64

Cornish-Fisher Modification

The Cornish-Fisher modification is an elegant and simple adjustment.

The z-score tells us how many standard deviations away from the mean we need to go to find the VaR. If the returns arent normal, we know that z-score will give us an inaccurate number. The basic idea is that since we can observe the skewness and kurtosis of the data, we can adjust the z-score up or down to come up with a modifed z-score. e.g. intuitively, all other things being equal, if the skewness is negative, we'll decrease the z-score further down, and if the skewness is positive, we'll push it up.

The adjusted z-score which we'll call $z_{cornishfisher}$ given by:

$$ z_{cornishfisher} = z +\frac{1}{6}(z^2-1)S + \frac{1}{24}(z^3-3z)(K-3)-\frac{1}{36}(2z^3-5z)S^2 $$

We can modify the previous function by adding a "modified" parameter with a default value of True as follows. If True then the following piece of code is executed, which modifes z:

In [26]:
PortfolioRisk.var_gaussian(hfi, modified=True).sort_values()
Out[26]:
Equity Market Neutral     0.010734
Merger Arbitrage          0.012612
Global Macro              0.013581
Relative Value            0.016157
Fixed Income Arbitrage    0.017881
Funds Of Funds            0.021576
Distressed Securities     0.025102
Convertible Arbitrage     0.025166
Event Driven              0.025516
Long/Short Equity         0.027935
CTA Global                0.033094
Emerging Markets          0.053011
Short Selling             0.066157
dtype: float64
In [27]:
var_table = [PortfolioRisk.var_historic(hfi),
             PortfolioRisk.cvar_historic(hfi), 
             PortfolioRisk.var_gaussian(hfi, modified=False), 
             PortfolioRisk.var_gaussian(hfi, modified=True), ]
comparison = pd.concat(var_table, axis=1)
comparison.columns=['Historic VaR', 'cVaR', 'Gaussian', 'Cornish-Fisher']
comparison.plot.bar(title="Hedge Fund Indices: VaR at 5%",figsize=(12,6))
Out[27]:
<matplotlib.axes._subplots.AxesSubplot at 0x153313e5e80>
In [ ]: