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:
Miha Kralj
2026-02-05 19:42:49 -08:00
parent 95838a6435
commit 26280ce80b
73 changed files with 8485 additions and 5254 deletions
+126 -104
View File
@@ -1,155 +1,177 @@
# DC: Donchian Channels
# DCHANNEL: Donchian Channels
> "The Turtles didn't need complex math. They needed to know when price broke out of its cage."
> "The Turtles made millions with a simple rule: buy the 20-day high, sell the 20-day low."
Donchian Channels (DC) track the highest high and lowest low over a lookback period, creating a price envelope that defines where the market has been. Unlike volatility-based bands (Bollinger, Keltner), Donchian uses actual price extremes—no standard deviations, no averages of true range. The result: bands that represent real support and resistance levels traders actually watch. This implementation uses monotonic deques for O(1) amortized updates rather than the naive O(n) rescan that plagues most implementations.
Donchian Channels are a price envelope indicator that tracks the highest high and lowest low over a specific lookback period. Unlike volatility-based bands (like Bollinger Bands) which rely on statistical dispersion, Donchian Channels represent actual historical price extremes—they define the "price box" in which the asset has traded. This implementation uses monotonic deques for O(1) amortized updates, making it scalable to long lookback periods and high-frequency data feeds.
## Historical Context
Richard Donchian developed these channels in the 1960s while managing one of the first publicly held commodity funds. His "4-week rule" (buy on 20-day high, sell on 20-day low) became the foundation for systematic trend-following.
**Richard Donchian** developed this indicator in the 1960s while managing one of the first publicly held commodity funds. Known as the "father of trend following," Donchian pioneered systematic trading approaches in an era dominated by discretionary methods.
The indicator gained fame through the Turtle Trading experiment in 1983. Richard Dennis and William Eckhardt recruited novice traders and taught them a mechanical system built on Donchian Channel breakouts. The Turtles reportedly made over $100 million. Curtis Faith's book and subsequent leaks revealed the core: enter on 20-day breakouts, exit on 10-day counter-breakouts.
The indicator gained legendary status through the **Turtle Trading** experiment in 1983. Richard Dennis and William Eckhardt recruited novice traders and taught them a mechanical system built on channel breakouts. The Turtles reportedly made over $100 million. Curtis Faith's 2007 book *Way of the Turtle* revealed the core system: enter on 20-day breakouts, exit on 10-day counter-breakouts.
Most implementations compute max/min by scanning the entire lookback window on every bar—O(n) per update, O(n²) for a series. This works for period=20 but becomes painful for longer windows or real-time feeds. QuanTAlib uses monotonic deques that maintain running max/min in O(1) amortized time, enabling period=500+ without performance degradation.
Donchian's "4-week rule" (buy on 20-day high, sell on 20-day low) became the foundation for systematic trend-following. The simplicity is the feature: no predictions, no indicators—just price breaking through defined boundaries.
## Architecture & Physics
Donchian Channels consist of three components: upper band (highest high), lower band (lowest low), and middle band (their average).
### 1. Upper Band (Highest High)
Tracks the maximum high price over the lookback window:
$$
U_t = \max_{i=0}^{n-1}(H_{t-i})
$$
where $H$ is the high price and $n$ is the period. The upper band moves up immediately when a new high occurs, but only drops when the previous highest high exits the lookback window.
### 2. Lower Band (Lowest Low)
Tracks the minimum low price over the lookback window:
$$
L_t = \min_{i=0}^{n-1}(L_{t-i})
$$
where $L$ is the low price. The lower band drops immediately on new lows but only rises when the previous lowest low exits the window.
### 3. Middle Band
The arithmetic mean of the upper and lower bands:
$$
M_t = \frac{U_t + L_t}{2}
$$
This represents the "equilibrium" price over the lookback period—not a moving average of closes, but the center of the price range.
## Mathematical Foundation
The physics of Donchian Channels is based on **Price Extremes** within a sliding time window. It answers the question: "What are the absolute boundaries of recent price action?"
### Monotonic Deque Algorithm
Instead of rescanning the window on each bar, the implementation maintains two monotonic deques:
Most implementations scan the entire lookback window for every bar, resulting in $O(N \times P)$ complexity (where $P$ is period). QuanTAlib uses **Monotonic Deques** to maintain the maximum and minimum candidates in sorted order.
**For maximum (upper band):**
1. **Efficiency:** This reduces the complexity to **Amortized O(1)**.
2. **Scalability:** Calculating a 500-period channel takes the same CPU time as a 20-period channel.
1. Remove elements from the back that are smaller than the new value
2. Add the new value with its index to the back
3. Remove elements from the front whose indices are outside the window
4. The front element is always the maximum
### Calculation Steps
**For minimum (lower band):**
1. Remove elements from the back that are larger than the new value
2. Add the new value with its index to the back
3. Remove elements from the front whose indices are outside the window
4. The front element is always the minimum
**Amortized Analysis:**
Each element is added once and removed at most once. Over $n$ operations, total work is $O(n)$, giving $O(1)$ amortized per update.
### Channel Width
The distance between bands measures price range volatility:
#### 1. Upper Band (Highest High)
$$
W_t = U_t - L_t
\text{Upper}_t = \max_{i=0}^{n-1}(H_{t-i})
$$
Wider channels indicate higher volatility; narrower channels suggest consolidation.
#### 2. Lower Band (Lowest Low)
$$
\text{Lower}_t = \min_{i=0}^{n-1}(L_{t-i})
$$
#### 3. Middle Band
$$
\text{Middle}_t = \frac{\text{Upper}_t + \text{Lower}_t}{2}
$$
Where $n$ = period (default: 20).
### Deque Maintenance
For each new bar:
1. **Upper Band (Max Deque):**
- Remove indices outside the window from the front
- Remove values smaller than the current High from the back
- Add current High to the back
- Front element is the highest high
2. **Lower Band (Min Deque):**
- Remove indices outside the window from the front
- Remove values larger than the current Low from the back
- Add current Low to the back
- Front element is the lowest low
**Amortized Analysis:** Each element enters the deque once and leaves at most once. Total work for $N$ bars is $O(N)$, yielding $O(1)$ amortized per bar.
## Performance Profile
### Operation Count (Streaming Mode, Scalar)
The implementation is highly optimized using the Monotonic Deque pattern, solving the performance bottleneck common in "sliding window max/min" problems.
Per-bar cost using monotonic deque optimization:
### Operation Count - Single value
| Operation | Count | Cost (cycles) | Subtotal |
| :--- | :---: | :---: | :---: |
| CMP | 4 | 1 | 4 |
| ADD | 1 | 1 | 1 |
| MUL | 1 | 3 | 3 |
| **Total** | **6** | | **~8 cycles** |
| CMP (deque maint.) | ~4 | 1 | ~4 |
| ADD (index/middle) | 2 | 1 | 2 |
| MUL (middle) | 1 | 3 | 3 |
| Deque ops | ~2 | 1 | ~2 |
| **Total** | **~9** | — | **~11 cycles** |
**Complexity**: O(1) amortized per bar—monotonic deque maintains max/min efficiently.
**Complexity:** O(1) amortized per bar.
### Batch Mode (512 values, SIMD/FMA)
### Operation Count - Batch processing
Finding max/min over sliding windows has limited SIMD benefit due to sequential dependency:
| Operation | Scalar Ops | SIMD Benefit | Notes |
| :--- | :---: | :---: | :--- |
| Max/Min update | 4 | 1× | Deque-based, sequential |
| Middle band | 2 | 2× | ADD + MUL parallelizable |
**Batch efficiency (512 bars):**
| Mode | Cycles/bar | Total (512 bars) | Improvement |
| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration |
| :--- | :---: | :---: | :---: |
| Scalar streaming | 8 | 4,096 | |
| Partial SIMD | ~7 | ~3,584 | **~12%** |
| Deque maintenance | ~6N | N/A | 1× |
| Middle calculation | N | N/8 | 8× |
Donchian Channels are already highly efficient due to the O(1) monotonic deque algorithm.
### Quality Metrics
| Metric | Score | Notes |
| :--- | :---: | :--- |
| **Accuracy** | 10/10 | Exact max/min calculation |
| **Timeliness** | 6/10 | Tracks past extremes, inherently lagging |
| **Overshoot** | 10/10 | No overshoot—bands are actual price levels |
| **Smoothness** | 5/10 | Bands move in discrete steps as extremes exit window |
*Note: Sliding window max/min is inherently sequential, limiting SIMD benefit. The deque algorithm is already highly efficient.*
## Validation
| Library | Status | Notes |
| :--- | :---: | :--- |
| **TA-Lib** | ✅ | Exact match for upper/lower bands |
| **Skender** | ✅ | Exact match within floating-point tolerance |
| **Tulip** | ✅ | Exact match |
| **Ooples** | ✅ | Exact match |
| **TA-Lib** | ✅ | Matches `MAX` and `MIN` functions |
| **Skender** | ✅ | Matches `DonchianChannels` exactly |
| **Tulip** | ✅ | Matches `max` and `min` functions |
| **Ooples** | ✅ | Cross-validated |
| **Manual** | ✅ | Verified against extreme values |
## Common Pitfalls
## Usage & Pitfalls
1. **Stale Extremes**: Donchian bands stay flat until a new extreme occurs or the old extreme exits the window. A band that hasn't moved in 15 bars isn't broken—it's waiting. Traders sometimes mistake this for indicator malfunction.
- **Breakout Trading**: The classic Donchian strategy: buy when price closes above Upper band, sell when it closes below Lower band. Simple but effective in trending markets.
- **Turtle Rules**: Consider asymmetric periods—20-day for entry, 10-day for exit—to lock in profits faster.
- **Stale Extremes**: The bands stay flat until a new extreme occurs or the old extreme exits the window. A band that hasn't moved in 15 bars is waiting for new information.
- **Breakout vs. Touch**: Price touching the band is not the same as breaking out. True breakouts require closes above/below the band. Intrabar spikes often reverse.
- **Choppy Markets**: In range-bound markets, Donchian generates many false breakouts. Consider filtering with ADX or volume.
- **Gap Handling**: Overnight gaps immediately extend the relevant band. These may not represent sustainable price levels.
2. **O(n) Trap**: Naive implementations rescan the full window every bar. For period=200 on tick data (60,000 bars/day), that's 12 million comparisons daily per symbol. The monotonic deque approach reduces this to ~120,000.
## API
3. **Breakout vs. Touch**: Price touching the upper band is not the same as breaking out. True breakouts close above/below the band. Intrabar spikes that don't close outside the channel often fail.
```mermaid
classDiagram
class Dchannel {
+Name : string
+WarmupPeriod : int
+Upper : TValue
+Lower : TValue
+Last : TValue
+IsHot : bool
+Update(TBar bar) TValue
+Update(TBarSeries source) TSeries
+Reset() void
}
```
4. **Asymmetric Exit**: The Turtle system used 20-day entry but 10-day exit. Using the same period for both typically underperforms. Consider different periods for entries and exits.
### Class: `Dchannel`
5. **Choppy Markets**: Donchian Channels generate frequent false signals during sideways consolidation. The bands narrow, making breakouts more likely, but these breakouts often fail. Filter with trend confirmation or volatility thresholds.
| Parameter | Type | Default | Range | Description |
| :--- | :--- | :--- | :--- | :--- |
| `period` | `int` | `20` | `>0` | Lookback window for finding highest high and lowest low. |
| `source` | `TBarSeries` | — | `any` | Initial input (optional). |
6. **Gap Behavior**: Overnight gaps can create instant breakouts that reverse quickly. The band immediately adjusts to include the gap, which may not represent sustainable price levels.
### Properties
7. **Memory Footprint**: The monotonic deque implementation requires storing (value, index) pairs. For period=200, this means up to 400 doubles (3.2 KB) per instance. For 5,000 symbols, budget ~16 MB.
- `Last` (`TValue`): The Middle Band value ((Upper + Lower) / 2).
- `Upper` (`TValue`): The Highest High over the lookback period.
- `Lower` (`TValue`): The Lowest Low over the lookback period.
- `IsHot` (`bool`): Returns `true` after `period` bars.
### Methods
- `Update(TBar bar)`: Updates the indicator with new OHLC data and returns the Middle band.
- `Update(TBarSeries source)`: Batch processes a bar series.
- `Reset()`: Clears all historical data and deques.
## C# Example
```csharp
using QuanTAlib;
// Initialize for a 20-day breakout system
var dchannel = new Dchannel(period: 20);
// Update Loop
foreach (var bar in bars)
{
var result = dchannel.Update(bar);
if (dchannel.IsHot)
{
Console.WriteLine($"{bar.Time}: Mid={result.Value:F2} Upper={dchannel.Upper.Value:F2} Lower={dchannel.Lower.Value:F2}");
// Turtle-style breakout detection
if (bar.Close > dchannel.Upper.Value)
Console.WriteLine(" BREAKOUT! Price exceeds 20-day high");
else if (bar.Close < dchannel.Lower.Value)
Console.WriteLine(" BREAKDOWN! Price below 20-day low");
}
}
```
## References
- Donchian, R. (1960). "High Finance in Copper." *Financial Analysts Journal*, 16(6), 133-142.
- Faith, C. (2007). *Way of the Turtle: The Secret Methods that Turned Ordinary People into Legendary Traders*. McGraw-Hill.
- Schwager, J. D. (1989). *Market Wizards: Interviews with Top Traders*. Harper & Row.
- Covel, M. (2007). *The Complete TurtleTrader*. HarperBusiness.