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
from pylab import plt
plt.style.use('seaborn')
%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', 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_sv', '  2_gbm', 0.1],
 ['  1_sv', '  3_gbm', -0.1],
 ['  1_sv', '  4_gbm', -0.1]]

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=False)  # 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    10400.2920
pos_delta      116.0584
pos_vega      8477.9800
dtype: float64
CPU times: user 47.7 s, sys: 890 ms, total: 48.6 s
Wall time: 12.4 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 0_option_pos_38 6 European single [ 1_sv] 7.263 EUR 43.578 3.6036 22.7046
1_option_pos_39 1_option_pos_39 8 American single [ 2_gbm] 5.398 EUR 43.184 -3.9168 124.5688
2_option_pos_37 2_option_pos_37 3 European single [ 3_gbm] 3.306 EUR 9.918 1.9935 42.8949
3_option_pos_37 3_option_pos_37 8 American single [ 4_gbm] 9.097 EUR 72.776 -8.0000 5.3336
4_option_pos_39 4_option_pos_39 1 European single [ 5_gbm] 17.664 EUR 17.664 0.7531 13.4445
5_option_pos_37 5_option_pos_37 3 American single [ 6_jd] 13.354 EUR 40.062 -3.0000 21.9111
6_option_pos_36 6_option_pos_36 9 European single [ 7_jd] 17.651 EUR 158.859 7.9128 41.3010
7_option_pos_39 7_option_pos_39 3 American single [ 8_gbm] 9.538 EUR 28.614 -2.9169 -4.6260
8_option_pos_36 8_option_pos_36 4 American single [ 9_sv] 2.584 EUR 10.336 -0.5512 5.1040
9_option_pos_39 9_option_pos_39 7 American single [ 10_sv] 9.945 EUR 69.615 -3.2571 -1.2845
10_option_pos_37 10_option_pos_37 9 European single [ 11_jd] 13.708 EUR 123.372 7.4493 70.9308
11_option_pos_38 11_option_pos_38 1 European single [ 12_jd] 1.247 EUR 1.247 0.2810 8.4409
12_option_pos_38 12_option_pos_38 2 American single [ 13_sv] 12.707 EUR 25.414 -1.5630 -11.0008
13_option_pos_37 13_option_pos_37 9 European single [ 14_jd] 21.096 EUR 189.864 7.2999 71.2782
14_option_pos_36 14_option_pos_36 3 American single [ 15_gbm] 7.734 EUR 23.202 -1.1520 41.3682
15_option_pos_37 15_option_pos_37 5 European single [ 16_gbm] 1.636 EUR 8.180 1.5570 51.8430
16_option_pos_38 16_option_pos_38 9 European single [ 17_jd] 9.175 EUR 82.575 5.5710 125.4699
17_option_pos_37 17_option_pos_37 3 European single [ 18_sv] 14.931 EUR 44.793 2.3937 2.6664
18_option_pos_39 18_option_pos_39 6 European single [ 19_gbm] 15.695 EUR 94.170 4.2756 87.2814
19_option_pos_36 19_option_pos_36 8 American single [ 20_jd] 12.461 EUR 99.688 -8.0000 -12.2504
20_option_pos_39 20_option_pos_39 8 American single [ 21_sv] 5.136 EUR 41.088 -2.0192 61.7368
21_option_pos_37 21_option_pos_37 2 American single [ 22_sv] 6.541 EUR 13.082 -0.9288 2.7770
22_option_pos_38 22_option_pos_38 8 American single [ 23_jd] 1.238 EUR 9.904 -0.3840 62.1928
23_option_pos_39 23_option_pos_39 6 American single [ 24_jd] 17.719 EUR 106.314 -4.3524 58.4406
24_option_pos_38 24_option_pos_38 4 European single [ 25_jd] 11.286 EUR 45.144 3.2148 22.3364
25_option_pos_36 25_option_pos_36 1 European single [ 26_gbm] 25.986 EUR 25.986 0.8555 11.0891
26_option_pos_36 26_option_pos_36 4 European single [ 27_jd] 7.186 EUR 28.744 3.0756 17.4980
27_option_pos_38 27_option_pos_38 7 European single [ 28_sv] 0.907 EUR 6.349 1.5309 13.2580
28_option_pos_37 28_option_pos_37 8 European single [ 29_jd] 25.429 EUR 203.432 6.8888 103.1016
29_option_pos_37 29_option_pos_37 5 European single [ 30_gbm] 12.805 EUR 64.025 3.4515 70.5780
... ... ... ... ... ... ... ... ... ...
220_option_pos_38 220_option_pos_38 8 European single [ 21_sv] 15.606 EUR 124.848 6.3080 27.5824
221_option_pos_39 221_option_pos_39 4 American single [ 22_sv] 7.654 EUR 30.616 -1.6732 18.2884
222_option_pos_38 222_option_pos_38 4 European single [ 23_jd] 23.064 EUR 92.256 3.5816 10.1348
223_option_pos_37 223_option_pos_37 5 American single [ 24_jd] 15.969 EUR 79.845 -3.1480 55.6190
224_option_pos_37 224_option_pos_37 3 American single [ 25_jd] 2.427 EUR 7.281 -0.4377 23.4090
225_option_pos_39 225_option_pos_39 2 European single [ 26_gbm] 23.994 EUR 47.988 1.6648 25.6842
226_option_pos_39 226_option_pos_39 5 European single [ 27_jd] 5.369 EUR 26.845 3.5640 32.4455
227_option_pos_37 227_option_pos_37 1 American single [ 28_sv] 16.571 EUR 16.571 -0.8666 -8.6498
228_option_pos_38 228_option_pos_38 3 European single [ 29_jd] 24.760 EUR 74.280 2.5620 40.4946
229_option_pos_38 229_option_pos_38 6 European single [ 30_gbm] 12.399 EUR 74.394 4.0752 86.0244
230_option_pos_36 230_option_pos_36 7 American single [ 31_sv] 13.602 EUR 95.214 -5.3767 26.1534
231_option_pos_38 231_option_pos_38 1 European single [ 32_jd] 9.795 EUR 9.795 0.7411 7.7550
232_option_pos_39 232_option_pos_39 1 American single [ 33_sv] 8.961 EUR 8.961 -0.3033 10.3389
233_option_pos_37 233_option_pos_37 9 American single [ 34_jd] 6.886 EUR 61.974 -2.0682 132.4161
234_option_pos_37 234_option_pos_37 6 American single [ 35_jd] 2.778 EUR 16.668 -0.6306 65.8488
235_option_pos_37 235_option_pos_37 4 American single [ 36_gbm] 0.813 EUR 3.252 -0.2988 32.4640
236_option_pos_39 236_option_pos_39 4 European single [ 37_jd] 0.273 EUR 1.092 0.8576 30.1120
237_option_pos_37 237_option_pos_37 9 American single [ 38_gbm] 15.832 EUR 142.488 -8.5698 22.3146
238_option_pos_38 238_option_pos_38 7 American single [ 39_sv] 9.088 EUR 63.616 -3.9452 -3.6386
239_option_pos_39 239_option_pos_39 6 European single [ 40_jd] 14.955 EUR 89.730 4.1862 72.1008
240_option_pos_36 240_option_pos_36 2 American single [ 41_jd] 5.380 EUR 10.760 -0.8410 16.4666
241_option_pos_36 241_option_pos_36 7 European single [ 42_sv] 6.254 EUR 43.778 4.3939 5.6280
242_option_pos_38 242_option_pos_38 1 American single [ 43_jd] 8.108 EUR 8.108 -0.2186 16.6002
243_option_pos_38 243_option_pos_38 5 American single [ 44_jd] 4.440 EUR 22.200 -1.0505 70.7775
244_option_pos_37 244_option_pos_37 7 European single [ 45_sv] 12.133 EUR 84.931 5.1870 22.6198
245_option_pos_36 245_option_pos_36 5 European single [ 46_gbm] 22.709 EUR 113.545 4.7900 -2.6650
246_option_pos_37 246_option_pos_37 4 European single [ 47_sv] 2.635 EUR 10.540 1.5900 3.7216
247_option_pos_39 247_option_pos_39 3 American single [ 48_sv] 11.420 EUR 34.260 -1.4394 6.6159
248_option_pos_36 248_option_pos_36 6 European single [ 49_gbm] 0.246 EUR 1.476 0.6678 32.0346
249_option_pos_36 249_option_pos_36 5 American single [ 50_gbm] 4.128 EUR 20.640 -1.6095 67.1685

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 11.5 s, sys: 246 ms, total: 11.8 s
Wall time: 2.98 s

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


