Skip to content

Commit 73880ac

Browse files
authored
Fix calibration (#59)
1 parent 4f60b95 commit 73880ac

32 files changed

Lines changed: 394 additions & 2202 deletions

.github/copilot-instructions.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,13 @@ The release procedure is fully driven by `make release` and the `release.yml` wo
5757
5. The tag push triggers `.github/workflows/release.yml`, which runs lint and tests, publishes the package to PyPI (`make publish`), and posts the extracted `## vX.Y.Z` section as the GitHub Release body.
5858

5959
Do not publish to PyPI manually or via the old `head_commit.message == 'release'` flow — the tag-triggered workflow is the only supported path.
60+
61+
### Release-notes conventions
62+
63+
The `## vX.Y.Z` section in `docs/release-notes.md` must follow these conventions, since the same content is rendered both on the docs site and as the GitHub Release body:
64+
65+
* Open with a one-paragraph summary describing the theme of the release. If the release contains breaking changes, point readers to the **Breaking changes** section in that paragraph.
66+
* Group entries under H3 subsections in this order: `### Breaking changes`, `### New features`, `### Improvements and fixes`, `### Documentation and assets`. Omit any subsection that has no entries.
67+
* Every PR reference must be a markdown link of the form `[#NN](https://github.com/quantmind/quantflow/pull/NN)`. Never write a bare `(#NN)` — both readers (docs and GitHub) benefit from the explicit URL, and GitHub's auto-linking only works in some contexts. When a single entry references multiple PRs, list them comma-separated inside one set of parentheses, each as its own markdown link.
68+
* Build the PR list by running `git log vPREV..HEAD --oneline` against the previous release tag and following each squashed-merge commit back to its PR. Cross-check with `gh pr list --state merged --base main` for any PRs merged since the previous tag.
69+
* End the section with a `[Full changelog](https://github.com/quantmind/quantflow/compare/vPREV...vX.Y.Z)` link comparing the new tag against the previous one.

docs/api/options/black.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# Black Pricing
22

3-
Here we define the log strike `k` as
4-
$$
3+
Here we define the [log strike](../../glossary.md#log-strike) `k` as
4+
5+
\begin{equation}
56
k = \log{\frac{K}{F}}
6-
$$
7+
\end{equation}
78

89
where $K$ is the strike price and $F$ is the forward price of the underlying asset.
9-
We also refers to this log-strike as `moneyness`, since it is zero for at-the-money (ATM) options,
10-
negative for in-the-money (ITM) call options, and positive for out-of-the-money (OTM) call options.
1110

1211

1312
::: quantflow.options.bs.black_price
59.3 KB
Loading

docs/examples/pricing_method_comparison.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class PricingMethodComparison(BaseModel):
6262
def _implied_vols(
6363
self, r: OptionPricingResult, log_strikes: np.ndarray, ttm: float
6464
) -> np.ndarray:
65-
call = np.asarray(r.call_at(log_strikes))
65+
call = np.asarray(r.call_price(log_strikes))
6666
intrinsic = np.maximum(0.0, 1.0 - np.exp(log_strikes))
6767
call = np.clip(call, intrinsic, 1.0)
6868
return implied_black_volatility(log_strikes, call, ttm, 0.5, 1.0).values
@@ -75,10 +75,10 @@ def _iv_error(
7575
ttm: float,
7676
) -> float:
7777
iv = implied_black_volatility(
78-
log_strikes, np.asarray(r.call_at(log_strikes)), ttm, 0.5, 1.0
78+
log_strikes, np.asarray(r.call_price(log_strikes)), ttm, 0.5, 1.0
7979
).values
8080
iv_ref = implied_black_volatility(
81-
log_strikes, np.asarray(ref.call_at(log_strikes)), ttm, 0.5, 1.0
81+
log_strikes, np.asarray(ref.call_price(log_strikes)), ttm, 0.5, 1.0
8282
).values
8383
finite = np.isfinite(iv) & np.isfinite(iv_ref)
8484
return float(np.max(np.abs(iv[finite] - iv_ref[finite])))
@@ -90,7 +90,7 @@ def run_ttm(self) -> None:
9090
log_strikes = ms.option_support(
9191
self.ref_n + 1, max_log_strike=max_log_strike
9292
)
93-
ref = ms.call_option(self.ref_n, max_log_strike=max_log_strike)
93+
ref = ms.call_option(self.ref_n, max_moneyness=self.max_moneyness)
9494
iv_ref = self._implied_vols(ref, log_strikes, ttm)
9595
moneyness_ref = log_strikes / np.sqrt(ttm)
9696
ttm_label = f"TTM={ttm}"
@@ -121,7 +121,7 @@ def run_ttm(self) -> None:
121121
for n in self.ns:
122122
r = ms.call_option(
123123
n,
124-
max_log_strike=max_log_strike,
124+
max_moneyness=self.max_moneyness,
125125
pricing_method=method,
126126
)
127127
ks = (

docs/examples/vol_surface_bns2_calibration.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
from docs.examples._utils import assets_path, print_model
44
from quantflow.options.calibration import BNS2Calibration
5-
from quantflow.options.pricer import OptionPricer
5+
from quantflow.options.calibration.base import ResidualKind
6+
from quantflow.options.pricer import OptionPricer, OptionPricingMethod
67
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
78
from quantflow.sp.bns import BNS, BNS2
89

@@ -23,14 +24,16 @@
2324
model=BNS2(
2425
bns1=BNS.create(vol=0.45, kappa=20.0, decay=10.0, rho=-0.6),
2526
bns2=BNS.create(vol=0.45, kappa=0.3, decay=10.0, rho=0.3),
26-
weight=0.3,
27-
)
27+
weight=0.5,
28+
),
29+
method=OptionPricingMethod.COS,
2830
)
2931

3032
calibration: BNS2Calibration[BNS2] = BNS2Calibration(
3133
pricer=pricer,
3234
vol_surface=surface,
33-
moneyness_weight=0.2,
35+
moneyness_weight=0.3,
36+
residual_kind=ResidualKind.IV,
3437
)
3538

3639
result = calibration.fit()

docs/examples/vol_surface_bns_calibration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from docs.examples._utils import assets_path, print_model
44
from quantflow.options.calibration import BNSCalibration
5-
from quantflow.options.pricer import OptionPricer
5+
from quantflow.options.pricer import OptionPricer, OptionPricingMethod
66
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
77
from quantflow.sp.bns import BNS
88

@@ -16,6 +16,7 @@
1616
# Create a BNS pricer with initial parameters
1717
pricer = OptionPricer(
1818
model=BNS.create(vol=0.5, kappa=1.0, decay=10.0, rho=-0.2),
19+
method=OptionPricingMethod.COS,
1920
)
2021

2122
calibration: BNSCalibration[BNS] = BNSCalibration(

docs/examples/vol_surface_heston_calibration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
calibration: HestonCalibration[Heston] = HestonCalibration(
2424
pricer=pricer,
2525
vol_surface=surface,
26+
moneyness_weight=0.3,
2627
)
2728

2829
result = calibration.fit()

docs/examples/vol_surface_hestonj_calibration.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
from docs.examples._utils import assets_path, print_model
44
from quantflow.options.calibration import HestonJCalibration
5+
from quantflow.options.calibration.base import ResidualKind
56
from quantflow.options.pricer import OptionPricer
67
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
78
from quantflow.sp.heston import HestonJ
89
from quantflow.utils.distributions import DoubleExponential
10+
from quantflow.utils.marginal import OptionPricingMethod
911

1012
# Load a saved volatility surface snapshot and build the surface
1113
with open("docs/examples/volsurface.json") as fp:
@@ -24,14 +26,15 @@
2426
sigma=0.8,
2527
jump_fraction=0.3,
2628
jump_asymmetry=0.2,
27-
)
29+
),
30+
method=OptionPricingMethod.COS,
2831
)
2932

