Quantative Risk Management 4/4 - Advanced risk management

4/4 Objectives:

  1. explore more general risk management tools. These advanced techniques are pivotal when attempting to understand extreme events, such as losses incurred during the financial crisis, and complicated loss distributions which may defy traditional estimation techniques.
  2. discover how neural networks can be implemented to approximate loss distributions and conduct real-time portfolio optimization.

Extreme value theory

Extreme value theory uses statistics to help understand the distribution of extreme values. In other words, it is a way to help model _the tail_ of the loss distribution. One way to do this is called the “block maxima” approach.

Extreme value theory: statistical distribution of extre values
Block Maxima:

  • break period into sub-periods
  • form blocks from each sub-period
  • set of block maxima = dataset
    Peak over threshold (POT):
  • Find all losses over given level
  • Set of such losses = dataset

Generalized Extreme Value Distribution (GEV)

VaR and CVaR from GEV distribution

Coving losses

risk management: covering losses

  • regulatory requirement (banks, insurance)
  • reserves must be avaliable to cover losses (for a specific time peroid/ at a specific confidence level)

VaR from GEV distribution:
estimate maximum loss in a given period and at a given confidence level

Example: reserve requirement (储备金要求)
99%的置信水平和一周的时间范围内

Exercises

Block maxima

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Resample the data into weekly blocks
weekly_maxima = losses.resample("W").max()

# Plot the resulting weekly maxima
axis_1.plot(weekly_maxima, label = "Weekly Maxima")
axis_1.legend()
plt.figure("weekly")
plt.show()

# Resample the data into monthly blocks
monthly_maxima = losses.resample("M").max()

# Plot the resulting monthly maxima
axis_2.plot(monthly_maxima, label = "Monthly Maxima")
axis_2.legend()
plt.figure("monthly")
plt.show()

# Resample the data into quarterly blocks
quarterly_maxima = losses.resample("Q").max()

# Plot the resulting quarterly maxima
axis_3.plot(quarterly_maxima, label = "Quarterly Maxima")
axis_3.legend()
plt.figure("quarterly")
plt.show()

Block maxima

Extreme events during the crisis

1
2
3
4
5
6
7
8
9
# Plot the log daily losses of GE over the period 2007-2009
losses.plot()

# Find all daily losses greater than 10%
extreme_losses = losses[losses > 0.10]

# Scatter plot the extreme losses
extreme_losses.plot(style='o')
plt.show()

Extreme events during the crisis

1
2
3
4
5
6
7
8
# Fit extreme distribution to weekly maximum of losses
fitted = genextreme.fit(weekly_max)

# Plot extreme distribution with weekly max losses historgram
x = np.linspace(min(weekly_max), max(weekly_max), 100)
plt.plot(x, genextreme.pdf(x, *fitted))
plt.hist(weekly_max, 50, density = True, alpha = 0.3)
plt.show()

GEV risk estimation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Compute the weekly block maxima for GE's stock
weekly_maxima = losses.resample("W").max()

# Fit the GEV distribution to the maxima
p = genextreme.fit(weekly_maxima)

# Compute the 99% VaR (needed for the CVaR computation)
VaR_99 = genextreme.ppf(0.99, *p)

# Compute the 99% CVaR estimate
CVaR_99 = (1 / (1 - 0.99)) * genextreme.expect(lambda x: x,
args=(p[0],), loc = p[1], scale = p[2], lb = VaR_99)

# Display the covering loss amount
print("Reserve amount: ", 1000000 * CVaR_99)

结果

1
Reserve amount:  148202.41307731598

Kernel density estimation

The histogram revisited

Up to now our risk factor distributions have been assumed (such as the _Normal_ or _T distribution_), fitted (from, for example, _parametric estimation_ or _Monte Carlo simulation_), or ignored (as with _historical simulation_).

In each case, actual data can be summarized with a histogram. A histogram is not a distribution: but we would like a function to represent the histogram as a probability distribution (or density function).

How can we do this? A middle ground between parametric estimation and ignoring the distribution is to _filter the data so that they become smooth enough to represent a distribution._ This can be done with non-parametric estimation, which does not assume a parametrized class of distributions (such as the Normal, Skewed Normal, or Student’s t-distribution).

Data smoothing

