tensorquantlib 0.3.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 (44) hide show
  1. tensorquantlib/__init__.py +313 -0
  2. tensorquantlib/__main__.py +315 -0
  3. tensorquantlib/backtest/__init__.py +48 -0
  4. tensorquantlib/backtest/engine.py +240 -0
  5. tensorquantlib/backtest/metrics.py +320 -0
  6. tensorquantlib/backtest/strategy.py +348 -0
  7. tensorquantlib/core/__init__.py +6 -0
  8. tensorquantlib/core/ops.py +70 -0
  9. tensorquantlib/core/second_order.py +465 -0
  10. tensorquantlib/core/tensor.py +928 -0
  11. tensorquantlib/data/__init__.py +16 -0
  12. tensorquantlib/data/market.py +160 -0
  13. tensorquantlib/finance/__init__.py +52 -0
  14. tensorquantlib/finance/american.py +263 -0
  15. tensorquantlib/finance/basket.py +291 -0
  16. tensorquantlib/finance/black_scholes.py +219 -0
  17. tensorquantlib/finance/credit.py +199 -0
  18. tensorquantlib/finance/exotics.py +885 -0
  19. tensorquantlib/finance/fx.py +204 -0
  20. tensorquantlib/finance/greeks.py +133 -0
  21. tensorquantlib/finance/heston.py +543 -0
  22. tensorquantlib/finance/implied_vol.py +277 -0
  23. tensorquantlib/finance/ir_derivatives.py +203 -0
  24. tensorquantlib/finance/jump_diffusion.py +203 -0
  25. tensorquantlib/finance/local_vol.py +146 -0
  26. tensorquantlib/finance/rates.py +381 -0
  27. tensorquantlib/finance/risk.py +344 -0
  28. tensorquantlib/finance/variance_reduction.py +420 -0
  29. tensorquantlib/finance/volatility.py +355 -0
  30. tensorquantlib/py.typed +0 -0
  31. tensorquantlib/tt/__init__.py +43 -0
  32. tensorquantlib/tt/decompose.py +576 -0
  33. tensorquantlib/tt/ops.py +386 -0
  34. tensorquantlib/tt/pricing.py +304 -0
  35. tensorquantlib/tt/surrogate.py +634 -0
  36. tensorquantlib/utils/__init__.py +5 -0
  37. tensorquantlib/utils/validation.py +126 -0
  38. tensorquantlib/viz/__init__.py +27 -0
  39. tensorquantlib/viz/plots.py +331 -0
  40. tensorquantlib-0.3.0.dist-info/METADATA +602 -0
  41. tensorquantlib-0.3.0.dist-info/RECORD +44 -0
  42. tensorquantlib-0.3.0.dist-info/WHEEL +5 -0
  43. tensorquantlib-0.3.0.dist-info/licenses/LICENSE +21 -0
  44. tensorquantlib-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,204 @@
