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
%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', 1000) # 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=(9, 5))
plt.plot(ssr.process.time_grid, ssr.process.get_instrument_values()[:, :10]);
plt.gcf().autofmt_xdate(); plt.grid()



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)

In [8]:
# valuation environment
val_env = market_environment('val_env', dt.datetime(2015, 1, 1))
val_env.add_constant('paths', 1000)
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_jd', '  2_jd', 0.0],
 ['  1_jd', '  3_sv', 0.0],
 ['  1_jd', '  4_sv', 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]
    positions[i] = derivatives_position(
                        name='option_pos_%d' % strike,
                        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=False)  # sequential calculation

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


Out[18]:
array([[ 1.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  1.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  1.        , ...,  0.        ,
         0.        ,  0.        ],
       ..., 
       [ 0.        ,  0.        ,  0.        , ...,  1.        ,
         0.        ,  0.        ],
       [ 0.1       ,  0.        ,  0.        , ...,  0.        ,
         0.99250926,  0.        ],
       [ 0.1       ,  0.        ,  0.        , ...,  0.        ,
        -0.01503802,  0.99239533]])

The call of the get_values method to value all instruments.


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


Totals
pos_value    7540.8710
pos_delta     165.7793
pos_vega     5652.5466
dtype: float64
CPU times: user 25.2 s, sys: 1.76 s, total: 27 s
Wall time: 20.8 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
0 option_pos_38 3 American single [ 1_jd] 13.853 EUR 41.559 -2.8443 -1.2000
1 option_pos_38 5 American single [ 2_jd] 6.212 EUR 31.060 -2.5230 46.0000
2 option_pos_37 9 American single [ 3_sv] 1.291 EUR 11.619 -0.6030 29.5524
3 option_pos_38 7 European single [ 4_sv] 0.844 EUR 5.908 1.8060 8.8347
4 option_pos_37 5 European single [ 5_gbm] 5.716 EUR 28.580 3.3395 45.6615
5 option_pos_36 2 European single [ 6_gbm] 4.286 EUR 8.572 1.1766 19.4424
6 option_pos_39 3 American single [ 7_jd] 3.928 EUR 11.784 -1.3374 30.0000
7 option_pos_38 6 American single [ 8_gbm] 4.415 EUR 26.490 -2.4930 78.0000
8 option_pos_39 6 American single [ 9_sv] 18.082 EUR 108.492 -5.9646 -13.2000
9 option_pos_39 4 American single [ 10_sv] 3.202 EUR 12.808 -1.4184 -2.0000
10 option_pos_37 8 American single [ 11_jd] 3.326 EUR 26.608 -3.0176 68.0000
11 option_pos_38 1 European single [ 12_jd] 16.568 EUR 16.568 0.8955 0.2452
12 option_pos_39 4 European single [ 13_jd] 2.935 EUR 11.740 1.8468 32.0340
13 option_pos_37 5 European single [ 14_sv] 0.873 EUR 4.365 1.2980 8.9355
14 option_pos_38 8 American single [ 15_sv] 3.117 EUR 24.936 -2.5256 8.8000
15 option_pos_36 2 American single [ 16_jd] 2.178 EUR 4.356 -0.2110 14.2018
16 option_pos_36 6 American single [ 17_jd] 3.477 EUR 20.862 -1.6470 64.4856
17 option_pos_38 4 American single [ 18_sv] 1.692 EUR 6.768 -0.5732 16.4000
18 option_pos_38 9 American single [ 19_sv] 1.214 EUR 10.926 -0.6903 35.3718
19 option_pos_36 6 American single [ 20_jd] 0.531 EUR 3.186 -0.2010 1.8000
20 option_pos_39 3 American single [ 21_sv] 0.398 EUR 1.194 -0.1545 3.9000
21 option_pos_39 1 American single [ 22_gbm] 3.235 EUR 3.235 -0.2232 9.6903
22 option_pos_37 5 European single [ 23_gbm] 13.188 EUR 65.940 4.6650 -0.2585
23 option_pos_38 5 European single [ 24_sv] 0.062 EUR 0.310 0.1610 0.4415
24 option_pos_39 1 European single [ 25_sv] 9.173 EUR 9.173 0.7312 4.1993
25 option_pos_37 6 American single [ 26_jd] 2.259 EUR 13.554 -1.6542 42.0000
26 option_pos_37 7 European single [ 27_gbm] 0.190 EUR 1.330 0.8288 29.5848
27 option_pos_38 7 European single [ 28_gbm] 1.773 EUR 12.411 2.1903 50.5022
28 option_pos_37 5 European single [ 29_sv] 19.723 EUR 98.615 4.2525 15.7190
29 option_pos_37 3 American single [ 30_sv] 16.802 EUR 50.406 -2.8038 1.8000
... ... ... ... ... ... ... ... ... ...
220 option_pos_37 3 European single [ 21_sv] 20.425 EUR 61.275 2.7303 1.4313
221 option_pos_39 1 American single [ 22_gbm] 3.235 EUR 3.235 -0.2232 9.6903
222 option_pos_37 1 American single [ 23_gbm] 0.000 EUR 0.000 0.0000 0.0000
223 option_pos_37 5 American single [ 24_sv] 12.926 EUR 64.630 -4.8215 2.0000
224 option_pos_38 4 European single [ 25_sv] 9.796 EUR 39.184 2.9936 16.0196
225 option_pos_38 2 American single [ 26_jd] 2.747 EUR 5.494 -0.9088 12.8000
226 option_pos_36 7 European single [ 27_gbm] 0.286 EUR 2.002 1.0927 35.3157
227 option_pos_38 3 European single [ 28_gbm] 1.773 EUR 5.319 0.9387 21.6438
228 option_pos_39 2 American single [ 29_sv] 1.480 EUR 2.960 -0.2170 8.4000
229 option_pos_39 6 American single [ 30_sv] 18.782 EUR 112.692 -5.7558 4.8000
230 option_pos_38 2 American single [ 31_sv] 3.922 EUR 7.844 -0.6738 2.8000
231 option_pos_38 9 European single [ 32_jd] 1.356 EUR 12.204 2.6991 56.5767
232 option_pos_36 1 European single [ 33_sv] 4.084 EUR 4.084 0.5697 3.4614
233 option_pos_37 6 American single [ 34_jd] 4.720 EUR 28.320 -3.4194 51.6000
234 option_pos_37 8 European single [ 35_sv] 15.273 EUR 122.184 6.4376 35.1160
235 option_pos_37 4 American single [ 36_gbm] 14.538 EUR 58.152 -4.0000 0.0000
236 option_pos_36 1 American single [ 37_jd] 1.390 EUR 1.390 -0.1290 5.2000
237 option_pos_38 5 European single [ 38_jd] 0.239 EUR 1.195 0.7315 21.2310
238 option_pos_36 9 American single [ 39_jd] 3.276 EUR 29.484 -1.8036 68.4963
239 option_pos_36 5 American single [ 40_jd] 0.817 EUR 4.085 -0.3370 13.0000
240 option_pos_39 1 European single [ 41_gbm] 11.429 EUR 11.429 0.9022 2.1905
241 option_pos_36 2 European single [ 42_sv] 2.240 EUR 4.480 0.8360 7.9746
242 option_pos_36 1 American single [ 43_sv] 0.879 EUR 0.879 -0.0760 2.9000
243 option_pos_37 3 American single [ 44_sv] 1.018 EUR 3.054 -0.2802 7.8000
244 option_pos_38 8 American single [ 45_gbm] 0.472 EUR 3.776 -0.5192 40.0000
245 option_pos_36 3 European single [ 46_sv] 12.767 EUR 38.301 2.4735 7.9152
246 option_pos_37 6 European single [ 47_jd] 19.890 EUR 119.340 5.1390 36.1260
247 option_pos_36 5 European single [ 48_jd] 0.289 EUR 1.445 0.7560 21.8690
248 option_pos_38 8 American single [ 49_gbm] 0.048 EUR 0.384 -0.0888 14.4000
249 option_pos_38 7 European single [ 50_jd] 5.509 EUR 38.563 4.3526 54.1604

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 5.66 s, sys: 430 ms, total: 6.09 s
Wall time: 4.54 s

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


