Source code for tensorquantlib.finance.fx
"""FX options: Garman-Kohlhagen and Quanto models.
Implements FX-specific option pricing:
- Garman-Kohlhagen (1983): Black-Scholes adapted for FX with foreign rate
- Quanto options: options on foreign assets settled in domestic currency
"""
from __future__ import annotations
import numpy as np
from scipy.stats import norm
# ---------------------------------------------------------------------------
# Garman-Kohlhagen model
# ---------------------------------------------------------------------------
[docs]
def garman_kohlhagen(
S: float,
K: float,
T: float,
r_d: float,
r_f: float,
sigma: float,
option_type: str = "call",
) -> float:
"""Garman-Kohlhagen FX option pricing model.
This is the Black-Scholes formula adapted for FX, where the foreign
risk-free rate acts as a continuous dividend yield.
Parameters
----------
S : float
Spot FX rate (domestic per foreign).
K : float
Strike FX rate.
T : float
Time to expiry.
r_d : float
Domestic risk-free rate.
r_f : float
Foreign risk-free rate.
sigma : float
FX volatility.
option_type : str
'call' or 'put'.
Returns
-------
float
Option price in domestic currency.
"""
d1 = (np.log(S / K) + (r_d - r_f + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == "call":
price = S * np.exp(-r_f * T) * norm.cdf(d1) - K * np.exp(-r_d * T) * norm.cdf(d2)
else:
price = K * np.exp(-r_d * T) * norm.cdf(-d2) - S * np.exp(-r_f * T) * norm.cdf(-d1)
return float(price)
[docs]
def gk_greeks(
S: float,
K: float,
T: float,
r_d: float,
r_f: float,
sigma: float,
option_type: str = "call",
) -> dict[str, float]:
"""Compute Garman-Kohlhagen Greeks.
Returns
-------
dict
{'delta': ..., 'gamma': ..., 'vega': ..., 'theta': ..., 'rho_d': ..., 'rho_f': ...}
"""
sqT = sigma * np.sqrt(T)
d1 = (np.log(S / K) + (r_d - r_f + 0.5 * sigma**2) * T) / sqT
d2 = d1 - sqT
exp_rf = np.exp(-r_f * T)
exp_rd = np.exp(-r_d * T)
# Gamma is the same for calls and puts
gamma = exp_rf * norm.pdf(d1) / (S * sqT)
# Vega is the same for calls and puts
vega = S * exp_rf * norm.pdf(d1) * np.sqrt(T)
if option_type == "call":
delta = exp_rf * norm.cdf(d1)
theta = (
-S * exp_rf * norm.pdf(d1) * sigma / (2.0 * np.sqrt(T))
+ r_f * S * exp_rf * norm.cdf(d1)
- r_d * K * exp_rd * norm.cdf(d2)
)
rho_d = K * T * exp_rd * norm.cdf(d2)
rho_f = -S * T * exp_rf * norm.cdf(d1)
else:
delta = exp_rf * (norm.cdf(d1) - 1.0)
theta = (
-S * exp_rf * norm.pdf(d1) * sigma / (2.0 * np.sqrt(T))
- r_f * S * exp_rf * norm.cdf(-d1)
+ r_d * K * exp_rd * norm.cdf(-d2)
)
rho_d = -K * T * exp_rd * norm.cdf(-d2)
rho_f = S * T * exp_rf * norm.cdf(-d1)
return {
"delta": float(delta),
"gamma": float(gamma),
"vega": float(vega),
"theta": float(theta),
"rho_d": float(rho_d),
"rho_f": float(rho_f),
}
[docs]
def fx_forward(
S: float,
r_d: float,
r_f: float,
T: float,
) -> float:
"""FX forward rate via covered interest rate parity.
F = S * exp((r_d - r_f) * T)
Returns
-------
float
Forward FX rate.
"""
return float(S * np.exp((r_d - r_f) * T))
# ---------------------------------------------------------------------------
# Quanto option
# ---------------------------------------------------------------------------
[docs]
def quanto_option(
S: float,
K: float,
T: float,
r_d: float,
r_f: float,
sigma_s: float,
sigma_fx: float,
rho: float,
fx_rate: float,
option_type: str = "call",
) -> float:
"""Quanto option pricing (option on foreign asset, settled in domestic currency).
The quanto adjustment modifies the drift of the foreign asset due to
the correlation between the asset and the FX rate.
Parameters
----------
S : float
Spot price of the foreign asset (in foreign currency).
K : float
Strike price (in foreign currency).
T : float
Time to expiry.
r_d : float
Domestic risk-free rate.
r_f : float
Foreign risk-free rate.
sigma_s : float
Volatility of the foreign asset.
sigma_fx : float
Volatility of the FX rate.
rho : float
Correlation between the asset and FX rate.
fx_rate : float
Fixed FX rate at which payoff is converted.
option_type : str
'call' or 'put'.
Returns
-------
float
Quanto option price in domestic currency.
"""
# Quanto-adjusted drift rate
r_q = r_d - rho * sigma_s * sigma_fx
d1 = (np.log(S / K) + (r_q + 0.5 * sigma_s**2) * T) / (sigma_s * np.sqrt(T))
d2 = d1 - sigma_s * np.sqrt(T)
if option_type == "call":
price = fx_rate * np.exp(-r_d * T) * (S * np.exp(r_q * T) * norm.cdf(d1) - K * norm.cdf(d2))
else:
price = (
fx_rate * np.exp(-r_d * T) * (K * norm.cdf(-d2) - S * np.exp(r_q * T) * norm.cdf(-d1))
)
return float(price)