- DYMI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator be...
DYMI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator becomes more responsive; when price action tightens, the period extends and the output smooths. The result is an oscillator that self-adjusts its sensitivity to the market's current state, avoiding both the lag of long fixed-period RSIs in trending regimes and the noise of short-period RSIs in ranging ones.
Tushar Chande and Stanley Kroll introduced DYMI in *The New Technical Trader* (1994) as a practical answer to a genuine problem: the standard RSI's fixed period is a blunt instrument. A 14-bar RSI responds identically whether the market has been oscillating ±5% per day or ±0.2%. Chande and Kroll observed that a shorter period in high-volatility environments catches reversals earlier; a longer period in quiet conditions eliminates whipsaws.
The mechanism they chose was straightforward: compute the ratio of short-term to long-term price standard deviation. When this ratio exceeds 1, the market is more volatile than its recent baseline — shorten the period. When the ratio is below 1, lengthen it. The result gets clamped to a configurable `[minPeriod, maxPeriod]` range, and a standard Wilder RSI runs on the resulting dynamic period.
The indicator has no widely adopted C# open-source implementation, which is why cross-library validation is self-consistency only. The original book uses population standard deviation over rolling windows — this implementation matches that specification.
### 3.1 Stage 1: Dual Circular-Buffer Standard Deviation
Two O(1) StdDev estimators maintain running sums for windows of `shortPeriod` and `longPeriod` bars respectively. Each bar, the oldest value is evicted and the new value is ingested:
This form avoids rescanning the window on every bar. Floating-point drift is inherent but bounded — the window size keeps the accumulated error small in practice (typical window sizes 5–30 bars).
### 3.2 Stage 2: Volatility Ratio → Dynamic Period
When $V = 0$ (both windows have identical prices, e.g., a flat series), $n_{\text{dyn}}$ defaults to $n_{\text{max}}$ as the safest fallback. When $V \leq 10^{-10}$ (effectively zero), the same clamp applies.
The clamp ensures the RSI period cannot collapse to 1 (which is numerically unstable and meaningless) or expand to absurd lengths. Default bounds [3, 30] match Chande and Kroll's original recommendation.
### 3.3 Stage 3: Wilder RMA RSI with Adaptive Alpha
Per-bar, a new alpha is derived from the current $n_{\text{dyn}}$:
Once $e_t \leq 10^{-10}$, the compensator deactivates and standard Wilder smoothing proceeds. This is the same design used throughout QuanTAlib's RSI-based oscillators (CRSI, QQE, DOSC).
### 3.5 Bar Correction (isNew Rollback)
The streaming `Update(TValue, bool isNew)` contract requires:
-`isNew = true`: snapshot state and both circular buffers, then advance.
-`isNew = false`: restore state and buffers from snapshot, recompute with new value.
Since `RingBuffer` instances are heap objects that cannot be rolled back via struct copy alone, explicit `Array.Copy` snapshots (`_shortBufSnap`, `_longBufSnap`) are maintained alongside the `State` record struct.
## Mathematical Foundation
### Full Derivation
Given close prices $c_1, c_2, \ldots, c_t$, let windows be $W_s$ of size $n_s$ and $W_l$ of size $n_l$, with $n_s < n_l$:
SIMD is not applicable to the streaming `Update` path because the period changes per bar, breaking vectorization. The static `Batch(Span)` path processes the entire series in a single loop with O(1) arithmetic per bar; AVX2 vectorization of the StdDev summation is structurally possible but not implemented, as the gains are marginal for typical window sizes (5–30).
**Complexity:** O(1) per bar for `Update`; O(n) total for `Batch`.
**Memory:** O(shortPeriod + longPeriod) for buffers; O(1) state beyond that.
**Quality metrics (1–10):**
| Attribute | Score | Note |
| :--- | :---: | :--- |
| Adaptiveness | 9 | Period covers minPeriod–maxPeriod range continuously |
**Mathematical identity test:** When `minPeriod == maxPeriod == basePeriod`, the dynamic period is always fixed at `basePeriod` regardless of the volatility ratio. Under this constraint, DYMI produces output numerically identical to `Rsi(basePeriod)` (verified at tolerance 1e-9).
1.**`longPeriod <= shortPeriod`**: The constructor throws `ArgumentException` if this constraint is violated. The volatility ratio is undefined when both windows cover the same bars.
2.**Zero-variance series (flat price)**: When `σ_long = 0`, the ratio is undefined; the implementation defaults to `V = 1` → `n_dyn = n_base`. This is correct — a flat series should produce neutral RSI(=50) at the base period rate, not a degenerate output.
3.**Warmup period misinterpretation**: `WarmupPeriod = longPeriod + maxPeriod`. The dominant warmup is the Wilder RMA, which takes `maxPeriod` bars to settle after the long StdDev window fills. Using DYMI output before `IsHot = true` will produce compensated but less accurate values.
4.**Period clamp masking pathology**: If `minPeriod` and `maxPeriod` are very close (e.g., both 14), the adaptive behavior is effectively disabled and DYMI degenerates to standard RSI. This is a valid use case but should be intentional.
5.**Floating-point drift in running sums**: The O(1) variance formula $E[x^2] - E[x]^2$ is numerically unstable for large values or large windows — specifically, catastrophic cancellation can occur. For price data in the range [0.01, 100000] and periods ≤ 100, drift is negligible in practice. For exotic inputs, a periodic full-recalculation reset (every N steps) would be appropriate; the current implementation does not perform this.
6.**Assumption of IID returns**: The period-selection formula $n_{\text{dyn}} = n_{\text{base}} / V$ implicitly assumes that the volatility ratio directly translates to an appropriate lookback scaling. This holds approximately for Gaussian returns but can under- or over-shoot in heavy-tailed regimes where short spikes inflate $V$ transiently.
7.**`Array.Copy` cost on rollback**: Each `isNew = false` call copies two arrays of size `shortPeriod` and `longPeriod`. For default periods (5+10=15 doubles = 120 bytes), this is negligible. For periods > 256, the copy still occurs on heap memory and remains fast relative to any downstream computation.
## References
- Chande, T. & Kroll, S. (1994). *The New Technical Trader*. John Wiley & Sons. Ch. 3: Dynamic Momentum Index.
- Wilder, J.W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. (RSI original source)