Coverage for rivapy/models/stoch_local_vol.py: 98%
50 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 14:27 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 14:27 +0000
1import numpy as np
2import bisect
3from rivapy.models.local_vol import LocalVol
4import rivapy.numerics.kernel_regression as kernel_regression
6class StochasticLocalVol:
7 def __init__(self, stoch_vol_model):
8 """ Stochastic Local Volatility model
10 Args:
11 stochastic_vol_model (StochasticVolModel): The underlying stochastic vol model
12 """
13 self._stoch_vol_model = stoch_vol_model
14 self._stoch_local_variance = None #np.ones(shape=(time_grid.shape[0], x_strikes.shape[0]))
15 self._x_strikes = None
16 self._time_grid = None
18 def calibrate_MC(self,
19 vol_param,
20 x_strikes: np.ndarray,
21 time_grid: np.ndarray,
22 n_sims,
23 local_var: np.ndarray=None,
24 call_prices: np.ndarray=None):
25 """Calibrate the Heston Local Volatility Model using kernel regression.
27 This method calibrates the local volatility part of the Heston Model given a volatility parametrization so that the
28 respective implied volatilities from the given vol parametrization are reproduced by the Heston Local Volatility model.
29 The calibration is based on kernel regression as described in `Applied Machine Learning for Stochastic Local Volatility Calibration <https://www.frontiersin.org/articles/10.3389/frai.2019.00004/full>`_.
31 Args:
32 vol_param ([type]): [description]
33 x_strikes (np.array): [description]
34 time_grid (np.array): [description]
35 n_sims ([type]): [description]
36 local_var (np.ndarray, optional): [description]. Defaults to None.
37 call_prices (np.ndarray, optional): Defaults to None.
38 """
39 if local_var is None:
40 local_var = LocalVol.compute_local_var(vol_param, x_strikes, time_grid, call_prices)
41 self._stoch_local_variance = StochasticLocalVol._calibrate_MC(self._stoch_vol_model,
42 x_strikes, time_grid, n_sims, local_var)
43 self._x_strikes = x_strikes
44 self._time_grid = time_grid
48 def apply_mc_step(self, x: np.ndarray, t0: float, t1: float, rnd: np.ndarray, inplace: bool = True):
49 """Apply a MC-Euler step for the Heston Local Vol Model for n different paths.
51 Args:
52 x (np.ndarray): 2-d array containing the start values for the spot and variance. The first column contains the spot, the second the variance values.
53 t0 ([type]): [description]
54 t1 ([type]): [description]
55 rnd ([type]): [description]
56 """
57 t0_index = bisect.bisect_left(self._time_grid, t0)
58 if t0_index == 0:# or t0_index == self._time_grid.shape[0]:
59 slv = self._stoch_local_variance[0]
60 elif t0_index == self._time_grid.shape[0]:
61 slv = self._stoch_local_variance[-1]
62 else:
63 dt = self._time_grid[t0_index] - self._time_grid[t0_index-1]
64 w1 = (t0-self._time_grid[t0_index-1])/dt
65 w2 = (self._time_grid[t0_index] - t0)/dt
66 slv = w1*self._stoch_local_variance[t0_index] + w2*self._stoch_local_variance[t0_index-1]
67 slv = np.interp(x[:,0], self._x_strikes, slv)
68 return self._stoch_vol_model.apply_mc_step(x, t0, t1, rnd, inplace, slv)
69 # if False:
70 # rnd_S = rnd[:,0]
71 # rnd_V = rnd[:,1]
72 # rnd_corr_S = np.sqrt(1.0-self._stoch_vol_model._correlation**2)*rnd_S + self._stoch_vol_model._correlation*rnd_V
73 # S = x_[:,0]
74 # v = x_[:,1]
75 # dt = t1-t0
76 # sqrt_dt = np.sqrt(dt)
77 # S *= np.exp(-0.5*v*slv*dt + np.sqrt(v*slv)*rnd_corr_S*sqrt_dt)
78 # v += self._stoch_vol_model._mean_reversion_speed*(self._stoch_vol_model._long_run_variance-v)*dt + self._stoch_vol_model._vol_of_vol*np.sqrt(v)*rnd_V*sqrt_dt
79 # x_[:,1] = np.maximum(v,0)
80 # return x_
81 def get_initial_value(self)->np.ndarray:
82 """Return the initial value (x0, v0)
84 Returns:
85 np.ndarray: Initial value.
86 """
87 return self._stoch_vol_model.get_initial_value()
89 @staticmethod
90 def _calibrate_MC(stoch_vol,
91 x_strikes: np.array,
92 time_grid: np.array,
93 n_sims,
94 local_var: np.ndarray,
95 x0 = 1.0):
97 # def apply_mc_step( x, t0, t1, rnd, stoch_local_var):
98 # slv = np.interp(x[:,0], x_strikes, stoch_local_var)
99 # rnd_S = rnd[:,0]
100 # rnd_V = rnd[:,1]
101 # rnd_corr_S = np.sqrt(1.0-stoch_vol._correlation**2)*rnd_S + stoch_vol._correlation*rnd_V
102 # S = x[:,0]
103 # v = x[:,1]
104 # dt = t1-t0
105 # sqrt_dt = np.sqrt(dt)
106 # S *= np.exp((0.5*v*slv)*dt + np.sqrt(v*slv)*rnd_corr_S*sqrt_dt)
107 # v += stoch_vol._mean_reversion_speed*(stoch_vol._long_run_variance-v)*dt + stoch_vol._vol_of_vol*np.sqrt(v)*rnd_V*sqrt_dt
108 # x[:,1] = np.maximum(v,0)
110 stoch_local_variance = np.empty(local_var.shape)
111 stoch_local_variance[0] = local_var[0]/stoch_vol._initial_variance
112 #now apply explicit euler to get new values for v and S and then apply kernel regression to estimate new local variance
113 x = np.empty((n_sims,2))
114 initial_value = stoch_vol.get_initial_value()
115 x[:,0] = initial_value[0]
116 x[:,1] = initial_value[1]
117 for i in range(1,time_grid.shape[0]):
118 rnd = np.random.normal(size=(n_sims,2))
119 slv = np.interp(x[:,0], x_strikes, stoch_local_variance[i-1])
120 stoch_vol.apply_mc_step(x, time_grid[i-1], time_grid[i], rnd, True, slv)
121 gamma = ( (4.0*np.std(x[:,0])**5) / (3.0*x.shape[0]) )**(-1.0/5.0)
122 kr = kernel_regression.KernelRegression(gamma = gamma).fit(x[:,0:1],x[:,1])
123 stoch_local_variance[i] = local_var[i]/kr.predict(x_strikes.reshape((-1,1)))
124 # overwrite all values at strikes that are outside the simulated spot range
125 min_spot = np.min(x[:,0])
126 stoch_local_variance[i][x_strikes<min_spot] = stoch_local_variance[i][x_strikes>min_spot][0]
127 max_spot = np.max(x[:,0])
128 stoch_local_variance[i][x_strikes>max_spot] = stoch_local_variance[i][x_strikes<max_spot][-1]
129 return stoch_local_variance