Coverage for rivapy/models/heston.py: 96%
56 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
1from typing import Union
2import numpy as np
3import scipy
4import scipy.constants
7class HestonModel:
8 def __init__(self, long_run_variance, mean_reversion_speed, vol_of_vol,
9 initial_variance, correlation):
10 """_summary_
12 Args:
13 long_run_variance (_type_): _description_
14 mean_reversion_speed (_type_): _description_
15 vol_of_vol (_type_): _description_
16 initial_variance (_type_): _description_
17 correlation (_type_): _description_
18 """
19 self._long_run_variance = long_run_variance
20 self._mean_reversion_speed = mean_reversion_speed
21 self._vol_of_vol = vol_of_vol
22 self._initial_variance = initial_variance
23 self._correlation = correlation
25 def feller_condition(self):
26 """Return True if the model parameter fulfill the Feller condition
27 ..:
29 Returns:
30 bool: True->Feller condition is fullfilled
31 """
32 return 2*self._mean_reversion_speed*self._long_run_variance>self._vol_of_vol > 0
34 def get_initial_value(self)->np.ndarray:
35 """Return the initial value (x0, v0)
37 Returns:
38 np.ndarray: Initial value.
39 """
40 return np.array([1.0, self._initial_variance])
42 def _characteristic_func(self, xi, s0, v0, tau):
43 """Characteristic function needed internally to compute call prices with analytic formula.
44 """
45 ixi = 1j * xi
46 d = np.sqrt((self._mean_reversion_speed - ixi * self._correlation * self._vol_of_vol)**2
47 + self._vol_of_vol**2 * (ixi + xi**2))
48 g = (self._mean_reversion_speed - ixi * self._correlation * self._vol_of_vol - d) / (self._mean_reversion_speed - ixi * self._correlation * self._vol_of_vol + d)
49 ee = np.exp(-d * tau)
50 C = self._mean_reversion_speed * self._long_run_variance / self._vol_of_vol**2 * (
51 (self._mean_reversion_speed - ixi * self._correlation * self._vol_of_vol - d) * tau - 2. * np.log((1 - g * ee) / (1 - g))
52 )
53 D = (self._mean_reversion_speed - ixi * self._correlation * self._vol_of_vol - d) / self._vol_of_vol**2 * (
54 (1 - ee) / (1 - g * ee)
55 )
56 return np.exp(C + D*v0 + ixi * np.log(s0))
58 def call_price(self, s0: float, v0: float, K: Union[np.ndarray, float], ttm: Union[np.ndarray, float])->Union[np.ndarray, float]:
59 """Computes a call price for the Heston model via integration over characteristic function.
61 Args:
62 s0 (float): current spot
63 v0 (float): current variance
64 K (float): strike
65 ttm (float): time to maturity
66 """
67 if isinstance(ttm, np.ndarray):
68 result = np.empty((ttm.shape[0], K.shape[0], ))
69 for i in range(ttm.shape[0]):
70 #for j in range(K.shape[0]):
71 #result[i,j] = self.call_price(s0,v0,K[j], tau[i])
72 result[i,:] = self.call_price(s0,v0,K, ttm[i])
73 return result
75 def integ_func(xi, s0, v0, K, tau, num):
76 ixi = 1j * xi
77 if num == 1:
78 return (self._characteristic_func(xi - 1j, s0, v0, tau) / (ixi * self._characteristic_func(-1j, s0, v0, tau)) * np.exp(-ixi * np.log(K))).real
79 else:
80 return (self._characteristic_func(xi, s0, v0, tau) / (ixi) * np.exp(-ixi * np.log(K))).real
82 if ttm < 1e-3:
83 res = (s0-K > 0) * (s0-K)
84 else:
85 "Simplified form, with only one integration. "
86 h = lambda xi: s0 * integ_func(xi, s0, v0, K, ttm, 1) - K * integ_func(xi, s0, v0, K, ttm, 2)
87 res = 0.5 * (s0 - K) + 1/scipy.constants.pi * scipy.integrate.quad_vec(h, 0, 500.)[0] #vorher 500
88 return res
91 def apply_mc_step(self, x: np.ndarray, t0: float, t1: float, rnd: np.ndarray, inplace: bool = True, slv: np.ndarray= None):
92 """Apply a MC-Euler step for the Heston Model for n different paths.
94 Args:
95 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.
96 t0 (float): The current time.
97 t1 (float): The next timestep to be computed.
98 rnd (np.ndarray): Two-dimensional array of shape (n_sims,2) containing the normal random numbers. Each row of the array is used to compute the correlated random numbers for the respective simulation.
99 slv (np.ndarray): Stochastic local variance (for each path) to be multiplied with the heston variance. This is used by the StochasticVolatilityModel and can be ignored.
100 """
101 if not inplace:
102 x_ = x.copy()
103 else:
104 x_ = x
105 rnd_V = np.sqrt(1.0-self._correlation**2)*rnd[:,1] + self._correlation*rnd[:,0]
106 rnd_corr_S = rnd[:,0]
107 S = x_[:,0]
108 v = x_[:,1]
109 dt = t1-t0
110 sqrt_dt = np.sqrt(dt)
111 if slv is None:
112 slv=1.0
113 S *= np.exp(- 0.5*v*slv*dt + np.sqrt(v*slv)*rnd_corr_S*sqrt_dt)
114 v += self._mean_reversion_speed*(self._long_run_variance-v)*dt + self._vol_of_vol*np.sqrt(v)*rnd_V*sqrt_dt
115 x_[:,1] = np.maximum(v,0)
116 return x_