1
+ """FX options: Garman-Kohlhagen and Quanto models.
2
+
3
+ Implements FX-specific option pricing:
4
+ - Garman-Kohlhagen (1983): Black-Scholes adapted for FX with foreign rate
5
+ - Quanto options: options on foreign assets settled in domestic currency
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ from scipy.stats import norm
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Garman-Kohlhagen model
15
+ # ---------------------------------------------------------------------------
16
+
17
+ def garman_kohlhagen(
18
+ S: float,
19
+ K: float,
20
+ T: float,
21
+ r_d: float,
22
+ r_f: float,
23
+ sigma: float,
24
+ option_type: str = "call",
25
+ ) -> float:
26
+ """Garman-Kohlhagen FX option pricing model.
27
+
28
+ This is the Black-Scholes formula adapted for FX, where the foreign
29
+ risk-free rate acts as a continuous dividend yield.
30
+
31
+ Parameters
32
+ ----------
33
+ S : float
34
+ Spot FX rate (domestic per foreign).
35
+ K : float
36
+ Strike FX rate.
37
+ T : float
38
+ Time to expiry.
39
+ r_d : float
40
+ Domestic risk-free rate.
41
+ r_f : float
42
+ Foreign risk-free rate.
43
+ sigma : float
44
+ FX volatility.
45
+ option_type : str
46
+ 'call' or 'put'.
47
+
48
+ Returns
49
+ -------
50
+ float
51
+ Option price in domestic currency.
52
+ """
53
+ d1 = (np.log(S / K) + (r_d - r_f + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
54
+ d2 = d1 - sigma * np.sqrt(T)
55
+
56
+ if option_type == "call":
57
+ price = S * np.exp(-r_f * T) * norm.cdf(d1) - K * np.exp(-r_d * T) * norm.cdf(d2)
58
+ else:
59
+ price = K * np.exp(-r_d * T) * norm.cdf(-d2) - S * np.exp(-r_f * T) * norm.cdf(-d1)
60
+
61
+ return float(price)
62
+
63
+
64
+ def gk_greeks(
65
+ S: float,
66
+ K: float,
67
+ T: float,
68
+ r_d: float,
69
+ r_f: float,
70
+ sigma: float,
71
+ option_type: str = "call",
72
+ ) -> dict[str, float]:
73
+ """Compute Garman-Kohlhagen Greeks.
74
+
75
+ Returns
76
+ -------
77
+ dict
78
+ {'delta': ..., 'gamma': ..., 'vega': ..., 'theta': ..., 'rho_d': ..., 'rho_f': ...}
79
+ """
80
+ sqT = sigma * np.sqrt(T)
81
+ d1 = (np.log(S / K) + (r_d - r_f + 0.5 * sigma ** 2) * T) / sqT
82
+ d2 = d1 - sqT
83
+
84
+ exp_rf = np.exp(-r_f * T)
85
+ exp_rd = np.exp(-r_d * T)
86
+
87
+ # Gamma is the same for calls and puts
88
+ gamma = exp_rf * norm.pdf(d1) / (S * sqT)
89
+
90
+ # Vega is the same for calls and puts
91
+ vega = S * exp_rf * norm.pdf(d1) * np.sqrt(T)
92
+
93
+ if option_type == "call":
94
+ delta = exp_rf * norm.cdf(d1)
95
+ theta = (
96
+ -S * exp_rf * norm.pdf(d1) * sigma / (2.0 * np.sqrt(T))
97
+ + r_f * S * exp_rf * norm.cdf(d1)
98
+ - r_d * K * exp_rd * norm.cdf(d2)
99
+ )
100
+ rho_d = K * T * exp_rd * norm.cdf(d2)
101
+ rho_f = -S * T * exp_rf * norm.cdf(d1)
102
+ else:
103
+ delta = exp_rf * (norm.cdf(d1) - 1.0)
104
+ theta = (
105
+ -S * exp_rf * norm.pdf(d1) * sigma / (2.0 * np.sqrt(T))
106
+ - r_f * S * exp_rf * norm.cdf(-d1)
107
+ + r_d * K * exp_rd * norm.cdf(-d2)
108
+ )
109
+ rho_d = -K * T * exp_rd * norm.cdf(-d2)
110
+ rho_f = S * T * exp_rf * norm.cdf(-d1)
111
+
112
+ return {
113
+ 'delta': float(delta),
114
+ 'gamma': float(gamma),
115
+ 'vega': float(vega),
116
+ 'theta': float(theta),
117
+ 'rho_d': float(rho_d),
118
+ 'rho_f': float(rho_f),
119
+ }
120
+
121
+
122
+ def fx_forward(
123
+ S: float,
124
+ r_d: float,
125
+ r_f: float,
126
+ T: float,
127
+ ) -> float:
128
+ """FX forward rate via covered interest rate parity.
129
+
130
+ F = S * exp((r_d - r_f) * T)
131
+
132
+ Returns
133
+ -------
134
+ float
135
+ Forward FX rate.
136
+ """
137
+ return float(S * np.exp((r_d - r_f) * T))
138
+
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Quanto option
142
+ # ---------------------------------------------------------------------------
143
+
144
+ def quanto_option(
145
+ S: float,
146
+ K: float,
147
+ T: float,
148
+ r_d: float,
149
+ r_f: float,
150
+ sigma_s: float,
151
+ sigma_fx: float,
152
+ rho: float,
153
+ fx_rate: float,
154
+ option_type: str = "call",
155
+ ) -> float:
156
+ """Quanto option pricing (option on foreign asset, settled in domestic currency).
157
+
158
+ The quanto adjustment modifies the drift of the foreign asset due to
159
+ the correlation between the asset and the FX rate.
160
+
161
+ Parameters
162
+ ----------
163
+ S : float
164
+ Spot price of the foreign asset (in foreign currency).
165
+ K : float
166
+ Strike price (in foreign currency).
167
+ T : float
168
+ Time to expiry.
169
+ r_d : float
170
+ Domestic risk-free rate.
171
+ r_f : float
172
+ Foreign risk-free rate.
173
+ sigma_s : float
174
+ Volatility of the foreign asset.
175
+ sigma_fx : float
176
+ Volatility of the FX rate.
177
+ rho : float
178
+ Correlation between the asset and FX rate.
179
+ fx_rate : float
180
+ Fixed FX rate at which payoff is converted.
181
+ option_type : str
182
+ 'call' or 'put'.
183
+
184
+ Returns
185
+ -------
186
+ float
187
+ Quanto option price in domestic currency.
188
+ """
189
+ # Quanto-adjusted drift rate
190
+ r_q = r_d - rho * sigma_s * sigma_fx
191
+
192
+ d1 = (np.log(S / K) + (r_q + 0.5 * sigma_s ** 2) * T) / (sigma_s * np.sqrt(T))
193
+ d2 = d1 - sigma_s * np.sqrt(T)
194
+
195
+ if option_type == "call":
196
+ price = fx_rate * np.exp(-r_d * T) * (
197
+ S * np.exp(r_q * T) * norm.cdf(d1) - K * norm.cdf(d2)
198
+ )
199
+ else:
200
+ price = fx_rate * np.exp(-r_d * T) * (
201
+ K * norm.cdf(-d2) - S * np.exp(r_q * T) * norm.cdf(-d1)
202
+ )
203
+
204
+ return float(price)
@@ -0,0 +1,133 @@
1
+ """
2
+ Autograd-based Greeks computation.
3
+
4
+ Computes option sensitivities by running the pricing function through
5
+ the Tensor autograd engine and extracting gradients.
6
+
7
+ - Delta (dV/dS) via first-order autograd
8
+ - Vega (dV/dσ) via first-order autograd
9
+ - Gamma (d²V/dS²) via second-order autograd (gamma_autograd)
10
+ - Vanna (d²V/dS dσ) via second-order autograd (vanna_autograd)
11
+ - Volga (d²V/dσ²) via second-order autograd (volga_autograd)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from collections.abc import Callable
17
+
18
+ import numpy as np
19
+
20
+ from tensorquantlib.core.tensor import Tensor
21
+ from tensorquantlib.core.second_order import (
22
+ gamma_autograd,
23
+ vanna_autograd,
24
+ volga_autograd,
25
+ second_order_greeks,
26
+ )
27
+
28
+
29
+ def compute_greeks(
30
+ price_fn: Callable[..., Tensor],
31
+ S: float,
32
+ K: float,
33
+ T: float,
34
+ r: float,
35
+ sigma: float,
36
+ q: float = 0.0,
37
+ option_type: str = "call",
38
+ include_second_order: bool = True,
39
+ ) -> dict[str, float]:
40
+ """Compute Delta, Vega, Gamma (and optionally Vanna, Volga) using autograd.
41
+
42
+ First-order Greeks (Delta, Vega) come from a single backward pass.
43
+ Second-order Greeks (Gamma, Vanna, Volga) use the hybrid semi-analytic
44
+ method from ``tensorquantlib.core.second_order``: analytical gradients
45
+ differentiated once more by central differences (~1e-10 accuracy).
46
+
47
+ Args:
48
+ price_fn: Pricing function accepting
49
+ ``(S, K, T, r, sigma, q, option_type)`` and returning a Tensor.
50
+ S: Spot price.
51
+ K: Strike price.
52
+ T: Time to expiry.
53
+ r: Risk-free rate.
54
+ sigma: Volatility.
55
+ q: Dividend yield.
56
+ option_type: ``'call'`` or ``'put'``.
57
+ include_second_order: If True (default), also compute Gamma, Vanna,
58
+ and Volga. Set False for speed when only
59
+ first-order Greeks are needed.
60
+
61
+ Returns:
62
+ Dict with keys ``'price'``, ``'delta'``, ``'vega'``, and (when
63
+ ``include_second_order=True``) ``'gamma'``, ``'vanna'``, ``'volga'``.
64
+ """
65
+ # ---- Delta & Vega via first-order autograd ----
66
+ S_t = Tensor(np.array(S, dtype=np.float64), requires_grad=True)
67
+ sigma_t = Tensor(np.array(sigma, dtype=np.float64), requires_grad=True)
68
+
69
+ price = price_fn(S_t, K, T, r, sigma_t, q, option_type)
70
+ price_val = float(price.data.item()) if price.data.size == 1 else float(price.data.sum())
71
+
72
+ if price.data.size > 1:
73
+ price.sum().backward()
74
+ else:
75
+ price.backward()
76
+
77
+ delta = float(S_t.grad.item()) if S_t.grad is not None and S_t.grad.size == 1 else (float(S_t.grad.sum()) if S_t.grad is not None else 0.0)
78
+ vega = float(sigma_t.grad.item()) if sigma_t.grad is not None and sigma_t.grad.size == 1 else (float(sigma_t.grad.sum()) if sigma_t.grad is not None else 0.0)
79
+
80
+ result: dict[str, float] = {
81
+ "price": price_val,
82
+ "delta": delta,
83
+ "vega": vega,
84
+ }
85
+
86
+ if include_second_order:
87
+ so = second_order_greeks(price_fn, S, K, T, r, sigma, q, option_type)
88
+ result["gamma"] = so["gamma"]
89
+ result["vanna"] = so["vanna"]
90
+ result["volga"] = so["volga"]
91
+
92
+ return result
93
+
94
+
95
+
96
+ def compute_greeks_vectorized(
97
+ price_fn: Callable[..., Tensor],
98
+ S_array: np.ndarray,
99
+ K: float,
100
+ T: float,
101
+ r: float,
102
+ sigma: float,
103
+ q: float = 0.0,
104
+ option_type: str = "call",
105
+ ) -> dict[str, np.ndarray]:
106
+ """Compute Greeks for a vector of spot prices.
107
+
108
+ Args:
109
+ price_fn: Pricing function.
110
+ S_array: 1D array of spot prices.
111
+ K, T, r, sigma, q, option_type: Option parameters.
112
+
113
+ Returns:
114
+ Dict with 'price', 'delta', 'vega' as arrays matching S_array.
115
+ """
116
+ S_t = Tensor(np.asarray(S_array, dtype=np.float64), requires_grad=True)
117
+ sigma_t = Tensor(np.array(sigma, dtype=np.float64), requires_grad=True)
118
+
119
+ price = price_fn(S_t, K, T, r, sigma_t, q, option_type)
120
+
121
+ if price.data.size > 1:
122
+ price.sum().backward()
123
+ else:
124
+ price.backward()
125
+
126
+ return {
127
+ "price": price.data.copy(),
128
+ "delta": S_t.grad.copy() if S_t.grad is not None else np.zeros_like(S_array),
129
+ "vega": np.full(
130
+ len(S_array),
131
+ float(sigma_t.grad.item()) if sigma_t.grad is not None else 0.0,
132
+ ),
133
+ }