Skip to content

Improve EMA indicator parity with Cython #2642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 16, 2025
Merged

Improve EMA indicator parity with Cython #2642

merged 1 commit into from
May 16, 2025

Conversation

nicolad
Copy link
Collaborator

@nicolad nicolad commented May 16, 2025

Improve EMA indicator parity with Cython

Restores feature-parity and behavioural equivalence between the Rust ExponentialMovingAverage (EMA) implementation and the canonical Python/Cython reference.


1 Why this matters — Context & Motivation

Theme Why it matters
Correctness ⇄ Consistency Mixed Python (quant research) + Rust/WASM (execution) stacks must produce identical indicator values or silent PnL drift creeps in.
Predictable warm-ups & resets Edge-cases (e.g., period = 1, reset() after NaN) previously left the indicator in an undefined state.
Baseline for SIMD hot-paths Before vectorising we need a byte-for-byte port; this PR closes the last gaps.

2 What changed

Concern Python/Cython (🏆 truth) Rust before Rust after (this PR)
Parameter validation period > 0 ✘ none assert!(period > 0)
alpha for period = 1 ✔ α = 1 → immediate tracking ✘ α < 1 ✔ α = 1, unit-tested
First-sample handling ✔ initialises & count = 1 count = 0 ✔ aligned
count increments ✔ every update ✘ skipped on first ✔ parity
reset() semantics ✔ full zero-state ✘ stale has_inputs ✔ cleared
Display::fmt n/a ✘ extra comma ✔ fixed
Thread-safety docs n/a ✘ missing ✔ explicit not Send + Sync
Test coverage full partial full parity + 🆕 rstest suite

3 Five practical ways to put EMA to work

# Use-case Why EMA helps Wiring sketch
1 Classic cross-over Baseline lag is acceptable for many trend followers. long when price > EMA, exit on cross-down.
2 Dynamic position sizing EMA slope ≈ momentum; scale size by slope / ATR. size = k · d(EMA)/dt.
3 Mid-price estimator in MM engines Smooths quote flicker better than mid-ticks. quote_mid = EMA(last_trade).
4 Volatility gating σ(price – EMA) ≈ realised σ without extra filters. Open only when σ < threshold.
5 Time-series momentum screener Rank assets by close / EMA – 1. Nightly batch pipeline.

4 Implementation notes

  • Guardrailsperiod > 0 panic matches Python.
  • Period = 1 fast-path — α = 1, so EMA == last sample; avoids divide-by-zero.
  • has_inputs lifecycle — toggled on first sample, cleared on reset().
  • NaN poisoning — once a NaN enters, value stays NaN until reset().
  • Thread-safety — indicator itself is not Send + Sync; wrap in Mutex/Arc for multi-threaded use.
  • Public API — no breaking signature changes; only stricter panics.

5 Tests added / updated

  • test_period_one_behaviour — α = 1 behaviour
  • test_nan_poisoning_and_reset_recovery
  • test_has_inputs_lifecycle
  • test_subnormal_inputs_do_not_underflow
  • Regression: new_panics_on_zero_period

Related: #2507

@nicolad nicolad requested a review from cjdsellers May 16, 2025 14:56
@nicolad nicolad self-assigned this May 16, 2025
Copy link
Member

@cjdsellers cjdsellers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @nicolad, many thanks!

@cjdsellers cjdsellers merged commit 28a8113 into develop May 16, 2025
17 checks passed
@cjdsellers cjdsellers deleted the 2507-ema branch May 16, 2025 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants