financialkit.exotic_options#
Barrier options: Merton’s down-and-out call, the Reiner-Rubinstein closed
forms for all eight single barriers with rebates, Ikeda-Kunitomo double
barriers, the Broadie-Glasserman-Kou correction for discrete monitoring
and Monte Carlo with Brownian-bridge crossing probabilities; binary
options (cash-or-nothing, asset-or-nothing, gap and one-touch); Asian
options (Kemna-Vorst geometric closed forms, arithmetic Monte Carlo with
the geometric control variate, Turnbull-Wakeman moment matching);
floating and fixed lookbacks with bridge-sampled extrema; variance swaps
replicated by a strip of options; Geske compound options, forward
starts and choosers; Margrabe exchange options, Stulz options on the
maximum and minimum and Reiner quantos; and American options by
Longstaff-Schwartz least-squares Monte Carlo. Paths come from
financialkit.integrators and Monte Carlo results from
financialkit.monte_carlo.
financialkit.exotic_options: barrier, binary, Asian, lookback, compound, multi-asset and American options.
Barriers: Merton’s down-and-out call by the method of images, the
Reiner-Rubinstein closed forms for all eight single-barrier options with
rebates, Ikeda-Kunitomo double barriers, the Broadie-Glasserman-Kou
correction for discrete monitoring, and Monte Carlo with Brownian-bridge
crossing probabilities. Binaries: cash-or-nothing, asset-or-nothing, gap
and one-touch options. Asians: Kemna-Vorst geometric closed forms, the
arithmetic average by Monte Carlo with the geometric control variate,
and Turnbull-Wakeman moment matching. Lookbacks: Goldman-Sosin-Gatto
floating and Conze-Viswanathan fixed strikes, with exact bridge-sampled
extrema for Monte Carlo. Variance swaps replicated by a strip of
out-of-the-money options (Demeterfi-Derman-Kamal-Zou). Geske compound
options, Rubinstein forward starts and choosers, Margrabe exchange
options, Stulz options on the maximum or minimum of two assets, Reiner
quantos, and American options by Longstaff-Schwartz least-squares Monte
Carlo. Paths are simulated through financialkit.integrators, and
Monte Carlo results are financialkit.monte_carlo result objects.
- class financialkit.exotic_options.LongstaffSchwartzResult(estimate, std_error, n_samples, confidence=0.95, method='crude', times=<factory>, exercise_boundary=<factory>, exercise_probability=0.0)[source]#
Bases:
MonteCarloResultAn American (Bermudan) option value by least-squares Monte Carlo (Longstaff-Schwartz 2001).
estimateis the mean discounted cash flow of the regression exercise policy andstd_errorits standard error; the policy is fitted on the same paths, so the estimate carries a small in-sample bias.- Parameters:
- class financialkit.exotic_options.VarianceSwapResult(fair_variance, strikes, weights, option_prices, forward, K0, T)[source]#
Bases:
objectThe fair strike of a variance swap from a strip of out-of-the-money options (Demeterfi et al. 1999).
The fair variance is \(\sum_i w_i Q(K_i) - (F/K_0 - 1)^2 / T\), with \(Q\) the out-of-the-money option price at strike \(K_i\) and \(w_i = 2 e^{rT} \Delta K_i / (T K_i^2)\).
- Parameters:
- property contributions: ndarray#
Each option’s share \(w_i Q(K_i)\) of the fair variance.
- Type:
ndarray
- option_prices: ndarray#
Out-of-the-money option prices \(Q(K_i)\) (the average of put and call at \(K_0\)).
- Type:
ndarray, shape (n,)
- financialkit.exotic_options.arithmetic_asian_mc(S, K, T, r, sigma, q=0.0, option_type='call', n_fixings=12, n_paths=50000, control_variate=True, rng=None, confidence=0.95)[source]#
Arithmetic-average option by Monte Carlo, with Kemna and Vorst’s geometric control variate.
The paths come from
financialkit.integrators.gbm_paths()on the fixing grid. The discounted geometric-average payoff on the same paths has the known meangeometric_asian_price()and a correlation with the arithmetic payoff above 0.99, sofinancialkit.monte_carlo.control_variate_estimate()removes almost all the variance.- Parameters:
S (
float) – Spot, strike, maturity, rate and volatility.K (
float) – Spot, strike, maturity, rate and volatility.T (
float) – Spot, strike, maturity, rate and volatility.r (
float) – Spot, strike, maturity, rate and volatility.sigma (
float) – Spot, strike, maturity, rate and volatility.q (
float) – Continuous dividend yield.option_type (
str)n_fixings (
int) – Number of equally spaced fixings \(t_i = iT/n\).n_paths (
int) – Number of paths.control_variate (
bool) – Use the geometric control; otherwise plain Monte Carlo.confidence (
float) – Coverage of the confidence interval.
- Return type:
- Returns:
ControlVariateResult or MonteCarloResult – A
ControlVariateResult(which also reports the crude estimate) with the control, aMonteCarloResultwithout.
Examples
>>> res = arithmetic_asian_mc(100.0, 100.0, 1.0, 0.05, 0.2, n_paths=20_000, rng=0) >>> bool(res.std_error < res.crude_std_error / 10) True
- financialkit.exotic_options.asset_or_nothing_price(S, K, T, r, sigma, q=0.0, option_type='call')[source]#
Asset-or-nothing digital: one share at expiry if the option finishes in the money.
\[c = S e^{-qT} N(d_1), \qquad p = S e^{-qT} N(-d_1).\]- Parameters:
S (
ArrayLike) – Spot, strike, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.19.3: S = 70, K = 65, T = 0.5, r = 7%, b = 2%, sigma = 27%:
>>> round(asset_or_nothing_price(70.0, 65.0, 0.5, 0.07, 0.27, q=0.05, option_type="put"), 4) 20.2069
- financialkit.exotic_options.barrier_crossing_probability(x0, x1, b, sigma, dt)[source]#
Probability that a Brownian bridge from x0 to x1 crosses the level b.
For a log-price with volatility \(\sigma\) over a step \(\Delta t\), with both endpoints on the same side of \(b\),
\[P = \exp\!\left(-\frac{2 (x_0 - b)(x_1 - b)}{\sigma^2 \Delta t}\right);\]it is 1 if either endpoint is on the far side. The drift does not matter once both endpoints are fixed (Glasserman 2004, Sec. 6.4).
- Parameters:
- Return type:
- Returns:
ndarray
Examples
>>> import numpy as np >>> float(barrier_crossing_probability(0.1, 0.1, 0.0, 0.2, 0.25)) 0.1353352832366127
- financialkit.exotic_options.barrier_option_mc(S, K, H, T, r, sigma, q=0.0, option_type='call', barrier_type='down-and-out', n_steps=50, n_paths=100000, continuous=True, rng=None, confidence=0.95)[source]#
Monte Carlo price of a single-barrier option without rebate.
Paths come from
financialkit.integrators.gbm_paths(). Withcontinuous=Falsethe barrier is checked only at the n_steps grid dates, which prices a discretely monitored option exactly. Withcontinuous=Trueeach step’s Brownian-bridge crossing probability \(p_i\) (barrier_crossing_probability()) weights the payoff by the survival probability \(\prod_i (1 - p_i)\), which prices continuous monitoring without discretisation bias (Glasserman 2004, Sec. 6.4).- Parameters:
S (
float) – Spot, strike, barrier, maturity, rate and volatility.K (
float) – Spot, strike, barrier, maturity, rate and volatility.H (
float) – Spot, strike, barrier, maturity, rate and volatility.T (
float) – Spot, strike, barrier, maturity, rate and volatility.r (
float) – Spot, strike, barrier, maturity, rate and volatility.sigma (
float) – Spot, strike, barrier, maturity, rate and volatility.q (
float) – Continuous dividend yield.option_type (
str)barrier_type (
str)n_steps (
int) – Time steps (monitoring dates whencontinuous=False).n_paths (
int) – Number of paths.continuous (
bool) – Continuous monitoring through the bridge correction, or discrete.confidence (
float) – Coverage of the confidence interval.
- Return type:
- Returns:
MonteCarloResult
Examples
>>> res = barrier_option_mc(100.0, 100.0, 90.0, 1.0, 0.05, 0.2, n_steps=20, n_paths=40_000, rng=0) >>> bool(abs(res.estimate - barrier_price(100.0, 100.0, 90.0, 1.0, 0.05, 0.2)) < 4 * res.std_error) True
- financialkit.exotic_options.barrier_price(S, K, H, T, r, sigma, q=0.0, option_type='call', barrier_type='down-and-out', rebate=0.0)[source]#
Reiner-Rubinstein (1991) price of a continuously monitored single-barrier option.
With \(b = r - q\), \(\mu = (b - \sigma^2/2)/\sigma^2\) and \(\lambda = \sqrt{\mu^2 + 2r/\sigma^2}\), every one of the eight calls and puts is a sum of the building blocks \(A, \dots, F\) of Haug (2007, Sec. 4.17.1): \(A\) is the vanilla option, \(B\) the vanilla struck at the barrier, \(C\) and \(D\) their reflections through the barrier, \(E\) a knock-in’s rebate paid at expiry if the barrier is never hit and \(F\) a knock-out’s rebate paid when it is hit. For example, for \(K > H\),
\[C_{do} = A - C + F, \qquad C_{di} = C + E.\]In-out parity holds when the rebate is zero:
knock-in + knock-out = vanilla. If the spot is already through the barrier, a knock-out is worth its rebate and a knock-in the vanilla option.- Parameters:
S (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.H (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)barrier_type (
str)rebate (
ArrayLike) – Cash paid when a knock-out is knocked out, or at expiry when a knock-in never knocks in.
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Table 4-13: S = 100, H = 95, rebate 3, T = 0.5, r = 8%, b = 4%, sigma = 25%:
>>> round(barrier_price(100.0, 90.0, 95.0, 0.5, 0.08, 0.25, q=0.04, barrier_type="down-and-out", rebate=3.0), 4) 9.0246
- financialkit.exotic_options.bivariate_normal_cdf(h, k, rho)[source]#
Standard bivariate normal distribution \(M(h, k; \rho) = P(X \le h, Y \le k)\).
Computed exactly from Owen’s T function (
scipy.special.owens_t()):\[M(h, k; \rho) = \tfrac12 N(h) + \tfrac12 N(k) - T\!\left(h, \frac{k - \rho h}{h \sqrt{1 - \rho^2}}\right) - T\!\left(k, \frac{h - \rho k}{k \sqrt{1 - \rho^2}}\right) - \beta,\]with \(\beta = 0\) when \(h\) and \(k\) have the same sign and \(\tfrac12\) otherwise (Owen 1956). It broadcasts over its inputs, which the compound and rainbow option formulas rely on.
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
>>> import numpy as np >>> bool(abs(bivariate_normal_cdf(0.0, 0.0, 0.5) - (0.25 + np.arcsin(0.5) / (2 * np.pi))) < 1e-12) True
- financialkit.exotic_options.bridge_extremum(log_paths, sigma, dt, kind='max', rng=None)[source]#
Exact samples of each path’s continuous maximum or minimum given its grid values.
Within a step from \(x_0\) to \(x_1\) the maximum of the Brownian bridge is
\[M = \tfrac12\left[x_0 + x_1 + \sqrt{(x_1 - x_0)^2 - 2\sigma^2 \Delta t \ln U}\right], \quad U \sim U(0, 1],\]and the minimum is the mirror image (Glasserman 2004, Sec. 6.4). The path’s extremum is the extremum over its steps.
- Parameters:
- Return type:
- Returns:
ndarray, shape (n_paths,) – The log of each path’s running maximum (or minimum) over the whole interval.
- financialkit.exotic_options.cash_or_nothing_price(S, K, T, r, sigma, q=0.0, option_type='call', cash=1.0)[source]#
Cash-or-nothing digital: cash at expiry if \(S_T > K\) (call) or \(S_T < K\) (put).
\[c = X e^{-rT} N(d_2), \qquad p = X e^{-rT} N(-d_2).\]- Parameters:
S (
ArrayLike) – Spot, strike, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)cash (
ArrayLike) – Amount paid \(X\).
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.19.2: S = 100, K = 80, T = 0.75, r = 6%, b = 0, sigma = 35%, cash 10:
>>> round(cash_or_nothing_price(100.0, 80.0, 0.75, 0.06, 0.35, q=0.06, option_type="put", cash=10.0), 3) 2.671
- financialkit.exotic_options.chooser_price(S, K, t_choose, T, r, sigma, q=0.0)[source]#
Rubinstein (1991) simple chooser: at \(t\) the holder picks a call or a put, both struck at \(K\) and expiring at \(T\).
At \(t\) the holder takes \(\max(c, p) = c + (p - c)^+\), and by put-call parity \(p - c = K e^{-r(T-t)} - S_t e^{-q(T-t)}\), so
\[V = c(S, K, T) + e^{-q(T-t)}\, p\big(S, K e^{-(r-q)(T-t)}, t\big).\]- Parameters:
S (
ArrayLike) – Spot and strike.K (
ArrayLike) – Spot and strike.t_choose (
ArrayLike) – Choice date and expiry,t_choose <= T.T (
ArrayLike) – Choice date and expiry,t_choose <= T.r (
ArrayLike) – Rate and volatility.sigma (
ArrayLike) – Rate and volatility.q (
ArrayLike) – Continuous dividend yield.
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.4.1: S = 50, K = 50, t = 0.25, T = 0.5, r = 8%, b = 8%, sigma = 25%:
>>> round(chooser_price(50.0, 50.0, 0.25, 0.5, 0.08, 0.25), 4) 6.1071
- financialkit.exotic_options.compound_option_price(S, K1, K2, T1, T2, r, sigma, q=0.0, option_type='call', underlying_type='call')[source]#
Geske (1979) compound option: an option expiring at \(T_1\) with strike \(K_1\) on an option to \(T_2\) struck at \(K_2\).
With \(I\) the critical spot (
critical_price()), \(\rho = \sqrt{T_1/T_2}\), \(y_{1,2}\) the Black-Scholes \(d_{1,2}\) for strike \(I\) and horizon \(T_1\), and \(z_{1,2}\) those for \(K_2\) and \(T_2\), the call on a call is\[S e^{-q T_2} M(z_1, y_1; \rho) - K_2 e^{-r T_2} M(z_2, y_2; \rho) - K_1 e^{-r T_1} N(y_2),\]and the other three combinations follow by flipping signs (Haug 2007, Sec. 4.6). They satisfy the compound put-call parity
call on V - put on V = V - K1 exp(-r T1).- Parameters:
S (
ArrayLike) – Spot.K1 (
float) – Strikes of the compound option and of the underlying option.K2 (
float) – Strikes of the compound option and of the underlying option.T1 (
float) – Expiries of the compound option and of the underlying,T1 < T2.T2 (
float) – Expiries of the compound option and of the underlying,T1 < T2.r (
float) – Rate and volatility.sigma (
float) – Rate and volatility.q (
float) – Continuous dividend yield.option_type (
str) – The compound option.underlying_type (
str) – The option it is written on.
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.6: a put on a call with S = 500, K1 = 50, K2 = 520, T1 = 0.25, T2 = 0.5, r = 8%, q = 3%, sigma = 35%:
>>> round(compound_option_price(500.0, 50.0, 520.0, 0.25, 0.5, 0.08, 0.35, q=0.03, option_type="put"), 3) 21.196
- financialkit.exotic_options.critical_price(K1, K2, tau, r, sigma, q=0.0, underlying_type='call')[source]#
Spot \(I\) at which the underlying option is worth the compound strike: \(V(I, K_2, \tau) = K_1\).
Solved by
scipy.optimize.brentq()on the Black-Scholes-Merton price of the underlying option with \(\tau = T_2 - T_1\) to run.- Parameters:
K1 (
float) – Strike of the compound option (the price paid for the underlying option).K2 (
float) – Strike of the underlying option.tau (
float) – Remaining life of the underlying option at \(T_1\), rate and volatility.r (
float) – Remaining life of the underlying option at \(T_1\), rate and volatility.sigma (
float) – Remaining life of the underlying option at \(T_1\), rate and volatility.q (
float) – Continuous dividend yield.underlying_type (
str)
- Return type:
- Returns:
float
Examples
>>> from financialkit.options import black_scholes_price >>> I = critical_price(5.0, 100.0, 0.5, 0.05, 0.2) >>> round(black_scholes_price(I, 100.0, 0.5, 0.05, 0.2), 10) 5.0
- financialkit.exotic_options.discrete_barrier_price(S, K, H, T, r, sigma, q=0.0, option_type='call', barrier_type='down-and-out', n_monitor=252, rebate=0.0)[source]#
Broadie-Glasserman-Kou (1997) price of a barrier monitored at n_monitor equally spaced dates.
A path can cross a discretely monitored barrier between two dates without being caught, so the option behaves like a continuously monitored one with the barrier moved away from the spot:
\[H \to H e^{\pm \beta \sigma \sqrt{\Delta t}}, \qquad \beta = -\zeta(1/2)/\sqrt{2\pi} \approx 0.5826,\]with
+for an up barrier and-for a down barrier. The error is \(o(1/\sqrt{m})\), against \(O(1/\sqrt{m})\) for ignoring the difference.- Parameters:
S (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.H (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, barrier, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)barrier_type (
str)n_monitor (
int) – Number of monitoring dates \(m\), the last at expiry.rebate (
ArrayLike) – Cash rebate, as inbarrier_price().
- Return type:
- Returns:
float or ndarray
Examples
Daily monitoring is worth more to the holder of a knock-out than continuous monitoring:
>>> args = (100.0, 100.0, 85.0, 1.0, 0.05, 0.3) >>> bool(discrete_barrier_price(*args, n_monitor=252) > barrier_price(*args)) True
- financialkit.exotic_options.double_barrier_price(S, K, L, U, T, r, sigma, q=0.0, option_type='call', knock='out', n_terms=5)[source]#
Ikeda-Kunitomo (1992) double-barrier option with flat barriers \(L < S < U\).
The knock-out call is a series of images reflected alternately through both barriers (Haug 2007, Sec. 4.17.3, with zero curvature):
\[c = S e^{(b - r)T} \sum_{n=-N}^{N} \left[\left(\tfrac{U^n}{L^n}\right)^{\mu} (N(d_1) - N(d_2)) - \left(\tfrac{L^{n+1}}{U^n S}\right)^{\mu} (N(d_3) - N(d_4))\right] - K e^{-rT} \sum_{n=-N}^{N} [\dots]_{\mu - 2,\; d_i - \sigma\sqrt T},\]with \(\mu = 2b/\sigma^2 + 1\). The terms decay like \(e^{-2n^2 \ln^2(U/L)/\sigma^2 T}\), so a handful suffice. The knock-in is the vanilla option minus the knock-out.
- Parameters:
S (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.L (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.U (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, lower and upper barriers, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)knock (
str)n_terms (
int) – The series runs over \(n = -N, \dots, N\).
- Return type:
- Returns:
float or ndarray
Examples
Far-away barriers leave the vanilla option:
>>> from financialkit.options import black_scholes_price >>> bool(abs(double_barrier_price(100.0, 100.0, 20.0, 500.0, 0.5, 0.05, 0.2) - black_scholes_price(100.0, 100.0, 0.5, 0.05, 0.2)) < 1e-8) True
- financialkit.exotic_options.fixed_lookback_price(S, K, T, r, sigma, q=0.0, option_type='call', S_extreme=None)[source]#
Conze-Viswanathan (1991) fixed-strike lookback: \((\max S - K)^+\) (call) or \((K - \min S)^+\) (put).
For the call with \(K \ge S_{\max}\),
\[c = S e^{(b-r)T} N(d_1) - K e^{-rT} N(d_2) + S e^{-rT} \frac{\sigma^2}{2b}\left[-\left(\frac{S}{K}\right)^{-2b/\sigma^2} N\!\left(d_1 - \frac{2b\sqrt T}{\sigma}\right) + e^{bT} N(d_1)\right];\]for \(K < S_{\max}\) the call locks in \(e^{-rT}(S_{\max} - K)\) and adds the same expression with \(S_{\max}\) in place of \(K\). The put mirrors it in the minimum.
- Parameters:
S (
ArrayLike) – Spot, strike, remaining maturity, rate and volatility.K (
ArrayLike) – Spot, strike, remaining maturity, rate and volatility.T (
ArrayLike) – Spot, strike, remaining maturity, rate and volatility.r (
ArrayLike) – Spot, strike, remaining maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, remaining maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)S_extreme (
ArrayLike|None) – Maximum (call) or minimum (put) observed so far; defaults to S.
- Return type:
- Returns:
float or ndarray
Examples
A lookback call is worth more than the vanilla call:
>>> from financialkit.options import black_scholes_price >>> bool(fixed_lookback_price(100.0, 100.0, 1.0, 0.05, 0.2) > black_scholes_price(100.0, 100.0, 1.0, 0.05, 0.2)) True
- financialkit.exotic_options.floating_lookback_price(S, T, r, sigma, q=0.0, option_type='call', S_extreme=None)[source]#
Goldman-Sosin-Gatto (1979) floating-strike lookback: \(S_T - \min S\) (call) or \(\max S - S_T\) (put).
With \(a_1 = [\ln(S/S_{\min}) + (b + \sigma^2/2)T]/(\sigma\sqrt T)\), \(a_2 = a_1 - \sigma\sqrt T\) and \(b = r - q\), the call is
\[c = S e^{(b-r)T} N(a_1) - S_{\min} e^{-rT} N(a_2) + S e^{-rT} \frac{\sigma^2}{2b}\left[\left(\frac{S}{S_{\min}}\right)^{-2b/\sigma^2} N\!\left(-a_1 + \frac{2b\sqrt T}{\sigma}\right) - e^{bT} N(-a_1)\right],\]and the put is its mirror image in the maximum.
- Parameters:
S (
ArrayLike) – Spot, remaining maturity, rate and volatility.T (
ArrayLike) – Spot, remaining maturity, rate and volatility.r (
ArrayLike) – Spot, remaining maturity, rate and volatility.sigma (
ArrayLike) – Spot, remaining maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)S_extreme (
ArrayLike|None) – Minimum (call) or maximum (put) observed so far; defaults to S for a newly written option.
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.15.1: S = 120, S_min = 100, T = 0.5, r = 10%, b = 4%, sigma = 30%:
>>> round(floating_lookback_price(120.0, 0.5, 0.10, 0.30, q=0.06, S_extreme=100.0), 3) 25.353
- financialkit.exotic_options.forward_start_price(S, alpha, t_start, T, r, sigma, q=0.0, option_type='call')[source]#
Rubinstein (1990) forward-start option: struck at \(\alpha S_{t}\) at the future date \(t\).
At time \(t\) the option is worth \(S_t\) times a Black-Scholes-Merton option on a unit spot with strike \(\alpha\), and the risk-neutral value of \(S_t\) is \(S e^{-qt}\):
\[V = S e^{-q t}\, \mathrm{BS}(1, \alpha, T - t, r, q, \sigma).\]The value does not depend on the path before \(t\), which is why employee stock options granted at the money in the future and the legs of a cliquet are priced this way.
- Parameters:
S (
ArrayLike) – Spot.alpha (
ArrayLike) – Strike as a fraction of the spot at the start date (1 for at the money).t_start (
ArrayLike) – Start date and expiry,t_start < T.T (
ArrayLike) – Start date and expiry,t_start < T.r (
ArrayLike) – Rate and volatility.sigma (
ArrayLike) – Rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.3: S = 60, alpha = 1.1, t = 0.25, T = 1, r = 8%, b = 4%, sigma = 30%:
>>> round(forward_start_price(60.0, 1.1, 0.25, 1.0, 0.08, 0.30, q=0.04), 3) 4.406
- financialkit.exotic_options.gap_option_price(S, K1, K2, T, r, sigma, q=0.0, option_type='call')[source]#
Gap option: pays \(S_T - K_2\) if \(S_T > K_1\) (call), \(K_2 - S_T\) if \(S_T < K_1\) (put).
It is an asset-or-nothing option minus \(K_2\) cash-or-nothing options, both triggered at \(K_1\):
\[c = S e^{-qT} N(d_1) - K_2 e^{-rT} N(d_2), \quad d_{1,2} \text{ at } K_1.\]- Parameters:
S (
ArrayLike) – Spot.K1 (
ArrayLike) – Trigger and payoff strikes.K2 (
ArrayLike) – Trigger and payoff strikes.T (
ArrayLike) – Maturity, rate and volatility.r (
ArrayLike) – Maturity, rate and volatility.sigma (
ArrayLike) – Maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.19.1: S = 50, K1 = 50, K2 = 57, T = 0.5, r = 9%, b = 9%, sigma = 20%; the value is negative:
>>> round(gap_option_price(50.0, 50.0, 57.0, 0.5, 0.09, 0.20), 4) -0.0053
- financialkit.exotic_options.geometric_asian_price(S, K, T, r, sigma, q=0.0, option_type='call', n_fixings=None)[source]#
Kemna-Vorst (1990) price of an option on the geometric average \(G\).
\(\ln G\) is normal with mean \(m\) and variance \(v\), so the option is Black’s formula on the forward \(e^{m + v/2}\) with volatility \(\sqrt{v/T}\). For continuous averaging \(m = \ln S + (b - \sigma^2/2) T/2\) and \(v = \sigma^2 T/3\); for \(n\) equally spaced fixings
\[m = \ln S + (b - \tfrac{\sigma^2}{2}) \frac{(n+1) T}{2n}, \qquad v = \sigma^2 T \frac{(n+1)(2n+1)}{6 n^2}.\]- Parameters:
S (
ArrayLike) – Spot, strike, maturity, rate and volatility.K (
ArrayLike) – Spot, strike, maturity, rate and volatility.T (
ArrayLike) – Spot, strike, maturity, rate and volatility.r (
ArrayLike) – Spot, strike, maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.option_type (
str)n_fixings (
int|None) – Number of fixings;Nonefor continuous averaging.
- Return type:
- Returns:
float or ndarray
Examples
Haug (2007), Section 4.20.1: S = 80, K = 85, T = 0.25, r = 5%, b = 8%, sigma = 20%:
>>> round(geometric_asian_price(80.0, 85.0, 0.25, 0.05, 0.20, q=-0.03, option_type="put"), 4) 4.6922
- financialkit.exotic_options.longstaff_schwartz(S, K, T, r, sigma, q=0.0, option_type='put', n_exercise=50, n_paths=50000, degree=3, basis='laguerre', antithetic=True, rng=None, confidence=0.95)[source]#
American (Bermudan) option value by least-squares Monte Carlo (Longstaff-Schwartz 2001).
Exercise is allowed on n_exercise equally spaced dates up to \(T\). At each date \(t_i\), working backward, the discounted future cash flow \(Y\) of each in-the-money path is regressed on
regression_basis()of \(S_{t_i}/K\), and the path is exercised where the payoff exceeds the fitted \(E[Y \mid S_{t_i}]\). The paths come fromfinancialkit.integrators.gbm_paths(), optionally with antithetic increments.- Parameters:
S (
float) – Spot, strike, maturity, rate and volatility.K (
float) – Spot, strike, maturity, rate and volatility.T (
float) – Spot, strike, maturity, rate and volatility.r (
float) – Spot, strike, maturity, rate and volatility.sigma (
float) – Spot, strike, maturity, rate and volatility.q (
float) – Continuous dividend yield.option_type (
str)n_exercise (
int) – Number of exercise dates (time steps).n_paths (
int) – Number of paths (rounded up to even with antithetic).degree (
int) – Number of non-constant regressors.basis (
str)antithetic (
bool) – Simulate antithetic pairs; the standard error then uses pair means.confidence (
float) – Coverage of the confidence interval.
- Return type:
- Returns:
LongstaffSchwartzResult
Examples
Longstaff and Schwartz (2001), Table 1, first row: S = 36, K = 40, r = 6%, sigma = 20%, T = 1, 50 exercise dates; the finite-difference value is 4.478:
>>> res = longstaff_schwartz(36.0, 40.0, 1.0, 0.06, 0.2, n_paths=20_000, rng=0) >>> bool(abs(res.estimate - 4.478) < 4 * res.std_error + 0.02) True
- financialkit.exotic_options.lookback_option_mc(S, T, r, sigma, q=0.0, option_type='call', K=None, n_steps=50, n_paths=100000, continuous=True, rng=None, confidence=0.95)[source]#
Monte Carlo price of a newly written lookback, floating (
K=None) or fixed strike.Paths come from
financialkit.integrators.gbm_paths(). Withcontinuous=Truethe path extremum is sampled exactly from the Brownian bridge in each step (bridge_extremum()), so the estimator is unbiased for continuous monitoring at any step size; withcontinuous=Falseit is the extremum over the grid dates, which prices a discretely monitored lookback.- Parameters:
S (
float) – Spot, maturity, rate and volatility.T (
float) – Spot, maturity, rate and volatility.r (
float) – Spot, maturity, rate and volatility.sigma (
float) – Spot, maturity, rate and volatility.q (
float) – Continuous dividend yield.option_type (
str)K (
float|None) – Fixed strike;Nonefor a floating-strike lookback.n_steps (
int) – Time steps (monitoring dates whencontinuous=False).n_paths (
int) – Number of paths.continuous (
bool) – Continuous monitoring through the bridge, or discrete.confidence (
float) – Coverage of the confidence interval.
- Return type:
- Returns:
MonteCarloResult
Examples
>>> res = lookback_option_mc(100.0, 1.0, 0.05, 0.2, n_steps=10, n_paths=40_000, rng=0) >>> bool(abs(res.estimate - floating_lookback_price(100.0, 1.0, 0.05, 0.2)) < 4 * res.std_error) True
- financialkit.exotic_options.margrabe_price(S1, S2, T, sigma1, sigma2, rho, q1=0.0, q2=0.0)[source]#
Margrabe (1978) value of the right to receive asset 1 in exchange for asset 2 at \(T\): \((S_1 - S_2)^+\).
Measured in units of asset 2 the payoff is a call on \(S_1/S_2\) struck at 1, and the ratio has volatility \(\sigma = \sqrt{\sigma_1^2 + \sigma_2^2 - 2\rho\sigma_1\sigma_2}\):
\[V = S_1 e^{-q_1 T} N(d_1) - S_2 e^{-q_2 T} N(d_2), \qquad d_{1,2} = \frac{\ln(S_1 e^{-q_1 T} / S_2 e^{-q_2 T}) \pm \sigma^2 T/2}{\sigma\sqrt T}.\]The interest rate drops out. This is
financialkit.options.black_scholes_price()with spot \(S_1 e^{-q_1 T}\), strike \(S_2 e^{-q_2 T}\) and zero rate.- Parameters:
S1 (
ArrayLike) – Prices of the asset received and the asset given up.S2 (
ArrayLike) – Prices of the asset received and the asset given up.T (
ArrayLike) – Maturity.sigma1 (
ArrayLike) – Volatilities.sigma2 (
ArrayLike) – Volatilities.rho (
ArrayLike) – Correlation of the two assets’ returns.q1 (
ArrayLike) – Continuous dividend yields.q2 (
ArrayLike) – Continuous dividend yields.
- Return type:
- Returns:
float or ndarray
Examples
When asset 2 is riskless cash growing at the rate \(r\) (zero volatility, “dividend yield” \(r\)) the exchange option is the Black-Scholes call:
>>> from financialkit.options import black_scholes_price >>> m = margrabe_price(42.0, 40.0, 0.5, 0.2, 0.0, 0.0, q2=0.10) >>> bool(abs(m - black_scholes_price(42.0, 40.0, 0.5, 0.10, 0.2)) < 1e-12) True
- financialkit.exotic_options.merton_down_and_out_call(S, K, H, T, r, sigma, q=0.0)[source]#
Merton’s (1973) down-and-out call with the barrier at or below the strike.
The barrier is an absorbing boundary for the log-price, and the method of images subtracts a call started from the reflected spot \(H^2 / S\):
\[C_{do} = c(S, K) - \left(\frac{H}{S}\right)^{2b/\sigma^2 - 1} c\!\left(\frac{H^2}{S}, K\right), \qquad b = r - q,\]with \(c\) the Black-Scholes-Merton call. It is zero once \(S \le H\).
- Parameters:
S (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.K (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.H (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.T (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.r (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.sigma (
ArrayLike) – Spot, strike, barrier (H <= K), maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.
- Return type:
- Returns:
float or ndarray
Examples
A barrier far below the spot hardly matters:
>>> from financialkit.options import black_scholes_price >>> bool(abs(merton_down_and_out_call(100.0, 100.0, 50.0, 1.0, 0.05, 0.2) - black_scholes_price(100.0, 100.0, 1.0, 0.05, 0.2)) < 1e-6) True
- financialkit.exotic_options.one_touch_price(S, H, T, r, sigma, q=0.0, cash=1.0)[source]#
One-touch: cash paid the first time the price touches \(H\) before \(T\).
The barrier is below the spot for \(H < S\) and above it otherwise. With \(\mu = (b - \sigma^2/2)/\sigma^2\), \(\lambda = \sqrt{\mu^2 + 2r/\sigma^2}\) and \(\eta = \pm 1\) for a down or up barrier,
\[V = X\left[\left(\tfrac{H}{S}\right)^{\mu + \lambda} N(\eta z) + \left(\tfrac{H}{S}\right)^{\mu - \lambda} N(\eta z - 2\eta\lambda\sigma\sqrt T)\right], \quad z = \frac{\ln(H/S)}{\sigma\sqrt T} + \lambda\sigma\sqrt T,\]the rebate term of a knock-out barrier option (Reiner and Rubinstein 1991). As \(T \to \infty\) it tends to the perpetual value \(X (H/S)^{\mu + \eta\lambda}\).
- Parameters:
S (
ArrayLike) – Spot, barrier, maturity, rate and volatility.H (
ArrayLike) – Spot, barrier, maturity, rate and volatility.T (
ArrayLike) – Spot, barrier, maturity, rate and volatility.r (
ArrayLike) – Spot, barrier, maturity, rate and volatility.sigma (
ArrayLike) – Spot, barrier, maturity, rate and volatility.q (
ArrayLike) – Continuous dividend yield.cash (
ArrayLike) – Amount paid at the touch.
- Return type:
- Returns:
float or ndarray
Examples
A barrier at the spot is touched at once:
>>> one_touch_price(100.0, 100.0, 1.0, 0.05, 0.2) 1.0
- financialkit.exotic_options.parse_barrier_type(barrier_type)[source]#
Split
"down-and-out"and friends into(is_down, is_out).Examples
>>> parse_barrier_type("up-and-in") (False, False)
- financialkit.exotic_options.plot_barrier_paths(times, paths, barriers, ax=None, max_paths=40)[source]#
Simulated price paths, red where they touch a barrier on the grid and blue where they survive.
- financialkit.exotic_options.plot_exercise_boundary(result, ax=None, label='Longstaff-Schwartz')[source]#
The early-exercise boundary of a
LongstaffSchwartzResult.- Parameters:
result (
LongstaffSchwartzResult)ax (matplotlib.axes.Axes, optional)
label (
str) – Legend label.
- Returns:
matplotlib.axes.Axes
- financialkit.exotic_options.plot_price_profile(x, curves, ax=None, xlabel='spot S', ylabel='option value', markers=())[source]#
Several pricing curves over a common axis, with dashed vertical markers (strikes, barriers).
- Parameters:
x (
ArrayLike) – Common abscissa (usually the spot).curves (
Mapping[str,ArrayLike]) – Label and values of each curve.ax (matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
xlabel (
str) – Axis labels.ylabel (
str) – Axis labels.markers (
Sequence[float]) – Positions of dashed vertical lines.
- Returns:
matplotlib.axes.Axes
- financialkit.exotic_options.plot_variance_strip(result, ax=None)[source]#
Each option’s contribution \(w_i Q(K_i)\) to a variance swap’s fair strike, puts left of \(K_0\) and calls right.
- Parameters:
result (
VarianceSwapResult)ax (matplotlib.axes.Axes, optional)
- Returns:
matplotlib.axes.Axes
- financialkit.exotic_options.quanto_price(S, K, T, r_d, r_f, sigma_S, sigma_X, rho, q=0.0, fixed_rate=1.0, option_type='call')[source]#
Reiner (1992) quanto option: a foreign-currency payoff \((S_T - K)^+\) paid in domestic currency at a fixed rate \(\bar X\).
Under the domestic risk-neutral measure the foreign asset drifts at \(r_f - q - \rho\sigma_S\sigma_X\), where \(\rho\) is the correlation between the asset and the exchange rate (domestic units per foreign unit). The quanto is Black’s formula on the adjusted forward, discounted domestically:
\[V = \bar X e^{-r_d T}\left[\hat F N(d_1) - K N(d_2)\right], \qquad \hat F = S e^{(r_f - q - \rho\sigma_S\sigma_X)T}.\]- Parameters:
S (
ArrayLike) – Foreign asset price and strike, in foreign currency.K (
ArrayLike) – Foreign asset price and strike, in foreign currency.T (
ArrayLike) – Maturity.r_d (
ArrayLike) – Domestic and foreign rates.r_f (
ArrayLike) – Domestic and foreign rates.sigma_S (
ArrayLike) – Volatilities of the asset and of the exchange rate.sigma_X (
ArrayLike) – Volatilities of the asset and of the exchange rate.rho (
ArrayLike) – Correlation between the asset and the exchange rate.q (
ArrayLike) – Continuous dividend yield of the asset.fixed_rate (
ArrayLike) – Fixed conversion rate \(\bar X\), domestic per foreign.option_type (
str)
- Return type:
- Returns:
float or ndarray
Examples
With no correlation the quanto is the foreign option at the fixed rate, re-discounted at the domestic rate:
>>> import numpy as np >>> from financialkit.options import black_scholes_price >>> v = quanto_price(100.0, 105.0, 0.5, 0.08, 0.05, 0.2, 0.1, 0.0, q=0.04, fixed_rate=1.5) >>> bool(abs(v - 1.5 * np.exp(-0.03 * 0.5) * black_scholes_price(100.0, 105.0, 0.5, 0.05, 0.2, q=0.04)) < 1e-12) True
- financialkit.exotic_options.realized_variance(paths, T)[source]#
Annualized realized variance \(\frac{1}{T}\sum_i \ln^2(S_{t_{i+1}}/S_{t_i})\) of each path.
- Parameters:
- Return type:
- Returns:
ndarray, shape (n_paths,) – A 0-d array for a single path.
Examples
>>> import numpy as np >>> round(float(realized_variance(100.0 * np.exp([0.0, 0.1, 0.0]), 0.5)), 10) 0.04
- financialkit.exotic_options.regression_basis(x, degree=3, basis='laguerre')[source]#
Regressors for the continuation value: a constant and degree basis functions of x.
- Parameters:
- Return type:
- Returns:
ndarray, shape (n, degree + 1)
Examples
>>> regression_basis([0.0, 1.0], degree=2).round(4).tolist() [[1.0, 1.0, 1.0], [1.0, 0.6065, 0.0]]
- financialkit.exotic_options.stulz_price(S1, S2, K, T, r, sigma1, sigma2, rho, q1=0.0, q2=0.0, option_type='call', kind='max')[source]#
Stulz (1982) option on the maximum or minimum of two assets: \((\max(S_1, S_2) - K)^+\) and its relatives.
With \(\sigma\) the ratio volatility, \(\rho_1 = (\sigma_1 - \rho\sigma_2)/\sigma\), \(\rho_2 = (\sigma_2 - \rho\sigma_1)/\sigma\), \(d = [\ln(S_1/S_2) + (b_1 - b_2 + \sigma^2/2)T]/(\sigma\sqrt T)\) and \(y_i = [\ln(S_i/K) + (b_i + \sigma_i^2/2)T]/(\sigma_i\sqrt T)\), the call on the minimum is
\[S_1 e^{-q_1 T} M(y_1, -d; -\rho_1) + S_2 e^{-q_2 T} M(y_2, d - \sigma\sqrt T; -\rho_2) - K e^{-rT} M(y_1 - \sigma_1\sqrt T, y_2 - \sigma_2\sqrt T; \rho),\]the call on the maximum is its counterpart (Haug 2007, Sec. 4.8.4), and the puts follow from parity with the zero-strike call, which is a Margrabe exchange option. Since \(\max + \min = S_1 + S_2\), the calls on the maximum and the minimum add up to the two vanilla calls.
- Parameters:
S1 (
ArrayLike) – Spots, strike, maturity and rate.S2 (
ArrayLike) – Spots, strike, maturity and rate.K (
ArrayLike) – Spots, strike, maturity and rate.T (
ArrayLike) – Spots, strike, maturity and rate.r (
ArrayLike) – Spots, strike, maturity and rate.sigma1 (
ArrayLike) – Volatilities.sigma2 (
ArrayLike) – Volatilities.rho (
ArrayLike) – Correlation, \(|\rho| < 1\).q1 (
ArrayLike) – Continuous dividend yields.q2 (
ArrayLike) – Continuous dividend yields.option_type (
str)kind (
str)
- Return type:
- Returns:
float or ndarray
Examples
>>> from financialkit.options import black_scholes_price >>> args = (100.0, 105.0, 98.0, 0.5, 0.05, 0.11, 0.16, 0.63) >>> total = stulz_price(*args, kind="max") + stulz_price(*args, kind="min") >>> vanilla = black_scholes_price(100.0, 98.0, 0.5, 0.05, 0.11) + black_scholes_price(105.0, 98.0, 0.5, 0.05, 0.16) >>> bool(abs(total - vanilla) < 1e-10) True
- financialkit.exotic_options.turnbull_wakeman_price(S, K, T, r, sigma, q=0.0, option_type='call', n_fixings=None)[source]#
Turnbull-Wakeman (1991) moment-matching approximation of an arithmetic-average option.
The arithmetic average \(A\) is replaced by a lognormal with the same first two moments \(M_1 = E[A]\), \(M_2 = E[A^2]\), and the option is Black’s formula on the forward \(M_1\) with volatility
\[\sigma_A^2 = \frac{1}{T} \ln \frac{M_2}{M_1^2}.\]For continuous averaging (Turnbull and Wakeman) \(M_1 = S (e^{bT} - 1)/(bT)\) and \(M_2\) is the double integral of \(E[S_t S_u]\); for discrete fixings the moments are Levy’s (1992) exact sums \(M_1 = \tfrac{S}{n}\sum_i e^{b t_i}\), \(M_2 = \tfrac{S^2}{n^2}\sum_{i,j} e^{b(t_i + t_j) + \sigma^2 \min(t_i, t_j)}\). The approximation is excellent at moderate volatility and drifts as \(\sigma^2 T\) grows.
- Parameters:
S (
ArrayLike) – Spot, strike, maturity, rate and volatility; with discrete fixings T, r, sigma and q must be scalars.K (
ArrayLike) – Spot, strike, maturity, rate and volatility; with discrete fixings T, r, sigma and q must be scalars.T (
ArrayLike) – Spot, strike, maturity, rate and volatility; with discrete fixings T, r, sigma and q must be scalars.r (
ArrayLike) – Spot, strike, maturity, rate and volatility; with discrete fixings T, r, sigma and q must be scalars.sigma (
ArrayLike) – Spot, strike, maturity, rate and volatility; with discrete fixings T, r, sigma and q must be scalars.q (
ArrayLike) – Continuous dividend yield.option_type (
str)n_fixings (
int|None) – Number of equally spaced fixings;Nonefor continuous averaging.
- Return type:
- Returns:
float or ndarray
Examples
A call struck at zero is worth the discounted mean of the average, which the approximation matches exactly:
>>> import numpy as np >>> c = turnbull_wakeman_price(100.0, 1e-8, 1.0, 0.05, 0.3) >>> bool(abs(c - 100.0 * np.exp(-0.05) * np.expm1(0.05) / 0.05) < 1e-6) True
- financialkit.exotic_options.variance_swap_strike(strikes, call_prices, put_prices, S, T, r, q=0.0)[source]#
Fair variance strike from out-of-the-money option prices (Demeterfi, Derman, Kamal and Zou 1999).
With \(K_0\) the largest strike at or below the forward \(F = S e^{(r-q)T}\), puts are used below \(K_0\), calls above and their average at \(K_0\), and
\[K_{\mathrm{var}} = \frac{2 e^{rT}}{T} \sum_i \frac{\Delta K_i}{K_i^2} Q(K_i) - \frac{1}{T}\left(\frac{F}{K_0} - 1\right)^2,\]with \(\Delta K_i\) half the distance between the neighbouring strikes. The last term is the second-order expansion of the forward adjustment above. The strike range truncates the integrals, so the strip should reach well into both wings.
- Parameters:
strikes (
ArrayLike) – Increasing strikes.call_prices (
ArrayLike) – Call and put prices at those strikes.put_prices (
ArrayLike) – Call and put prices at those strikes.S (
float) – Spot, maturity and rate.T (
float) – Spot, maturity and rate.r (
float) – Spot, maturity and rate.q (
float) – Continuous dividend yield.
- Return type:
- Returns:
VarianceSwapResult
Examples
Under Black-Scholes the fair variance is \(\sigma^2\):
>>> import numpy as np >>> from financialkit.options import black_scholes_price >>> K = np.arange(20.0, 400.0, 1.0) >>> c, p = (black_scholes_price(100.0, K, 1.0, 0.05, 0.2, option_type=t) for t in ("call", "put")) >>> round(variance_swap_strike(K, c, p, 100.0, 1.0, 0.05).vol_strike, 4) 0.2