Changelog
All notable changes to this project will be documented in this file.
[Unreleased]
Fixed
-
Benchmark synthetic data is now reproducible across numpy versions; published benchmark numbers are reset (
quantwave-5yjg).benchmarks/data.pydrew fromnp.random.default_rng, whoseGeneratorstreams NumPy explicitly reserves the right to change between feature releases (NEP 19 freezes only the legacyRandomState). The same seed therefore produced different data on different numpy builds — confirmed here as numpy 1.26.4 and 2.4.6 yielding different digests from an identical config. Sincedataset.frame_hashinbenchmarks/results/latest.jsonexists precisely to prove two runs measured the same data, it could not do its job, and the nightly committed a spurious diff tomainwhenever CI's pip resolution shifted.Values now come from SplitMix64 implemented in explicit
uint64arithmetic insidebenchmarks/data.py, depending on no library RNG. It is counter-based, so rowiis a pure function of(seed, column, i)— a 100k smoke run is a true prefix of the 1M nightly run.frame_hashalso folds in column name, dtype and length and normalises to little-endian, so a reordered or retyped frame can no longer collide with the original.This changes the synthetic dataset, so benchmark figures produced before this release are not comparable with those after it.
tests/python/test_benchmark_harness.pynow pins the generator with golden digests; if they ever fail, the data stream moved and the baseline must be reset deliberately rather than re-blessed.
Changed
-
BREAKING (Polars plugin surface):
ta_betanow defaults totimeperiod=5andta_correltotimeperiod=30, matching TA-Lib and their non-prefixed siblings (quantwave-0h4o).The
ta_*signatures were generated with a single blankettimeperiod=14. TA-Lib does not use one uniform default —BETAis 5 andCORRELis 30, while theLINEARREGfamily,TSF,ATRandNATRare 14 — so.ta.ta_correl(other)silently computed a 14-bar correlation where.ta.correl(other)and TA-Lib compute a 30-bar one, and.ta.ta_beta(other)a 14-bar beta where both others use 5. The formulas were always correct and the twins are bit-identical at equal periods; only the default diverged. That put the divergence on exactly the wrong surface: theta_prefix exists to promise TA-Lib fidelity.# before — 14 bars, disagreeing with .ta.correl on the same data pl.col("a").ta.ta_correl("b") # now — 30 bars, TA-Lib's own default; agrees with .ta.correl("b") pl.col("a").ta.ta_correl("b")This changes results for anyone who relied on the old bare-call default. Pass
timeperiod=14explicitly to keep the previous numbers. The classic-array shim inherits its defaults from these signatures, soquantwave.talib.TABETA/TACORRELchange with them and now agree withBETA/CORREL.Audited the full
ta_*set:ta_atr,ta_natr,ta_linearreg,ta_linearreg_angle,ta_linearreg_intercept,ta_linearreg_slopeandta_tsfwere already at TA-Lib's 14 and are unchanged;ta_trangetakes no period. Onlyta_betaandta_correlmoved. The non-prefixedmax,min,sum,maxindex,minindexandwmakeep quantwave's house default of 14 even though TA-Lib uses 30 for those — they are quantwave's own surface, not a fidelity promise, and this is now recorded deliberately rather than falling out of a blanket default.Fixed in the generator (
scripts/gen_pyo3_plugins_py.py), not just its output: a per-function default table drives the emitted signatures, and a period-taking plugin missing from that table makes the generator fail rather than invent a uniform value. Regression cover lives intests/python/test_ta_prefixed_defaults.py, anchored to a Pearson correlation and a TA-Lib BETA accumulation written out longhand in the test, plus guards that a bare call no longer returns the old 14-bar value. -
BREAKING (Polars plugin surface):
ta_atr,ta_natrandta_trangenow takeclosein the receiver, matching their non-prefixed siblings (quantwave-sww3).These were generated with opaque positional parameters —
ta_atr(self, in2, in3)— and forwarded as[self, in2, in3]to a plugin that consumes(high, low, close). The receiver therefore had to be high, while the sibling.ta.atrtakes close. Writing the call the way its sibling reads permuted the inputs and returned a plausible wrong number with no error: on a 200-bar random walk,1.252823against a hand-computed Wilder RMA of1.384819— roughly 10% off, and data-dependent, so it never looked obviously broken. All three arguments are equal-lengthf64columns, so nothing could raise.This bit hardest exactly where it mattered least tolerably: you reach for a
ta_-prefixed function specifically when you want TA-Lib-identical values, usually to reconcile against a chart.# before — receiver had to be high pl.col("high").ta.ta_atr("low", "close", timeperiod=14) # now — same shape as .ta.atr pl.col("close").ta.ta_atr("high", "low", timeperiod=14)Parameters are now named
high/lowrather thanin2/in3, so a mis-ordered call is visible at the call site and keyword arguments work.ta_betaandta_correlalready matched their siblings' shape and are unchanged apart fromin2becomingother. The generator (scripts/gen_pyo3_plugins_py.py) was fixed, not just its output, so regenerating cannot reintroduce this. Regression cover lives intests/python/test_ta_prefixed_arg_order.py, anchored to independently computed reference values rather than to current behaviour. -
All 60 remaining candlestick patterns ported to native Rust —
talib-rshas left the shipped dependency graph. Thenative_cdl!macro pushed every bar onto a 32-bar window and re-ran the fulltalib_rs::pattern::*batch function per bar, discarding all but the last value. All 60 are now O(1) streamingNext<T>implementations underquantwave_core::indicators::patterns, joiningCDLDOJIand the 14 already-native single-bar patterns for 61 native candlestick patterns.talib-rsmoved from[dependencies]to[dev-dependencies]inquantwave-core, and the unused declaration was dropped fromquantwave-py. It is retained solely as the#[cfg(test)]parity oracle and thebenches/talib_comparisonbaseline, both of which stay. The "221 native indicators" claim is now literally true — nothing in the shipped wheel or crate delegates to C TA-Lib or to a third-party TA crate. Struct names are unchanged (CDLHAMMER,CDL2CROWS, …), so this is not a breaking change for any caller.The dead
native_cdl!macro and the eleven never-invokedtalib_*macros were deleted fromindicators/talib_wrapper.rs; the two live native macros (native_pointwise_1!,native_binary_2!) remain. -
BREAKING (Rust API only):
talib_rs::MaTypereplaced by a nativequantwave_core::MaType(quantwave-wxii). Everymatypefield and constructor argument —APO,PPO,BBANDS,MACDEXT,MAVP,STOCH,STOCHF,STOCHRSI, and the correspondingquantwave-polars.ta()methods — now takesquantwave_core::MaType. The discriminants are unchanged (Sma = 0…T3 = 8), so Python users are unaffected:matype=still takes the same integers and strings.pub use talib_rs as talib;has been removed fromquantwave-core. talib-rs is a parity oracle used from#[cfg(test)]andbenches/, and a dependency in that role must not appear in a public signature. Rust callers substitute:MaTypeoffersto_i32(),TryFrom<i32>,FromStr,Display,MaType::ALL, andfrom_u8_or_sma(). To cross to talib-rs in your own tests or benches, go through the integer code:talib_rs::MaType::try_from(m.to_i32()). -
BREAKING:
execution_delaynow defaults to"next_bar"(T+1) instead of"same_bar"(T+0) (quantwave-zmjw). Affects.bt.backtest,.bt.backtest_with_report,.bt.backtest_metrics,.bt.portfolio_backtestand every other.btentry point, the PythonBacktestConfig, the RustBacktestConfig::default()/ExecutionDelay::default(), andBtOptions::default()inquantwave-polars."same_bar"fills at the close of the very bar that produced the signal. Since signals are almost always derived from that same close (e.g.(rsi < 30)computed on bart), the old default executed on information that only existed at the instant the bar ended — a look-ahead the live strategy never has, and one that flatters results systematically. On an identical signal frame over a rising series,same_barentered at100.5wherenext_barentered at101.0.Your existing backtests will report different — generally worse — numbers after upgrading. That difference is the look-ahead being removed, not a regression.
To restore the previous behaviour, pass it explicitly:
"same_bar"remains fully supported and is the correct choice when it genuinely describes your execution: you trade the closing auction, or your signal is built purely from data through bart-1so bart's close is not an input. Otherwise, prefer the new default.
Fixed
-
calmar_ratioreturnedinfon a zero-drawdown run while the rest of the bundle returnedNaN(quantwave-gz7d).quantwave-s3iumovedsortino_ratioandprofit_factortoNaNfor an empty denominator but leftcalmar_ratio = cagr / max_drawdown_pctout of scope, so a single-trade run reported two different conventions for the same condition —extended_metrics()gavesortino_ratio=nan,profit_factor=nan,calmar_ratio=inf. Calmar now returnsNaNwhenmax_drawdown_pctis zero with positive CAGR (and still0.0when CAGR is non-positive, the no-activity case).This is not cosmetic. Walk-forward and sweep selection pick the argmax with a
v > best_valcomparison:inf > anythingisTrue, so a degenerate variant that simply never lost would win the in-fold optimisation and be carried into the out-of-sample window, whereasNaN > anythingisFalseand the undefined variant is skipped like a null. The grid selector is now the extracted, unit-testedselect_best_objective, and both it and the TPE pool selector have explicit NaN-safety tests (including the all-undefined fallback: index 0 with-infas the fold'strain_metric).The same sweep caught one more divergence:
sharpe_ratioreturned0.0when the return series had zero dispersion but a non-zero mean — a perfectly constant non-zero return isx/0, undefined, and0.0reads as a bad Sharpe for a run with no measurable risk. It now returnsNaN, matching thesortino_ratiozero-downside-deviation branch. A genuinely flat series (mean ≈ 0), a series shorter than two observations, and an empty one all still return0.0.var_95/cvar_95were checked and deliberately left alone — they are quantiles, not ratios, with no denominator to be empty — as wasbenchmark, which staysNone(rather than aNaN-filled dict) when alpha/beta are undefined.win_rate,total_return,cagrandavg_trade_pnlguard their divisors against the no-activity case and are unchanged..metrics()remains the same 10 keys. - The Benchmarks Nightly workflow failed on every scheduled run:pyarrowwas never installed (quantwave-ss7q).benchmarks/harness.pybuilds the pandas side of the memory-footprint comparison with polars.to_pandas(), which delegates topyarrow— an optional polars dependency the workflow did not install. The build step passed, only the harness step died withModuleNotFoundError: No module named pyarrow, sobenchmarks/results/latest.jsonand the rendereddocs/benchmarks.mdhad not been refreshed since the dependency drifted out.pyarrowis now installed in.github/workflows/benchmarks-nightly.yml; the benchmark still compares against a genuine pandas frame, since it is what backs the "2-5x lower memory footprint vs pandas" claim.harness.pyalso gained acheck_dependencies()startup check that names each missing module and prints the exactpip installline, so a missing dependency now fails in the first second rather than after data generation.--dry-runonly requirespolarsandnumpy, keepingscripts/quantwave_verify.shrunnable without the comparison stack. -
Four candlestick patterns diverged from the TA-Lib reference in ways the random-walk parity tests never reached. Each substituted a different condition group for the reference's, and each was masked by a fixture that happened to satisfy both readings:
CDLGAPSIDESIDEWHITE— had noEQUALaccumulator at all, substitutingopen[i] < close[i-1]for the reference's near-equal-opens test, and derived the signal's sign from candle colour instead of gap direction. The reference returns+100for an upside gap and-100for a downside gap with both candles white; the old code required a black pair for the bearish case, so it never emitted-100.CDLCONCEALBABYSWALL— tested the real-body gap between barsi-2andi-3instead ofi-1andi-2, required the first two marubozu shadows to be exactly zero rather than shorter than theSHADOW_VERY_SHORTaverage, and imposed a long-lower-shadow test on the 3rd bar that the reference does not have.CDL3STARSINSOUTH— the entire third-candle group was substituted: the reference's short-body, short-upper-shadow and short-lower-shadow tests were missing, replaced by body-containment within the 2nd candle and an exact-zero lower shadow, and thelow/highcontainment was tested against the 2nd body rather than the 2nd bar's range.CDLBREAKAWAY— tracked the three-candle descent withclosewhere the reference useshigh/low, added a long-body requirement on the closing candle that the reference does not impose, constrained the 4th candle's colour (the reference leaves it free), and compared the final close against the 2nd/1st candle bodies rather thanopen[i-3]/close[i-4].
The fixtures in
patterns/fixtures.rsfor all four were rebuilt so they discriminate: each now contains blocks that fire under the reference semantics but not the old reading (and, where applicable, the reverse), separated by re-priming bars so the rolling candle averages return to known values. Each was verified to fail against the pre-fix implementation before the fix was applied. -MaStreamsilently substituted SMA for every matype except EMA (quantwave-ii0g). The enum carried onlySmaandEmavariants and a catch-all arm mapped Wma/Dema/Tema/Trima/Kama/Mama/T3 to SMA, soAPO::new(12, 26, MaType::Wma)returned an SMA-based APO with no error or warning.PPO,MACDEXT,STOCH,STOCHFandSTOCHRSIwere affected the same way. All nine families now dispatch to their native streaming implementation (MaType::Mamausesfastlimit = 0.5/slowlimit = 0.05andMaType::T3usesvfactor = 0.7, matching C TA-Lib'sta_MA.cwhen reached through amatypeargument). Values change for any non-SMA, non-EMA matype — the old numbers were the wrong algorithm. -BBANDSwith a non-SMA matype was O(n) per bar and leaked memory (quantwave-3nyh). The non-SMA path appended every bar to an unboundedhistoryand re-ran the batchbbandsacross the whole thing each tick — O(n²) over a series, in a struct whose SMA path is O(1). It is now genuinely incremental: the middle band comes fromMaStreamand the deviation is a two-pass sum of squared deviations over a rolling window oftimeperiodvalues. Memory is bounded bytimeperiod. Results are unchanged forMaType::Sma. -KAMA's streaming state retained the entire input history although only the lasttimeperiod + 1samples are ever read; it now keeps a bounded ring buffer. The arithmetic, including summation order, is unchanged. - Shared-capital portfolio streaming ignoredexecution_delay(quantwave-zmjw).run_shared_capital_streaming_simulationpassed the delay down tosimulate_shared_capital, which discards it — the batch path pre-shifts signals per timestamp group, but the streaming path never did. UnderSameBar(the old default) both paths agreed, so the bug was invisible; any caller who explicitly asked fornext_barsilently got same-bar fills in streaming mode and broke batch↔streaming parity. The streaming path now applies the same per-timestamp-group shift as batch. - 68 indicators silently resolved to a streaming class instead of their batch function (quantwave-84cu). The generated TA registry introduced in 0.7.0 derived native batch symbol names withpascal_to_snake()(SuperTrend→super_trend), but theexport_*!macros emitpub fn [<$name:lower>](SuperTrend→supertrend). Every multi-word name missed; the 44 single-word ones (rsi,sma,atr) passed only becausepascal_to_snake("Rsi") == "rsi"._resolve_ta_bindingtreated the miss as a fallback and returnednative_streaming, soqw.supertrendwas a class whileqw.rsiwas a function — with no error or warning.qw.supertrend(period=10, multiplier=3.0, high=…, low=…, close=…)again returnslist[SuperTrendResult]as it did in 0.6; callers need no changes. -_resolve_ta_bindingnow raisesImportErrorwhen an entry declares anative_batchsymbol the build does not export, rather than silently substituting the streaming class (whose calling convention differs). Anative_batchofNonestill falls through to streaming/polars as before. - Corrected stale hand-written aliases inscripts/api_slug_aliases.json:fm_demodulator,fourier_series_model,my_rsi,precision_trend_analysisnamed non-existent snake_cased symbols;linreg,oc2,true_rangedeclared batch exports that do not exist and now fall through to their polars methods;sr_monitordeclaredSrInteractionMonitor, a class never exported to Python.
Added
- Batch↔streaming parity proptests parameterised over every
MaTypeforMaStream,BBANDS,APO,PPOandMACDEXT, plus SMA/EMA/WMA/TRIMA coverage forSTOCH/STOCHF. The pre-existing suite only ever constructedMaType::Sma, which is precisely why both bugs above survived unnoticed. - TA-Lib-parity streaming
TalibWma,TalibTemaandTalibMama(the last reusing the existing incremental Hilbert engine), each with its own parity proptest. The general-purposeWMA,TEMAandMAMAuse different seeding and do not reproduce TA-Lib's values. qw.trim_warmup()/qw.warmup_rows()(quantwave-4rsq). Indicator warmup is emitted asNaN, nevernull, sodrop_nulls()/dropna()is a silent no-op on it and warmup rows flow into backtests and feature matrices unnoticed.qw.trim_warmup(frame, *specs, extra=0, strict=True)slices off the maximum warmup across every named indicator, keeping columns with different warmups row-aligned (unlikedrop_nans(), which trims per column set). Accepts"rsi",("rsi", {"period": 21}),{"rsi": {...}, "ema": {...}}, or an explicitintbar count, and works onDataFrame/LazyFrame/Series, includingdf.pipe(qw.trim_warmup, "rsi"). Unknown indicator names raise by default rather than silently trimming nothing..btwarmup warning (quantwave-4rsq).backtest,backtest_with_report,backtest_metrics,portfolio_backtest,walk_forward,monte_carloandorder_backtestnow emit aquantwave.WarmupWarningwhen thesignalorclosecolumn they receive starts withNaN/nullrows. It is a warning, not an error — existing code keeps working — and is silenceable withwarnings.filterwarnings("ignore", category=qw.WarmupWarning).- NaN-vs-null semantics documented prominently in the Python getting-started guide, the backtest quickstart, and the FAQ.
test_registry_native_symbols_resolve_against_build— asserts every declared native symbol exists in the compiled module. The prior test only checked a name was present, never that it resolved, sonative_batch: "super_trend"passed cleanly. Plus a regression test that multi-word slugs bind as batch functions, not classes.
[0.7.0] - 2026-07-13
Added
- Complete classic TA-Lib surface (
quantwave.talib): 161 functions, up from 8 — RSI, MACD, SMA, EMA, ATR, ADX, BBANDS, STOCH, OBV, all 61 candlestick patterns, and the math/price transforms. The classic array-in/array-out API (talib.RSI(close, timeperiod=14), multi-output tuples, OHLC/candlestick inputs) delegates to the Polars.taplugins, so values are the talib-rs-parity-tested Rust results. (quantwave-yp9a) - Top-level native symbol access restored:
qw.FracDiff,qw.fracdiff,qw.rsi,qw.SuperTrend, … bind alongside the slug-basedqw.tanamespace.
Changed
- Unified the Python FFI on PyO3 (abi3), retiring uniffi. The indicator bindings, the Polars expression plugins, and the backtest engine are now a single PyO3
abi3-py39extension in one crate (quantwave-py) producing one cdylib — a singlematurin buildyields onecp39-abi3wheel with no wheel-merge step. (quantwave-5ipk.10,quantwave-6dgg) - Collapsed the three PyO3 crates (
quantwave-python,quantwave-plugins,quantwave-backtest-py) intoquantwave-py; deletedscripts/build_unified_wheel.pyand consolidated to onepyproject.toml.
Fixed
- Wheel tag / install correctness: the published wheel is now
cp39-abi3and installs correctly on CPython 3.9–3.13. 0.6.1 shipped apy3-nonewheel bundling CPython-3.12-only extensions, which brokepip installon 3.9/3.10/3.11/3.13. (quantwave-9gek.1)
[0.6.0] - 2026-06-28
Added
- Fractional differencing (
FracDiff) (quantwave-wnd9): Prado-style stationary features; RustNext<f64>, Polarslf.ta.frac_diff(), Pythonfracdiff() - HTML tear sheets (
quantwave-0gi1):BacktestReport.to_html()/save_html()with equity, drawdown, and trade tables - Research loop (Tier 2):
qw.build_feature_matrix(),lf.ta().features().recommended_matrix(),lf.bt.monte_carlo(), Rust.btWFO-optimize + MC bootstrap - Product guardrails (Tier 1):
scripts/quantwave_verify.sh, metadata drift gate, streamlined CI (verify → plugins → deploy-docs) - 55 custom Polars expression plugins and 98 auto-generated pyo3-polars bindings for standard indicators
- PA foundation: S/R Polars, confluence, geometric patterns with H&S neckline breakout
- Streaming readiness (
quantwave-h6xe) and Rust metadata codegen (quantwave-iqq7) - Plugin vs
.taguide, expanded regime user guide, comparison one-pager - Indicator doc SOA complete (
quantwave-frq0): 220+ native pages underDOCUMENTATION_STANDARDS.mdwith PNG previews, doc drift script in verify - Full visual depth layer (p1k6):
docs/generate_all_previews.py+ standards lint rejecting placeholders
Changed
- GitHub Actions consolidated from four workflows to CI + Release (
v*→ crates.io + PyPI) - Platform planning docs split into
INDICATORS_SOA.mdandBACKTEST_SOA.md
Fixed
- CI:
cargo-nextest, maturin venv,uniffi-bindgen==0.31.0for verify job - Doc lint for
fractional_differentiation.md(preview PNG + description depth) - Empty Python API Reference page (
quantwave-rbz4) - Broken imports for
quantwave >=0.4.1on fresh installs without polars
[0.5.2] - 2026-05-31
Added (Python DX improvements)
- Discovery API:
quantwave.indicators()andquantwave.is_indicator(name). - Rich Metadata:
quantwave.metadata(name)returningIndicatorMetawith params, data inputs, outputs, warmup_bars, category, etc. - Streaming lookup:
quantwave.streaming_class(name). - Parity testing:
quantwave.assert_parity()helper for verifying batch vs streaming bit-identical behavior. warmup_bars(name, params)helper.- Namespace improvements: New
quantwave.results,quantwave.options, andquantwave.talibsubmodules. Old top-level access now emits deprecation warnings. - Public exception base:
quantwave.QuantwaveError. __version__properly exposed.- Linux arm64 (aarch64) wheels are now built and published.
Changed
- Release workflow no longer hard-gates on docs build (docs issues can be fixed independently).
Documentation
- Official Standards Published: Created
docs/DOCUMENTATION_STANDARDS.md(v1.0, 2026-05-31 IST) under task quantwave-d2hk / epic p1k6. Defines mandatory enforceable template for all 223+ indicator pages: required sections (Visual Example, full batch+streaming+Polars Usage Examples, Edge Cases & Limitations, Sources), type-specific guidance (classic scalar / patterns / rich struct / Ehlers), good-vs-bad examples, tone/visual/cross-link rules, and 4-phase rollout. - Updated
contributing.md(new indicator docs step) and appended full decision record + rationale (diagnosis of thin stubs vs. PA notebook quality) toDOCUMENTATION_DECISIONS.md. - Minor alignments in
gallery.md. - This is the foundation for all future indicator documentation work and the planned xtask generator. See
DOCUMENTATION_STANDARDS.mdfor the complete template and checklist. - Candle Standards Proof batch (p1k6 child, 2026-05-31 IST): 8 worst-duplication candlestick pages (doji.md + gravestone/dragonfly variants, harami.md + harami_cross, three_black_crows.md + three_white_soldiers.md, abandoned_baby.md) + engulfing.md enhancement fully rewritten to DOCUMENTATION_STANDARDS.md (mandatory visuals, 3-surface code, edges, authoritative TA-Lib+core sources, no Nison boilerplate).
docs/gen_candle_previews.pyextended (portable + 8+ generators); 11 professional PNGs produced inassets/candlestick-previews/. Cross-refs + full decision record in DOCUMENTATION_DECISIONS.md. Proves template + gens scale for Phase 1 rollout. See decisions file for files touched and bd tracking attempt details. - Ehlers DSP Phase 1 batch 2 (p1k6, 2026-05-31 IST): 5 high-value thin Ehlers DSP pages (ehlers_filter.md, reflex.md, ehlers_stochastic.md, ehlers_loops.md, ultimatesmoother.md) rewritten to full Ehlers/scalar STANDARDS conformance. Extended
gen_indicator_previews.py(portable, pure-numpy core ports for the 5, CLI, professional DSP styling); 5 new PNG visuals generated with 2026-05-31 IST captions mapping directly to core .rs Next logic. 3-surface examples, Edge Cases, authoritative sources (exact core paths + Ehlers papers). Cross-refs + detailed decision record appended. Worktree clean for merge. See DOCUMENTATION_DECISIONS.md for complete list of files + checklist confirmation.
[0.5.1] - 2026-05-31
Fixed
- Publishing completeness: Fixed workspace dependency configuration so that
cargo publishsucceeds for all internal crates (quantwave-core,quantwave-polars,quantwave-plugins,quantwave-backtest,quantwave). Internal crates now correctly declareversion.workspace = truein[workspace.dependencies]. - Release reliability: Added required
build-docsjob (export +mkdocs build --strict) to the release workflow. Release publishing now hard-gates on successful docs build. Removed allcontinue-on-error: truefrom publish steps — any failure is now fatal. - Docs build: Fixed filename collision (
*.py.mdlanding pages conflicting with*.pynotebooks) that was breaking main deploys. Renamed affected landing pages and cleaned up references + committed__pycache__. - Modernized
cargo publishsteps to useCARGO_REGISTRY_TOKENenvironment variable (no more deprecated--tokenflag).
Changed
- 0.5.1 is the first complete, trustworthy release of the Backtest Engine v0.2 features (including
quantwave-backtestcrate on crates.io) plus the full Polars + Python package set.
[0.5.0] - 2026-05-30
Added
- Backtest Engine v0.2 (major milestone):
- Rich-Metadata Position Sizing: New
PositionSizertrait +InitialRiskPositionSizerthat directly consumes rich PA detector metadata (fraction_at_risk,pole_height_atr, strength, etc.) for dynamic, risk-aware sizing. Inspired by QF-Lib patterns. IncludesSizingAdapterfor seamless streamingNext<T>generators. - Pluggable Realistic Execution Models: Proper
CommissionModelandSlippageModeltraits with high-quality implementations, includingSquareRootMarketImpactSlippage(volatility × √(volume/ADV)) andmax_volume_share_limitsupport. - High-Fidelity Execution Simulator Mode: New execution path that applies the full sophisticated models while being driven by the exact same rich
StrategySignal/ PA struct stream as the fast vectorized path. Perfect for "pre-live" validation. - Professional Tearsheet & Reporting Layer: New
BacktestTearsheetwithPerformanceSummary,RiskMetrics,EnrichedTrade(carries full PA metadata for attribution),AttributionReport,to_markdown(), and Polars DataFrame export for Excel. Institutional-quality output. - Full batch + streaming
Next<T>parity maintained across all new features. - Updated canonical examples and documentation demonstrating PA detector + rich metadata workflows.
Changed
- Backtester is now production-grade ready for complex PA + ML strategies (Flags, H&S, Market Structure, etc.).
[0.4.0] - 2026-05-19
Added
- Options India Analytics: Comprehensive suite for NSE options including Black-Scholes Greeks (Price, Delta, Gamma, Theta, Vega, Rho), Implied Volatility, and Chain Analytics (Max Pain, PCR, GEX, OI Zones, ATM Straddle, Synthetic Futures).
- Polars Integration for Options: Full support for
options_indiaas native Polars expressions with robust handling of column-or-value parameters. - NSE Utilities: Added
nse_lot_sizeandmoneynesshelpers for the Indian market.
Fixed
- Release Build: Resolved a critical 'maturin' conflict where tracked
__init__.pyfiles were being overwritten during the wheel build process. - Code Hygiene: Cleaned up all compiler warnings and unused imports across the entire workspace.
[0.3.0] - 2026-05-18
Added
- Multi-Asset Regime Detection: Enhanced
MultiAssetClustererwith rolling correlation structures and dispersion analysis to identify joint market states. - Advanced Conditioned Risk Metrics: Expanded
regimes_conditioned_metricsin Polars to include Skewness, Kurtosis, and Sortino Ratio. - Polars Enhancements: Enabled
momentandcum_aggfeatures for vectorized higher-order statistics.
Fixed
- Release Stability: Fixed workspace dependency alignment issues that caused CI failures in previous releases.
- Compilation: Resolved method resolution errors for
skewandkurtosisin Polars pipelines.
[0.2.0] - 2026-05-18
Added
- Regime Detection Suite (
quantwave::regimes):- Volatility Clustering (Prakash et al. 2021) with online K-Means.
- Hidden Markov Models (Hamilton 1989) with Viterbi decoding.
- Gaussian Mixture Models (Two Sigma 2021) foundations.
- Changepoint Detection (PELT - Killick et al. 2012) for exact segmentation.
- Polars integration for all regime detection tools.
- Comprehensive documentation and guides for market state tools.