Kernal: filter choice/ the “window”
kernel
smooth

The Gaussian kernel

gaussian

KDE in Python

KDE in Python

Find VaR using KDE

Find VaR using KDE

Exercises

1
2
3
4
5
6
7
8
9
10
11
# Generate a fitted T distribution over losses
params = t.fit(losses)

# Generate a Gaussian kernal density estimate over losses
kde = gaussian_kde(losses)

# Add the PDFs of both estimates to a histogram, and display
loss_range = np.linspace(np.min(losses), np.max(losses), 1000)
axis.plot(loss_range, t.pdf(loss_range, *params), label = 'T distribution')
axis.plot(loss_range, kde.pdf(loss_range), label = 'Gaussian KDE')
plt.legend(); plt.show()

KDE of a loss distribution

Fitted distributions

  • The Gaussian KDE captures the tails well, but another distribution is better at fitting the loss distribution’s peak.
  • The T distribution does capture the peak well. But there’s another distribution which captures the tails of the distribution better.
  • The T and Gaussian KDE estimates are both good fits, each in a different way: the T captures the peak well, while the KDE captures the tails better.

CVaR and loos cover selection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Find the VaR as a quantile of random samples from the distributions
VaR_99_T = np.quantile(t.rvs(size=1000, *p), 0.99)
VaR_99_KDE = np.quantile(kde.resample(size=1000), 0.99)

# Find the expected tail losses, with lower bounds given by the VaR measures
integral_T = t.expect(lambda x: x, args = (p[0],), loc = p[1], scale = p[2], lb = VaR_99_T)
integral_KDE = kde.expect(lambda x: x, lb = VaR_99_KDE)

# Create the 99% CVaR estimates
CVaR_99_T = (1 / (1 - 0.99)) * integral_T
CVaR_99_KDE = (1 / (1 - 0.99)) * integral_KDE

# Display the results
print("99% CVaR for T: ", CVaR_99_T, "; 99% CVaR for KDE: ", CVaR_99_KDE)

结果

1
99% CVaR for T:  0.2716201587142073 ; 99% CVaR for KDE:  0.2395800993943245

Neural network risk management

Real-time portfolio updating

Risk management

  • Define risk measures (VaR, CVaR)
  • Estimate risk measures (parameteric, historical, Monte Carlo)
  • Optimized portfolio (e.g. Mordern Portfolio Theory)

New Market information -> update portfolio weights

  • Problem: portfolio optimization costly
  • Solution: weights = f(weights)
  • Evaluate f in real-time
  • Update f only occasionally

Neural Networks

Neural Networks
A neural network is a function: it takes an input (such as _asset prices_) and returns an output (such as _portfolio weights_).

It’s called a neural network because it contains interconnected processing nodes known as ‘neurons’, loosely resembling the connections between neurons in the brain.

Neural networks have been around since the 1940s, but they have enjoyed a resurgence of development since the early 2000s.

They are used to solve problems with large data sets, such as image recognition, financial analysis, and search engine performance.

Very large neural networks make up “Deep Learning” and are a part of the Machine Learning discipline. In 2015, Google released Tensorflow, an open-source environment, initially in Python, for performing Deep Learning.

Neural network structure

Neural network structure

Using neural networks for portfolio optimization

Training

  • Compare output and pre-existing “best” portfolio weights
  • Goal: minimize “error” between output and weights
  • Small error -> network is trained
    通过将神经网络与预先存在的“最佳”权重进行比较,来评估神经网络在创建其输出中的表现。(这些权重可从历史数据的投资组合优化中找到)
    目的是使输出和历史“最佳”权重之间的差异尽可能小,使得神经网络这个函数可以达到输出最佳投资权重的功能。

Usage

  • Input: new, unseen asset prices
  • Output: predicted “best” portfolio weights for new asset prices
  • Best weights = risk management

Creating neural networks in Python

Keras: high-level python library for neural networks/ deep learning

