Quite Complex Portfolios ======================== This part illustrates that you can **model, value and risk manage quite complex derivatives portfolios** with DX Analytics. .. code:: python 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. .. code:: python 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) .. code:: python plt.figure(figsize=(10, 6)) plt.plot(ssr.process.time_grid, ssr.process.get_instrument_values()[:, :10]); plt.gcf().autofmt_xdate() .. image:: 13_dx_quite_complex_portfolios_files/13_dx_quite_complex_portfolios_7_0.png .. code:: python # market environments me = market_environment('gbm', dt.datetime(2015, 1, 1)) .. code:: python # geometric Brownian motion me.add_constant('initial_value', 36.) me.add_constant('volatility', 0.2) me.add_constant('currency', 'EUR') .. code:: python # jump diffusion me.add_constant('lambda', 0.4) me.add_constant('mu', -0.4) me.add_constant('delta', 0.2) .. code:: python # 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. .. code:: python # 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)) .. code:: python # add valuation environment to market environments me.add_environment(val_env) .. code:: python no = 50 # 50 different risk factors in total .. code:: python 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) .. code:: python 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] .. parsed-literal:: [[' 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. .. code:: python 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. .. code:: python # 5 times the number of risk factors # as portfolio positions/instruments pos = 5 * no .. code:: python 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) .. code:: python # number of derivatives positions len(positions) .. parsed-literal:: 250 Portfolio Valuation ------------------- First, the derivatives portfolio with **sequential valuation**. .. code:: python port = derivatives_portfolio( name='portfolio', positions=positions, val_env=val_env, risk_factors=risk_factors, correlations=correlations, parallel=True) # sequential calculation .. code:: python port.val_env.get_list('cholesky_matrix') .. parsed-literal:: 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**. .. code:: python %time res = port.get_statistics(fixed_seed=True) .. parsed-literal:: 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 .. code:: python res.set_index('position', inplace=False) .. raw:: html
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. .. code:: python %time pvs = port.get_present_values() .. parsed-literal:: CPU times: user 658 ms, sys: 8.02 s, total: 8.68 s Wall time: 13.9 s .. code:: python plt.figure(figsize=(10, 6)) plt.hist(pvs, bins=30); plt.xlabel('portfolio present values') plt.ylabel('frequency') .. parsed-literal:: .. image:: 13_dx_quite_complex_portfolios_files/13_dx_quite_complex_portfolios_36_1.png Some **statistics** via pandas. .. code:: python pdf = pd.DataFrame(pvs) pdf.describe() .. raw:: html
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. .. code:: python %%time deltas, benchmark = port.get_port_risk(Greek='Delta', fixed_seed=True, step=0.2, risk_factors=risk_factors.keys()[:4]) risk_report(deltas) .. parsed-literal:: 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. .. code:: python %%time vegas, benchmark = port.get_port_risk(Greek='Vega', fixed_seed=True, step=0.2, risk_factors=risk_factors.keys()[:3]) risk_report(vegas) .. parsed-literal:: 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**. .. code:: python res[['pos_value', 'pos_delta', 'pos_vega']].hist(bins=30, figsize=(9, 6)) plt.ylabel('frequency') .. parsed-literal:: .. image:: 13_dx_quite_complex_portfolios_files/13_dx_quite_complex_portfolios_45_1.png **Sample paths** for three underlyings. .. code:: python 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() .. code:: python 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() .. parsed-literal:: Paths for 19_gbm (blue) Paths for 24_jd (red) Paths for 30_gbm (green) .. image:: 13_dx_quite_complex_portfolios_files/13_dx_quite_complex_portfolios_48_1.png **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