Out[22]:
Text(0, 0.5, 'frequency')

Some statistics via pandas.


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


Out[23]:
0
count 1000.000000
mean 10385.491824
std 1891.255451
min 6045.025829
25% 9059.919260
50% 10189.486685
75% 11436.385373
max 20446.938649

The delta risk report.


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


  1_sv
0.8
1.0
1.2

  2_gbm
0.8
1.0
1.2

  3_gbm
0.8
1.0
1.2

  4_gbm
0.8
1.0
1.2




  1_sv_Delta
dim_0  dim_1 
0.8    factor       28.73
       value     10318.88
1.0    factor       35.91
       value     10385.48
1.2    factor       43.09
       value     10490.80
Name:   1_sv_Delta, dtype: float64

  2_gbm_Delta
dim_0  dim_1 
0.8    factor       30.36
       value     10410.14
1.0    factor       37.95
       value     10385.48
1.2    factor       45.54
       value     10391.22
Name:   2_gbm_Delta, dtype: float64

  3_gbm_Delta
dim_0  dim_1 
0.8    factor       30.04
       value     10440.04
1.0    factor       37.55
       value     10385.48
1.2    factor       45.06
       value     10421.06
Name:   3_gbm_Delta, dtype: float64

  4_gbm_Delta
dim_0  dim_1 
0.8    factor       22.29
       value     10429.74
