Done
-
Read all five repos and ran every test suite: 164 passed and 1 skipped in total, all green. Counts and runtimes are in
PLAN.md§1. -
Ran every offline example. Every scripted README headline number reproduces exactly.
-
Found three README prose errors, none produced by a script (PLAN Q6).
-
Checked the CLAUDE.md API map against the code. The differences are in PLAN §2. The largest are:
- volsurface already has smooth θ and a local-vol Monte Carlo;
- btlab already has square-root impact;
- risk-lab has 37 tests, not 21;
- lobkit releases through release-please.
-
Wrote
PLAN.md. -
Q1–Q3 answered; see the PLAN header.
-
Set up quant-stack: ran
git init, added the five submodules from GitHub underprojects/, and created the skeleton:pyproject.tomlsrc/quantstack- a smoke test, 5 passed
- CI
-
Re-ran each library suite from the submodules. All green:
- sp-pricer: 19 passed
- volsurface (main): 37 passed
- btlab: 38 passed
- lobkit: 31 passed
- risklab: 36 passed, 1 skipped
-
Skills:
- ponytail is active and graphify is installed.
- agentskill skill installed. Its CLI needs cargo, which is not installed.
- api-anything was skipped.
Not done yet
- No library code has been changed.
- The old top-level folders are still on disk, locked and git-ignored.
Next
- Approval of PLAN.md and answers to Q4–Q7.
- You merge volsurface M6, then I pin it.
- Build the graphify graph over
projects/. - Phase 1, starting with volsurface.
| Repo | Version / tag | Tests before → after (default; slow) | Changes |
|---|---|---|---|
| volsurface | 0.2.0, v0.2.0 |
40 → 46; +2 slow | local_vol(floor=, cap=, extrapolate="flat") with clamp counts, k_range, to_dict/from_dict, shifted() / ShiftedSurface |
| sp-pricer | 0.2.0, v0.2.0 |
19 → 28; +1 slow | Curve, Market(curves=, discount_curve=), PathModel, GBMModel, LocalVolSource, LocalVolModel, price_mc(model=) |
| backtest-lab | 0.4.0, v0.4.0 |
38 → 48; +1 slow | CostModel/FlatBps/SpreadImpactCost, periods_per_year, CovarianceStrategy, TimeSeriesMomentum(vol_estimator=), risk_report |
| microstructure-lab | feat: commit; release-please tags 0.3.0 on merge |
31 → 38 | calibrate_costs, to_bars |
| risk-lab | 0.4.0, v0.4.0 (plus v0.3.0 retro-tag) |
37 → 49 (incl. 1 skip); +2 slow | scenarios module (M5), Portfolio(vol_fn=, sticky=, underlying=), missing look-ahead tests |
Notes
- Each repo's README numbers were pinned in tests before any code changed, and every pin still passes.
- Default paths are unchanged; the new hooks are checked against the old code paths.
- No library imports a sibling or
quantstack. The one exception is volsurfaceexamples/autocallable_skew.py(Q4), which moves to quant-stack after S1. - volsurface
mainwas fast-forwarded locally to the M6 commit (9e98505), because the GitHub merge had not happened.
README fixes (Q6)
- btlab: long-only raw return is 21%, not 16%. Example 03 now prints it.
- lobkit: a tick is 0.0013 / 0.044 bp, not 0.13 / 0.44. Example 04 now prints it.
- volsurface: the wing-band RMSE figures are now printed by
real_data_study.py, defined as z = k/√w. That run found "0.15" should be "0.16".
Surprises
check_lookaheadrejects a calendar month-end schedule, because spotting the last trading day of a month needs tomorrow's date.CovarianceStrategytherefore recomputes on positionsevery-1, 2·every-1, ..., which pairs withrun_backtest(rebalance=every, execution_lag=1).- sp-pricer's barrier bridge assumes flat vol, so
price_mcraises for non-European monitoring underLocalVolModel. - Euler bias at dt = 1/2, 1/4 and 1/8 for a steep-skew stub: −0.59, −0.18 and −0.03 per 100.
Next: Phase 2 in quant-stack: consistency tests, then Links A, B, C, D and their studies.
quant-stack
adapters/surface.py(Link A),adapters/notes.py(Link B: CRN repricing, horizon guard, validated price grid),adapters/costs.py(Link D: every unit conversion documented, plus fees).- Tests: 18 (consistency, Links A, B, D, smoke). All pass on current and on the oldest supported numpy, pandas and scipy.
- Studies S1–S5 run end to end with fixed seeds. Tables are in
docs/s*_output.txt, alongside the plots. README.md(umbrella) anddocs/profile_README.md(draft for your GitHub profile).- graphify code graph in
graphify-out/(git-ignored): 1,731 nodes.
Library follow-ups found while integrating. The tags below were moved, which is safe because nothing is pushed.
- risk-lab:
Scenariolength for vol-only batches (bug, now tested). - volsurface:
local_vol_surface()dropsk_range(now tested). The autocallable example moved to S1 and thepricerextra was removed. - volsurface and risk-lab: the regression pins were too tight across supported scipy versions. They are now held at the precision the README prints; see each test module's docstring.
Headline results. Full write-up in the README section "What was surprising".
- S1: local vol lowers the SPX/NDX worst-of note by 4.00 ± 0.04 per 100. This reproduces volsurface's independent simulator. A flat vol taken at the barrier strike overshoots the effect.
- S2: delta-normal VaR of the hedged book is 0, against 90 under full revaluation. Delta-gamma-vega overstates it at 152, because the notes' value is concave in vol (checked by repricing).
- S3: long-only minimum variance has the lowest true risk after lag and costs (DSR 0.94). HRP turnover barely falls with the window on synthetic data but falls 4× on ETFs. FHS VaR under-predicts on ETFs.
- S4: OFI fails after spread. Queue imbalance survives spread, latency and book walking, but its break-even fee is about 0.2–0.3 bp, so any exchange taker fee kills it.
- S5: sticky moneyness more than doubles a short straddle's 1-day VaR against sticky strike.
Test counts at the end (default; slow in brackets):
| Repo | Tests |
|---|---|
| sp-pricer | 28 (+1) |
| volsurface | 47 (+2) |
| backtest-lab | 48 (+1) |
| microstructure-lab | 38 |
| risk-lab | 49, 1 skipped (+2) |
| quant-stack | 18 |
Not done, needs you:
- Deleting the old top-level folders (
/sp-pricerand the others), which were locked during setup and are git-ignored.
Each library was merged by PR. CI on main was checked before the next one started, and quant-stack was
re-pinned to the released commit.
| Repo | PR | Release | Pinned commit | Checks |
|---|---|---|---|---|
| volsurface | #6, merge | v0.2.0 |
132c38c | main CI green |
| sp-pricer | #1, merge | v0.2.0 |
95fafc6 | main CI green |
| risk-lab | #1, merge | v0.3.0, v0.4.0 |
05fd489 | main CI green |
| backtest-lab | #9, merge | v0.4.0, tagged by publish.yml |
c5dce70 | PyPI btlab 0.4.0 installs |
| microstructure-lab | #10, squash | v0.3.0, tagged by release-please |
91b9feb | PyPI lobkit 0.3.0 installs |
- backtest-lab: CI on pytest 8.3 and later rejected
pytest.approxon a nested dict. Fixed with==before merging. - microstructure-lab: the squash commit's tree matches the tested branch exactly. The release commit changes only the version, manifest and changelog. The two failed runs on the release PR started no jobs: the bot merged the PR and deleted its branch first. The 0.2.2 release shows the same pattern.
Tidy-ups.
- Deleted the merged
link/v0.2branches on sp-pricer, volsurface, backtest-lab and risk-lab. microstructure-lab keeps its branch: after the squash merge, it is the only ref holding df1d49a, which older quant-stack commits pin. Every commit quant-stack has ever pinned was checked as still fetchable. - Deleted the old top-level folders after checking: no unpushed commits, stashes or changes, and data identical to
projects/. - quant-stack, sp-pricer, volsurface and risk-lab moved to actions/checkout and setup-python v7 (Node 20 deprecation),
each by PR, with CI green on the branch and on
main. - GitHub topics on all six repos.
S6, "How did autocallable investors actually do?"
- New adapter
realised_cashflows(product, levels)inadapters/notes.py. It reads a note's cash flows on a realised path from the product's own payoff, by discounting one date at a time, and has a test. - 250 monthly SPY Phoenix notes, 2005-01 to 2025-10, with the coupon set at issue for a fair value of 98. Realised PV is 101.20, or +3.20 ± 0.72 (Newey-West). That is +2.28 ± 0.38 without the 2008, 2009 and 2020 vintages. 94% autocalled and none lost capital; the lowest maturity level was 72%.
- Checks:
- The coupon solve is exact under common random numbers, asserted to 1e-9.
- The payoff code agrees with the study on when each note ended.
- The output is deterministic, and the study skips cleanly offline.
- A first version used a fixed 8% coupon, giving fair values up to 112. That is unrealistic, so it is kept only as a secondary comparison.
- 13 issues in 2013–2016 have a negative par coupon because there is no funding spread and the note is on total return. This is reported, not hidden.
- A README claim that 2000 issues would have lost capital was checked against data and was wrong: they end at 60–85%, and April 2000 matures at 60.4%. Corrected.
- quant-stack tests: 18 → 19.
- Setup:
studies/s7_market_maker_risk.pyruns the Avellaneda–Stoikov quoter in lobkit's order book: 2,500 sessions of 5 minutes for γ ∈ {0.001, 0.005, 0.02} × {inventory, symmetric}. risk-lab measures session P&L tails and backtests four VaR models on sessions 1,001–2,500. It runs offline in about 7 minutes. - Tails: in the paper's fill model no session loses money. In the book, γ = 0.005 inventory has the best mean/ES (1.90). Symmetric quotes earn 46–65% more with 5–13× the ES.
- Backtests:
- Historical passes 6 of 6 and FHS 5 of 6 (one rejection at p = 0.032, in line with 24 tests).
- Normal fails 4 of 6, and its ES is rejected by Z2 on 4 books.
- EWMA has 0 exceptions on every inventory book: it assumes zero-mean P&L, so it overstates VaR 2.6–5.7×.
- Checks:
- risk-lab VaR models across input units: exact for three; FHS moves the mean VaR by 0.1% (GARCH optima).
- The first 200 sessions reproduce microstructure-lab's README row exactly.
- Finding for microstructure-lab, not acted on (needs approval): its γ = 0.005 inventory mean of 19.4 (seeds 0–199) is a low draw. 2,500 sessions give 22.6 ± 0.26, and fresh seed blocks give 21.3–24.1. Inventory control costs 32–40% of mean P&L, not 33–45%.