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.
- tensorquantlib/__init__.py +313 -0
- tensorquantlib/__main__.py +315 -0
- tensorquantlib/backtest/__init__.py +48 -0
- tensorquantlib/backtest/engine.py +240 -0
- tensorquantlib/backtest/metrics.py +320 -0
- tensorquantlib/backtest/strategy.py +348 -0
- tensorquantlib/core/__init__.py +6 -0
- tensorquantlib/core/ops.py +70 -0
- tensorquantlib/core/second_order.py +465 -0
- tensorquantlib/core/tensor.py +928 -0
- tensorquantlib/data/__init__.py +16 -0
- tensorquantlib/data/market.py +160 -0
- tensorquantlib/finance/__init__.py +52 -0
- tensorquantlib/finance/american.py +263 -0
- tensorquantlib/finance/basket.py +291 -0
- tensorquantlib/finance/black_scholes.py +219 -0
- tensorquantlib/finance/credit.py +199 -0
- tensorquantlib/finance/exotics.py +885 -0
- tensorquantlib/finance/fx.py +204 -0
- tensorquantlib/finance/greeks.py +133 -0
- tensorquantlib/finance/heston.py +543 -0
- tensorquantlib/finance/implied_vol.py +277 -0
- tensorquantlib/finance/ir_derivatives.py +203 -0
- tensorquantlib/finance/jump_diffusion.py +203 -0
- tensorquantlib/finance/local_vol.py +146 -0
- tensorquantlib/finance/rates.py +381 -0
- tensorquantlib/finance/risk.py +344 -0
- tensorquantlib/finance/variance_reduction.py +420 -0
- tensorquantlib/finance/volatility.py +355 -0
- tensorquantlib/py.typed +0 -0
- tensorquantlib/tt/__init__.py +43 -0
- tensorquantlib/tt/decompose.py +576 -0
- tensorquantlib/tt/ops.py +386 -0
- tensorquantlib/tt/pricing.py +304 -0
- tensorquantlib/tt/surrogate.py +634 -0
- tensorquantlib/utils/__init__.py +5 -0
- tensorquantlib/utils/validation.py +126 -0
- tensorquantlib/viz/__init__.py +27 -0
- tensorquantlib/viz/plots.py +331 -0
- tensorquantlib-0.3.0.dist-info/METADATA +602 -0
- tensorquantlib-0.3.0.dist-info/RECORD +44 -0
- tensorquantlib-0.3.0.dist-info/WHEEL +5 -0
- tensorquantlib-0.3.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
}
|