optPricing 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. optpricing-2.0.0.dist-info/METADATA +105 -0
  2. optpricing-2.0.0.dist-info/RECORD +92 -0
  3. optpricing-2.0.0.dist-info/WHEEL +5 -0
  4. optpricing-2.0.0.dist-info/entry_points.txt +3 -0
  5. optpricing-2.0.0.dist-info/licenses/LICENSE +9 -0
  6. optpricing-2.0.0.dist-info/top_level.txt +1 -0
  7. quantfin/__init__.py +101 -0
  8. quantfin/atoms/__init__.py +21 -0
  9. quantfin/atoms/bond.py +28 -0
  10. quantfin/atoms/option.py +70 -0
  11. quantfin/atoms/rate.py +46 -0
  12. quantfin/atoms/stock.py +39 -0
  13. quantfin/calibration/__init__.py +25 -0
  14. quantfin/calibration/calibrator.py +155 -0
  15. quantfin/calibration/fit_jump_parameters.py +66 -0
  16. quantfin/calibration/fit_market_params.py +120 -0
  17. quantfin/calibration/iv_surface.py +155 -0
  18. quantfin/calibration/technique_selector.py +47 -0
  19. quantfin/calibration/vectorized_bsm_iv.py +93 -0
  20. quantfin/calibration/vectorized_integration_iv.py +143 -0
  21. quantfin/cli.py +372 -0
  22. quantfin/config.py +57 -0
  23. quantfin/dashboard/__init__.py +14 -0
  24. quantfin/dashboard/app.py +26 -0
  25. quantfin/dashboard/pages/1_Calibration.py +57 -0
  26. quantfin/dashboard/pages/2_Pricer_and_Greeks.py +150 -0
  27. quantfin/dashboard/pages/3_Financial_Tools.py +69 -0
  28. quantfin/dashboard/pages/__init__.py +0 -0
  29. quantfin/dashboard/plots.py +157 -0
  30. quantfin/dashboard/service.py +176 -0
  31. quantfin/dashboard/widgets.py +87 -0
  32. quantfin/data/__init__.py +26 -0
  33. quantfin/data/historical_manager.py +86 -0
  34. quantfin/data/market_data_manager.py +236 -0
  35. quantfin/models/__init__.py +54 -0
  36. quantfin/models/base/__init__.py +16 -0
  37. quantfin/models/base/base_model.py +198 -0
  38. quantfin/models/base/validators.py +95 -0
  39. quantfin/models/bates.py +192 -0
  40. quantfin/models/blacks_approx.py +134 -0
  41. quantfin/models/bsm.py +262 -0
  42. quantfin/models/cev.py +109 -0
  43. quantfin/models/cgmy.py +189 -0
  44. quantfin/models/cir.py +90 -0
  45. quantfin/models/dupire_local.py +67 -0
  46. quantfin/models/heston.py +204 -0
  47. quantfin/models/hyperbolic.py +170 -0
  48. quantfin/models/kou.py +148 -0
  49. quantfin/models/merton_jump.py +225 -0
  50. quantfin/models/nig.py +180 -0
  51. quantfin/models/perpetual_put.py +115 -0
  52. quantfin/models/sabr.py +64 -0
  53. quantfin/models/sabr_jump.py +68 -0
  54. quantfin/models/vasicek.py +88 -0
  55. quantfin/models/vg.py +178 -0
  56. quantfin/parity/__init__.py +13 -0
  57. quantfin/parity/implied_rate.py +110 -0
  58. quantfin/parity/parity_model.py +178 -0
  59. quantfin/techniques/__init__.py +31 -0
  60. quantfin/techniques/base/__init__.py +21 -0
  61. quantfin/techniques/base/base_technique.py +54 -0
  62. quantfin/techniques/base/greek_mixin.py +265 -0
  63. quantfin/techniques/base/iv_mixin.py +116 -0
  64. quantfin/techniques/base/lattice_technique.py +129 -0
  65. quantfin/techniques/base/pricing_result.py +27 -0
  66. quantfin/techniques/base/random_utils.py +31 -0
  67. quantfin/techniques/closed_form.py +296 -0
  68. quantfin/techniques/crr.py +59 -0
  69. quantfin/techniques/fft.py +178 -0
  70. quantfin/techniques/integration.py +202 -0
  71. quantfin/techniques/kernels/__init__.py +37 -0
  72. quantfin/techniques/kernels/lattice_kernels.py +286 -0
  73. quantfin/techniques/kernels/mc_kernels.py +337 -0
  74. quantfin/techniques/leisen_reimer.py +59 -0
  75. quantfin/techniques/monte_carlo.py +259 -0
  76. quantfin/techniques/pde.py +155 -0
  77. quantfin/techniques/topm.py +59 -0
  78. quantfin/workflows/__init__.py +14 -0
  79. quantfin/workflows/backtest_workflow.py +133 -0
  80. quantfin/workflows/configs/__init__.py +51 -0
  81. quantfin/workflows/configs/bates_config.py +25 -0
  82. quantfin/workflows/configs/bsm_config.py +9 -0
  83. quantfin/workflows/configs/cev_config.py +16 -0
  84. quantfin/workflows/configs/cgmy_config.py +18 -0
  85. quantfin/workflows/configs/heston_config.py +21 -0
  86. quantfin/workflows/configs/hyperbolic_config.py +18 -0
  87. quantfin/workflows/configs/kou_config.py +16 -0
  88. quantfin/workflows/configs/merton_config.py +18 -0
  89. quantfin/workflows/configs/nig_config.py +13 -0
  90. quantfin/workflows/configs/sabr_config.py +19 -0
  91. quantfin/workflows/configs/vg_config.py +9 -0
  92. quantfin/workflows/daily_workflow.py +150 -0
