Files
QuanTAlib/lib/trends_IIR/rema/Rema.md
T
Miha Kralj 67ad6f0cba v0.8.7: Replace periodic ResyncInterval with Kahan compensated summation
Comprehensive refactor across all indicators replacing the periodic
ResyncInterval-based drift correction (every 1000 ticks recalculate
from scratch) with Kahan compensated summation for running sums.

Key changes:
- Remove ResyncInterval constants and TickCount fields from all State records
- Add Kahan compensation fields (SumComp, SumSqComp, etc.) to State records
- Replace naive sum += val - removed with Kahan delta pattern
- Remove Resync()/RecalculateSum() methods that did O(N) recalculation
- Update batch/SIMD paths to use Kahan compensation instead of resync loops
- IIR filters (EMA, REMA, RGMA) simplified: inherently self-correcting
- Version bump to 0.8.7
- Build system: README version stamping via Directory.Build.props
- Minor doc/test tolerance adjustments for new numerical characteristics

Affected modules: channels, core, cycles, dynamics, errors, momentum,
oscillators, statistics, trends_FIR, trends_IIR, volatility, volume
2026-03-13 22:01:31 -07:00

9.8 KiB
Raw Blame History

REMA: Regularized Exponential Moving Average

Someone looked at the EMA and thought: 'What if we punished it for changing its mind?' The result is REMA—an EMA with a conscience that remembers where it was going and resists the temptation to chase every price wiggle.

Property Value
Category Trend (IIR MA)
Inputs Source (close)
Parameters period, lambda (default 0.5)
Outputs Single series (Rema)
Output range Tracks input
Warmup period bars
PineScript rema.pine
Signature rema_signature
  • REMA (Regularized Exponential Moving Average) combines exponential smoothing with a regularization term that penalizes deviations from the previous...
  • Similar: EMA, DEMA | Complementary: Volatility filters | Trading note: Regularized EMA; lambda term reduces whipsaws.
  • Validated against TA-Lib, Skender, and Tulip reference implementations where available.

REMA (Regularized Exponential Moving Average) combines exponential smoothing with a regularization term that penalizes deviations from the previous trend direction. The result is a filter that responds to genuine price movements while suppressing noise-induced oscillations. Think of it as an EMA with momentum awareness: it knows where it was heading and applies a penalty for sudden course corrections.

Historical Context

The concept of regularization comes from machine learning and signal processing, where it's used to prevent overfitting by penalizing model complexity. REMA applies this principle to moving averages: the "complexity" being penalized is deviation from the established trend. When price noise tries to yank the average in a new direction, the regularization term pushes back, saying "prove it." The lambda parameter controls how much proof is required—at lambda=1, REMA believes everything (standard EMA); at lambda=0, it's pure momentum that ignores new information entirely.

Architecture & Physics

REMA introduces a two-component calculation:

  1. EMA Component: Standard exponential smoothing that responds to new prices
  2. Regularization Component: Momentum continuation that extrapolates the previous trend

The lambda parameter blends these components:

  • lambda = 1: Pure EMA behavior. Every price gets full consideration.
  • lambda = 0.5: Balanced. New prices compete with trend momentum.
  • lambda = 0: Pure momentum extrapolation. New prices are ignored entirely (not recommended).

The regularization component calculates where the average would be if the current trend continued unchanged. The final REMA value is a weighted blend between where EMA wants to go (following price) and where momentum wants to go (continuing trend).

The Compensator (Warmup Correction)

Like QuanTAlib's EMA implementation, REMA includes a mathematical compensator that corrects for initialization bias. The first N bars aren't approximations—they're mathematically valid from bar one. This means REMA(lambda=1) will match QuanTAlib's EMA implementation exactly, including the bias-corrected warmup period.

Mathematical Foundation

The standard EMA alpha calculation:

\alpha = \frac{2}{N + 1}

The EMA component (standard exponential smoothing):

\text{EMA}_t = \alpha \cdot (P_t - \text{REMA}_{t-1}) + \text{REMA}_{t-1}

The regularization component (momentum continuation):

\text{REG}_t = \text{REMA}_{t-1} + (\text{REMA}_{t-1} - \text{REMA}_{t-2})

The final REMA calculation:

\text{REMA}_t = \lambda \cdot (\text{EMA}_t - \text{REG}_t) + \text{REG}_t

This can be expanded:

\text{REMA}_t = \lambda \cdot \text{EMA}_t + (1 - \lambda) \cdot \text{REG}_t

When \lambda = 1: \text{REMA}_t = \text{EMA}_t (standard EMA)

When \lambda = 0: \text{REMA}_t = \text{REG}_t (pure momentum extrapolation)

Bias Compensation

To handle initialization bias, the compensator tracks the sum of weights:

E_t = (1 - \alpha)^t \text{Corrected REMA}_t = \frac{\text{Uncorrected REMA}_t}{1 - E_t}

Performance Profile

Operation Count (Streaming Mode)

REMA combines EMA with a regularization term that extrapolates trend:

