8. Derivatives Portfolio Risk Statistics¶
From a risk management perspective it is important to know how sensitive derivatives portfolios are with regard to certain parameter values (market quotes, model assumptions, etc.). This part illustrates how to generate certain risk reports for derivatives_portfolio
objects.
[1]:
import dx
import datetime as dt
import time
import numpy as np
8.1. Risk Factors¶
The example is based on two risk factors, both modeled as geometric Brownian motions.
[2]:
# constant short rate
r = dx.constant_short_rate('r', 0.01)
[3]:
# market environment
me_gbm_1 = dx.market_environment('gbm_1', dt.datetime(2015, 1, 1))
[4]:
# geometric Brownian motion
me_gbm_1.add_constant('initial_value', 40.)
me_gbm_1.add_constant('volatility', 0.2)
me_gbm_1.add_constant('currency', 'EUR')
me_gbm_1.add_constant('model', 'gbm')
[5]:
me_gbm_2 = dx.market_environment('gbm_2', me_gbm_1.pricing_date)
[6]:
# valuation environment
val_env = dx.market_environment('val_env', dt.datetime(2015, 1, 1))
val_env.add_constant('paths', 25000)
# 25,000 paths
val_env.add_constant('frequency', 'W')
# weekly frequency
val_env.add_curve('discount_curve', r)
val_env.add_constant('starting_date', dt.datetime(2015, 1, 1))
val_env.add_constant('final_date', dt.datetime(2015, 12, 31))
[7]:
# add valuation environment to market environments
me_gbm_1.add_environment(val_env)
me_gbm_2.add_environment(me_gbm_1)
me_gbm_2.add_constant('initial_value', 40.)
me_gbm_2.add_constant('volatility', 0.5)
# higher volatility
[8]:
risk_factors = {'gbm_1' : me_gbm_1, 'gbm_2' : me_gbm_2}
# market with two risk factors
8.2.2. Derivatives Positions¶
We are going to model total of 6 derivatives positions.
8.2.1. Market Environment¶
All derivatives instruments (positions) share the same market_environment
object.
[9]:
# market environment for the options
me_option = dx.market_environment('put', dt.datetime(2015, 1, 1))
me_option.add_constant('maturity', dt.datetime(2015, 12, 31))
me_option.add_constant('currency', 'EUR')
me_option.add_environment(val_env)
8.2.2. Derivatives Positions¶
Two different kinds of derivatives make up the portfolio—an American put option and a European maximum call option. Both types of derivatives populate three positions, respectively.
[10]:
positions = {}
half = 3 # 2 times that many options
for i in range(half):
name = 'am_put_pos_%s' %i # same name for position key and name
positions[name] = dx.derivatives_position(
name=name,
quantity=1,
underlyings=['gbm_1'],
mar_env=me_option,
otype='American single',
payoff_func='np.maximum(instrument_values - 40., 0)')
multi_payoff = "np.maximum(np.maximum(maturity_value['gbm_1'], maturity_value['gbm_2']) - 40., 0)"
for i in range(half, 2 * half):
name = 'multi_pos_%s' %i # same name for position key and name
positions[name] = dx.derivatives_position(
name=name,
quantity=1,
underlyings=['gbm_1', 'gbm_2'],
mar_env=me_option,
otype='European multi',
payoff_func=multi_payoff)
8.3. Portfolio Modeling and Valuation¶
The instantiation of the derivatives_portfolio
object is as usual.
[11]:
portfolio = dx.derivatives_portfolio(
name='portfolio',
positions=positions,
val_env=val_env,
risk_factors=risk_factors,
correlations=None,
parallel=False)
[12]:
%time res = portfolio.get_values(fixed_seed=True)
Total
pos_value 40.912
dtype: float64
CPU times: user 879 ms, sys: 60.5 ms, total: 939 ms
Wall time: 915 ms
Here, the value estimates from the Monte Carlo simulation and valuation.
[13]:
res
[13]:
position | name | quantity | otype | risk_facts | value | currency | pos_value | |
---|---|---|---|---|---|---|---|---|
0 | am_put_pos_0 | am_put_pos_0 | 1 | American single | [gbm_1] | 3.278 | EUR | 3.278 |
1 | am_put_pos_1 | am_put_pos_1 | 1 | American single | [gbm_1] | 3.267 | EUR | 3.267 |
2 | am_put_pos_2 | am_put_pos_2 | 1 | American single | [gbm_1] | 3.320 | EUR | 3.320 |
3 | multi_pos_3 | multi_pos_3 | 1 | European multi | [gbm_1, gbm_2] | 10.349 | EUR | 10.349 |
4 | multi_pos_4 | multi_pos_4 | 1 | European multi | [gbm_1, gbm_2] | 10.349 | EUR | 10.349 |
5 | multi_pos_5 | multi_pos_5 | 1 | European multi | [gbm_1, gbm_2] | 10.349 | EUR | 10.349 |
8.4. Portfolio Risk Reports¶
Portfolio risk reports are meant to provide a broad overview of how sensitive the value of a portfolio is with regard to the value of certain input parameters (market data, model parameters). While Greeks provide the same information with regard to marginal changes in the input paramters, risk reports provide a wider range input-output (parameter-portfolio value) combinations.
8.4.1. No Correlation¶
First, consider the portfolio from before, i.e. without correlation.
[14]:
portfolio.val_env.get_list('cholesky_matrix')
[14]:
array([[1., 0.],
[0., 1.]])
Calling the method get_port_risk
and providing a key for the respetive Greek yields sensitivities with regard to all risk factors (here: gbm_1
and gbm_2
).
[15]:
%%time
vegas, benchvalue = portfolio.get_port_risk(Greek='Vega',
fixed_seed=True)
gbm_1
0.8
0.9
1.0
1.1
1.2
gbm_2
0.8
0.9
1.0
1.1
1.2
CPU times: user 5.33 s, sys: 264 ms, total: 5.6 s
Wall time: 5.48 s
The return object is a pandas Panel
object.
[16]:
vegas
[16]:
gbm_1_Vega | gbm_2_Vega | ||
---|---|---|---|
major | minor | ||
0.8 | factor | 0.160 | 0.400 |
value | 32.307 | 29.448 | |
0.9 | factor | 0.180 | 0.450 |
value | 33.246 | 31.815 | |
1.0 | factor | 0.200 | 0.500 |
value | 40.965 | 40.965 | |
1.1 | factor | 0.220 | 0.550 |
value | 35.109 | 36.519 | |
1.2 | factor | 0.240 | 0.600 |
value | 35.994 | 38.856 |
Using the helper funtion risk_report
allows the easy, readable printout of the results, i.e. the portfolio volatility sensitivities. In this case you can see that, for example, the increase in the first risk fator’s (gbm_1
) volatility by 10% leads to a portfolio value increase bya bit less than 1 currency unit. Decreasing the same input parameter by 10% reduces the portfolio value by a bit less than 1 currency unit.
[17]:
dx.risk_report(vegas)
gbm_1_Vega
major minor
0.8 factor 0.16
value 32.31
0.9 factor 0.18
value 33.25
1.0 factor 0.20
value 40.96
1.1 factor 0.22
value 35.11
1.2 factor 0.24
value 35.99
Name: gbm_1_Vega, dtype: float64
gbm_2_Vega
major minor
0.8 factor 0.40
value 29.45
0.9 factor 0.45
value 31.82
1.0 factor 0.50
value 40.96
1.1 factor 0.55
value 36.52
1.2 factor 0.60
value 38.86
Name: gbm_2_Vega, dtype: float64
Of course, you can generate the same risk report for the portfolio initial value sensitivities.
[18]:
%time deltas, benchvalue = portfolio.get_port_risk(Greek='Delta', fixed_seed=True)
gbm_1
0.8
0.9
1.0
1.1
1.2
gbm_2
0.8
0.9
1.0
1.1
1.2
CPU times: user 5.33 s, sys: 246 ms, total: 5.58 s
Wall time: 5.46 s
For example, increasing the initial value of the first risk factor (gbm_1
) by 10% increases the portfolio value by about 11 currency units.
[19]:
deltas
[19]:
gbm_1_Delta | gbm_2_Delta | ||
---|---|---|---|
major | minor | ||
0.8 | factor | 32.000 | 32.000 |
value | 25.476 | 24.138 | |
0.9 | factor | 36.000 | 36.000 |
value | 28.440 | 28.311 | |
1.0 | factor | 40.000 | 40.000 |
value | 40.965 | 40.965 | |
1.1 | factor | 44.000 | 44.000 |
value | 45.306 | 41.601 | |
1.2 | factor | 48.000 | 48.000 |
value | 61.356 | 50.091 |
[20]:
deltas.loc(axis=0)[:, 'value'] - benchvalue
[20]:
gbm_1_Delta | gbm_2_Delta | ||
---|---|---|---|
major | minor | ||
0.8 | value | -15.489 | -16.827 |
0.9 | value | -12.525 | -12.654 |
1.0 | value | 0.000 | 0.000 |
1.1 | value | 4.341 | 0.636 |
1.2 | value | 20.391 | 9.126 |
8.4.2. With Correlation¶
Consider now a highly negative correlation case.
[21]:
correlations = [['gbm_1', 'gbm_2', -0.9]]
[22]:
portfolio = dx.derivatives_portfolio(
'portfolio', positions, val_env,
risk_factors, correlations, parallel=False)
[23]:
portfolio.val_env.get_list('cholesky_matrix')
[23]:
array([[ 1. , 0. ],
[-0.9 , 0.43588989]])
Since the value of the European maximum call option is dependent on the risk factor correlation you see a significant change in this derivative’s value estimate.
[24]:
%time portfolio.get_values(fixed_seed=True)
Total
pos_value 44.112
dtype: float64
CPU times: user 780 ms, sys: 49.3 ms, total: 829 ms
Wall time: 788 ms
[24]:
position | name | quantity | otype | risk_facts | value | currency | pos_value | |
---|---|---|---|---|---|---|---|---|
0 | am_put_pos_0 | am_put_pos_0 | 1 | American single | [gbm_1] | 3.293 | EUR | 3.293 |
1 | am_put_pos_1 | am_put_pos_1 | 1 | American single | [gbm_1] | 3.293 | EUR | 3.293 |
2 | am_put_pos_2 | am_put_pos_2 | 1 | American single | [gbm_1] | 3.293 | EUR | 3.293 |
3 | multi_pos_3 | multi_pos_3 | 1 | European multi | [gbm_1, gbm_2] | 11.411 | EUR | 11.411 |
4 | multi_pos_4 | multi_pos_4 | 1 | European multi | [gbm_1, gbm_2] | 11.411 | EUR | 11.411 |
5 | multi_pos_5 | multi_pos_5 | 1 | European multi | [gbm_1, gbm_2] | 11.411 | EUR | 11.411 |
Via the step
parameter, you can influence the granularity of the risk report.
[25]:
%%time
deltas, benchvalue = portfolio.get_port_risk(Greek='Delta',
fixed_seed=True,
step=0.05)
gbm_1
0.8
0.8500000000000001
0.9000000000000001
0.9500000000000002
1.0000000000000002
1.0500000000000003
1.1000000000000003
1.1500000000000004
1.2000000000000004
gbm_2
0.8
0.8500000000000001
0.9000000000000001
0.9500000000000002
1.0000000000000002
1.0500000000000003
1.1000000000000003
1.1500000000000004
1.2000000000000004
CPU times: user 8.97 s, sys: 613 ms, total: 9.59 s
Wall time: 9.02 s
In this case, an increase in the intial value of the first risk factor (gbm_1
) by 10% leads to a much higher increase in the portfolio value of about 15 currency units.
[26]:
deltas
[26]:
gbm_1_Delta | gbm_2_Delta | ||
---|---|---|---|
major | minor | ||
0.80 | factor | 32.000 | 32.000 |
value | 27.195 | 31.665 | |
0.85 | factor | 34.000 | 34.000 |
value | 29.649 | 34.377 | |
0.90 | factor | 36.000 | 36.000 |
value | 33.234 | 37.365 | |
0.95 | factor | 38.000 | 38.000 |
value | 38.124 | 40.617 | |
1.00 | factor | 40.000 | 40.000 |
value | 44.112 | 44.112 | |
1.05 | factor | 42.000 | 42.000 |
value | 51.057 | 47.820 | |
1.10 | factor | 44.000 | 44.000 |
value | 58.965 | 51.729 | |
1.15 | factor | 46.000 | 46.000 |
value | 67.641 | 55.803 | |
1.20 | factor | 48.000 | 48.000 |
value | 76.977 | 60.021 |
[27]:
deltas.loc(axis=0)[:, 'value'] - benchvalue
[27]:
gbm_1_Delta | gbm_2_Delta | ||
---|---|---|---|
major | minor | ||
0.80 | value | -16.917 | -12.447 |
0.85 | value | -14.463 | -9.735 |
0.90 | value | -10.878 | -6.747 |
0.95 | value | -5.988 | -3.495 |
1.00 | value | 0.000 | 0.000 |
1.05 | value | 6.945 | 3.708 |
1.10 | value | 14.853 | 7.617 |
1.15 | value | 23.529 | 11.691 |
1.20 | value | 32.865 | 15.909 |
Copyright, License & Disclaimer
© Dr. Yves J. Hilpisch | The Python Quants GmbH
DX Analytics (the “dx library” or “dx package”) is licensed under the GNU Affero General Public License version 3 or later (see http://www.gnu.org/licenses/).
DX Analytics comes with no representations or warranties, to the extent permitted by applicable law.
http://tpq.io | dx@tpq.io | http://twitter.com/dyjh
Quant Platform | http://pqp.io
Python for Finance Training | http://training.tpq.io
Certificate in Computational Finance | http://compfinance.tpq.io
Derivatives Analytics with Python (Wiley Finance) | http://dawp.tpq.io
Python for Finance (2nd ed., O’Reilly) | http://py4fi.tpq.io