Quite Complex Portfolios

This part illustrates that you can model, value and risk manage quite complex derivatives portfolios with DX Analytics.


In [1]:
from dx import *
import time
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
%matplotlib inline
np.random.seed(10000)

Multiple Risk Factors

The example is based on a multiple, correlated risk factors, all (for the ease of exposition) geometric_brownian_motion objects.


In [2]:
mer = market_environment(name='me', pricing_date=dt.datetime(2015, 1, 1))
mer.add_constant('initial_value', 0.01)
mer.add_constant('volatility', 0.1)
mer.add_constant('kappa', 2.0)
mer.add_constant('theta', 0.05)
mer.add_constant('paths', 100) # dummy
mer.add_constant('frequency', 'M') # dummy
mer.add_constant('starting_date', mer.pricing_date)
mer.add_constant('final_date', dt.datetime(2015, 12, 31)) # dummy
ssr = stochastic_short_rate('ssr', mer)

In [3]:
plt.figure(figsize=(10, 6))
plt.plot(ssr.process.time_grid, ssr.process.get_instrument_values()[:, :10]);
plt.gcf().autofmt_xdate()



In [4]:
# market environments
me = market_environment('gbm', dt.datetime(2015, 1, 1))

In [5]:
# geometric Brownian motion
me.add_constant('initial_value', 36.)
me.add_constant('volatility', 0.2) 
me.add_constant('currency', 'EUR')

In [6]:
# jump diffusion
me.add_constant('lambda', 0.4)
me.add_constant('mu', -0.4) 
me.add_constant('delta', 0.2)

In [7]:
# stochastic volatility
me.add_constant('kappa', 2.0)
me.add_constant('theta', 0.3) 
me.add_constant('vol_vol', 0.5)
me.add_constant('rho', -0.5)

Using 2,500 paths and monthly discretization for the example.


In [8]:
# valuation environment
val_env = market_environment('val_env', dt.datetime(2015, 1, 1))
val_env.add_constant('paths', 2500)
val_env.add_constant('frequency', 'M')
val_env.add_curve('discount_curve', ssr)
val_env.add_constant('starting_date', dt.datetime(2015, 1, 1))
val_env.add_constant('final_date', dt.datetime(2016, 12, 31))

In [9]:
# add valuation environment to market environments
me.add_environment(val_env)

In [10]:
no = 50  # 50 different risk factors in total

In [11]:
risk_factors = {}
for rf in range(no):
    # random model choice
    sm = np.random.choice(['gbm', 'jd', 'sv'])
    key = '%3d_%s' % (rf + 1, sm)
    risk_factors[key] = market_environment(key, me.pricing_date)
    risk_factors[key].add_environment(me)
    # random initial_value
    risk_factors[key].add_constant('initial_value',
                                    np.random.random() * 40. + 20.)
    # radnom volatility
    risk_factors[key].add_constant('volatility',
                                    np.random.random() * 0.6 + 0.05)
    # the simulation model to choose
    risk_factors[key].add_constant('model', sm)

In [12]:
correlations = []
keys = sorted(risk_factors.keys())
for key in keys[1:]:
    correlations.append([keys[0], key, np.random.choice([-0.1, 0.0, 0.1])])
correlations[:3]


Out[12]:
[['  1_sv', '  2_gbm', 0.10000000000000001],
 ['  1_sv', '  3_gbm', -0.10000000000000001],
 ['  1_sv', '  4_gbm', -0.10000000000000001]]

Options Modeling

We model a certain number of derivative instruments with the following major assumptions.


In [13]:
me_option = market_environment('option', me.pricing_date)
# choose from a set of maturity dates (month ends)
maturities = pd.date_range(start=me.pricing_date,
                           end=val_env.get_constant('final_date'),
                           freq='M').to_pydatetime()
me_option.add_constant('maturity', np.random.choice(maturities))
me_option.add_constant('currency', 'EUR')
me_option.add_environment(val_env)

Portfolio Modeling

The derivatives_portfolio object we compose consists of multiple derivatives positions. Each option differs with respect to the strike and the risk factor it is dependent on.


In [14]:
# 5 times the number of risk factors
# as portfolio positions/instruments
pos = 5 * no

In [15]:
positions = {}
for i in range(pos):
    ot = np.random.choice(['am_put', 'eur_call'])
    if ot == 'am_put':
        otype = 'American single'
        payoff_func = 'np.maximum(%5.3f - instrument_values, 0)'
    else:
        otype = 'European single'
        payoff_func = 'np.maximum(maturity_value - %5.3f, 0)'
    # random strike
    strike = np.random.randint(36, 40)
    underlying = sorted(risk_factors.keys())[(i + no) % no]
    name = '%d_option_pos_%d' % (i, strike)
    positions[name] = derivatives_position(
                        name=name,
                        quantity=np.random.randint(1, 10),
                        underlyings=[underlying],
                        mar_env=me_option,
                        otype=otype,
        payoff_func=payoff_func % strike)

