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.

In [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.

In [2]:
# constant short rate
r = dx.constant_short_rate('r', 0.01)
In [3]:
# market environment
me_gbm_1 = dx.market_environment('gbm_1', dt.datetime(2015, 1, 1))
In [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')
In [5]:
me_gbm_2 = dx.market_environment('gbm_2', me_gbm_1.pricing_date)
In [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))
In [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
In [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.

In [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.

In [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.

In [11]:
portfolio = dx.derivatives_portfolio(
                        name='portfolio',
                        positions=positions,
                        val_env=val_env,
                        risk_factors=risk_factors,
                        correlations=None,
                        parallel=False)
In [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.

In [13]:
res
Out[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.

In [14]:
portfolio.val_env.get_list('cholesky_matrix')
Out[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).

portfolio.valuation_objects[3].underlying_objects['gbm_1'].update(initial_value=15)
In [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.

In [16]:
vegas
Out[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.

In [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.

In [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.

In [19]:
deltas
Out[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
In [20]:
deltas.loc(axis=0)[:, 'value'] - benchvalue
Out[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.

In [21]:
correlations = [['gbm_1', 'gbm_2', -0.9]]
In [22]:
portfolio = dx.derivatives_portfolio(
                        'portfolio', positions, val_env,
                        risk_factors, correlations, parallel=False)
In [23]:
portfolio.val_env.get_list('cholesky_matrix')
Out[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.

In [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
Out[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.

In [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.

In [26]:
deltas
Out[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
In [27]:
deltas.loc(axis=0)[:, 'value'] - benchvalue
Out[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”) 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 | team@tpq.io | http://twitter.com/dyjh

Quant Platform | http://quant-platform.com

Derivatives Analytics with Python (Wiley Finance) | http://derivatives-analytics-with-python.com

Python for Finance (O’Reilly) | http://python-for-finance.com