quantfin/models/nig.py ADDED
@@ -0,0 +1,180 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+
8
+ from quantfin.models.base import CF, BaseModel, ParamValidator
9
+
10
+ __doc__ = """
11
+ Defines the Normal Inverse Gaussian (NIG) model, a pure-jump Lévy process.
12
+ """
13
+
14
+
15
+ class NIGModel(BaseModel):
16
+ """Normal Inverse Gaussian (NIG) model, a pure-jump Lévy process."""
17
+
18
+ name: str = "Normal Inverse Gaussian"
19
+ supports_cf: bool = True
20
+ is_pure_levy: bool = True
21
+
22
+ default_params = {"alpha": 15.0, "beta": -5.0, "delta": 0.5}
23
+
24
+ def __init__(self, params: dict[str, float] | None = None):
25
+ """
26
+ Initializes the NIG model.
27
+
28
+ Parameters
29
+ ----------
30
+ params : dict[str, float] | None, optional
31
+ A dictionary of model parameters. If None, `default_params` are used.
32
+ """
33
+ super().__init__(params or self.default_params)
34
+
35
+ def _validate_params(self) -> None:
36
+ p = self.params
37
+ req = ["alpha", "beta", "delta"]
38
+ ParamValidator.require(p, req, model=self.name)
39
+ ParamValidator.positive(p, ["alpha", "delta"], model=self.name)
40
+ if not (abs(p["beta"]) < p["alpha"]):
41
+ raise ValueError("NIG params must satisfy |beta| < alpha.")
42
+
43
+ def __eq__(self, other: object) -> bool:
44
+ if not isinstance(other, NIGModel):
45
+ return NotImplemented
46
+ return self.params == other.params
47
+
48
+ def __hash__(self) -> int:
49
+ return hash((self.__class__, tuple(sorted(self.params.items()))))
50
+
51
+ def _cf_impl(
52
+ self,
53
+ *,
54
+ t: float,
55
+ spot: float,
56
+ r: float,
57
+ q: float,
58
+ **_: Any,
59
+ ) -> CF:
60
+ """
61
+ Risk-neutral characteristic function for the log-spot price log(S_t).
62
+
63
+ This is the risk-neutral characteristic function, which includes the
64
+ drift adjustment.
65
+
66
+ Parameters
67
+ ----------
68
+ t : float
69
+ The time to maturity of the option, in years.
70
+ spot : float
71
+ The current price of the underlying asset.
72
+ r : float
73
+ The continuously compounded risk-free rate.
74
+ q : float
75
+ The continuously compounded dividend yield.
76
+
77
+ Returns
78
+ -------
79
+ CF
80
+ The characteristic function.
81
+ """
82
+ p = self.params
83
+ alpha, beta, delta = p["alpha"], p["beta"], p["delta"]
84
+ compensator = delta * (
85
+ np.sqrt(alpha**2 - (beta + 1) ** 2) - np.sqrt(alpha**2 - beta**2)
86
+ )
87
+ drift = r - q + compensator
88
+
89
+ def phi(u: np.ndarray | complex) -> np.ndarray | complex:
90
+ term1 = 1j * u * (np.log(spot) + drift * t)
91
+ term2 = (
92
+ delta
93
+ * t
94
+ * (
95
+ np.sqrt(alpha**2 - beta**2)
96
+ - np.sqrt(alpha**2 - (beta + 1j * u) ** 2)
97
+ )
98
+ )
99
+ return np.exp(term1 + term2)
100
+
101
+ return phi
102
+
103
+ def raw_cf(self, *, t: float) -> Callable:
104
+ """
105
+ Returns the CF of the raw NIG process, without drift or spot.
106
+
107
+ This function represents the characteristic function of the process
108
+ before the risk-neutral drift adjustment is applied.
109
+
110
+ Parameters
111
+ ----------
112
+ t : float
113
+ The time horizon.
114
+
115
+ Returns
116
+ -------
117
+ Callable
118
+ The raw characteristic function.
119
+ """
120
+ p = self.params
121
+ alpha, beta, delta = p["alpha"], p["beta"], p["delta"]
122
+
123
+ def phi_raw(u: np.ndarray | complex) -> np.ndarray | complex:
124
+ term = (
125
+ delta
126
+ * t
127
+ * (
128
+ np.sqrt(alpha**2 - beta**2)
129
+ - np.sqrt(alpha**2 - (beta + 1j * u) ** 2)
130
+ )
131
+ )
132
+ return np.exp(term)
133
+
134
+ return phi_raw
135
+
136
+ def sample_terminal_log_return(
137
+ self,
138
+ T: float,
139
+ size: int,
140
+ rng: np.random.Generator,
141
+ ) -> np.ndarray:
142
+ """
143
+ Generates exact samples of the terminal log-return for the NIG process.
144
+
145
+ This leverages the representation of the NIG process as a Brownian
146
+ motion with drift, time-changed by an Inverse Gaussian process.
147
+
148
+ Parameters
149
+ ----------
150
+ T : float
151
+ The time to maturity, in years.
152
+ size : int
153
+ The number of samples to generate.
154
+ rng : np.random.Generator
155
+ A random number generator instance for reproducibility.
156
+
157
+ Returns
158
+ -------
159
+ np.ndarray
160
+ An array of simulated terminal log-returns.
161
+ """
162
+ from scipy.stats import invgauss
163
+
164
+ p = self.params
165
+ alpha, beta, delta = p["alpha"], p["beta"], p["delta"]
166
+ mu_ig = delta * T / np.sqrt(alpha**2 - beta**2)
167
+ ig_time = invgauss.rvs(mu=mu_ig, size=size, random_state=rng)
168
+ bm_drift = beta * ig_time
169
+ bm_diffusion = np.sqrt(ig_time) * rng.standard_normal(size=size)
170
+ return bm_drift + bm_diffusion
171
+
172
+ # Abstract Method Implementations
173
+ def _sde_impl(self, **kwargs: Any) -> Any:
174
+ raise NotImplementedError("NIG is a pure Lévy process, use terminal sampling.")
175
+
176
+ def _pde_impl(self, **kwargs: Any) -> Any:
177
+ raise NotImplementedError
178
+
179
+ def _closed_form_impl(self, **kwargs: Any) -> Any:
180
+ raise NotImplementedError
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import Any
5
+
6
+ from quantfin.models.base import BaseModel, ParamValidator
7
+
8
+ __doc__ = """
9
+ Defines a model for pricing a perpetual American put option.
10
+ """
11
+
12
+
13
+ class PerpetualPutModel(BaseModel):
14
+ """
15
+ Provides a closed-form price for a perpetual American put option.
16
+
17
+ A perpetual option has no expiry date. This model assumes the holder will
18
+ exercise optimally. The risk-free rate and volatility are considered
19
+ intrinsic parameters of the model itself.
20
+ """
21
+
22
+ name: str = "Perpetual Put"
23
+ has_closed_form: bool = True
24
+
25
+ default_params = {"sigma": 0.20, "rate": 0.08}
26
+
27
+ def __init__(self, params: dict[str, float]) -> None:
28
+ """
29
+ Initializes the model with its parameters.
30
+
31
+ Parameters
32
+ ----------
33
+ params : dict[str, float] | None, optional
34
+ A dictionary requiring 'sigma' and 'rate'. If None, `default_params`
35
+ are used.
36
+ """
37
+ super().__init__(params=params)
38
+
39
+ def _validate_params(self) -> None:
40
+ """Validates that 'sigma' and 'rate' are present and positive."""
41
+ ParamValidator.require(self.params, ["sigma", "rate"], model=self.name)
42
+ ParamValidator.positive(self.params, ["sigma", "rate"], model=self.name)
43
+
44
+ def __eq__(self, other: object) -> bool:
45
+ if not isinstance(other, PerpetualPutModel):
46
+ return NotImplemented
47
+ return self.params == other.params
48
+
49
+ def __hash__(self) -> int:
50
+ return hash((self.__class__, tuple(sorted(self.params.items()))))
51
+
52
+ def _closed_form_impl(
53
+ self,
54
+ *,
55
+ spot: float,
56
+ strike: float,
57
+ q: float,
58
+ **_: Any,
59
+ ) -> float:
60
+ """
61
+ Calculates the price of a perpetual American put.
62
+
63
+ Note: This model uses the 'rate' from its own parameters (`self.params`),
64
+ ignoring the risk-free rate passed in from the pricing technique. This
65
+ is a specific design choice for this model. The `t` and `call`
66
+ parameters are also ignored as they are not applicable.
67
+
68
+ Parameters
69
+ ----------
70
+ spot : float
71
+ The current price of the underlying asset.
72
+ strike : float
73
+ The strike price of the option.
74
+ q : float
75
+ The continuously compounded dividend yield of the asset.
76
+
77
+ Returns
78
+ -------
79
+ float
80
+ The price of the perpetual American put.
81
+ """
82
+ # uses its own intrinsic rate, not the one from the Rate object.
83
+ r = self.params["rate"]
84
+ sigma = self.params["sigma"]
85
+ vol_sq = sigma**2
86
+
87
+ # negative root of the characteristic quadratic equation
88
+ b = r - q - 0.5 * vol_sq
89
+ gamma = (-b - math.sqrt(b**2 + 2 * r * vol_sq)) / vol_sq
90
+
91
+ # Calculate the optimal exercise boundary (S*)
92
+ s_star = strike * gamma / (gamma - 1.0)
93
+
94
+ if spot <= s_star:
95
+ # If it is optimal to exercise, the value is the intrinsic value.
96
+ return strike - spot
97
+ else:
98
+ # Otherwise, the value is given by the standard formula.
99
+ return (strike - s_star) * (spot / s_star) ** gamma
100
+
101
+ # Abstract Method Implementations
102
+ def _cf_impl(
103
+ self,
104
+ *args: Any,
105
+ **kwargs: Any,
106
+ ) -> Any:
107
+ raise NotImplementedError(
108
+ f"{self.name} does not support a characteristic function."
109
+ )
110
+
111
+ def _sde_impl(self) -> Any:
112
+ raise NotImplementedError(f"{self.name} does not support SDE sampling.")
113
+
114
+ def _pde_impl(self) -> Any:
115
+ raise NotImplementedError(f"{self.name} does not support PDE solving.")
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from quantfin.models.base import BaseModel, ParamValidator
6
+
7
+ __doc__ = """
8
+ Defines the Stochastic Alpha, Beta, Rho (SABR) model.
9
+ """
10
+
11
+
12
+ class SABRModel(BaseModel):
13
+ """
14
+ Stochastic Alpha, Beta, Rho (SABR) model from Hagan et al. (2002).
15
+ """
16
+
17
+ name: str = "SABR"
18
+ supports_sde: bool = True
19
+ has_variance_process: bool = True
20
+ is_sabr: bool = True
21
+ default_params = {"alpha": 0.5, "beta": 0.8, "rho": -0.6}
22
+
23
+ def __init__(self, params: dict[str, float] | None = None):
24
+ """
25
+ Initializes the SABR model.
26
+
27
+ Parameters
28
+ ----------
29
+ params : dict[str, float] | None, optional
30
+ A dictionary of model parameters. If None, `default_params` are used.
31
+ """
32
+ super().__init__(params or self.default_params)
33
+
34
+ def _validate_params(self) -> None:
35
+ """Validates that 'alpha', 'beta', and 'rho' are present and positive."""
36
+ p = self.params
37
+ req = ["alpha", "beta", "rho"]
38
+ ParamValidator.require(p, req, model=self.name)
39
+ ParamValidator.positive(p, ["alpha"], model=self.name)
40
+ ParamValidator.bounded(p, "beta", 0.0, 1.0, model=self.name)
41
+ ParamValidator.bounded(p, "rho", -1.0, 1.0, model=self.name)
42
+
43
+ def __eq__(self, other: object) -> bool:
44
+ if not isinstance(other, SABRModel):
45
+ return NotImplemented
46
+ return self.params == other.params
47
+
48
+ def __hash__(self) -> int:
49
+ return hash((self.__class__, tuple(sorted(self.params.items()))))
50
+
51
+ # Abstract Method Implementations
52
+ def _sde_impl(self, **kwargs: Any) -> Any:
53
+ raise NotImplementedError(
54
+ "SABR uses a specialized kernel, not a generic stepper."
55
+ )
56
+
57
+ def _cf_impl(self, **kwargs: Any) -> Any:
58
+ raise NotImplementedError
59
+
60
+ def _pde_impl(self, **kwargs: Any) -> Any:
61
+ raise NotImplementedError
62
+
63
+ def _closed_form_impl(self, **kwargs: Any) -> Any:
64
+ raise NotImplementedError
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from quantfin.models.base import BaseModel, ParamValidator
6
+
7
+ __doc__ = """
8
+ Defines the SABR model with an added log-normal jump component.
9
+ """
10
+
11
+
12
+ class SABRJumpModel(BaseModel):
13
+ """SABR model with an added log-normal jump component on the spot process."""
14
+
15
+ name: str = "SABR with Jumps"
16
+ supports_sde: bool = True
17
+ has_variance_process: bool = True
18
+ has_jumps: bool = True
19
+ is_sabr: bool = True
20
+
21
+ default_params = {
22
+ "alpha": 0.5,
23
+ "beta": 0.8,
24
+ "rho": -0.6,
25
+ "lambda": 0.4,
26
+ "mu_j": -0.1,
27
+ "sigma_j": 0.15,
28
+ }
29
+
30
+ def __init__(self, params: dict[str, float] | None = None):
31
+ """
32
+ Initializes the SABR with Jumps model.
33
+
34
+ Parameters
35
+ ----------
36
+ params : dict[str, float] | None, optional
37
+ A dictionary of model parameters. If None, `default_params` are used.
38
+ Defaults to None.
39
+ """
40
+ super().__init__(params or self.default_params)
41
+
42
+ def _validate_params(self) -> None:
43
+ p = self.params
44
+ req = ["alpha", "beta", "rho", "lambda", "mu_j", "sigma_j"]
45
+ ParamValidator.require(p, req, model=self.name)
46
+
47
+ def __eq__(self, other: object) -> bool:
48
+ if not isinstance(other, SABRJumpModel):
49
+ return NotImplemented
50
+ return self.params == other.params
51
+
52
+ def __hash__(self) -> int:
53
+ return hash((self.__class__, tuple(sorted(self.params.items()))))
54
+
55
+ # Abstract Method Implementations
56
+ def _sde_impl(self, **kwargs: Any) -> Any:
57
+ raise NotImplementedError(
58
+ "SABR uses a specialized kernel, not a generic stepper."
59
+ )
60
+
61
+ def _cf_impl(self, **kwargs: Any) -> Any:
62
+ raise NotImplementedError
63
+
64
+ def _pde_impl(self, **kwargs: Any) -> Any:
65
+ raise NotImplementedError
66
+
67
+ def _closed_form_impl(self, **kwargs: Any) -> Any:
68
+ raise NotImplementedError
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import Any
5
+
6
+ from quantfin.models.base import BaseModel, ParamValidator
7
+
8
+ __doc__ = """
9
+ Defines the Vasicek (1977) mean-reverting interest rate model.
10
+ """
11
+
12
+
13
+ class VasicekModel(BaseModel):
14
+ """
15
+ Vasicek mean-reverting short rate model.
16
+ dr_t = kappa * (theta - r_t) * dt + sigma * dW_t
17
+ """
18
+
19
+ name: str = "Vasicek"
20
+ has_closed_form: bool = True
21
+
22
+ default_params = {"kappa": 0.86, "theta": 0.09, "sigma": 0.02}
23
+
24
+ def __init__(self, params: dict[str, float] | None = None):
25
+ """
26
+ Initializes the Vasicek model.
27
+
28
+ Parameters
29
+ ----------
30
+ params : dict[str, float] | None, optional
31
+ A dictionary of model parameters. If None, `default_params` are used.
32
+ Defaults to None.
33
+ """
34
+ super().__init__(params or self.default_params)
35
+
36
+ def _validate_params(self) -> None:
37
+ """Validates that 'kappa', 'theta', and 'sigma' are present and positive."""
38
+ p = self.params
39
+ req = ["kappa", "theta", "sigma"]
40
+ ParamValidator.require(p, req, model=self.name)
41
+ ParamValidator.positive(p, ["kappa", "theta", "sigma"], model=self.name)
42
+
43
+ def _closed_form_impl(
44
+ self,
45
+ *,
46
+ spot: float,
47
+ t: float,
48
+ **_: Any,
49
+ ) -> float:
50
+ """
51
+ Calculates the price of a Zero-Coupon Bond.
52
+
53
+ Note: Re-interprets 'spot' as the initial short rate r0 and 't' as
54
+ the bond's maturity T. Ignores other option-specific parameters.
55
+
56
+ Parameters
57
+ ----------
58
+ spot : float
59
+ The initial short rate, r0.
60
+ t : float
61
+ The maturity of the bond, in years.
62
+
63
+ Returns
64
+ -------
65
+ float
66
+ The price of the zero-coupon bond.
67
+ """
68
+ r0, T = spot, t
69
+ p = self.params
70
+ kappa, theta, sigma = p["kappa"], p["theta"], p["sigma"]
71
+
72
+ B = (1 / kappa) * (1 - math.exp(-kappa * T))
73
+ A_log = (theta - sigma**2 / (2 * kappa**2)) * (B - T) - (
74
+ sigma**2 / (4 * kappa)
75
+ ) * B**2
76
+
77
+ price = math.exp(A_log - B * r0)
78
+ return price
79
+
80
+ # Abstract Method Implementations
81
+ def _sde_impl(self, **kwargs: Any) -> Any:
82
+ raise NotImplementedError
83
+
84
+ def _cf_impl(self, **kwargs: Any) -> Any:
85
+ raise NotImplementedError
86
+
87
+ def _pde_impl(self, **kwargs: Any) -> Any:
88
+ raise NotImplementedError
quantfin/models/vg.py ADDED
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+
8
+ from quantfin.models.base import CF, BaseModel, ParamValidator
9
+
10
+ __doc__ = """
11
+ Defines the Variance Gamma (VG) model, a pure-jump Lévy process.
12
+ """
13
+
14
+
15
+ class VarianceGammaModel(BaseModel):
16
+ """Variance Gamma (VG) model, a pure-jump Lévy process."""
17
+
18
+ name: str = "Variance Gamma"
19
+ supports_cf: bool = True
20
+ is_pure_levy: bool = True
21
+
22
+ default_params = {"sigma": 0.2, "nu": 0.1, "theta": -0.14}
23
+ param_defs = {
24
+ "sigma": {
25
+ "label": "Volatility",
26
+ "default": 0.2,
27
+ "min": 0.01,
28
+ "max": 2.0,
29
+ "step": 0.01,
30
+ },
31
+ "nu": {
32
+ "label": "Variance Rate",
33
+ "default": 0.1,
34
+ "min": 0.001,
35
+ "max": 2.0,
36
+ "step": 0.01,
37
+ },
38
+ "theta": {
39
+ "label": "Drift",
40
+ "default": -0.14,
41
+ "min": -2.0,
42
+ "max": 2.0,
43
+ "step": 0.05,
44
+ },
45
+ }
46
+
47
+ def __init__(self, params: dict[str, float] | None = None):
48
+ """
49
+ Initializes the Variance Gamma (VG) model.
50
+
51
+ Parameters
52
+ ----------
53
+ params : dict[str, float]
54
+ A dictionary of model parameters.
55
+ """
56
+ super().__init__(params or self.default_params)
57
+
58
+ def _validate_params(self) -> None:
59
+ p = self.params
60
+ req = ["sigma", "nu", "theta"]
61
+ ParamValidator.require(p, req, model=self.name)
62
+ ParamValidator.positive(p, ["sigma", "nu"], model=self.name)
63
+
64
+ def __eq__(self, other: object) -> bool:
65
+ if not isinstance(other, VarianceGammaModel):
66
+ return NotImplemented
67
+ return self.params == other.params
68
+
69
+ def __hash__(self) -> int:
70
+ return hash((self.__class__, tuple(sorted(self.params.items()))))
71
+
72
+ def _cf_impl(
73
+ self,
74
+ *,
75
+ t: float,
76
+ spot: float,
77
+ r: float,
78
+ q: float,
79
+ **_: Any,
80
+ ) -> CF:
81
+ """
82
+ Variance Gamma characteristic function for the log-spot price log(S_t).
83
+
84
+ This is the risk-neutral characteristic function, which includes the
85
+ drift adjustment.
86
+
87
+ Parameters
88
+ ----------
89
+ t : float
90
+ The time to maturity of the option, in years.
91
+ spot : float
92
+ The current price of the underlying asset.
93
+ r : float
94
+ The continuously compounded risk-free rate.
95
+ q : float
96
+ The continuously compounded dividend yield.
97
+
98
+ Returns
99
+ -------
100
+ CF
101
+ The characteristic function.
102
+ """
103
+ compensator = np.log(self.raw_cf(t=1.0)(-1j))
104
+ drift = r - q - compensator
105
+
106
+ def phi(u: np.ndarray | complex) -> np.ndarray | complex:
107
+ return self.raw_cf(t=t)(u) * np.exp(1j * u * (np.log(spot) + drift * t))
108
+
109
+ return phi
110
+
111
+ def raw_cf(self, *, t: float) -> Callable:
112
+ """
113
+ Returns the raw characteristic function of the VG process itself.
114
+
115
+ This function represents the characteristic function of the process
116
+ before the risk-neutral drift adjustment is applied.
117
+
118
+ Parameters
119
+ ----------
120
+ t : float
121
+ The time horizon.
122
+
123
+ Returns
124
+ -------
125
+ Callable
126
+ The raw characteristic function.
127
+ """
128
+ p = self.params
129
+ sigma, nu, theta = p["sigma"], p["nu"], p["theta"]
130
+
131
+ def phi_raw(u: np.ndarray | complex) -> np.ndarray | complex:
132
+ return (1 - 1j * u * theta * nu + 0.5 * u**2 * sigma**2 * nu) ** (-t / nu)
133
+
134
+ return phi_raw
135
+
136
+ def sample_terminal_log_return(
137
+ self,
138
+ T: float,
139
+ size: int,
140
+ rng: np.random.Generator,
141
+ ) -> np.ndarray:
142
+ """
143
+ Generates exact samples of the terminal log-return for the VG process.
144
+
145
+ This leverages the representation of the VG process as a Brownian
146
+ motion with drift, time-changed by a Gamma process.
147
+
148
+ Parameters
149
+ ----------
150
+ T : float
151
+ The time to maturity, in years.
152
+ size : int
153
+ The number of samples to generate.
154
+ rng : np.random.Generator
155
+ A random number generator instance for reproducibility.
156
+
157
+ Returns
158
+ -------
159
+ np.ndarray
160
+ An array of simulated terminal log-returns.
161
+ """
162
+
163
+ p = self.params
164
+ sigma, nu, theta = p["sigma"], p["nu"], p["theta"]
165
+ gamma_time = rng.gamma(shape=T / nu, scale=nu, size=size)
166
+ bm_drift = theta * gamma_time
167
+ bm_diffusion = sigma * np.sqrt(gamma_time) * rng.standard_normal(size=size)
168
+ return bm_drift + bm_diffusion
169
+
170
+ # Abstract Method Implementations
171
+ def _sde_impl(self, **kwargs: Any) -> Any:
172
+ raise NotImplementedError
173
+
174
+ def _pde_impl(self, **kwargs: Any) -> Any:
175
+ raise NotImplementedError
176
+
177
+ def _closed_form_impl(self, **kwargs: Any) -> Any:
178
+ raise NotImplementedError
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ __doc__ = """
4
+ The `parity` package provides models and utilities based on put-call parity.
5
+ """
6
+
7
+ from .implied_rate import ImpliedRateModel
8
+ from .parity_model import ParityModel
9
+
10
+ __all__ = [
11
+ "ParityModel",
12
+ "ImpliedRateModel",
13
+ ]