Skip to content

Fix FX variance swap fair strike and the realised variance normalisation - #283

Merged
domokane merged 1 commit into
domokane:masterfrom
Cashubski:fix/fx-variance-swap-fair-strike
Sep 30, 2026
Merged

domokane merged 1 commit into
domokane:masterfrom
Cashubski:fix/fx-variance-swap-fair-strike

Conversation

@Cashubski

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #282, which fixed the equity variance swap. The FX class has the same replication code and more.

1. FinFXVarianceSwap.fair_strike cannot run. It prices the replication options with FXVanillaOption(self.maturity_dt, k, put_type), the equity constructor's arguments, so every call raises

TypeError: FXVanillaOption.__init__() missing 3 required positional arguments: 'opt_type', 'notional', and 'prem_currency'

The class has no currency pair to build an FX option with, and does not need one: the replication options are now priced with the Garman–Kohlhagen closed form (european_value with the foreign rate as the dividend yield).

2. The same three defects as the equity version. The log-contract drift used r_d instead of r_d - r_f, the option portfolio was compounded by exp((r_d - r_f) t) instead of exp(r_d t), and a put grid truncated to positive strikes was still looped over with the requested count and could contain a zero strike. In FX the foreign rate is never zero in practice, so the first two bias every fair strike by about 2 r_f.

With the fix, under a flat 12% volatility the fair variance strike is within 0.02% of 0.0144 for (r_d, r_f) in {(3%, 0%), (3%, 5%), (1%, 4%), (4%, 4%)}.

3. Realised variance normalisation (both classes). realised_variance sums the N - 1 squared returns of N prices but divides by N. The market-standard definition divides by the number of returns, so the result was low by a factor (N - 1) / N: eleven prices with a constant 1% daily log return gave 0.02291 instead of 252 × 0.01² = 0.0252. Fewer than two prices now raises FinError.

Tests

New tests/unit/test_FinFXVarianceSwap.py (fair strike equals the flat variance across rate pairs; reduced put grid; realised variance of a constant-return series) and one realised-variance test in test_FinEquityVarianceSwap.py. Full unit suite passes (1,065 tests).

FinFXVarianceSwap.fair_strike could not run: it built FXVanillaOption with
the equity constructor's arguments and raised TypeError, and it had no
tests. It also carried the three defects fixed in the equity version: the
log-contract drift used r_d instead of r_d - r_f, the option portfolio was
compounded by exp((r_d - r_f) T) instead of exp(r_d T), and a put grid
reduced to positive strikes was still looped over with the requested
count and could include a zero strike. Price the replication options with
the Garman-Kohlhagen closed form and apply the same corrections.

realised_variance in both the equity and the FX class summed N - 1 squared
returns but divided by N, the number of prices; divide by the number of
returns.
@domokane
domokane merged commit 11e06c6 into domokane:master Sep 30, 2026
2 checks passed
@domokane

Copy link
Copy Markdown
Owner

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants