14. 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)


14.1. 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('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

In [6]:

# jump diffusion

In [7]:

# stochastic volatility


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))

In [9]:

# add valuation environment to market environments

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)
# random initial_value
np.random.random() * 40. + 20.)
np.random.random() * 0.6 + 0.05)
# the simulation model to choose

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]]


14.2. 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()


14.3. 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


14.4. 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 19.8 s, sys: 1.82 s, total: 21.6 s
Wall time: 15.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

14.5. Risk Analysis¶

Full distribution of portfolio present values illustrated via histogram.

In [21]:

%time pvs = port.get_present_values()

CPU times: user 4.61 s, sys: 456 ms, total: 5.07 s
Wall time: 3.52 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 0x1109aeba8>


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
major  minor
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
major  minor
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
major  minor
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
major  minor
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 5.2 s, sys: 502 ms, total: 5.71 s
Wall time: 4 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
major  minor
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
major  minor
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
major  minor
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 5.05 s, sys: 481 ms, total: 5.53 s
Wall time: 3.89 s


14.6. 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 0x111e61550>


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  48_sv (blue)
Paths for  12_jd (red)
Paths for  22_sv (green)