Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,15 @@ applyTo: '/**'

* Strategy runtime markdown descriptions (read by `load_description()` at runtime) live inside the package at `quantflow/options/strategies/docs/` — they must be inside the package to be accessible when the library is installed
* mkdocs documentation pages live in `docs/api/options/` — do not mix these two locations

## Releasing

The release procedure is fully driven by `make release` and the `release.yml` workflow:

1. Bump `version` in `pyproject.toml` to the new release version.
2. Add a `## vX.Y.Z` section to `docs/release-notes.md` with the notes for the release. The header text is matched verbatim by the workflow's `awk` extractor, so it must be `## vX.Y.Z` exactly (no trailing dash, no title after the version).
3. Commit and merge to `main`.
4. From `main`, run `make release` — it reads the version from `pyproject.toml`, prompts for confirmation, then creates an annotated `vX.Y.Z` tag and pushes it.
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.

Do not publish to PyPI manually or via the old `head_commit.message == 'release'` flow — the tag-triggered workflow is the only supported path.
3 changes: 0 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./build/coverage.xml
- name: publish
if: ${{ matrix.python-version == '3.14' && github.event.head_commit.message == 'release' }}
run: make publish

image:
if: github.ref == 'refs/heads/main'
Expand Down
78 changes: 78 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: release

# Triggered by `v*` tags. Builds and publishes the package to PyPI, then
# extracts the matching section from docs/release-notes.md and publishes it
# as the GitHub Release body. Re-runs idempotently update an existing
# release rather than failing.

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
env:
PYTHON_ENV: ci
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
FMP_API_KEY: ${{ secrets.FMP_API_KEY }}
steps:
- uses: actions/checkout@v4

- name: install rops
uses: quantmind/rops/.github/actions/setup-rops@main
env:
GITHUB_TOKEN: ${{ secrets.TOKEN_DEPLOYMENT }}

- name: install taplo
run: rops tools update taplo

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.14"

- name: Install uv
run: pip install -U pip uv

- name: Install dependencies
run: make install-dev

- name: run lint
run: make lint-check

- name: run tests
run: make tests

- name: publish to PyPI
run: make publish

- name: Extract release notes
run: |
TAG="${GITHUB_REF_NAME}"
awk -v tag="## ${TAG}" '
$0 == tag { in_section = 1; next }
/^## / && in_section { exit }
in_section { print }
' docs/release-notes.md > /tmp/notes.md
if [ ! -s /tmp/notes.md ]; then
echo "No section '## ${TAG}' found in docs/release-notes.md" >&2
exit 1
fi
echo "--- extracted notes for ${TAG} ---"
cat /tmp/notes.md

- name: Publish GitHub Release
run: |
TAG="${GITHUB_REF_NAME}"
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --notes-file /tmp/notes.md
else
gh release create "$TAG" --title "$TAG" --notes-file /tmp/notes.md
fi
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ publish: ## Release to pypi
@uv build
@uv publish --token $(PYPI_TOKEN)

.PHONY: release
release: ## Tag current version (from pyproject.toml) and push
$(eval VERSION := $(shell grep '^version' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/'))
@read -p "Tagging with v$(VERSION), are you sure? [Y/n] " ans; \
ans=$${ans:-Y}; \
if [ "$$ans" = "Y" ] || [ "$$ans" = "y" ]; then \
git tag -a v$(VERSION) -m "v$(VERSION)" && git push origin v$(VERSION); \
else \
echo "Aborted."; \
fi

.PHONY: tests
tests: ## Unit tests
@./dev/test
Expand Down
68 changes: 68 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Release Notes

This page is the source of truth for quantflow release notes. Each section
below maps to a tagged release on
[GitHub](https://github.com/quantmind/quantflow/releases). When a new tag is
pushed, the matching section is extracted by
`.github/workflows/release.yml` and published as the GitHub Release body.

## v0.8.0

Volatility-surface calibration overhaul. This release adds a two-factor BNS
model, a double-Heston model (with optional jumps), Lewis and COS pricing
methods, and reworks the calibration package layout. Several module renames
and signature changes were made along the way: see **Breaking changes** below.

### Breaking changes

**Module renames.**

- `quantflow.sp.weiner` is now `quantflow.sp.wiener` (typo fix). Update
imports.
- `quantflow.options.calibration` is now a package, not a single module.
Top-level imports keep working through the package `__init__.py`
re-exports. Code reaching into the old `quantflow.options.heston_calibration`
must switch to `quantflow.options.calibration.heston`.

**`ModelOptionPrice` field rename.** (#47)

- `ModelOptionPrice.moneyness` previously meant `log(K/F)`. It now means
standardised moneyness `log(K/F) / sqrt(ttm)`, and the raw log-strike is
exposed as a new field `log_strike`. Code reading `option.moneyness` and
expecting a log-strike must switch to `option.log_strike`.
- `get_intrinsic_value(moneyness=...)` argument renamed to `log_strike=...`.

### New features

- **`BNS2`**: two-factor Barndorff-Nielsen & Shephard stochastic-volatility
model with a single Brownian motion driving a convex combination of
independent Gamma-OU variances and per-factor leverage. New section in the
BNS calibration tutorial. (#54)
- **`DoubleHeston` and `DoubleHestonJ`**: two-factor Heston (with optional
log-price jumps) and matching `DoubleHestonCalibration` /
`DoubleHestonJCalibration`. (#46)
- **Lewis and COS option-pricing methods**: selectable via
`OptionPricingMethod`, alongside the existing Carr-Madan / FFT path. (#47)
- **CIR tutorial** with PDF comparison example. (#49)

### Improvements and fixes

- Heston calibration convergence fixes. (#45, #49)
- BNS calibration: dedicated `BNSCalibration` class extracted, characteristic
exponent derivation cleaned up, broader test coverage. (#50, #51)
- OU module reworked: clearer Gamma-OU API, stronger tests for moments and
the integrated Laplace transform. (#51)
- `pricing_method_comparison` example simplified; redundant time-comparison
code removed. (#48)

### Documentation and assets

- New logo set (favicon, lockup, marks, social banners) under
`docs/assets/logos/`. (#53)
- New `docs/mcp.md` page covering the MCP server.
- Bibliography rebuilt from BibTeX via `docs/bib2md.py`; glossary expanded;
mathjax tweaks for inline rendering. (#47, #49)
- Tutorial-writing instructions added at
`.github/instructions/tutorial.instructions.md`.

[Full changelog](https://github.com/quantmind/quantflow/compare/v0.7.0...v0.8.0)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ nav:
- Glossary: glossary.md
- Contributing: contributing.md
- Bibliography: bibliography.md
- Release Notes: release-notes.md
markdown_extensions:
- attr_list
- tables
Expand Down
Loading