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

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

Derivatives Positions

We are going to model total of 6 derivatives positions.

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)

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)

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.806
dtype: float64
CPU times: user 2.51 s, sys: 51.9 ms, total: 2.56 s
Wall time: 644 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.325 EUR 3.325
1 am_put_pos_1 am_put_pos_1 1 American single [gbm_1] 3.303 EUR 3.303
2 am_put_pos_2 am_put_pos_2 1 American single [gbm_1] 3.275 EUR 3.275
3 multi_pos_3 multi_pos_3 1 European multi [gbm_1, gbm_2] 10.301 EUR 10.301
4 multi_pos_4 multi_pos_4 1 European multi [gbm_1, gbm_2] 10.301 EUR 10.301
5 multi_pos_5 multi_pos_5 1 European multi [gbm_1, gbm_2] 10.301 EUR 10.301

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.

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_2
0.8
0.9
1.0
1.1
1.2

gbm_1
0.8
0.9
1.0
1.1
1.2



CPU times: user 15.6 s, sys: 328 ms, total: 15.9 s
Wall time: 4 s

The return object is a pandas Panel object.


In [16]:
vegas


Out[16]:
gbm_2_Vega gbm_1_Vega
dim_0 dim_1
0.8 factor 0.400 0.160
value 36.357 32.307
0.9 factor 0.450 0.180
value 38.655 33.246
1.0 factor 0.500 0.200
value 40.965 40.965
1.1 factor 0.550 0.220
value 43.269 35.109
1.2 factor 0.600 0.240
value 45.570 35.994

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_2_Vega
dim_0  dim_1 
0.8    factor     0.40
       value     36.36
0.9    factor     0.45
       value     38.66
1.0    factor     0.50
       value     40.96
1.1    factor     0.55
       value     43.27
1.2    factor     0.60
       value     45.57
Name: gbm_2_Vega, dtype: float64

gbm_1_Vega
dim_0  dim_1 
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

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_2
0.8
0.9
1.0
1.1
1.2

gbm_1
0.8
0.9
1.0
1.1
1.2



CPU times: user 15.7 s, sys: 366 ms, total: 16.1 s
Wall time: 4.06 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_2_Delta gbm_1_Delta
dim_0 dim_1
0.8 factor 32.000 32.000
value 29.841 25.476
0.9 factor 36.000 36.000
value 34.902 28.440
1.0 factor 40.000 40.000
value 40.965 40.965
1.1 factor 44.000 44.000
value 47.964 45.306
1.2 factor 48.000 48.000
value 55.773 61.356

In [20]:
deltas.loc(axis=0)[:, 'value'] - benchvalue


Out[20]:
gbm_2_Delta gbm_1_Delta
dim_0 dim_1
0.8 value -11.124 -15.489
0.9 value -6.063 -12.525
1.0 value 0.000 0.000
1.1 value 6.999 4.341
1.2 value 14.808 20.391

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 2.09 s, sys: 37 ms, total: 2.13 s
Wall time: 539 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_2
0.8
0.85
0.9
0.95
1.0
1.05
1.1
1.15
1.2

gbm_1
0.8
0.85
0.9
0.95
1.0
1.05
1.1
1.15
1.2



CPU times: user 25.5 s, sys: 515 ms, total: 26 s
Wall time: 6.57 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_2_Delta gbm_1_Delta
dim_0 dim_1
0.80 factor 32.000 32.000
value 31.665 27.195
0.85 factor 34.000 34.000
value 34.377 29.649
0.90 factor 36.000 36.000
value 37.365 33.234
0.95 factor 38.000 38.000
value 40.617 38.124
1.00 factor 40.000 40.000
value 44.112 44.112
1.05 factor 42.000 42.000
value 47.820 51.057
1.10 factor 44.000 44.000
value 51.729 58.965
1.15 factor 46.000 46.000
value 55.803 67.641
1.20 factor 48.000 48.000
value 60.021 76.977

In [27]:
deltas.loc(axis=0)[:, 'value'] - benchvalue


Out[27]:
gbm_2_Delta gbm_1_Delta
dim_0 dim_1
0.80 value -12.447 -16.917
0.85 value -9.735 -14.463
0.90 value -6.747 -10.878
0.95 value -3.495 -5.988
1.00 value 0.000 0.000
1.05 value 3.708 6.945
1.10 value 7.617 14.853
1.15 value 11.691 23.529
1.20 value 15.909 32.865

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