Add new moving average implementations: LTMA, MCNMA, NLMA, NMA, NYQMA, RAIN, and TRAMA

- LTMA (Linear Trend Moving Average): Introduces a predictive moving average using dual cascaded EMAs for trend estimation.
- MCNMA (McNicholl EMA): Implements a zero-lag TEMA using a cascaded EMA structure for enhanced responsiveness.
- NLMA (Non-Lag Moving Average): Utilizes a damped cosine kernel to achieve reduced lag in moving averages.
- NMA (Natural Moving Average): Adapts smoothing based on volatility profiles using a square-root kernel.
- NYQMA (Nyquist Moving Average): Applies the Nyquist-Shannon theorem to prevent aliasing in cascaded moving averages.
- RAIN (Rainbow Moving Average): Combines multiple SMA layers with weighted averages for multi-scale smoothing.
- TRAMA (Trend Regularity Adaptive Moving Average): Adapts smoothing based on the frequency of new highs and lows in price data.
This commit is contained in:
Miha Kralj
2026-02-20 21:40:32 -08:00
parent cbeefc9d64
commit 90d5638008
121 changed files with 9595 additions and 6315 deletions
+63 -47
View File
@@ -1,77 +1,93 @@
# ADXR: Average Directional Movement Rating
> If ADX is the speedometer, ADXR is the cruise control setting. It smooths out the acceleration to tell you if the trend has staying power.
The Average Directional Movement Rating (ADXR) is a smoothed version of the ADX. It dampens the volatility of the ADX itself, providing a more stable—albeit significantly more lagging—measure of trend strength. It is primarily used to rate the efficacy of trend-following strategies before capital is committed.
The Average Directional Movement Rating is a smoothed version of ADX that dampens short-term fluctuations in trend strength by averaging the current ADX with a historical ADX value. This creates a doubly-lagged metric that sacrifices all timing utility in exchange for stable regime classification. ADXR answers one question: does the current market environment reward trend-following strategies? If ADXR is high, deploy momentum logic. If low, deploy mean-reversion. It is a strategic filter, not a tactical signal.
## Historical Context
J. Welles Wilder Jr. introduced ADXR alongside ADX in *New Concepts in Technical Trading Systems* (1978). His goal was simple: ADX can be erratic. By averaging the current ADX with a past ADX, he created a metric that ignores short-term fluctuations in trend strength.
It is effectively a "momentum of momentum" indicator, smoothed to the point of geological stability.
J. Welles Wilder Jr. introduced ADXR alongside ADX in *New Concepts in Technical Trading Systems* (1978). His reasoning was pragmatic: ADX itself can be erratic during transitions between trending and ranging regimes, producing whipsaw readings that confuse systematic allocation. By averaging the current ADX with its value from $N-1$ bars ago, Wilder created a "momentum of momentum" indicator smoothed to geological stability. The ADXR found its architectural niche not as a trading signal but as a capital allocation filter — determining whether a trend-following system should be active at all. Its double lag (ADX already lags price; ADXR lags ADX) makes it useless for entry timing by design.
## Architecture & Physics
ADXR is a composite indicator. It does not interact with price directly; it interacts with the output of the ADX.
### 1. ADX Dependency
1. **Dependency**: It instantiates and maintains a full `Adx` indicator internally.
2. **History**: It maintains a circular buffer of historical ADX values.
3. **Averaging**: It computes the arithmetic mean of the current ADX and the ADX from `Period - 1` bars ago.
ADXR is a composite indicator that does not interact with price directly. It instantiates and maintains a full ADX pipeline internally:
### The Lag Trade-off
$$\text{Price} \rightarrow \text{DM/TR} \rightarrow \text{RMA} \rightarrow \text{DI} \rightarrow \text{DX} \rightarrow \text{ADX} \rightarrow \text{ADXR}$$
ADXR is intentionally slow.
### 2. Historical Buffer
* **ADX** lags price because of its multiple smoothing layers.
* **ADXR** lags ADX because it averages the current value with a value from the distant past.
A circular buffer of size $N$ stores historical ADX values, providing $O(1)$ access to the value from $N-1$ bars ago.
This double lag makes ADXR useless for entry timing. Its only valid architectural purpose is **regime filtering**: determining *if* a trend-following system should be active, not *when* it should trade.
### 3. Rating Calculation
$$ADXR_t = \frac{ADX_t + ADX_{t-(N-1)}}{2}$$
The $N-1$ lag (rather than $N$) matches TA-Lib's reference implementation exactly.
### 4. Complexity
- **Time:** $O(1)$ per bar — ADX update plus one buffer lookup and one average
- **Space:** $O(N)$ — circular buffer for ADX history
- **Warmup:** $\approx 3N$ bars (ADX convergence + buffer fill)
## Mathematical Foundation
The formula is deceptively simple, but relies on the complex ADX calculation underneath.
### Parameters
$$ ADXR_t = \frac{ADX_t + ADX_{t-(n-1)}}{2} $$
| Symbol | Parameter | Default | Constraint |
|--------|-----------|---------|------------|
| $N$ | period | 14 | $N \geq 2$ |
Where:
The period controls both the internal ADX calculation and the historical lookback depth.
* $ADX_t$ is the current ADX value.
* $n$ is the Period (typically 14).
* $ADX_{t-(n-1)}$ is the ADX value from `n-1` periods ago.
### Pseudo-code
*Note: The `n-1` lag is used to match TA-Lib's implementation exactly. Some sources cite `n`, but standard reference implementations use `n-1`.*
```
Initialize:
adx = new Adx(period)
adxBuffer = RingBuffer(period)
bar_count = 0
## Performance Profile
On each bar (high, low, close, isNew):
if !isNew: restore previous state
The performance cost is dominated by the underlying ADX calculation. The ADXR step itself is trivial.
// Full ADX pipeline
adxValue = adx.Update(high, low, close, isNew)
### Zero-Allocation Design
// Store in history
adxBuffer.Add(adxValue)
bar_count++
The implementation uses a circular buffer (`RingBuffer`) to store historical ADX values, ensuring O(1) access and zero heap allocations during the update cycle.
// ADXR = average of current and (N-1)-lagged ADX
if bar_count >= period:
historicalAdx = adxBuffer[0] // oldest value in buffer
ADXR = (adxValue + historicalAdx) / 2.0
else:
ADXR = adxValue // insufficient history
| Metric | Score | Notes |
| :--- | :--- | :--- |
| **Throughput** | 6ns | 6ns / bar (Apple M1 Max). |
| **Allocations** | 0 | Hot path is allocation-free. |
| **Complexity** | O(1) | Ring buffer access is constant time. |
| **Accuracy** | 10/10 | Matches TA-Lib to 1e-9. |
| **Timeliness** | 1/10 | Double lag (ADX + History). |
| **Overshoot** | 10/10 | Extremely stable. |
| **Smoothness** | 10/10 | Extremely stable trend rating. |
output = ADXR
```
## Validation
### Lag Analysis
Validation is performed against industry-standard libraries.
| Component | Lag Source |
|-----------|-----------|
| DM → RMA | $\approx N$ bars (Wilder smoothing) |
| DX → ADX | $\approx N$ bars (second RMA) |
| ADX → ADXR | $N-1$ bars (historical average) |
| **Total effective lag** | $\approx 3N - 1$ bars |
| Library | Status | Notes |
| :--- | :--- | :--- |
| **QuanTAlib** | ✅ | Validated. |
| **TA-Lib** | ✅ | Matches `TA_ADXR` to 1e-9. |
| **Skender** | N/A | Not implemented in Skender. |
| **Tulip** | ✅ | Matches `ti.adxr`. |
| **Ooples** | N/A | Not implemented. |
For the default period of 14, ADXR carries roughly 41 bars of effective lag. This is a feature, not a limitation — it ensures that only sustained regime changes register in the output.
### Common Pitfalls
### Regime Classification
* **Using for Entries**: Do not use ADXR crossovers for entries. The signal is too late.
* **Short Periods**: Using a short period (e.g., 3) defeats the purpose of ADXR. If you want responsiveness, use ADX. ADXR is for stability.
| ADXR Value | Interpretation |
|------------|----------------|
| < 20 | Sustained range-bound; favor mean-reversion |
| 2025 | Ambiguous regime; reduce position sizing |
| > 25 | Sustained trending; favor momentum strategies |
## Resources
- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978)
- PineScript reference: `adxr.pine` in indicator directory