1
2
3
4
5
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(10, imput_dim = 4, activation = 'sigmoid'))
model.add(Dense(4))

  1. To use Keras, we first need to import the kind of neural network—this is a ‘Sequential’ network going from input-to-hidden-to-output layers.
  2. Then we need to import the connections between layers (the arrows we saw previously). We’ll connect all neurons together using ‘Dense’ layers.
  3. Next we’ll define our neural network ‘model’ and create our hidden and output layers, using the ‘.add()’ method. Notice how the input layer is specified with the ‘input_dim’ keyword, which in our case covers the four assets in our investment bank portfolio. And since we also need four output weights, there are four “neurons” in the last output layer.

Training the network in Python


We’ve put asset prices into the ‘training_input’ matrix and portfolio weights into the ‘training_output’ vector. These are used to train the network.

  1. The model is first compiled to specify the error minimization function and how optimization is performed.
  2. Then we “fit” the network to the data, which carries out the minimization.
  3. The number of iterations to fit the data is given with the ‘epochs’ keyword—generally more is better.

Risk Management in Python


To use the network we can give it ‘new_asset_prices’ it has never seen before!
The network evaluates the new asset prices using the ‘.predict()’ method, and _returns a set of portfolio weights_.

Over time, of course, new asset prices will accumulate, and they provide valuable information. Re-training the network every so often on new data, and backtesting it on old data, is part of the neural network workflow.

Exercises

Single layer neural networks

  • Create the output training values using Numpy’s sqrt() function.
  • Create the neural network with one hidden layer of 16 neurons, one input value, and one output value.
  • Compile and fit the neural network on the training values, for 100 epochs
  • Plot the training values (in blue) against the neural network’s predicted values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Create the training values from the square root function
y = np.sqrt(x)

# Create the neural network
model = Sequential()
model.add(Dense(16, input_dim=1, activation='relu'))
model.add(Dense(1))

# Train the network
model.compile(loss='mean_squared_error', optimizer='rmsprop')
model.fit(x, y, epochs=100)

## Plot the resulting approximation and the training values
plt.plot(x, y, x, model.predict(x))
plt.show()

Asset price prediction

Now we can use a neural network to predict an asset price, which is a large component of quantitative financial analysis as well as risk management.

  • Set the input data to be all bank prices except Morgan Stanley, and the output data to be only Morgan Stanley’s prices.
  • Create a Sequential neural network model with two Dense hidden layers: the first with 16 neurons (and three input neurons), and the second with 8 neurons.
  • Add a single Dense output layer of 1 neuron to represent Morgan Stanley’s price.
  • Compile the neural network, and train it by fitting the model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Set the input and output data
training_input = prices.drop('Morgan Stanley', axis=1)
training_output = prices['Morgan Stanley']

# Create and train the neural network with two hidden layers
model = Sequential()
model.add(Dense(16, input_dim=3, activation='sigmoid'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1))

model.compile(loss='mean_squared_logarithmic_error', optimizer='rmsprop')
model.fit(training_input, training_output, epochs=100)

# Scatter plot of the resulting model prediction
axis.scatter(training_output, model.predict(training_input)); plt.show()

Real-time risk management

A 14-day rolling window of asset returns provides enough data to create a time series of minimum volatility portfolios using Modern Portfolio Theory, as we saw in Chapter 2. These minimum_vol portfolio weights are the training values for a neural network. This is a (1497 x 4) matrix.

The input is the matrix of weekly average_asset_returns, corresponding to each efficient portfolio. This is a (1497 x 4) matrix.

Create a Sequential neural network with the proper input dimension and two hidden layers. Training this network would take too long, so you’ll use an available pre_trained_model of identical type to predict portfolio weights for a new asset price vector.

  • Create a Sequential neural network with two hidden layers, one input layer and one output layer.
  • Use the pre_trained_model to predict what the minimum volatility portfolio would be, when new asset data asset_returns is presented.
1
2
3
4
5
6
7
8
9
10
# Create neural network model
model = Sequential()
model.add(Dense(128, input_dim = 4, activation = 'relu'))
model.add(Dense(64, activation = 'relu'))
model.add(Dense(4, activation = 'relu'))

# Use the pre-trained model to predict portfolio weights given new asset returns
asset_returns = np.array([0.001060, 0.003832, 0.000726, -0.002787])
asset_returns.shape = (1,4)
print("Predicted minimum volatility portfolio: ", pre_trained_model.predict(asset_returns))

结果

1
Predicted minimum volatility portfolio:  [[0.         0.28107247 0.         0.7649856 ]]