Operation Count Cost (cycles) Subtotal
SUB (Pt - REMAt-1) 1 1 1
FMA (EMA update) 1 4 4
SUB (momentum: REMAt-1 - REMAt-2) 1 1 1
ADD (REG: prev + momentum) 1 1 1
SUB (EMA - REG) 1 1 1
FMA (λ × diff + REG) 1 4 4
Total (hot) 6 ~12 cycles

During warmup (bias compensation active):

Operation Count Cost (cycles) Subtotal
MUL (E × decay) 1 3 3
SUB (1 - E) 1 1 1
DIV (correction) 1 15 15
CMP (warmup check) 1 1 1
Warmup overhead 4 ~20 cycles

Total during warmup: ~32 cycles/bar; Post-warmup: ~12 cycles/bar.

Batch Mode (SIMD Analysis)

REMA is inherently recursive due to state dependency on previous two values. SIMD parallelization across bars is not possible:

Optimization Benefit
FMA instructions Already using 2 FMAs per bar
State locality REMA + PrevRema fit in registers

Benchmark Results

Metric Value Notes
Throughput (Batch) ~400 μs / 500K bars ~0.8 ns/bar
Throughput (Streaming) ~2 ns/bar Single Update() call
Allocations (Hot Path) 0 bytes Verified via BenchmarkDotNet
Complexity O(1) Two FMA operations per bar
State Size 44 bytes REMA, PrevRema, E, flags

Quality Metrics

Quality Score (1-10) Notes
Accuracy 8 Tracks price well when lambda > 0.5
Timeliness 7 Regularization adds slight lag vs pure EMA
Smoothness 9 Primary benefit—significantly smoother than EMA
Overshoot 3 Low overshoot due to momentum awareness

Usage Examples

// Streaming: Process one bar at a time
var rema = new Rema(20, lambda: 0.5);  // 20-period, balanced regularization
foreach (var bar in liveStream)
{
    var result = rema.Update(new TValue(bar.Time, bar.Close));
    Console.WriteLine($"REMA: {result.Value:F2}");
}

// Different lambda values for different behaviors
var smooth = new Rema(20, lambda: 0.3);   // Strong regularization, very smooth
var balanced = new Rema(20, lambda: 0.5); // Balanced (default)
var responsive = new Rema(20, lambda: 0.8); // Weak regularization, more responsive

// When lambda = 1, REMA equals EMA
var asEma = new Rema(20, lambda: 1.0);  // Equivalent to Ema(20)

// Batch processing with Span (zero allocation)
double[] prices = LoadHistoricalData();
double[] remaValues = new double[prices.Length];
Rema.Batch(prices.AsSpan(), remaValues.AsSpan(), period: 20, lambda: 0.5);

// Batch processing with TSeries
var series = new TSeries();
// ... populate series ...
var results = Rema.Batch(series, period: 20, lambda: 0.5);

// Event-driven chaining
var source = new TSeries();
var rema20 = new Rema(source, 20, 0.5);  // Auto-updates when source changes
source.Add(new TValue(DateTime.UtcNow, 100.0));  // REMA updates

// Pre-load with historical data
var rema = new Rema(20, 0.5);
rema.Prime(historicalPrices);  // Ready to process live data immediately

Validation

Validated in Rema.Validation.Tests.cs:

Test Status Notes
Lambda=1 matches EMA REMA(period, 1.0) equals EMA(period)
Mode consistency Batch, Streaming, Span, Eventing all match
Smoothing behavior Lower lambda produces smoother output
Prime consistency Prime() produces same results as streaming

Run validation: dotnet test --filter "FullyQualifiedName~RemaValidation"

Common Pitfalls

  1. Lambda Confusion: lambda=1 is standard EMA (no regularization), lambda=0 is pure momentum (ignores new prices). Most use cases want something in between. Start with 0.5 and adjust based on your tolerance for lag vs smoothness.

  2. Not a Prediction Tool: The regularization component extrapolates trend, but REMA is not a forecasting indicator. It's a filter that resists noise. Don't interpret the momentum component as a price prediction.

  3. Comparing to Other Implementations: REMA isn't standardized across platforms. The formula here matches the PineScript reference implementation. Other platforms may implement "regularized" averages differently.

  4. Over-regularization: Setting lambda too low (below 0.3) makes REMA extremely laggy and unresponsive. It will miss genuine trend changes. Use lower lambda values only for visualization or as a baseline reference, not for signal generation.

  5. Using REMA(20, 0.5) Like EMA(20): Due to regularization, REMA with lambda < 1 will lag behind EMA. If you're replacing an EMA-based strategy, you may need to reduce the period to compensate, or use higher lambda values.

  6. Forgetting isNew for Live Data: When processing live ticks within the same bar, use Update(value, isNew: false) to update without advancing state. Use isNew: true (default) only when a new bar opens.

When to Use REMA

REMA is ideal when:

  • You need smoother signals than EMA provides
  • Noise-induced whipsaws are causing false signals
  • You want to maintain trend-following behavior with reduced sensitivity to outliers
  • Your strategy benefits from a filter that "commits" to trends

REMA is less suitable when:

  • You need maximum responsiveness (use EMA instead)
  • You're comparing against external libraries that don't implement REMA
  • You need predictable, standardized behavior across platforms