3033
# Set up the calibration, dropping the first (very short) maturity
3134
calibration: HestonJCalibration[DoubleExponential] = HestonJCalibration(
3235
pricer=pricer,
3336
vol_surface=surface,
34-
moneyness_weight=0.5,
37+
residual_kind=ResidualKind.IV,
3538
)
3639

3740
result = calibration.fit()

docs/release-notes.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ and signature changes were made along the way: see **Breaking changes** below.
2424
re-exports. Code reaching into the old `quantflow.options.heston_calibration`
2525
must switch to `quantflow.options.calibration.heston`.
2626

27-
**`ModelOptionPrice` field rename.** (#47)
27+
**`ModelOptionPrice` field rename.** ([#47](https://github.com/quantmind/quantflow/pull/47))
2828

2929
- `ModelOptionPrice.moneyness` previously meant `log(K/F)`. It now means
3030
standardised moneyness `log(K/F) / sqrt(ttm)`, and the raw log-strike is
@@ -37,31 +37,40 @@ and signature changes were made along the way: see **Breaking changes** below.
3737
- **`BNS2`**: two-factor Barndorff-Nielsen & Shephard stochastic-volatility
3838
model with a single Brownian motion driving a convex combination of
3939
independent Gamma-OU variances and per-factor leverage. New section in the
40-
BNS calibration tutorial. (#54)
40+
BNS calibration tutorial. ([#54](https://github.com/quantmind/quantflow/pull/54))
4141
- **`DoubleHeston` and `DoubleHestonJ`**: two-factor Heston (with optional
4242
log-price jumps) and matching `DoubleHestonCalibration` /
43-
`DoubleHestonJCalibration`. (#46)
43+
`DoubleHestonJCalibration`. ([#46](https://github.com/quantmind/quantflow/pull/46))
4444
- **Lewis and COS option-pricing methods**: selectable via
45-
`OptionPricingMethod`, alongside the existing Carr-Madan / FFT path. (#47)
46-
- **CIR tutorial** with PDF comparison example. (#49)
45+
`OptionPricingMethod`, alongside the existing Carr-Madan / FFT path.
46+
([#47](https://github.com/quantmind/quantflow/pull/47))
47+
- **CIR tutorial** with PDF comparison example.
48+
([#49](https://github.com/quantmind/quantflow/pull/49))
4749

4850
### Improvements and fixes
4951

50-
- Heston calibration convergence fixes. (#45, #49)
52+
- Heston calibration convergence fixes.
53+
([#45](https://github.com/quantmind/quantflow/pull/45),
54+
[#49](https://github.com/quantmind/quantflow/pull/49))
5155
- BNS calibration: dedicated `BNSCalibration` class extracted, characteristic
52-
exponent derivation cleaned up, broader test coverage. (#50, #51)
56+
exponent derivation cleaned up, broader test coverage.
57+
([#50](https://github.com/quantmind/quantflow/pull/50),
58+
[#51](https://github.com/quantmind/quantflow/pull/51))
5359
- OU module reworked: clearer Gamma-OU API, stronger tests for moments and
54-
the integrated Laplace transform. (#51)
60+
the integrated Laplace transform.
61+
([#51](https://github.com/quantmind/quantflow/pull/51))
5562
- `pricing_method_comparison` example simplified; redundant time-comparison
56-
code removed. (#48)
63+
code removed. ([#48](https://github.com/quantmind/quantflow/pull/48))
5764

5865
### Documentation and assets
5966

6067
- New logo set (favicon, lockup, marks, social banners) under
61-
`docs/assets/logos/`. (#53)
68+
`docs/assets/logos/`. ([#53](https://github.com/quantmind/quantflow/pull/53))
6269
- New `docs/mcp.md` page covering the MCP server.
6370
- Bibliography rebuilt from BibTeX via `docs/bib2md.py`; glossary expanded;
64-
mathjax tweaks for inline rendering. (#47, #49)
71+
mathjax tweaks for inline rendering.
72+
([#47](https://github.com/quantmind/quantflow/pull/47),
73+
[#49](https://github.com/quantmind/quantflow/pull/49))
6574
- Tutorial-writing instructions added at
6675
`.github/instructions/tutorial.instructions.md`.
6776

0 commit comments

Comments
 (0)