mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-25 13:58:04 +00:00
Add Choppiness Index (CHOP) implementation and tests
- Implemented ChopIndicator for Quantower with configurable period and cold value display. - Created Chop class for calculating the Choppiness Index with detailed documentation. - Added comprehensive unit tests for Chop functionality, covering various market conditions and edge cases. - Developed markdown documentation for CHOP, detailing its historical context, mathematical foundation, and usage examples. - Established a remediation plan for channel indicators documentation, identifying gaps and prioritizing updates.
This commit is contained in:
+99
-179
@@ -1,222 +1,142 @@
|
||||
# JBANDS: Jurik Adaptive Envelope Bands
|
||||
|
||||
> "Volatility is the only free lunch in finance—if you know how to digest it."
|
||||
> "Markets have memory, but it fades—Jurik bands capture this with elegant exponential decay."
|
||||
|
||||
JBANDS exposes the internal adaptive envelope tracking from Jurik's Moving Average algorithm as a channel indicator. Unlike fixed-width bands, these envelopes snap instantly to new price extremes but decay smoothly back toward price during consolidations. The result: volatility-responsive channels that widen during breakouts and contract during ranging periods, with JMA's signature smoothness in both the middle band and envelope decay.
|
||||
Jurik Bands (JBANDS) expose the internal adaptive envelope mechanism of the Jurik Moving Average (JMA). Unlike standard volatility bands (Bollinger, Keltner) which maintain symmetrical width around a central average, JBANDS feature asymmetric "snap-and-decay" behavior. They expand instantly to encompass new price extremes ("snap") and exponentially decay towards the price during consolidation. The decay rate is dynamically modulated by a sophisticated volatility estimation engine, making the bands tight during sideways markets and expansive during trends.
|
||||
|
||||
## Historical Context
|
||||
|
||||
Mark Jurik introduced JMA in the mid-1990s as a proprietary alternative to exponential moving averages. While JMA itself became well-known for its low-lag characteristics, the internal envelope bands received less attention. These bands emerged from Jurik's volatility estimation mechanism—a necessary component for adaptive smoothing that happened to create excellent dynamic support/resistance levels.
|
||||
The Jurik Moving Average and its associated bands were developed by **Mark Jurik** of Jurik Research in the 1990s. Unlike academic indicators, JMA was designed as a proprietary commercial tool optimized for real-world trading, with particular emphasis on reducing lag while maintaining smoothness.
|
||||
|
||||
The envelope mechanism differs fundamentally from Bollinger Bands or Keltner Channels. Those indicators apply symmetric volatility measures around a central average. JMA's envelopes track actual price extremes and decay asymmetrically—upper bands decay downward while lower bands decay upward, each at rates determined by current volatility conditions. This creates channels that respond to market structure rather than statistical assumptions about price distribution.
|
||||
Jurik's innovation was the introduction of **adaptive volatility modulation**—the bands don't use a fixed decay rate but instead adjust their behavior based on a sophisticated two-stage volatility estimator. During low volatility, the bands contract quickly to capture the next move; during high volatility, they remain wide to avoid premature signals.
|
||||
|
||||
Traditional channel indicators assume volatility is symmetric and normally distributed. Price data rarely cooperates. JMA's bands adapt to actual price behavior: when price breaks to new highs, the upper band jumps immediately; when price consolidates, both bands gradually converge toward the smoothed price.
|
||||
The "snap-and-decay" behavior draws inspiration from **hysteresis** in physics—systems that respond differently to increasing versus decreasing inputs. When price moves to a new extreme, the band snaps immediately (plasticity). When price retreats, the band decays gradually (elasticity). This asymmetry matches how markets actually behave: breakouts are sudden, consolidations are gradual.
|
||||
|
||||
## Architecture & Physics
|
||||
|
||||
JBANDS consists of four interconnected subsystems:
|
||||
The system models price distinctively from standard Gaussian noise:
|
||||
|
||||
### 1. Local Deviation Tracker
|
||||
1. **Snap (Plasticity):** When price penetrates the band, the band instantly deforms (snaps) to the new price level. This represents the immediate acceptance of a new price reality.
|
||||
2. **Decay (Elasticity):** When price retreats, the band recovers (decays) towards the center. The rate of decay is governed by the system's "temperature" (volatility).
|
||||
- **High Volatility:** Slow decay (bands stay wide to accommodate noise).
|
||||
- **Low Volatility:** Fast decay (bands tighten to capture the next move).
|
||||
3. **Volatility Engine:** A two-stage estimator (SMA + Trimmed Mean) calculates the "reference volatility" to normalize market noise.
|
||||
|
||||
The first stage computes local deviation from the current envelope boundaries:
|
||||
### Formula
|
||||
|
||||
$$
|
||||
d_{local} = \max(|P_t - U_{t-1}|, |P_t - L_{t-1}|)
|
||||
$$
|
||||
The core adaptive logic revolves around the dynamic exponent $d$:
|
||||
$$Ratio = \frac{|Price - Band|}{Volatility_{ref}}$$
|
||||
$$d = \min(Mean(Ratio)^{power}, Limit)$$
|
||||
|
||||
where $U$ is the upper band and $L$ is the lower band. This captures how far price has moved from the nearest envelope boundary—essential for determining whether to expand or contract the channel.
|
||||
The decay factor $\alpha$ is modulated by $d$:
|
||||
$$\alpha = e^{\text{constant} \cdot \sqrt{d}}$$
|
||||
|
||||
### 2. Volatility Estimation (10-Bar SMA + 128-Bar Trimmed Mean)
|
||||
Band update (Upper Band example):
|
||||
$$Upper_t = \begin{cases} Price & \text{if } Price > Upper_{t-1} \\ Upper_{t-1} - \alpha \cdot (Upper_{t-1} - Price) & \text{otherwise} \end{cases}$$
|
||||
|
||||
Local deviations feed a two-stage volatility estimator:
|
||||
## Calculation Steps
|
||||
|
||||
**Stage A: 10-bar SMA of local deviation**
|
||||
|
||||
$$
|
||||
V_{short,t} = \frac{1}{10}\sum_{i=0}^{9} d_{local,t-i}
|
||||
$$
|
||||
|
||||
**Stage B: 128-sample trimmed mean**
|
||||
|
||||
The middle 65 samples from the 128-sample volatility history provide the reference volatility:
|
||||
|
||||
$$
|
||||
V_{ref} = \text{trimmed-mean}_{65}(\{V_{short,t-127}, ..., V_{short,t}\})
|
||||
$$
|
||||
|
||||
This trimmed mean rejects outliers while maintaining responsiveness to genuine volatility shifts.
|
||||
|
||||
### 3. Dynamic Exponent Calculation
|
||||
|
||||
The ratio of current deviation to reference volatility determines the adaptive exponent:
|
||||
|
||||
$$
|
||||
r_t = \frac{d_{local}}{V_{ref}}
|
||||
$$
|
||||
|
||||
$$
|
||||
d_t = \text{clamp}(r_t^{P_{exp}}, 1, \text{logParam})
|
||||
$$
|
||||
|
||||
where:
|
||||
|
||||
- $P_{exp} = \max(\text{logParam} - 2, 0.5)$
|
||||
- $\text{logParam} = \log_2(\sqrt{(period-1)/2}) + 2$
|
||||
|
||||
Higher volatility ratios produce larger exponents, causing faster band adaptation.
|
||||
|
||||
### 4. Band Update Logic (Snap and Decay)
|
||||
|
||||
The core envelope behavior:
|
||||
|
||||
$$
|
||||
\alpha_{band} = e^{\text{logSqrtDivider} \cdot \sqrt{d_t}}
|
||||
$$
|
||||
|
||||
$$
|
||||
U_t = \begin{cases}
|
||||
P_t & \text{if } P_t > U_{t-1} \\
|
||||
\alpha_{band} \cdot U_{t-1} + (1 - \alpha_{band}) \cdot P_t & \text{otherwise}
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
$$
|
||||
L_t = \begin{cases}
|
||||
P_t & \text{if } P_t < L_{t-1} \\
|
||||
\alpha_{band} \cdot L_{t-1} + (1 - \alpha_{band}) \cdot P_t & \text{otherwise}
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
Bands snap instantly to new extremes (breakout detection) but decay smoothly toward price during consolidations. The decay rate adapts to current volatility—faster decay during quiet periods, slower during volatile ones.
|
||||
|
||||
### 5. Middle Band (JMA IIR Filter)
|
||||
|
||||
The middle band uses JMA's 2-pole IIR filter with phase adjustment:
|
||||
|
||||
$$
|
||||
\alpha = e^{\text{logLengthDivider} \cdot d_t}
|
||||
$$
|
||||
|
||||
$$
|
||||
C_0 = \alpha \cdot C_{0,t-1} + (1-\alpha) \cdot P_t
|
||||
$$
|
||||
|
||||
$$
|
||||
C_8 = \text{lengthDivider} \cdot C_{8,t-1} + (1-\text{lengthDivider}) \cdot (P_t - C_0)
|
||||
$$
|
||||
|
||||
$$
|
||||
A_8 = \alpha^2 \cdot A_{8,t-1} + (\text{phaseParam} \cdot C_8 + C_0 - JMA_{t-1}) \cdot (1 - 2\alpha + \alpha^2)
|
||||
$$
|
||||
|
||||
$$
|
||||
JMA_t = JMA_{t-1} + A_8
|
||||
$$
|
||||
|
||||
The phase parameter maps from [-100, 100] to [0.5, 2.5], controlling overshoot characteristics.
|
||||
|
||||
## Mathematical Foundation
|
||||
|
||||
### Adaptive Smoothing Factor
|
||||
|
||||
The core innovation lies in how smoothing adapts to volatility:
|
||||
|
||||
$$
|
||||
\text{lengthParam} = \frac{period - 1}{2}
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{logParam} = \max(0, \log_2(\sqrt{\text{lengthParam}}) + 2)
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{sqrtParam} = \sqrt{\text{lengthParam}} \cdot \text{logParam}
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{lengthDivider} = \frac{0.9 \cdot \text{lengthParam}}{0.9 \cdot \text{lengthParam} + 2}
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{sqrtDivider} = \frac{\text{sqrtParam}}{\text{sqrtParam} + 1}
|
||||
$$
|
||||
|
||||
### Phase Mapping
|
||||
|
||||
The phase parameter transforms user input to internal coefficient:
|
||||
|
||||
$$
|
||||
\text{phaseParam} = \begin{cases}
|
||||
0.5 & \text{if phase} < -100 \\
|
||||
2.5 & \text{if phase} > 100 \\
|
||||
\text{phase} \cdot 0.01 + 1.5 & \text{otherwise}
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
Lower phase values reduce overshoot; higher values increase responsiveness at the cost of potential ringing.
|
||||
1. **Local Deviation:** Measure how far the price is from the current envelope walls.
|
||||
2. **Volatility Estimation:**
|
||||
- Calculate 10-period SMA of the local deviation.
|
||||
- Store in a circular buffer.
|
||||
- Calculate a 128-period **Trimmed Mean** (discarding outliers) to find the Reference Volatility.
|
||||
3. **Dynamic Exponent:** Compute the modulation exponent $d$ based on the ratio of current deviation to reference volatility.
|
||||
4. **Update Bands:** Apply the "Snap or Decay" logic using the dynamic exponent.
|
||||
5. **Update JMA:** Calculate the Central Moving Average (Middle Band) using the JMA smoothing algorithm.
|
||||
|
||||
## Performance Profile
|
||||
|
||||
### Operation Count (Streaming Mode, Per Bar)
|
||||
JBANDS is computationally intensive due to its sophisticated volatility engine and use of transcendental functions.
|
||||
|
||||
### Operation Count (Streaming Mode, per Bar)
|
||||
|
||||
| Operation | Count | Cost (cycles) | Subtotal |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| ADD/SUB | 18 | 1 | 18 |
|
||||
| MUL | 12 | 3 | 36 |
|
||||
| DIV | 3 | 15 | 45 |
|
||||
| CMP/ABS | 8 | 1 | 8 |
|
||||
| ADD/MUL | 30 | 2 | 60 |
|
||||
| EXP | 2 | 15 | 30 |
|
||||
| POW | 1 | 20 | 20 |
|
||||
| SQRT | 2 | 15 | 30 |
|
||||
| EXP | 2 | 50 | 100 |
|
||||
| POW | 1 | 60 | 60 |
|
||||
| LOG (precomputed) | 0 | 0 | 0 |
|
||||
| **Total** | **46** | — | **~297 cycles** |
|
||||
| Partial Sort | 1 | ~150 | 150 |
|
||||
| **Total** | **36** | — | **~290 cycles** |
|
||||
|
||||
**Dominant cost:** Transcendental functions (EXP, POW, SQRT) account for 64% of computational cost. The log-based parameters are precomputed in the constructor.
|
||||
### Complexity Analysis
|
||||
|
||||
### Batch Mode (SIMD Limitations)
|
||||
|
||||
Due to the recursive IIR filter and stateful band tracking, SIMD vectorization provides limited benefit for JBANDS. The algorithm is inherently sequential—each bar's output depends on the previous bar's state. However, the span-based Calculate API avoids heap allocations during batch processing.
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
| Metric | Score | Notes |
|
||||
| Mode | Complexity | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **Accuracy** | 9/10 | Exact JMA algorithm reproduction |
|
||||
| **Timeliness** | 9/10 | Near-zero effective lag in band adaptation |
|
||||
| **Overshoot** | 8/10 | Phase parameter provides control |
|
||||
| **Smoothness** | 9/10 | JMA's hallmark characteristic |
|
||||
| **Adaptivity** | 10/10 | True volatility-responsive behavior |
|
||||
| Streaming | O(1) | Amortized, IIR recursive |
|
||||
| Batch | O(n) | Sequential, limited SIMD |
|
||||
|
||||
## Validation
|
||||
|
||||
JBANDS is a novel extraction of JMA internals. No external library exposes these bands directly.
|
||||
|
||||
| Library | Status | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **TA-Lib** | — | No JMA or JBANDS implementation |
|
||||
| **Skender** | — | No JMA or JBANDS implementation |
|
||||
| **Tulip** | — | No JMA or JBANDS implementation |
|
||||
| **Ooples** | — | Has JMA, but no band extraction |
|
||||
| **JMA Middle Band** | ✅ | Validated against standalone JMA |
|
||||
| **Jurik Research** | ✅ | Matches described behavior from Jurik literature |
|
||||
| **JMA** | ✅ | Middle band validated against standard JMA |
|
||||
| **Behavioral** | ✅ | Verified snap-on-breakout, decay-on-retrace pattern |
|
||||
|
||||
Internal validation confirms the middle band exactly matches the standalone JMA indicator for all period/phase combinations.
|
||||
## Usage & Pitfalls
|
||||
|
||||
## Common Pitfalls
|
||||
- **Extended Warmup:** JBANDS requires a long warmup period (approx 20 + 80 × Period^0.36 bars). Wait for `IsHot=true` before using signals.
|
||||
- **Snap vs Decay:** Bands snap instantly to new extremes but decay gradually. Expect asymmetric behavior—this is by design.
|
||||
- **Volatility Sensitivity:** The `power` parameter (default 0.45) modulates volatility sensitivity. Higher values make bands more reactive to volatility changes.
|
||||
- **Computational Cost:** ~300+ cycles per bar due to transcendental functions and trimmed mean calculation. Consider this for high-frequency applications.
|
||||
- **Phase Parameter:** Controls JMA overshoot (-100 to 100). Default 0 is balanced; negative values reduce lag at the cost of more overshoot.
|
||||
- **Not Symmetrical:** Unlike Bollinger Bands, JBANDS are asymmetric. Upper and lower bands behave independently.
|
||||
|
||||
1. **Warmup period underestimation.** JBANDS requires approximately $20 + 80 \cdot period^{0.36}$ bars for the volatility estimation buffers to stabilize. For period=14, this means ~52 bars; for period=50, ~87 bars. Using the indicator before warmup produces erratic band behavior.
|
||||
## API
|
||||
|
||||
2. **Phase parameter confusion.** Phase affects the middle band (JMA), not the envelope bands. Negative phase reduces overshoot; positive phase increases responsiveness. The envelope snap-and-decay behavior is controlled by the period parameter and volatility conditions.
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Jbands {
|
||||
+Jbands(int period, int phase = 0, double power = 0.45)
|
||||
+TValue Last
|
||||
+TValue Upper
|
||||
+TValue Lower
|
||||
+bool IsHot
|
||||
+TValue Update(TValue value)
|
||||
+void Reset()
|
||||
}
|
||||
```
|
||||
|
||||
3. **Band interpretation.** Unlike Bollinger Bands where touches indicate overbought/oversold, JBANDS touches indicate breakout detection. When price exceeds the upper band, the band snaps to the new level—this signals strength, not reversal.
|
||||
### Class: `Jbands`
|
||||
|
||||
4. **Memory footprint.** Each JBANDS instance maintains 128 + 10 = 138 double values in ring buffers plus scalar state. Memory per instance: ~1.3 KB. Scale accordingly for multi-instrument deployments.
|
||||
| Parameter | Type | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `period` | `int` | — | `>0` | The nominal lookback length. |
|
||||
| `phase` | `int` | `0` | `-100–100` | Controls middle band overshoot. |
|
||||
| `power` | `double` | `0.45` | `>0` | Modulates volatility sensitivity. |
|
||||
|
||||
5. **Computational cost.** At ~297 cycles per bar, JBANDS is 3-4x more expensive than simple channel indicators (Donchian, Keltner). The cost comes from JMA's sophisticated volatility estimation. Budget accordingly for high-frequency applications.
|
||||
### Properties
|
||||
|
||||
6. **isNew parameter.** Bar correction (isNew=false) triggers full state rollback and recalculation. This is essential for real-time chart updates but adds overhead. For historical backtesting with clean data, always pass isNew=true.
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| `Last` | `TValue` | The Middle Band (JMA) value. |
|
||||
| `Upper` | `TValue` | The Adaptive Upper Envelope. |
|
||||
| `Lower` | `TValue` | The Adaptive Lower Envelope. |
|
||||
| `IsHot` | `bool` | Returns `true` after long warmup (≈ 20 + 80 × Period^0.36 bars). |
|
||||
|
||||
## References
|
||||
### Methods
|
||||
|
||||
- Jurik, M. (1995). "JMA: Jurik Moving Average." Jurik Research.
|
||||
- Ehlers, J. (2001). "Rocket Science for Traders." Wiley. (Discussion of adaptive smoothing techniques)
|
||||
- QuanTAlib JMA implementation: [lib/trends_IIR/jma/Jma.md](../../trends_IIR/jma/Jma.md)
|
||||
- `Update(TValue value)`: Updates the indicator with a new value.
|
||||
- `Reset()`: Clears all historical data and volatility buffers.
|
||||
|
||||
## C# Example
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
// 1. Initialize
|
||||
var jbands = new Jbands(period: 14, phase: 0);
|
||||
|
||||
// 2. Stream data
|
||||
var price = 100.0;
|
||||
// ... loop over data ...
|
||||
jbands.Update(new TValue(DateTime.Now, price));
|
||||
|
||||
// 3. JMA interpretation
|
||||
if (price > jbands.Upper.Value)
|
||||
{
|
||||
Console.WriteLine("Volatility Breakout - Band Snapped Up");
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user