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:
+88
-234
@@ -1,122 +1,43 @@
|
||||
# APZ: Adaptive Price Zone
|
||||
|
||||
## Overview and Purpose
|
||||
> "Volatility is not noise; it is the breathing rhythm of the market."
|
||||
|
||||
The Adaptive Price Zone (APZ) is a volatility-based technical indicator developed by Lee Leibfarth and first introduced in the September 2006 issue of *Technical Analysis of Stocks & Commodities* magazine. The APZ was specifically designed to help traders identify potential turning points in non-trending, choppy markets where price action tends to oscillate within a defined range rather than establishing clear directional trends.
|
||||
APZ (Adaptive Price Zone) is a volatility-based envelop composed of double-smoothed exponential moving averages. Unlike standard bands that often perform poorly in non-trending "choppy" markets, APZ uses a square-root weighted EMA to create a highly responsive zone that identifies reversal points in sideways action.
|
||||
|
||||
Unlike traditional channel indicators that use fixed-period moving averages, the APZ employs a unique double-smoothed exponential moving average (EMA) calculation with a modified smoothing factor based on the square root of the lookback period. This adaptive approach allows the indicator to respond quickly to price changes while maintaining a smooth channel that tracks price fluctuations, especially in volatile market conditions.
|
||||
## Historical Context
|
||||
|
||||
The indicator forms a set of bands around a central line, creating a "zone" that acts as a statistical envelope for price action. When prices deviate significantly from this zone by crossing above the upper band or below the lower band, it signals a potential reversal opportunity as prices tend to revert back toward the statistical mean. This mean-reversion characteristic makes the APZ particularly valuable for range-bound trading strategies and short-term tactical entries in non-trending environments.
|
||||
Created by Lee Leibfarth and published in *Technical Analysis of Stocks & Commodities* (Sep 2006, "Trading With An Adaptive Price Zone"), APZ was specifically engineered for the "non-trending" phase of market cycles. Leibfarth recognized that most indicators fail in chop; trend followers get whipsawed, and oscillators saturate. APZ fills this gap by adapting its bandwidth dynamically to statistical noise, allowing traders to fade extremes in range-bound environments.
|
||||
|
||||
## Core Concepts
|
||||
## Architecture & Physics
|
||||
|
||||
* **Double-Smoothed EMA:** The APZ uses a two-stage exponential smoothing process where an EMA is calculated on another EMA, but with a modified period of sqrt(lookback_period). This creates a faster-responding average than standard EMAs while reducing lag.
|
||||
APZ relies on a "Double-Smoothed EMA" (DS-EMA) for both the centerline and the band width. The smoothing factor is aggressive, derived from the square root of the period, making it significantly faster than a standard EMA.
|
||||
|
||||
* **Adaptive Range Calculation:** Instead of using Average True Range (ATR), the APZ calculates an adaptive range by applying the same double-smoothed EMA process to the high-low range. This creates bands that dynamically adjust to recent volatility.
|
||||
### Calculation Steps
|
||||
|
||||
* **Volatility-Based Bands:** The upper and lower bands expand and contract based on current market volatility. Wider bands indicate higher volatility and uncertainty, while narrower bands suggest lower volatility and consolidation.
|
||||
1. **Smoothing Factor**:
|
||||
$$\alpha = \frac{2}{\sqrt{Period} + 1}$$
|
||||
|
||||
* **Mean Reversion Signal:** The core trading logic relies on the statistical principle that prices tend to revert to their mean. When price breaches the bands, it suggests an overextension that is likely to reverse.
|
||||
2. **Center Line (DS-EMA of Price)**:
|
||||
$$EMA1_{Price} = \text{Price}_t \times \alpha + EMA1_{Price, t-1} \times (1 - \alpha)$$
|
||||
$$Center_t = EMA1_{Price} \times \alpha + Center_{t-1} \times (1 - \alpha)$$
|
||||
|
||||
* **Non-Trending Market Focus:** The APZ is specifically designed for choppy, sideways markets. It works best when used in conjunction with a trend filter like ADX to avoid false signals during strong trends.
|
||||
3. **Adaptive Range (DS-EMA of Range)**:
|
||||
$$Range_t = \text{High}_t - \text{Low}_t$$
|
||||
$$EMA1_{Range} = Range_t \times \alpha + EMA1_{Range, t-1} \times (1 - \alpha)$$
|
||||
$$SmoothRange_t = EMA1_{Range} \times \alpha + SmoothRange_{t-1} \times (1 - \alpha)$$
|
||||
|
||||
## Common Settings and Parameters
|
||||
4. **Bands**:
|
||||
$$BandWidth_t = SmoothRange_t \times Factor$$
|
||||
$$Upper_t = Center_t + BandWidth_t$$
|
||||
$$Lower_t = Center_t - BandWidth_t$$
|
||||
|
||||
| Parameter | Default | Function | When to Adjust |
|
||||
| --------- | ------- | -------- | -------------- |
|
||||
| Period | 20 | Controls the lookback period for calculations (sqrt applied internally) | Increase (30-50) for longer-term analysis and smoother bands; decrease (10-15) for more responsive, shorter-term signals |
|
||||
| Band Multiplier | 2.0 | Multiplier for band width based on adaptive range | Increase (2.5-3.0) for wider bands in more volatile markets; decrease (1.5-1.8) for tighter bands and more frequent signals |
|
||||
| Source | source | Data source for middle line calculation | Use 'typical price' (hlc3) for incorporating full bar range; use 'close' for end-of-period focus |
|
||||
|
||||
**Pro Tip:** Start with the default settings (period=20, multiplier=2.0) and adjust based on your trading timeframe and market conditions. For intraday trading on choppy markets, consider period=30 with multiplier=1.8 for more frequent signals. For daily charts in range-bound markets, period=20 with multiplier=2.2 provides reliable reversal points. Always combine with a trend filter (ADX < 30) to avoid using the APZ in strongly trending conditions where it may generate false signals.
|
||||
|
||||
## Calculation and Mathematical Foundation
|
||||
|
||||
**Explanation:**
|
||||
The APZ calculation begins by determining a modified smoothing period using the square root of the user-specified lookback period. This creates a faster response time compared to using the full period. Two independent double-smoothed EMAs are then calculated: one for the price data and one for the price range. The price EMA forms the middle line, while the range EMA determines the band width. The bands are created by adding and subtracting a multiple of the adaptive range from the middle line.
|
||||
|
||||
**Technical formula:**
|
||||
|
||||
``` code
|
||||
Step 1: Calculate smoothing period
|
||||
smoothing_period = sqrt(period)
|
||||
alpha = 2 / (smoothing_period + 1)
|
||||
|
||||
Step 2: Calculate double-smoothed EMA for price
|
||||
EMA1_price = alpha × price + (1 - alpha) × EMA1_price[1]
|
||||
EMA2_price = alpha × EMA1_price + (1 - alpha) × EMA2_price[1]
|
||||
middle_line = EMA2_price
|
||||
|
||||
Step 3: Calculate double-smoothed EMA for range
|
||||
range = high - low
|
||||
EMA1_range = alpha × range + (1 - alpha) × EMA1_range[1]
|
||||
EMA2_range = alpha × EMA1_range + (1 - alpha) × EMA2_range[1]
|
||||
adaptive_range = EMA2_range
|
||||
|
||||
Step 4: Calculate bands
|
||||
upper_band = middle_line + (band_multiplier × adaptive_range)
|
||||
lower_band = middle_line - (band_multiplier × adaptive_range)
|
||||
```
|
||||
|
||||
> 🔍 **Technical Note:** The implementation uses compound warmup compensation for the nested EMAs. Since both smoothing stages use the same alpha, the total warmup decay factor is beta², where beta = (1 - alpha). This optimization provides accurate values from bar 1 without requiring separate compensation for each smoothing stage. The adaptive range calculation using high-low spread provides a faster-responding volatility measure compared to ATR, making the bands more reactive to sudden volatility changes.
|
||||
|
||||
## Interpretation Details
|
||||
|
||||
### **Primary Use Case: Mean Reversion Trading**
|
||||
|
||||
* When price crosses **above the upper band**, consider this a **sell signal** in anticipation of a reversal back toward the middle line
|
||||
* When price crosses **below the lower band**, consider this a **buy signal** in anticipation of a reversal back toward the middle line
|
||||
* The magnitude of the breach (how far price extends beyond the band) can indicate the strength of the expected reversal
|
||||
|
||||
### **Secondary Use Case: Volatility Assessment**
|
||||
|
||||
* **Widening bands** indicate increasing volatility and market uncertainty, suggesting caution or preparation for a breakout
|
||||
* **Narrowing bands** indicate decreasing volatility and consolidation, often preceding a significant move
|
||||
* **Band squeeze** (very narrow bands) can signal an impending breakout or breakdown, though direction remains uncertain
|
||||
|
||||
### **Trend Filter Integration**
|
||||
|
||||
* The APZ works best in **non-trending markets** when ADX < 30
|
||||
* When ADX > 30 and rising, the market is trending and price may continue beyond the bands rather than reversing
|
||||
* In strong trends, band violations may signal continuation rather than reversal, leading to false signals
|
||||
|
||||
### **Entry and Exit Strategy**
|
||||
|
||||
* **Aggressive Entry:** Enter immediately when price touches or crosses the band
|
||||
* **Conservative Entry:** Wait for price to close beyond the band and then re-enter the zone on the next bar
|
||||
* **Exit Strategy:** Target the middle line or opposite band; use ATR-based stops rather than waiting for opposite band signal
|
||||
* **Partial Exits:** Consider scaling out at the middle line and holding remainder for opposite band
|
||||
|
||||
## Limitations and Considerations
|
||||
|
||||
* **Trending Market Ineffectiveness:** The APZ is specifically designed for range-bound markets and will generate numerous false signals during strong trends. When ADX readings exceed 30, the indicator's reliability decreases significantly as price may continue in the trend direction rather than reversing at the bands.
|
||||
|
||||
* **Lag Despite Fast Response:** While the double-smoothed EMA with sqrt(period) responds faster than standard moving averages, it still contains inherent lag. In rapidly changing markets, the bands may not adjust quickly enough to prevent losses from momentum-driven moves.
|
||||
|
||||
* **No Directional Bias:** The APZ provides reversal signals based purely on statistical overextension but offers no insight into which direction is more likely after a reversal. Traders should use additional tools (market structure, volume, momentum indicators) to assess directional bias.
|
||||
|
||||
* **Parameter Sensitivity:** The effectiveness of the APZ is highly dependent on proper parameter selection. Too tight (low multiplier) generates excessive false signals, while too wide (high multiplier) misses reversal opportunities. Parameters must be optimized for specific markets and timeframes.
|
||||
|
||||
* **Whipsaw Risk in Volatile Markets:** During periods of high volatility with no clear direction, price may oscillate across the bands multiple times, generating conflicting signals and potential whipsaw losses. The adaptive range helps but doesn't eliminate this risk.
|
||||
|
||||
* **Requires Complementary Analysis:** The APZ should never be used in isolation. Successful implementation requires combining it with trend filters (ADX), volume confirmation, market structure analysis, and proper risk management. Entry and exit rules must be clearly defined and tested.
|
||||
|
||||
## References
|
||||
|
||||
* Leibfarth, Lee (2006). "Trading With An Adaptive Price Zone," *Technical Analysis of Stocks & Commodities*, Volume 24:9, September 2006, pages 28-31.
|
||||
* Investopedia. "Adaptive Price Zone (APZ)"
|
||||
|
||||
## Validation Sources
|
||||
|
||||
**Patterns:** §2, §7, §9, §11, §16
|
||||
**Wolfram:** Manual verification of double-smoothed EMA formula and compound warmup
|
||||
**External:** Leibfarth 2006 original article, Investopedia definition, TradingView implementations
|
||||
**API:** ref-tools verified input.int, input.float, input.source, plot signatures
|
||||
**Planning:** sequential-thinking phases = research, formula_analysis, nested_ema_structure, warmup_strategy, category_placement, parameter_validation, documentation_requirements, implementation_summary
|
||||
Where $Period$ determines responsiveness and $Factor$ scales the zone width.
|
||||
|
||||
## Performance Profile
|
||||
|
||||
### Operation Count (Streaming Mode, per Bar)
|
||||
The implementation utilizes compounded warmup compensation to stabilize the nested EMAs derived from bar 1 (zero-lag start).
|
||||
|
||||
### Operation Count - Single value
|
||||
|
||||
| Operation | Count | Cost (cycles) | Subtotal |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
@@ -126,154 +47,87 @@ lower_band = middle_line - (band_multiplier × adaptive_range)
|
||||
| SQRT | 1 | 15 | 15 |
|
||||
| **Total** | **21** | — | **~67 cycles** |
|
||||
|
||||
**Breakdown:**
|
||||
- SQRT(period): 1 SQRT = 15 cycles (computed once at construction)
|
||||
- Double EMA (price): 2 FMA = 8 cycles (EMA1 → EMA2)
|
||||
- Double EMA (range): 2 FMA = 8 cycles (EMA1 → EMA2)
|
||||
- Range calc: 1 SUB = 1 cycle (High - Low)
|
||||
- Upper band: 1 MUL + 1 ADD = 4 cycles
|
||||
- Lower band: 1 MUL + 1 SUB = 4 cycles
|
||||
*Note: SQRT is computed once at initialization. The runtime complexity is dominated by the 4 FMA instructions for the double smoothing.*
|
||||
|
||||
*Note: SQRT is amortized since period is computed once at indicator creation.*
|
||||
### Operation Count - Batch processing
|
||||
|
||||
### Complexity Analysis
|
||||
| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Double Smoothing | 4N | N/A | 1× |
|
||||
|
||||
| Mode | Complexity | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| Streaming | O(1) | Four IIR recursions (double-smoothed EMAs), constant time |
|
||||
| Batch | O(n) | Linear scan, n = series length |
|
||||
|
||||
**Memory**: ~80 bytes (four EMA states + alpha/decay constants).
|
||||
|
||||
**Warmup Period**: $2 \times \lceil 3/\alpha \rceil$ bars due to double-smoothing.
|
||||
|
||||
### SIMD Analysis
|
||||
|
||||
| Optimization | Applicable | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| AVX2 vectorization | ❌ | Nested EMA recursion prevents cross-bar parallelization |
|
||||
| FMA | ✅ | All four EMA stages use `Math.FusedMultiplyAdd` |
|
||||
| Batch parallelism | Partial | Price and range double-EMAs can run in parallel |
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
| Metric | Score | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **Accuracy** | 10/10 | Mathematically exact double-smoothed EMA |
|
||||
| **Timeliness** | 7/10 | Faster than standard EMA due to sqrt(period) factor |
|
||||
| **Overshoot** | 4/10 | Adaptive range more responsive than ATR |
|
||||
| **Smoothness** | 8/10 | Double-smoothing reduces noise significantly |
|
||||
*Note: Due to the nested recursive nature ($t$ depends on $t-1$), vectorization is limited to parallel processing of Price and Range chains.*
|
||||
|
||||
## Validation
|
||||
|
||||
| Library | Status | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| :--- | :--- | :--- |
|
||||
| **TA-Lib** | N/A | Not implemented |
|
||||
| **Skender** | N/A | Not implemented |
|
||||
| **Tulip** | N/A | Not implemented |
|
||||
| **Ooples** | N/A | Not implemented |
|
||||
| **Internal** | ✅ | Mode consistency verified |
|
||||
| **TradingView** | ✅ | Matches Leibfarth's original formula |
|
||||
| **Internal** | ✅ | Validated against Leibfarth's formula |
|
||||
| **TradingView** | ✅ | Matches standard scripts |
|
||||
|
||||
## C# Usage Examples
|
||||
## Usage & Pitfalls
|
||||
|
||||
### Basic Streaming Usage
|
||||
- **Market Regime**: APZ is a **Mean Reversion** tool. It works best when ADX < 30. In strong trends, price will "surf" the bands rather than reverse.
|
||||
- **Whipsaw**: The bands are extremely responsive. A closing price outside the bands suggests an immediate reversal, not a breakout.
|
||||
- **Period Selection**: Because of the square root, a period of 20 (sqrt≈4.47) behaves like an EMA of ~3.5. It is much faster than a standard 20 EMA.
|
||||
|
||||
## API
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Apz {
|
||||
+TValue Last
|
||||
+TValue Upper
|
||||
+TValue Lower
|
||||
+bool IsHot
|
||||
+Update(TBar bar) TValue
|
||||
+Update(TBarSeries source) tuple
|
||||
+Batch(...) void
|
||||
}
|
||||
```
|
||||
|
||||
### Class: `Apz`
|
||||
|
||||
| Parameter | Type | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `period` | `int` | — | `>0` | Lookback period (internally $\sqrt{P}$). |
|
||||
| `multiplier` | `double` | `2.0` | `>0` | Band width factor. |
|
||||
| `source` | `TBarSeries` | — | `any` | Initial input source (optional). |
|
||||
|
||||
### Properties
|
||||
|
||||
- `Last` (`TValue`): The current center line (DS-EMA Price).
|
||||
- `Upper` (`TValue`): The current upper band.
|
||||
- `Lower` (`TValue`): The current lower band.
|
||||
- `IsHot` (`bool`): Returns `true` if valid data is available (warmup complete).
|
||||
|
||||
### Methods
|
||||
|
||||
- `Update(TBar input)`: Updates the indicator with a new bar.
|
||||
- `Update(TBarSeries source)`: Processes a full series.
|
||||
- `Batch(...)`: Static method for high-performance batch processing.
|
||||
|
||||
## C# Example
|
||||
|
||||
```csharp
|
||||
// Create APZ indicator with period 20 and band multiplier 2.0
|
||||
var apz = new Apz(period: 20, multiplier: 2.0);
|
||||
using QuanTAlib;
|
||||
|
||||
// Process bars one at a time (streaming)
|
||||
foreach (var bar in barSeries)
|
||||
// Initialize
|
||||
var indicator = new Apz(period: 20, multiplier: 2.0);
|
||||
|
||||
// Update Loop
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
apz.Update(bar);
|
||||
var center = indicator.Update(bar);
|
||||
|
||||
// Access the three output values
|
||||
double middle = apz.Last.Value; // Double-smoothed EMA middle line
|
||||
double upper = apz.Upper.Value; // Upper band
|
||||
double lower = apz.Lower.Value; // Lower band
|
||||
|
||||
// Check if indicator has warmed up
|
||||
if (apz.IsHot)
|
||||
// Mean reversion logic
|
||||
if (indicator.IsHot)
|
||||
{
|
||||
// Mean reversion signals
|
||||
if (bar.Close > upper)
|
||||
Console.WriteLine("Price above upper band - potential sell signal");
|
||||
else if (bar.Close < lower)
|
||||
Console.WriteLine("Price below lower band - potential buy signal");
|
||||
if (bar.Close > indicator.Upper.Value)
|
||||
Console.WriteLine("Overshoot: Sell Signal");
|
||||
if (bar.Close < indicator.Lower.Value)
|
||||
Console.WriteLine("Undershoot: Buy Signal");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
|
||||
```csharp
|
||||
// Static batch method for processing entire series at once
|
||||
var (middle, upper, lower) = Apz.Batch(barSeries, period: 20, multiplier: 2.0);
|
||||
|
||||
// Access results as TSeries
|
||||
for (int i = 0; i < middle.Count; i++)
|
||||
{
|
||||
Console.WriteLine($"Bar {i}: Middle={middle[i].Value:F2}, " +
|
||||
$"Upper={upper[i].Value:F2}, Lower={lower[i].Value:F2}");
|
||||
}
|
||||
```
|
||||
|
||||
### High-Performance Span-Based Calculation
|
||||
|
||||
```csharp
|
||||
// Zero-allocation span-based calculation for maximum performance
|
||||
double[] highArr = barSeries.High.Values.ToArray();
|
||||
double[] lowArr = barSeries.Low.Values.ToArray();
|
||||
double[] closeArr = barSeries.Close.Values.ToArray();
|
||||
|
||||
double[] middleOutput = new double[closeArr.Length];
|
||||
double[] upperOutput = new double[closeArr.Length];
|
||||
double[] lowerOutput = new double[closeArr.Length];
|
||||
|
||||
Apz.Batch(
|
||||
highArr.AsSpan(), lowArr.AsSpan(), closeArr.AsSpan(),
|
||||
middleOutput.AsSpan(), upperOutput.AsSpan(), lowerOutput.AsSpan(),
|
||||
period: 20, multiplier: 2.0);
|
||||
```
|
||||
|
||||
### Event-Driven Architecture
|
||||
|
||||
```csharp
|
||||
// Subscribe to bar source for reactive updates
|
||||
var barSource = new TBarSeries();
|
||||
var apz = new Apz(barSource, period: 20, multiplier: 2.0);
|
||||
|
||||
// APZ automatically updates when bars are added to the source
|
||||
barSource.Add(newBar);
|
||||
Console.WriteLine($"Current APZ: {apz.Last.Value:F2}");
|
||||
```
|
||||
|
||||
### Calculate with Primed Indicator
|
||||
|
||||
```csharp
|
||||
// Get both results and a primed indicator for continued streaming
|
||||
var ((middle, upper, lower), indicator) = Apz.Calculate(barSeries, period: 20, multiplier: 2.0);
|
||||
|
||||
// Indicator is now "hot" and ready for streaming
|
||||
Console.WriteLine($"Indicator ready: IsHot={indicator.IsHot}");
|
||||
|
||||
// Continue streaming new bars
|
||||
indicator.Update(newBar);
|
||||
```
|
||||
|
||||
### Bar Correction (Intra-Bar Updates)
|
||||
|
||||
```csharp
|
||||
var apz = new Apz(period: 20, multiplier: 2.0);
|
||||
|
||||
// First update creates a new bar
|
||||
apz.Update(bar, isNew: true);
|
||||
|
||||
// Subsequent updates within the same bar use isNew: false
|
||||
apz.Update(updatedBar, isNew: false); // Corrects the current bar
|
||||
apz.Update(finalBar, isNew: false); // Further correction
|
||||
|
||||
// Next bar starts with isNew: true again
|
||||
apz.Update(nextBar, isNew: true);
|
||||
```
|
||||
Reference in New Issue
Block a user