In [16]:
# number of derivatives positions
len(positions)


Out[16]:
250

Portfolio Valuation

First, the derivatives portfolio with sequential valuation.


In [17]:
port = derivatives_portfolio(
                name='portfolio',
                positions=positions,
                val_env=val_env,
                risk_factors=risk_factors,
                correlations=correlations,
                parallel=True)  # sequential calculation

In [18]:
port.val_env.get_list('cholesky_matrix')


Out[18]:
array([[ 1.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.1       ,  0.99498744,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [-0.1       ,  0.01005038,  0.99493668, ...,  0.        ,
         0.        ,  0.        ],
       ..., 
       [-0.1       ,  0.01005038, -0.01015242, ...,  0.99239533,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         1.        ,  0.        ],
       [ 0.1       , -0.01005038,  0.01015242, ...,  0.01526762,
         0.        ,  0.99227788]])

The call of the get_values method to value all instruments.


In [19]:
%time res = port.get_statistics(fixed_seed=True)


Totals
pos_value    10327.477405
pos_delta      115.001400
pos_vega      8778.425900
dtype: float64
CPU times: user 849 ms, sys: 9.15 s, total: 10 s
Wall time: 31.3 s

In [20]:
res.set_index('position', inplace=False)


Out[20]:
name quantity otype risk_facts value currency pos_value pos_delta pos_vega
position
94_option_pos_38 94_option_pos_38 9 European single [ 45_sv] 11.689901 EUR 105.209109 6.5340 31.3659
184_option_pos_37 184_option_pos_37 3 American single [ 35_jd] 2.659000 EUR 7.977000 -0.4491 33.9000
187_option_pos_36 187_option_pos_36 1 European single [ 38_gbm] 0.035181 EUR 0.035181 0.0215 1.2782
181_option_pos_38 181_option_pos_38 6 European single [ 32_jd] 10.185577 EUR 61.113462 4.4790 57.4704
22_option_pos_38 22_option_pos_38 8 American single [ 23_jd] 1.136000 EUR 9.088000 -0.5248 64.0000
103_option_pos_36 103_option_pos_36 2 European single [ 4_gbm] 0.068706 EUR 0.137412 0.1276 7.2696
201_option_pos_38 201_option_pos_38 3 American single [ 2_gbm] 4.859000 EUR 14.577000 -1.2135 42.9000
144_option_pos_39 144_option_pos_39 9 European single [ 45_sv] 11.177332 EUR 100.595988 6.4188 31.9941
203_option_pos_38 203_option_pos_38 5 European single [ 4_gbm] 0.019660 EUR 0.098300 0.1115 7.7775
155_option_pos_39 155_option_pos_39 5 American single [ 6_jd] 15.146000 EUR 75.730000 -4.0440 13.5000
146_option_pos_36 146_option_pos_36 3 European single [ 47_sv] 2.894735 EUR 8.684205 1.2897 2.9382
46_option_pos_39 46_option_pos_39 2 European single [ 47_sv] 2.200646 EUR 4.401292 0.7166 1.8760
34_option_pos_37 34_option_pos_37 9 American single [ 35_jd] 2.659000 EUR 23.931000 -1.3473 101.7000
188_option_pos_39 188_option_pos_39 8 American single [ 39_sv] 9.770000 EUR 78.160000 -3.9096 13.7800
222_option_pos_38 222_option_pos_38 4 European single [ 23_jd] 22.837920 EUR 91.351680 3.5512 12.6368
159_option_pos_36 159_option_pos_36 9 American single [ 10_sv] 7.882000 EUR 70.938000 -4.1706 6.3000
217_option_pos_39 217_option_pos_39 4 European single [ 18_sv] 13.021438 EUR 52.085752 3.0700 4.1216
137_option_pos_39 137_option_pos_39 6 American single [ 38_gbm] 17.842000 EUR 107.052000 -5.9940 0.6000
130_option_pos_37 130_option_pos_37 1 European single [ 31_sv] 1.140401 EUR 1.140401 0.2380 1.2870
135_option_pos_38 135_option_pos_38 2 European single [ 36_gbm] 18.964940 EUR 37.929880 1.7504 19.0952
190_option_pos_36 190_option_pos_36 7 American single [ 41_jd] 5.429000 EUR 38.003000 -2.7216 79.8000
109_option_pos_37 109_option_pos_37 5 European single [ 10_sv] 3.839486 EUR 19.197430 2.4440 6.1185
191_option_pos_38 191_option_pos_38 9 American single [ 42_sv] 7.267000 EUR 65.403000 -3.6981 11.7000
164_option_pos_36 164_option_pos_36 4 European single [ 15_gbm] 5.920606 EUR 23.682424 2.2248 55.3132
112_option_pos_37 112_option_pos_37 3 European single [ 13_sv] 1.718209 EUR 5.154627 0.9345 1.9341
240_option_pos_36 240_option_pos_36 2 American single [ 41_jd] 5.429000 EUR 10.858000 -0.7776 22.8000
39_option_pos_38 39_option_pos_38 3 European single [ 40_jd] 15.213182 EUR 45.639546 2.1216 35.8626
233_option_pos_37 233_option_pos_37 9 American single [ 34_jd] 6.746000 EUR 60.714000 -2.0925 129.9690
175_option_pos_39 175_option_pos_39 7 American single [ 26_gbm] 3.204000 EUR 22.428000 -1.2404 95.8629
37_option_pos_38 37_option_pos_38 2 European single [ 38_gbm] 0.020348 EUR 0.040696 0.0252 1.6426
... ... ... ... ... ... ... ... ... ...
235_option_pos_37 235_option_pos_37 4 American single [ 36_gbm] 0.820000 EUR 3.280000 -0.2464 36.0000
11_option_pos_38 11_option_pos_38 1 European single [ 12_jd] 1.310847 EUR 1.310847 0.2786 8.6502
15_option_pos_37 15_option_pos_37 5 European single [ 16_gbm] 1.684895 EUR 8.424475 1.5800 53.0640
162_option_pos_37 162_option_pos_37 9 American single [ 13_sv] 11.671000 EUR 105.039000 -6.5502 39.6000
179_option_pos_39 179_option_pos_39 2 European single [ 30_gbm] 12.171664 EUR 24.343328 1.3514 29.9058
104_option_pos_36 104_option_pos_36 2 American single [ 5_gbm] 3.937000 EUR 7.874000 -0.4282 27.1214
31_option_pos_38 31_option_pos_38 9 European single [ 32_jd] 10.185577 EUR 91.670193 6.7185 86.2056
96_option_pos_36 96_option_pos_36 3 American single [ 47_sv] 9.187000 EUR 27.561000 -1.7361 4.5000
19_option_pos_36 19_option_pos_36 8 American single [ 20_jd] 12.316000 EUR 98.528000 -8.0000 13.6000
87_option_pos_36 87_option_pos_36 6 American single [ 38_gbm] 14.845000 EUR 89.070000 -5.9514 -3.6000
121_option_pos_36 121_option_pos_36 7 European single [ 22_sv] 8.149622 EUR 57.047354 4.6865 19.7043
200_option_pos_37 200_option_pos_37 8 European single [ 1_sv] 7.251997 EUR 58.015976 4.9464 29.2368
99_option_pos_36 99_option_pos_36 4 European single [ 50_gbm] 7.058561 EUR 28.234244 2.5984 52.8508
53_option_pos_37 53_option_pos_37 3 European single [ 4_gbm] 0.037188 EUR 0.111564 0.1203 7.5129
111_option_pos_37 111_option_pos_37 4 American single [ 12_jd] 11.090000 EUR 44.360000 -2.9284 43.2000
204_option_pos_36 204_option_pos_36 1 American single [ 5_gbm] 3.937000 EUR 3.937000 -0.2141 13.5607
81_option_pos_36 81_option_pos_36 3 European single [ 32_jd] 11.396591 EUR 34.189773 2.3433 25.3110
246_option_pos_37 246_option_pos_37 4 European single [ 47_sv] 2.644399 EUR 10.577596 1.6224 3.8472
147_option_pos_36 147_option_pos_36 1 American single [ 48_sv] 9.179000 EUR 9.179000 -0.5690 0.7000
243_option_pos_38 243_option_pos_38 5 American single [ 44_jd] 4.452000 EUR 22.260000 -1.0335 72.5000
83_option_pos_39 83_option_pos_39 8 American single [ 34_jd] 7.775000 EUR 62.200000 -2.1904 118.8864
75_option_pos_39 75_option_pos_39 6 European single [ 26_gbm] 24.214688 EUR 145.288128 4.9746 90.0108
234_option_pos_37 234_option_pos_37 6 American single [ 35_jd] 2.659000 EUR 15.954000 -0.8982 67.8000
163_option_pos_37 163_option_pos_37 5 European single [ 14_jd] 21.499076 EUR 107.495380 3.9800 55.7650
49_option_pos_37 49_option_pos_37 4 American single [ 50_gbm] 4.377000 EUR 17.508000 -1.4884 62.0000
110_option_pos_36 110_option_pos_36 5 American single [ 11_jd] 2.080000 EUR 10.400000 -0.7460 40.5000
228_option_pos_38 228_option_pos_38 3 European single [ 29_jd] 23.634880 EUR 70.904640 2.5209 28.7898
118_option_pos_37 118_option_pos_37 8 American single [ 19_gbm] 6.387000 EUR 51.096000 -2.0376 122.3936
150_option_pos_36 150_option_pos_36 8 American single [ 1_sv] 7.070000 EUR 56.560000 -2.9960 26.9624
124_option_pos_38 124_option_pos_38 1 European single [ 25_jd] 11.170898 EUR 11.170898 0.8039 6.6341

250 rows × 9 columns

Risk Analysis

Full distribution of portfolio present values illustrated via histogram.


In [21]:
%time pvs = port.get_present_values()


CPU times: user 658 ms, sys: 8.02 s, total: 8.68 s
Wall time: 13.9 s

In [22]:
plt.figure(figsize=(10, 6))
plt.hist(pvs, bins=30);
plt.xlabel('portfolio present values')
plt.ylabel('frequency')


Out[22]:
<matplotlib.text.Text at 0x7f00502b7e10>

Some statistics via pandas.


In [23]:
pdf = pd.DataFrame(pvs)
pdf.describe()


Out[23]:
0
count 2500.000000
mean 10327.480869
std 2151.707101
min 4691.853004
25% 8781.364261
50% 10122.550615
75% 11584.503917
max 20911.950046

The delta risk report.


In [24]:
%%time
deltas, benchmark = port.get_port_risk(Greek='Delta', fixed_seed=True, step=0.2,
                                       risk_factors=risk_factors.keys()[:4])
risk_report(deltas)


 19_gbm
0.8 1.0 1.2 
 24_jd
0.8 1.0 1.2 
 30_gbm
0.8 1.0 1.2 
 20_jd
0.8 1.0 1.2 



 19_gbm_Delta
             0.8       1.0       1.2
factor     37.70     47.12     56.54
value   10234.95  10327.48  10451.53

 20_jd_Delta
             0.8       1.0       1.2
factor     18.88     23.60     28.32
value   10403.18  10327.48  10253.94

 24_jd_Delta
             0.8       1.0       1.2
factor     18.75     23.43     28.12
value   10400.36  10327.48  10268.91

 30_gbm_Delta
             0.8       1.0       1.2
factor     32.72     40.90     49.08
value   10253.47  10327.48  10415.84
CPU times: user 2.57 s, sys: 6.96 s, total: 9.53 s
Wall time: 9.84 s

The vega risk report.


In [25]:
%%time
vegas, benchmark = port.get_port_risk(Greek='Vega', fixed_seed=True, step=0.2,
                                      risk_factors=risk_factors.keys()[:3])
risk_report(vegas)


 19_gbm
0.8 1.0 1.2 
 24_jd
0.8 1.0 1.2 
 30_gbm
0.8 1.0 1.2 



 19_gbm_Vega
             0.8       1.0       1.2
factor      0.52      0.65      0.78
value   10249.92  10327.48  10406.17

 24_jd_Vega
             0.8       1.0       1.2
factor      0.49      0.62      0.74
value   10295.93  10327.48  10362.83

 30_gbm_Vega
             0.8       1.0       1.2
factor      0.52      0.65      0.78
value   10290.69  10327.48  10363.17
CPU times: user 1.9 s, sys: 7.01 s, total: 8.91 s
Wall time: 9.13 s

Visualization of Results

Selected results visualized.


In [26]:
res[['pos_value', 'pos_delta', 'pos_vega']].hist(bins=30, figsize=(9, 6))
plt.ylabel('frequency')


Out[26]:
<matplotlib.text.Text at 0x7f004afaf9d0>

Sample paths for three underlyings.


In [27]:
paths_0 = port.underlying_objects.values()[0]
paths_0.generate_paths()
paths_1 = port.underlying_objects.values()[1]
paths_1.generate_paths()
paths_2 = port.underlying_objects.values()[2]
paths_2.generate_paths()

In [28]:
pa = 5
plt.figure(figsize=(10, 6))
plt.plot(port.time_grid, paths_0.instrument_values[:, :pa], 'b');
print 'Paths for %s (blue)' % paths_0.name
plt.plot(port.time_grid, paths_1.instrument_values[:, :pa], 'r.-');
print 'Paths for %s (red)' % paths_1.name
plt.plot(port.time_grid, paths_2.instrument_values[:, :pa], 'g-.', lw=2.5);
print 'Paths for %s (green)' % paths_2.name
plt.ylabel('risk factor level')
plt.gcf().autofmt_xdate()


Paths for  19_gbm (blue)
Paths for  24_jd (red)
Paths for  30_gbm (green)

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