1.0    factor       27.87
       value     10385.48
1.2    factor       33.44
       value     10351.02
Name:   4_gbm_Delta, dtype: float64
CPU times: user 14.7 s, sys: 325 ms, total: 15 s
Wall time: 3.82 s

The vega risk report.


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


  1_sv
0.8
1.0
1.2

  2_gbm
0.8
1.0
1.2

  3_gbm
0.8
1.0
1.2




  1_sv_Vega
dim_0  dim_1 
0.8    factor        0.42
       value     10377.30
1.0    factor        0.52
       value     10384.95
1.2    factor        0.62
       value     10402.19
Name:   1_sv_Vega, dtype: float64

  2_gbm_Vega
dim_0  dim_1 
0.8    factor        0.27
       value     10365.33
1.0    factor        0.34
       value     10384.95
1.2    factor        0.41
       value     10404.53
Name:   2_gbm_Vega, dtype: float64

  3_gbm_Vega
dim_0  dim_1 
0.8    factor        0.12
       value     10374.04
1.0    factor        0.14
       value     10384.95
1.2    factor        0.17
       value     10395.83
Name:   3_gbm_Vega, dtype: float64
CPU times: user 13.2 s, sys: 200 ms, total: 13.4 s
Wall time: 3.38 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]:
Text(0, 0.5, 'frequency')

Sample paths for three underlyings.


In [27]:
paths_0 = list(port.underlying_objects.values())[0]
paths_0.generate_paths()
paths_1 = list(port.underlying_objects.values())[1]
paths_1.generate_paths()
paths_2 = list(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  32_jd (blue)
Paths for  10_sv (red)
Paths for   3_gbm (green)

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