- 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.
8.9 KiB
MMCHANNEL: Min-Max Channel
"The market's true range isn't about averages. It's about extremes—and who's winning."
Min-Max Channel (MMCHANNEL) tracks the highest high and lowest low over a lookback period, creating a pure price envelope without any midpoint calculation. Unlike Donchian Channels which include a middle band, MMCHANNEL delivers only the raw extremes—exactly what breakout traders and range analysis need. This implementation uses monotonic deques for O(1) amortized updates, making it suitable for high-frequency applications and long lookback periods.
Historical Context
Min-Max channels represent the simplest form of price envelope analysis, predating most technical indicators. The concept is intuitive: track where price has been at its highest and lowest points over a defined period.
The approach gained prominence through Richard Donchian's work in the 1960s and later through the Turtle Trading system. While Donchian Channels include a midpoint average, MMCHANNEL strips this away, focusing purely on support and resistance levels defined by actual price extremes.
Most implementations suffer from O(n) complexity per update—scanning the entire window to find max/min values. For period=200 on tick data, this means 200 comparisons per tick. QuanTAlib uses monotonic deques that maintain sorted order implicitly, achieving O(1) amortized updates regardless of period length.
Architecture & Physics
MMCHANNEL consists of two components: the upper band (highest high) and lower band (lowest low).
1. Upper Band (Highest High)
Tracks the maximum high price over the lookback window using a decreasing monotonic deque:
U_t = \max_{i=0}^{n-1}(H_{t-i})
where H is the high price and n is the period. New highs immediately update the upper band; the band only decreases when the previous maximum exits the lookback window.
Monotonic deque invariant: Elements are stored in decreasing order by value. The front element is always the maximum.
2. Lower Band (Lowest Low)
Tracks the minimum low price over the lookback window using an increasing monotonic deque:
L_t = \min_{i=0}^{n-1}(L_{t-i})
where L is the low price. New lows immediately update the lower band; the band only increases when the previous minimum exits the window.
Monotonic deque invariant: Elements are stored in increasing order by value. The front element is always the minimum.
Mathematical Foundation
Monotonic Deque Algorithm
The key insight is maintaining sorted order without explicit sorting:
For maximum (upper band):
- Back removal: Remove elements from the back that are ≤ the new value
- Insert: Add the new (value, index) pair to the back
- Front expiry: Remove elements from the front whose indices are outside the window
- Query: The front element is always the maximum
For minimum (lower band):
- Back removal: Remove elements from the back that are ≥ the new value
- Insert: Add the new (value, index) pair to the back
- Front expiry: Remove elements from the front whose indices are outside the window
- Query: The front element is always the minimum
Amortized Analysis:
Each element enters the deque exactly once and leaves at most once (either from the back during insertion or from the front during expiry). Over n operations, total work is O(n), yielding O(1) amortized per update.
Channel Width
The distance between bands measures the price range:
W_t = U_t - L_t
Channel width indicates volatility: wider channels suggest larger price swings; narrower channels indicate consolidation.
Performance Profile
Operation Count (Streaming Mode, Scalar)
Per-bar cost using monotonic deque optimization:
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| CMP (deque maintenance) | ~4 | 1 | ~4 |
| Memory access (deque) | ~4 | 3 | ~12 |
| Total | ~8 | — | ~16 cycles |
Complexity: O(1) amortized per bar. Worst case O(n) occurs only when a monotonically increasing (for max) or decreasing (for min) sequence forces clearing the entire deque—rare in practice.
Batch Mode (512 values, SIMD/FMA)
Sliding window max/min has limited SIMD benefit due to sequential dependency in deque operations:
| Operation | Scalar Ops | SIMD Benefit | Notes |
|---|---|---|---|
| Deque update | ~8 | 1× | Sequential by nature |
| Index comparison | 2 | 2× | SIMD possible for batch |
Batch efficiency (512 bars):
| Mode | Cycles/bar | Total (512 bars) | Improvement |
|---|---|---|---|
| Scalar streaming | 16 | 8,192 | — |
| Partial SIMD | ~14 | ~7,168 | ~12% |
The monotonic deque algorithm is already highly efficient; SIMD provides marginal gains.
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 | 4/10 | Bands move in discrete steps as extremes exit window |
Validation
| Library | Status | Notes |
|---|---|---|
| Dchannel | ✅ | Exact match for upper/lower bands |
| Skender | ✅ | Exact match via Donchian upper/lower |
| TA-Lib | ✅ | Exact match via MAX/MIN functions |
| Tulip | ✅ | Exact match via max/min functions |
Usage & Pitfalls
- 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 isn't broken—it's waiting for price to exceed the current extreme or for that extreme to age out.
- O(n) Implementation Trap: Naive implementations rescan the window every bar. For period=200 on 60,000 bars/day, that's 12 million comparisons per symbol. The monotonic deque approach reduces this to ~120,000 operations.
- Breakout vs. Touch: Price touching the upper band differs from breaking out. True breakouts require closes above/below the band. Intrabar spikes that don't close outside the channel often reverse.
- No Middle Band: Unlike Donchian Channels, MMCHANNEL has no middle line. If you need a centerline, use Donchian or compute
(Upper + Lower) / 2separately. - Asymmetric Movement: Upper and lower bands move independently.
- Gap Handling: Overnight gaps immediately adjust the relevant band.
- Memory Footprint: The monotonic deque stores (value, index) pairs. Worst case is
2 * periodpairs per deque. - Bar Correction: When
isNew=false, the indicator must restore prior state before computing.
API
classDiagram
class Mmchannel {
+string Name
+int WarmupPeriod
+TValue Last
+TValue Upper
+TValue Lower
+bool IsHot
+Mmchannel(int period)
+Mmchannel(TBarSeries source, int period)
+TValue Update(TBar input, bool isNew)
+Tuple~TSeries,TSeries~ Update(TBarSeries source)
+void Prime(TBarSeries source)
+void Reset()
+static void Batch(ReadOnlySpan~double~ high, ReadOnlySpan~double~ low, Span~double~ upper, Span~double~ lower, int period)
+static Tuple~TSeries,TSeries~ Batch(TBarSeries source, int period)
+static Tuple~Tuple~TSeries,TSeries~,Mmchannel~ Calculate(TBarSeries source, int period)
}
Class: Mmchannel
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
period |
int |
— | >0 |
Lookback period for highest high and lowest low. |
Properties
Last(TValue): Returns the upper band value (for single-value compatibility).Upper(TValue): The highest high over the lookback period.Lower(TValue): The lowest low over the lookback period.IsHot(bool): Returnstruewhen warmup period is complete.
Methods
Update(TBar input, bool isNew): Updates the indicator with a new bar and returns the result.Update(TBarSeries source): Processes an entire bar series and returns (Upper, Lower) tuple of TSeries.Prime(TBarSeries source): Initializes internal state from historical data.Reset(): Resets the indicator to its initial state.Batch(...): Static method for zero-allocation span-based batch processing.Calculate(TBarSeries source, int period): Static factory that returns results and indicator instance.
C# Example
using QuanTAlib;
// Initialize
var mmchannel = new Mmchannel(period: 20);
// Update Loop
foreach (var bar in quotes)
{
mmchannel.Update(bar, isNew: true);
// Use valid results
if (mmchannel.IsHot)
{
Console.WriteLine($"{bar.Time}: Upper={mmchannel.Upper.Value:F2}, Lower={mmchannel.Lower.Value:F2}");
}
}
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.
- Cormen, T. H., et al. (2009). Introduction to Algorithms, 3rd ed. MIT Press. (Monotonic deque analysis)