Repository navigation
12.0.0: lots of new features (~95) - #14
Merged
Merged
Conversation
Max and Min rescanned the whole window when the current extreme was evicted, which is O(window_len) per update in the worst case (e.g. monotone input). They now keep a monotonic deque of (index, value) candidates (Lemire 2006, streaming max-min filter): each value is pushed and popped at most once, so updates are amortized O(1). Public API and semantics are unchanged: last() is Some after the first value and is the extreme of the last window_len values (of all values during warm-up). Equal values are collapsed to the newest one, which does not change the result. MinMaxNormalizer and the pipeline do not depend on Max/Min internals (pipeline only uses Max::new / Min::new). Tests compare against a naive rescan on seeded random data, data with many ties, and monotone / constant sequences. Adds benches/max_min.rs (window 1024).
HighPass removes periods longer than cutoff_period. HighPassPoles::One is the first order filter from Ehlers, Cycle Analytics for Traders (2013); HighPassPoles::Two is the second order filter from 'Predictive Indicators for Effective Trading Strategies', the same formula as the high pass stage of RoofingFilter (which uses 4.4422 ~= 0.707*2pi). Other agents need a standalone high-pass building block. O(1) update and memory. Both orders share one branch-free update with precomputed coefficients. Deviations / choices: the input history is seeded with the first value, so the filter starts at rest instead of seeing a step from 0 (replaces Ehlers' CurrentBar guard). last() is None for the first cutoff_period - 1 values, as in SuperSmoother. The constructor asserts cutoff_period >= 5 (one pole) / >= 3 (two poles), because otherwise cos(angle) <= 0 and a1 leaves (0, 1), giving an unstable or degenerate filter. Adds benches/ehlers_filters.rs.
BandPass passes cycles near the center period with unit gain and attenuates trend and noise. Formula from Ehlers, Cycle Analytics for Traders (2013) ch. 5 and Ehlers & Way, Empirical Mode Decomposition (2010): L1 = cos(2pi/P), G1 = cos(delta*2pi/P), S1 = 1/G1 - sqrt(1/G1^2 - 1), BP = 0.5(1 - S1)(x - x[2]) + L1(1 + S1)BP[1] - S1*BP[2]. O(1) update and memory. Deviations / choices: S1 is computed as cos(theta) / (1 + sin(theta)), which is algebraically equal but avoids cancellation when G1 is near 0. The EMD article's delta is a half bandwidth (720*delta); here bandwidth follows the book (360*bandwidth). The book's pre high-pass, AGC and trigger line are not included (they could not be verified against the source and the output should keep the input scale); they can be composed with HighPass and other views. Input history is seeded with the first value (filter starts at rest), last() is None for the first period - 1 values. Asserts period >= 2, 0 < bandwidth < 1 and 4*bandwidth < period (G1 > 0).
Wma weights the last n values 1..n, newest heaviest, and divides by n(n+1)/2. Other indicators (e.g. Hull MA) need it as a building block. Update is amortized O(1): a running sum S and weighted sum W are updated with W' = W - S + n*x and S' = S - oldest + x. Because W - S accumulates rounding error over long series, both sums are recomputed exactly from the window every window_len evictions (O(n) every n steps, O(1) amortized). last() is None until the window is full, like Sma. Adds benches/moving_averages.rs.
…elper RollingQuantile computes the q-quantile of the last window_len values with linear interpolation between order statistics (Hyndman & Fan type 7, default of R and numpy). q = 0 / 0.5 / 1 give min / median / max. last() is None until the window is full; q outside [0, 1] (or NaN) panics in the constructor. The order statistics live in the new crate-private SortedWindow<T>: a FIFO VecDeque plus a sorted Vec maintained with binary-search insert/remove. Per push this is O(log n) search plus an O(n) memmove, which is fast for typical windows and much simpler than an order-statistic tree; queries are O(1) (quantile/median) or O(log n) (count_less/count_less_equal). A Vec (not VecDeque) is used so sorted() can return a contiguous slice. SortedWindow is meant to be reused by other indicators (median, percentile rank, ...). Methods not yet used by library code are #[cfg(test)] to avoid dead_code warnings; remove the attribute when using one. MadScaler is intentionally not refactored here. Adds benches/statistics.rs.
Wilder's smoothing (RMA/SMMA): alpha = 1/n, seeded with the SMA of the first n values per the TA-Lib convention (Wilder's original seeds with the first value; the difference washes out after a few window lengths). Reference: J. W. Wilder Jr., New Concepts in Technical Trading Systems (1978). O(1) per update after warm-up.
DEMA = 2*EMA(x) - EMA(EMA(x)), reducing lag vs a plain EMA at the cost of overshoot on reversals (Patrick Mulloy, Technical Analysis of Stocks & Commodities, 1994). Composes two internal Ema<Echo> stages fed by the chained input. O(1) per update, O(1) memory.
TEMA = 3*EMA(x) - 3*EMA(EMA(x)) + EMA(EMA(EMA(x))), reducing lag further than DEMA at the cost of more overshoot on reversals (Patrick Mulloy, Technical Analysis of Stocks & Commodities, 1994). Composes three internal Ema<Echo> stages. O(1) per update, O(1) memory.
Tillson's T3 moving average: six chained EMAs combined with the standard c1..c4 coefficients derived from the volume factor v (default 0.7, asserted in [0, 1]) (Tim Tillson, Technical Analysis of Stocks & Commodities, 1998; TA-Lib's T3). O(1) per update, O(1) memory.
Zero-lag EMA (Ehlers & Way, Technical Analysis of Stocks & Commodities, 2010): EMA of (2x - x[lag]) with lag = (n-1)/2, floored. Composes an internal Lag<Echo> and Ema<Echo>. O(1) per update, O(lag) memory.
Hull Moving Average (Alan Hull, 2005): WMA(2*WMA(n/2) - WMA(n), round(sqrt(n))), with n/2 and sqrt(n) rounded half away from zero (some implementations floor n/2 instead). Requires n >= 2. Composes three internal Wma<Echo> stages. O(1) per update, O(n) memory.
Exponential Hull MA: the same max(n, n/2) + sqrt(n) construction as Hma, but with every WMA stage replaced by an EMA. n/2 and sqrt(n) rounded half away from zero, n >= 2 required. Composes three internal Ema<Echo> stages. O(1) per update, O(1) memory.
Triangular MA (TA-Lib TRIMA): implemented as a double SMA per TA-Lib's odd/even split (p=q=(n+1)/2 for odd n; p=n/2, q=p+1 for even n), giving a triangular weight shape of width n without computing the weights directly. Composes two internal Sma<Echo> stages. O(1) per update, O(n) memory.
MathisWellmann
force-pushed
the
new-features
branch
from
October 12, 2026 15:02
c1162ff to
497cb50
Compare
McGinley Dynamic (John R. McGinley, Technical Analysis of Stocks & Commodities, 1997): MD = MD[1] + (x - MD[1]) / (k*n*(x/MD[1])^4), seeded with the first value, k default 0.6. Requires strictly positive input. Unlike windowed averages, ready from the first update (no warm-up). O(1) per update, O(1) memory.
Kaufman Efficiency Ratio (Perry J. Kaufman, Smarter Trading, 1995): ER = |x - x[n]| / sum(|x[i]-x[i-1]|) over window_len, in [0, 1] by the triangle inequality. 0/0 (a flat window) is defined as 0. O(1) per update via two bounded VecDeques, O(window_len) memory.
Kaufman's Adaptive Moving Average (Perry J. Kaufman, Smarter Trading, 1995): sc = (ER*(fast_sc - slow_sc) + slow_sc)^2, fast/slow periods default 2/30, reusing KaufmanEfficiencyRatio internally. Seeded with the first value for which ER is ready. O(1) per update, O(window_len) memory.
Chande Momentum Oscillator (Tushar Chande, The New Technical Trader, 1994): 100*(sum_up - sum_down)/(sum_up + sum_down) over window_len, in [-100, 100]. 0/0 (a flat window) is defined as 0. O(1) per update via a bounded VecDeque of (up, down) pairs, O(window_len) memory.
Chande's Variable Index Dynamic Average (Tushar Chande, 'Adapting Moving Averages To Market Volatility', TASC 1992): an EMA whose smoothing factor alpha * k scales with the inner CMO's volatility index k = |CMO| / 100 in [0, 1], so it moves close to the input during a strong trend and barely moves in a flat/choppy market. Reference: VIDYA = alpha * k * x + (1 - alpha * k) * VIDYA[1], alpha = 2 / (window_len + 1), seeded with the first value for which the inner CMO is ready (after window_len + 1 inputs). Reuses the existing sliding_windows::Cmo<T, Echo<T>> internally rather than re-deriving the up/down move sums. Complexity: O(1) per update (the inner CMO is O(1) amortized); O(window_len) memory for its window. Deviation: tested the naive-reference step-by-step formula against a gentle multiplicative random walk rather than i.i.d. uniform noise, matching the convention already used for McGinleyDynamic and Kama, since CMO-driven smoothing factors are more representative of real price series that way. Also added a dedicated test asserting VIDYA freezes exactly when the volatility index is 0 (fully flat window), which is the degenerate case unique to this indicator.
Mark Jurik's JMA ('JMA - Jurik Moving Average', Technical Analysis of
Stocks & Commodities, 1999): a low-lag adaptive three-stage filter.
Reference: Jurik Research has never published the original algorithm, so
this implements the widely-circulated open reconstruction (beta/alpha/
phase_ratio filter chain over e0/e1/e2) independently confirmed across
pandas-ta's jma() core loop, TradingView Pine jma() libraries and MQL
ports, itself reverse-engineered from
https://c.mql5.com/forextsd/forum/164/jurik_1.pdf. This is the simplified,
non-adaptive-volatility variant (period/phase/power only); it is a close
approximation, not a bit-exact match of Jurik's proprietary binary.
Complexity: O(1) per update, O(1) memory - no window buffer is needed, only
three running filter states (e0, e1, e2).
Deviation: per task instructions, JMA was only attempted because the
three-stage phase/power reconstruction above is confidently and
consistently documented across multiple independent open-source ports (not
just one blog post), unlike some other 'community JMA' variants that
reconstruct Jurik's additional adaptive-volatility bands, which differ more
between implementations and are not attempted here. phase is clamped to
[-100, 100] rather than rejected (matching every reference above); power is
asserted to be in 1..=4 (the range used by existing open crates) since
larger exponents make alpha collapse towards 0 and the filter degenerate.
MACD (Appel, 1979): EMA(fast) - EMA(slow), signal = EMA(macd, signal_period), histogram = macd - signal. Classic defaults 12/26/9. Composes the existing Ema view internally (Ema<T, Echo<T>>) for fast/slow/signal, each O(1) per update, O(1) memory. Creates benches/oscillators.rs (this group's bench file) with one criterion benchmark_group per indicator; registers it in Cargo.toml.
Percentage Price Oscillator: 100*(EMA(fast)-EMA(slow))/EMA(slow), signal = EMA(ppo, signal_period), histogram = ppo - signal. Deviation: TA-Lib's PPO defaults to SMA; here EMA is always used (matches the common MACD-style PPO on most charting platforms), documented in the struct doc. O(1) per update via three chained Ema<T, Echo<T>>. Adds the ppo benchmark_group to benches/oscillators.rs.
Absolute Price Oscillator (TA-Lib APO): EMA(fast) - EMA(slow), no signal line. Classic defaults 12/26. O(1) per update via two chained Ema<T, Echo<T>>. Adds the apo benchmark_group to benches/oscillators.rs.
TRIX (Hutson, 1983; TA-Lib TRIX): 1-bar percent rate of change of a triple-cascaded EMA (EMA(EMA(EMA(x,n),n),n)). Warm-up gates each stage on the previous being ready, matching Macd/Ppo. O(1) per update via three chained Ema<T, Echo<T>>. Adds the trix benchmark_group to benches/oscillators.rs.
True Strength Index (William Blau, 1991): 100 * EMA(EMA(m,r),s) / EMA(EMA(|m|,r),s), m = x - x_prev; defaults r=25, s=13. Deviation: adds a signal line (EMA of TSI, default 7-period) via Tsi::signal, an extension not in Blau's original definition but common on charting platforms; documented in the struct doc. 0/0 is defined as 0. O(1) per update via four chained Ema<T, Echo<T>> plus the signal EMA. Adds the tsi benchmark_group to benches/oscillators.rs.
Stochastic RSI (Chande & Kroll, 1994; TA-Lib STOCHRSI fastk): (RSI - min(RSI,n)) / (max(RSI,n) - min(RSI,n)), %D = SMA(%K, d_period). Deviation: kept in [0, 1] instead of TA-Lib's *100 scaling (documented). Flat RSI window (max == min) defines %K as 0. Reuses Rsi, the monotonic Max/Min deques and Sma internally, all O(1) amortized per update. Adds the stoch_rsi benchmark_group to benches/oscillators.rs.
RSX - Jurik's 'noise free' RSI: a direct transliteration of the widely published open-source RSX recursion (f8..f90, matching the ProRealTime and everget/Pine Script ports byte-for-byte in formula terms), triple-smoothing momentum and |momentum| through cascaded EMA pairs blended as 1.5*fast - 0.5*slow, then ratioed and scaled to [0, 100]. Deviation: last() is Some from the first update (the formula defines its own short, period-independent internal warm-up that outputs the neutral 50), unlike this crate's usual None-during-warmup convention; documented in the struct doc. O(1) per update, O(1) memory (13 scalar state variables, no buffers).
Schaff Trend Cycle (Doug Schaff, 1999), the classic 'double stochastic of MACD' variant used by most MT4/MT5 ports: MACD(23,50) -> stochastic over a 10-bar cycle, smoothed with factor 0.5 -> stochastic again over the same cycle, smoothed with factor 0.5. Flat stochastic ranges hold the previous raw ratio instead of NaN. Output stays in [0, 100] since 0.5-smoothing is a convex combination of two in-range ratios. Reuses Ema and the monotonic Max/Min deques internally, all O(1) amortized per update. Adds the schaff_trend_cycle benchmark_group to benches/oscillators.rs.
Quantitative Qualitative Estimation (Yuri Reshetov): smooths RSI with an EMA (SF=5 default) into RsiMa, then builds a volatility band from a twice Wilder-smoothed ATR-of-RSI (period = 2*rsi_period-1), scaled by factor (default 4.236). A trailing stop-like level follows RsiMa and flips trend on a band cross (Qqe::trailing_level getter). Deviation: the original's trend/band update compares RsiMa against a one-bar-shifted view of the band series itself (a 'crossover of a crossover'), fragile to transcribe outside Pine Script. This instead detects the cross using the previous bar's RsiMa against the previous bar's band value directly, which matches the original closely while being well-defined bar-by-bar. RsiMa is in [0, 100]; last() is None until RSI+EMA warm up, and trailing_level() additionally needs both Wilder stages to warm up. O(1) per update, O(1) memory. Reused the crate's existing sliding-window Rsi (not Wilder-exponential) for consistency with StochRsi's prior deviation. Tests: plot, warm-up gating for both last() and trailing_level(), range check, trailing_level finiteness, constant-input RSI saturation at 100, and a naive-reference test independently reimplementing the sliding RSI, EMA, Wilder smoothing and band/trend logic against 5000 random values (max abs diff < 1e-9).
MathisWellmann
force-pushed
the
new-features
branch
from
October 12, 2026 15:47
497cb50 to
fbb6430
Compare
Bayesian online changepoint detection (Adams & MacKay 2007) with constant hazard 1/lambda and a Normal-Inverse-Gamma prior (mu0, kappa0, alpha0, beta0) for unknown mean and variance, Student-t predictive, all probabilities in log space (log-sum-exp) to avoid underflow. The ln Gamma ratio of the Student-t density is carried per run length with the recurrence ln G(a+1) - ln G(a+1/2) = ln a - (ln G(a+1/2) - ln G(a)), so only the constructor evaluates ln Gamma (private Stirling-series helper). Output is the MAP run length; changepoint_probability() is P(r_t <= 1) because P(r_t = 0) always equals the hazard under a constant hazard; expected_run_length() is the posterior mean. Run lengths are truncated at max_run_length: mass growing beyond it is merged into the last bin (keeping the statistics of the more probable merged run) and the distribution renormalized, so the MAP saturates instead of collapsing on long stationary data (deviation from plain drop-and-renormalize). O(max_run_length) time and preallocated memory per update.
Historical (non-parametric) Value at Risk over a sliding window of returns: VaR_c = -Q(1 - c) with the type 7 quantile of the crate-private SortedWindow, so a positive value is a loss. Useful as a tail-risk feature and position sizing input. O(log n) search + O(n) memmove per update, O(1) last(), O(n) memory. Adds benches/risk.rs.
Historical Conditional Value at Risk / expected shortfall (Rockafellar & Uryasev 2002) over a sliding window of returns: -mean of the returns <= the type 7 quantile Q(1 - c) (ties included, the tail is never empty), so CVaR >= VaR (clamped against ulp rounding on all-tie tails). Positive = loss. Captures tail severity that VaR ignores. Uses SortedWindow::sorted and count_less_equal, whose #[cfg(test)] lines are removed. O(n) memmove + O(n (1 - c)) tail sum per update, O(1) last(), O(n) memory.
Peter Martin's Ulcer Index (Martin & McCann 1989) over a window of n prices: sqrt(mean of the last n squared percent drawdowns), each drawdown measured from the classic rolling n-period max ending at that price (not the max within the averaging window), so the first output comes after 2n - 1 prices. Captures depth and duration of drawdowns, unlike volatility. A monotonic deque gives the window max and a running sum the mean square (recomputed every n updates and reset to exactly zero when no drawdown is in the window, as sqrt amplifies rounding residuals): amortized O(1) per update, O(n) preallocated memory.
Per-period ex-post Sharpe ratio (Sharpe 1966/1994) over a sliding window of returns: (mean - risk_free) / sample std (n - 1), not annualized. Risk-adjusted performance feature. O(1) per update with running sums of deviations from a shift value taken from the window (avoids catastrophic cancellation), recomputed and re-centered every window_len updates (amortized O(1)) to bound drift; O(n) memory. Zero std -> None, where zero means the sample variance is below 4 eps times the mean square deviation from the shift (rounding noise, e.g. constant returns). window_len must be >= 2.
Per-period Sortino ratio (Sortino & Price 1994) over a sliding window of returns: (mean - target) / downside deviation, with DD = sqrt(sum(min(0, r - target)^2) / n) over the whole window, not annualized. Penalizes only harmful volatility, unlike Sharpe. O(1) per update with running sums and a count of returns below the target, so the downside sum is exactly zero without downside (-> None, as the ratio is undefined); sums are recomputed every window_len updates (amortized O(1)) to bound drift; O(n) memory. Also exposes downside_deviation().
The crate gained about 95 views (Ehlers filters and cycle tools, classic moving averages and oscillators, regression, rolling statistics, complexity measures, adaptive estimators, change detection, risk). The README list is the entry point for users, so group them by category there.
The DSL can now build the Ehlers filters added to the crate at runtime:
high_pass, band_pass, ultimate_smoother, ultimate_bands, decycler,
decycler_oscillator, gaussian_filter, butterworth, ehlers_filter, dsma,
continuation_index and emd. The pipeline text comes from untrusted
authors (LLMs), so check_params is the validation boundary: parameters
that pass it must never reach a constructor assert.
Infrastructure, for this and the following categories:
- Param::Integer { min, max }: a small integer choice (pole count,
embedding order), mapped to the view's enum in build. Not a window,
so warm_up does not sum it.
- Param::Positive: a finite real above 0.
- Optional table fields: check (constraints between parameters and caps
that keep constructor asserts unreachable; errors are prefixed with the
signature) and warm_up (overrides the sum of windows for views with
fixed warm-ups or chained stages).
- Params accessors assert the declared kind, so a wiring mistake in the
table fails on the first call instead of misreading a value.
- example_params/larger_params (tests): valid parameters at the small
and larger end. The property tests now run every function at both.
- A table-driven test compares each call with the directly constructed
view, to catch parameter order and pole mapping mistakes.
Band-pass and Butterworth periods are capped at 100000: their recursions
lose accuracy to rounding beyond (the three-pole Butterworth diverges by
1e7) and narrow band-passes round to an unstable pole. Views above about
200 bytes are boxed in State, so they do not inflate every state slot.
Adds homodyne_dominant_cycle, hilbert_phasor, sine_wave, instantaneous_trendline, hilbert_trend_mode, mama, autocorrelation_periodogram, even_better_sinewave, adaptive_rsi, adaptive_laguerre_filter, auto_tune_filter and adaptive_super_smoother. - The Hilbert transform views and the autocorrelation periodogram family have fixed internals, so they take no parameters and override the warm-up (50, 63, 32 bars; 109 and 119 for the periodogram and the adaptive RSI, from their filter chains). The periodogram keeps the book's periods 10 to 48: its O(L^2) update stays small, and two period parameters would need disjoint ranges to be checkable. - auto_tune_filter costs O(window^2) per update, so its window is capped at 100 by a check; mama checks slow limit <= fast limit. - hilbert_trend_mode requires positive input: its 1.5% trendline rule is relative and always says trend near zero. - Ranges: cycle periods [6, 50] and [10, 48], phasor angle [-180, 180], sine waves [-1, 1], trend mode [0, 1]; mama and the instantaneous trendline stay within the input range (convex averages). The adaptive RSI is real-valued: its super smoother can overshoot [0, 1]. - Views above 200 bytes are boxed in State. View fix (squashed into 'sliding_windows: add HomodyneDominantCycle'): the periods are smoothed from zero, so after the 50-bar warm-up they were still below 6 on constant input (5.9997); the reported periods are now clamped to [6, 50] as documented.
Adds inverse_fisher, ehlers_stochastic, universal_oscillator and reversion_index. - inverse_fisher is stateless, so it is a Pure variant like tanh: tanh(gain * a) with a positive gain, and its range maps the argument's domain through the increasing function. - universal_oscillator is in [-1, 1] by construction (divided by a peak of at least |filt|). ehlers_stochastic and reversion_index end in a super smoother of a bounded ratio, which can overshoot, so their ranges are real. - Warm-ups: ehlers_stochastic needs the roofing filter, a full stochastic window and the smoother of the same length; the universal oscillator two inputs before its smoother. - EhlersStochastic is boxed in State (above 200 bytes). No view needed a fix: a suspected underflow of the universal oscillator's decaying peak on long constant input does not happen (0.991 times the smallest subnormal rounds back to it).
Adds wma, rma, dema, tema, t3, zlema, hma, ehma, trima, mc_ginley_dynamic, kama, vidya and jma. The convex combinations of past inputs (wma, rma, trima, kama, vidya and the now step-capped mc_ginley_dynamic) declare the input range; the lag-reducing averages (dema, tema, t3, zlema, hma, ehma, jma) overshoot on reversals, so they declare the real line. Chained stages override the warm-up: dema 2w - 1, tema 3w - 2, t3 6w - 5, zlema w + (w - 1) / 2, hma/ehma w + isqrt(w), kama/vidya w + 1. t3 exposes the classic volume factor as a fraction; jma exposes the phase, checked to lie in [-100, 100] because the view clamps beyond it silently; kama and mc_ginley_dynamic use the views' defaults (fast 2, slow 30; k 0.6). mc_ginley_dynamic requires positive input (it divides by the ratio of input to average). tema, t3, hma and ehma are boxed (over 200 bytes). View fixes found by the adversarial property tests, squashed into the views' revisions: Cmo and KaufmanEfficiencyRatio floor their running sums at 0 and clamp the ratio to its proven bound (rounding broke the debug asserts and the ranges); McGinleyDynamic caps its step at 1, as a drop made the raw formula overshoot negative and diverge to infinity.
The running average gain and loss are updated by adding and subtracting change / window. Moves leave the window in another order than they entered it, so rounding can leave an average slightly below zero once its moves left the window: the RSI then fell below 0 or above 100, or became infinite when 1 + rs rounded to 0. Integer prices (e.g. ticks) hit this, as does ConnorsRsi's streak RSI. Floor the averages at 0 and clamp the RSI. Rsi's own history is immutable, so the fix is its own revision.
Adds cmo, macd, ppo, trix, tsi, stoch_rsi, rsx, schaff_trend_cycle, connors_rsi, dpo, coppock and kst. Multi-output views expose their main line only (MACD, PPO, TSI, %K, KST); signal lines compose, e.g. ema(macd(x, 12, 26), 9). macd, ppo and schaff_trend_cycle check fast < slow, which their constructors assert. ppo, trix, coppock and kst are percentages of the input level, so they require positive input; that bounds them below (-100, -100, -200, -1000). cmo and tsi lie in [-100, 100], stoch_rsi in [0, 1], rsx, schaff_trend_cycle and connors_rsi in [0, 100]. Warm-up overrides: cmo w + 1, macd/ppo the slow window, trix 3w - 1, schaff_trend_cycle slow + 2 cycle, dpo max(w, w / 2 + 2); coppock and kst count until their rates of change compare against full windows (the views report earlier against the first value). kst takes no parameters (Pring's nine classic periods). Skipped: Apo, whose output equals macd's MACD line; Qqe, whose main output RsiMa is exactly ema(rsi(x, n), sf) and whose QQE-specific trailing level is a getter. A fuzz over integer price walks found ConnorsRsi leaving [0, 100] through Rsi's rounding; that fix is the parent revision, as Rsi's history is immutable.
Adds efficiency_ratio (KaufmanEfficiencyRatio), lin_reg_slope, lin_reg_intercept, lin_reg_angle, r_squared, tsf, standard_error and vhf. min_window follows the constructors' asserts: 2 for the regression views and vhf, 3 for standard_error (n - 2 degrees of freedom), 1 for the efficiency ratio. efficiency_ratio, r_squared and vhf lie in [0, 1] (vhf by the triangle inequality: the window's range is at most the path between its extremes, although its docs call it unbounded), lin_reg_angle in [-90, 90], standard_error is non-negative; slope, intercept and tsf are real (the fitted line extrapolates past the input range). efficiency_ratio and vhf need one value before their window (warm-up window + 1). vhf costs O(window) per update, said in its doc. The rounding fix of KaufmanEfficiencyRatio (clamp to [0, 1]) was squashed into its revision with the moving average functions, where kama found it.
Adds quantile (RollingQuantile), median (RollingMedian), iqr, percentile_rank, gaussian_rank (QuantileNormalizer), skewness, kurtosis, coefficient_of_variation, ewma_variance, and the stateless power transforms box_cox and yeo_johnson as Pure variants with a real lambda. min_window follows the constructors' asserts (iqr and the coefficient of variation 2, skewness 3, kurtosis 4) and is 2 elsewhere, where a single value is degenerate. quantile takes q as a Fraction: q = 0 and 1 are min and max. coefficient_of_variation requires a positive input: it is a dispersion relative to the level, which explodes near a zero mean. Ranges: quantile and median stay within the input; iqr lies in [0, width of the input] and ewma_variance in [0, (width / 2)^2] (Popoviciu: Finch's recursion is an exactly weighted variance); percentile_rank in [0, 100] (the view's scale); gaussian_rank within the normal quantiles of the extreme plotting positions 1 / (n + 1) and n / (n + 1), computed with the view's inverse_normal_cdf (now pub(crate)); skewness and kurtosis are real, as their exact-arithmetic bounds fail for windows that are flat up to round-off. The transforms map the argument domain, as both are increasing in it for every lambda. BoxCox and YeoJohnson asserted a finite output in debug builds, which a large lambda overflows (1e12^30); they now return None there. The fixes were squashed into the revisions that added them.
Adds shannon_entropy, permutation_entropy, sample_entropy, hurst (HurstExponent), variance_ratio, autocorrelation and ou_half_life. shannon_entropy bins over [lo, lo + width] (lo Real, width Positive) instead of lo and hi: two Reals cannot express lo < hi, and the test parameters (both 0.5) would violate that check. A check still rejects a width that rounding erases at lo or an end that overflows, which the constructor asserts. Bins are Integer 2 to 100, the permutation order Integer 2 to 7 (the view's limits, delay fixed at 1) and the sample entropy template length Integer 1 to 5. Costs: sample_entropy is O(window^2 m) per update and capped at window 100 (check_quadratic_window); hurst is O(window log window) and capped at 1000 (about 10^4 operations per update). sample_entropy needs a window of 20: shorter windows have so few template pairs that it is mostly undefined. The lag of autocorrelation and the period of variance_ratio are windows that come before the window that must exceed them, as macd's fast window does. Ranges: the entropies in [0, 1], sample_entropy in [0, ln(pairs)] (at least one pair matches), autocorrelation in [-1, 1], variance_ratio non-negative, ou_half_life non-negative (the view reports exactly 0 for an AR(1) coefficient of 0, so positive would be unsound), hurst real (not clamped). Warm-ups: permutation_entropy window + order - 1, ou_half_life window + 1, autocorrelation and variance_ratio their window. No view needed a fix.
Adds one_euro, alpha_beta, kalman, huber_ema, nlms, rls and hampel. - one_euro(min_cutoff, beta): derivative cutoff fixed at the reference default 1; beta is a real checked to be non-negative, as 0 is a plain EMA. kalman, huber_ema, hampel and one_euro are convex updates or pick an input value, so they keep the argument's range. - alpha_beta(alpha, beta): checks beta <= 2 and 4 - 2 alpha - beta > 0 exactly as the constructor computes it, since rounding decides near alpha = 1, beta = 2. Range is any real (it overshoots turns). - kalman(q, r): checks 2 (q + r) is finite, so the variance sums of the update cannot overflow. - nlms(order 1..=64, mu < 2): regularizer fixed at 1e-12, which bounds a weight step while the taps are near zero. rls(order 1..=32, lambda): O(order^2) per update, so the order is capped; delta fixed at 0.01. Both override the warm-up with order + 1. - hampel(window 3..=1000, t): O(window log window) per update, capped like hurst. - LmsFilter is not offered: its step must suit the unknown input power, so no parameter check keeps it from diverging; nlms is the same predictor with a scale-free step. RlsFilter was fixed in its own revision: zeros, constants or too short a memory for the order let P wind up by 1 / lambda per update until rounding made it indefinite and it overflowed (a debug assert fired on a random walk with order 4, lambda 0.5 after 8000 updates). It now resets P to I / delta (covariance resetting) once an entry exceeds 1e6 / delta or h^T P h turns negative.
Adds cusum, page_hinkley, bocpd, adwin, var, cvar, ulcer_index, sharpe and sortino. Detectors output their statistic (the views' last()), not their alarms. - cusum(k, h): target mean 0 and std 1 are fixed, as the DSL's arithmetic already standardizes (`cusum((x - m) / s, k, h)` or `cusum(zscore(x, n), k, h)`). Range: -max(0, h - lo - k) to max(0, h + hi - k), as both statistics are at most h before an update. - page_hinkley(min_instances, delta, lambda, direction): direction is an integer, 0 both, 1 increases, 2 decreases; forgetting fixed at scikit-multiflow's 0.9999. Range: |statistic| <= max(min_instances w, lambda + w) with w = max(0, hi - lo - delta), one-sided per direction. - bocpd(lambda, max_run_length): both windows (lambda > 1 must hold for the 0.5 example reals too), max_run_length capped at 1000 (O(n) per update), prior for standardized input; output in [0, max_run_length]; warm-up 0, ready from the first value. - adwin(delta): MOA's 5 buckets and clock 32; the window mean keeps the input's range. - var/cvar(window, confidence): negated input range; O(window) per update accepted and documented like quantile. - ulcer_index(window): requires positive prices, in [0, 100], warm-up 2 window - 1. - sharpe/sortino(window, rate): |rate| <= 1e6, so the excess over the smallest positive deviation cannot overflow. Sortino is at least -1 (the mean excess is at least minus the downside deviation). The warm-up test feeds sortino returns (roc), as on prices around 100 no value is ever below its target. View fixes, each in the view's own revision: - Bocpd panicked in release (assert) once a value's squared deviation overflowed (|x| beyond about 1e154): it maps a NaN predictive to an impossible value and starts a new run when every run is impossible. - SortinoRatio hit a debug assert (and returned -inf) when the downside deviation underflowed to 0 for deviations below about 1e-162: it is now not ready then.
Without getter outputs its DSL function returned the center line, i.e. exactly ultimate_smoother, and its num_sds had no effect. A duplicate name only confuses pipeline authors (an LLM reading the reference), so remove it. The UltimateBands view keeps its band getters for code users.
RoofingFilter::new takes the high-pass cutoff period first (the HP alpha uses it) and the SuperSmoother period second. The DSL doc said 'low-pass window, high-pass window', which reads the other way round, so an LLM following the reference would swap them. Name both by what they do and give Ehlers' usual 48, 10 as example.
MathisWellmann
force-pushed
the
new-features
branch
from
October 12, 2026 16:13
fbb6430 to
c2e0ef1
Compare
Adding a pipeline function or parameter kind changed these public exhaustive enums, which made every such addition a semver-major change (cargo-semver-checks: enum_variant_added). Since 12.0.0 is a major release anyway, mark them non-exhaustive now so future functions and parameter kinds only need a minor bump. Downstream code iterates Function::ALL rather than matching every variant.
Breaking per cargo-semver-checks against 11.0.0: the public exhaustive enums pipeline::Function and pipeline::Param gained variants (enum_variant_added), and Param::Integer { min, max } is a struct variant, so Param's discriminants are no longer defined (enum_discriminants_undefined_non_unit_variant). Both enums are now #[non_exhaustive], so future additions only need a minor bump. Everything else is additive.
MathisWellmann
force-pushed
the
new-features
branch
from
October 12, 2026 16:21
6ad397b to
efbc1e0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
John Ehlers filters
Rolling statistics
Complexity and dependence
Adaptive estimators and robust filters
Change detection
Risk