Some statistics via pandas.


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


Out[23]:
0
count 1000.000000
mean 7558.224233
std 899.266706
min 5031.813531
25% 6895.337989
50% 7507.224884
75% 8151.979680
max 11260.964406

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()[:3])
risk_report(deltas)


 44_sv
0.8 1.0 1.2 
 20_jd
0.8 1.0 1.2 
 13_jd
0.8 1.0 1.2 



 13_jd_Delta
            0.8      1.0      1.2
factor    28.56    35.69    42.83
value   7563.98  7558.20  7599.35

 20_jd_Delta
            0.8      1.0      1.2
factor    42.92    53.65    64.38
value   7411.66  7558.20  7715.81

 44_sv_Delta
            0.8      1.0      1.2
factor    42.84    53.55    64.26
value   7435.19  7558.20  7708.91
CPU times: user 7.05 s, sys: 552 ms, total: 7.6 s
Wall time: 5.67 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)


 44_sv
0.8 1.0 1.2 
 20_jd
0.8 1.0 1.2 
 13_jd
0.8 1.0 1.2 



 13_jd_Vega
            0.8     1.0      1.2
factor     0.32     0.4     0.48
value   7538.97  7558.2  7575.06

 20_jd_Vega
            0.8      1.0      1.2
factor     0.21     0.27     0.32
value   7551.84  7558.20  7558.12

 44_sv_Vega
            0.8      1.0      1.2
factor     0.34     0.42     0.51
value   7549.46  7558.20  7565.04
CPU times: user 6.64 s, sys: 496 ms, total: 7.14 s
Wall time: 5.52 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 0x10a825490>

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.grid(); plt.ylabel('risk factor level')
plt.gcf().autofmt_xdate()


Paths for  44_sv (blue)
Paths for  20_jd (red)
Paths for  13_jd (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://www.pythonquants.com | analytics@pythonquants.com | http://twitter.com/dyjh

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

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

Python for Finance (O'Reilly) | http://shop.oreilly.com/product/0636920032441.do