diff --git a/lib/_index.md b/lib/_index.md index ef79774e..243adef2 100644 --- a/lib/_index.md +++ b/lib/_index.md @@ -126,8 +126,8 @@ | [ENTROPY](statistics/entropy/Entropy.md) | Shannon Entropy | Statistics | | [EOM](volume/eom/Eom.md) | Ease of Movement | Volume | | [EVWMA](volume/evwma/Evwma.md) | Elastic Volume Weighted MA | Volume | -| ER | Efficiency Ratio | Oscillators | -| ERI | Elder Ray Index | Oscillators | +| [ER](oscillators/er/Er.md) | Efficiency Ratio | Oscillators | +| [ERI](oscillators/eri/Eri.md) | Elder Ray Index | Oscillators | | [EWMA](volatility/ewma/Ewma.md) | EWMA Volatility | Volatility | | EXPDIST | Exponential Distribution | Numerics | | [EXPTRANS](numerics/exptrans/Exptrans.md) | Exponential Transform | Numerics | @@ -192,7 +192,7 @@ | [KCHANNEL](channels/kchannel/kchannel.md) | Keltner Channel | Channels | | [KDJ](oscillators/kdj/Kdj.md) | KDJ Indicator | Oscillators | | [KENDALL](statistics/kendall/Kendall.md) | Kendall Rank Correlation | Statistics | -| KRI | Kairi Relative Index | Oscillators | +| [KRI](oscillators/kri/Kri.md) | Kairi Relative Index | Oscillators | | LANCZOS | Lanczos (sinc) Window MA | Trends (FIR) | | KST | KST Oscillator | Oscillators | | [KURTOSIS](statistics/kurtosis/Kurtosis.md) | Kurtosis | Statistics | @@ -267,7 +267,7 @@ | [PPO](momentum/ppo/Ppo.md) | Percentage Price Oscillator | Momentum | | [PRS](momentum/prs/Prs.md) | Price Relative Strength | Momentum | | [PSAR](reversals/psar/Psar.md) | Parabolic Stop And Reverse | Reversals | -| PSL | Psychological Line | Oscillators | +| [PSL](oscillators/psl/Psl.md) | Psychological Line | Oscillators | | [PSEUDOHUBER](errors/pseudohuber/Pseudohuber.md) | Pseudo-Huber Loss | Errors | | [PVD](volume/pvd/Pvd.md) | Price Volume Divergence | Volume | | [PVI](volume/pvi/Pvi.md) | Positive Volume Index | Volume | diff --git a/lib/channels/abber/abber.md b/lib/channels/abber/abber.md index 453cd4e7..471e30b0 100644 --- a/lib/channels/abber/abber.md +++ b/lib/channels/abber/abber.md @@ -1,134 +1,89 @@ # ABBER: Aberration Bands -> "Standard deviation punishes outliers twice: once when they happen, once when they distort everything else." - -ABBER (Aberration Bands) measures price deviation from a central moving average using absolute deviation rather than standard deviation. The result: dynamic bands that adapt to volatility while remaining robust against extreme outliers. Where Bollinger Bands amplify outliers through squaring, ABBER uses raw absolute differences efficiently. Bands respond to typical price behavior, not the occasional spike that yanks everything sideways. +ABBER measures price deviation from a central moving average using mean absolute deviation rather than standard deviation, producing dynamic bands that adapt to volatility while remaining robust against extreme outliers. Where Bollinger Bands amplify outliers through squaring (the $L^2$ norm), ABBER uses raw absolute differences (the $L^1$ norm), so bands respond to typical price behavior rather than the occasional spike that yanks everything sideways. For a 20-period window with a 2.0 multiplier, ABBER contains approximately 89% of normally-distributed price action, but its real advantage emerges with fat-tailed distributions where standard deviation overreacts to single-bar anomalies. ## Historical Context -Aberration Bands emerged as a response to the statistical assumptions baked into Bollinger Bands. Standard deviation assumes normally distributed returns. Markets often defy that assumption daily with fat tails, volatility clustering, and flash crashes. The squared-deviation approach treats these events as if they carry information about typical behavior, whereas they often represent noise. +The absolute deviation approach predates Bollinger's work by decades. Mean absolute deviation appears in early 20th-century statistics as a robust alternative to standard deviation, championed by statisticians who recognized that squaring deviations gives disproportionate weight to outliers. In financial markets, applying absolute deviation to band construction arrived after practitioners grew tired of watching Bollinger Bands blow out on single-bar anomalies such as flash crashes, earnings gaps, and fat-finger trades. -The absolute deviation approach predates Bollinger's work (mean absolute deviation appears in early 20th-century statistics), but applying it to band construction arrived later, once practitioners grew tired of watching their bands blow out on single-bar anomalies. No single inventor claims credit; the technique spread through trading floors where robustness mattered more than textbook elegance. +No single inventor claims credit for ABBER. The technique spread through trading floors where robustness mattered more than textbook elegance. The mathematical distinction is fundamental: standard deviation is a quadratic spring that amplifies outliers, while mean absolute deviation is a linear damper that treats all deviations proportionally. Under Gaussian assumptions, $\text{MAD} \approx 0.7979 \sigma$, so ABBER with multiplier 2.0 is roughly equivalent to Bollinger Bands with multiplier 1.6. But on real market data with kurtosis > 3, the gap widens in ABBER's favor. ## Architecture & Physics -ABBER computes three outputs through running sums maintained in O(1) streaming time. The fundamental difference from standard deviation is linearity: ABBER is a linear damper, while standard deviation is a quadratic spring. +### 1. Central Tendency (SMA) -### Calculation Steps +The middle band is a Simple Moving Average over the lookback window: -The algorithm maintains a central tendency (SMA) and a dispersion measure (Average Absolute Deviation). +$$\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} x_{t-i}$$ -1. **Middle Band (SMA)** - $$\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} \text{Source}_{t-i}$$ +### 2. Absolute Deviation -2. **Absolute Deviation** - $$\text{Deviation}_t = |\text{Source}_t - \text{Middle}_{t-1}|$$ +Each bar's deviation is measured against the previous middle band value: -3. **Average Absolute Deviation** - $$\text{AvgDev}_t = \frac{1}{n} \sum_{i=0}^{n-1} \text{Deviation}_{t-i}$$ +$$d_t = |x_t - \text{Middle}_{t-1}|$$ -4. **Band Calculation** - $$\text{Upper}_t = \text{Middle}_t + (k \times \text{AvgDev}_t)$$ - $$\text{Lower}_t = \text{Middle}_t - (k \times \text{AvgDev}_t)$$ +### 3. Average Absolute Deviation - Where $n$ = lookback period (default: 20), $k$ = multiplier (default: 2.0). +The deviation series is itself averaged over the same window: -## Performance Profile +$$\text{AvgDev}_t = \frac{1}{n} \sum_{i=0}^{n-1} d_{t-i}$$ -The implementation uses circular buffers to maintain running sums for both the SMA and the Average Deviation, ensuring O(1) complexity per update regardless of period length. +### 4. Band Construction -### Operation Count - Single value +$$\text{Upper}_t = \text{Middle}_t + k \cdot \text{AvgDev}_t$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 6 | 1 | 6 | -| MUL | 2 | 3 | 6 | -| DIV | 2 | 15 | 30 | -| ABS | 1 | 1 | 1 | -| **Total** | **11** | — | **~43 cycles** | +$$\text{Lower}_t = \text{Middle}_t - k \cdot \text{AvgDev}_t$$ -### Operation Count - Batch processing +### 5. Complexity -While the recursive nature of SMA prevents full vectorization of the running state dependent steps, the final band construction supports SIMD. +Both the SMA and the average deviation use circular buffers with running sums, yielding $O(1)$ per bar in streaming mode. The SIMD-accelerable portion is the final band construction step ($\text{Middle} \pm k \cdot \text{AvgDev}$), while the running-sum maintenance is inherently serial. -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| Band Construction | 2N | 2N/VectorSize | ~4-8× | -| Deviation | N | N | 1× | +## Mathematical Foundation -## Validation +### Parameters -ABBER lacks wide support in standard libraries like TA-Lib, so validation relies on internal consistency checks between streaming, batch, and span-based modes. +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback window for SMA and deviation averaging | 20 | $> 0$ | +| `multiplier` | Band width scale factor ($k$) | 2.0 | $> 0$ | +| `source` | Input price series | close | | +| `ma_line` | Pre-computed moving average (center line) | SMA | configurable | -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | N/A | Not implemented | -| **Skender** | N/A | Not implemented | -| **Internal** | ✅ | Streaming/Batch/Span match exactly | -| **Manual** | ✅ | Validated against spreadsheet calculation | +### Relationship to Standard Deviation -## Usage & Pitfalls +For a normal distribution: -- **Parameter Sensitivity**: Multiplier of 2.0 captures ~89% of data under Gaussian assumptions, but market distributions vary. Adjust based on asset volatility characteristics. -- **Lag Inheritance**: ABBER inherits SMA lag. For a 20-period setting, expect approximately 10 bars of delay in band response. -- **Band Squeeze**: Narrowing bands signal consolidation, but ABBER narrows more slowly than Bollinger Bands after volatility spikes. -- **Interpretation**: Price touching the upper band indicates strength (potentially overbought), while touching the lower band indicates weakness. +$$\text{MAD} = \sigma \sqrt{\frac{2}{\pi}} \approx 0.7979\,\sigma$$ -## API +Therefore ABBER with $k = 2.0$ captures approximately the same range as Bollinger Bands with $k \approx 1.596$. -```mermaid -classDiagram - class Abber { - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +event Pub - +Update(TValue input) TValue - +Update(TSeries source) tuple - +Batch(TSeries source, int p, double k) tuple - } +### Pseudo-code + +``` +function ABBER(source, ma_line, period, multiplier): + // Deviation from center line + deviation = |source - ma_line| + + // Average absolute deviation (SMA of deviations) + avg_dev = SMA(deviation, period) + + // Band construction + upper = ma_line + multiplier * avg_dev + lower = ma_line - multiplier * avg_dev + + return [upper, lower, avg_dev] ``` -### Class: `Abber` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback period for SMA and deviation. | -| `multiplier` | `double` | `2.0` | `>0` | Multiplier for band width (k). | -| `source` | `TSeries` | — | `any` | Initial input source (optional). | +| Output | Description | +|--------|-------------| +| `upper` | Upper aberration band | +| `lower` | Lower aberration band | +| `avg_dev` | Current average absolute deviation (band half-width before scaling) | -### Properties +## Resources -- `Last` (`TValue`): The current middle band value (SMA). -- `Upper` (`TValue`): The current upper band value. -- `Lower` (`TValue`): The current lower band value. -- `IsHot` (`bool`): Returns `true` if valid data is available (warmup complete). - -### Methods - -- `Update(TValue input)`: Updates the indicator with a new data point. -- `Update(TSeries source)`: Processes a full series. -- `Batch(...)`: Static method for high-performance batch processing. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var indicator = new Abber(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in quotes) -{ - // Update with Close price - var result = indicator.Update(bar.Close); - - // Use valid results - if (indicator.IsHot) - { - Console.WriteLine($"{bar.Date}: Middle={result.Value:F2} Upper={indicator.Upper.Value:F2} Lower={indicator.Lower.Value:F2}"); - } -} -``` +- **Pham-Gia, T. & Hung, T.L.** "The Mean and Median Absolute Deviations." *Mathematical and Computer Modelling*, 34(7-8), 2001. (MAD vs. standard deviation theory) +- **Bollinger, J.** *Bollinger on Bollinger Bands*. McGraw-Hill, 2001. (Standard deviation band predecessor) +- **Hampel, F.R.** "The Influence Curve and its Role in Robust Estimation." *Journal of the American Statistical Association*, 69(346), 1974. (Robustness theory for $L^1$ vs $L^2$ norms) diff --git a/lib/channels/accbands/accbands.md b/lib/channels/accbands/accbands.md index adc55ac7..eb699650 100644 --- a/lib/channels/accbands/accbands.md +++ b/lib/channels/accbands/accbands.md @@ -1,124 +1,89 @@ # ACCBANDS: Acceleration Bands -> "Price creates its own envelope, expanding with potential and contracting with consensus." - -Acceleration Bands (ACCBANDS) serve as an adaptive volatility envelope based on the high-low range rather than standard deviation. Unlike Bollinger Bands which use close-to-close variance, Acceleration Bands utilize the intra-bar high-low spread to gauge volatility, creating channels that accommodate the full price excursion of the underlying asset. +Acceleration Bands construct a volatility envelope using the intra-bar high-low range rather than close-to-close standard deviation, creating channels that accommodate the full price excursion of the underlying asset. Each bar's contribution to band width is normalized by price level ($w = (H-L)/(H+L)$), making the bands scale-invariant across instruments. Three independent Simple Moving Averages of the adjusted high, adjusted low, and close prices form the upper, lower, and middle bands respectively. Headley's original breakout rule declares a trend when price closes outside the bands for two consecutive bars. ## Historical Context -Developed by Price Headley and detailed in *Big Trends in Trading* (2002), Acceleration Bands addressed the need for a breakout-specific envelope. Headley observed that standard deviation often lagged in fast-moving breakout scenarios. By incorporating the High and Low prices directly into the band width calculation — using a per-bar normalized range width — he created a system that reacts immediately to range expansion, often serving as a trigger for trend-following entries when price closes outside the bands. +Price Headley developed Acceleration Bands and detailed them in *Big Trends in Trading* (Wiley, 2002). Headley observed that standard deviation bands often lag in fast-moving breakout scenarios because they require several bars of expanded volatility before the bands visibly widen. By incorporating High and Low prices directly into the band width calculation through a per-bar normalized range, he created a system that reacts immediately to range expansion. + +The normalization $w = (H-L)/(H+L)$ is the key design choice. Dividing range by the sum of high and low produces a dimensionless ratio that is comparable across any price level. A $5 stock with a $0.50 range and a $500 stock with a $50 range both produce $w = 0.05$. The default factor of 4.0 was Headley's empirically determined value for equity markets on daily timeframes, matching the TA-Lib reference implementation. ## Architecture & Physics -The indicator applies a per-bar width adjustment based on the normalized range `w = (H-L)/(H+L)` before averaging. This means wider-range bars contribute proportionally more to band expansion. Three Simple Moving Averages (adjusted high, adjusted low, close) construct the bands. +### 1. Per-Bar Normalized Width -### Calculation Steps (Headley's Formula) +For each bar, compute the range as a fraction of total price: -1. **Per-bar normalized width**: - $$w_t = \frac{High_t - Low_t}{High_t + Low_t}$$ +$$w_t = \frac{H_t - L_t}{H_t + L_t}$$ -2. **Adjusted prices per bar**: - $$AdjHigh_t = High_t \times (1 + Factor \times w_t)$$ - $$AdjLow_t = Low_t \times (1 - Factor \times w_t)$$ +When $H_t + L_t = 0$ (price is zero), $w_t = 0$ to prevent division by zero. -3. **Band Construction**: - $$Upper_t = SMA(AdjHigh, n)$$ - $$Lower_t = SMA(AdjLow, n)$$ - $$Middle_t = SMA(Close, n)$$ +### 2. Adjusted Prices - Where $n$ = period (default 20), $Factor$ = multiplier (default 4.0). +The high and low are expanded by the normalized width scaled by the factor: -## Performance Profile +$$\text{AdjHigh}_t = H_t \times (1 + F \cdot w_t)$$ -The implementation uses three independent circular buffers (adjusted high, adjusted low, close) to maintain O(1) complexity for the moving averages. +$$\text{AdjLow}_t = L_t \times (1 - F \cdot w_t)$$ -### Operation Count - Single value +### 3. Band Construction (Three SMAs) -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 10 | 1 | 10 | -| MUL | 4 | 3 | 12 | -| DIV | 4 | 15 | 60 | -| **Total** | **18** | — | **~82 cycles** | +$$\text{Upper}_t = \text{SMA}(\text{AdjHigh}, n)$$ -### Operation Count - Batch processing +$$\text{Lower}_t = \text{SMA}(\text{AdjLow}, n)$$ -SIMD optimization is applied to the sum resynchronization, though the recursive nature of the SMAs limits full vectorization of the state maintenance. +$$\text{Middle}_t = \text{SMA}(\text{Close}, n)$$ -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| Band Construction | 3N | 3N/VectorSize | ~4-8× | -| SMAs | 3N | 3N | 1× | +### 4. Complexity -## Validation +Three independent circular buffers maintain running sums for $O(1)$ streaming updates. Each bar requires computing $w_t$, the two adjusted prices, and three buffer updates. -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | ✅ | All three bands match exactly (same Headley formula) | -| **Internal** | ✅ | Streaming/Batch/Span match exactly | +## Mathematical Foundation -## Usage & Pitfalls +### Parameters -- **Trend Definition**: Headley defines a breakout as two consecutive closes outside the bands. -- **Parameter Sensitivity**: The default factor of 4.0 matches TA-Lib and Headley's original. Lower factors (e.g., 2.0) produce tighter bands; higher factors (e.g., 6.0) may be needed for crypto/FX. -- **Lag**: Inherits the lag of the underlying SMA. Not suitable for ultra-high-frequency reacting. -- **Range vs Variance**: Because it uses High-Low range, it is more sensitive to "wicks" or momentary spikes than close-based envelopes. -- **Division by Zero**: When High + Low = 0 (price is zero), the normalized width defaults to 0. +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback period for the three SMAs ($n$) | 20 | $> 0$ | +| `factor` | Multiplier for normalized width ($F$) | 4.0 | $> 0$ | -## API +### Pseudo-code -```mermaid -classDiagram - class AccBands { - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +event Pub - +Update(TBar bar) TValue - +Update(TBarSeries source) tuple - +Batch(TBarSeries source, int p, double f) tuple - } +``` +function ACCBANDS(high, low, close, period, factor): + // Per-bar normalized width + denom = high + low + w = denom ≠ 0 ? (high - low) / denom : 0 + + // Adjusted prices + adj_high = high * (1 + factor * w) + adj_low = low * (1 - factor * w) + + // Three independent SMAs + upper = SMA(adj_high, period) + lower = SMA(adj_low, period) + middle = SMA(close, period) + + return [middle, upper, lower] ``` -### Class: `AccBands` +### Breakout Rule (Headley) -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback period for SMAs. | -| `factor` | `double` | `4.0` | `>0` | Multiplier for normalized width. | -| `source` | `TBarSeries` | — | `any` | Initial input TBar data (optional). | +A trend is confirmed when: -### Properties +$$\text{Close}_t > \text{Upper}_t \quad \text{AND} \quad \text{Close}_{t-1} > \text{Upper}_{t-1}$$ -- `Last` (`TValue`): The current middle band value (SMA of Close). -- `Upper` (`TValue`): The current upper band value (SMA of adjusted High). -- `Lower` (`TValue`): The current lower band value (SMA of adjusted Low). -- `IsHot` (`bool`): Returns `true` if valid data is available (warmup complete). +(Two consecutive closes above the upper band.) Reverse logic for downside breakouts. -### Methods +### Output Interpretation -- `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. +| Output | Description | +|--------|-------------| +| `upper` | SMA of adjusted highs (resistance envelope) | +| `lower` | SMA of adjusted lows (support envelope) | +| `middle` | SMA of close (center line) | -## C# Example +## Resources -```csharp -using QuanTAlib; - -// Initialize -var indicator = new AccBands(period: 20, factor: 4.0); - -// Update Loop -foreach (var bar in bars) -{ - var result = indicator.Update(bar); - - // Use valid results - if (indicator.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={result.Value:F2} Up={indicator.Upper.Value:F2} Low={indicator.Lower.Value:F2}"); - } -} -``` +- **Headley, P.** *Big Trends in Trading*. Wiley, 2002. (Original Acceleration Bands specification) +- **TA-Lib** `TA_ACCBANDS` function. (Reference implementation with factor = 4.0) diff --git a/lib/channels/apchannel/apchannel.md b/lib/channels/apchannel/apchannel.md index a3b904a5..5d4095ce 100644 --- a/lib/channels/apchannel/apchannel.md +++ b/lib/channels/apchannel/apchannel.md @@ -1,126 +1,97 @@ # APCHANNEL: Adaptive Price Channel -> "A channel isn't a prediction—it's an acknowledgment that price has inertia and boundaries." - -APCHANNEL (Adaptive Price Channel) transforms the classic high-low tracking problem into an exponentially weighted persistence model. Unlike rigid lookback windows that drop price extremes abruptly, this indicator applies exponential decay to price highs and lows. The result is a channel that "remembers" significant resistance and support levels while gradually fading their influence over time, creating a smooth, lag-free volatility envelope. +APCHANNEL applies exponential smoothing independently to price highs and lows, creating a dynamic envelope that "remembers" significant extremes while gradually fading their influence over time. Unlike rigid Donchian channels that drop price extremes abruptly when they exit the lookback window (the "cliff effect"), APCHANNEL decays them smoothly through leaky integration. The result is a channel with continuously sloping boundaries that responds to volatility without the discontinuous jumps that plague fixed-window approaches. The algorithm is $O(1)$ per bar with only two state variables and no buffers. ## Historical Context -While traditional Price Channels (Donchian) define range by the absolute highest high and lowest low over a fixed period, the Adaptive Price Channel originates from the signal processing domain. It applies the concept of "leaky integration" or exponential smoothing directly to price extremes. This approach addresses the "cliff effect" of fixed windows: where a major high from 20 bars ago suddenly vanishes from the calculation. In APCHANNEL, that high fades gracefully, providing continuous rather than discontinuous volatility modeling. +Traditional Price Channels (Donchian, 1960s) define range by the absolute highest high and lowest low over a fixed period. When a major high from $n$ bars ago drops out of the window, the upper boundary can collapse instantaneously, producing discontinuous channel behavior that generates false signals. The Adaptive Price Channel addresses this by borrowing the exponential smoothing concept from signal processing, applying the same "leaky integrator" principle that electrical engineers use for envelope detection in AM radio circuits. + +The approach is equivalent to running two independent EMAs: one on the High series and one on the Low series. This connection to EMA theory means the channel inherits well-understood convergence properties. The half-life of influence is $\ln(2) / \ln(1/(1-\alpha))$ bars, and the channel is considered warm after approximately $3/\alpha$ bars. The single-parameter design ($\alpha$) makes APCHANNEL simpler to tune than multi-parameter alternatives. ## Architecture & Physics -The core mechanism is a dual Exponential Moving Average (EMA) system running on parallel tracks: one effectively smoothing the "ceilings" (highs) and another smoothing the "floors" (lows). +### 1. Dual EMA Recursion -### Calculation Steps +The upper and lower bands are independent EMA filters on High and Low: -1. **Exponential Decay**: - Each new bar's High and Low is integrated into the channel state using a smoothing factor $\alpha$. - $$Upper_t = \text{High}_{t} \times \alpha + Upper_{t-1} \times (1 - \alpha)$$ - $$Lower_t = \text{Low}_{t} \times \alpha + Lower_{t-1} \times (1 - \alpha)$$ +$$\text{Upper}_t = \alpha \cdot H_t + (1 - \alpha) \cdot \text{Upper}_{t-1}$$ -2. **Midpoint**: - The center of the channel is simply the arithmetic mean of the bands. - $$Middle_t = \frac{Upper_t + Lower_t}{2}$$ +$$\text{Lower}_t = \alpha \cdot L_t + (1 - \alpha) \cdot \text{Lower}_{t-1}$$ - Where $\alpha$ (alpha) is the smoothing factor ($0 < \alpha \le 1$). +Using the FMA pattern with $\text{decay} = 1 - \alpha$: -### Physics of Alpha +$$\text{Upper}_t = \text{FMA}(\text{decay}, \text{Upper}_{t-1}, \alpha \cdot H_t)$$ -- **High Alpha (e.g., 0.8)**: Short memory. The channel snaps quickly to new highs/lows and forgets old ones rapidly. -- **Low Alpha (e.g., 0.1)**: Long memory. Significant highs persist as resistance for a long time, decaying slowly. +### 2. Midpoint -## Performance Profile +$$\text{Middle}_t = \frac{\text{Upper}_t + \text{Lower}_t}{2}$$ -Because the calculation relies on recursive EMA logic, it is inherently O(1) in a streaming context—no history buffers or iterations are required. +### 3. Alpha Semantics -### Operation Count - Single value +- **High $\alpha$ (e.g., 0.8)**: Short memory. Channel snaps quickly to new extremes, forgets old ones rapidly. +- **Low $\alpha$ (e.g., 0.1)**: Long memory. Significant highs persist as resistance for dozens of bars. +- **Period approximation**: $\alpha \approx 2 / (P + 1)$ where $P$ is the equivalent EMA period. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| FMA | 2 | 4 | 8 | -| ADD | 1 | 1 | 1 | -| MUL | 0 | 3 | 0 | -| DIV | 1 | 15 | 15 | -| **Total** | **4** | — | **~24 cycles** | +### 4. Complexity -*Note: The implementation utilizes `Math.FusedMultiplyAdd` (FMA) for the EMA recursion step, combining multiplication and addition into a single, higher-precision CPU instruction.* +$O(1)$ per bar: 2 FMA operations + 1 addition + 1 division. No buffers, no history. The two bands are independent and can be computed in parallel. -### Operation Count - Batch processing +## Mathematical Foundation -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| EMA Recursion | 2N | N/A | 1× | +### Parameters -*Note: EMA recursion is strictly serial (requires $t-1$ to compute $t$), preventing vectorization across the time dimension. However, the High and Low bands are computed independently.* +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `alpha` | Smoothing factor (higher = faster decay) | 0.2 | $(0, 1]$ | -## Validation +### Initialization -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | N/A | Not implemented | -| **Skender** | ✅ | Validated against `GetEma` on High/Low | -| **Internal** | ✅ | Streaming/Batch/Span match exactly | +On the first bar: -## Usage & Pitfalls +$$\text{Upper}_0 = H_0, \quad \text{Lower}_0 = L_0$$ -- **Alpha vs Period**: Users familiar with periods can approximate $\alpha \approx 2 / (Period + 1)$. -- **Warmup**: The EMA structure requires a convergence period. The indicator is considered "hot" after $\approx 3/\alpha$ bars. -- **Responsiveness**: Unlike Donchian channels which are flat until a new breakout, APCHANNEL is constantly sloping. This makes it excellent for trend-following stops (trailing variance). -- **Whipsaws**: High alpha values in choppy markets will produce tight bands that generate excessive false breakout signals. +### Half-Life -## API +The number of bars for a price extreme's influence to decay by 50%: -```mermaid -classDiagram - class Apchannel { - +double UpperBand - +double LowerBand - +TValue Last - +bool IsHot - +Update(TBar bar) TValue - +Update(TBarSeries source) tuple - +Batch(double[] high, double[] low, ...) void - } +$$t_{1/2} = \frac{\ln 2}{\ln(1 / (1 - \alpha))}$$ + +For $\alpha = 0.2$: $t_{1/2} \approx 3.1$ bars. For $\alpha = 0.05$: $t_{1/2} \approx 13.5$ bars. + +### Pseudo-code + +``` +function APCHANNEL(high, low, alpha): + validate: 0 < alpha ≤ 1 + decay = 1 - alpha + + // EMA of highs + if first_bar: + upper = high + else: + upper = decay * upper + alpha * high + + // EMA of lows + if first_bar: + lower = low + else: + lower = decay * lower + alpha * low + + middle = (upper + lower) / 2 + + return [middle, upper, lower] ``` -### Class: `Apchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `alpha` | `double` | `0.2` | `(0, 1]` | Smoothing factor (higher = faster decay). | -| `source` | `TBarSeries` | — | `any` | Initial input source (optional). | +| Output | Description | +|--------|-------------| +| `upper` | Exponentially smoothed high (resistance) | +| `lower` | Exponentially smoothed low (support) | +| `middle` | Arithmetic mean of upper and lower | -### Properties +## Resources -- `Last` (`TValue`): The current midpoint value. -- `UpperBand` (`double`): The current upper exponential band value. -- `LowerBand` (`double`): The current lower exponential band value. -- `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 -using QuanTAlib; - -// Initialize with slowing decay (long memory) -var channel = new Apchannel(alpha: 0.1); - -// Update Loop -foreach (var bar in bars) -{ - var mid = channel.Update(bar); - - // Use valid results - if (channel.IsHot) - { - Console.WriteLine($"{bar.Time}: Upper={channel.UpperBand:F2} Lower={channel.LowerBand:F2}"); - } -} -``` +- **Wilder, J.W.** *New Concepts in Technical Trading Systems*. Trend Research, 1978. (EMA smoothing foundations) +- **Donchian, R.** "Trend Following Methods in Commodity Price Analysis." *Commodity Research Bureau*, 1960. (Fixed-window channel predecessor) +- **Haykin, S.** *Adaptive Filter Theory*. Prentice Hall, 2002. (Leaky integrator / exponential smoothing theory) diff --git a/lib/channels/apz/apz.md b/lib/channels/apz/apz.md index 600c2673..33b00ea7 100644 --- a/lib/channels/apz/apz.md +++ b/lib/channels/apz/apz.md @@ -1,133 +1,119 @@ # APZ: Adaptive Price Zone -> "Volatility is not noise; it is the breathing rhythm of the market." - -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. +APZ constructs a volatility-adaptive envelope using double-smoothed exponential moving averages with an aggressive smoothing factor derived from $\sqrt{\text{period}}$, making it significantly faster than standard EMA-based channels. The center line is a double-EMA of price; the band width is a double-EMA of the high-low range, scaled by a multiplier. Designed specifically for mean-reversion trading in non-trending markets, APZ identifies overbought/oversold extremes where price is likely to reverse rather than continue. A closing price outside the zone signals an immediate overshoot, not a breakout. ## Historical Context -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. +Lee Leibfarth created the Adaptive Price Zone and published it in *Technical Analysis of Stocks & Commodities* (September 2006) under the article "Trading With An Adaptive Price Zone." Leibfarth recognized that most indicators fail in choppy, range-bound markets: trend followers get whipsawed, and oscillators saturate at extremes. APZ fills this gap by adapting its bandwidth dynamically to statistical noise, allowing traders to fade extremes in consolidation phases. + +The critical design decision is the square-root smoothing factor: $\alpha = 2 / (\sqrt{P} + 1)$. For a period of 20, $\sqrt{20} \approx 4.47$, producing $\alpha \approx 0.365$, which behaves like an EMA of period $\sim$3.5. This makes APZ extremely responsive compared to a standard 20-period EMA ($\alpha = 0.095$). The double-smoothing (EMA of EMA) adds some lag back, but the net result is still far faster than conventional approaches. The compound warmup compensator $e = \beta^{2t}$ (where $\beta = 1 - \alpha$) ensures accurate values from bar 1 without the typical EMA initialization bias. ## Architecture & Physics -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. +### 1. Aggressive Smoothing Factor -### Calculation Steps +$$\alpha = \frac{2}{\sqrt{P} + 1}, \quad \beta = 1 - \alpha$$ -1. **Smoothing Factor**: - $$\alpha = \frac{2}{\sqrt{Period} + 1}$$ +### 2. Center Line (Double-Smoothed EMA of Price) -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)$$ +First EMA: -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)$$ +$$\text{EMA1}_t = \alpha \cdot x_t + \beta \cdot \text{EMA1}_{t-1}$$ -4. **Bands**: - $$BandWidth_t = SmoothRange_t \times Factor$$ - $$Upper_t = Center_t + BandWidth_t$$ - $$Lower_t = Center_t - BandWidth_t$$ +Second EMA (double-smoothing): - Where $Period$ determines responsiveness and $Factor$ scales the zone width. +$$\text{Center}_t = \alpha \cdot \text{EMA1}_t + \beta \cdot \text{Center}_{t-1}$$ -## Performance Profile +### 3. Adaptive Range (Double-Smoothed EMA of High-Low) -The implementation utilizes compounded warmup compensation to stabilize the nested EMAs derived from bar 1 (zero-lag start). +$$R_t = H_t - L_t$$ -### Operation Count - Single value +$$\text{EMA1R}_t = \alpha \cdot R_t + \beta \cdot \text{EMA1R}_{t-1}$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 6 | 1 | 6 | -| MUL | 10 | 3 | 30 | -| FMA | 4 | 4 | 16 | -| SQRT | 1 | 15 | 15 | -| **Total** | **21** | — | **~67 cycles** | +$$\text{SmoothRange}_t = \alpha \cdot \text{EMA1R}_t + \beta \cdot \text{SmoothRange}_{t-1}$$ -*Note: SQRT is computed once at initialization. The runtime complexity is dominated by the 4 FMA instructions for the double smoothing.* +### 4. Band Construction -### Operation Count - Batch processing +$$\text{Width}_t = F \cdot \text{SmoothRange}_t$$ -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| Double Smoothing | 4N | N/A | 1× | +$$\text{Upper}_t = \text{Center}_t + \text{Width}_t$$ -*Note: Due to the nested recursive nature ($t$ depends on $t-1$), vectorization is limited to parallel processing of Price and Range chains.* +$$\text{Lower}_t = \text{Center}_t - \text{Width}_t$$ -## Validation +### 5. Warmup Compensation -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | N/A | Not implemented | -| **Skender** | N/A | Not implemented | -| **Internal** | ✅ | Validated against Leibfarth's formula | -| **TradingView** | ✅ | Matches standard scripts | +To eliminate EMA initialization bias, a compound compensator tracks the accumulated decay: -## Usage & Pitfalls +$$e_t = \beta^2 \cdot e_{t-1}, \quad e_0 = 1$$ -- **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. +During warmup ($e > 10^{-10}$): -## API +$$\text{Center}_t^* = \frac{\text{Center}_t}{1 - e_t}, \quad \text{SmoothRange}_t^* = \frac{\text{SmoothRange}_t}{1 - e_t}$$ -```mermaid -classDiagram - class Apz { - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +Update(TBar bar) TValue - +Update(TBarSeries source) tuple - +Batch(...) void - } +### 6. Complexity + +$O(1)$ per bar: 4 EMA updates (2 for price, 2 for range), plus band arithmetic. The square root is computed once at initialization. No buffers required. + +## Mathematical Foundation + +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Input period ($P$); $\sqrt{P}$ used for smoothing | 20 | $> 0$ | +| `multiplier` | Band width factor ($F$) | 2.0 | $> 0$ | +| `source` | Input price series | close | | + +### Effective EMA Period + +The actual smoothing period experienced by the double-EMA is much shorter than the input period: + +$$P_{\text{effective}} = \sqrt{P} \approx \frac{2}{\alpha} - 1$$ + +For $P = 20$: $P_{\text{eff}} \approx 4.47$. For $P = 100$: $P_{\text{eff}} \approx 10$. + +### Pseudo-code + +``` +function APZ(source, high, low, period, multiplier): + validate: period > 0, multiplier > 0 + + alpha = 2 / (√period + 1) + beta = 1 - alpha + + // Double-smoothed EMA of price + ema1_price = alpha * source + beta * ema1_price + center = alpha * ema1_price + beta * center + + // Double-smoothed EMA of range + range = high - low + ema1_range = alpha * range + beta * ema1_range + smooth_range = alpha * ema1_range + beta * smooth_range + + // Warmup compensator + e *= beta² + if e > 1e-10: + compensator = 1 / (1 - e) + center *= compensator + smooth_range *= compensator + + // Bands + width = multiplier * smooth_range + upper = center + width + lower = center - width + + return [center, upper, lower] ``` -### Class: `Apz` +### Output Interpretation -| 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). | +| Output | Description | +|--------|-------------| +| `center` | Double-smoothed EMA of price (fast center line) | +| `upper` | Center + scaled adaptive range (overbought zone) | +| `lower` | Center - scaled adaptive range (oversold zone) | -### Properties +## Resources -- `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 -using QuanTAlib; - -// Initialize -var indicator = new Apz(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in bars) -{ - var center = indicator.Update(bar); - - // Mean reversion logic - if (indicator.IsHot) - { - if (bar.Close > indicator.Upper.Value) - Console.WriteLine("Overshoot: Sell Signal"); - if (bar.Close < indicator.Lower.Value) - Console.WriteLine("Undershoot: Buy Signal"); - } -} -``` +- **Leibfarth, L.** "Trading With An Adaptive Price Zone." *Technical Analysis of Stocks & Commodities*, September 2006. (Original APZ specification) +- **Mulloy, P.** "Smoothing Data with Less Lag." *Technical Analysis of Stocks & Commodities*, February 1994. (Double-smoothed EMA theory) diff --git a/lib/channels/atrbands/atrbands.md b/lib/channels/atrbands/atrbands.md index 7f94f5f8..4cd6b782 100644 --- a/lib/channels/atrbands/atrbands.md +++ b/lib/channels/atrbands/atrbands.md @@ -1,125 +1,98 @@ # ATRBANDS: Average True Range Bands -> "True Range reveals the market's actual footprint, ignoring the gaps that deceive the eye." - -ATR Bands create a volatility-adaptive envelope around a central moving average. Unlike fixed-percentage bands (like Envelopes) or standard deviation bands (like Bollinger), ATR Bands use Wilder's Average True Range to measure volatility. This makes them particularly robust for assets with gaps, pre-market moves, or 24/7 discontinuities, as the True Range accounts for the "hidden" volatility between bars. +ATR Bands create a volatility-adaptive envelope by projecting Wilder's Average True Range above and below a central Simple Moving Average. Unlike fixed-percentage envelopes or standard-deviation bands, ATR Bands use True Range to measure volatility, making them robust for assets with gaps, pre-market moves, and 24/7 trading where the "hidden" volatility between bars is significant. The True Range captures the maximum of intra-bar range, gap-up distance, and gap-down distance, ensuring that overnight gaps contribute fully to band width even when the current bar's open-to-close range is narrow. ## Historical Context -Developed by futures traders in the 1980s following J. Welles Wilder's introduction of ATR in *New Concepts in Technical Trading Systems* (1978). While Wilder used ATR primarily for trailing stops (Volty Stop) and directional indicators, traders quickly realized that projecting ATR above and below a Trend MA created an excellent breakout/containment channel. It effectively answers the question: "How far can price move away from the average before it is statistically abnormal?" +J. Welles Wilder introduced Average True Range in *New Concepts in Technical Trading Systems* (1978), primarily as a trailing stop mechanism (the "Volatility Stop") and as a component of the Average Directional Index (ADX). Wilder used his own smoothing method, now known as RMA or Wilder's Smoothing, which is equivalent to an EMA with $\alpha = 1/n$. Futures traders in the 1980s quickly realized that projecting ATR above and below a trend-following moving average created a practical channel answering the question: "How far can price move from the average before it is statistically abnormal?" + +ATR Bands differ from Keltner Channels only in the center line: ATR Bands use SMA, Keltner uses EMA. Some implementations use SMA-based ATR averaging instead of Wilder's smoothing. The QuanTAlib implementation uses Wilder's smoothing (RMA) for ATR with a warmup compensator for accurate early values, and SMA for the center line. ## Architecture & Physics -The system consists of a central tendency (SMA) and a dispersion measure (ATR). The physics are those of an elastic boundary: the envelope expands linearly with volatility, creating "breathing room" for price during high-stress periods. +### 1. True Range -### Calculation Steps +True Range captures the maximum extent of price movement, including gaps: -1. **True Range**: - $$TR_t = \max(\text{High}_t - \text{Low}_t, |\text{High}_t - \text{Close}_{t-1}|, |\text{Low}_t - \text{Close}_{t-1}|)$$ +$$TR_t = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|)$$ -2. **Average True Range (Wilder's Smoothing)**: - $$ATR_t = \frac{ATR_{t-1} \times (n-1) + TR_t}{n}$$ +### 2. Average True Range (Wilder's Smoothing / RMA) -3. **Bands**: - $$Middle_t = SMA(\text{Source}, n)$$ - $$Upper_t = Middle_t + (ATR_t \times Multiplier)$$ - $$Lower_t = Middle_t - (ATR_t \times Multiplier)$$ +$$ATR_t = \frac{ATR_{t-1} \times (n - 1) + TR_t}{n}$$ - Where $n$ = period (default 20), $Multiplier$ = scale factor (default 2.0). +This is equivalent to EMA with $\alpha = 1/n$. The warmup compensator corrects for initialization bias: -## Performance Profile +$$e_t = (1 - \alpha) \cdot e_{t-1}, \quad ATR_t^* = \frac{ATR_t}{1 - e_t} \text{ while } e > \epsilon$$ -The implementation uses O(1) iterative updates. The SMA uses a circular buffer for running sums, while the ATR uses a recursive IIR filter (Wilder's smoothing). +### 3. Center Line (SMA) -### Operation Count - Single value +$$\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} x_{t-i}$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 6 | 1 | 6 | -| MUL | 4 | 3 | 12 | -| DIV | 1 | 15 | 15 | -| CMP/ABS | 3 | 1 | 3 | -| FMA | 1 | 4 | 4 | -| **Total** | **15** | — | **~40 cycles** | +### 4. Band Construction -### Operation Count - Batch processing +$$\text{Upper}_t = \text{Middle}_t + k \cdot ATR_t$$ -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| SMA Update | N | N | 1× | -| ATR Update | N | N | 1× | -| Band Calc | 3N | 3N/VectorSize | ~4-8× | +$$\text{Lower}_t = \text{Middle}_t - k \cdot ATR_t$$ -*Note: The recursive nature of ATR and SMA limits full vectorization, but the final band projection is fully accelerated.* +### 5. Complexity -## Validation +The SMA uses a circular buffer for $O(1)$ running sums. The ATR uses recursive IIR smoothing, also $O(1)$. True Range computation requires retaining the previous close. Total: $O(1)$ per bar with one buffer of size $n$ for the SMA. -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | N/A | Not implemented | -| **Skender** | ✅ | Matches `GetAtr` + SMA logic | -| **Internal** | ✅ | Streaming/Batch/Span match exactly | +## Mathematical Foundation -## Usage & Pitfalls +### Parameters -- **Stop Placement**: ATR Bands are widely used for placing stop-losses. A common technique is placing a stop just outside the 2.0-3.0 ATR band. -- **Keltner Channels Comparison**: Keltner Channels typically use EMA for the center line. ATR Bands use SMA. The bandwidth logic is identical. -- **Lag**: Because it uses SMA, the center line lags significantly compared to an EMA-based channel. -- **Warmup**: ATR requires significant warmup (typically >50 bars) to stabilize fully due to the infinite memory of the Wilder smoothing function. +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback for SMA and ATR smoothing ($n$) | 20 | $> 0$ | +| `multiplier` | Band width scale factor ($k$) | 2.0 | $> 0$ | +| `source` | Input series for center line | close | | -## API +### True Range Components -```mermaid -classDiagram - class AtrBands { - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +Update(TBar bar) TValue - +Update(TBarSeries source) tuple - +Batch(...) void - } +| Component | Formula | Captures | +|-----------|---------|----------| +| Intra-bar | $H_t - L_t$ | Current bar's range | +| Gap-up | $\|H_t - C_{t-1}\|$ | Upward gap distance | +| Gap-down | $\|L_t - C_{t-1}\|$ | Downward gap distance | + +### Pseudo-code + +``` +function ATRBANDS(source, high, low, close, period, multiplier): + validate: period > 0, multiplier > 0 + + // True Range + tr = max(high - low, |high - prev_close|, |low - prev_close|) + prev_close = close + + // ATR via Wilder's smoothing (RMA) + alpha = 1 / period + raw_rma = (raw_rma * (period - 1) + tr) / period + e *= (1 - alpha) + atr = e > ε ? raw_rma / (1 - e) : raw_rma + + // Center line (SMA via circular buffer) + middle = SMA(source, period) + + // Bands + width = atr * multiplier + upper = middle + width + lower = middle - width + + return [middle, upper, lower] ``` -### Class: `AtrBands` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback for SMA and ATR. | -| `multiplier` | `double` | `2.0` | `>0` | Band width factor. | -| `source` | `TBarSeries` | — | `any` | Initial input source (optional). | +| Output | Description | +|--------|-------------| +| `middle` | SMA of source (center line) | +| `upper` | Middle + scaled ATR (volatility-adjusted resistance) | +| `lower` | Middle - scaled ATR (volatility-adjusted support) | -### Properties +## Resources -- `Last` (`TValue`): The current middle band value (SMA). -- `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 -using QuanTAlib; - -// Initialize -var indicator = new AtrBands(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in bars) -{ - var mid = indicator.Update(bar); - - // Use valid results - if (indicator.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={mid.Value:F2} Upper={indicator.Upper.Value:F2}"); - } -} -``` +- **Wilder, J.W.** *New Concepts in Technical Trading Systems*. Trend Research, 1978. (Original ATR and Wilder's Smoothing) +- **Keltner, C.** "How to Use the 10-Day Moving Average Rule." *Commodities*, 1960. (EMA-centered ATR channel variant) +- **Bollinger, J.** *Bollinger on Bollinger Bands*. McGraw-Hill, 2001. (Standard deviation band alternative for comparison) diff --git a/lib/channels/bbands/bbands.md b/lib/channels/bbands/bbands.md index 543818a1..e0091693 100644 --- a/lib/channels/bbands/bbands.md +++ b/lib/channels/bbands/bbands.md @@ -1,167 +1,110 @@ # BBANDS: Bollinger Bands -> "Two standard deviations contain 95% of price action—until they don't." - -Bollinger Bands® are volatility-based envelopes that surround a central moving average. The bands adapt to changing market conditions by expanding during periods of high volatility and contracting during periods of low volatility, solving the problem of fixed-width envelopes by using standard deviation as a dynamic measure of width. +Bollinger Bands construct a volatility-adaptive envelope around a Simple Moving Average using population standard deviation as the width measure. The bands expand during high-volatility periods and contract during consolidation, dynamically adapting to changing market conditions. Under Gaussian assumptions, $\pm 2\sigma$ contains approximately 95.4% of price action, but financial returns exhibit fat tails and volatility clustering, so the bands function more as a volatility-normalized reference frame than a strict probability envelope. The derived metrics %B (price position as a fraction of band width) and BandWidth (normalized band spread) extend the raw bands into a complete analytical toolkit. ## Historical Context -**John Bollinger** developed Bollinger Bands in the early 1980s while working as a market technician. He registered "Bollinger Bands" as a trademark in 1996. The indicator emerged from Bollinger's observation that volatility is not static—a simple percentage envelope fails to account for the market's changing breath. +John Bollinger developed Bollinger Bands in the early 1980s while working as a market technician. He registered the name as a trademark in 1996 and published *Bollinger on Bollinger Bands* (McGraw-Hill, 2001). The indicator emerged from Bollinger's observation that fixed-percentage envelopes fail to account for changing volatility: a 5% envelope that works during quiet markets becomes useless during volatile phases, and vice versa. -Bollinger drew inspiration from statistical probability theory. Under a normal distribution, approximately 68% of data falls within ±1σ, 95% within ±2σ, and 99.7% within ±3σ. By setting the default multiplier to 2.0, Bollinger created bands that theoretically contain ~95% of price action. However, financial returns are famously non-Gaussian (fat tails, skewness), so the bands serve more as a volatility-normalized reference than a probability envelope. +Bollinger drew on statistical probability theory. Under the normal distribution, approximately 68% of observations fall within $\pm 1\sigma$, 95% within $\pm 2\sigma$, and 99.7% within $\pm 3\sigma$. By defaulting the multiplier to 2.0, he created bands targeting the 95% containment level. Chebyshev's inequality guarantees at least 75% containment at $\pm 2\sigma$ regardless of distribution shape. In practice, with fat-tailed market returns (typical kurtosis 4-6), expect 92-95% containment rather than 95.4%. -The indicator became one of the most widely adopted technical analysis tools, featured in virtually every charting platform. Bollinger authored *Bollinger on Bollinger Bands* (2001), detailing trading methodologies including "the squeeze" (low volatility preceding breakouts) and "%B" (price position within the bands as an oscillator). +The "Squeeze" pattern (BandWidth at multi-period lows) became one of the most recognized technical analysis signals, predating and influencing the TTM Squeeze indicator. Walking the bands (price hugging the upper or lower band during trends) is a momentum signal, not a reversal signal. Bollinger Bands became one of the most widely adopted technical analysis tools, available in virtually every charting platform. ## Architecture & Physics -The system relies on the statistical properties of the **Normal Distribution** (Gaussian bell curve): +### 1. Middle Band (SMA) -1. **Central Tendency:** The middle band defines the "center of gravity" for price, typically a Simple Moving Average (SMA). -2. **Dispersion:** The width of the bands is determined by the Population Standard Deviation ($\sigma$), representing the volatility or "energy" in the system. -3. **Probability Event Horizons:** - - $\pm 2\sigma$ theoretically contains ~95.4% of price action (Chebyshev's inequality guarantees at least 75%, normal distribution implies 95%). - - Excursions outside the bands represent statistically significant "anomalies" or extreme momentum. +$$\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} x_{t-i}$$ -### Calculation Steps +### 2. Population Standard Deviation -#### 1. Middle Band (SMA) +Using the computational formula (running sums of $x$ and $x^2$): -$$ -\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} \text{Close}_{t-i} -$$ +$$\sigma_t = \sqrt{\frac{\sum x_i^2}{n} - \left(\frac{\sum x_i}{n}\right)^2}$$ -#### 2. Population Standard Deviation +Note: this is population standard deviation (divide by $n$), not sample standard deviation (divide by $n-1$). Bollinger specified population $\sigma$, and most reference implementations (TA-Lib, TradingView) use this convention. -$$ -\sigma_t = \sqrt{\frac{1}{n} \sum_{i=0}^{n-1} (\text{Close}_{t-i} - \text{Middle}_t)^2} -$$ +### 3. Band Construction -#### 3. Band Construction +$$\text{Upper}_t = \text{Middle}_t + k \cdot \sigma_t$$ -$$ -\text{Upper}_t = \text{Middle}_t + (k \times \sigma_t) -$$ +$$\text{Lower}_t = \text{Middle}_t - k \cdot \sigma_t$$ -$$ -\text{Lower}_t = \text{Middle}_t - (k \times \sigma_t) -$$ +### 4. Derived Metrics -Where $n$ = period (default: 20), $k$ = multiplier (default: 2.0). +**BandWidth** (normalized volatility measure): -#### 4. Derived Metrics +$$\text{BandWidth}_t = \frac{\text{Upper}_t - \text{Lower}_t}{\text{Middle}_t}$$ -$$ -\text{BandWidth}_t = \frac{\text{Upper}_t - \text{Lower}_t}{\text{Middle}_t} -$$ +**%B** (price position oscillator, 0-1 range when inside bands): -$$ -\text{PercentB}_t = \frac{\text{Close}_t - \text{Lower}_t}{\text{Upper}_t - \text{Lower}_t} -$$ +$$\%B_t = \frac{x_t - \text{Lower}_t}{\text{Upper}_t - \text{Lower}_t}$$ -## Performance Profile +### 5. Complexity -The implementation utilizes **O(1)** circular buffer algorithms for both the SMA and Standard Deviation components, ensuring performance remains constant regardless of the lookback period. +The circular buffer maintains running sums of $x$ and $x^2$, enabling $O(1)$ computation of both mean and variance per bar. The square root for $\sigma$ is the most expensive operation. -### Operation Count - Single value +## Mathematical Foundation -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 5 | 1 | 5 | -| MUL | 3 | 3 | 9 | -| DIV | 2 | 15 | 30 | -| SQRT | 1 | 15 | 15 | -| **Total** | **11** | — | **~59 cycles** | +### Parameters -### Operation Count - Batch processing +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback for SMA and standard deviation ($n$) | 20 | $> 0$ | +| `multiplier` | Number of standard deviations ($k$) | 2.0 | $> 0$ | +| `source` | Input price series | close | | -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| SMA computation | N | N | 1× | -| Variance/StdDev | 2N | 2N/4 | ~4× | -| Band construction | 3N | 3N/8 | ~8× | +### Containment Guarantees -## Validation +| Multiplier ($k$) | Normal Distribution | Chebyshev (any dist.) | +|:-:|:-:|:-:| +| 1.0 | 68.3% | 0% (trivial bound) | +| 2.0 | 95.4% | 75.0% | +| 3.0 | 99.7% | 88.9% | -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | ✅ | Matches `TA_BBANDS` exactly | -| **Skender** | ✅ | Matches `GetBollingerBands` | -| **Pandas-TA** | ✅ | Matches `ta.bbands` | -| **Spreadsheet** | ✅ | Manual Excel validation | +### Pseudo-code -*Note: Differences in Standard Deviation types (Sample vs. Population) are the most common cause of discrepancies across libraries. QuanTAlib uses **Population** Standard Deviation, consistent with John Bollinger's specification.* +``` +function BBANDS(source, period, multiplier): + validate: period > 0, multiplier > 0 -## Usage & Pitfalls + // Circular buffer maintains running sums + sum += source; sumSq += source² + oldest = buffer[head] + if oldest exists: sum -= oldest; sumSq -= oldest² -- **The Squeeze**: Narrow bands (low BandWidth) often precede explosive moves. Watch for BandWidth at multi-month lows. -- **Walking the Bands**: In strong trends, price can "walk" along the upper or lower band for extended periods. Touching the band is not inherently a reversal signal. -- **%B Oscillator**: Use PercentB as a normalized oscillator: >1.0 = above upper band, <0.0 = below lower band, 0.5 = at middle. -- **Standard Deviation Type**: Ensure your implementation matches your expected behavior—Population σ (divide by n) vs Sample σ (divide by n-1) produces different band widths. -- **Warmup Period**: The indicator requires `period` bars before producing valid results. During warmup, bands may appear artificially narrow. -- **Non-Normal Returns**: Markets exhibit fat tails; expect more than 5% of price action outside ±2σ bands in practice. + // SMA (middle band) + middle = sum / count -## API + // Population standard deviation + variance = max(0, sumSq/count - middle²) + sigma = √variance + dev = multiplier * sigma -```mermaid -classDiagram - class Bbands { - +Name : string - +WarmupPeriod : int - +Middle : TValue - +Upper : TValue - +Lower : TValue - +Width : TValue - +PercentB : TValue - +IsHot : bool - +Update(TValue input) TValue - +Update(TSeries source) TSeries - } + // Bands + upper = middle + dev + lower = middle - dev + + // Derived metrics + bandwidth = (upper - lower) / middle + percentB = (source - lower) / (upper - lower) + + return [middle, upper, lower, bandwidth, percentB] ``` -### Class: `Bbands` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>0` | Lookback period for SMA and StdDev. | -| `multiplier` | `double` | `2.0` | `>0` | Number of standard deviations for band width. | -| `source` | `TSeries` | — | `any` | Initial input source (optional). | +| Output | Range | Meaning | +|--------|-------|---------| +| `middle` | price-scale | SMA center line | +| `upper` | price-scale | Middle + $k\sigma$ | +| `lower` | price-scale | Middle - $k\sigma$ | +| `bandwidth` | $[0, \infty)$ | Normalized volatility; low values signal "squeeze" | +| `percentB` | typically $[0, 1]$ | $> 1$: above upper band; $< 0$: below lower band | -### Properties +## Resources -- `Middle` (`TValue`): The Simple Moving Average (Mean). -- `Upper` (`TValue`): The Upper Bollinger Band. -- `Lower` (`TValue`): The Lower Bollinger Band. -- `Width` (`TValue`): Normalized BandWidth: $(Upper - Lower) / Middle$. -- `PercentB` (`TValue`): %B Indicator: $(Price - Lower) / (Upper - Lower)$. -- `IsHot` (`bool`): Returns `true` after `period` bars. - -### Methods - -- `Update(TValue input)`: Updates the indicator with a new price point and returns the Middle band. -- `Update(TSeries source)`: Batch processes a series. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize with standard settings (20, 2.0) -var bbands = new Bbands(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in bars) -{ - var result = bbands.Update(bar.Close); - - if (bbands.IsHot) - { - Console.WriteLine($"{bar.Time}: Upper={bbands.Upper.Value:F2} Mid={result.Value:F2} Lower={bbands.Lower.Value:F2}"); - Console.WriteLine($" %B={bbands.PercentB.Value:F2} Width={bbands.Width.Value:F4}"); - } -} -``` - -## References - -- Bollinger, J. (2001). *Bollinger on Bollinger Bands*. McGraw-Hill. -- Bollinger, J. (1992). "Using Bollinger Bands." *Technical Analysis of Stocks & Commodities*. +- **Bollinger, J.** *Bollinger on Bollinger Bands*. McGraw-Hill, 2001. (Definitive reference) +- **Bollinger, J.** "Using Bollinger Bands." *Technical Analysis of Stocks & Commodities*, 1992. +- **Chebyshev, P.L.** "Des valeurs moyennes." *Journal de Mathématiques Pures et Appliquées*, 1867. (Distribution-free containment bound) +- **TA-Lib** `TA_BBANDS` function. (Population standard deviation reference implementation) diff --git a/lib/channels/dchannel/dchannel.md b/lib/channels/dchannel/dchannel.md index 921ce9a6..02d3972c 100644 --- a/lib/channels/dchannel/dchannel.md +++ b/lib/channels/dchannel/dchannel.md @@ -1,177 +1,86 @@ # DCHANNEL: Donchian Channels -> "The Turtles made millions with a simple rule: buy the 20-day high, sell the 20-day low." - -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. +Donchian Channels track the highest high and lowest low over a fixed lookback period, defining the absolute price boundaries within which an asset has traded. Unlike volatility-based bands that compute statistical dispersion, Donchian Channels represent actual historical extremes — the literal "price box." The implementation uses monotonic deques for $O(1)$ amortized sliding-window max/min, ensuring that computing a 500-period channel costs no more than a 20-period one. The midpoint of the upper and lower bands serves as a simple trend bias indicator. ## Historical Context -**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. +Richard Donchian developed this channel in the 1960s while managing one of the first publicly held commodity funds. Known as the "father of trend following," Donchian pioneered systematic trading in an era dominated by discretionary methods. His "4-week rule" (buy on a 20-day high, sell on a 20-day low) became one of the earliest documented mechanical trading systems. -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. - -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. +The indicator achieved 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 earned over $100 million. Curtis Faith's *Way of the Turtle* (2007) revealed the core system: enter on 20-day breakouts, exit on 10-day counter-breakouts. The simplicity is the feature: no predictions, no fitting, no optimization — just price breaking through defined boundaries. ## Architecture & Physics -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?" +### 1. Upper Band (Sliding Window Maximum) -### Monotonic Deque Algorithm +$$\text{Upper}_t = \max_{i=0}^{n-1}(H_{t-i})$$ -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. +### 2. Lower Band (Sliding Window Minimum) -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. +$$\text{Lower}_t = \min_{i=0}^{n-1}(L_{t-i})$$ -### Calculation Steps +### 3. Middle Band -#### 1. Upper Band (Highest High) +$$\text{Middle}_t = \frac{\text{Upper}_t + \text{Lower}_t}{2}$$ -$$ -\text{Upper}_t = \max_{i=0}^{n-1}(H_{t-i}) -$$ +### 4. Monotonic Deque Algorithm -#### 2. Lower Band (Lowest Low) +The naive approach scans the entire window for each bar: $O(n)$ per update. The monotonic deque (also called a sliding window max/min queue) maintains candidates in sorted order: -$$ -\text{Lower}_t = \min_{i=0}^{n-1}(L_{t-i}) -$$ +**For the max deque (upper band):** -#### 3. Middle Band +1. Remove indices outside the window from the front +2. Remove values $\leq$ current High from the back (they can never be the maximum again) +3. Push current index to the back +4. The front element is always the maximum -$$ -\text{Middle}_t = \frac{\text{Upper}_t + \text{Lower}_t}{2} -$$ +Each element enters exactly once and exits at most once, yielding $O(1)$ amortized per bar over any sequence of $N$ updates. -Where $n$ = period (default: 20). +### 5. Stale Extremes -### Deque Maintenance +The bands stay flat until either 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. This piecewise-constant behavior is the defining characteristic: unlike smoothed envelopes, Donchian Channels are discontinuous, stepping only at regime transitions. -For each new bar: +## Mathematical Foundation -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 +### Parameters -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 +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback window for high/low extremes ($n$) | 20 | $> 0$ | -**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. +### Pseudo-code -## Performance Profile +``` +function DCHANNEL(high, low, period): + validate: period > 0 -The implementation is highly optimized using the Monotonic Deque pattern, solving the performance bottleneck common in "sliding window max/min" problems. + // Monotonic deque for max (upper band) + while max_deque.front is outside window: pop front + while max_deque.back value ≤ high: pop back + push high to max_deque back + upper = max_deque.front value -### Operation Count - Single value + // Monotonic deque for min (lower band) + while min_deque.front is outside window: pop front + while min_deque.back value ≥ low: pop back + push low to min_deque back + lower = min_deque.front value -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| 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** | + middle = (upper + lower) / 2 -**Complexity:** O(1) amortized per bar. - -### Operation Count - Batch processing - -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| Deque maintenance | ~6N | N/A | 1× | -| Middle calculation | N | N/8 | 8× | - -*Note: Sliding window max/min is inherently sequential, limiting SIMD benefit. The deque algorithm is already highly efficient.* - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **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 | - -## Usage & Pitfalls - -- **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. - -## API - -```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 - } + return [middle, upper, lower] ``` -### Class: `Dchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>0` | Lookback window for finding highest high and lowest low. | -| `source` | `TBarSeries` | — | `any` | Initial input (optional). | +| Output | Description | +|--------|-------------| +| `upper` | Highest high over the lookback (resistance) | +| `lower` | Lowest low over the lookback (support) | +| `middle` | Midpoint of channel (trend bias) | -### Properties +## Resources -- `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. -- Covel, M. (2007). *The Complete TurtleTrader*. HarperBusiness. +- **Donchian, R.** "High Finance in Copper." *Financial Analysts Journal*, 16(6), 1960. (Original channel concept) +- **Faith, C.** *Way of the Turtle: The Secret Methods that Turned Ordinary People into Legendary Traders*. McGraw-Hill, 2007. (Turtle Trading system) +- **Covel, M.** *The Complete TurtleTrader*. HarperBusiness, 2007. +- **Cormen, T.H. et al.** *Introduction to Algorithms*. MIT Press, 2009. (Monotonic deque / sliding window algorithms) diff --git a/lib/channels/decaychannel/decaychannel.md b/lib/channels/decaychannel/decaychannel.md index 69c81054..5e1dbb07 100644 --- a/lib/channels/decaychannel/decaychannel.md +++ b/lib/channels/decaychannel/decaychannel.md @@ -1,141 +1,114 @@ # DECAYCHANNEL: Decay Min-Max Channel -> "Price extremes have a half-life—the market forgets yesterday's drama at an exponential rate." - -Decay Channel is a price envelope that combines the absolute boundaries of Donchian Channels with an exponential decay mechanism. While Donchian Channels hold their width until an extreme exits the lookback window, Decay Channels allow the bands to effectively "forget" old extremes over time, converging towards the center. This creates a dynamic envelope that expands instantly on new volatility but contracts smoothly during consolidation, modeling the "half-life" of price memory. +Decay Channel combines the absolute price boundaries of Donchian Channels with exponential decay toward the midpoint, creating an envelope that expands instantly on new volatility but contracts smoothly during consolidation. While Donchian Channels hold their width until an extreme exits the lookback window, Decay Channel allows the bands to "forget" old extremes over time using a half-life model. The period parameter serves as the half-life: after that many bars without a new extreme, the band has decayed 50% of the distance back toward center. The decayed values are always clamped within Donchian bounds, ensuring they never extrapolate beyond actual price history. ## Historical Context -The Decay Channel is a QuanTAlib innovation that applies principles from physics—specifically **radioactive decay** and **Newton's Law of Cooling**—to price channel construction. The concept emerged from the observation that standard Donchian Channels exhibit a discontinuous "cliff edge" behavior: bands remain static until an old extreme exits the lookback window, then jump abruptly. +The Decay Channel is a QuanTAlib design that applies principles from physics — specifically radioactive decay and Newton's Law of Cooling — to price channel construction. Standard Donchian Channels exhibit a discontinuous "cliff edge" behavior: bands remain static until an old extreme exits the lookback window, then jump abruptly. This doesn't reflect how markets work: traders naturally give less weight to older price extremes as time passes. -This behavior doesn't reflect how markets actually work. Traders naturally give less weight to older price extremes as time passes. The Decay Channel formalizes this intuition using the exponential decay function, where the `period` parameter serves as the "half-life"—the number of bars after which an extreme's influence is reduced by 50%. - -The mathematical foundation draws from the decay constant λ = ln(2)/T, the same formula used in carbon dating and thermal cooling calculations. This creates bands that behave more like a physical system with memory—instantly responsive to new extremes, but gradually relaxing during consolidation. +The mathematical foundation uses the decay constant $\lambda = \ln(2)/T$, the same formula used in carbon dating and thermal cooling. A signal extreme from $T$ bars ago retains exactly half its influence on band width. This produces asymmetric behavior that matches market reality: breakouts are sudden (bands snap to new extremes), consolidations are gradual (bands decay smoothly). ## Architecture & Physics -The system models price extremes as energetic events that decay over time, similar to **Newton's Law of Cooling** or radioactive decay. +### 1. Decay Constant -1. **Price Extremes:** The outer boundaries are constrained by the actual Highest High and Lowest Low (Donchian Channel) over the `Period`. -2. **Exponential Decay:** When a new extreme is not established, the band decays towards the midpoint. -3. **Radioactive Half-Life:** The decay rate ($\lambda$) is calibrated such that the influence of an extreme reduces by 50% over the specified `Period`. +$$\lambda = \frac{\ln 2}{\text{period}}$$ -### Formula +### 2. Extreme Tracking -The decay constant $\lambda$ is derived from the half-life formula: -$$\lambda = \frac{\ln(2)}{Period}$$ +For each bar, the algorithm tracks how many bars have elapsed since the last new high (or low): -For each bar, if a new raw extreme is not found, the band decays: -$$Age = \text{Bars since last extreme}$$ -$$Factor = e^{-\lambda \times Age}$$ -$$DecayedMax = Midpoint + Factor \times (max_{initial} - Midpoint)$$ +- If $H_t \geq \text{currentMax}$: snap $\text{currentMax} = H_t$, reset $\text{age}_{\max} = 0$ +- Otherwise: increment $\text{age}_{\max}$ -The final upper/lower bands are clamped: -$$Upper = \min(DecayedMax, DonchianUpper)$$ -$$Lower = \max(DecayedMin, DonchianLower)$$ -$$Middle = \frac{Upper + Lower}{2}$$ +Symmetric logic for the minimum. -## Calculation Steps +### 3. Exponential Decay Toward Midpoint -1. **Update Extremes:** Compute the raw Highest High and Lowest Low for the `Period` using efficient Monotonic Deques. -2. **Track Age:** If the current High $\ge$ Raw Max, reset Max Age to 0. Otherwise, increment Age. -3. **Apply Decay:** Calculate the exponential decay factor based on Age. -4. **Constrain:** Ensure the Decayed value does not exceed the Raw Donchian bounds (e.g., Upper band cannot be higher than the highest high). -5. **Compute Midpoint:** Average the constrained Upper and Lower bands. +When no new extreme occurs, the band decays toward the channel midpoint: -## Performance Profile +$$\text{decayRate} = 1 - e^{-\lambda \cdot \text{age}}$$ -The implementation balances the computational cost of transcendental functions (`Math.Exp`) with efficient memory management for the sliding window extremes. +$$\text{midpoint} = \frac{\text{currentMax} + \text{currentMin}}{2}$$ -### Operation Count (Streaming Mode, per Bar) +$$\text{currentMax} \leftarrow \text{currentMax} - \text{decayRate} \cdot (\text{currentMax} - \text{midpoint})$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| CMP (Deque extremes) | 3 | 1 | 3 | -| EXP (Decay factor) | 2 | 15 | 30 | -| MUL | 2 | 3 | 6 | -| ADD/SUB | 2 | 1 | 2 | -| MIN/MAX | 2 | 1 | 2 | -| **Total** | **11** | — | **~43 cycles** | +$$\text{currentMin} \leftarrow \text{currentMin} - \text{decayRate} \cdot (\text{currentMin} - \text{midpoint})$$ -### Complexity Analysis +### 4. Donchian Clamping -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming | O(1) | Amortized via monotonic deque | -| Batch | O(n) | FMA optimization for decay | +The decayed values are constrained to never exceed the raw Donchian extremes: -## Validation +$$\text{Upper} = \min(\text{currentMax},\; \text{DonchianUpper})$$ -| Library | Status | Notes | -| :--- | :---: | :--- | -| **Donchian** | ✅ | Decay bands never exceed Donchian bounds | -| **Mathematical** | ✅ | Value decays exactly 50% towards mean after `Period` bars | -| **QuanTAlib** | ✅ | Original implementation | +$$\text{Lower} = \max(\text{currentMin},\; \text{DonchianLower})$$ -## Usage & Pitfalls +### 5. Complexity -- **Half-Life Interpretation:** The `period` parameter is the half-life, not a lookback window. After `period` bars without a new extreme, the band has decayed 50% towards center. -- **Asymmetric Behavior:** Bands snap instantly to new extremes but decay gradually. This asymmetry is intentional—it models how markets accept new price levels quickly but forget old extremes slowly. -- **Requires High/Low:** The indicator uses bar High/Low for extremes, not close prices. Ensure your data includes these fields. -- **Bar Correction:** Use `isNew=false` when updating the current bar's value, `isNew=true` for new bars. -- **Donchian Constraint:** Decayed bands are always within Donchian bounds—useful for confirmation that bands aren't artificially extended. -- **Consolidation Detection:** Narrow bands (Upper ≈ Lower) indicate extended consolidation where old extremes have fully decayed. +The Donchian scan is $O(n)$ per bar in the reference implementation (loop over the buffer). The decay computation adds 2 exponentials per bar. Total: $O(n)$ per bar. -## API +## Mathematical Foundation -```mermaid -classDiagram - class Decaychannel { - +Decaychannel(int period) - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +TValue Update(TBar bar) - +void Reset() - } +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Half-life in bars and Donchian lookback window | 100 | $> 0$ | + +### Half-Life Property + +After $T$ bars without a new extreme, the band has decayed exactly 50% of the distance from its initial position to the midpoint: + +$$\text{decayRate}(T) = 1 - e^{-\lambda T} = 1 - e^{-\ln 2} = 0.5$$ + +After $2T$ bars: 75% decay. After $3T$ bars: 87.5% decay. + +### Pseudo-code + +``` +function DECAYCHANNEL(high, low, period): + validate: period > 0 + lambda = ln(2) / period + + // Scan buffer for Donchian bounds + periodMax = max(high_buffer over period) + periodMin = min(low_buffer over period) + periodAvg = avg(midpoints over period) + + // Snap or age + if high ≥ currentMax: + currentMax = high; ageMax = 0 + else: + ageMax += 1 + + if low ≤ currentMin: + currentMin = low; ageMin = 0 + else: + ageMin += 1 + + // Decay toward midpoint + midpoint = (currentMax + currentMin) / 2 + maxDecay = 1 - exp(-lambda * ageMax) + minDecay = 1 - exp(-lambda * ageMin) + currentMax -= maxDecay * (currentMax - midpoint) + currentMin -= minDecay * (currentMin - midpoint) + + // Clamp to Donchian bounds + currentMax = min(currentMax, periodMax) + currentMin = max(currentMin, periodMin) + + return [currentMax, currentMin] ``` -### Class: `Decaychannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback window for extremes and half-life calculation. | +| Output | Description | +|--------|-------------| +| `upper` | Decayed high (resistance that fades with time) | +| `lower` | Decayed low (support that fades with time) | -### Properties +## Resources -| Name | Type | Description | -|---|---|---| -| `Last` | `TValue` | The Middle Band value. | -| `Upper` | `TValue` | The Decayed Upper Band. | -| `Lower` | `TValue` | The Decayed Lower Band. | -| `IsHot` | `bool` | Returns `true` when the indicator has processed enough bars to cover the `period`. | - -### Methods - -- `Update(TBar bar)`: Updates the indicator with a new bar (High/Low required). -- `Reset()`: Clears all historical data, deques, and decay timers. - -## C# Example - -```csharp -using QuanTAlib; - -// 1. Initialize with a 20-bar half-life -var decay = new Decaychannel(period: 20); - -// 2. Stream data -var bars = GetHistory(); -foreach (var bar in bars) -{ - decay.Update(bar); - - // The Upper band will be lower than a standard 20-period Donchian - // if no new highs have occurred recently. - if (bar.Close > decay.Upper.Value) - { - Console.WriteLine("Breakout over decayed resistance"); - } -} -``` +- **Rutherford, E.** "Radioactive Substances and their Radiations." Cambridge University Press, 1913. (Exponential decay / half-life mathematics) +- **Newton, I.** "Scala Graduum Caloris." *Philosophical Transactions*, 1701. (Newton's Law of Cooling) +- **Donchian, R.** "High Finance in Copper." *Financial Analysts Journal*, 1960. (Donchian Channel predecessor) diff --git a/lib/channels/fcb/fcb.md b/lib/channels/fcb/fcb.md index b5e1019d..8d8d6c8a 100644 --- a/lib/channels/fcb/fcb.md +++ b/lib/channels/fcb/fcb.md @@ -1,136 +1,93 @@ # FCB: Fractal Chaos Bands -> "Fractals are nature's fingerprints—the market reveals its structure through self-similar patterns at every scale." - -Fractal Chaos Bands filter price action to identify significant turning points using Bill Williams' fractal logic. Unlike raw price channels (Donchian), FCB connects the highest high and lowest low of confirmed 3-bar fractals over a lookback period. This results in a "cleaner" channel that ignores transient spikes and focuses on structural support and resistance levels. The indicator effectively flattens out during trends and steps up/down only when new structural pivots are confirmed, making it ideal for support/resistance identification. +Fractal Chaos Bands filter raw price action through Bill Williams' fractal detection logic, tracking the highest confirmed fractal high and lowest confirmed fractal low over a lookback period. Unlike Donchian Channels which use every bar's high and low, FCB uses only structurally significant turning points — bars where the middle element of a 3-bar pattern is a local extremum. The result is a "cleaner" channel that ignores transient spikes and focuses on confirmed support and resistance levels. The bands tend to remain flat during trends and step discretely when new structural pivots form, making them useful for identifying genuine breakouts versus noise. ## Historical Context -Fractal Chaos Bands derive from **Bill Williams'** work on trading psychology and chaos theory, presented in his influential books "Trading Chaos" (1995) and "New Trading Dimensions" (1998). Williams was among the first to apply chaos theory and fractal mathematics to financial markets, drawing inspiration from Benoit Mandelbrot's groundbreaking work on fractal geometry. +Bill Williams introduced fractal analysis to financial markets in *Trading Chaos* (1995) and *New Trading Dimensions* (1998), drawing inspiration from Benoit Mandelbrot's work on fractal geometry and chaos theory. Williams defined a fractal as a 5-bar pattern (later simplified to 3-bar in many implementations) where the central bar represents a local extremum — a point where supply and demand reached temporary equilibrium. -Williams defined a fractal as a simple 5-bar pattern (later simplified to 3-bar in many implementations) where the middle bar represents a local extremum—a point where the market "pauses" before continuing or reversing. These fractals serve as natural support and resistance levels because they represent moments where supply and demand reached temporary equilibrium. - -The Fractal Chaos Bands indicator extends this concept by tracking the highest up-fractal and lowest down-fractal over a lookback period, creating an envelope of "structural" extremes rather than raw price extremes. This filtering eliminates noise from transient spikes while preserving meaningful market structure. +The Fractal Chaos Bands indicator extends Williams' fractal concept by tracking the monotonic extremes of these structural turning points over a lookback window, rather than raw price extremes. This filtering eliminates noise from transient wicks and gap spikes while preserving meaningful market structure. The 3-bar fractal requires one future bar for confirmation, providing inherent stability at the cost of a 1-bar lag. ## Architecture & Physics -The system relies on **Chaos Theory** market geometry: +### 1. Fractal Detection (3-Bar Pattern) -1. **Fractals:** Specific 3-bar price formations where the middle bar represents a local extremum (High > neighbors for Up Fractal; Low < neighbors for Down Fractal). -2. **State Memory:** The bands track the Monotonic Extremes of these fractal values, not raw prices. -3. **Hysteresis:** Since fractals require a future bar for confirmation, the bands have inherent stability and resistance to noise. +**Up Fractal** (local high at $t-1$): -### Formula +$$\text{UpFractal}_t = (H_{t-1} > H_{t-2}) \;\wedge\; (H_{t-1} > H_t)$$ -**3-Bar Fractal Detection:** -$$UpFractal_t = (High_{t-1} > High_{t-2}) \land (High_{t-1} > High_t)$$ -$$DownFractal_t = (Low_{t-1} < Low_{t-2}) \land (Low_{t-1} < Low_t)$$ +**Down Fractal** (local low at $t-1$): -**Bands:** -$$Upper_t = \max(UpFractals \in Period)$$ -$$Lower_t = \min(DownFractal \in Period)$$ -$$Middle_t = \frac{Upper_t + Lower_t}{2}$$ +$$\text{DownFractal}_t = (L_{t-1} < L_{t-2}) \;\wedge\; (L_{t-1} < L_t)$$ -## Calculation Steps +### 2. Fractal Value Tracking -1. **Detect Fractals:** Analyze the most recent 3 bars. If a fractal pattern is confirmed at index $t-1$, record the value. -2. **Update Deques:** Maintain Monotonic Deques of the detected fractal values for the lookback `Period`. - - New Fractal High $\rightarrow$ Push to Max Deque. - - New Fractal Low $\rightarrow$ Push to Min Deque. -3. **Expire Old:** Remove fractal values from the deques that have exited the lookback window. -4. **Derive Bands:** The front of the Max/Min deques represents the highest/lowest fractal value within the period. +A persistent state variable holds the most recent fractal value: -## Performance Profile +- On up fractal confirmation: $\text{hiFractal} = H_{t-1}$ +- On down fractal confirmation: $\text{loFractal} = L_{t-1}$ +- Between fractals: values persist (hold last fractal) -The implementation utilizes **Monotonic Deques** for O(1) amortized complexity, ensuring efficiency even with large lookback periods. +### 3. Band Construction (Sliding Window Max/Min of Fractals) -### Operation Count (Streaming Mode, per Bar) +$$\text{Upper}_t = \max(\text{hiFractal values over period})$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| CMP (Fractal check) | 4 | 1 | 4 | -| CMP (Deque ops) | 3 | 1 | 3 | -| ADD | 1 | 1 | 1 | -| MUL | 1 | 3 | 3 | -| **Total** | **9** | — | **~11 cycles** | +$$\text{Lower}_t = \min(\text{loFractal values over period})$$ -### Complexity Analysis +The monotonic deque operates on fractal values rather than raw prices, using the same $O(1)$ amortized algorithm as Donchian Channels. -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming | O(1) | Amortized via monotonic deque | -| Batch | O(n) | Sequential fractal detection | +### 4. Structural Filtering Property -## Validation +FCB bands are always within or equal to Donchian bounds: -| Library | Status | Notes | -| :--- | :---: | :--- | -| **Donchian** | ✅ | FCB bands always within Donchian bounds | -| **Property** | ✅ | FCB_Upper ≤ Donchian_Upper, FCB_Lower ≥ Donchian_Lower | -| **Williams** | ✅ | Matches Bill Williams' fractal definition | +$$\text{FCB}_{\text{Upper}} \leq \text{Donchian}_{\text{Upper}}$$ -## Usage & Pitfalls +$$\text{FCB}_{\text{Lower}} \geq \text{Donchian}_{\text{Lower}}$$ -- **Confirmation Lag:** Fractals require one future bar for confirmation. The bands lag at least 1 bar behind price—this is intentional and provides stability. -- **Flat Bands:** During strong trends, bands may remain flat for extended periods as no new fractals form in the opposite direction. -- **Structural Breakouts:** A close above FCB Upper is more significant than a close above Donchian Upper because it represents a break of a confirmed structural level. -- **Bar Correction:** Use `isNew=false` when updating the current bar's value, `isNew=true` for new bars. -- **Period Selection:** Larger periods capture more significant fractals but may miss shorter-term pivots. Common settings: 20 (swing trading), 50 (position trading). -- **Noise Filtering:** FCB naturally filters out single-bar spikes that would affect Donchian Channels, but may miss valid breakouts on gap bars. +This is because fractals are a subset of raw highs/lows. A breakout above FCB upper is more significant than above Donchian upper because it breaks a confirmed structural level. -## API +### 5. Complexity -```mermaid -classDiagram - class Fcb { - +Fcb(int period = 20) - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +TValue Update(TBar bar) - +void Reset() - } +Fractal detection is $O(1)$ (3 comparisons). Deque maintenance is $O(1)$ amortized. Total: $O(1)$ amortized per bar. + +## Mathematical Foundation + +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback window for highest/lowest fractal values | 20 | $> 0$ | + +### Pseudo-code + +``` +function FCB(high, low, period): + validate: period > 0 + + // 3-bar fractal detection (confirmed at current bar) + is_fractal_high = high[1] > high[2] AND high[1] > high[0] + is_fractal_low = low[1] < low[2] AND low[1] < low[0] + + // Update persistent fractal values + if is_fractal_high: hi_fractal = high[1] + if is_fractal_low: lo_fractal = low[1] + + // Sliding window max/min via monotonic deques + upper = max(hi_fractal over period) // deque-based + lower = min(lo_fractal over period) // deque-based + + return [upper, lower] ``` -### Class: `Fcb` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>0` | Lookback window for finding highest/lowest fractals. | +| Output | Description | +|--------|-------------| +| `upper` | Highest confirmed fractal high over lookback (structural resistance) | +| `lower` | Lowest confirmed fractal low over lookback (structural support) | -### Properties +## Resources -| Name | Type | Description | -|---|---|---| -| `Last` | `TValue` | The Middle Band value. | -| `Upper` | `TValue` | The Highest Fractal High over the lookback period. | -| `Lower` | `TValue` | The Lowest Fractal Low over the lookback period. | -| `IsHot` | `bool` | Returns `true` after `period + 2` bars (requires warmup + fractal confirmation). | - -### Methods - -- `Update(TBar bar)`: Updates the indicator with a new bar. -- `Reset()`: Clears all historical data and buffers. - -## C# Example - -```csharp -using QuanTAlib; - -// 1. Initialize -var fcb = new Fcb(period: 20); - -// 2. Stream data -var bars = GetHistory(); -foreach (var bar in bars) -{ - fcb.Update(bar); - - // Check for breakouts through structural resistance - if (bar.Close > fcb.Upper.Value) - { - Console.WriteLine($"Fractal Resistance Broken at {fcb.Upper.Value}"); - } -} -``` +- **Williams, B.** *Trading Chaos*. Wiley, 1995. (Original fractal definition for markets) +- **Williams, B.** *New Trading Dimensions*. Wiley, 1998. (Extended fractal analysis) +- **Mandelbrot, B.** *The Fractal Geometry of Nature*. W.H. Freeman, 1982. (Mathematical fractal theory) diff --git a/lib/channels/jbands/jbands.md b/lib/channels/jbands/jbands.md index 462589f7..7e9047eb 100644 --- a/lib/channels/jbands/jbands.md +++ b/lib/channels/jbands/jbands.md @@ -1,142 +1,129 @@ # JBANDS: Jurik Adaptive Envelope Bands -> "Markets have memory, but it fades—Jurik bands capture this with elegant exponential 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. +JBANDS expose the internal adaptive envelope mechanism of the Jurik Moving Average (JMA), producing asymmetric bands that snap instantly to new price extremes and decay exponentially during consolidation. Unlike standard volatility bands (Bollinger, Keltner) which maintain symmetric width around a center line, JBANDS feature "snap-and-decay" hysteresis: expansion is instantaneous (plasticity), contraction is gradual (elasticity). The decay rate is dynamically modulated by a two-stage volatility estimator — a 10-bar SMA feeding a 128-bar trimmed mean — making the bands tight during quiet markets and expansive during trends. The center line is the full JMA: a 2-pole IIR filter with phase control and adaptive alpha. ## Historical Context -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. +Mark Jurik of Jurik Research developed the Jurik Moving Average and its associated bands in the 1990s as a proprietary commercial tool optimized for real-world trading. Unlike academic indicators, JMA was designed with emphasis on reducing lag while maintaining smoothness, using adaptive volatility modulation to adjust bandwidth dynamically. -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. - -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. +The "snap-and-decay" behavior draws from hysteresis in physics — systems that respond differently to increasing versus decreasing inputs. When price moves to a new extreme, the band deforms immediately (plastic response). When price retreats, the band recovers gradually (elastic response). This asymmetry matches empirical market behavior: breakouts are sudden, consolidations are gradual. The two-stage volatility engine (local deviation → SMA → trimmed mean) provides robust reference volatility that resists contamination by outliers. ## Architecture & Physics -The system models price distinctively from standard Gaussian noise: +### 1. Local Deviation -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 maximum absolute distance from price to either band: -### Formula +$$d_{\text{local}} = \max(|x_t - \text{Upper}_{t-1}|,\; |x_t - \text{Lower}_{t-1}|) + \epsilon$$ -The core adaptive logic revolves around the dynamic exponent $d$: -$$Ratio = \frac{|Price - Band|}{Volatility_{ref}}$$ -$$d = \min(Mean(Ratio)^{power}, Limit)$$ +### 2. Two-Stage Volatility Estimation -The decay factor $\alpha$ is modulated by $d$: -$$\alpha = e^{\text{constant} \cdot \sqrt{d}}$$ +**Stage 1**: 10-bar SMA of local deviation: -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}$$ +$$\text{highD}_t = \text{SMA}(d_{\text{local}},\; 10)$$ -## Calculation Steps +**Stage 2**: 128-bar trimmed mean (discard top/bottom 25% when full, or 25% of available count during warmup): -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. +$$d_{\text{ref}} = \text{TrimmedMean}(\text{highD values},\; 128)$$ -## Performance Profile +### 3. Dynamic Exponent -JBANDS is computationally intensive due to its sophisticated volatility engine and use of transcendental functions. +$$\text{ratio} = \frac{|x_t - \text{band}|}{d_{\text{ref}}}$$ -### Operation Count (Streaming Mode, per Bar) +$$d = \min\left(\max\left(\text{ratio}^{P_{\text{exp}}},\; 1\right),\; \log_2\sqrt{\frac{P-1}{2}} + 2\right)$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/MUL | 30 | 2 | 60 | -| EXP | 2 | 15 | 30 | -| POW | 1 | 20 | 20 | -| SQRT | 2 | 15 | 30 | -| Partial Sort | 1 | ~150 | 150 | -| **Total** | **36** | — | **~290 cycles** | +Where $P_{\text{exp}} = \max(\log_2\sqrt{(P-1)/2},\; 0.5)$. -### Complexity Analysis +### 4. Adaptive Decay -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming | O(1) | Amortized, IIR recursive | -| Batch | O(n) | Sequential, limited SIMD | +The adaptation factor uses the precomputed $\text{sqrtDiv}$: -## Validation +$$\alpha_{\text{band}} = \text{sqrtDiv}^{\sqrt{d}}$$ -| Library | Status | Notes | -| :--- | :---: | :--- | -| **Jurik Research** | ✅ | Matches described behavior from Jurik literature | -| **JMA** | ✅ | Middle band validated against standard JMA | -| **Behavioral** | ✅ | Verified snap-on-breakout, decay-on-retrace pattern | +### 5. Snap-and-Decay Band Update -## Usage & Pitfalls +$$\text{Upper}_t = \begin{cases} x_t & \text{if } x_t > \text{Upper}_{t-1} \\ x_t - (x_t - \text{Upper}_{t-1}) \cdot \alpha_{\text{band}} & \text{otherwise} \end{cases}$$ -- **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. +$$\text{Lower}_t = \begin{cases} x_t & \text{if } x_t < \text{Lower}_{t-1} \\ x_t - (x_t - \text{Lower}_{t-1}) \cdot \alpha_{\text{band}} & \text{otherwise} \end{cases}$$ -## API +### 6. JMA Center Line (2-Pole IIR) -```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() - } +$$\alpha_{\text{jma}} = \text{lenDiv}^d$$ + +$$c_0 = (1 - \alpha_{\text{jma}}) \cdot x_t + \alpha_{\text{jma}} \cdot c_{0,t-1}$$ + +$$c_8 = (x_t - c_0)(1 - \text{lenDiv}) + \text{lenDiv} \cdot c_{8,t-1}$$ + +$$a_8 = (\text{phase} \cdot c_8 + c_0 - \text{JMA}_{t-1}) \cdot (1 + \alpha_{\text{jma}}^2 - 2\alpha_{\text{jma}}) + \alpha_{\text{jma}}^2 \cdot a_{8,t-1}$$ + +$$\text{JMA}_t = \text{JMA}_{t-1} + a_8$$ + +### 7. Complexity + +Dominated by the trimmed mean's partial sort: $O(n \log n)$ for the 128-element buffer. All other operations are $O(1)$. In practice, the 128-element sort is fast due to cache-friendly size. + +## Mathematical Foundation + +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Nominal lookback length | 10 | $> 0$ | +| `phase` | Controls JMA overshoot/smoothness | 0 | $[-100, 100]$ | +| `source` | Input price series | close | | + +### Precomputed Constants (from period and phase) + +| Constant | Formula | +|----------|---------| +| $\text{\_PHASE}$ | $\text{phase}/100 + 1.5$, clamped to $[0.5, 2.5]$ | +| $\text{\_LEN0}$ | $(P - 1) / 2$ | +| $\text{\_LOG\_PARAM}$ | $\max(\log_2\sqrt{\text{\_LEN0}} + 2,\; 0)$ | +| $\text{\_SQRT\_PARAM}$ | $\sqrt{\text{\_LEN0}} \cdot \text{\_LOG\_PARAM}$ | +| $\text{lenDiv}$ | $\text{\_LEN0} \cdot 0.9 / (\text{\_LEN0} \cdot 0.9 + 2)$ | +| $\text{sqrtDiv}$ | $\text{\_SQRT\_PARAM} / (\text{\_SQRT\_PARAM} + 1)$ | +| $P_{\text{exp}}$ | $\max(\text{\_LOG\_PARAM} - 2,\; 0.5)$ | + +### Pseudo-code + +``` +function JBANDS(source, period, phase): + precompute constants from period and phase + + // 1. Local deviation + dLocal = max(|source - upper|, |source - lower|) + ε + + // 2. Volatility: 10-bar SMA → 128-bar trimmed mean + highD = SMA(dLocal, 10) + dRef = TrimmedMean(highD_history, 128) + + // 3. Dynamic exponent + ratio = |source - band| / dRef + d = clamp(ratio^P_exp, 1, LOG_PARAM) + + // 4. Snap-and-decay bands + adapt = sqrtDiv^√d + if source > upper: upper = source + else: upper = source - (source - upper) * adapt + (symmetric for lower) + + // 5. JMA center line (2-pole IIR) + alpha = lenDiv^d + ... (c0, c8, a8 recursion) ... + jma = prev_jma + a8 + + return [jma, upper, lower] ``` -### Class: `Jbands` +### Output Interpretation -| 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. | +| Output | Description | +|--------|-------------| +| `middle` | JMA center line (adaptive low-lag smoothed price) | +| `upper` | Adaptive upper envelope (snaps up, decays down) | +| `lower` | Adaptive lower envelope (snaps down, decays up) | -### Properties +## Resources -| 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). | - -### Methods - -- `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"); -} -``` +- **Jurik, M.** Jurik Research. (Proprietary JMA specification and band logic) +- **Mandelbrot, B.** "The Variation of Certain Speculative Prices." *Journal of Business*, 36(4), 1963. (Fat-tailed distributions motivating adaptive approaches) diff --git a/lib/channels/kchannel/kchannel.md b/lib/channels/kchannel/kchannel.md index e36f251e..b6653dee 100644 --- a/lib/channels/kchannel/kchannel.md +++ b/lib/channels/kchannel/kchannel.md @@ -1,175 +1,107 @@ # KCHANNEL: Keltner Channel -> "True Range reveals what close-to-close volatility hides—the overnight gaps." - -Keltner Channels are volatility-based envelopes set above and below an **Exponential Moving Average (EMA)**. Unlike Bollinger Bands, which use Standard Deviation (statistical dispersion), Keltner Channels use **Average True Range (ATR)** (actual price range). This produces bands that are smoother and less prone to "sausage" effects (violent pinching/expanding) than Bollinger Bands, making them particularly effective for trend identification and gap handling in futures and gapping markets. +Keltner Channel constructs a volatility-adaptive envelope by projecting Average True Range above and below an Exponential Moving Average center line. The channel differs from ATR Bands solely in the center line: Keltner uses EMA (faster, more responsive) while ATR Bands use SMA (more stable, more lag). The EMA center combined with ATR width creates a channel that both tracks trend and adapts to volatility, making it one of the most widely used channel indicators for trend-following and mean-reversion strategies. The implementation uses EMA with warmup compensation for accurate early values and Wilder's smoothing (RMA) for ATR. ## Historical Context -**Chester Keltner** introduced the original Keltner Channel in his 1960 book *How To Make Money in Commodities*. His version used a 10-day Simple Moving Average of "typical price" (High+Low+Close)/3 with bands at ±1× the 10-day SMA of the daily range (High-Low). +Chester Keltner introduced the original "Ten-Day Moving Average Trading Rule" in his 1960 book *How to Make Money in Commodities*. Keltner's original channel used a 10-day SMA of the "typical price" (HLC/3) as the center, with the band width based on the 10-day SMA of the daily range (High - Low, without gap adjustment). -**Linda Bradford Raschke** modernized the indicator in the 1980s, replacing the SMA with an EMA for faster response and substituting the simple range with Wilder's Average True Range (ATR). ATR captures gap volatility that the simple High-Low range misses, making the channel more robust for markets that trade overnight or have limit moves. - -The modern Keltner Channel (EMA + ATR) gained popularity through Raschke's work and is now the standard implementation in most charting platforms. The indicator became central to the "TTM Squeeze" setup, which detects when Bollinger Bands nest inside Keltner Channels—a compression pattern often preceding explosive moves. +Linda Bradford Raschke modernized the indicator in the 1990s by replacing the SMA center with an EMA and the simple range with Average True Range. This modern version became widely known as "Keltner Channels" and is the standard implementation in most platforms. The switch to EMA reduces lag in the center line, and the switch to ATR ensures that gaps contribute to band width — critical for futures and stocks that gap regularly. The ATR component uses Wilder's smoothing ($\alpha = 1/n$), providing infinite memory that makes the channel particularly stable after sufficient warmup. ## Architecture & Physics -The system relies on "True Range" volatility, which accounts for gaps between bars: +### 1. Center Line (EMA with Warmup Compensation) -1. **Center of Gravity:** The middle line is an EMA, providing a more responsive center than the SMA used in Bollinger Bands. -2. **Volatility Measure:** The width is determined by ATR (specifically, Wilder's RMA of True Range), which captures the typical "spatial volume" of price movement. -3. **Envelope Logic:** - - Price above the upper channel indicates strong momentum (breakout/trend). - - Price below the lower channel indicates weakness. - - Mean reversion is expected when price moves significantly outside the bands. +$$\alpha = \frac{2}{n + 1}$$ -### Calculation Steps +$$\text{raw}_t = \alpha \cdot x_t + (1 - \alpha) \cdot \text{raw}_{t-1}$$ -#### 1. True Range +$$w_t = \alpha + (1 - \alpha) \cdot w_{t-1}$$ -$$ -TR_t = \max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|) -$$ +$$\text{EMA}_t = \frac{\text{raw}_t}{w_t}$$ -Where $H$ = High, $L$ = Low, $C$ = Close. +The weight accumulator $w$ compensates for EMA initialization bias, producing accurate values from bar 1. -#### 2. Average True Range (Wilder's Smoothing) +### 2. True Range -$$ -ATR_t = \frac{ATR_{t-1} \times (n-1) + TR_t}{n} -$$ +$$TR_t = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|)$$ -#### 3. Middle Band (EMA) +### 3. Average True Range (Wilder's Smoothing / RMA) -$$ -\alpha = \frac{2}{n + 1} -$$ +$$\alpha_{\text{atr}} = \frac{1}{n}$$ -$$ -EMA_t = \alpha \times C_t + (1 - \alpha) \times EMA_{t-1} -$$ +$$\text{raw\_rma}_t = \frac{\text{raw\_rma}_{t-1} \cdot (n-1) + TR_t}{n}$$ -#### 4. Channel Construction +$$e_t = (1 - \alpha_{\text{atr}}) \cdot e_{t-1}$$ -$$ -\text{Upper}_t = EMA_t + (k \times ATR_t) -$$ +$$ATR_t = \frac{\text{raw\_rma}_t}{1 - e_t} \text{ (during warmup)}$$ -$$ -\text{Lower}_t = EMA_t - (k \times ATR_t) -$$ +### 4. Band Construction -Where $n$ = period (default: 20), $k$ = multiplier (default: 2.0). +$$\text{Upper}_t = \text{EMA}_t + k \cdot ATR_t$$ -## Performance Profile +$$\text{Lower}_t = \text{EMA}_t - k \cdot ATR_t$$ -The calculation is highly efficient, relying on recursive O(1) formulas (EMA and RMA). +### 5. Complexity -### Operation Count - Single value +$O(1)$ per bar: one EMA update, one True Range computation, one RMA update, and two band calculations. No buffers required. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 5 | 1 | 5 | -| MUL | 4 | 3 | 12 | -| DIV | 1 | 15 | 15 | -| CMP/ABS | 4 | 1 | 4 | -| FMA | 2 | 4 | 8 | -| **Total** | **16** | — | **~44 cycles** | +## Mathematical Foundation -### Operation Count - Batch processing +### Parameters -| Operation | Scalar Ops | SIMD Ops (AVX/SSE) | Acceleration | -| :--- | :---: | :---: | :---: | -| TR calculation | 3N | 3N/8 | ~8× | -| ATR (IIR) | N | N | 1× | -| EMA (IIR) | N | N | 1× | -| Band construction | 2N | 2N/8 | ~8× | +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback for EMA and ATR smoothing ($n$) | 20 | $> 0$ | +| `multiplier` | ATR scale factor ($k$) | 2.0 | $> 0$ | +| `source` | Input series for EMA center | close | | -*Note: Recursive filters (EMA, ATR) cannot be fully vectorized, but the final band projection and TR calculation benefit from SIMD.* +### Keltner vs. ATR Bands vs. Bollinger -## Validation +| Feature | Keltner | ATR Bands | Bollinger | +|---------|---------|-----------|-----------| +| Center | EMA | SMA | SMA | +| Width | ATR | ATR | StdDev | +| Gap sensitivity | Yes (via TR) | Yes (via TR) | No | +| Distribution assumption | None | None | Gaussian | -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | No direct implementation | -| **Skender** | ✅ | Matches `GetKeltnerChannels` | -| **TradingView** | ✅ | Matches standard "Keltner Channels" indicator | -| **Pandas-TA** | ✅ | Matches `ta.kc` | +### Pseudo-code -*Note: Minor startup divergence may occur due to different warmup seeding strategies.* +``` +function KCHANNEL(source, high, low, close, period, multiplier): + validate: period > 0, multiplier > 0 -## Usage & Pitfalls + // EMA center line (with warmup compensation) + alpha = 2 / (period + 1) + raw_ema = alpha * source + (1-alpha) * raw_ema + weight = alpha + (1-alpha) * weight + ema = raw_ema / weight -- **TTM Squeeze**: When Bollinger Bands move inside Keltner Channels, volatility is compressed. Watch for the squeeze release. -- **Trend Following**: In strong trends, use the middle band (EMA) as trailing support/resistance. Price respecting the EMA confirms trend continuation. -- **EMA vs SMA**: The EMA middle band reacts faster than an SMA-based center. This reduces lag but may produce more whipsaws in choppy markets. -- **ATR Warmup**: Wilder's smoothing has infinite memory—ATR requires significant warmup (~50+ bars) to fully stabilize. Early values may differ from other implementations. -- **Multiplier Selection**: 2.0× ATR is standard for daily charts. Consider 1.5× for intraday or 2.5× for weekly timeframes. -- **Gap Sensitivity**: ATR includes gaps, so a large overnight gap will widen the channel. This is feature, not bug—it reflects actual volatility. + // ATR (Wilder's RMA with warmup) + tr = max(high - low, |high - prev_close|, |low - prev_close|) + prev_close = close + raw_rma = (raw_rma * (period-1) + tr) / period + e *= (1 - 1/period) + atr = e > ε ? raw_rma / (1-e) : raw_rma -## API + // Bands + width = multiplier * atr + upper = ema + width + lower = ema - width -```mermaid -classDiagram - class Kchannel { - +Name : string - +WarmupPeriod : int - +Upper : TValue - +Lower : TValue - +Last : TValue - +IsHot : bool - +Update(TBar bar) TValue - +Update(TBarSeries source) TSeries - } + return [ema, upper, lower] ``` -### Class: `Kchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>0` | Lookback for EMA and ATR. | -| `multiplier` | `double` | `2.0` | `>0` | ATR multiplier for band width. | -| `source` | `TBarSeries` | — | `any` | Initial input (optional). | +| Output | Description | +|--------|-------------| +| `middle` | EMA center line (trend direction) | +| `upper` | EMA + scaled ATR (dynamic resistance) | +| `lower` | EMA - scaled ATR (dynamic support) | -### Properties +## Resources -- `Last` (`TValue`): The Middle Band (EMA) value. -- `Upper` (`TValue`): The Upper Keltner Channel. -- `Lower` (`TValue`): The Lower Keltner Channel. -- `IsHot` (`bool`): Returns `true` after `period` bars. - -### Methods - -- `Update(TBar bar)`: Updates the indicator with OHLC data and returns the Middle band. -- `Update(TBarSeries source)`: Batch processes a bar series. -- `Reset()`: Clears all historical data. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize with standard settings (20, 2.0) -var kchannel = new Kchannel(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in bars) -{ - var result = kchannel.Update(bar); - - if (kchannel.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={result.Value:F2} Upper={kchannel.Upper.Value:F2} Lower={kchannel.Lower.Value:F2}"); - - // Trend Confirmation - if (bar.Close > kchannel.Upper.Value) - Console.WriteLine(" Strong Uptrend (Above Keltner)"); - } -} -``` - -## References - -- Keltner, C. (1960). *How To Make Money in Commodities*. The Keltner Statistical Service. -- Raschke, L.B. & Connors, L.A. (1995). *Street Smarts: High Probability Short-Term Trading Strategies*. -- Wilder, J.W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. +- **Keltner, C.** *How to Make Money in Commodities*. 1960. (Original channel concept) +- **Raschke, L.B. & Connors, L.** *Street Smarts*. M. Gordon Publishing, 1995. (Modern EMA + ATR version) +- **Wilder, J.W.** *New Concepts in Technical Trading Systems*. Trend Research, 1978. (ATR and Wilder's Smoothing) diff --git a/lib/channels/maenv/maenv.md b/lib/channels/maenv/maenv.md index 14e21921..247ca90c 100644 --- a/lib/channels/maenv/maenv.md +++ b/lib/channels/maenv/maenv.md @@ -1,135 +1,88 @@ # MAENV: Moving Average Envelope -> "Sometimes the simplest tools are the most honest—a fixed percentage tells you exactly where you stand." - -Moving Average Envelope is a straightforward channel indicator that creates a fixed percentage-based envelope around a central moving average. Unlike volatility-based bands (which expand/contract), MAENV maintains a constant proportional width relative to the price. This simplicity makes it ideal for identifying mean reversion candidates in stable markets, or for defining "safe" trading zones where price deviation is considered normal. +Moving Average Envelope (MA Envelope) constructs symmetric bands at a fixed percentage distance above and below a moving average center line. Unlike volatility-adaptive channels (Bollinger, Keltner, ATR Bands) where band width varies with market conditions, MA Envelope uses a constant percentage offset, creating bands whose absolute width scales only with price level. The indicator supports configurable moving average types (SMA, EMA, WMA) for the center line, allowing users to trade off between lag, smoothness, and responsiveness. ## Historical Context -Moving Average Envelopes are among the oldest channel indicators in technical analysis, predating even Bollinger Bands. The concept emerged from the simple observation that prices tend to oscillate around their moving average by a relatively consistent percentage during normal market conditions. +Moving Average Envelopes are one of the oldest band-type indicators, predating Bollinger Bands by decades. The concept is straightforward: if a moving average represents "fair value," then price consistently trading a certain percentage above or below that average represents overbought or oversold conditions. The fixed-percentage approach was the standard technique before John Bollinger introduced standard deviation-based adaptive bands in the 1980s. -The indicator gained popularity in the 1970s and 1980s as traders sought objective methods to identify overbought and oversold conditions. Unlike the later volatility-based approaches of Bollinger (1983) and Keltner (1960), MA Envelopes use a fixed percentage, making them conceptually simpler but less adaptive to changing market conditions. - -The trade-off is intentional: a fixed percentage provides a stable reference frame that doesn't expand during volatility spikes—useful for identifying when prices have moved "too far" from the mean regardless of current market conditions. This makes MAENV particularly valuable in ranging markets where volatility-based bands would produce false signals. +The simplicity is both the strength and weakness. Percentage envelopes require manual calibration for each instrument and timeframe: a 1% envelope works for low-volatility large-cap equities but is meaningless for cryptocurrency. The percentage must match the asset's typical volatility. Despite this limitation, fixed envelopes remain popular in institutional settings where the known percentage corresponds to a specific risk threshold or margin requirement. ## Architecture & Physics -The system geometry is constant and proportional: +### 1. Center Line (Configurable MA) -1. **Central Tendency:** A user-selectable moving average (SMA, EMA, or WMA) defines the trend baseline. -2. **Fixed Proportionality:** The bands are calculated as a direct percentage of the moving average value. -3. **Behavior:** - - **SMA:** Stable, laggy, reliable for long-term trends. - - **EMA:** Responsive, recent-bias, good for shorter-term pullbacks. - - **WMA:** Linear weighting, compromise between stability and speed. +Three moving average types are supported: -### Formula +**SMA** (type = 0): $O(1)$ via circular buffer -$$Middle = MA(Source, Period)$$ -$$Offset = Middle \times \frac{Percentage}{100}$$ -$$Upper = Middle + Offset$$ -$$Lower = Middle - Offset$$ +$$\text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} x_{t-i}$$ -## Calculation Steps +**EMA** (type = 1): $O(1)$ via recursive update with warmup compensation -1. **Compute MA:** Calculate the selected Moving Average (SMA/EMA/WMA) for the current bar. - - *SMA/WMA use efficient ring buffers.* - - *EMA uses recursive calculation with warmup compensation.* -2. **Compute Offset:** Multiply the MA value by the target percentage (e.g., 2.0%). -3. **Apply Bands:** Add/Subtract the offset from the MA. +$$\alpha = \frac{2}{n + 1}$$ -## Performance Profile +$$\text{Middle}_t = \frac{\alpha \cdot x_t + (1-\alpha) \cdot \text{raw}_{t-1}}{w_t}$$ -Performance varies slightly by MA type but is generally extremely fast. +**WMA** (type = 2): $O(n)$ weighted sum -### Operation Count (Streaming Mode, per Bar) - SMA/EMA +$$\text{Middle}_t = \frac{\sum_{i=0}^{n-1} (n-i) \cdot n \cdot x_{t-i}}{\sum_{i=0}^{n-1} (n-i) \cdot n}$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 3 | 1 | 3 | -| MUL | 1 | 3 | 3 | -| DIV | 1 | 15 | 15 | -| **Total** | **5** | — | **~21 cycles** | +### 2. Fixed Percentage Offset -*Note: WMA requires O(N) linear iteration, scaling with period.* +$$\text{distance}_t = \text{Middle}_t \times \frac{P}{100}$$ -### Complexity Analysis +$$\text{Upper}_t = \text{Middle}_t + \text{distance}_t$$ -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming (SMA/EMA) | O(1) | Constant per bar | -| Streaming (WMA) | O(N) | Linear in period | -| Batch | O(n) | Sequential processing | +$$\text{Lower}_t = \text{Middle}_t - \text{distance}_t$$ -## Validation +### 3. Scale Invariance -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TradingView** | ✅ | Matches "Moving Average Envelopes" indicator | -| **Manual** | ✅ | Verified calculations for SMA, EMA, WMA types | -| **Standard** | ✅ | Industry-standard implementation | +Because the offset is a percentage of the MA value, the bands automatically scale with price level. A 1% envelope on a $100 stock produces $1 bands; on a $10 stock, $0.10 bands. This is multiplicative scaling, not additive. -## Usage & Pitfalls +### 4. Complexity -- **Fixed Width:** Unlike Bollinger Bands, MAENV maintains constant percentage width. This means bands won't widen during volatility—useful for stable reference but may produce false signals during high-volatility periods. -- **MA Type Selection:** SMA is stable but laggy; EMA is responsive but may overshoot; WMA is a middle ground. Choose based on your trading timeframe. -- **Percentage Calibration:** Common settings are 1-3% for equities, 0.5-1% for major forex pairs. Backtest to find the optimal percentage for your instrument. -- **Mean Reversion:** MAENV works best in ranging markets where price oscillates around the MA. Avoid during strong trends where price can stay outside bands indefinitely. -- **Bar Correction:** Use `isNew=false` when updating the current bar's value, `isNew=true` for new bars. -- **WMA Performance:** WMA requires O(N) operations per bar, making it slower for large periods. Consider SMA or EMA for performance-critical applications. +$O(1)$ for SMA and EMA modes. $O(n)$ for WMA mode due to the weighted sum. -## API +## Mathematical Foundation -```mermaid -classDiagram - class Maenv { - +Maenv(int period = 20, double percentage = 1.0, MaenvType maType = EMA) - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +TValue Update(TValue value) - +void Reset() - } +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback for the moving average ($n$) | 20 | $> 0$ | +| `percentage` | Band distance as percent of MA ($P$) | 1.0 | $> 0$ | +| `ma_type` | Moving average type: 0=SMA, 1=EMA, 2=WMA | 1 (EMA) | $\{0, 1, 2\}$ | +| `source` | Input price series | close | | + +### Pseudo-code + +``` +function MAENV(source, period, percentage, ma_type): + validate: period > 0, percentage > 0 + + // Compute center line based on MA type + if ma_type == 0: middle = SMA(source, period) + if ma_type == 1: middle = EMA(source, period) // with warmup + if ma_type == 2: middle = WMA(source, period) + + // Fixed percentage offset + dist = middle * percentage / 100 + upper = middle + dist + lower = middle - dist + + return [middle, upper, lower] ``` -### Class: `Maenv` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>0` | Lookback size for the moving average. | -| `percentage` | `double` | `1.0` | `>0` | Width of envelope (e.g., 1.0 = 1%). | -| `maType` | `MaenvType` | `EMA` | `SMA,EMA,WMA` | Type of moving average. | +| Output | Description | +|--------|-------------| +| `middle` | Moving average center line | +| `upper` | MA + fixed percentage (overbought threshold) | +| `lower` | MA - fixed percentage (oversold threshold) | -### Properties +## Resources -| Name | Type | Description | -|---|---|---| -| `Last` | `TValue` | The Middle Band (MA) value. | -| `Upper` | `TValue` | The Upper Envelope Band. | -| `Lower` | `TValue` | The Lower Envelope Band. | -| `IsHot` | `bool` | Returns `true` after `period` bars. | - -### Methods - -- `Update(TValue value)`: Updates the indicator with a new price point. -- `Reset()`: Clears all historical data. - -## C# Example - -```csharp -using QuanTAlib; - -// 1. Initialize (20-period SMA, 2.5% envelope) -var maenv = new Maenv(period: 20, percentage: 2.5, maType: MaenvType.SMA); - -// 2. Stream data -var price = 100.0; -maenv.Update(new TValue(DateTime.Now, price)); - -// 3. Check bounds -if (price > maenv.Upper.Value) -{ - Console.WriteLine($"Overbought (> {maenv.Upper.Value:F2})"); -} -``` +- **Murphy, J.J.** *Technical Analysis of the Financial Markets*. New York Institute of Finance, 1999. (Moving average envelope fundamentals) +- **Bollinger, J.** *Bollinger on Bollinger Bands*. McGraw-Hill, 2001. (Adaptive alternative that replaced fixed envelopes) diff --git a/lib/channels/mmchannel/mmchannel.md b/lib/channels/mmchannel/mmchannel.md index 726dd33f..51510363 100644 --- a/lib/channels/mmchannel/mmchannel.md +++ b/lib/channels/mmchannel/mmchannel.md @@ -1,208 +1,94 @@ # 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. +Min-Max Channel 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. The implementation uses monotonic deques for O(1) amortized updates: each element enters the deque once and leaves at most once, so total work over $N$ bars is $O(N)$ regardless of period length. ## 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. +Min-max channels are the simplest form of price envelope analysis, predating most technical indicators. The concept is elemental: track where price has been at its highest and lowest points over a defined window. -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. +The approach gained prominence through Richard Donchian's commodity trading work in the 1960s and later through the Turtle Trading system in 1983. Curtis Faith's public disclosure of the Turtle rules revealed that a 20-day breakout channel formed the core entry signal. While Donchian Channels add a midpoint average, MMCHANNEL strips this away, focusing purely on the 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. +Most naive implementations suffer from $O(n)$ complexity per update, rescanning the entire window to locate max/min values. For period 200 on tick data, that means 200 comparisons per tick. The monotonic deque approach maintains sorted order implicitly, reducing amortized cost to $O(1)$ per bar. ## Architecture & Physics -MMCHANNEL consists of two components: the upper band (highest high) and lower band (lowest low). +### 1. Upper Band (Sliding Window Maximum) -### 1. Upper Band (Highest High) - -Tracks the maximum high price over the lookback window using a decreasing monotonic deque: +The upper band tracks the maximum high price over the lookback window using a decreasing monotonic deque: $$ -U_t = \max_{i=0}^{n-1}(H_{t-i}) +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 (Sliding Window Minimum) -### 2. Lower Band (Lowest Low) - -Tracks the minimum low price over the lookback window using an increasing monotonic deque: +The lower band tracks the minimum low price using an increasing monotonic deque: $$ -L_t = \min_{i=0}^{n-1}(L_{t-i}) +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. +### 3. Monotonic Deque Invariants + +The maximum deque stores (value, index) pairs in decreasing order by value; the front element is always the current maximum. The minimum deque stores pairs in increasing order; the front element is always the current minimum. No explicit sorting is needed because superseded elements are removed on insertion. + +### 4. No Middle Band + +Unlike DCHANNEL and PCHANNEL, MMCHANNEL emits only upper and lower bands. If a midpoint is needed, compute $(U_t + L_t) / 2$ externally. + +### 5. Complexity + +Streaming: $O(1)$ amortized per bar (each element enters/exits the deque at most once). Worst case $O(n)$ occurs only on monotonically increasing/decreasing sequences that flush the entire deque. Memory: two deques of at most $n$ (value, index) pairs plus two circular buffers of $n$ floats. ## Mathematical Foundation +### Parameters + +| Symbol | Name | Constraint | Description | +|--------|------|------------|-------------| +| $n$ | period | $> 0$ | Lookback window size | + ### Monotonic Deque Algorithm -The key insight is maintaining sorted order without explicit sorting: +For the **maximum** (upper band), on each new bar with high value $h$: -**For maximum (upper band):** +``` +push h into circular buffer at (bar_index mod period) -1. **Back removal:** Remove elements from the back that are ≤ the new value -2. **Insert:** Add the new (value, index) pair to the back -3. **Front expiry:** Remove elements from the front whose indices are outside the window -4. **Query:** The front element is always the maximum +// expire stale front +while deque not empty AND front index <= bar_index - period: + remove front -**For minimum (lower band):** +// remove dominated back elements +while deque not empty AND buffer[back index mod period] <= h: + remove back -1. **Back removal:** Remove elements from the back that are ≥ the new value -2. **Insert:** Add the new (value, index) pair to the back -3. **Front expiry:** Remove elements from the front whose indices are outside the window -4. **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) / 2` separately. -- **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 * period` pairs per deque. -- **Bar Correction:** When `isNew=false`, the indicator must restore prior state before computing. - -## API - -```mermaid -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) - } +push (bar_index) to back +upper = buffer[front index mod period] ``` -### Class: `Mmchannel` +For the **minimum** (lower band), the same structure with $\geq$ replacing $\leq$ in the back-removal step. -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback period for highest high and lowest low. | +### Amortized Analysis -### Properties +Each element is pushed to the deque exactly once and popped 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 cost per update. -- `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`): Returns `true` when warmup period is complete. +### Output Interpretation -### Methods +| Output | Interpretation | +|--------|---------------| +| $U_t$ rising | New highs being set within the window | +| $U_t$ flat | No new high; previous extreme still in window | +| $L_t$ falling | New lows being set within the window | +| $U_t - L_t$ contracting | Consolidation; range tightening | +| $U_t - L_t$ expanding | Volatility expansion; breakout potential | -- `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. +## Resources -## C# Example - -```csharp -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) +- Donchian, R. (1960). "High Finance in Copper." *Financial Analysts Journal*, 16(6). +- Faith, C. (2007). *Way of the Turtle*. McGraw-Hill. +- Cormen, T. et al. (2009). *Introduction to Algorithms*, 3rd ed. MIT Press. (Monotonic deque analysis) diff --git a/lib/channels/pchannel/pchannel.md b/lib/channels/pchannel/pchannel.md index be8dc049..60faa12b 100644 --- a/lib/channels/pchannel/pchannel.md +++ b/lib/channels/pchannel/pchannel.md @@ -1,40 +1,36 @@ # PCHANNEL: Price Channel -> "The Turtles didn't need complex math. They needed to know when price broke out of its cage." - -Price Channel (PC) tracks the highest high and lowest low over a lookback period, creating a price envelope that defines where the market has been. Functionally identical to Donchian Channels—same algorithm, different name. Unlike volatility-based bands (Bollinger, Keltner), Price Channel 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. +Price Channel tracks the highest high and lowest low over a lookback period with a midpoint average, creating a three-line price envelope that defines where the market has been. Functionally identical to Donchian Channels, the indicator uses actual price extremes rather than volatility estimates, producing bands that represent real support and resistance levels. This implementation uses monotonic deques for O(1) amortized updates instead of the naive O(n) rescan that most platforms use internally. ## Historical Context -Price Channel is the generic name for what **Richard Donchian** formalized in the 1960s while managing one of the first publicly held commodity funds. The indicator is also known as Donchian Channels, N-period high/low channels, or simply "breakout bands." +Price Channel is the generic name for what Richard Donchian formalized in the 1960s while managing one of the first publicly held commodity funds. The indicator appears under various aliases: Donchian Channels, N-period high/low channels, or breakout bands. -The "4-week rule" (buy on 20-day high, sell on 20-day low) became the foundation for systematic trend-following. 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 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 "4-week rule" (buy on 20-day high, sell on 20-day low) became the foundation for systematic trend-following. The indicator gained fame through the Turtle Trading experiment in 1983, when Richard Dennis and William Eckhardt recruited novice traders and taught them a mechanical system built on channel breakouts. The Turtles reportedly earned over \$100 million using entry signals on 20-day breakouts with exits 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. +Most implementations compute max/min by scanning the entire lookback window on every bar: $O(n)$ per update, $O(n^2)$ for a series. This works for period 20 but becomes costly for longer windows or real-time feeds. The monotonic deque approach maintains running max/min in $O(1)$ amortized time, enabling period 500+ without performance degradation. ## Architecture & Physics -Price Channel consists 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: +Tracks the maximum high price over the lookback window using a decreasing monotonic deque: $$ -U_t = \max_{i=0}^{n-1}(H_{t-i}) +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. +where $H$ is the high price and $n$ is the period. The upper band moves up immediately on a new high 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: +Tracks the minimum low price using an increasing monotonic deque: $$ -L_t = \min_{i=0}^{n-1}(L_{t-i}) +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. +The lower band drops immediately on new lows but only rises when the previous lowest low exits the window. ### 3. Middle Band @@ -44,131 +40,75 @@ $$ M_t = \frac{U_t + L_t}{2} $$ -This represents the "equilibrium" price over the lookback period. +This represents the equilibrium price of the lookback window. Unlike MMCHANNEL which omits the midpoint, PCHANNEL always emits all three lines. -### Monotonic Deque Algorithm +### 4. Monotonic Deque Mechanism -Instead of rescanning the window on each bar, the implementation maintains two monotonic deques: +Two deques maintain sorted order without explicit sorting: -1. **Deque (Max):** Valid indices of decreasing values. Front is always the Max. -2. **Deque (Min):** Valid indices of increasing values. Front is always the Min. -3. **Update:** - - Remove old indices from front (expired). - - Remove values from back that are superseded by new value. - - Add new value to back. +- **Max deque:** stores indices in decreasing value order; front is always the maximum. +- **Min deque:** stores indices in increasing value order; front is always the minimum. -**Complexity:** Each element is added once and removed at most once. Total work for $N$ bars is $O(N)$, averaging $O(1)$ per bar. +On each bar: (1) expire stale front indices outside the window, (2) remove back elements superseded by the new value, (3) push the new index to the back. -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, Scalar) +Streaming: $O(1)$ amortized per bar. Each element enters and exits each deque at most once. Memory: two circular buffers of $n$ floats plus two deques of at most $n$ indices. -Per-bar cost using monotonic deque optimization: +## Mathematical Foundation -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| CMP (Bound checks) | 4 | 1 | 4 | -| ADD (Index update) | 1 | 1 | 1 | -| MUL (Average) | 1 | 3 | 3 | -| Deque Maint. | ~2 | 1 | ~2 | -| **Total** | **8** | — | **~10 cycles** | +### Parameters -**Complexity**: O(1) amortized. +| Symbol | Name | Constraint | Description | +|--------|------|------------|-------------| +| $n$ | period | $> 0$ | Lookback window size | -### Batch Mode (512 values, SIMD/FMA) +### Pseudo-code -Finding max/min over sliding windows has limited SIMD benefit due to sequential dependency and the efficiency of the scalar deque algorithm. +``` +function pchannel(high[], low[], period): + max_deque = empty // decreasing monotonic deque of indices + min_deque = empty // increasing monotonic deque of indices + hbuf = circular_buffer(period) + lbuf = circular_buffer(period) -| Operation | Scalar Ops | SIMD Benefit | Notes | -| :--- | :---: | :---: | :--- | -| Max/Min update | 4 | 1× | Deque-based, sequential | -| Middle band | 2 | 2× | ADD + MUL parallelizable | + for each bar t: + hbuf[t mod period] = high[t] + lbuf[t mod period] = low[t] -| Mode | Cycles/bar | Total (512 bars) | Improvement | -| :--- | :---: | :---: | :---: | -| Scalar streaming | 10 | 5,120 | — | -| Partial SIMD | ~8 | ~4,096 | **~20%** | + // expire stale front entries + while max_deque not empty AND max_deque.front <= t - period: + max_deque.pop_front() + while min_deque not empty AND min_deque.front <= t - period: + min_deque.pop_front() -## Validation + // remove dominated back entries + while max_deque not empty AND hbuf[max_deque.back mod period] <= high[t]: + max_deque.pop_back() + while min_deque not empty AND lbuf[min_deque.back mod period] >= low[t]: + min_deque.pop_back() -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | - | No implementation | -| **Skender** | - | No implementation (uses Donchian) | -| **Tulip** | - | No implementation | -| **Ooples** | ✅ | Cross-validated via Donchian equivalence | -| **Dchannel** | ✅ | Exact match—identical algorithm | + max_deque.push_back(t) + min_deque.push_back(t) -## Usage & Pitfalls + upper = hbuf[max_deque.front mod period] + lower = lbuf[min_deque.front mod period] + middle = (upper + lower) / 2 -- **Stale Extremes**: Price Channel bands stay flat until a new extreme occurs or the old extreme exits the window. This is feature, not a bug. -- **O(n) Trap**: Naive implementations rescan the full window every bar. QuanTAlib's solution is O(1). -- **Breakout vs. Touch**: Price touching the upper band is not the same as breaking out. True breakouts close above/below the band. -- **Asymmetric Exit**: Consider different periods for long/short entries and exits (e.g., Turtle 20/10 rule). - -## API - -```mermaid -classDiagram - class Pchannel { - +Name : string - +WarmupPeriod : int - +Upper : TValue - +Lower : TValue - +Last : TValue - +IsHot : bool - +Update(TBar bar) TValue - +Update(TBarSeries source) TSeries - +Prime(TBarSeries source) void - } + emit (upper, middle, lower) ``` -### Class: `Pchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | — | `>0` | Lookback window size. | -| `source` | `TBarSeries` | — | `any` | Initial input source (optional). | +| Output | Interpretation | +|--------|---------------| +| Price closes above $U_t$ | Breakout signal (Turtle entry) | +| Price closes below $L_t$ | Breakdown signal | +| $M_t$ rising | Upward drift in the price range | +| $U_t - L_t$ contracting | Consolidation; range tightening | +| $U_t - L_t$ expanding | Volatility expansion | -### Properties - -- `Name` (`string`): The indicator name (e.g., "Pchannel(20)"). -- `WarmupPeriod` (`int`): The number of samples needed for full validity. -- `Upper` (`TValue`): The current highest high. -- `Lower` (`TValue`): The current lowest low. -- `Last` (`TValue`): The current middle line value ((Upper + Lower) / 2). -- `IsHot` (`bool`): Returns `true` if we have processed `period` samples. - -### Methods - -- `Update(TBar bar)`: Updates the indicator with a new bar (High/Low) and returns the Middle band value. -- `Update(TBarSeries source)`: Batch processes a series and returns (Middle, Upper, Lower) tuple. -- `Prime(TBarSeries source)`: Pre-loads the indicator with history without returning results. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var channel = new Pchannel(period: 20); - -// Update Loop -foreach (var bar in bars) -{ - var result = channel.Update(bar); - - if (channel.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={result.Value:F2} Upper={channel.Upper.Value:F2} Lower={channel.Lower.Value:F2}"); - } -} - -// Batch Processing -var (mid, upper, lower) = channel.Update(bars); -``` - -## References +## Resources - Donchian, R. (1960). "High Finance in Copper." *Financial Analysts Journal*. -- Faith, C. (2007). *Way of the Turtle: The Secret Methods that Turned Ordinary People into Legendary Traders*. +- Faith, C. (2007). *Way of the Turtle: The Secret Methods that Turned Ordinary People into Legendary Traders*. McGraw-Hill. diff --git a/lib/channels/regchannel/regchannel.md b/lib/channels/regchannel/regchannel.md index 7eccd9b9..993be5c8 100644 --- a/lib/channels/regchannel/regchannel.md +++ b/lib/channels/regchannel/regchannel.md @@ -1,229 +1,142 @@ # REGCHANNEL: Linear Regression Channel -> "Linear regression isn't about predicting the future—it's about understanding where price *should* be given recent history, and measuring how far it's strayed." - -The Linear Regression Channel (REGCHANNEL) plots a best-fit line through price data over a specified period, with parallel bands at a configurable standard deviation distance. This implementation uses ordinary least squares (OLS) regression with population standard deviation of residuals, providing a statistically grounded view of trend direction and price deviation. +Linear Regression Channel plots a best-fit line through price data over a specified period with parallel bands at a configurable standard deviation of residuals. Unlike moving average envelopes that offset from a smoothed price, regression channels adapt their slope to the underlying trend and their width to actual dispersion around that trend. The algorithm uses ordinary least squares with precomputed index sums, requiring two passes per bar: one for the regression coefficients and one for the residual standard deviation. ## Historical Context -Linear regression channels emerged from basic statistical analysis applied to financial markets. The concept combines two fundamental statistical tools: linear regression (fitting a line to minimize squared errors) and standard deviation (measuring dispersion around that line). +Linear regression channels emerged from basic statistical methods applied to financial markets in the 1980s and 1990s. Gilbert Raff popularized "Raff Regression Channels" which use the same concept: fit a line, measure how far price wanders from it, and draw parallel bands at that distance. -Unlike moving average envelopes that simply offset from a smoothed price, regression channels adapt their slope to the underlying trend and their width to actual price volatility around that trend. This makes them particularly useful for identifying when prices have deviated significantly from their recent trajectory. +The key insight separating regression channels from moving average envelopes: a moving average treats all recent prices equally, while linear regression fits a line that best explains the directional trend. The residuals (actual minus predicted) measure how much price deviates from this trajectory. When prices consistently touch the upper band, the trend is accelerating; when they compress toward the regression line, momentum is fading. -The indicator is functionally identical to SDCHANNEL but uses "Regchannel" naming convention, which may be preferred in some trading platforms and literature. +REGCHANNEL and SDCHANNEL implement identical algorithms. The distinction is purely naming convention: some platforms and literature label the indicator "Regression Channel" while others use "Standard Deviation Channel." Both compute OLS regression with population standard deviation of residuals. ## Architecture & Physics ### 1. Sliding Window Buffer -The indicator maintains a rolling window of the most recent `period` price values: +The indicator maintains a rolling window of the most recent $n$ price values: $$ -W_t = \{P_{t-n+1}, P_{t-n+2}, \ldots, P_t\} +W_t = \{P_{t-n+1},\; P_{t-n+2},\; \ldots,\; P_t\} $$ -where $n = \min(t+1, \text{period})$. During warmup ($t < \text{period}$), all available values are used. +### 2. Linear Regression via Normal Equations -### 2. Linear Regression via Least Squares - -For each update, the indicator computes the best-fit line $y = mx + b$ using the normal equations: +For each update, the best-fit line $y = mx + b$ is computed using time indices $x_i = i$ and prices $y_i = P_i$: $$ -m = \frac{n \sum_{i=0}^{n-1} x_i y_i - \sum_{i=0}^{n-1} x_i \sum_{i=0}^{n-1} y_i}{n \sum_{i=0}^{n-1} x_i^2 - \left(\sum_{i=0}^{n-1} x_i\right)^2} +m = \frac{n \sum x_i y_i - \sum x_i \sum y_i}{n \sum x_i^2 - \left(\sum x_i\right)^2} $$ $$ -b = \frac{\sum_{i=0}^{n-1} y_i - m \sum_{i=0}^{n-1} x_i}{n} +b = \frac{\sum y_i - m \sum x_i}{n} $$ -where $x_i = i$ (time index) and $y_i = P_i$ (price at that index). - -### 3. Regression Value Calculation - -The middle line value at the current bar (rightmost point of the regression line): +The middle band value is the regression line evaluated at the rightmost point: $$ -\text{Middle}_t = m \cdot (n-1) + b +\text{Middle}_t = m \cdot (n - 1) + b $$ -This represents the expected price based on the linear trend through the window. +### 3. Standard Deviation of Residuals -### 4. Standard Deviation of Residuals - -The indicator computes population standard deviation of the residuals (differences between actual and predicted values): +The population standard deviation of the differences between actual and predicted values: $$ -\sigma_t = \sqrt{\frac{\sum_{i=0}^{n-1} (y_i - \hat{y}_i)^2}{n}} +\sigma_t = \sqrt{\frac{1}{n} \sum_{i=0}^{n-1} \left(y_i - (m \cdot i + b)\right)^2} $$ -where $\hat{y}_i = m \cdot i + b$ is the predicted value at position $i$. +### 4. Band Construction -### 5. Channel Bands - -Upper and lower bands are placed at a configurable multiple of the standard deviation: +Upper and lower bands at a configurable multiple $k$ of the residual standard deviation: $$ -\text{Upper}_t = \text{Middle}_t + k \cdot \sigma_t +U_t = \text{Middle}_t + k \cdot \sigma_t $$ $$ -\text{Lower}_t = \text{Middle}_t - k \cdot \sigma_t +L_t = \text{Middle}_t - k \cdot \sigma_t $$ -where $k$ is the multiplier parameter (default 2.0). +### 5. Complexity + +Per bar: $O(n)$ due to two loops over the window (one for sums, one for residuals). Memory: a ring buffer of $n$ doubles. The index sums $\sum x$ and $\sum x^2$ are constants for fixed $n$ and can be precomputed at construction. ## Mathematical Foundation -### Efficient Computation Using Running Sums +### Parameters -Rather than recalculating sums from scratch each bar, the implementation maintains running sums and adjusts them incrementally. For a sliding window of size $n$: +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 20 | $> 1$ | Lookback window for regression | +| $k$ | multiplier | 2.0 | $> 0$ | Stddev multiplier for band width | -- $\sum x = 0 + 1 + \ldots + (n-1) = \frac{n(n-1)}{2}$ -- $\sum x^2 = 0^2 + 1^2 + \ldots + (n-1)^2 = \frac{n(n-1)(2n-1)}{6}$ +### Precomputed Constants -These are constants for a fixed period, computed once at construction. - -### Denominator and Numerical Stability - -The denominator in the slope calculation: +For a fixed period $n$, the index sums are constants: $$ -D = n \sum x^2 - \left(\sum x\right)^2 +\sum_{i=0}^{n-1} i = \frac{n(n-1)}{2}, \qquad \sum_{i=0}^{n-1} i^2 = \frac{n(n-1)(2n-1)}{6} $$ -For $n \geq 2$, this is always positive, ensuring numerical stability. The implementation guards against $D = 0$ (which can only occur for $n = 1$). - -### Residual Calculation - -For each point in the window: - $$ -r_i = y_i - (m \cdot i + b) +D = n \sum i^2 - \left(\sum i\right)^2 $$ -The sum of squared residuals: +For $n \geq 2$, $D > 0$ always, ensuring numerical stability. -$$ -\text{SSR} = \sum_{i=0}^{n-1} r_i^2 -$$ +### Pseudo-code -## Performance Profile +``` +function regchannel(source[], period, multiplier): + buf = ring_buffer(period) + sum_x = period * (period - 1) / 2 + sum_x2 = period * (period - 1) * (2 * period - 1) / 6 + denom = period * sum_x2 - sum_x * sum_x -### Operation Count (Streaming Mode, Scalar) + for each bar t: + buf.add(source[t]) + n = buf.count -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | ~3n+15 | 1 | ~3n+15 | -| MUL | ~2n+10 | 3 | ~6n+30 | -| DIV | 4 | 15 | 60 | -| SQRT | 1 | 15 | 15 | -| Ring buffer ops | 2 | 5 | 10 | -| **Total** | — | — | **~9n+130** | + // pass 1: accumulate sums for regression + sum_y = 0 + sum_xy = 0 + for i = 0 to n-1: + y = buf[i] + sum_y += y + sum_xy += i * y -For period=20: approximately 310 cycles per bar. + slope = (n * sum_xy - sum_x * sum_y) / denom + intercept = (sum_y - slope * sum_x) / n + middle = slope * (n - 1) + intercept -### Quality Metrics + // pass 2: residual standard deviation + ssr = 0 + for i = 0 to n-1: + predicted = slope * i + intercept + residual = buf[i] - predicted + ssr += residual * residual -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 9/10 | Exact OLS regression; population σ | -| **Timeliness** | 7/10 | Inherent lag from lookback window | -| **Smoothness** | 8/10 | Regression naturally smooths | -| **Responsiveness** | 6/10 | Slower to react than EMA-based channels | + stddev = sqrt(ssr / n) + upper = middle + multiplier * stddev + lower = middle - multiplier * stddev -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | No direct equivalent | -| **Skender** | N/A | No direct equivalent | -| **Tulip** | N/A | No direct equivalent | -| **Manual** | ✅ | Verified against hand calculations | - -Linear regression channels are not commonly found in standard TA libraries with this exact specification. Validation relies on mathematical verification against known formulas. - -## Usage & Pitfalls - -- **Warmup Period**: The indicator requires `period` bars to reach full accuracy. During warmup, it uses all available data but may produce different results than post-warmup. -- **Slope Interpretation**: A positive slope indicates uptrend within the window; negative indicates downtrend. The magnitude indicates trend strength. -- **Band Width = 0**: When prices fall perfectly on a line (zero residuals), bands collapse to the middle line. This is mathematically correct but visually unexpected. -- **Standard Deviation Choice**: This implementation uses population σ (dividing by n), not sample σ (dividing by n-1). Some implementations differ. -- **Memory Footprint**: Each instance requires a RingBuffer of `period` doubles (~8 bytes each) plus state structs (~80 bytes). For period=20: ~240 bytes per instance. -- **isNew Parameter**: When `isNew=false`, the indicator rolls back to the previous state before incorporating the update. This enables bar correction without state accumulation errors. - -## API - -```mermaid -classDiagram - class Regchannel { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Lower - +double Slope - +double StdDev - +bool IsHot - +Regchannel(int period, double multiplier) - +Regchannel(TSeries source, int period, double multiplier) - +TValue Update(TValue input, bool isNew) - +Tuple~TSeries,TSeries,TSeries~ Update(TSeries source) - +void Prime(TSeries source) - +void Reset() - +static void Batch(ReadOnlySpan~double~ source, Span~double~ middle, Span~double~ upper, Span~double~ lower, int period, double multiplier) - +static Tuple~TSeries,TSeries,TSeries~ Batch(TSeries source, int period, double multiplier) - +static Tuple~Tuple~TSeries,TSeries,TSeries~,Regchannel~ Calculate(TSeries source, int period, double multiplier) - } + emit (upper, middle, lower) ``` -### Class: `Regchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>1` | Lookback period for linear regression calculation. | -| `multiplier` | `double` | `2.0` | `>0` | Standard deviation multiplier for band width. | +| Output | Interpretation | +|--------|---------------| +| Positive slope | Uptrend within the window | +| Negative slope | Downtrend within the window | +| $\sigma \to 0$ | Price perfectly linear; bands collapse to the regression line | +| Price at upper band | Overextended above trend (mean-reversion signal) | +| Price at lower band | Overextended below trend | +| Band width expanding | Increasing residual dispersion; trend becoming noisy | -### Properties +## Resources -- `Last` (`TValue`): The current linear regression value (middle line). -- `Upper` (`TValue`): The upper band (regression + multiplier × σ). -- `Lower` (`TValue`): The lower band (regression - multiplier × σ). -- `Slope` (`double`): The slope of the linear regression line. -- `StdDev` (`double`): The standard deviation of residuals. -- `IsHot` (`bool`): Returns `true` when warmup period is complete. - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new value and returns the result. -- `Update(TSeries source)`: Processes an entire series and returns (Middle, Upper, Lower) tuple of TSeries. -- `Prime(TSeries source)`: Initializes internal state from historical data. -- `Reset()`: Resets the indicator to its initial state. -- `Batch(...)`: Static method for span-based batch processing. -- `Calculate(TSeries source, int period, double multiplier)`: Static factory that returns results and indicator instance. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var regchannel = new Regchannel(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in quotes) -{ - var result = regchannel.Update(bar.Close); - - // Use valid results - if (regchannel.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={result.Value:F2}, Upper={regchannel.Upper.Value:F2}, Lower={regchannel.Lower.Value:F2}, Slope={regchannel.Slope:F4}"); - } -} -``` - -## References - -- Draper, N.R. & Smith, H. (1998). "Applied Regression Analysis." Wiley. -- Murphy, J.J. (1999). "Technical Analysis of the Financial Markets." New York Institute of Finance. -- PineScript Reference: Linear Regression implementation patterns. +- Raff, G. (1991). "Trading the Regression Channel." *Technical Analysis of Stocks & Commodities*. +- Draper, N. & Smith, H. (1998). *Applied Regression Analysis*. Wiley. +- Kaufman, P. (2013). *Trading Systems and Methods*, 5th ed. Wiley. diff --git a/lib/channels/sdchannel/sdchannel.md b/lib/channels/sdchannel/sdchannel.md index 0316c3af..31b640e3 100644 --- a/lib/channels/sdchannel/sdchannel.md +++ b/lib/channels/sdchannel/sdchannel.md @@ -1,253 +1,146 @@ # SDCHANNEL: Standard Deviation Channel -> "The regression line tells you where price should be. The standard deviation tells you how wrong the market usually is." - -Standard Deviation Channel (SDCHANNEL) plots a linear regression line through price data with parallel bands positioned at a specified number of standard deviations of the residuals above and below. Unlike Bollinger Bands which measure deviation from a moving average, SDCHANNEL measures deviation from the best-fit trend line—capturing how much price wanders from its underlying trajectory rather than from its simple average. +Standard Deviation Channel plots a linear regression line through price data with parallel bands at a specified number of standard deviations of residuals above and below. Unlike Bollinger Bands which measure deviation from a moving average, SDCHANNEL measures deviation from the best-fit trend line, capturing how much price wanders from its underlying trajectory rather than from its simple average. The algorithm is identical to REGCHANNEL; the distinction is purely a naming convention found across different platforms and literature. ## Historical Context -Linear regression channels emerged from statistical methods applied to financial markets in the 1980s and 1990s. Gilbert Raff popularized "Raff Regression Channels" which use similar concepts. The standard deviation of residuals approach provides a statistically meaningful measure of dispersion around the trend. +Linear regression channels emerged from statistical methods applied to financial markets in the 1980s and 1990s. Gilbert Raff popularized "Raff Regression Channels" which use the same principle: fit a least-squares line to a price window and draw parallel bands at the residual standard deviation distance. -The key insight: a moving average treats all recent prices equally, while linear regression fits a line that best explains the trend. The residuals (differences between actual and predicted prices) measure how much price deviates from this trend. When prices consistently touch the upper band, the trend is accelerating; when they hug the lower band, momentum is fading. +The critical distinction from moving average bands: a regression line projects the trend direction, not the average level. The residuals (actual minus predicted prices) measure how much price deviates from this directional fit. When residuals are small, price is tracking the trend cleanly. When residuals grow, the trend is becoming noisy or price is breaking away from its recent trajectory. -Most charting platforms compute linear regression naively with O(n) operations per bar. This implementation precomputes constants and uses FMA operations for efficiency. +Some platforms label this indicator "Standard Deviation Channel" (emphasizing the band-width metric), while others use "Regression Channel" (emphasizing the centerline method). Both SDCHANNEL and REGCHANNEL implement identical OLS regression with population standard deviation. ## Architecture & Physics -Standard Deviation Channels consist of three components: the linear regression line (middle), and upper/lower bands at ±multiplier × standard deviation of residuals. - ### 1. Linear Regression (Middle Band) -The best-fit line through the lookback window using ordinary least squares: +The best-fit line through the lookback window using ordinary least squares, with time indices $x_i = i$ and prices $y_i = P_i$: $$ -y = mx + b -$$ - -where: - -$$ -m = \frac{n \sum xy - \sum x \sum y}{n \sum x^2 - (\sum x)^2} +m = \frac{n \sum x_i y_i - \sum x_i \sum y_i}{n \sum x_i^2 - \left(\sum x_i\right)^2} $$ $$ -b = \frac{\sum y - m \sum x}{n} +b = \frac{\sum y_i - m \sum x_i}{n} $$ -The middle band value is the regression line evaluated at the current bar (x = n-1). - -### 2. Standard Deviation of Residuals - -For each point, compute the residual (difference between actual and predicted): +The middle band is the regression line evaluated at the current bar (the rightmost point): $$ -r_i = y_i - (m \cdot x_i + b) +\text{Middle}_t = m \cdot (n - 1) + b $$ -The standard deviation of these residuals: +### 2. Residual Standard Deviation + +For each point in the window, the residual is the difference between the actual and predicted value. The population standard deviation of these residuals: $$ -\sigma = \sqrt{\frac{\sum_{i=0}^{n-1} r_i^2}{n}} +\sigma_t = \sqrt{\frac{1}{n} \sum_{i=0}^{n-1} \left(y_i - (m \cdot i + b)\right)^2} $$ -Note: This uses population standard deviation (divide by n), not sample standard deviation (divide by n-1). +Note: this uses population $\sigma$ (dividing by $n$), not sample $s$ (dividing by $n - 1$). -### 3. Upper and Lower Bands - -Parallel lines at fixed distance from the regression line: +### 3. Band Construction $$ -U_t = R_t + k \cdot \sigma +U_t = \text{Middle}_t + k \cdot \sigma_t $$ $$ -L_t = R_t - k \cdot \sigma +L_t = \text{Middle}_t - k \cdot \sigma_t $$ -where $R_t$ is the regression value at time $t$ and $k$ is the multiplier (typically 2.0). +where $k$ is the multiplier (default 2.0). Under normality assumptions, $k = 2$ captures approximately 95% of residuals. + +### 4. Residual Properties + +By definition of least squares: (1) the sum of residuals equals zero, (2) residuals are uncorrelated with the $x$ values, and (3) when $\sigma = 0$, all points lie exactly on the regression line. + +### 5. Complexity + +Per bar: $O(n)$ due to two passes over the window (sums, then residuals). Memory: a ring buffer of $n$ doubles. The index sums $\sum x$ and $\sum x^2$ are precomputed constants for fixed $n$. ## Mathematical Foundation +### Parameters + +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 20 | $> 1$ | Lookback window for regression | +| $k$ | multiplier | 2.0 | $> 0$ | Stddev multiplier for band width | + ### Precomputed Constants -For a fixed period $n$, several sums can be precomputed: - $$ -\sum x = 0 + 1 + ... + (n-1) = \frac{n(n-1)}{2} +\sum_{i=0}^{n-1} i = \frac{n(n-1)}{2}, \qquad \sum_{i=0}^{n-1} i^2 = \frac{n(n-1)(2n-1)}{6} $$ $$ -\sum x^2 = 0^2 + 1^2 + ... + (n-1)^2 = \frac{(n-1)n(2n-1)}{6} +D = n \sum i^2 - \left(\sum i\right)^2 $$ -$$ -\text{denom} = n \sum x^2 - (\sum x)^2 -$$ +For $n \geq 2$, $D > 0$ always holds, so the slope denominator is never zero. -This reduces per-bar computation to: +### Pseudo-code -1. Calculate $\sum y$ and $\sum xy$ over the window -2. Compute slope and intercept using precomputed values -3. Evaluate regression at current point -4. Compute residuals and their standard deviation +``` +function sdchannel(source[], period, multiplier): + buf = ring_buffer(period) + sum_x = period * (period - 1) / 2 + sum_x2 = period * (period - 1) * (2 * period - 1) / 6 + denom = period * sum_x2 - sum_x * sum_x + + for each bar t: + buf.add(source[t]) + n = buf.count + + // pass 1: regression coefficients + sum_y = 0, sum_xy = 0 + for i = 0 to n-1: + y = buf[i] + sum_y += y + sum_xy += i * y + + slope = (n * sum_xy - sum_x * sum_y) / denom + intercept = (sum_y - slope * sum_x) / n + middle = slope * (n - 1) + intercept + + // pass 2: residual standard deviation + ssr = 0 + for i = 0 to n-1: + predicted = slope * i + intercept + residual = buf[i] - predicted + ssr += residual * residual + + stddev = sqrt(ssr / n) + upper = middle + multiplier * stddev + lower = middle - multiplier * stddev + + emit (upper, middle, lower) +``` ### Slope Interpretation -The slope indicates trend direction and strength: +| Slope | Market State | +|-------|-------------| +| $m > 0$ | Uptrend within the window | +| $m < 0$ | Downtrend within the window | +| $m \approx 0$ | Sideways / consolidation | +| $|m|$ increasing | Trend accelerating | +| $|m|$ decreasing | Trend decelerating | -- $m > 0$: Uptrend (higher slope = steeper ascent) -- $m < 0$: Downtrend (lower slope = steeper descent) -- $m \approx 0$: Sideways/consolidating market +### Output Interpretation -### Residual Properties +| Output | Interpretation | +|--------|---------------| +| Price at upper band | Overextended above trend | +| Price at lower band | Overextended below trend | +| $\sigma \to 0$ | Perfect linear trend; bands collapse | +| Band width expanding | Increasing noise around the trend | -By definition of least squares regression: - -- Sum of residuals = 0 -- Residuals are uncorrelated with x values -- Points above and below the line balance out - -When $\sigma = 0$, all points lie exactly on the regression line (perfect linear trend). - -## Performance Profile - -### Operation Count (Streaming Mode, Scalar) - -Per-bar cost for streaming update: - -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | ~4n | 1 | 4n | -| MUL | ~2n | 3 | 6n | -| DIV | 4 | 15 | 60 | -| SQRT | 1 | 15 | 15 | -| FMA | 2n | 4 | 8n | -| **Total** | — | — | **~18n + 75 cycles** | - -For period=20: ~435 cycles per bar. The algorithm is O(n) per bar due to the sum calculations over the window. - -### Batch Mode (512 values, SIMD/FMA) - -Linear regression has limited SIMD benefit due to sequential dependencies and the need to accumulate sums: - -| Operation | Scalar Ops | SIMD Benefit | Notes | -| :--- | :---: | :---: | :--- | -| Sum Y, Sum XY | O(n) | Partial | Reduction operations | -| Residual calc | O(n) | 4-8× | Embarrassingly parallel | -| StdDev | O(n) | Partial | Reduction at end | - -**Batch efficiency (512 bars, period=20):** - -| Mode | Cycles/bar | Total (512 bars) | Overhead | -| :--- | :---: | :---: | :---: | -| Scalar streaming | 435 | 222,720 | — | -| SIMD residuals | ~380 | ~194,560 | — | -| **Improvement** | **~13%** | **~28K saved** | — | - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Exact least squares calculation | -| **Timeliness** | 5/10 | Regression lags by nature—fits past data | -| **Overshoot** | 8/10 | Bands based on residuals, not price velocity | -| **Smoothness** | 7/10 | Regression line smooths noise; bands vary with residual dispersion | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | No equivalent function | -| **Skender** | N/A | No equivalent function | -| **Tulip** | N/A | No equivalent function | -| **Ooples** | N/A | No equivalent function | -| **Manual** | ✅ | Verified against hand calculations | - -The indicator is validated against manual calculations of linear regression and standard deviation of residuals. - -## Usage & Pitfalls - -- **Period Selection**: Short periods (5-10) make the regression overly sensitive to recent bars; long periods (50+) create substantial lag. Period 20 is common, matching roughly one month of daily data. -- **Multiplier Choice**: The default multiplier of 2.0 captures ~95% of residuals assuming normal distribution. Use 1.0 for tighter bands (~68%), 3.0 for wider bands (~99.7%). -- **Warmup Period**: The indicator requires at least 2 bars to compute a regression line. WarmupPeriod equals the period parameter. Before warmup, bands equal the input value. -- **Zero Standard Deviation**: When all points lie exactly on a line (perfect linear trend or constant values), $\sigma = 0$ and bands collapse to the regression line. -- **Regression vs. Moving Average**: The regression line projects the trend, not the average. It can be above or below all recent prices if the trend is strong. -- **O(n) Complexity**: Unlike EMA (O(1)) or SMA with ring buffer (O(1)), linear regression requires O(n) operations per bar. -- **Memory**: The ring buffer stores `period` doubles. For period=50, that's 400 bytes per instance. - -## API - -```mermaid -classDiagram - class Sdchannel { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Lower - +double Slope - +double StdDev - +bool IsHot - +Sdchannel(int period, double multiplier) - +Sdchannel(TSeries source, int period, double multiplier) - +TValue Update(TValue input, bool isNew) - +Tuple~TSeries,TSeries,TSeries~ Update(TSeries source) - +void Prime(TSeries source) - +void Reset() - +static void Batch(ReadOnlySpan~double~ source, Span~double~ middle, Span~double~ upper, Span~double~ lower, int period, double multiplier) - +static Tuple~TSeries,TSeries,TSeries~ Batch(TSeries source, int period, double multiplier) - +static Tuple~Tuple~TSeries,TSeries,TSeries~,Sdchannel~ Calculate(TSeries source, int period, double multiplier) - } -``` - -### Class: `Sdchannel` - -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `>1` | Lookback period for linear regression calculation. | -| `multiplier` | `double` | `2.0` | `>0` | Standard deviation multiplier for band width. | - -### Properties - -- `Last` (`TValue`): The current linear regression value (middle line). -- `Upper` (`TValue`): The upper band (regression + multiplier × σ). -- `Lower` (`TValue`): The lower band (regression - multiplier × σ). -- `Slope` (`double`): The slope of the linear regression line. -- `StdDev` (`double`): The standard deviation of residuals. -- `IsHot` (`bool`): Returns `true` when warmup period is complete. - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new value and returns the result. -- `Update(TSeries source)`: Processes an entire series and returns (Middle, Upper, Lower) tuple of TSeries. -- `Prime(TSeries source)`: Initializes internal state from historical data. -- `Reset()`: Resets the indicator to its initial state. -- `Batch(...)`: Static method for span-based batch processing. -- `Calculate(TSeries source, int period, double multiplier)`: Static factory that returns results and indicator instance. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var sdchannel = new Sdchannel(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in quotes) -{ - var result = sdchannel.Update(bar.Close); - - // Use valid results - if (sdchannel.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={result.Value:F2}, Upper={sdchannel.Upper.Value:F2}, Lower={sdchannel.Lower.Value:F2}, Slope={sdchannel.Slope:F4}"); - } -} -``` - -## References +## Resources - Raff, G. (1991). "Trading the Regression Channel." *Technical Analysis of Stocks & Commodities*. -- Bulkowski, T. (2005). *Encyclopedia of Chart Patterns*, 2nd ed. Wiley. (Chapter on Linear Regression) -- Kaufman, P. J. (2013). *Trading Systems and Methods*, 5th ed. Wiley. (Linear Regression Indicators) +- Draper, N. & Smith, H. (1998). *Applied Regression Analysis*. Wiley. +- Bulkowski, T. (2005). *Encyclopedia of Chart Patterns*, 2nd ed. Wiley. +- Kaufman, P. (2013). *Trading Systems and Methods*, 5th ed. Wiley. diff --git a/lib/channels/starchannel/starchannel.md b/lib/channels/starchannel/starchannel.md index 99187a3c..cb1cf16d 100644 --- a/lib/channels/starchannel/starchannel.md +++ b/lib/channels/starchannel/starchannel.md @@ -1,183 +1,136 @@ # STARCHANNEL: Stoller Average Range Channel -> "Volatility is the market's pulse—STARC channels let you feel it." - -The Stoller Average Range Channel (STARCHANNEL) creates a volatility-adaptive price envelope using the Average True Range (ATR) to determine band width around a simple moving average centerline. Developed by Manning Stoller, this indicator provides dynamic support and resistance levels that automatically expand during volatile periods and contract during calmer markets—offering more relevant and responsive trading signals than fixed percentage envelopes. +Stoller Average Range Channel creates a volatility-adaptive price envelope using Average True Range (ATR) to determine band width around a simple moving average centerline. The bands automatically expand during volatile periods and contract during calmer markets. The implementation uses a circular buffer for the SMA running sum and Wilder's RMA with a warmup compensator for ATR, achieving O(1) streaming updates per bar. ## Historical Context -Manning Stoller developed the STARC Bands in the early 1980s as a volatility-adaptive alternative to fixed percentage envelopes. His insight was simple: channels should widen during high volatility and contract during low volatility, reflecting actual market conditions rather than arbitrary percentages. +Manning Stoller developed STARC Bands in the early 1980s as a volatility-adaptive alternative to fixed percentage envelopes. His insight was straightforward: channels should widen during high volatility and contract during low volatility, reflecting actual market conditions rather than arbitrary percentages. -The indicator combines two established concepts: the simple moving average (for trend direction) and Average True Range (for volatility measurement). J. Welles Wilder had already popularized ATR in his 1978 book "New Concepts in Technical Trading Systems." Stoller's contribution was recognizing that ATR-based bands would naturally adapt to each security's volatility characteristics. +The indicator combines two established building blocks: the simple moving average (for trend direction) and Average True Range (for volatility measurement). J. Welles Wilder had already introduced ATR in his 1978 book *New Concepts in Technical Trading Systems*. Stoller's contribution was recognizing that ATR-based bands would naturally adapt to each security's volatility characteristics without requiring manual adjustment across different instruments or timeframes. -STARC Bands gained popularity among futures traders in the 1980s and remain widely used today. The approach influenced many subsequent indicators that combine trend-following centerlines with volatility-based band widths. +STARC Bands gained popularity among futures traders in the 1980s and influenced many subsequent volatility-adaptive channel indicators. The structure is similar to Keltner Channels (EMA center + ATR width) but uses an SMA centerline, which gives equal weight to all bars in the window rather than exponentially decaying emphasis on recent prices. ## Architecture & Physics -STARCHANNEL consists of three components: a simple moving average centerline and upper/lower bands at a configurable ATR multiple. - ### 1. Simple Moving Average (Middle Band) -The centerline is a standard SMA of the close price: +The centerline is a standard SMA of the source price using a circular buffer with running sum: $$ \text{Middle}_t = \frac{1}{n} \sum_{i=0}^{n-1} C_{t-i} $$ -where $C$ is the close price and $n$ is the period. +where $C$ is the source price (typically close) and $n$ is the period. -### 2. True Range Calculation +### 2. True Range True Range captures the full extent of price movement including gaps: $$ -TR_t = \max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|) +TR_t = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|) $$ -### 3. Average True Range (ATR) +### 3. Average True Range (RMA with Warmup Compensation) -ATR uses Wilder's smoothing (RMA) with warmup compensation: +ATR uses Wilder's smoothing (RMA) with a warmup compensator to eliminate cold-start bias: $$ -ATR_t = \frac{ATR_{t-1} \times (n-1) + TR_t}{n} -$$ - -### 4. Channel Bands - -Upper and lower bands are placed at a configurable ATR multiple: - -$$ -\text{Upper}_t = \text{Middle}_t + k \times ATR_t +\text{raw\_rma}_t = \frac{\text{raw\_rma}_{t-1} \cdot (n - 1) + TR_t}{n} $$ $$ -\text{Lower}_t = \text{Middle}_t - k \times ATR_t +e_t = (1 - \alpha) \cdot e_{t-1}, \quad \alpha = \frac{1}{n} +$$ + +$$ +ATR_t = \begin{cases} \text{raw\_rma}_t \;/\; (1 - e_t) & \text{if } e_t > \epsilon \\ \text{raw\_rma}_t & \text{otherwise} \end{cases} +$$ + +The compensator $e_t$ converges to zero as bars accumulate, removing the initialization bias that would otherwise undercount early ATR values. + +### 4. Band Construction + +$$ +U_t = \text{Middle}_t + k \cdot ATR_t +$$ + +$$ +L_t = \text{Middle}_t - k \cdot ATR_t $$ where $k$ is the multiplier (default 2.0). -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, Scalar) +Streaming: $O(1)$ per bar. The SMA uses a running sum with circular buffer (add new, subtract oldest). The RMA is a single-pole IIR filter. Memory: one circular buffer of $n$ floats for SMA, plus scalar state for RMA/compensator. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 6 | 1 | 6 | -| MUL | 3 | 3 | 9 | -| DIV | 2 | 15 | 30 | -| CMP/ABS/MAX | 3 | 1 | 3 | -| **Total** | **14** | — | **~48 cycles** | +## Mathematical Foundation -**Complexity:** O(1) per bar for streaming updates using running sums. +### Parameters -### Batch Mode (SIMD) +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 20 | $> 0$ | SMA and ATR lookback period | +| $k$ | multiplier | 2.0 | $> 0$ | ATR multiplier for band width | +| $n_{\text{atr}}$ | atr_length | 0 | $\geq 0$ | Separate ATR period (0 = same as SMA period) | -| Operation | Scalar Ops | SIMD Benefit | Notes | -| :--- | :---: | :---: | :--- | -| True Range | 3N | 8× | Vectorizable | -| ATR (Wilder) | N | 1× | Sequential dependency | -| SMA running sum | N | 1× | Sequential | -| Band calculation | 4N | 8× | Vectorizable | +### Pseudo-code -### Quality Metrics +``` +function starchannel(source[], high[], low[], close[], period, multiplier, atr_length): + effective_atr = atr_length > 0 ? atr_length : period + alpha = 1.0 / effective_atr -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 8/10 | ATR provides accurate volatility measure | -| **Timeliness** | 6/10 | SMA lag + ATR smoothing delay | -| **Overshoot** | 7/10 | Bands lag during volatility spikes | -| **Smoothness** | 8/10 | SMA centerline provides smooth reference | + buf = circular_buffer(period) + sum = 0.0 + count = 0 -## Validation + raw_rma = 0.0 + e = 1.0 // warmup compensator + prevClose = close[0] + EPSILON = 1e-10 -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | Not directly available | -| **Skender** | N/A | Not directly available | -| **Tulip** | N/A | Not directly available | -| **TradingView** | ✅ | Matches PineScript implementation | -| **Manual** | ✅ | Verified against hand calculations | + for each bar t: + // SMA via running sum + if buf.is_full: + sum -= buf.oldest + count -= 1 + buf.add(source[t]) + sum += source[t] + count += 1 + middle = sum / count -## Usage & Pitfalls + // True Range + tr = max(high[t] - low[t], + abs(high[t] - prevClose), + abs(low[t] - prevClose)) + prevClose = close[t] -- **Lagging nature:** As a moving average-based indicator incorporating ATR, the channel reacts to volatility changes with some delay. -- **Parameter sensitivity:** Performance varies significantly based on period and multiplier settings, requiring optimization for specific securities. -- **False signals in trending markets:** Channel touches may not indicate reversals during strong trends, potentially leading to premature position exits. -- **Complementary tool requirement:** Most effective when combined with trend identification and momentum indicators. -- **Volatility regime changes:** During sudden extreme volatility spikes, channel may widen with a delay. -- **Lookback period trade-offs:** Shorter periods increase responsiveness but also noise; longer periods provide stability but increase lag. -- **Gap handling:** While ATR accounts for gaps, sudden large gaps can temporarily distort channel calculations. + // RMA with warmup compensator + raw_rma = (raw_rma * (effective_atr - 1) + tr) / effective_atr + e = (1 - alpha) * e + atr = e > EPSILON ? raw_rma / (1 - e) : raw_rma -## API + // Bands + width = atr * multiplier + upper = middle + width + lower = middle - width -```mermaid -classDiagram - class Starchannel { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Lower - +bool IsHot - +Starchannel(int period, double multiplier) - +Starchannel(TBarSeries source, int period, double multiplier) - +TValue Update(TBar input, bool isNew) - +Tuple~TSeries,TSeries,TSeries~ Update(TBarSeries source) - +void Prime(TBarSeries source) - +void Reset() - +static void Batch(ReadOnlySpan~double~ high, ReadOnlySpan~double~ low, ReadOnlySpan~double~ close, Span~double~ middle, Span~double~ upper, Span~double~ lower, int period, double multiplier) - +static Tuple~TSeries,TSeries,TSeries~ Batch(TBarSeries source, int period, double multiplier) - +static Tuple~Tuple~TSeries,TSeries,TSeries~,Starchannel~ Calculate(TBarSeries source, int period, double multiplier) - } + emit (middle, upper, lower) ``` -### Class: `Starchannel` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `≥1` | Lookback period for both SMA and ATR calculations. | -| `multiplier` | `double` | `2.0` | `>0` | ATR multiplier for band width. | +| Output | Interpretation | +|--------|---------------| +| Band width expanding | ATR rising; volatility increasing | +| Band width contracting | ATR falling; volatility decreasing | +| Price at upper band | Overextended above SMA by ATR measure | +| Price at lower band | Overextended below SMA by ATR measure | +| Middle band slope positive | SMA trending upward | -### Properties +## Resources -- `Last` (`TValue`): The current SMA value (middle line). -- `Upper` (`TValue`): The upper band (SMA + multiplier × ATR). -- `Lower` (`TValue`): The lower band (SMA - multiplier × ATR). -- `IsHot` (`bool`): Returns `true` when 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 (Middle, 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, double multiplier)`: Static factory that returns results and indicator instance. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var starchannel = new Starchannel(period: 20, multiplier: 2.0); - -// Update Loop -foreach (var bar in quotes) -{ - starchannel.Update(bar, isNew: true); - - // Use valid results - if (starchannel.IsHot) - { - Console.WriteLine($"{bar.Time}: Mid={starchannel.Last.Value:F2}, Upper={starchannel.Upper.Value:F2}, Lower={starchannel.Lower.Value:F2}"); - } -} -``` - -## References - -- Stoller, M. (1980s). Development of the Stoller Average Range Channel concept. +- Stoller, M. (1980s). Development of the Stoller Average Range Channel. - Wilder, J. W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. -- Kaufman, P. J. (2013). *Trading Systems and Methods*, 5th ed. John Wiley & Sons. -- Murphy, J. J. (1999). *Technical Analysis of the Financial Markets*. New York Institute of Finance. +- Kaufman, P. (2013). *Trading Systems and Methods*, 5th ed. Wiley. diff --git a/lib/channels/stbands/stbands.md b/lib/channels/stbands/stbands.md index 8c299c8a..c1d88cd6 100644 --- a/lib/channels/stbands/stbands.md +++ b/lib/channels/stbands/stbands.md @@ -1,78 +1,73 @@ # STBANDS: Super Trend Bands -> "The best trailing stop is one that only moves when the market agrees with you." - -Super Trend Bands provide ATR-based dynamic support and resistance levels that adapt to price action. Unlike static channels, these bands only tighten in the direction of the current trend—upper bands only move down during downtrends, lower bands only move up during uptrends—creating natural trailing stop-loss levels that respect market momentum. +Super Trend Bands provide ATR-based dynamic support and resistance levels with asymmetric ratchet logic: the upper band only tightens downward during downtrends, and the lower band only tightens upward during uptrends. This creates natural trailing stop-loss levels that respect market momentum. A trend direction signal ($+1$ or $-1$) flips when price breaches the opposite band. The ATR is computed as a simple moving average of True Range via a ring buffer with running sum, providing O(1) streaming updates. ## Historical Context -The SuperTrend indicator emerged from the trading community's need for a volatility-adaptive trend-following tool. Olivier Seban popularized the concept, building on Wilder's ATR foundation to create bands that respect trend direction rather than blindly following price. +The SuperTrend indicator emerged from the trading community's need for a volatility-adaptive trend-following tool. Olivier Seban popularized the concept, building on Wilder's ATR foundation to create bands that respect trend direction rather than blindly following price symmetrically. -Traditional channel indicators like Bollinger Bands expand and contract symmetrically around price. SuperTrend takes a different approach: once a band establishes a level favorable to the trend, it refuses to retreat. This asymmetric behavior creates the "ratchet effect" that makes it useful for trailing stops. +Traditional channel indicators like Bollinger Bands expand and contract symmetrically around price. SuperTrend takes a different approach: once a band establishes a level favorable to the trend, it refuses to retreat. This asymmetric "ratchet effect" means the lower band in an uptrend can only rise (never fall), creating a progressively tighter trailing stop. The band resets only when price violates it, triggering a trend reversal. -The implementation here follows the canonical PineScript algorithm, using a simple moving average of True Range rather than Wilder's smoothed ATR, which produces slightly more responsive bands. +The implementation here follows the canonical PineScript algorithm, using a simple moving average of True Range rather than Wilder's exponentially smoothed ATR. This produces slightly more responsive bands since the SMA gives equal weight to all TR values in the window, while Wilder's RMA carries heavier memory of older values. ## Architecture & Physics -### 1. True Range Calculation +### 1. True Range True Range captures the full extent of price movement including gaps: $$ -TR_t = \max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|) +TR_t = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|) $$ -where: -- $H_t$ = current high -- $L_t$ = current low -- $C_{t-1}$ = previous close +### 2. Average True Range (SMA of TR) -### 2. Average True Range (ATR) - -The implementation uses a simple moving average of TR over the period: +The implementation uses a simple moving average of TR via ring buffer with running sum: $$ -ATR_t = \frac{1}{n}\sum_{i=0}^{n-1} TR_{t-i} +ATR_t = \frac{1}{\min(t+1, n)} \sum_{i=0}^{\min(t,n-1)} TR_{t-i} $$ -A ring buffer with running sum provides O(1) updates. +The running sum provides $O(1)$ updates: subtract the oldest TR leaving the window, add the new TR. ### 3. Basic Band Calculation -Bands center on the HL2 (typical price midpoint): +Bands center on HL2 (the bar midpoint): $$ \text{HL2}_t = \frac{H_t + L_t}{2} $$ $$ -\text{BasicUpper}_t = \text{HL2}_t + (k \times ATR_t) +\text{BasicUpper}_t = \text{HL2}_t + k \cdot ATR_t $$ $$ -\text{BasicLower}_t = \text{HL2}_t - (k \times ATR_t) +\text{BasicLower}_t = \text{HL2}_t - k \cdot ATR_t $$ -where $k$ = multiplier (default 3.0) +where $k$ is the multiplier (default 3.0). ### 4. Ratchet Logic (Final Bands) -The defining characteristic—bands only move in the favorable direction: +The defining characteristic. Bands only move in the trend-favorable direction: $$ \text{Upper}_t = \begin{cases} -\text{BasicUpper}_t & \text{if } \text{BasicUpper}_t < \text{Upper}_{t-1} \text{ OR } C_{t-1} > \text{Upper}_{t-1} \\ +\text{BasicUpper}_t & \text{if } \text{BasicUpper}_t < \text{Upper}_{t-1} \;\text{OR}\; C_{t-1} > \text{Upper}_{t-1} \\ \text{Upper}_{t-1} & \text{otherwise} \end{cases} $$ $$ \text{Lower}_t = \begin{cases} -\text{BasicLower}_t & \text{if } \text{BasicLower}_t > \text{Lower}_{t-1} \text{ OR } C_{t-1} < \text{Lower}_{t-1} \\ +\text{BasicLower}_t & \text{if } \text{BasicLower}_t > \text{Lower}_{t-1} \;\text{OR}\; C_{t-1} < \text{Lower}_{t-1} \\ \text{Lower}_{t-1} & \text{otherwise} \end{cases} $$ +In words: the upper band adopts the new (lower) basic value only if it tightens, or if price already broke above the previous upper band (resetting it). The lower band rises only if the new basic value is higher, or if price already broke below the previous lower band. + ### 5. Trend Determination Trend flips when price breaches the opposite band: @@ -85,168 +80,98 @@ $$ \end{cases} $$ +$+1$ = bullish (price is above the lower band trailing stop), $-1$ = bearish (price is below the upper band trailing stop). + +### 6. Complexity + +Streaming: $O(1)$ per bar. The TR running sum uses a ring buffer; the ratchet logic and trend determination are constant-time comparisons. Memory: one ring buffer of $n$ floats plus scalar state for previous bands, trend, and close. + ## Mathematical Foundation -### ATR Ring Buffer Implementation +### Parameters -The running sum approach avoids O(n) recalculation: +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 10 | $> 0$ | ATR lookback period | +| $k$ | multiplier | 3.0 | $> 0$ | ATR multiplier for band distance from HL2 | + +### Pseudo-code ``` -On new bar: - if buffer.IsFull: - trSum -= buffer.Oldest - trSum += newTR - buffer.Add(newTR) - ATR = trSum / buffer.Count +function stbands(high[], low[], close[], period, multiplier): + tr_buf = ring_buffer(period) + tr_sum = 0, count = 0 + prev_close = close[0] + final_upper = NaN, final_lower = NaN + trend = +1 + + for each bar t: + h = high[t], l = low[t], c = close[t] + + // True Range + tr = max(h - l, abs(h - prev_close), abs(l - prev_close)) + + // ATR via running sum ring buffer + if tr_buf.is_full: + tr_sum -= tr_buf.oldest + count -= 1 + tr_buf.add(tr) + tr_sum += tr + count += 1 + atr = tr_sum / count + + // Basic bands centered on HL2 + hl2 = (h + l) / 2 + basic_upper = hl2 + multiplier * atr + basic_lower = hl2 - multiplier * atr + + if t == 0: + final_upper = basic_upper + final_lower = basic_lower + trend = +1 + else: + // Ratchet: upper only tightens or resets on breakout + if basic_upper < final_upper OR prev_close > final_upper: + final_upper = basic_upper + // otherwise hold + + // Ratchet: lower only tightens or resets on breakdown + if basic_lower > final_lower OR prev_close < final_lower: + final_lower = basic_lower + // otherwise hold + + // Trend flip + if c <= final_lower: + trend = +1 + else if c >= final_upper: + trend = -1 + // otherwise hold previous trend + + prev_close = c + emit (final_upper, final_lower, trend) ``` ### Band State Transitions -The ratchet logic creates four possible state transitions per bar: - -| Condition | Upper Band Action | Lower Band Action | -|:----------|:------------------|:------------------| +| Condition | Upper Band | Lower Band | +|-----------|-----------|------------| | Uptrend, price rising | Holds | Rises (tightens) | -| Uptrend, price falling | May drop if breaks | Holds | +| Uptrend, price breaks upper | Resets to basic | Holds | | Downtrend, price falling | Drops (tightens) | Holds | -| Downtrend, price rising | Holds | May rise if breaks | +| Downtrend, price breaks lower | Holds | Resets to basic | -## Performance Profile +### Output Interpretation -### Operation Count (Streaming Mode, Scalar) +| Output | Interpretation | +|--------|---------------| +| Trend = $+1$ | Bullish; lower band acts as trailing stop | +| Trend = $-1$ | Bearish; upper band acts as trailing stop | +| Trend flip $+1 \to -1$ | Bearish reversal; price breached upper band | +| Trend flip $-1 \to +1$ | Bullish reversal; price breached lower band | +| Band width contracting | ATR falling; volatility decreasing | -| Operation | Count | Cost (cycles) | Subtotal | -|:----------|:-----:|:-------------:|:--------:| -| ADD/SUB | 8 | 1 | 8 | -| MUL | 2 | 3 | 6 | -| DIV | 2 | 15 | 30 | -| CMP/MAX | 6 | 1 | 6 | -| ABS | 2 | 1 | 2 | -| **Total** | **20** | — | **~52 cycles** | +## Resources -The dominant cost is the two divisions (ATR calculation and HL2 normalization). - -### Batch Mode (SIMD) - -The recursive nature of the ratchet logic limits SIMD vectorization. However, the TR calculation across multiple bars can be parallelized: - -| Operation | Scalar Ops | SIMD Ops (AVX2) | Speedup | -|:----------|:----------:|:---------------:|:-------:| -| TR calculation | 3N | 3N/8 | 8× | -| ATR (running sum) | N | N | 1× | -| Band ratchet | 4N | 4N | 1× | - -**Per-bar improvement with SIMD:** ~15% for TR calculation only. - -### Quality Metrics - -| Metric | Score | Notes | -|:------:|:-----:|:------| -| **Accuracy** | 9/10 | Matches PineScript reference exactly | -| **Timeliness** | 8/10 | Responds within ATR period | -| **Overshoot** | 9/10 | Ratchet prevents adverse movement | -| **Smoothness** | 7/10 | ATR averaging provides moderate smoothing | -| **Memory** | 10/10 | O(period) ring buffer only | - -## Validation - -| Library | Status | Notes | -|:--------|:------:|:------| -| **TA-Lib** | N/A | Not implemented | -| **Skender** | N/A | SuperTrend available but different algorithm | -| **Tulip** | N/A | Not implemented | -| **Ooples** | N/A | Not implemented | -| **TradingView/PineScript** | ✅ | Reference implementation matched | - -## Usage & Pitfalls - -- **Warmup Period**: The indicator requires `period` bars before ATR stabilizes. During warmup, bands may appear wider than expected as the TR sample size grows. -- **Multiplier Sensitivity**: Default multiplier of 3.0 works well for daily data. Intraday charts often benefit from 2.0-2.5 to avoid bands too far from price. -- **Gap Handling**: Large overnight gaps can cause TR spikes that persist in the ATR for `period` bars, temporarily widening bands. -- **Trend Initialization**: First bar always initializes to trend = +1 (bullish). This matches PineScript behavior but may not reflect actual market state. -- **Bar Correction (isNew=false)**: When updating the same bar multiple times (intra-bar updates), the indicator properly rolls back state. Failing to set `isNew=false` for corrections will advance the indicator incorrectly. -- **NaN/Infinity Handling**: Non-finite OHLC values are replaced with the last valid close. This prevents NaN propagation but may mask data quality issues. - -## API - -```mermaid -classDiagram - class Stbands { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Lower - +TValue Trend - +TValue Width - +bool IsHot - +Stbands(int period, double multiplier) - +TValue Update(TBar input, bool isNew) - +TValue Update(TValue input, bool isNew) - +TSeries Update(TBarSeries source) - +TSeries Update(TSeries source) - +void Reset() - +void Prime(ReadOnlySpan~double~ source, TimeSpan? step) - +static TSeries Calculate(TBarSeries source, int period, double multiplier) - +static void Calculate(ReadOnlySpan~double~ high, ReadOnlySpan~double~ low, ReadOnlySpan~double~ close, Span~double~ upper, Span~double~ lower, Span~double~ trend, int period, double multiplier) - } -``` - -### Class: `Stbands` - -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `10` | `≥1` | Lookback period for ATR calculation. | -| `multiplier` | `double` | `3.0` | `>0.001` | ATR multiplier for band distance from HL2. | - -### Properties - -- `Last` (`TValue`): Returns the trend-appropriate band (Lower when bullish, Upper when bearish). -- `Upper` (`TValue`): The upper band (resistance level). -- `Lower` (`TValue`): The lower band (support level). -- `Trend` (`TValue`): Trend direction: +1 = bullish, -1 = bearish. -- `Width` (`TValue`): Band width (Upper - Lower). -- `IsHot` (`bool`): Returns `true` when warmup period is complete. - -### Methods - -- `Update(TBar input, bool isNew)`: Updates the indicator with a new bar and returns the result. -- `Update(TValue input, bool isNew)`: Updates with a single value (treats as O=H=L=C). -- `Update(TBarSeries source)`: Processes an entire bar series and returns TSeries. -- `Reset()`: Resets the indicator to its initial state. -- `Prime(ReadOnlySpan source, TimeSpan? step)`: Initializes from span data. -- `Calculate(TBarSeries source, int period, double multiplier)`: Static factory method. -- `Calculate(...)`: Static span-based calculation for zero-allocation processing. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var stbands = new Stbands(period: 10, multiplier: 3.0); - -// Update Loop -foreach (var bar in quotes) -{ - stbands.Update(bar, isNew: true); - - // Use valid results - if (stbands.IsHot) - { - double support = stbands.Lower.Value; - double resistance = stbands.Upper.Value; - int trend = (int)stbands.Trend.Value; - - // Use trend-appropriate band as trailing stop - double trailingStop = trend > 0 ? support : resistance; - Console.WriteLine($"{bar.Time}: Stop={trailingStop:F2}, Trend={trend}"); - } -} -``` - -## References - -- Seban, O. "SuperTrend Indicator." Trading methodology documentation. -- Wilder, J. W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. (ATR foundation) -- TradingView. "SuperTrend." Pine Script Reference. https://www.tradingview.com/wiki/SuperTrend \ No newline at end of file +- Seban, O. SuperTrend Indicator methodology. +- Wilder, J. W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. +- TradingView. "SuperTrend." Pine Script Reference. diff --git a/lib/channels/ttm_lrc/TtmLrc.md b/lib/channels/ttm_lrc/TtmLrc.md index 23922de7..6abb8ccd 100644 --- a/lib/channels/ttm_lrc/TtmLrc.md +++ b/lib/channels/ttm_lrc/TtmLrc.md @@ -1,76 +1,136 @@ # TTM_LRC: TTM Linear Regression Channel -> **Pending Implementation** - Placeholder for John Carter's TTM LRC indicator +TTM Linear Regression Channel plots a least-squares regression line through price data with dual standard deviation bands at $\pm 1\sigma$ and $\pm 2\sigma$. Developed by John Carter as part of his TTM (Trade The Markets) indicator suite, it extends the standard regression channel by providing two band levels that create statistically meaningful trading zones. The algorithm is identical to REGCHANNEL/SDCHANNEL in its regression and residual computation, but uses a longer default period (100) and emits four bands instead of two. ## Historical Context -John Carter's TTM LRC (Linear Regression Channel) provides a clean, statistically-based price channel using linear regression analysis. Unlike Bollinger Bands which measure volatility around a moving average, LRC measures price deviation from the trend line, making it particularly useful for identifying overbought/oversold conditions within a defined trend. +John Carter developed the TTM LRC as part of his Trade The Markets methodology, popularized in his book *Mastering the Trade* (2005). Carter's approach combines linear regression channels with his TTM Squeeze indicator to identify high-probability setups: the regression channel defines the trend envelope while the squeeze identifies momentum compression within it. -## Algorithm +The underlying mathematics (OLS regression + population standard deviation of residuals) dates back to Gauss and Legendre in the early 1800s. Gilbert Raff applied regression channels to trading in the 1990s. Carter's contribution was the dual-band structure and the specific parameter choices (period 100, dual $\sigma$ levels) calibrated for swing trading on daily and intraday charts. + +The dual band structure creates five statistical zones. Under normality assumptions, 68% of price action falls within $\pm 1\sigma$ and 95% within $\pm 2\sigma$. Price reaching the $\pm 2\sigma$ band represents a statistically significant deviation from trend, while the $\pm 1\sigma$ bands define the boundaries of "normal" price oscillation. + +## Architecture & Physics + +### 1. Linear Regression (Midline) + +The best-fit line through the lookback window using ordinary least squares: + +$$ +m = \frac{n \sum x_i y_i - \sum x_i \sum y_i}{n \sum x_i^2 - \left(\sum x_i\right)^2} +$$ + +$$ +b = \frac{\sum y_i - m \sum x_i}{n} +$$ + +The midline is the regression evaluated at the current bar: + +$$ +\text{Mid}_t = m \cdot (n - 1) + b +$$ + +### 2. Residual Standard Deviation + +Population standard deviation of the differences between actual and predicted values: + +$$ +\sigma_t = \sqrt{\frac{1}{n} \sum_{i=0}^{n-1} \left(y_i - (m \cdot i + b)\right)^2} +$$ + +### 3. Dual Band Construction + +Four bands at two statistical distances: + +$$ +U_{1,t} = \text{Mid}_t + 1 \cdot \sigma_t, \qquad L_{1,t} = \text{Mid}_t - 1 \cdot \sigma_t +$$ + +$$ +U_{2,t} = \text{Mid}_t + k \cdot \sigma_t, \qquad L_{2,t} = \text{Mid}_t - k \cdot \sigma_t +$$ + +where $k$ is the outer deviation multiplier (default 2.0). The inner bands ($\pm 1\sigma$) are always at one standard deviation. + +### 4. Slope and R-Squared + +The slope $m$ indicates trend direction and strength. The coefficient of determination $R^2$ measures how well the linear model fits: + +$$ +R^2 = 1 - \frac{\text{SSR}}{\text{SST}} = 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2} +$$ + +High $R^2$ (near 1.0) indicates price is moving linearly; low $R^2$ indicates choppy or non-linear behavior. + +### 5. Complexity + +Per bar: $O(n)$ due to two loops over the window. Memory: a ring buffer of $n$ doubles. The index sums $\sum x$ and $\sum x^2$ are precomputed constants. + +## Mathematical Foundation + +### Parameters + +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 100 | $> 1$ | Lookback window for regression | +| $k$ | deviations | 2.0 | $> 0$ | Outer band stddev multiplier | + +### Pseudo-code -### Linear Regression Line ``` -// Least squares regression over N periods -slope = (N * ΣXY - ΣX * ΣY) / (N * ΣX² - (ΣX)²) -intercept = (ΣY - slope * ΣX) / N -midline = intercept + slope * (current_bar - start_bar) +function ttm_lrc(source[], period, deviations): + buf = ring_buffer(period) + sum_x = period * (period - 1) / 2 + sum_x2 = period * (period - 1) * (2 * period - 1) / 6 + denom = period * sum_x2 - sum_x * sum_x + + for each bar t: + buf.add(source[t]) + n = buf.count + + // pass 1: regression + sum_y = 0, sum_xy = 0 + for i = 0 to n-1: + y = buf[i] + sum_y += y + sum_xy += i * y + + slope = (n * sum_xy - sum_x * sum_y) / denom + intercept = (sum_y - slope * sum_x) / n + midline = slope * (n - 1) + intercept + + // pass 2: residuals + ssr = 0, sst = 0 + mean_y = sum_y / n + for i = 0 to n-1: + predicted = slope * i + intercept + residual = buf[i] - predicted + ssr += residual * residual + sst += (buf[i] - mean_y)^2 + + stddev = sqrt(ssr / n) + r_squared = sst > 0 ? 1 - ssr / sst : 0 + + upper1 = midline + 1.0 * stddev + lower1 = midline - 1.0 * stddev + upper2 = midline + deviations * stddev + lower2 = midline - deviations * stddev + + emit (midline, upper1, lower1, upper2, lower2, slope, r_squared) ``` -### Standard Deviation Bands -``` -residuals = close - linreg_value -stddev = sqrt(Σ(residuals²) / N) +### Statistical Zone Interpretation -upper_band_1 = midline + 1 * stddev -lower_band_1 = midline - 1 * stddev -upper_band_2 = midline + 2 * stddev -lower_band_2 = midline - 2 * stddev -``` +| Zone | Probability | Interpretation | +|------|------------|----------------| +| Above $+2\sigma$ | ~2.5% | Extremely overbought relative to trend | +| $+1\sigma$ to $+2\sigma$ | ~13.5% | Overbought | +| $-1\sigma$ to $+1\sigma$ | ~68% | Normal oscillation around trend | +| $-2\sigma$ to $-1\sigma$ | ~13.5% | Oversold | +| Below $-2\sigma$ | ~2.5% | Extremely oversold relative to trend | -## Default Parameters +## Resources -| Parameter | Value | Description | -|:----------|:------|:------------| -| Length | 100 | Regression lookback period | -| Deviations | 2.0 | Number of standard deviations for outer bands | -| ShowMidline | true | Display the regression line | -| ShowInnerBands | true | Display ±1σ bands | - -## Outputs - -| Output | Type | Description | -|:-------|:-----|:------------| -| Midline | double | Linear regression value (trend line) | -| Upper1 | double | +1 standard deviation band | -| Lower1 | double | -1 standard deviation band | -| Upper2 | double | +2 standard deviation band | -| Lower2 | double | -2 standard deviation band | -| Slope | double | Current regression slope (trend direction) | -| RSquared | double | Coefficient of determination (trend quality) | - -## Band Interpretation - -| Zone | Statistical Meaning | Trading Implication | -|:-----|:--------------------|:--------------------| -| Above +2σ | 2.5% probability | Extremely overbought | -| +1σ to +2σ | 13.5% probability | Overbought | -| -1σ to +1σ | 68% probability | Normal range | -| -2σ to -1σ | 13.5% probability | Oversold | -| Below -2σ | 2.5% probability | Extremely oversold | - -## Trading Strategy - -1. **Trend Following:** Trade in direction of slope when price bounces off midline -2. **Mean Reversion:** Fade moves to ±2σ bands when R² is high -3. **Breakout:** Watch for sustained moves beyond ±2σ as trend acceleration signals - -## Category - -**Channels** - Linear regression-based price channel with statistical deviation bands. - -## See Also - -- [REGCHANNEL: Linear Regression Channel](../regchannel/RegChannel.md) -- [SDCHANNEL: Standard Deviation Channel](../sdchannel/SdChannel.md) -- [BBANDS: Bollinger Bands](../bbands/Bbands.md) -- [TTM_SQUEEZE: TTM Squeeze](../../dynamics/ttm_squeeze/TtmSqueeze.md) +- Carter, J. (2005). *Mastering the Trade*. McGraw-Hill. +- Raff, G. (1991). "Trading the Regression Channel." *Technical Analysis of Stocks & Commodities*. +- Draper, N. & Smith, H. (1998). *Applied Regression Analysis*. Wiley. diff --git a/lib/channels/ubands/ubands.md b/lib/channels/ubands/ubands.md index 8432d07e..cb4b8f81 100644 --- a/lib/channels/ubands/ubands.md +++ b/lib/channels/ubands/ubands.md @@ -1,294 +1,153 @@ # UBANDS: Ehlers Ultimate Bands -> "The best filters are those that eliminate the noise while preserving the signal. The Ultrasmooth Filter does this with remarkable precision, making it the ideal foundation for volatility bands." - -Ehlers Ultimate Bands (UBANDS) represent John Ehlers' 2024 evolution of volatility-based channel indicators, replacing the conventional SMA foundation with his Ultrasmooth Filter (USF)—a 2-pole IIR filter with exceptional noise rejection and zero-lag properties. The bands are defined by the RMS (Root Mean Square) of residuals between price and the smooth, providing a mathematically rigorous measure of deviation that adapts to actual price behavior rather than assuming normal distributions. +Ehlers Ultimate Bands replace the conventional SMA foundation of Bollinger Bands with the Ultrasmooth Filter (USF), a 2-pole IIR filter with zero overshoot and minimal lag. Band width is determined by the RMS (Root Mean Square) of residuals between price and the smoothed centerline, providing a mathematically rigorous deviation measure that makes no assumptions about the distribution of returns. The USF is a recursive filter requiring O(1) computation per bar, while the RMS calculation scans the lookback window at O(n) per bar. ## Historical Context -John F. Ehlers introduced the Ultimate Bands in 2024 as part of his ongoing research into digital signal processing applied to financial markets. Unlike Bollinger Bands (which use SMA + standard deviation), Ultimate Bands leverage the Ultrasmooth Filter—a filter Ehlers developed to achieve superior smoothing with minimal lag. +John F. Ehlers introduced Ultimate Bands in 2024 as part of his ongoing research into digital signal processing applied to financial markets. Ehlers' career spans decades of applying engineering concepts (particularly from electrical and mechanical engineering) to trading indicator design. -The key insight behind Ultimate Bands is that traditional standard deviation measures assume stationarity and normality—assumptions that financial time series routinely violate. By instead measuring the RMS of the actual residuals (the difference between price and the smoothed value), the bands adapt to whatever distribution the market presents, making no assumptions about the shape of returns. +The key insight behind Ultimate Bands: traditional standard deviation measures assume stationarity and normality, assumptions that financial time series routinely violate. By measuring the RMS of actual residuals (the difference between price and the USF-smoothed value), the bands adapt to whatever distribution the market presents. RMS is the natural measure of dispersion around zero; since the residuals are already centered on the smooth, RMS is the mathematically correct choice. -The Ultrasmooth Filter itself is derived from Ehlers' work on maximally flat filters. Its 2-pole IIR design achieves: - -- **Zero overshoot**: Unlike many smoothing filters that ring or overshoot on sharp moves -- **Minimal lag**: Better than SMA of equivalent smoothness -- **Excellent noise rejection**: Superior high-frequency attenuation - -This implementation faithfully reproduces Ehlers' published formula while adding production-grade features: NaN handling, bar correction support, and multiple calculation modes (streaming, batch, span). +The Ultrasmooth Filter itself is derived from Ehlers' work on maximally flat filters. Its 2-pole IIR design achieves zero overshoot (unlike many smoothing filters that ring on sharp price moves), minimal lag compared to SMA of equivalent smoothness, and excellent high-frequency noise rejection with 12 dB/octave rolloff. ## Architecture & Physics -Ultimate Bands consist of three components with distinct mathematical foundations: +### 1. Ultrasmooth Filter Coefficients -### 1. Middle Band (Ehlers Ultrasmooth Filter) - -The foundation is a 2-pole IIR filter with carefully chosen coefficients: +The USF coefficients are derived from the period parameter $n$: $$ -\text{arg} = \frac{\sqrt{2} \cdot \pi}{n} +\text{arg} = \frac{\sqrt{2}\,\pi}{n} $$ $$ -c_2 = 2 \cdot e^{-\text{arg}} \cdot \cos(\text{arg}) +c_2 = 2\,e^{-\text{arg}} \cos(\text{arg}) $$ $$ -c_3 = -e^{-2 \cdot \text{arg}} +c_3 = -e^{-2\,\text{arg}} $$ $$ c_1 = \frac{1 + c_2 - c_3}{4} $$ -The filter recursion: +### 2. USF Recursion (Middle Band) + +The filter processes input prices $P_t$ through a 2-pole IIR structure: $$ -\text{USF}_t = (1 - c_1) \cdot P_t + (2c_1 - c_2) \cdot P_{t-1} - (c_1 + c_3) \cdot P_{t-2} + c_2 \cdot \text{USF}_{t-1} + c_3 \cdot \text{USF}_{t-2} +\text{USF}_t = (1 - c_1)\,P_t + (2c_1 - c_2)\,P_{t-1} - (c_1 + c_3)\,P_{t-2} + c_2\,\text{USF}_{t-1} + c_3\,\text{USF}_{t-2} $$ -where $P_t$ is the input price and $n$ is the period parameter. +During the first few bars (before sufficient history exists), the filter initializes directly to the input value. -**Implementation note:** We precompute the coefficients $k_0 = 1 - c_1$, $k_1 = 2c_1 - c_2$, and $k_2 = -(c_1 + c_3)$ for FMA optimization, reducing the hot path to four fused multiply-add operations. +### 3. Residuals and RMS -### 2. Residual Calculation - -The residual measures the deviation between price and the smooth: +The residual captures the high-frequency component rejected by the filter: $$ r_t = P_t - \text{USF}_t $$ -This captures the "noise" component that the filter rejected—the very component that defines volatility in Ehlers' framework. - -### 3. RMS-Based Bands - -Unlike standard deviation (which requires mean subtraction), RMS operates directly on the residuals: +The RMS over the lookback window: $$ -\text{RMS}_t = \sqrt{\frac{1}{n} \sum_{i=t-n+1}^{t} r_i^2} +\text{RMS}_t = \sqrt{\frac{1}{n} \sum_{i=0}^{n-1} r_{t-i}^2} $$ -The bands then extend symmetrically: +### 4. Band Construction $$ -\text{Upper}_t = \text{USF}_t + k \cdot \text{RMS}_t +U_t = \text{USF}_t + k \cdot \text{RMS}_t $$ $$ -\text{Lower}_t = \text{USF}_t - k \cdot \text{RMS}_t +L_t = \text{USF}_t - k \cdot \text{RMS}_t $$ -where $k$ is the multiplier parameter (default 1.0). +where $k$ is the multiplier (default 1.0). Note the default is 1.0 (not 2.0 as in Bollinger Bands), because RMS of residuals from the USF is typically larger than population standard deviation from an SMA. -**Why RMS instead of StdDev?** Standard deviation measures dispersion around the mean; RMS measures dispersion around zero. Since our residuals are already deviations from the smooth (which serves as our "center"), RMS is the mathematically correct measure. For residuals with zero mean, RMS equals StdDev—but RMS is computationally cheaper (no mean calculation) and more robust when residuals have non-zero drift. +### 5. Complexity + +The USF recursion is $O(1)$ per bar (four multiply-adds). The RMS calculation scans $n$ residuals per bar, yielding $O(n)$ total. Memory: two scalar states for USF history plus a buffer of $n$ squared residuals. ## Mathematical Foundation +### Parameters + +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n$ | period | 20 | $\geq 1$ | USF smoothing and RMS lookback period | +| $k$ | multiplier | 1.0 | $> 0$ | RMS multiplier for band width | + ### USF Transfer Function -In the z-domain, the Ultrasmooth Filter has transfer function: +In the z-domain: $$ -H(z) = \frac{k_0 + k_1 z^{-1} + k_2 z^{-2}}{1 - c_2 z^{-1} - c_3 z^{-2}} +H(z) = \frac{(1 - c_1) + (2c_1 - c_2)\,z^{-1} - (c_1 + c_3)\,z^{-2}}{1 - c_2\,z^{-1} - c_3\,z^{-2}} $$ -This reveals the 2-pole structure (denominator roots determine filter characteristics) with a feedforward numerator that shapes the passband. +Cutoff frequency: approximately $f_c \approx 1/(2\pi n)$ cycles per bar. Rolloff: 12 dB/octave. -**Frequency response characteristics:** +### Pseudo-code -- Cutoff frequency: approximately $f_c = 1/(2\pi n)$ cycles per bar -- Rolloff: 12 dB/octave (characteristic of 2-pole filters) -- Phase delay: minimal compared to SMA of equivalent smoothness +``` +function ubands(source[], period, multiplier): + // precompute USF coefficients + arg = sqrt(2) * pi / period + c2 = 2 * exp(-arg) * cos(arg) + c3 = -exp(-2 * arg) + c1 = (1 + c2 - c3) / 4 -### RMS Running Calculation + usf_prev1 = NaN, usf_prev2 = NaN -For streaming mode, we maintain a ring buffer of squared residuals: + for each bar t: + s0 = source[t] + s1 = source[t-1] // or s0 if unavailable + s2 = source[t-2] // or s1 if unavailable -$$ -\text{SumSq}_t = \sum_{i=t-n+1}^{t} r_i^2 -$$ + if usf not initialized: + usf = s0 + else: + usf = (1 - c1)*s0 + (2*c1 - c2)*s1 + - (c1 + c3)*s2 + c2*usf_prev1 + c3*usf_prev2 -$$ -\text{RMS}_t = \sqrt{\frac{\text{SumSq}_t}{n}} -$$ + usf_prev2 = usf_prev1 + usf_prev1 = usf -The ring buffer enables O(1) updates: subtract the outgoing squared residual, add the incoming one. + // RMS of residuals over window + sum_sq = 0, count = 0 + for i = 0 to period-1: + r = source[t-i] - usf_at[t-i] // residual at bar t-i + if r is valid: + sum_sq += r * r + count += 1 -### Bar Correction Protocol + rms = count > 0 ? sqrt(sum_sq / count) : 0 + upper = usf + multiplier * rms + lower = usf - multiplier * rms -The `isNew` parameter controls whether updates advance history or modify in-place: - -- `isNew = true`: Save current state to `_p_state`, advance counters, incorporate new data -- `isNew = false`: Restore `_p_state`, recalculate without advancing - -Both the USF state (previous filter outputs and inputs) and the RingBuffer support this protocol, enabling accurate intrabar updates. - -## Performance Profile - -### Operation Count (Streaming Mode, Scalar) - -Per bar update: - -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| FMA (USF) | 4 | 4 | 16 | -| SUB (residual) | 1 | 1 | 1 | -| MUL (squared) | 1 | 3 | 3 | -| RingBuffer update | 1 | ~5 | 5 | -| DIV (RMS avg) | 1 | 15 | 15 | -| SQRT (RMS) | 1 | 15 | 15 | -| MUL (offset) | 1 | 3 | 3 | -| ADD/SUB (bands) | 2 | 1 | 2 | -| **Total** | **~13 ops** | — | **~60 cycles** | - -The dominant costs are DIV and SQRT for RMS calculation (~50% of total). The USF calculation is highly efficient thanks to FMA optimization. - -### Batch Mode (512 values, SIMD/FMA) - -The span-based `Calculate` method processes 512 bars: - -**USF is inherently sequential** (IIR recursion), so no SIMD benefit for the filter itself. However, FMA provides ~20% speedup over separate MUL+ADD. - -| Operation | Scalar Ops | FMA Benefit | Speedup | -| :--- | :---: | :---: | :---: | -| USF recursion | 4 MUL + 4 ADD | 4 FMA | ~20% | -| Residual squared | 512 MUL | — | 1× | -| RMS calculation | 512 DIV + 512 SQRT | — | 1× | - -**Per-bar savings with FMA:** - -| Optimization | Cycles Saved | New Total | -| :--- | :---: | :---: | -| FMA for USF | ~4 | ~56 cycles | -| **Total savings** | **~7%** | **~56 cycles** | - -**Batch efficiency (512 bars):** - -| Mode | Cycles/bar | Total (512 bars) | Overhead | -| :--- | :---: | :---: | :---: | -| Scalar streaming | 60 | 30,720 | — | -| FMA streaming | 56 | 28,672 | -7% | -| **Improvement** | **7%** | **2,048 saved** | — | - -The modest improvement reflects the IIR nature of USF—recursion blocks parallelization. The value of this indicator lies in its mathematical properties (zero lag, RMS bands), not raw computational speed. - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Matches PineScript reference implementation exactly | -| **Timeliness** | 9/10 | USF provides near-zero lag; far superior to SMA-based bands | -| **Overshoot** | 10/10 | USF is designed for zero overshoot; bands follow price cleanly | -| **Smoothness** | 9/10 | Excellent noise rejection; RMS bands are less jittery than StdDev | -| **Adaptability** | 9/10 | RMS responds to actual residuals, not assumed distributions | - -## Validation - -This implementation has been validated against the PineScript reference: - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **PineScript (ubands.pine)** | ✅ | Reference implementation; exact match | -| **TA-Lib** | N/A | Not implemented | -| **Skender** | N/A | Not implemented | -| **Tulip** | N/A | Not implemented | -| **Ooples** | N/A | Not implemented | - -**Validation scope:** - -- **Streaming mode:** Incremental updates via `Update(TValue, isNew)` -- **Batch mode:** TSeries-based calculation via `Update(TSeries)` -- **Span mode:** Direct span-to-span calculation via `Calculate(ReadOnlySpan, Span, Span, Span)` -- **Consistency check:** All three modes produce identical results -- **Middle band verification:** Matches standalone USF implementation exactly - -**Note:** As a proprietary Ehlers indicator (2024), Ultimate Bands are not yet implemented in common open-source libraries. Our validation relies on the PineScript reference and mathematical verification against the USF filter implementation. - -## Usage & Pitfalls - -- **Warmup Period Awareness**: UBANDS requires $n$ bars before the USF stabilizes and RMS buffer fills. For $n=20$, the first 19 bars produce valid but not fully "hot" output. Always check `IsHot` in production. -- **Multiplier Interpretation**: The default multiplier is 1.0 (not 2.0 like Bollinger Bands). RMS of residuals is typically larger than standard deviation of prices. -- **IIR Filter Initialization**: The USF requires several bars to "spin up." During the first 3 bars, we return the input value directly (no filtering). -- **Computational Cost (IIR vs FIR)**: Unlike FIR filters (SMA, WMA), the USF cannot be parallelized due to its recursive nature. Each output depends on previous outputs. -- **Memory Footprint**: Each UBANDS instance maintains USF state (32 bytes), RingBuffer ($8n$ bytes), and metadata (~100 bytes). For $n=20$: ~292 bytes/instance. -- **Zero Volatility Edge Case**: When all residuals are zero, RMS = 0 and bands collapse to the middle line. The `Width` output makes this condition explicit. -- **isNew Parameter**: Critical for bar correction. Use `isNew=true` for new bars, `isNew=false` when updating the current bar. - -## API - -```mermaid -classDiagram - class Ubands { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Middle - +TValue Lower - +TValue Width - +bool IsHot - +Ubands(int period, double multiplier) - +TValue Update(TValue input, bool isNew) - +TSeries Update(TSeries source) - +void Reset() - +void Prime(ReadOnlySpan~double~ source, TimeSpan? step) - +static TSeries Calculate(TSeries source, int period, double multiplier) - +static void Calculate(ReadOnlySpan~double~ source, Span~double~ upper, Span~double~ middle, Span~double~ lower, int period, double multiplier) - } + emit (upper, usf, lower) ``` -### Class: `Ubands` +### RMS vs Standard Deviation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `20` | `≥1` | Lookback period for USF and RMS calculation. | -| `multiplier` | `double` | `1.0` | `>0.001` | RMS multiplier for band width. | +Standard deviation measures dispersion around the mean: $\sigma = \sqrt{E[(X - \mu)^2]}$. RMS measures dispersion around zero: $\text{RMS} = \sqrt{E[X^2]}$. Since the residuals $r_t = P_t - \text{USF}_t$ are already deviations from the smooth centerline, RMS is the correct measure. When the mean of residuals is zero (as it approximately is for a well-fitted filter), RMS equals standard deviation. -### Properties +### Output Interpretation -- `Last` (`TValue`): The upper band value (for single-value compatibility). -- `Upper` (`TValue`): The upper band (middle + multiplier × RMS). -- `Middle` (`TValue`): The Ehlers Ultrasmooth Filter value. -- `Lower` (`TValue`): The lower band (middle - multiplier × RMS). -- `Width` (`TValue`): Band width (Upper - Lower = 2 × multiplier × RMS). -- `IsHot` (`bool`): Returns `true` when warmup period is complete. +| Output | Interpretation | +|--------|---------------| +| USF slope positive | Underlying trend is up | +| Bands widening | Residual volatility increasing | +| Bands narrowing | Residual volatility compressing | +| Price at upper band | High-frequency component is large positive | +| Price at lower band | High-frequency component is large negative | -### Methods +## Resources -- `Update(TValue input, bool isNew)`: Updates the indicator with a new value and returns the result. -- `Update(TSeries source)`: Processes an entire series and returns TSeries. -- `Reset()`: Resets the indicator to its initial state. -- `Prime(ReadOnlySpan source, TimeSpan? step)`: Initializes from span data. -- `Calculate(TSeries source, int period, double multiplier)`: Static factory method. -- `Calculate(...)`: Static span-based calculation for zero-allocation processing. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var ubands = new Ubands(period: 20, multiplier: 1.0); - -// Update Loop -foreach (var bar in quotes) -{ - var result = ubands.Update(bar.Close); - - // Use valid results - if (ubands.IsHot) - { - Console.WriteLine($"{bar.Time}: Upper={ubands.Upper.Value:F2}, Middle={ubands.Middle.Value:F2}, Lower={ubands.Lower.Value:F2}"); - } -} -``` - -## References - -- Ehlers, John F. (2024). "Ultimate Bands." *Technical Analysis of Stocks & Commodities*. -- Ehlers, John F. (2013). *Cycle Analytics for Traders*. Wiley. -- Ehlers, John F. (2001). *Rocket Science for Traders*. Wiley. -- [MESA Software](https://www.mesasoftware.com/) - Ehlers' research and tools -- [PineScript Reference](https://www.tradingview.com/) - ubands.pine implementation \ No newline at end of file +- Ehlers, J. F. (2024). "Ultimate Bands." *Technical Analysis of Stocks & Commodities*. +- Ehlers, J. F. (2013). *Cycle Analytics for Traders*. Wiley. +- Ehlers, J. F. (2001). *Rocket Science for Traders*. Wiley. diff --git a/lib/channels/uchannel/uchannel.md b/lib/channels/uchannel/uchannel.md index 3e2fdd29..4285a602 100644 --- a/lib/channels/uchannel/uchannel.md +++ b/lib/channels/uchannel/uchannel.md @@ -1,252 +1,167 @@ # UCHANNEL: Ehlers Ultimate Channel -> "The best volatility channel uses the best smoother—applied twice." - -The Ehlers Ultimate Channel combines the Ultrasmooth Filter (USF) for both the centerline and volatility measurement, creating a channel that adapts to price movements with minimal lag while maintaining smooth, responsive boundaries. Unlike traditional channels that use standard deviation or ATR, UCHANNEL employs the Smoothed True Range (STR)—the USF applied to True Range—for band width calculation. +Ehlers Ultimate Channel applies the Ultrasmooth Filter (USF) twice: once to the close price for the centerline and once to True Range for band width, creating a channel where both the trend estimate and the volatility measure share the same low-lag, zero-overshoot filter characteristics. Unlike UBANDS which uses RMS of price residuals, UCHANNEL uses Smoothed True Range (STR) for band width, making it responsive to gap-inclusive volatility. Separate period parameters allow independent tuning of centerline smoothness and band-width responsiveness. ## Historical Context -John F. Ehlers introduced the Ultimate Channel in 2024 as the natural companion to his Ultimate Bands indicator. While Ultimate Bands (UBANDS) uses RMS of price deviations from the USF centerline to determine band width, the Ultimate Channel takes a different approach: it smooths True Range directly with the USF to create the band width multiplier. +John F. Ehlers introduced the Ultimate Channel in 2024 as the natural companion to his Ultimate Bands (UBANDS) indicator. The two share the same USF foundation but differ in how they determine band width: -This distinction matters for several reasons: +- **UBANDS**: measures RMS of price deviations from the USF centerline (statistical dispersion) +- **UCHANNEL**: smooths True Range with the USF (range-based volatility) -1. **True Range captures gaps**: Unlike simple high-low range, True Range accounts for overnight gaps by comparing current high/low to the previous close -2. **USF smoothing on TR**: Applying USF to True Range produces a volatility measure with the same low-lag characteristics as the centerline -3. **Independent tuning**: Separate periods for STR and centerline smoothing allow traders to optimize each component independently +The True Range approach captures overnight gaps, making UCHANNEL more appropriate for markets with significant gap activity (equities, futures) where the high-low range alone would underestimate actual price risk. -The design philosophy reflects Ehlers' preference for using the same high-quality filter throughout an indicator system, ensuring consistent lag characteristics across all components. +The design philosophy reflects Ehlers' preference for using the same high-quality filter throughout an indicator system. By applying the USF to both the centerline and the True Range, all components share consistent lag characteristics and zero-overshoot behavior. ## Architecture & Physics -### 1. True Range Calculation +### 1. True Range -True Range extends the simple high-low range to capture gaps: +True Range extends the high-low range to capture gaps: $$ -TR_t = \max(H_t, C_{t-1}) - \min(L_t, C_{t-1}) +TR_t = \max(H_t,\, C_{t-1}) - \min(L_t,\, C_{t-1}) $$ -where: -- $H_t$ = current high -- $L_t$ = current low -- $C_{t-1}$ = previous close +This formulation ensures gap-ups (today's low above yesterday's close) and gap-downs (today's high below yesterday's close) are fully captured. -This formulation ensures that a gap up (where today's low exceeds yesterday's close) or gap down (where today's high falls below yesterday's close) is fully captured in the volatility measurement. +### 2. Ultrasmooth Filter Coefficients -### 2. Ultrasmooth Filter (USF) Coefficients - -The USF is a 2-pole IIR filter with coefficients derived from the period parameter: +Each USF instance derives its coefficients from a period parameter $n$: $$ -\text{arg} = \frac{\sqrt{2} \cdot \pi}{\text{period}} +\text{arg} = \frac{\sqrt{2}\,\pi}{n} $$ $$ -c_2 = 2 \cdot e^{-\text{arg}} \cdot \cos(\text{arg}) +c_2 = 2\,e^{-\text{arg}} \cos(\text{arg}), \qquad c_3 = -e^{-2\,\text{arg}}, \qquad c_1 = \frac{1 + c_2 - c_3}{4} $$ -$$ -c_3 = -e^{-2 \cdot \text{arg}} -$$ +### 3. USF Recursion + +The 2-pole IIR recursion applied to input series $X$: $$ -c_1 = \frac{1 + c_2 - c_3}{4} +\text{USF}_t = (1 - c_1)\,X_t + (2c_1 - c_2)\,X_{t-1} - (c_1 + c_3)\,X_{t-2} + c_2\,\text{USF}_{t-1} + c_3\,\text{USF}_{t-2} $$ -### 3. USF Recursion Formula +This recursion is applied twice with potentially different periods: -The filter is applied using a 2-pole IIR structure: - -$$ -\text{USF}_t = (1 - c_1) \cdot X_t + (2c_1 - c_2) \cdot X_{t-1} - (c_1 + c_3) \cdot X_{t-2} + c_2 \cdot \text{USF}_{t-1} + c_3 \cdot \text{USF}_{t-2} -$$ - -This formula is applied twice: -- Once to close prices to produce the centerline (Middle) -- Once to True Range to produce the Smoothed True Range (STR) +- To close prices → **centerline** (Middle band) +- To True Range → **Smoothed True Range** (STR) ### 4. Channel Construction -With both smoothed values computed: - $$ -\text{Middle}_t = \text{USF}(\text{Close}, \text{centerPeriod}) +\text{Middle}_t = \text{USF}(C_t,\; n_{\text{center}}) $$ $$ -\text{STR}_t = \text{USF}(\text{TR}, \text{strPeriod}) +\text{STR}_t = \text{USF}(TR_t,\; n_{\text{str}}) $$ $$ -\text{Upper}_t = \text{Middle}_t + (\text{multiplier} \times \text{STR}_t) +U_t = \text{Middle}_t + k \cdot \text{STR}_t $$ $$ -\text{Lower}_t = \text{Middle}_t - (\text{multiplier} \times \text{STR}_t) +L_t = \text{Middle}_t - k \cdot \text{STR}_t $$ +where $k$ is the multiplier (default 1.0). + +### 5. Complexity + +Streaming: $O(1)$ per bar. Both USF instances are IIR recursions requiring only four multiply-adds each plus scalar state. No buffers or window scans needed. Memory: approximately 200 bytes for the two USF states plus metadata. + ## Mathematical Foundation -### Transfer Function +### Parameters -The USF can be expressed in the z-domain as: +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $n_{\text{str}}$ | strPeriod | 20 | $\geq 1$ | USF period for smoothing True Range | +| $n_{\text{center}}$ | centerPeriod | 20 | $\geq 1$ | USF period for smoothing the centerline | +| $k$ | multiplier | 1.0 | $> 0$ | STR multiplier for band width | + +### USF Transfer Function $$ -H(z) = \frac{(1 - c_1) + (2c_1 - c_2)z^{-1} - (c_1 + c_3)z^{-2}}{1 - c_2 z^{-1} - c_3 z^{-2}} +H(z) = \frac{(1 - c_1) + (2c_1 - c_2)\,z^{-1} - (c_1 + c_3)\,z^{-2}}{1 - c_2\,z^{-1} - c_3\,z^{-2}} $$ -### State-Space Form +Frequency response: cutoff at approximately $f_c \approx 1/(2\pi n)$ cycles per bar; 12 dB/octave rolloff. -For efficient computation, the filter maintains state variables: +### Pseudo-code -**For STR smoothing:** -- `UsStr1`, `UsStr2`: Previous USF outputs -- `Str1`, `Str2`: Previous True Range values +``` +function uchannel(close[], high[], low[], strPeriod, centerPeriod, multiplier): + // compute USF coefficients for STR + arg_s = sqrt(2) * pi / strPeriod + c2_s = 2 * exp(-arg_s) * cos(arg_s) + c3_s = -exp(-2 * arg_s) + c1_s = (1 + c2_s - c3_s) / 4 -**For centerline smoothing:** -- `UsCen1`, `UsCen2`: Previous USF outputs -- `Cen1`, `Cen2`: Previous close values + // compute USF coefficients for centerline + arg_c = sqrt(2) * pi / centerPeriod + c2_c = 2 * exp(-arg_c) * cos(arg_c) + c3_c = -exp(-2 * arg_c) + c1_c = (1 + c2_c - c3_c) / 4 -### FMA Optimization + usf_str = [NaN, NaN] // two-element state + usf_cen = [NaN, NaN] -The USF formula is implemented using Fused Multiply-Add for maximum precision and performance: + for each bar t: + // True Range + th = max(high[t], close[t-1]) + tl = min(low[t], close[t-1]) + tr = th - tl -```csharp -cenValue = Math.FusedMultiplyAdd(1 - c1_cen, cen_s0, - Math.FusedMultiplyAdd(2 * c1_cen - c2_cen, cen_s1, - Math.FusedMultiplyAdd(-(c1_cen + c3_cen), cen_s2, - Math.FusedMultiplyAdd(c2_cen, usCen1, c3_cen * usCen2)))); + // USF for True Range → STR + if usf_str not initialized: + str_val = tr + else: + str_val = (1-c1_s)*tr + (2*c1_s-c2_s)*tr[t-1] + - (c1_s+c3_s)*tr[t-2] + + c2_s*usf_str[0] + c3_s*usf_str[1] + usf_str = [str_val, usf_str[0]] + + // USF for close → centerline + if usf_cen not initialized: + center = close[t] + else: + center = (1-c1_c)*close[t] + (2*c1_c-c2_c)*close[t-1] + - (c1_c+c3_c)*close[t-2] + + c2_c*usf_cen[0] + c3_c*usf_cen[1] + usf_cen = [center, usf_cen[0]] + + upper = center + multiplier * str_val + lower = center - multiplier * str_val + + emit (upper, center, lower) ``` -## Performance Profile +### UCHANNEL vs UBANDS -### Operation Count (Streaming Mode, Scalar) +| Aspect | UBANDS | UCHANNEL | +|--------|--------|----------| +| Centerline | USF of close | USF of close | +| Band width | RMS of residuals ($O(n)$) | USF of True Range ($O(1)$) | +| Gap sensitivity | Indirect (via residuals) | Direct (True Range includes gaps) | +| Parameters | 1 period | 2 periods (STR, center) | +| Per-bar cost | $O(n)$ | $O(1)$ | -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 12 | 1 | 12 | -| MUL | 14 | 3 | 42 | -| MAX/MIN | 2 | 1 | 2 | -| FMA | 8 | 4 | 32 | -| **Total** | **36** | — | **~88 cycles** | +### Output Interpretation -The dominant cost is the 8 FMA operations (4 for STR + 4 for centerline). +| Output | Interpretation | +|--------|---------------| +| Centerline rising | USF-filtered uptrend | +| STR increasing | Smoothed True Range expanding; volatility rising | +| Band width contracting | Volatility compression | +| Price beyond upper | Extreme positive deviation from USF trend | -### Batch Mode (512 values, SIMD/FMA) +## Resources -Due to the recursive IIR structure, SIMD vectorization is limited. However, FMA instructions provide measurable improvement: - -| Operation | Scalar Ops | With FMA | Improvement | -| :--- | :---: | :---: | :---: | -| MUL+ADD chains | 16 | 8 FMA | ~15% | - -**Per-bar estimate:** ~75 cycles with FMA optimization - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Exact implementation per Ehlers specification | -| **Timeliness** | 9/10 | USF provides near-zero lag response | -| **Overshoot** | 8/10 | Minimal overshoot in trending markets | -| **Smoothness** | 9/10 | Very smooth bands due to 2-pole filtering | -| **Gap Handling** | 10/10 | True Range properly captures overnight gaps | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | Not implemented (proprietary Ehlers indicator) | -| **Skender** | N/A | Not implemented | -| **Tulip** | N/A | Not implemented | -| **Ooples** | N/A | Not implemented | -| **PineScript** | ✅ | Reference implementation in `uchannel.pine` | -| **Self-consistency** | ✅ | Streaming, batch, and span modes match | - -## Usage & Pitfalls - -- **Warmup Period**: The indicator requires `max(strPeriod, centerPeriod)` bars before producing stable values. Using results during warmup can lead to erratic signals. -- **Parameter Confusion**: Unlike UBANDS (which uses a single period), UCHANNEL accepts two periods: `strPeriod` controls band width responsiveness, `centerPeriod` controls centerline responsiveness. -- **Gap Sensitivity**: True Range includes gap size, so significant overnight gaps will widen the channel. -- **Memory Footprint**: Each instance requires ~200 bytes for state. At 1000 symbols: ~200KB. -- **Different from UBANDS**: UBANDS uses RMS of price deviations for band width; UCHANNEL uses USF-smoothed True Range × multiplier. -- **Bar Correction (`isNew=false`)**: When correcting the current bar, ensure you pass the complete updated OHLC values. - -## API - -```mermaid -classDiagram - class Uchannel { - +string Name - +int WarmupPeriod - +TValue Last - +TValue Upper - +TValue Middle - +TValue Lower - +TValue STR - +TValue Width - +bool IsHot - +Uchannel(int strPeriod, int centerPeriod, double multiplier) - +TValue Update(TBar input, bool isNew) - +TValue Update(TValue input, bool isNew) - +TSeries Update(TBarSeries source) - +TSeries Update(TSeries source) - +void Reset() - +void Prime(ReadOnlySpan~double~ source, TimeSpan? step) - +static TSeries Calculate(TBarSeries source, int strPeriod, int centerPeriod, double multiplier) - } -``` - -### Class: `Uchannel` - -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `strPeriod` | `int` | `20` | `≥1` | Period for smoothing True Range. | -| `centerPeriod` | `int` | `20` | `≥1` | Period for smoothing centerline. | -| `multiplier` | `double` | `1.0` | `>0.001` | STR multiplier for band width. | - -### Properties - -- `Last` (`TValue`): The upper band value (for single-value compatibility). -- `Upper` (`TValue`): The upper band (Middle + multiplier × STR). -- `Middle` (`TValue`): The USF-smoothed centerline. -- `Lower` (`TValue`): The lower band (Middle - multiplier × STR). -- `STR` (`TValue`): The Smoothed True Range (USF of TR). -- `Width` (`TValue`): Channel width (Upper - Lower). -- `IsHot` (`bool`): Returns `true` when warmup period is complete. - -### Methods - -- `Update(TBar input, bool isNew)`: Updates the indicator with a new bar and returns the result. -- `Update(TValue input, bool isNew)`: Updates with a single value (treats as O=H=L=C). -- `Update(TBarSeries source)`: Processes an entire bar series and returns TSeries. -- `Reset()`: Resets the indicator to its initial state. -- `Prime(ReadOnlySpan source, TimeSpan? step)`: Initializes from span data. -- `Calculate(TBarSeries source, int strPeriod, int centerPeriod, double multiplier)`: Static factory method. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize -var uchannel = new Uchannel(strPeriod: 20, centerPeriod: 20, multiplier: 1.0); - -// Update Loop -foreach (var bar in quotes) -{ - uchannel.Update(bar, isNew: true); - - // Use valid results - if (uchannel.IsHot) - { - Console.WriteLine($"{bar.Time}: Upper={uchannel.Upper.Value:F2}, Middle={uchannel.Middle.Value:F2}, Lower={uchannel.Lower.Value:F2}"); - } -} -``` - -## References - -- Ehlers, John F. (2024). "Ultimate Channel." *Technical Analysis of Stocks & Commodities*. -- Ehlers, John F. (2013). *Cycle Analytics for Traders*. Wiley Trading. -- Ehlers, John F. (2001). *Rocket Science for Traders*. Wiley Trading. \ No newline at end of file +- Ehlers, J. F. (2024). "Ultimate Channel." *Technical Analysis of Stocks & Commodities*. +- Ehlers, J. F. (2013). *Cycle Analytics for Traders*. Wiley. +- Ehlers, J. F. (2001). *Rocket Science for Traders*. Wiley. diff --git a/lib/channels/vwapbands/vwapbands.md b/lib/channels/vwapbands/vwapbands.md index 6c794d7b..dbfd191b 100644 --- a/lib/channels/vwapbands/vwapbands.md +++ b/lib/channels/vwapbands/vwapbands.md @@ -1,233 +1,135 @@ -# VWAPBANDS: Volume Weighted Average Price with Dual Standard Deviation Bands +# VWAPBANDS: VWAP with Dual Standard Deviation Bands -> "Where volume speaks, VWAP listens—and the bands show how far the market dares to stray." - -Volume Weighted Average Price Bands (VWAPBANDS) extends the standard VWAP indicator by adding two levels of standard deviation bands: ±1σ and ±2σ. This dual-band approach provides traders with a complete volatility framework, distinguishing between normal price fluctuations (within 1σ bands, ~68% of price action) and statistically significant moves (beyond 2σ bands, ~95% confidence level). Volume weighting ensures that prices where significant trading activity occurred contribute proportionally more to both the average and the deviation calculations, making VWAPBANDS particularly valuable for institutional traders benchmarking execution quality. +VWAP Bands extend the Volume Weighted Average Price with dual standard deviation bands at $\pm 1\sigma$ and $\pm 2\sigma$ levels, creating a five-line channel system anchored to volume-weighted fair value. Three running sums (cumulative price×volume, cumulative volume, cumulative price²×volume) enable O(1) streaming updates per bar. A session reset mechanism clears accumulations at configurable intervals, keeping the indicator anchored to current market structure. ## Historical Context -The Volume Weighted Average Price (VWAP) emerged in the 1980s as institutional traders sought a benchmark that reflected actual market participation rather than simple price averages. The concept gained prominence following the work of Berkowitz, Logue, and Noser (1988) on transaction costs, establishing VWAP as the gold standard for measuring execution quality against a fair market price. +VWAP emerged in the 1980s as institutional traders sought a benchmark reflecting actual market participation rather than simple price averages. Berkowitz, Logue, and Noser (1988) established VWAP as the standard for measuring execution quality: buying below VWAP or selling above it indicates favorable execution relative to the market's true average price. -The extension to standard deviation bands followed the same statistical reasoning as John Bollinger's work in the early 1980s—using standard deviation to quantify price dispersion around a central tendency. By combining volume weighting with dual-band construction, VWAPBANDS creates a statistically rigorous framework where the 1σ bands capture approximately 68% of price action and the 2σ bands capture approximately 95%, following normal distribution properties. +The extension to standard deviation bands follows the same statistical reasoning as Bollinger Bands: use standard deviation to quantify price dispersion around a central tendency. The critical difference is that VWAP weights by volume, so prices where heavy trading occurred contribute proportionally more to both the average and the deviation. This makes VWAPBANDS particularly meaningful for institutional traders benchmarking execution quality. -Unlike simple VWAP with single bands, VWAPBANDS creates distinct trading zones. The region between VWAP and ±1σ represents the "normal trading zone" where institutional algorithms typically execute. The area between ±1σ and ±2σ serves as an "alert zone" indicating elevated but not extreme deviation. Price beyond ±2σ signals statistically significant moves that often precede reversals or continuation breakouts. +The dual-band structure creates distinct statistical zones. The $\pm 1\sigma$ bands capture approximately 68% of price action (normal trading zone). The $\pm 2\sigma$ bands capture approximately 95% (extreme deviation zone). Price beyond $\pm 2\sigma$ represents a statistically significant departure from volume-weighted fair value. ## Architecture & Physics -VWAPBANDS calculates a volume-weighted average price with dual standard deviation bands using running sums for O(1) streaming updates. +### 1. Running Sum Accumulation -### 1. Typical Price Calculation +Three cumulative sums, reset at session boundaries: $$ -P_{typical} = \frac{High + Low + Close}{3} +\Sigma_{pv} = \sum_{i=1}^{n} P_i \cdot V_i, \quad \Sigma_{v} = \sum_{i=1}^{n} V_i, \quad \Sigma_{p^2v} = \sum_{i=1}^{n} P_i^2 \cdot V_i $$ -The HLC3 typical price provides a balanced measure considering the full trading range of each bar. +where $P_i$ is the source price (typically HLC3) and $V_i$ is volume at bar $i$. Zero-volume bars are skipped. -### 2. Running Sum Accumulation +### 2. VWAP (Center Line) $$ -\sum_{pv} = \sum_{i=1}^{n} P_i \times V_i +\text{VWAP}_t = \frac{\Sigma_{pv}}{\Sigma_v} +$$ + +### 3. Volume-Weighted Standard Deviation + +Using the computational identity $\text{Var}(X) = E[X^2] - (E[X])^2$: + +$$ +\sigma^2 = \frac{\Sigma_{p^2v}}{\Sigma_v} - \text{VWAP}^2 $$ $$ -\sum_{vol} = \sum_{i=1}^{n} V_i +\sigma = \sqrt{\max(0,\;\sigma^2)} +$$ + +The $\max(0, \cdot)$ guard prevents negative variance from floating-point accumulation errors. + +### 4. Dual Band Construction + +$$ +U_{1,t} = \text{VWAP}_t + k \cdot \sigma_t, \qquad L_{1,t} = \text{VWAP}_t - k \cdot \sigma_t $$ $$ -\sum_{pv^2} = \sum_{i=1}^{n} P_i^2 \times V_i +U_{2,t} = \text{VWAP}_t + 2k \cdot \sigma_t, \qquad L_{2,t} = \text{VWAP}_t - 2k \cdot \sigma_t $$ -Three running sums enable O(1) updates: cumulative price×volume, cumulative volume, and cumulative price²×volume. +where $k$ is the multiplier (default 1.0). With $k = 1$, the bands are at standard $1\sigma$ and $2\sigma$ levels. -### 3. VWAP Calculation +### 5. Session Reset -$$ -VWAP = \frac{\sum_{pv}}{\sum_{vol}} -$$ +On a reset condition (e.g., new trading day), all running sums restart from zero. This prevents stale historical data from dominating the calculation and keeps the indicator anchored to the current session. -The volume-weighted average divides cumulative price×volume by cumulative volume. +### 6. Complexity -### 4. Variance and Standard Deviation +Streaming: $O(1)$ per bar. Three additions to running sums, one division, one square root. No buffers or window scans. Memory: three doubles for running sums plus scalar state. -$$ -\sigma^2 = \frac{\sum_{pv^2}}{\sum_{vol}} - VWAP^2 -$$ +## Mathematical Foundation -$$ -\sigma = \sqrt{\max(0, \sigma^2)} -$$ +### Parameters -Variance uses the algebraic identity E[X²] - E[X]², with a guard against negative values from floating-point precision. +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $k$ | multiplier | 1.0 | $> 0$ | Scales the standard deviation for band width | -### 5. Dual Band Construction +### Pseudo-code -$$ -Upper_1 = VWAP + (1 \times k \times \sigma) -$$ +``` +function vwapbands(source[], volume[], reset[], multiplier): + sum_pv = 0, sum_vol = 0, sum_pv2 = 0, count = 0 -$$ -Lower_1 = VWAP - (1 \times k \times \sigma) -$$ + for each bar t: + price = source[t] + vol = volume[t] -$$ -Upper_2 = VWAP + (2 \times k \times \sigma) -$$ + if reset[t]: + // session boundary: restart accumulation + if vol > 0: + sum_pv = price * vol + sum_vol = vol + sum_pv2 = price * price * vol + count = 1 + else: + sum_pv = 0, sum_vol = 0, sum_pv2 = 0, count = 0 + else: + if vol > 0: + sum_pv += price * vol + sum_vol += vol + sum_pv2 += price * price * vol + count += 1 -$$ -Lower_2 = VWAP - (2 \times k \times \sigma) -$$ + vwap = sum_vol > 0 ? sum_pv / sum_vol : price -Where $k$ is the multiplier (default 1.0). The 1σ bands capture ~68% of price action, while 2σ bands capture ~95%. + variance = 0 + if sum_vol > 0 and count > 1: + variance = max(0, sum_pv2 / sum_vol - vwap * vwap) -### 6. Channel Width + stddev = sqrt(variance) -$$ -Width = Upper_2 - Lower_2 = 4 \times k \times \sigma -$$ + upper1 = vwap + multiplier * stddev + lower1 = vwap - multiplier * stddev + upper2 = vwap + 2 * multiplier * stddev + lower2 = vwap - 2 * multiplier * stddev -The full channel width provides a single volatility metric for cross-session comparison. - -## Performance Profile - -### Operation Count (Streaming Mode, per Bar) - -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 9 | 1 | 9 | -| MUL | 6 | 3 | 18 | -| DIV | 3 | 15 | 45 | -| SQRT | 1 | 15 | 15 | -| **Total** | **19** | — | **~87 cycles** | - -**Breakdown:** - -* Typical price (HLC3): 2 ADD + 1 DIV = 17 cycles -* Running sums (pv, vol, pv²): 3 ADD + 3 MUL = 12 cycles -* VWAP + variance: 2 DIV + 1 MUL + 1 SUB = 35 cycles -* StdDev: 1 SQRT = 15 cycles -* Dual bands + width: 4 ADD + 2 MUL = 10 cycles (with FMA optimization) - -### Complexity Analysis - -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming | O(1) | Running sums, constant per bar | -| Batch | O(n) | Linear scan required | - -**Memory:** ~80 bytes per instance (state struct with running sums, last valid values, and output properties) - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Mathematically exact volume-weighted statistics | -| **Timeliness** | 7/10 | Incorporates all session data, becomes stable over time | -| **Overshoot** | 9/10 | Bands adapt to actual volume-weighted volatility | -| **Smoothness** | 9/10 | Running sums provide inherent smoothing | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | No VWAP bands implementation | -| **Skender** | N/A | Has VWAP but not with dual bands | -| **Tulip** | N/A | No VWAP implementation | -| **Ooples** | N/A | No dual-band VWAP | -| **TradingView** | ✅ | Reference: vwapbands.pine | - -## Usage & Pitfalls - -* **Session Reset Timing:** Failing to reset VWAP at session boundaries causes stale historical data to dominate. Use the `reset` parameter for intraday strategies. -* **Multiplier Confusion:** Multiplier = 2.0 gives 2σ and 4σ bands, not 1σ and 2σ. Keep multiplier = 1.0 for standard statistical interpretation. -* **Early Session Instability:** VWAP bands are volatile in the first 15-30 minutes. Avoid trading band touches until sufficient volume accumulates. -* **Zero Volume Handling:** Extended periods of zero volume degrade indicator quality despite fallback to last valid values. -* **Bar Correction:** Use `isNew=false` when updating the current bar's value (same timestamp), `isNew=true` for new bars. -* **Intraday Focus:** Without session resets, cumulative calculations become less responsive as early data dominates. -* **Volume Dependency:** Requires reliable volume data; forex and index CFDs may not provide accurate signals. - -## API - -```mermaid -classDiagram - class Vwapbands { - +Vwapbands(double multiplier = 1.0) - +TValue Upper1 - +TValue Lower1 - +TValue Upper2 - +TValue Lower2 - +TValue Vwap - +TValue StdDev - +TValue Width - +bool IsHot - +TValue Update(TBar bar, bool isNew, bool reset) - +TSeries Update(TBarSeries source) - +void Reset() - } - AbstractBase <|-- Vwapbands + emit (vwap, upper1, lower1, upper2, lower2, stddev) ``` -### Class: `Vwapbands` +### Statistical Zone Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `multiplier` | `double` | `1.0` | `≥0.001` | Scales the standard deviation for band width. | +| Zone | Coverage | Interpretation | +|------|----------|----------------| +| Within $\pm 1\sigma$ | ~68% | Normal trading range; institutional execution zone | +| $\pm 1\sigma$ to $\pm 2\sigma$ | ~27% | Alert zone; elevated deviation from fair value | +| Beyond $\pm 2\sigma$ | ~5% | Extreme deviation; statistically significant move | -### Properties +### Output Interpretation -* `Upper1` (`TValue`): Upper band at 1σ (VWAP + mult × StdDev). -* `Lower1` (`TValue`): Lower band at 1σ (VWAP - mult × StdDev). -* `Upper2` (`TValue`): Upper band at 2σ (VWAP + 2 × mult × StdDev). -* `Lower2` (`TValue`): Lower band at 2σ (VWAP - 2 × mult × StdDev). -* `Vwap` (`TValue`): Volume-weighted average price (center line). -* `StdDev` (`TValue`): Standard deviation of volume-weighted prices. -* `Width` (`TValue`): Band width (Upper1 - Lower1 = 2 × mult × StdDev). -* `IsHot` (`bool`): Returns `true` when warmup is complete (≥2 bars). +| Output | Interpretation | +|--------|---------------| +| Price above VWAP | Buyers paying above fair value; bullish bias | +| Price below VWAP | Sellers accepting below fair value; bearish bias | +| $\sigma$ increasing | Volume-weighted dispersion growing | +| Bands expanding | Intraday volatility increasing | -### Methods +## Resources -* `Update(TBar bar, bool isNew = true, bool reset = false)`: Updates with new OHLCV bar. Use `reset=true` at session boundaries. -* `Update(TBarSeries source)`: Batch update from bar series. -* `Reset()`: Clears state and restarts calculations. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize with default multiplier (1.0 = standard 1σ and 2σ bands) -var vwapbands = new Vwapbands(multiplier: 1.0); - -// Streaming update - intraday with session reset -bool isSessionStart = true; -foreach (var bar in intradayBars) -{ - bool isNewBar = bar.Time > lastBarTime; - vwapbands.Update(bar, isNew: isNewBar, reset: isSessionStart); - isSessionStart = false; - lastBarTime = bar.Time; - - if (vwapbands.IsHot) - { - Console.WriteLine($"{bar.Time}: VWAP={vwapbands.Vwap.Value:F2}"); - Console.WriteLine($" 1σ Bands: [{vwapbands.Lower1.Value:F2}, {vwapbands.Upper1.Value:F2}]"); - Console.WriteLine($" 2σ Bands: [{vwapbands.Lower2.Value:F2}, {vwapbands.Upper2.Value:F2}]"); - - // Zone-based trading signals - double price = bar.Close; - if (price > vwapbands.Upper2.Value) - Console.WriteLine(" ⚠️ Price in extreme overbought zone (>2σ)"); - else if (price < vwapbands.Lower2.Value) - Console.WriteLine(" ⚠️ Price in extreme oversold zone (<-2σ)"); - } -} - -// Batch processing -var (upper1, lower1, upper2, lower2, vwap, stdDev) = Vwapbands.Calculate(barSeries, multiplier: 1.0); -``` - -## References - -* Berkowitz, S. A., Logue, D. E., & Noser, E. A. (1988). The Total Cost of Transactions on the NYSE. *The Journal of Finance*, 43(1), 97-112. -* Kissell, R. (2013). *The Science of Algorithmic Trading and Portfolio Management*. Academic Press. -* TradingView (2024). VWAP Standard Deviation Bands. Pine Script Reference. +- Berkowitz, S., Logue, D. & Noser, E. (1988). "The Total Cost of Transactions on the NYSE." *The Journal of Finance*, 43(1), 97–112. +- Kissell, R. (2013). *The Science of Algorithmic Trading and Portfolio Management*. Academic Press. diff --git a/lib/channels/vwapsd/vwapsd.md b/lib/channels/vwapsd/vwapsd.md index c5c5c676..f3a1fbd0 100644 --- a/lib/channels/vwapsd/vwapsd.md +++ b/lib/channels/vwapsd/vwapsd.md @@ -1,220 +1,128 @@ # VWAPSD: VWAP with Standard Deviation Bands -> "The market's true average is weighted by conviction—and the bands reveal when conviction wavers." - -The Volume Weighted Average Price with Standard Deviation Bands (VWAPSD) combines VWAP with statistical volatility bands. VWAP represents the true average price weighted by volume, making it the institutional benchmark for execution quality. The addition of configurable standard deviation bands transforms VWAP from a simple reference line into a complete channel system that measures both central tendency and price dispersion. VWAPSD is primarily used as an intraday indicator with session resets, ensuring the indicator remains relevant to current market conditions. +VWAP with Standard Deviation Bands combines the Volume Weighted Average Price with a single configurable standard deviation band pair, providing a simpler alternative to VWAPBANDS (which uses dual $\pm 1\sigma$ and $\pm 2\sigma$ levels). Three running sums enable O(1) streaming updates. A session reset mechanism clears accumulations at configurable intervals, keeping the indicator anchored to current market structure. The configurable deviation parameter allows traders to select their desired confidence level ($1\sigma$ ≈ 68%, $2\sigma$ ≈ 95%, $3\sigma$ ≈ 99.7%). ## Historical Context -The Volume Weighted Average Price emerged in the 1980s as institutional traders sought a benchmark that reflected actual market participation rather than simple price averages. The seminal work by Berkowitz, Logue, and Noser (1988) on transaction costs established VWAP as the gold standard for measuring execution quality—buying below VWAP or selling above it indicates favorable execution relative to the market's true average. +VWAP emerged in the 1980s as institutional traders needed a benchmark reflecting actual market participation. Berkowitz, Logue, and Noser (1988) established VWAP as the standard for measuring execution quality. The concept is straightforward: weight each price by the volume traded at that price, producing an average that reflects where the most conviction-backed trading occurred. -The extension to standard deviation bands follows the statistical reasoning popularized by John Bollinger in the early 1980s. By applying standard deviation to volume-weighted prices, VWAPSD creates bands that adapt to actual market volatility while respecting the volume-weighted nature of the central tendency. The configurable deviation parameter (1σ, 2σ, or 3σ) allows traders to select their desired confidence level. +The standard deviation extension follows the same reasoning as Bollinger Bands but applied to volume-weighted statistics. By adding bands at $n$ standard deviations from VWAP, the indicator creates a statistically grounded channel that adapts to actual volume-weighted volatility. -Unlike simple moving average bands, VWAPSD anchors to session boundaries, resetting calculations at configurable intervals (daily, weekly, hourly). This anchored approach prevents the accumulation of stale historical data and keeps the indicator focused on current market structure—a critical feature for intraday traders who need actionable levels for the current session. +VWAPSD differs from VWAPBANDS only in output structure: VWAPSD emits one band pair at a configurable distance, while VWAPBANDS always emits two band pairs ($\pm 1\sigma$ and $\pm 2\sigma$). The underlying VWAP and variance calculations are identical. ## Architecture & Physics -VWAPSD calculates a volume-weighted average price with configurable standard deviation bands using running sums for O(1) streaming updates. +### 1. Running Sum Accumulation -### 1. Typical Price Calculation +Three cumulative sums, reset at session boundaries: $$ -P_{typical} = \frac{High + Low + Close}{3} +\Sigma_{pv} = \sum_{i=1}^{n} P_i \cdot V_i, \quad \Sigma_v = \sum_{i=1}^{n} V_i, \quad \Sigma_{p^2v} = \sum_{i=1}^{n} P_i^2 \cdot V_i $$ -The HLC3 typical price provides a balanced measure considering the full trading range of each bar. +where $P_i$ is the source price (typically HLC3) and $V_i$ is volume. Zero-volume bars are skipped to prevent distortion. -### 2. Running Sum Accumulation +### 2. VWAP (Center Line) $$ -\sum_{pv} = \sum_{i=1}^{n} P_i \times V_i +\text{VWAP}_t = \frac{\Sigma_{pv}}{\Sigma_v} +$$ + +### 3. Volume-Weighted Standard Deviation + +Using the computational identity $\text{Var}(X) = E[X^2] - (E[X])^2$: + +$$ +\sigma^2 = \frac{\Sigma_{p^2v}}{\Sigma_v} - \text{VWAP}^2 $$ $$ -\sum_{vol} = \sum_{i=1}^{n} V_i +\sigma = \sqrt{\max(0,\;\sigma^2)} +$$ + +### 4. Band Construction + +$$ +U_t = \text{VWAP}_t + k \cdot \sigma_t $$ $$ -\sum_{pv^2} = \sum_{i=1}^{n} P_i^2 \times V_i +L_t = \text{VWAP}_t - k \cdot \sigma_t $$ -Three running sums enable O(1) updates: cumulative price×volume, cumulative volume, and cumulative price²×volume. +where $k$ is the number of standard deviations (default 2.0). -### 3. VWAP Calculation +### 5. Session Reset -$$ -VWAP = \frac{\sum_{pv}}{\sum_{vol}} -$$ +On a reset condition, all running sums restart from zero. Configurable reset intervals include intraday (1m through 4H), daily, weekly, monthly, quarterly, semi-annual, annual, or never. -The volume-weighted average divides cumulative price×volume by cumulative volume. +### 6. Complexity -### 4. Variance and Standard Deviation +Streaming: $O(1)$ per bar. Three additions to running sums, one division, one square root. Memory: three doubles for running sums plus scalar state (~64 bytes per instance). -$$ -\sigma^2 = \frac{\sum_{pv^2}}{\sum_{vol}} - VWAP^2 -$$ +## Mathematical Foundation -$$ -\sigma = \sqrt{\max(0, \sigma^2)} -$$ +### Parameters -Variance uses the algebraic identity E[X²] - E[X]², with a guard against negative values from floating-point precision. +| Symbol | Name | Default | Constraint | Description | +|--------|------|---------|------------|-------------| +| $k$ | numDevs | 2.0 | $0.1$ – $5.0$ | Number of standard deviations for bands | -### 5. Band Construction +### Pseudo-code -$$ -Upper = VWAP + (n \times \sigma) -$$ +``` +function vwapsd(source[], volume[], reset[], numDevs): + sum_pv = 0, sum_vol = 0, sum_pv2 = 0 -$$ -Lower = VWAP - (n \times \sigma) -$$ + for each bar t: + price = source[t] + vol = volume[t] -Where $n$ is the number of standard deviations (default 2.0). Common settings: 1σ (~68%), 2σ (~95%), 3σ (~99.7%). + if reset[t]: + if vol > 0: + sum_pv = price * vol + sum_vol = vol + sum_pv2 = price * price * vol + else: + sum_pv = 0, sum_vol = 0, sum_pv2 = 0 + else: + if vol > 0: + sum_pv += price * vol + sum_vol += vol + sum_pv2 += price * price * vol -### 6. Channel Width + vwap = sum_vol > 0 ? sum_pv / sum_vol : price -$$ -Width = Upper - Lower = 2 \times n \times \sigma -$$ + variance = sum_vol > 0 ? sum_pv2 / sum_vol - vwap * vwap : 0 + stddev = sqrt(max(0, variance)) -The channel width provides a single volatility metric for position sizing and risk assessment. + upper = vwap + numDevs * stddev + lower = vwap - numDevs * stddev -## Performance Profile - -### Operation Count (Streaming Mode, per Bar) - -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD/SUB | 7 | 1 | 7 | -| MUL | 4 | 3 | 12 | -| DIV | 3 | 15 | 45 | -| SQRT | 1 | 15 | 15 | -| **Total** | **15** | — | **~79 cycles** | - -**Breakdown:** - -- Typical price (HLC3): 2 ADD + 1 DIV = 17 cycles -- Running sums (pv, vol, pv²): 3 ADD + 3 MUL = 12 cycles -- VWAP + variance: 2 DIV + 1 MUL + 1 SUB = 35 cycles -- StdDev + bands: 1 SQRT + 2 ADD = 17 cycles - -### Complexity Analysis - -| Mode | Complexity | Notes | -| :--- | :---: | :--- | -| Streaming | O(1) | Running sums, no buffer iteration | -| Batch | O(n) | Linear scan per session | - -**Memory:** ~64 bytes per instance (3 running sums × 8 bytes + state variables) - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Volume-weighted mean is mathematically exact | -| **Timeliness** | 7/10 | Incorporates all data since session start | -| **Overshoot** | 9/10 | Bands based on actual volatility | -| **Smoothness** | 9/10 | Running average smooths noise progressively | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **TA-Lib** | N/A | No VWAP bands implementation | -| **Skender** | N/A | Has VWAP but not with StdDev bands | -| **Tulip** | N/A | No VWAP implementation | -| **TradingView** | ✅ | Reference: vwapsd.pine | - -## Usage & Pitfalls - -- **Session Reset Timing:** Failing to reset VWAP at session boundaries causes stale data to dominate. Use the `reset` parameter at session start. -- **NumDevs Selection:** Use 1σ for active trading (more signals), 2σ for standard analysis (~95% confidence), 3σ for extreme moves only. -- **Early Session Instability:** VWAP is volatile in the first 15-30 minutes. Wait for sufficient volume before trading band signals. -- **Zero Volume Handling:** Extended periods of zero volume degrade indicator quality despite fallback to last valid values. -- **Bar Correction:** Use `isNew=false` when updating the current bar's value (same timestamp), `isNew=true` for new bars. -- **Intraday Focus:** Without session resets, cumulative calculations become less responsive as early data dominates. -- **Volume Dependency:** Requires reliable volume data; forex and index CFDs may not provide accurate signals. -- **Gap Sensitivity:** Large overnight gaps distort morning VWAP until sufficient volume accumulates. - -## API - -```mermaid -classDiagram - class Vwapsd { - +Vwapsd(double numDevs = 2.0) - +TValue Upper - +TValue Lower - +TValue Vwap - +TValue StdDev - +TValue Width - +bool IsHot - +TValue Update(TBar bar, bool isNew, bool reset) - +TSeries Update(TBarSeries source) - +void Reset() - } - AbstractBase <|-- Vwapsd + emit (vwap, upper, lower) ``` -### Class: `Vwapsd` +### VWAPSD vs VWAPBANDS -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `numDevs` | `double` | `2.0` | `0.1–5.0` | Number of standard deviations for bands. | +| Aspect | VWAPSD | VWAPBANDS | +|--------|--------|-----------| +| Band pairs | 1 (configurable $k\sigma$) | 2 ($\pm 1\sigma$ and $\pm 2\sigma$) | +| Default deviation | 2.0 | 1.0 (inner); 2.0 (outer) | +| VWAP calculation | Identical | Identical | +| Variance calculation | Identical | Identical | -### Properties +### Output Interpretation -- `Upper` (`TValue`): Upper band (VWAP + numDevs × StdDev). -- `Lower` (`TValue`): Lower band (VWAP - numDevs × StdDev). -- `Vwap` (`TValue`): Volume-weighted average price (center line). -- `StdDev` (`TValue`): Standard deviation of volume-weighted prices. -- `Width` (`TValue`): Band width (Upper - Lower = 2 × numDevs × StdDev). -- `IsHot` (`bool`): Returns `true` when warmup is complete (≥2 bars). +| Output | Interpretation | +|--------|---------------| +| Price above VWAP | Buyers paying above fair value; bullish intraday bias | +| Price below VWAP | Sellers accepting below fair value; bearish intraday bias | +| Price at upper band | Overextended above volume-weighted mean by $k\sigma$ | +| Price at lower band | Overextended below volume-weighted mean by $k\sigma$ | +| Band width expanding | Intraday volume-weighted dispersion increasing | +| Band width near zero | Very tight price clustering around VWAP | -### Methods +## Resources -- `Update(TBar bar, bool isNew = true, bool reset = false)`: Updates with new OHLCV bar. Use `reset=true` at session boundaries. -- `Update(TBarSeries source)`: Batch update from bar series. -- `Reset()`: Clears state and restarts calculations. - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize with 2 standard deviations (~95% confidence) -var vwapsd = new Vwapsd(numDevs: 2.0); - -// Streaming update - intraday with session reset -bool isSessionStart = true; -foreach (var bar in intradayBars) -{ - bool isNewBar = bar.Time > lastBarTime; - vwapsd.Update(bar, isNew: isNewBar, reset: isSessionStart); - isSessionStart = false; - lastBarTime = bar.Time; - - if (vwapsd.IsHot) - { - Console.WriteLine($"{bar.Time}: VWAP={vwapsd.Vwap.Value:F2}"); - Console.WriteLine($" Bands: [{vwapsd.Lower.Value:F2}, {vwapsd.Upper.Value:F2}]"); - Console.WriteLine($" Width: {vwapsd.Width.Value:F2}"); - - // Mean reversion signals - double price = bar.Close; - if (price > vwapsd.Upper.Value) - Console.WriteLine(" ⚠️ Overbought - potential short"); - else if (price < vwapsd.Lower.Value) - Console.WriteLine(" ⚠️ Oversold - potential long"); - } -} - -// Batch processing -var (upper, lower, vwap, stdDev) = Vwapsd.Calculate(barSeries, numDevs: 2.0); -``` - -## References - -- Berkowitz, S. A., Logue, D. E., & Noser, E. A. (1988). The Total Cost of Transactions on the NYSE. *The Journal of Finance*, 43(1), 97-112. +- Berkowitz, S., Logue, D. & Noser, E. (1988). "The Total Cost of Transactions on the NYSE." *The Journal of Finance*, 43(1), 97–112. - Kissell, R. (2013). *The Science of Algorithmic Trading and Portfolio Management*. Academic Press. -- TradingView (2024). Volume Weighted Average Price (VWAP). TradingView Support Documentation. diff --git a/lib/cycles/ccor/Ccor.md b/lib/cycles/ccor/Ccor.md new file mode 100644 index 00000000..cbc74b86 --- /dev/null +++ b/lib/cycles/ccor/Ccor.md @@ -0,0 +1,137 @@ +# CCOR: Ehlers Correlation Cycle + +CCOR extracts cycle phase by computing Pearson correlation of a price window against cosine (Real) and negative-sine (Imaginary) reference waves of a presumed fixed period, converting the resulting phasor to an angle with a monotonic constraint, and classifying the market state as trending or cycling based on the angle rate of change. Unlike Hilbert Transform approaches that rely on analytic signal construction, CCOR uses the statistical machinery of correlation to measure how well price "fits" each quadrature component, yielding bounded $[-1, +1]$ outputs that double as confidence measures. The method was introduced to address the instability of Hilbert-based phasors during trend-dominated regimes. + +## Historical Context + +John F. Ehlers published "Correlation As A Cycle Indicator" in *Technical Analysis of Stocks & Commodities* (June 2020), presenting CCOR as a more robust alternative to his earlier Hilbert Transform phasor (circa 2001). The Hilbert approach suffers from amplitude sensitivity and poor convergence during strong trends because it treats all price action as containing a dominant cycle. CCOR sidesteps this by measuring correlation strength rather than instantaneous frequency; when price is trending, correlation with both cosine and sine references drops, naturally suppressing false cycle signals. + +The key insight is that Pearson correlation normalizes for both mean and variance, making the Real and Imaginary outputs invariant to price level and volatility. This is a meaningful improvement over raw quadrature demodulation, where amplitude scaling can distort phase angle estimates. The addition of a monotonic angle constraint and a state classifier (trending vs. cycling) was Ehlers' acknowledgment that no cycle indicator should pretend to find cycles where none exist. + +## Architecture & Physics + +### 1. Dual Pearson Correlation (Quadrature Demodulation) + +Two independent Pearson correlations are computed over a sliding window of length $N$ (the presumed period): + +**Real component** correlates price with $\cos(2\pi k / N)$: + +$$r_{\text{real}} = \frac{N \sum x_k \cos_k - \sum x_k \sum \cos_k}{\sqrt{(N \sum x_k^2 - (\sum x_k)^2)(N \sum \cos_k^2 - (\sum \cos_k)^2)}}$$ + +**Imaginary component** correlates price with $-\sin(2\pi k / N)$: + +$$r_{\text{imag}} = \frac{N \sum x_k (-\sin_k) - \sum x_k \sum (-\sin_k)}{\sqrt{(N \sum x_k^2 - (\sum x_k)^2)(N \sum \sin_k^2 - (\sum \sin_k)^2)}}$$ + +Both $r_{\text{real}}, r_{\text{imag}} \in [-1, +1]$ by construction. + +### 2. Phasor Angle with Quadrant Resolution + +The raw angle (degrees) is computed from the arctangent of the Real/Imaginary ratio with quadrant correction: + +$$\theta = \begin{cases} 90° + \arctan\!\left(\frac{r_{\text{real}}}{r_{\text{imag}}}\right) & \text{if } r_{\text{imag}} \neq 0 \\ 0° & \text{if } r_{\text{imag}} = 0 \end{cases}$$ + +If $r_{\text{imag}} > 0$, subtract $180°$ to resolve the correct quadrant. + +### 3. Monotonic Constraint + +The angle is never allowed to decrease: + +$$\theta_t = \max(\theta_t, \theta_{t-1})$$ + +This prevents the phasor from "spinning backward" during noise, which would generate spurious state transitions. + +### 4. Market State Detection + +The angular velocity $|\Delta\theta| = |\theta_t - \theta_{t-1}|$ classifies regime: + +$$\text{state} = \begin{cases} +1 & \text{if } |\Delta\theta| < \text{threshold} \text{ and } \theta \geq 0° \\ -1 & \text{if } |\Delta\theta| < \text{threshold} \text{ and } \theta \leq 0° \\ 0 & \text{otherwise (cycling)} \end{cases}$$ + +Small angle changes indicate the phasor is "stuck" in one region, implying a trend. Large angle changes indicate active cycling. + +### 5. Complexity + +Each bar requires two full Pearson correlation loops over $N$ samples: $O(N)$ per bar. The five accumulators ($S_x, S_y, S_{xx}, S_{xy}, S_{yy}$) per correlation can be maintained incrementally for $O(1)$ streaming, but the reference implementation uses explicit loops. + +## Mathematical Foundation + +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Presumed dominant cycle wavelength | 20 | $> 0$ | +| `threshold` | Angle rate threshold (degrees) for state detection | 9.0 | $> 0$ | +| `source` | Input price series | close | | + +### Pearson Correlation (Detailed) + +For window index $k = 0, 1, \ldots, N-1$: + +$$S_x = \sum_{k=0}^{N-1} x_{t-k}, \quad S_y = \sum_{k=0}^{N-1} y_k$$ + +$$S_{xx} = \sum_{k=0}^{N-1} x_{t-k}^2, \quad S_{yy} = \sum_{k=0}^{N-1} y_k^2, \quad S_{xy} = \sum_{k=0}^{N-1} x_{t-k} \cdot y_k$$ + +$$D = (N \cdot S_{xx} - S_x^2)(N \cdot S_{yy} - S_y^2)$$ + +$$r = \begin{cases} \frac{N \cdot S_{xy} - S_x \cdot S_y}{\sqrt{D}} & \text{if } D > 0 \\ 0 & \text{otherwise} \end{cases}$$ + +Where: +- Real: $y_k = \cos(2\pi k / N)$ +- Imaginary: $y_k = -\sin(2\pi k / N)$ + +### Pseudo-code + +``` +function CCOR(source, period, threshold): + // Real correlation + Sx_r = Sy_r = Sxx_r = Sxy_r = Syy_r = 0 + for k = 0 to period-1: + x = source[k] + y = cos(2π * k / period) + Sx_r += x; Sy_r += y + Sxx_r += x*x; Sxy_r += x*y; Syy_r += y*y + denom_r = (N*Sxx_r - Sx_r²) * (N*Syy_r - Sy_r²) + real = denom_r > 0 ? (N*Sxy_r - Sx_r*Sy_r) / √denom_r : 0 + + // Imaginary correlation (same accumulators for x, different y) + Sx_i = Sy_i = Sxx_i = Sxy_i = Syy_i = 0 + for k = 0 to period-1: + x = source[k] + y = -sin(2π * k / period) + Sx_i += x; Sy_i += y + Sxx_i += x*x; Sxy_i += x*y; Syy_i += y*y + denom_i = (N*Sxx_i - Sx_i²) * (N*Syy_i - Sy_i²) + imag = denom_i > 0 ? (N*Sxy_i - Sx_i*Sy_i) / √denom_i : 0 + + // Phasor angle + angle = 0 + if imag ≠ 0: angle = 90 + atan(real/imag) * (180/π) + if imag > 0: angle -= 180 + + // Monotonic constraint + angle = max(angle, prev_angle) + prev_angle = angle + + // State detection + Δθ = |angle - saved_prev_angle| + state = 0 + if Δθ < threshold and angle ≥ 0: state = +1 + if Δθ < threshold and angle ≤ 0: state = -1 + + return [real, imag, angle, state] +``` + +### Output Interpretation + +| Output | Range | Meaning | +|--------|-------|---------| +| `real` | $[-1, +1]$ | Correlation with cosine reference (in-phase strength) | +| `imag` | $[-1, +1]$ | Correlation with negative-sine reference (quadrature strength) | +| `angle` | monotonically increasing degrees | Phasor angle of detected cycle | +| `state` | $\{-1, 0, +1\}$ | $-1$ = downtrend, $0$ = cycling, $+1$ = uptrend | + +## Resources + +- **Ehlers, J.F.** "Correlation As A Cycle Indicator." *Technical Analysis of Stocks & Commodities*, June 2020. +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. (Hilbert Transform phasor predecessor) +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. (Broader cycle analysis framework) +- **Pearson, K.** "Notes on Regression and Inheritance in the Case of Two Parents." *Proceedings of the Royal Society of London*, 58, 1895. (Original Pearson correlation) diff --git a/lib/cycles/ccyc/Ccyc.md b/lib/cycles/ccyc/Ccyc.md new file mode 100644 index 00000000..545b0d69 --- /dev/null +++ b/lib/cycles/ccyc/Ccyc.md @@ -0,0 +1,142 @@ +# CCYC: Ehlers Cyber Cycle + +CCYC isolates the dominant cycle component from price data using a 2-pole high-pass IIR filter applied to a 4-tap FIR-smoothed input, producing an oscillator that strips trend while preserving cyclical content with minimal lag. The companion trigger line (one-bar delay of the cycle output) provides crossover signals for timing entries and exits. Unlike band-pass approaches that require specifying a center frequency, CCYC's high-pass architecture extracts whatever cyclic energy exists above a cutoff controlled by a single $\alpha$ damping parameter, making it adaptive to the dominant period present in the data. + +## Historical Context + +John F. Ehlers introduced the Cyber Cycle in Chapter 4 of *Cybernetic Analysis for Stocks and Futures* (Wiley, 2004) as part of his broader framework applying digital signal processing to market data. The name "cybernetic" references Norbert Wiener's cybernetics, the study of control and communication in systems, reflecting Ehlers' view that markets are feedback-driven systems with measurable oscillatory modes. + +The Cyber Cycle was designed to solve a specific problem with earlier cycle extraction methods: the Hilbert Transform phasor (Ehlers, 2001) and simple band-pass filters both require assumptions about the dominant period. The Cyber Cycle's high-pass design avoids this by removing trend (the zero-frequency component) and letting whatever cycle energy remains pass through. The 4-tap FIR pre-smoother eliminates 2-bar and 3-bar cycle artifacts that would otherwise contaminate the output with aliased noise. + +The $\alpha$ parameter controls the high-pass cutoff: smaller values of $\alpha$ push the cutoff to lower frequencies, extracting only long-period cycles and producing smoother output at the cost of additional lag. The default $\alpha = 0.07$ was empirically tuned by Ehlers to balance responsiveness against noise rejection for typical equity and futures data on daily timeframes. + +## Architecture & Physics + +### 1. FIR Pre-Smoother (4-Tap) + +A weighted average eliminates 2-bar and 3-bar cycle components: + +$$\text{smooth}_t = \frac{x_t + 2x_{t-1} + 2x_{t-2} + x_{t-3}}{6}$$ + +The weights $[1, 2, 2, 1] / 6$ form a symmetric FIR kernel. The frequency response has exact zeros at periods 2 and 3 bars, which are below the Nyquist frequency for most meaningful market cycles and represent sampling artifacts rather than real cyclical content. + +### 2. High-Pass IIR Filter (2-Pole) + +The core filter applies a second-order high-pass IIR to the smoothed input: + +$$\text{cycle}_t = c_{hp} \cdot (\text{smooth}_t - 2 \cdot \text{smooth}_{t-1} + \text{smooth}_{t-2}) + c_{fb1} \cdot \text{cycle}_{t-1} + c_{fb2} \cdot \text{cycle}_{t-2}$$ + +Where the coefficients are derived from $\alpha$: + +$$c_{hp} = (1 - 0.5\alpha)^2$$ + +$$c_{fb1} = 2(1 - \alpha)$$ + +$$c_{fb2} = -(1 - \alpha)^2$$ + +The term $(\text{smooth}_t - 2 \cdot \text{smooth}_{t-1} + \text{smooth}_{t-2})$ is a second-difference operator, equivalent to a discrete Laplacian that emphasizes curvature. The feedback terms $c_{fb1}$ and $c_{fb2}$ create resonance, amplifying signals near the natural frequency determined by $\alpha$. + +### 3. Initialization Bootstrap + +For the first 6 bars (before the IIR has sufficient history), the cycle is bootstrapped using a simple second-difference of raw price: + +$$\text{cycle}_t = \frac{x_t - 2x_{t-1} + x_{t-2}}{4} \quad \text{for } t < 7$$ + +This seeds the IIR filter with a reasonable approximation, allowing convergence within the first few cycles rather than requiring a long zero-state warmup. + +### 4. Trigger Line + +$$\text{trigger}_t = \text{cycle}_{t-1}$$ + +A one-bar delay. Crossovers between cycle and trigger identify turning points. + +### 5. Complexity + +The algorithm is $O(1)$ per bar: 6 multiplications, 5 additions, and 2 state variables. No loops, no window scans, no allocations. + +## Mathematical Foundation + +### Parameters + +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `alpha` | Damping factor controlling high-pass cutoff | 0.07 | $(0, 1)$ exclusive | +| `source` | Input price series | hl2 | | + +### z-Domain Transfer Function + +The FIR pre-smoother: + +$$H_{\text{FIR}}(z) = \frac{1 + 2z^{-1} + 2z^{-2} + z^{-3}}{6}$$ + +The IIR high-pass filter: + +$$H_{\text{IIR}}(z) = \frac{c_{hp}(1 - 2z^{-1} + z^{-2})}{1 - c_{fb1} z^{-1} - c_{fb2} z^{-2}}$$ + +Combined transfer function: + +$$H(z) = H_{\text{FIR}}(z) \cdot H_{\text{IIR}}(z)$$ + +### Coefficient Derivation + +Given damping factor $\alpha \in (0, 1)$: + +$$c_{hp} = \left(1 - \frac{\alpha}{2}\right)^2$$ + +$$c_{fb1} = 2(1 - \alpha)$$ + +$$c_{fb2} = -(1 - \alpha)^2$$ + +The characteristic equation of the IIR section: + +$$z^2 - 2(1-\alpha)z + (1-\alpha)^2 = 0$$ + +has a double pole at $z = 1 - \alpha$. For $\alpha = 0.07$, the pole is at $z = 0.93$, well inside the unit circle (stable), with a $-3$ dB cutoff period of approximately $\frac{2\pi}{\alpha} \approx 90$ bars. + +### Pseudo-code + +``` +function CCYC(source, alpha): + validate: 0 < alpha < 1 + + // FIR smoother + smooth = (source[0] + 2*source[1] + 2*source[2] + source[3]) / 6 + + // IIR coefficients + c_hp = (1 - 0.5*alpha)² + c_fb1 = 2*(1 - alpha) + c_fb2 = -(1 - alpha)² + + if bar_count < 7: + // Bootstrap: second-difference of raw price + cycle = (source[0] - 2*source[1] + source[2]) / 4 + else: + // Steady-state: 2-pole high-pass on smoothed input + cycle = c_hp * (smooth - 2*smooth[1] + smooth[2]) + + c_fb1 * cycle[1] + c_fb2 * cycle[2] + + trigger = cycle[1] + + return [cycle, trigger] +``` + +### Output Interpretation + +| Output | Description | +|--------|-------------| +| `cycle` | Dominant cycle component, zero-mean oscillator | +| `trigger` | One-bar delayed cycle for crossover detection | + +### Signal Interpretation + +- **Cycle crosses above trigger**: potential cycle trough (buy signal) +- **Cycle crosses below trigger**: potential cycle peak (sell signal) +- **Both near zero**: minimal cyclic energy; trend-dominated regime + +## Resources + +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. Chapter 4: "Cyber Cycle." +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. (Foundational DSP framework for markets) +- **Ehlers, J.F.** "Cybernetic Analysis." *Technical Analysis of Stocks & Commodities*, 2004. +- **Wiener, N.** *Cybernetics: Or Control and Communication in the Animal and the Machine*. MIT Press, 1948. (Origin of the "cybernetic" terminology) +- **Oppenheim, A.V. & Schafer, R.W.** *Discrete-Time Signal Processing*. Pearson, 2009. (IIR filter theory, z-domain analysis) diff --git a/lib/cycles/cg/cg.md b/lib/cycles/cg/cg.md index 13bf98d1..6bfa57c6 100644 --- a/lib/cycles/cg/cg.md +++ b/lib/cycles/cg/cg.md @@ -1,129 +1,79 @@ # CG: Ehlers Center of Gravity -> "The market's center of mass reveals where momentum shifts before price does." - -The Center of Gravity (CG) oscillator identifies potential turning points in price action using the physics concept of weighted center of mass. Developed by John Ehlers, it measures where the "weight" of prices is concentrated within a lookback window, providing leading signals for trend reversals with minimal lag. +CG identifies potential turning points using the physics concept of weighted center of mass applied to a price window. Developed by John Ehlers, the oscillator measures where the "weight" of prices is concentrated within a lookback period, producing a leading indicator that oscillates around zero with minimal lag compared to traditional moving average crossover systems. ## Historical Context -John Ehlers introduced the Center of Gravity oscillator in his 2002 book *Cybernetic Analysis for Stocks and Futures*. Ehlers, an electrical engineer turned trader, applied signal processing concepts to financial markets, focusing on creating indicators with minimal lag. - -The CG oscillator draws directly from physics: just as the center of gravity of an object determines its balance point, the CG of price determines where momentum is balanced within a window. Ehlers designed it to be a leading indicator, contrasting it with the inherent lag of traditional moving averages. +John Ehlers introduced the Center of Gravity oscillator in *Cybernetic Analysis for Stocks and Futures* (2002). Drawing from classical mechanics, the indicator applies the concept that the center of mass of a distribution reveals its balance point. In the price context, the CG identifies where momentum is concentrated within a sliding window. Unlike momentum oscillators that differentiate price (and amplify noise), CG integrates position-weighted price, providing smoother turning point detection. The indicator's leading characteristic arises from the weighting scheme: as new prices shift the balance point, the CG responds before the window's simple average would. ## Architecture & Physics -The indicator calculates a weighted center of mass that oscillates around zero using a sliding window. - ### 1. Weighted Sum (Numerator) -$$ -Num = \sum_{i=1}^{n} i \cdot P_{t-n+i} -$$ +Position-weighted accumulation over the lookback window: -where $i$ ranges from 1 (oldest) to $n$ (newest), giving more weight to recent data. +$$Num = \sum_{i=1}^{n} i \cdot P_{t-n+i}$$ + +where $i$ ranges from 1 (oldest) to $n$ (newest), giving linearly increasing weight to more recent data. ### 2. Simple Sum (Denominator) -$$ -Den = \sum_{i=1}^{n} P_{t-n+i} -$$ +$$Den = \sum_{i=1}^{n} P_{t-n+i}$$ ### 3. Center of Gravity -$$ -CG_t = \frac{Num}{Den} - \frac{n + 1}{2} -$$ +$$CG_t = \frac{Num}{Den} - \frac{n + 1}{2}$$ -The term $\frac{n + 1}{2}$ represents the geometric center of the window, centering the indicator around zero. +The term $\frac{n + 1}{2}$ is the geometric center of the window, centering the output around zero. When recent prices dominate, $CG > 0$ (bullish); when older prices dominate, $CG < 0$ (bearish). -## Performance Profile +### 4. Complexity -### Operation Count (Streaming Mode, per Bar) +Streaming uses running sums for both numerator and denominator: $O(1)$ per bar with $O(n)$ memory for the ring buffer. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| ADD (running sum update) | 2 | 1 | 2 | -| SUB (oldest value removal) | 2 | 1 | 2 | -| MUL (weight × price) | 1 | 3 | 3 | -| DIV (Num / Den) | 1 | 15 | 15 | -| **Total** | **6** | — | **~22 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(1) per bar using running sums -- **Memory:** O(n) for RingBuffer storage -- **Warmup:** n bars required +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Lookback window length | 10 | $> 0$ | -## Validation +### Pseudo-code -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not standard in TA-Lib | -| Skender | N/A | Not standard in Skender.Stock.Indicators | -| PineScript | ✅ | Validated against `ta.cg()` | +``` +function CG(source, period): + buffer ← RingBuffer(period) + runNum ← 0 // weighted sum + runDen ← 0 // simple sum -## Usage & Pitfalls + for each price in source: + buffer.Add(price) + if buffer.Count < period: continue -- **Zero crossing** signals shift in momentum balance—bullish when crossing up, bearish when crossing down -- **Positive values** indicate weight concentrated in recent prices (uptrend) -- **Negative values** indicate weight concentrated in older prices (downtrend) -- **Period of 10** is standard—smaller periods increase noise, larger periods add lag -- **Strong trends** cause CG to hang at extremes; wait for zero crossing for reversal confirmation -- **Pair with trigger line** (1-bar delay or small SMA) to reduce whipsaws + // Compute from buffer (or maintain running sums) + num = 0 + den = 0 + for i = 0 to period-1: + w = i + 1 + num += w * buffer[i] + den += buffer[i] -## API + cg = (den ≠ 0) ? (num / den) - (period + 1) / 2.0 : 0 -```mermaid -classDiagram - class Cg { - +int Period - +double Value - +bool IsHot - +Cg(int period) - +Cg(ITValuePublisher source, int period) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + emit cg ``` -### Class: `Cg` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `10` | `>0` | Lookback window for CG calculation | +| Condition | Meaning | +|-----------|---------| +| $CG > 0$ | Weight concentrated in recent prices (bullish momentum) | +| $CG < 0$ | Weight concentrated in older prices (bearish momentum) | +| Zero crossing up | Momentum shifting bullish | +| Zero crossing down | Momentum shifting bearish | +| Hanging at extremes | Strong trend in progress | -### Properties +## Resources -- `Value` (`double`): The current CG value (oscillates around 0) -- `IsHot` (`bool`): Returns `true` when warmup period is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create a 10-period CG indicator -var cg = new Cg(period: 10); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = cg.Update(new TValue(bar.Date, bar.Close)); - - if (cg.IsHot) - { - Console.WriteLine($"{bar.Date}: CG = {result.Value:F4}"); - - // Signal detection - if (result.Value > 0 && cg.Previous.Value <= 0) - Console.WriteLine(" → Bullish crossover"); - } -} - -// Batch calculation -var output = Cg.Calculate(sourceSeries, period: 10); -``` +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2002. +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. diff --git a/lib/cycles/dsp/dsp.md b/lib/cycles/dsp/dsp.md index 67efa00d..b4871509 100644 --- a/lib/cycles/dsp/dsp.md +++ b/lib/cycles/dsp/dsp.md @@ -1,145 +1,93 @@ # DSP: Ehlers Detrended Synthetic Price -> "Remove the trend, reveal the cycles." - -The Detrended Synthetic Price (DSP) indicator creates a zero-centered oscillator by subtracting a half-cycle EMA from a quarter-cycle EMA. Developed by John Ehlers, this "synthetic" price highlights underlying cyclical movement, identifying momentum shifts when the faster EMA crosses the slower one. +DSP creates a zero-centered oscillator by subtracting a half-cycle EMA from a quarter-cycle EMA, isolating the dominant cyclical component of price while cancelling longer-term trends. Developed by John Ehlers, the indicator is grounded in cycle theory rather than arbitrary period selection, making it a principled alternative to MACD for cycle-aware trading. Bias-corrected EMAs ensure accurate amplitude during warmup. ## Historical Context -John Ehlers introduced the DSP as part of his research into cycle analytics for traders. While many indicators (like MACD) use arbitrary periods (12/26), DSP is grounded in cycle theory. Ehlers posits that to effectively isolate a cycle, one should filter data based on the dominant cycle period. - -The use of period/4 and period/2 roughly corresponds to extracting the cycle's momentum while cancelling out longer-term trends. This makes DSP particularly effective for cycle-based trading strategies. +John Ehlers introduced the Detrended Synthetic Price as part of his cycle analytics framework. While MACD uses fixed periods (12/26), DSP calibrates its two EMAs to specific fractions of the dominant cycle period: quarter-cycle for the fast component and half-cycle for the slow. Subtracting aligned filters at these frequencies effectively bandpass-isolates the cycle of interest while suppressing both high-frequency noise and low-frequency trend. The "synthetic" label reflects that the output is a constructed signal that exposes cyclical energy invisible in raw price. ## Architecture & Physics -DSP utilizes a dual EMA architecture, calibrated to specific fractions of the cycle period. - ### 1. Component Periods -$$ -P_{fast} = \max(2, \text{round}(P / 4)) -$$ +From the user-specified dominant cycle period $P$: -$$ -P_{slow} = \max(3, \text{round}(P / 2)) -$$ +$$P_{fast} = \max(2, \lfloor P / 4 + 0.5 \rfloor)$$ + +$$P_{slow} = \max(3, \lfloor P / 2 + 0.5 \rfloor)$$ ### 2. Alpha Coefficients -$$ -\alpha_{fast} = \frac{2}{P_{fast} + 1} -$$ +Standard EMA smoothing factors: -$$ -\alpha_{slow} = \frac{2}{P_{slow} + 1} -$$ +$$\alpha_{fast} = \frac{2}{P_{fast} + 1}, \qquad \alpha_{slow} = \frac{2}{P_{slow} + 1}$$ -### 3. EMA Updates (with Bias Correction) +### 3. EMA Updates with Bias Correction -$$ -EMA_{raw} = \alpha \cdot Price + (1 - \alpha) \cdot EMA_{raw\_prev} -$$ +Raw EMA recursion: -$$ -EMA_{corrected} = \frac{EMA_{raw}}{1 - (1-\alpha)^n} -$$ +$$EMA_{raw,t} = \alpha \cdot P_t + (1 - \alpha) \cdot EMA_{raw,t-1}$$ -### 4. DSP Calculation +Warmup bias correction (prevents initial distortion): -$$ -DSP = EMA_{fast} - EMA_{slow} -$$ +$$EMA_t = \frac{EMA_{raw,t}}{1 - (1 - \alpha)^n}$$ -## Performance Profile +where $n$ is the number of bars processed. -### Operation Count (Streaming Mode, per Bar) +### 4. DSP Output -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| FMA (EMA updates) | 2 | 4 | 8 | -| MUL (decay factors) | 2 | 3 | 6 | -| DIV (bias correction) | 2 | 15 | 30 | -| SUB (DSP = fast - slow) | 1 | 1 | 1 | -| **Total** | **7** | — | **~45 cycles** | +$$DSP_t = EMA_{fast,t} - EMA_{slow,t}$$ -### Complexity Analysis +### 5. Complexity -- **Streaming:** O(1) per bar—fixed calculation depth -- **Memory:** O(1)—only EMA state variables -- **Warmup:** ~2 × slow period for convergence +$O(1)$ per bar with $O(1)$ memory. Two EMA state variables plus two bias correction accumulators. -## Validation +## Mathematical Foundation -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not standard | -| Skender | N/A | Not standard | -| PineScript | ✅ | Matches Ehlers' reference logic | +### Parameters -## Usage & Pitfalls +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Dominant cycle period | 40 | $\geq 4$ | -- **Zero crossing** indicates cycle phase change—above zero is bullish, below zero is bearish -- **Period should match market cycle**—if market cycle is 20 bars, use period 20 not 40 -- **Not normalized**—amplitude reflects absolute price difference, varies by asset -- **Whipsaws** occur in ranging markets with cycles shorter than the setting -- **Divergence** (higher price highs with lower DSP highs) suggests cycle energy loss -- **Use FusedMultiplyAdd** for optimal precision in EMA recursion +### Pseudo-code -## API +``` +function DSP(source, period): + pFast ← max(2, round(period / 4)) + pSlow ← max(3, round(period / 2)) + αFast ← 2 / (pFast + 1) + αSlow ← 2 / (pSlow + 1) -```mermaid -classDiagram - class Dsp { - +int Period - +double Value - +bool IsHot - +Dsp(int period) - +Dsp(ITValuePublisher source, int period) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + emaFastRaw ← 0 + emaSlowRaw ← 0 + decayFast ← 1.0 // (1 - αFast)^n + decaySlow ← 1.0 // (1 - αSlow)^n + + for each price in source: + emaFastRaw ← FMA(αFast, price, (1 - αFast) * emaFastRaw) + emaSlowRaw ← FMA(αSlow, price, (1 - αSlow) * emaSlowRaw) + + decayFast *= (1 - αFast) + decaySlow *= (1 - αSlow) + + emaFast ← emaFastRaw / (1 - decayFast) + emaSlow ← emaSlowRaw / (1 - decaySlow) + + dsp ← emaFast - emaSlow + emit dsp ``` -### Class: `Dsp` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `40` | `≥4` | Dominant cycle period | +| Condition | Meaning | +|-----------|---------| +| $DSP > 0$ | Fast EMA above slow: bullish cycle phase | +| $DSP < 0$ | Fast EMA below slow: bearish cycle phase | +| Zero crossing | Cycle phase transition point | +| Divergence from price | Cycle energy waning; potential trend exhaustion | -### Properties +## Resources -- `Value` (`double`): The current DSP value (oscillates around 0) -- `IsHot` (`bool`): Returns `true` when warmup is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create DSP for a 40-bar cycle -var dsp = new Dsp(period: 40); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = dsp.Update(new TValue(bar.Date, bar.Close)); - - if (dsp.IsHot) - { - Console.WriteLine($"{bar.Date}: DSP = {result.Value:F4}"); - - // Cycle phase detection - if (result.Value > 0) - Console.WriteLine(" → Bullish cycle phase"); - else - Console.WriteLine(" → Bearish cycle phase"); - } -} - -// Batch calculation -var output = Dsp.Calculate(sourceSeries, period: 40); -``` +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. diff --git a/lib/cycles/eacp/eacp.md b/lib/cycles/eacp/eacp.md index c3658ab3..c862dbd2 100644 --- a/lib/cycles/eacp/eacp.md +++ b/lib/cycles/eacp/eacp.md @@ -1,145 +1,118 @@ # EACP: Ehlers Autocorrelation Periodogram -> "The autocorrelation periodogram uses the Wiener-Khinchin theorem to transform autocorrelation into spectral density, revealing the dominant cycle hidden within price noise." - -The Ehlers Autocorrelation Periodogram (EACP) is an advanced spectral analysis tool that estimates the dominant cycle period of a financial time series. It computes autocorrelation across various lags and transforms this into a power spectrum to identify the most potent frequency, enabling adaptive indicator tuning. +EACP estimates the dominant cycle period of a financial time series by computing autocorrelation across multiple lags and transforming the result into a power spectrum via the Wiener-Khinchin theorem. The output is a continuously updating cycle period measurement (in bars) that can adaptively tune other indicators to the market's current rhythm, making fixed-period assumptions unnecessary. ## Historical Context -John Ehlers introduced the Autocorrelation Periodogram to the trading community as a solution for measuring market cycles. He leveraged the **Wiener-Khinchin theorem**, which links the time domain (autocorrelation) to the frequency domain (power spectral density). - -This allows traders to detect the current "heartbeat" of the market—the dominant cycle—which can then tune other indicators (like RSI or Stochastic) to the current market speed, creating truly adaptive trading systems. +John Ehlers introduced the Autocorrelation Periodogram to solve the fundamental problem of cycle measurement in noisy financial data. Traditional spectral methods (FFT) assume stationarity and require long data windows, making them impractical for real-time trading. Ehlers leveraged the Wiener-Khinchin theorem, which establishes that a signal's autocorrelation function and its power spectral density form a Fourier transform pair. By computing autocorrelation in the time domain and transforming to frequency via a discrete cosine transform, the algorithm identifies spectral peaks corresponding to dominant periodicities. The center-of-gravity weighting of spectral peaks provides a robust, noise-tolerant period estimate. This enables truly adaptive trading systems where RSI, Stochastic, or moving average periods track the market's actual cycle length rather than relying on fixed parameters. ## Architecture & Physics -The algorithm proceeds in three major stages: Pre-filtering, Correlation, and Spectral Analysis. - ### 1. Signal Pre-processing -High-pass filter removes DC component and trends; Super-smoother attenuates aliasing noise. +A high-pass filter removes the DC (trend) component, and a Super-Smoother filter attenuates aliasing noise above the Nyquist frequency: -$$ -HP_t = (1 - \alpha_{HP}/2)^2 \cdot (P_t - 2P_{t-1} + P_{t-2}) + 2(1-\alpha_{HP}) \cdot HP_{t-1} - (1-\alpha_{HP})^2 \cdot HP_{t-2} -$$ +$$HP_t = (1 - \alpha_{HP}/2)^2 (P_t - 2P_{t-1} + P_{t-2}) + 2(1 - \alpha_{HP}) HP_{t-1} - (1 - \alpha_{HP})^2 HP_{t-2}$$ + +The Super-Smoother then applies a 2-pole Butterworth low-pass to $HP_t$. ### 2. Autocorrelation -For every lag $k$ from 0 to MaxPeriod: +For each lag $k$ from 0 to MaxPeriod, the normalized Pearson autocorrelation is computed over an averaging window of $M$ samples: -$$ -R_k = \frac{\sum (x_i - \bar{x})(x_{i-k} - \bar{x})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (x_{i-k} - \bar{x})^2}} -$$ +$$R_k = \frac{\sum_{i=0}^{M-1} (x_i - \bar{x})(x_{i-k} - \bar{x})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (x_{i-k} - \bar{x})^2}}$$ -A high correlation at lag 20 implies a 20-bar cycle. +A high $R_k$ at lag 20 implies a 20-bar cycle is present. -### 3. Dominant Cycle Extraction +### 3. Power Spectrum (DFT of Autocorrelation) -Power spectrum via DFT, smoothed with exponential decay: +For each candidate period $p$ in [MinPeriod, MaxPeriod]: -$$ -S_p = 0.2 \cdot P_p^2 + 0.8 \cdot S_{p-1} -$$ +$$P_p = \left(\sum_{k=0}^{M-1} R_k \cos\!\left(\frac{2\pi k}{p}\right)\right)^2$$ -Dominant cycle as center of gravity of spectral peaks: +Smoothed with exponential decay: $S_p = 0.2 \cdot P_p + 0.8 \cdot S_{p,prev}$ -$$ -DC = \frac{\sum Power_i \cdot Period_i}{\sum Power_i} -$$ +### 4. Dominant Cycle Extraction -## Performance Profile +Center-of-gravity weighting across spectral peaks: -### Operation Count (Streaming Mode, per Bar) +$$DC = \frac{\sum S_p \cdot p}{\sum S_p}$$ -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| Correlation loop | N×M | 5 | 5NM | -| DFT inner loop | N×N | 8 | 8N² | -| Power smoothing | N | 4 | 4N | -| AGC normalization | N | 3 | 3N | -| **Total** | — | — | **O(N²)** | +### 5. Optional Enhancement -### Complexity Analysis +When `enhance=true`, spectral values are cubed before CG weighting, sharpening peaks but increasing sensitivity to noise. -- **Streaming:** O(N × M) where N=period range, M=averaging length -- **Memory:** O(N) for correlation and power arrays -- **Warmup:** ~2 × MaxPeriod bars +### 6. Complexity -**Note:** This is one of the most computationally expensive indicators due to nested loops. +$O(N \times M)$ per bar where $N$ is the period range and $M$ is the averaging length. This is one of the most computationally expensive indicators due to nested correlation and DFT loops. Memory is $O(N)$ for correlation and power arrays. -## Validation +## Mathematical Foundation -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not implemented | -| Skender | N/A | Not implemented | -| PineScript | ✅ | Validated against Ehlers' reference code | +### Parameters -## Usage & Pitfalls +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `minPeriod` | Minimum period to evaluate | 8 | $\geq 3$ | +| `maxPeriod` | Maximum period to evaluate | 48 | $> minPeriod$ | +| `enhance` | Apply cubic emphasis to spectral peaks | true | | -- **Primary use is tuning**—provides `period` parameter for other indicators (RSI, Stochastic) -- **Requires substantial warmup** (~2 × MaxPeriod) to stabilize spectrum -- **Struggles with rapid cycle changes**—period jumping from 10 to 40 in few bars -- **Compute on bar close only**—avoid running on every tick for many symbols -- **Enhance mode** (`enhance=true`) sharpens peaks but can cause jumpiness -- **Pure sine wave** of period 20 correctly converges to ~20.0 +### Pseudo-code -## API +``` +function EACP(source, minPeriod, maxPeriod, enhance): + N ← maxPeriod - minPeriod + 1 + M ← maxPeriod // averaging window + hpBuf ← HighPassFilter(source) + ssfBuf ← SuperSmoother(hpBuf) -```mermaid -classDiagram - class Eacp { - +int MinPeriod - +int MaxPeriod - +double DominantCycle - +double NormalizedPower - +bool IsHot - +Eacp(int minPeriod, int maxPeriod, bool enhance) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + power[N] ← {0} + smoothPower[N] ← {0} + + for each bar: + // Autocorrelation for each lag + corr[0..maxPeriod] ← PearsonAutocorrelation(ssfBuf, M) + + // DFT: convert autocorrelation to power spectrum + for p = minPeriod to maxPeriod: + cosPower ← 0 + for k = 0 to M-1: + cosPower += corr[k] * cos(2π * k / p) + power[p] ← cosPower² + + // Exponential smoothing of spectrum + for p = minPeriod to maxPeriod: + smoothPower[p] ← 0.2 * power[p] + 0.8 * smoothPower[p] + + // Optional cubic enhancement + if enhance: + for p: smoothPower[p] ← smoothPower[p]³ + + // AGC normalization + maxPow ← max(smoothPower) + for p: smoothPower[p] /= maxPow // normalize to [0, 1] + + // Center-of-gravity dominant cycle + num ← 0; den ← 0 + for p = minPeriod to maxPeriod: + num += smoothPower[p] * p + den += smoothPower[p] + dominantCycle ← (den > 0) ? num / den : (minPeriod + maxPeriod) / 2 + + emit dominantCycle ``` -### Class: `Eacp` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `minPeriod` | `int` | `8` | `≥3` | Minimum period to evaluate | -| `maxPeriod` | `int` | `48` | `>minPeriod` | Maximum period to evaluate | -| `enhance` | `bool` | `true` | — | Apply cubic emphasis to peaks | +| Output | Meaning | +|--------|---------| +| `dominantCycle` | Estimated dominant period in bars (use to tune other indicators) | +| Stable value | Market exhibiting regular cyclical behavior | +| Rapidly changing value | Market transitioning between regimes | +| Pegged at maxPeriod | No clear cycle detected; likely trending | -### Properties +## Resources -- `DominantCycle` (`double`): Estimated dominant cycle period in bars -- `NormalizedPower` (`double`): Power at dominant period (0-1) -- `IsHot` (`bool`): Returns `true` when warmup is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Configure for cycles between 8 and 48 bars -var eacp = new Eacp(minPeriod: 8, maxPeriod: 48, enhance: true); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = eacp.Update(new TValue(bar.Date, bar.Close)); - - if (eacp.IsHot) - { - Console.WriteLine($"{bar.Date}: Dominant Cycle = {eacp.DominantCycle:F1} bars"); - - // Use cycle to tune RSI - int adaptivePeriod = (int)(eacp.DominantCycle / 2); - var adaptiveRsi = new Rsi(adaptivePeriod); - } -} - -// Batch calculation -var output = Eacp.Calculate(sourceSeries, minPeriod: 8, maxPeriod: 48); -``` +- **Ehlers, J.F.** *Cycle Analytics for Traders*. Wiley, 2013. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Wiener, N.** "Generalized Harmonic Analysis." *Acta Mathematica*, 55(1), 1930. +- **Khinchin, A.** "Korrelationstheorie der stationären stochastischen Prozesse." *Mathematische Annalen*, 109(1), 1934. diff --git a/lib/cycles/ebsw/ebsw.md b/lib/cycles/ebsw/ebsw.md index 7ef4c1b4..900282e8 100644 --- a/lib/cycles/ebsw/ebsw.md +++ b/lib/cycles/ebsw/ebsw.md @@ -1,151 +1,117 @@ # EBSW: Ehlers Even Better Sinewave -> "When you combine a high-pass filter with a super-smoother, you get cleaner cycles with automatic gain control." - -The Even Better Sinewave (EBSW) indicator is a refined cycle oscillator developed by John Ehlers. It combines a high-pass filter (trend removal) with a Super-Smoother filter (noise removal) and Automatic Gain Control to produce an oscillator normalized between -1 and +1 that synthesizes a clean sine wave from price action. +EBSW is a refined cycle oscillator that combines a high-pass filter (trend removal), a Super-Smoother filter (noise removal), and Automatic Gain Control to produce a normalized $[-1, +1]$ output representing the current position within the dominant market cycle. Developed by John Ehlers as an improvement over the original Hilbert Transform SineWave, it provides cleaner turning point detection without requiring complex phase extraction mathematics. ## Historical Context -Ehlers' original "Sinewave" indicator relied on the Hilbert Transform to extract phase. However, he found that direct Hilbert Transforms were often unstable on real market data. The "Even Better" Sinewave simplifies the approach: instead of complex phase math, it uses a tuned bandpass filter (High-Pass + Low-Pass) to isolate the wave, then normalizes it. - -This resulted in a more robust tool for identifying turning points in both trending and ranging markets, first published in *Cycle Analytics for Traders*. +Ehlers' original SineWave indicator relied on the Hilbert Transform to extract phase, but direct Hilbert Transforms proved unstable on real market data due to amplitude sensitivity and convergence issues during strong trends. The "Even Better" SineWave, published in *Cycle Analytics for Traders* (2013), simplifies the approach: instead of complex phase math, it uses a tuned bandpass filter (high-pass cascaded with a 2-pole low-pass) to isolate the dominant cycle, then normalizes the result via RMS-based AGC. The 3-bar averaging in both the wave and power calculations acts as a simple anti-aliasing stage. The result is a more robust tool for identifying turning points in both trending and ranging markets. ## Architecture & Physics -The transformation pipeline consists of four distinct stages. - ### 1. High-Pass Filter (Trend Removal) -$$ -\alpha_1 = \frac{1 - \sin(2\pi/HP)}{\cos(2\pi/HP)} -$$ +A single-pole high-pass filter removes frequencies below the cutoff: -$$ -HP_t = 0.5 (1 + \alpha_1)(P_t - P_{t-1}) + \alpha_1 \cdot HP_{t-1} -$$ +$$\alpha_1 = \frac{1 - \sin(2\pi / P_{HP})}{\cos(2\pi / P_{HP})}$$ + +$$HP_t = \frac{1 + \alpha_1}{2}(P_t - P_{t-1}) + \alpha_1 \cdot HP_{t-1}$$ ### 2. Super-Smoother Filter (Noise Removal) -$$ -\alpha_2 = e^{-\sqrt{2}\pi / SSF} -$$ +A 2-pole Butterworth low-pass attenuates high-frequency aliasing noise: -$$ -Filt_t = \frac{1 - 2\alpha_2\cos(\sqrt{2}\pi/SSF) + \alpha_2^2}{2}(HP_t + HP_{t-1}) + 2\alpha_2\cos(\sqrt{2}\pi/SSF) \cdot Filt_{t-1} - \alpha_2^2 \cdot Filt_{t-2} -$$ +$$a = e^{-\sqrt{2}\pi / P_{SSF}}$$ -### 3. Wave & Power Calculation +$$b = 2a \cos(\sqrt{2}\pi / P_{SSF})$$ -$$ -Wave = \frac{Filt_t + Filt_{t-1} + Filt_{t-2}}{3} -$$ +$$Filt_t = \frac{(1 - b + a^2)}{2}(HP_t + HP_{t-1}) + b \cdot Filt_{t-1} - a^2 \cdot Filt_{t-2}$$ -$$ -Power = \frac{Filt_t^2 + Filt_{t-1}^2 + Filt_{t-2}^2}{3} -$$ +### 3. Wave and Power Calculation + +Three-bar averaging for both signal and energy: + +$$Wave_t = \frac{Filt_t + Filt_{t-1} + Filt_{t-2}}{3}$$ + +$$Power_t = \frac{Filt_t^2 + Filt_{t-1}^2 + Filt_{t-2}^2}{3}$$ ### 4. Normalization (AGC) -$$ -EBSW = \frac{Wave}{\sqrt{Power}} -$$ +$$EBSW_t = \frac{Wave_t}{\sqrt{Power_t}}$$ -Result is clamped to $\pm 1$. +Result is clamped to $[-1, +1]$. When $Power \approx 0$, output is zero. -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, per Bar) +$O(1)$ per bar. Fixed cascaded IIR filters with $O(1)$ memory (only filter state variables and 3-bar history for wave/power). -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| FMA (filter updates) | 4 | 4 | 16 | -| MUL (power calc) | 3 | 3 | 9 | -| ADD/SUB | 6 | 1 | 6 | -| DIV | 1 | 15 | 15 | -| SQRT | 1 | 12 | 12 | -| **Total** | **15** | — | **~58 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(1) per bar—fixed cascaded IIR filters -- **Memory:** O(1)—only filter state variables -- **Warmup:** ~hpLength bars for HP filter convergence +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `hpLength` | High-pass filter period (detrending cutoff) | 40 | $\geq 1$, $\neq 4$ | +| `ssfLength` | Super-smoother filter period (noise cutoff) | 10 | $\geq 1$ | -## Validation +### Precomputed Coefficients -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not standard | -| Skender | N/A | Not standard | -| PineScript | ✅ | Matches `ebsw` script | -| Reference | ✅ | Matches *Cycle Analytics for Traders* logic | - -## Usage & Pitfalls - -- **Range is -1 to +1**—zero crossings signal cycle phase changes -- **HP Length is critical**—should match expected market cycle (e.g., 40 bars) -- **Too short HP Length** filters out everything as "trend" -- **AGC amplifies noise** in low volatility—verify with price action -- **Strong step moves** cause railing at ±1 for extended periods -- **Buy at valley** (EBSW turning up from -0.8), **sell at peak** (turning down from +0.8) - -## API - -```mermaid -classDiagram - class Ebsw { - +int HpLength - +int SsfLength - +double Value - +bool IsHot - +Ebsw(int hpLength, int ssfLength) - +Ebsw(ITValuePublisher source, int hpLength, int ssfLength) - +TValue Update(TValue input, bool isNew) - +void Reset() - } +``` +α₁ = (1 - sin(2π/hpLength)) / cos(2π/hpLength) +a = exp(-√2·π / ssfLength) +b = 2·a·cos(√2·π / ssfLength) +c₁ = (1 - b + a²) / 2 ``` -### Class: `Ebsw` +### Pseudo-code -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `hpLength` | `int` | `40` | `≥1, ≠4` | High-pass filter period (detrending) | -| `ssfLength` | `int` | `10` | `≥1` | Super-smoother filter period | - -### Properties - -- `Value` (`double`): The current EBSW value (bounded -1 to +1) -- `IsHot` (`bool`): Returns `true` when warmup is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create EBSW for 40-bar cycle with 10-bar smoothing -var ebsw = new Ebsw(hpLength: 40, ssfLength: 10); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = ebsw.Update(new TValue(bar.Date, bar.Close)); - - if (ebsw.IsHot) - { - Console.WriteLine($"{bar.Date}: EBSW = {result.Value:F4}"); - - // Cycle turning point detection - if (result.Value < -0.8 && result.Value > ebsw.Previous.Value) - Console.WriteLine(" → Potential cycle bottom"); - else if (result.Value > 0.8 && result.Value < ebsw.Previous.Value) - Console.WriteLine(" → Potential cycle top"); - } -} - -// Batch calculation -var output = Ebsw.Calculate(sourceSeries, hpLength: 40, ssfLength: 10); ``` +function EBSW(source, hpLength, ssfLength): + // Precompute HP coefficient + α₁ ← (1 - sin(2π/hpLength)) / cos(2π/hpLength) + + // Precompute SSF coefficients + a ← exp(-√2·π / ssfLength) + b ← 2·a·cos(√2·π / ssfLength) + c₁ ← (1 - b + a²) / 2 + + hp_prev ← 0; p_prev ← 0 + filt_1 ← 0; filt_2 ← 0 + + for each price in source: + // High-pass filter + hp ← 0.5·(1 + α₁)·(price - p_prev) + α₁·hp_prev + + // Super-smoother + filt ← c₁·(hp + hp_prev) + b·filt_1 - a²·filt_2 + + // Wave (3-bar average of filtered signal) + wave ← (filt + filt_1 + filt_2) / 3 + + // Power (3-bar RMS²) + power ← (filt² + filt_1² + filt_2²) / 3 + + // AGC normalization + ebsw ← (power > 0) ? wave / √power : 0 + ebsw ← clamp(ebsw, -1, +1) + + // Shift state + hp_prev ← hp; p_prev ← price + filt_2 ← filt_1; filt_1 ← filt + + emit ebsw +``` + +### Output Interpretation + +| Condition | Meaning | +|-----------|---------| +| $EBSW \approx +1$ | Cycle peak (potential short entry) | +| $EBSW \approx -1$ | Cycle trough (potential long entry) | +| Zero crossing up | Bullish phase transition | +| Zero crossing down | Bearish phase transition | +| Railing at $\pm 1$ | Strong directional move overwhelming cycle | + +## Resources + +- **Ehlers, J.F.** *Cycle Analytics for Traders*. Wiley, 2013. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. diff --git a/lib/cycles/homod/homod.md b/lib/cycles/homod/homod.md index b128a380..f3f54ecc 100644 --- a/lib/cycles/homod/homod.md +++ b/lib/cycles/homod/homod.md @@ -1,155 +1,125 @@ # HOMOD: Ehlers Homodyne Discriminator -> "The homodyne discriminator reveals instantaneous frequency by multiplying a signal with its delayed self — the phase rotation between samples directly encodes the cycle period." - -The Homodyne Discriminator (HOMOD) estimates the dominant cycle period of a market using homodyne mixing—multiplying the signal by a delayed version of itself. This technique exposes the angular phase change between bars, allowing calculation of the instantaneous period at every time step. +HOMOD estimates the dominant cycle period of a market using homodyne mixing, a technique from radio engineering where a signal is multiplied by a delayed copy of itself to expose the angular phase change between samples. The output is a continuously varying period measurement (in bars) that tracks the market's instantaneous cycle length, enabling adaptive indicator tuning. Developed by John Ehlers, it offers better noise rejection and stability than the raw Hilbert Transform period estimator. ## Historical Context -In *Rocket Science for Traders* and *Cybernetic Analysis for Stocks and Futures*, John Ehlers introduced signal processing concepts novel to technical analysis. The Homodyne Discriminator was presented as a superior alternative to the Hilbert Transform Discriminator for cycle measurement. - -It offers better noise rejection and stability while maintaining reasonable responsiveness, making it practical for real-time trading applications. +John Ehlers introduced the Homodyne Discriminator in *Rocket Science for Traders* (2001) and refined it in *Cybernetic Analysis for Stocks and Futures* (2004). In RF engineering, homodyne detection multiplies a signal with a local oscillator at the same frequency to extract phase information. Ehlers adapted this by multiplying the complex analytic signal $z_t = I_t + jQ_t$ by its own conjugate delayed by one bar, yielding the phase rotation per sample. The angular velocity directly encodes the instantaneous frequency (and hence period). Compared to the raw Hilbert Transform discriminator which estimates phase absolutely, homodyne detection measures phase *differences*, making it less sensitive to amplitude variations and more stable during noisy market conditions. ## Architecture & Physics -The algorithm is a complex pipeline of filters and transformations designed to isolate the analytic signal. - ### 1. Pre-Processing (4-Bar WMA) -$$ -Smooth = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10} -$$ +$$Smooth_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}$$ ### 2. Analytic Signal Generation -In-Phase (I) and Quadrature (Q) components via Hilbert Transform: +The Hilbert Transform FIR generates quadrature components using Ehlers' 4-tap approximation with coefficients $A = 0.0962$ and $B = 0.5769$: -$$ -I_2 = I_1 - JQ -$$ +$$Detrender_t = A \cdot Smooth_t + B \cdot Smooth_{t-2} - B \cdot Smooth_{t-4} - A \cdot Smooth_{t-6}$$ -$$ -Q_2 = Q_1 + JI -$$ +In-Phase ($I_1$) is the detrender delayed by 3 bars. Quadrature ($Q_1$) is the Hilbert transform of the detrender. Both are further refined: -Smoothed with EMA (α = 0.2). +$$I_2 = I_1 - jQ, \qquad Q_2 = Q_1 + jI$$ + +Smoothed with EMA ($\alpha = 0.2$). ### 3. Homodyne Mixing -Multiplying complex signal $z_t$ by its conjugate delayed by one bar: +Multiplying the complex signal by its one-bar-delayed conjugate: -$$ -Real = (I_2 \cdot I_{2,prev}) + (Q_2 \cdot Q_{2,prev}) -$$ +$$Re_t = I_{2,t} \cdot I_{2,t-1} + Q_{2,t} \cdot Q_{2,t-1}$$ -$$ -Imag = (I_2 \cdot Q_{2,prev}) - (Q_2 \cdot I_{2,prev}) -$$ +$$Im_t = I_{2,t} \cdot Q_{2,t-1} - Q_{2,t} \cdot I_{2,t-1}$$ + +Both smoothed with EMA ($\alpha = 0.2$). ### 4. Period Extraction -$$ -\theta = \operatorname{atan2}(Imag, Real) -$$ +$$\theta = \operatorname{atan2}(Im_t, Re_t)$$ -$$ -Period = \frac{2\pi}{\theta} -$$ +$$Period_{raw} = \frac{2\pi}{\theta}$$ -Clamped to [MinPeriod, MaxPeriod] and smoothed. +Clamped to $[MinPeriod, MaxPeriod]$ and smoothed with EMA ($\alpha = 0.33$). -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, per Bar) +$O(1)$ per bar with $O(1)$ memory. The pipeline consists entirely of fixed-depth IIR filters and short delay lines. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| MUL (Hilbert taps) | 14 | 3 | 42 | -| MUL (homodyne mix) | 4 | 3 | 12 | -| ADD/SUB | 20 | 1 | 20 | -| ATAN2 | 1 | 25 | 25 | -| DIV | 2 | 15 | 30 | -| **Total** | **41** | — | **~129 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(1) per bar—fixed filter depth -- **Memory:** O(1)—state struct with history variables -- **Warmup:** ~2 × MaxPeriod bars for convergence +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `minPeriod` | Minimum detectable period | 6.0 | $> 0$ | +| `maxPeriod` | Maximum detectable period | 50.0 | $> minPeriod$ | -## Validation +### Pseudo-code -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not implemented | -| Skender | N/A | Not implemented | -| PineScript | ✅ | Matches Ehlers' reference code | +``` +function HOMOD(source, minPeriod, maxPeriod): + A ← 0.0962; B ← 0.5769 + smoothBuf ← CircularBuffer(7) + detBuf ← CircularBuffer(7) -## Usage & Pitfalls + I2_prev ← 0; Q2_prev ← 0 + Re_prev ← 0; Im_prev ← 0 + period_prev ← (minPeriod + maxPeriod) / 2 -- **Output is period in bars**—not an oscillator like RSI, but a measurement like ATR -- **Long settling time** (~2 × MaxPeriod)—early values unreliable -- **Trending markets** make "cycle" ill-defined—period drifts to MaxPeriod -- **Check for cycling** (ADX or trend filter) before trusting period values -- **High noise causes jitter**—pre-smooth extremely noisy data -- **Use for adaptive tuning**: `Stochastic(length: homod.DominantCycle)` + for each price in source: + // 4-bar WMA + smooth ← (4·price + 3·p[1] + 2·p[2] + p[3]) / 10 + smoothBuf.Add(smooth) -## API + // Detrender (Hilbert FIR) + det ← A·smooth[0] + B·smooth[2] - B·smooth[4] - A·smooth[6] + detBuf.Add(det) -```mermaid -classDiagram - class Homod { - +double MinPeriod - +double MaxPeriod - +double DominantCycle - +bool IsHot - +Homod(double minPeriod, double maxPeriod) - +Homod(ITValuePublisher source, double minPeriod, double maxPeriod) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + // I1 = det[3], Q1 = Hilbert(det) + I1 ← det[3] + Q1 ← A·det[0] + B·det[2] - B·det[4] - A·det[6] + + // Hilbert of I1 and Q1 + jI ← HilbertFIR(I1_history) + jQ ← HilbertFIR(Q1_history) + + // Phasor components (smoothed) + I2 ← 0.2·(I1 - jQ) + 0.8·I2_prev + Q2 ← 0.2·(Q1 + jI) + 0.8·Q2_prev + + // Homodyne mixing + re ← 0.2·(I2·I2_prev + Q2·Q2_prev) + 0.8·Re_prev + im ← 0.2·(I2·Q2_prev - Q2·I2_prev) + 0.8·Im_prev + + // Period extraction + if im ≠ 0 and re ≠ 0: + period ← 2π / atan2(im, re) + else: + period ← period_prev + + period ← clamp(period, minPeriod, maxPeriod) + period ← 0.33·period + 0.67·period_prev + + // Update state + I2_prev ← I2; Q2_prev ← Q2 + Re_prev ← re; Im_prev ← im + period_prev ← period + + emit period ``` -### Class: `Homod` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `minPeriod` | `double` | `6.0` | `>0` | Minimum period to detect | -| `maxPeriod` | `double` | `50.0` | `>minPeriod` | Maximum period to detect | +| Output | Meaning | +|--------|---------| +| `period` | Dominant cycle length in bars | +| Stable period | Market exhibiting regular cyclical behavior | +| Period drifting to maxPeriod | Trending market; cycle measurement unreliable | +| Rapidly fluctuating period | Noisy or transitioning market regime | -### Properties +## Resources -- `DominantCycle` (`double`): Current dominant cycle period in bars -- `IsHot` (`bool`): Returns `true` when warmup is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Configure for cycles between 6 and 50 bars -var homod = new Homod(minPeriod: 6, maxPeriod: 50); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = homod.Update(new TValue(bar.Date, bar.Close)); - - if (homod.IsHot) - { - double period = homod.DominantCycle; - Console.WriteLine($"{bar.Date}: Dominant Cycle = {period:F1} bars"); - - // Use cycle to tune Stochastic - int adaptiveLength = (int)Math.Round(period); - var adaptiveStoch = new Stochastic(adaptiveLength); - } -} - -// Batch calculation -var output = Homod.Calculate(sourceSeries, minPeriod: 6, maxPeriod: 50); -``` +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Haykin, S.** *Communication Systems*. 4th ed., Wiley, 2001. (Homodyne detection theory) diff --git a/lib/cycles/ht_dcperiod/HtDcperiod.md b/lib/cycles/ht_dcperiod/HtDcperiod.md index b9fe3ce8..36dd8ffc 100644 --- a/lib/cycles/ht_dcperiod/HtDcperiod.md +++ b/lib/cycles/ht_dcperiod/HtDcperiod.md @@ -1,143 +1,109 @@ # HT_DCPERIOD: Ehlers Hilbert Transform Dominant Cycle Period -> "Knowing the cycle period is the master key—it calibrates other indicators to the market's current rhythm." - -HT_DCPERIOD estimates the period of the dominant market cycle using Ehlers' Hilbert Transform cascade. The indicator measures the instantaneous period based on the rate of change of the phase angle, providing a variable period length (typically 6-50 bars) that dynamically tunes other indicators. +HT_DCPERIOD estimates the period of the dominant market cycle using Ehlers' Hilbert Transform cascade. The algorithm extracts In-Phase and Quadrature components from price, computes instantaneous phase via homodyne discrimination, and derives the period from the phase rate of change. Output is a continuously varying period (typically 6-50 bars) compatible with TA-Lib's `HT_DCPERIOD` function. The indicator enables dynamic tuning of other indicators to the market's actual rhythm rather than fixed-parameter assumptions. ## Historical Context -John Ehlers introduced the Hilbert Transform Dominant Cycle Period in *Rocket Science for Traders* (2001). The goal was to overcome the limitations of fixed-period indicators by measuring the actual cycle length present in the data. - -TA-Lib implements HT_DCPERIOD using Ehlers' specific coefficients (A = 0.0962, B = 0.5769) and smoothing algorithms. QuanTAlib matches the TA-Lib implementation within floating-point tolerance. +John Ehlers introduced the Hilbert Transform Dominant Cycle Period in *Rocket Science for Traders* (2001) to overcome the fundamental limitation of fixed-period technical indicators. Markets cycle at variable rates, yet traditional indicators like RSI-14 or SMA-20 assume constant periodicity. HT_DCPERIOD measures the actual cycle length present in price data, enabling adaptive parameter selection. The TA-Lib implementation codified specific Hilbert Transform coefficients ($A = 0.0962$, $B = 0.5769$) and smoothing algorithms that became the de facto standard. QuanTAlib matches the TA-Lib implementation within floating-point tolerance, including the 32-bar lookback convention. ## Architecture & Physics -The algorithm follows a complex pipeline to extract cycle period from phase information. - ### 1. WMA Price Smoothing -$$ -SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10} -$$ +A 4-bar weighted moving average removes Nyquist-frequency noise: -### 2. Hilbert Transform Components +$$SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}$$ -The Hilbert Transform generates In-Phase (I) and Quadrature (Q) components: +### 2. Hilbert Transform FIR -- **Detrender**: Removes DC component and trend -- **Q1**: Quadrature component of detrender -- **I1**: In-Phase component (delayed detrender) -- **jI, jQ**: Hilbert transforms of I1 and Q1 +The discrete Hilbert approximation generates the detrender and quadrature components using coefficients $A = 0.0962$ and $B = 0.5769$. The detrender, $Q_1$, and Hilbert transforms of $I_1$ and $Q_1$ ($jI$, $jQ$) are all computed with the same 4-tap FIR structure. ### 3. Phasor Components -$$ -I2_t = I1_t - jQ_t -$$ +$$I_{2,t} = I_{1,t} - jQ_t, \qquad Q_{2,t} = Q_{1,t} + jI_t$$ -$$ -Q2_t = Q1_t + jI_t -$$ +Both smoothed with EMA ($\alpha = 0.2$). -Smoothed with EMA (α = 0.2). +### 4. Homodyne Period Extraction -### 4. Period Extraction +$$Re_t = 0.2(I_{2,t} \cdot I_{2,t-1} + Q_{2,t} \cdot Q_{2,t-1}) + 0.8 \cdot Re_{t-1}$$ -$$ -Period_t = \frac{2\pi}{\arctan(Im_t / Re_t)} -$$ +$$Im_t = 0.2(I_{2,t} \cdot Q_{2,t-1} - Q_{2,t} \cdot I_{2,t-1}) + 0.8 \cdot Im_{t-1}$$ -Clamped to [6, 50] and smoothed with EMA (α = 0.33). +$$Period_{raw} = \frac{2\pi}{\arctan(Im_t / Re_t)}$$ -## Performance Profile +### 5. Period Smoothing -### Operation Count (Streaming Mode, per Bar) +Clamped to $[6, 50]$ bars, then smoothed: -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| MUL (Hilbert taps) | 28 | 3 | 84 | -| MUL (homodyne mix) | 4 | 3 | 12 | -| ADD/SUB | 40 | 1 | 40 | -| ATAN2 | 1 | 25 | 25 | -| DIV | 3 | 15 | 45 | -| **Total** | **76** | — | **~206 cycles** | +$$Period_t = 0.33 \cdot Period_{raw} + 0.67 \cdot Period_{t-1}$$ -### Complexity Analysis +### 6. Complexity -- **Streaming:** O(1) per bar—fixed Hilbert cascade -- **Memory:** ~1.2 KB per instance (circular buffers + state) -- **Warmup:** 32 bars (TA-Lib lookback) +$O(1)$ per bar. Fixed Hilbert cascade with circular buffers totaling approximately 1.2 KB per instance. Warmup: 32 bars (TA-Lib lookback). -## Validation +## Mathematical Foundation -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | ✅ | Matches `TALib.Functions.HtDcPeriod()` | -| Skender | N/A | Not implemented | -| PineScript | ✅ | Matches `ht_dcperiod.pine` reference | +### Parameters -## Usage & Pitfalls +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -- **Output is period in bars** (6-50 range)—not an oscillator -- **32-bar warmup required**—ignore early values -- **Trending markets** cause period to drift to upper limit (50) -- **High noise** causes jitter—internal smoothing helps -- **Use for adaptive tuning**: `RSI(period: htDcperiod.Value / 2)` -- **Stable periods** indicate rhythmic market suitable for oscillators +The period range [6, 50] and all smoothing constants are fixed by the TA-Lib specification. -## API +### Pseudo-code -```mermaid -classDiagram - class HtDcperiod { - +double Value - +bool IsHot - +HtDcperiod() - +HtDcperiod(ITValuePublisher source) - +TValue Update(TValue input, bool isNew) - +void Reset() - } +``` +function HT_DCPERIOD(source): + A ← 0.0962; B ← 0.5769 + smoothBuf ← CircularBuffer(7) + detBuf, q1Buf, i1Buf ← CircularBuffers + + I2 ← 0; Q2 ← 0 + Re ← 0; Im ← 0 + period ← 15 // initial estimate + + for each price in source: + // Step 1: WMA smooth + smooth ← (4·price + 3·p[1] + 2·p[2] + p[3]) / 10 + + // Step 2: Hilbert FIR (adaptive to period) + adj ← A + B // coefficient adjustment + det ← adj·(smooth[0] - smooth[6]) + B·(smooth[2] - smooth[4]) + Q1 ← adj·(det[0] - det[6]) + B·(det[2] - det[4]) + I1 ← det[3] + jI ← adj·(I1[0] - I1[6]) + B·(I1[2] - I1[4]) + jQ ← adj·(Q1[0] - Q1[6]) + B·(Q1[2] - Q1[4]) + + // Step 3: Phasor (EMA smoothed) + I2 ← 0.2·(I1 - jQ) + 0.8·I2 + Q2 ← 0.2·(Q1 + jI) + 0.8·Q2 + + // Step 4: Homodyne discriminator + Re ← 0.2·(I2·I2_prev + Q2·Q2_prev) + 0.8·Re + Im ← 0.2·(I2·Q2_prev - Q2·I2_prev) + 0.8·Im + + // Step 5: Period + if Im ≠ 0 and Re ≠ 0: + p ← 2π / atan(Im / Re) + p ← clamp(p, 6, 50) + period ← 0.33·p + 0.67·period + + emit period ``` -### Class: `HtDcperiod` +### Output Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| (none) | — | — | — | No constructor parameters | +| Output | Meaning | +|--------|---------| +| `period` $\approx 6$-$15$ | Short-cycle market; fast oscillator settings appropriate | +| `period` $\approx 15$-$30$ | Medium-cycle; standard indicator periods work | +| `period` $\approx 30$-$50$ | Long-cycle or trending; period drifting toward upper bound suggests trend | +| Stable value | Regular cyclical market, ideal for oscillator-based strategies | -### Properties +## Resources -- `Value` (`double`): Dominant cycle period in bars (6-50) -- `IsHot` (`bool`): Returns `true` when warmup (32 bars) is complete - -### Methods - -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create HT_DCPERIOD -var htPeriod = new HtDcperiod(); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = htPeriod.Update(new TValue(bar.Date, bar.Close)); - - if (htPeriod.IsHot) - { - double period = result.Value; - Console.WriteLine($"{bar.Date}: Dominant Cycle = {period:F2} bars"); - - // Use cycle to tune RSI adaptively - int adaptivePeriod = (int)(period / 2); - var adaptiveRsi = new Rsi(adaptivePeriod); - } -} - -// Batch calculation -var output = HtDcperiod.Calculate(sourceSeries); -``` +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. +- **TA-Lib** `TA_HT_DCPERIOD()` reference implementation. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. diff --git a/lib/cycles/ht_dcphase/HtDcphase.md b/lib/cycles/ht_dcphase/HtDcphase.md index 62107e55..2606a67e 100644 --- a/lib/cycles/ht_dcphase/HtDcphase.md +++ b/lib/cycles/ht_dcphase/HtDcphase.md @@ -1,152 +1,107 @@ # HT_DCPHASE: Ehlers Hilbert Transform Dominant Cycle Phase -> "The phase advances through a full 360-degree cycle as the dominant cycle completes; rapid phase changes indicate turning points." - -HT_DCPHASE measures the instantaneous phase angle of the dominant market cycle using Ehlers' Hilbert Transform cascade. The output ranges from -45° to 315°, with phase discontinuities marking cycle completions. This indicator times entries/exits based on cycle position. +HT_DCPHASE measures the instantaneous phase angle of the dominant market cycle using Ehlers' Hilbert Transform cascade. The output ranges from $-45°$ to $315°$, with phase discontinuities at cycle completions marking the transition from one cycle to the next. Compatible with TA-Lib's `HT_DCPHASE` function, the indicator enables cycle-position timing for entries and exits based on where price currently sits within the dominant cycle. ## Historical Context -John Ehlers developed the Hilbert Transform cycle indicators in *Rocket Science for Traders* (2001). TA-Lib implements HT_DCPHASE directly from Ehlers' coefficients (A = 0.0962, B = 0.5769) with a 4-bar WMA prefilter and DC phase extraction from smoothed price history. - -QuanTAlib matches TA-Lib HT_DCPHASE output within floating-point tolerance. +John Ehlers developed the Hilbert Transform cycle indicators in *Rocket Science for Traders* (2001) as extensions of David Hilbert's 1905 mathematical transform to financial data. While HT_DCPERIOD measures *how long* a cycle takes, HT_DCPHASE measures *where within the cycle* the market currently sits. This distinction matters for timing: a 20-bar cycle at phase 0° (bottom) has different implications than the same cycle at phase 180° (top). The TA-Lib implementation uses a DFT-like accumulation over the smoothed period to compute the DC phase from smoothed price history, requiring 63 bars of lookback for stable output. QuanTAlib matches TA-Lib within floating-point tolerance. ## Architecture & Physics -The algorithm extracts phase from the complex analytic signal. +### 1. Hilbert Transform Cascade -### 1. WMA Price Smoothing +Identical pipeline to HT_DCPERIOD: 4-bar WMA smoothing, Hilbert FIR detrender with coefficients $A = 0.0962$, $B = 0.5769$, phasor component extraction ($I_2$, $Q_2$), and homodyne period estimation. -$$ -SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10} -$$ +### 2. Smoothed Period -### 2. Hilbert Transform Cascade +The dominant cycle period from the homodyne discriminator, clamped to $[6, 50]$ and EMA-smoothed ($\alpha = 0.33$). -- **Detrender (D)**: Removes DC component -- **Quadrature (Q1)**: 90° phase-shifted version of D -- **In-Phase (I1)**: D delayed by 3 bars -- **jI, jQ**: Hilbert transforms of I1, Q1 +### 3. DC Phase via DFT Accumulation -### 3. Phasor Components +Over the smoothed period $P$, accumulate weighted contributions from the price history: -$$ -I2_t = I1_t - jQ_t -$$ +$$RealPart = \sum_{i=0}^{P-1} \sin\!\left(\frac{2\pi i}{P}\right) \cdot SmoothPrice_{t-i}$$ -$$ -Q2_t = Q1_t + jI_t -$$ +$$ImagPart = \sum_{i=0}^{P-1} \cos\!\left(\frac{2\pi i}{P}\right) \cdot SmoothPrice_{t-i}$$ -Smoothed with EMA (α = 0.2). +$$DCPhase_{raw} = \arctan\!\left(\frac{RealPart}{ImagPart}\right) \cdot \frac{180°}{\pi}$$ -### 4. DC Phase Calculation +### 4. Phase Adjustment -Via DFT-like accumulation over smoothed period: +If $ImagPart > 0$: $DCPhase \mathrel{-}= 180°$ -$$ -DCPhase = \arctan\left(\frac{RealPart}{ImagPart}\right) \cdot \frac{180°}{\pi} -$$ +Final unwrapping: $DCPhase \mathrel{+}= 90°$, then if $DCPhase < -45°$: $DCPhase \mathrel{+}= 360°$. -Wrapped to range [-45°, 315°]. +Result is wrapped to $[-45°, 315°]$. -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, per Bar) +$O(P)$ per bar where $P$ is the smoothed period (typically 6-50), due to the DFT accumulation loop over the price history. Memory is approximately 1.2 KB per instance for circular buffers and state. Warmup: 63 bars (TA-Lib lookback). -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| MUL (Hilbert + DFT) | 45 | 3 | 135 | -| SIN/COS (DFT loop) | 100 | 15 | 1500 | -| ADD/SUB | 60 | 1 | 60 | -| ATAN2 | 2 | 25 | 50 | -| **Total** | **~207** | — | **~1745 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(P) per bar where P is smoothed period (~6-50) -- **Memory:** ~1.2 KB per instance -- **Warmup:** 63 bars (TA-Lib lookback) +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -## Validation +All internal constants are fixed by the TA-Lib specification. -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | ✅ | Matches `TALib.Functions.HtDcPhase()` | -| Skender | N/A | Not implemented | -| PineScript | ✅ | Matches `ht_dcphase.pine` | +### Pseudo-code -## Usage & Pitfalls +``` +function HT_DCPHASE(source): + // Same Hilbert cascade as HT_DCPERIOD + // ... (WMA smooth, Hilbert FIR, phasor, homodyne) + // Produces: smoothPeriod, smoothPriceBuf -- **Phase range is -45° to 315°**—discontinuity at wrap is expected -- **63-bar warmup required**—ignore early values -- **Phase interpretation**: - - -45° to 45°: Bottom / Start of uptrend - - 45° to 135°: Rising / Mid-uptrend - - 135° to 225°: Top / Start of downtrend - - 225° to 315°: Falling / Mid-downtrend -- **Do not smooth across discontinuity**—315° to -45° jump is cycle completion -- **Strong trends** cause phase to advance slowly or get stuck -- **Rapid phase change** often precedes price reversals + for each bar (after warmup): + P ← round(smoothPeriod) -## API + // DFT accumulation over dominant period + realPart ← 0; imagPart ← 0 + for i = 0 to P-1: + realPart += sin(2π·i / P) · smoothPriceBuf[t - i] + imagPart += cos(2π·i / P) · smoothPriceBuf[t - i] -```mermaid -classDiagram - class HtDcphase { - +double Value - +bool IsHot - +HtDcphase() - +HtDcphase(ITValuePublisher source) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + // Phase extraction + if |imagPart| > 0: + dcPhase ← atan(realPart / imagPart) · (180/π) + else: + dcPhase ← 90 · sign(realPart) + + if imagPart > 0: dcPhase -= 180 + dcPhase += 90 + + // Wrap to [-45, 315] + if dcPhase < -45: dcPhase += 360 + + emit dcPhase ``` -### Class: `HtDcphase` +### Phase Quadrant Interpretation -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| (none) | — | — | — | No constructor parameters | +| Phase Range | Cycle Position | +|-------------|----------------| +| $-45°$ to $45°$ | Bottom zone (start of uptrend) | +| $45°$ to $135°$ | Rising phase (mid-uptrend) | +| $135°$ to $225°$ | Top zone (start of downtrend) | +| $225°$ to $315°$ | Falling phase (mid-downtrend) | +| $315°$ to $-45°$ jump | Cycle completion (discontinuity) | -### Properties +### Output Interpretation -- `Value` (`double`): DC phase in degrees (-45° to 315°) -- `IsHot` (`bool`): Returns `true` when warmup (63 bars) is complete +| Condition | Meaning | +|-----------|---------| +| Phase advancing steadily | Regular cyclical market | +| Phase stuck or slow | Trending market (cycle suppressed) | +| Rapid phase change | Potential reversal imminent | +| Discontinuity ($315° \to -45°$) | One cycle complete, new cycle begins | -### Methods +## Resources -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create HT_DCPHASE -var htPhase = new HtDcphase(); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = htPhase.Update(new TValue(bar.Date, bar.Close)); - - if (htPhase.IsHot) - { - double phase = result.Value; - Console.WriteLine($"{bar.Date}: Phase = {phase:F1}°"); - - // Cycle position detection - if (phase >= -45 && phase < 45) - Console.WriteLine(" → Cycle bottom zone"); - else if (phase >= 45 && phase < 135) - Console.WriteLine(" → Rising phase"); - else if (phase >= 135 && phase < 225) - Console.WriteLine(" → Cycle top zone"); - else - Console.WriteLine(" → Falling phase"); - } -} - -// Batch calculation -var output = HtDcphase.Calculate(sourceSeries); -``` +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. +- **TA-Lib** `TA_HT_DCPHASE()` reference implementation. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Hilbert, D.** *Grundzüge einer allgemeinen Theorie der linearen Integralgleichungen*. Teubner, 1912. diff --git a/lib/cycles/ht_phasor/HtPhasor.md b/lib/cycles/ht_phasor/HtPhasor.md index f7eb8b18..0435406a 100644 --- a/lib/cycles/ht_phasor/HtPhasor.md +++ b/lib/cycles/ht_phasor/HtPhasor.md @@ -1,151 +1,96 @@ # HT_PHASOR: Ehlers Hilbert Transform Phasor Components -> "Phasors let us measure a cycle's position and strength; trading becomes geometry over time." - -HT_PHASOR decomposes the price signal into two orthogonal components: **InPhase** (I) and **Quadrature** (Q) using the Hilbert Transform. These components form a complex phasor (Z = I + jQ) that describes the instantaneous amplitude and phase of the market cycle. +HT_PHASOR decomposes the price signal into two orthogonal components, InPhase ($I$) and Quadrature ($Q$), using the Hilbert Transform. Together these form a complex phasor $Z = I + jQ$ that describes the instantaneous amplitude and phase of the dominant market cycle. Compatible with TA-Lib's `HT_PHASOR` function, this dual-output indicator provides the fundamental building blocks for cycle analysis, phasor crossover timing, and instantaneous amplitude measurement. ## Historical Context -John Ehlers introduced the decomposition of market data into phasor components in *Rocket Science for Traders* (2001). This decomposition is fundamental to his entire suite of cycle indicators (SineWave, Homodyne, etc.). - -TA-Lib implements HT_PHASOR to expose these intermediate components directly for advanced analysis. QuanTAlib matches the TA-Lib implementation. +John Ehlers introduced phasor decomposition of market data in *Rocket Science for Traders* (2001). In electrical engineering, a phasor represents a sinusoidal signal as a rotating complex vector, separating the cycle's "position" (InPhase) from its "velocity" (Quadrature). Ehlers recognized that this decomposition is the mathematical foundation for all his cycle indicators: HT_SINE, HT_DCPERIOD, HT_DCPHASE, and HOMOD all derive from these same I/Q components. TA-Lib exposes HT_PHASOR to give advanced users direct access to the analytic signal for custom cycle analysis. The InPhase output is delayed by 3 bars to align with the Quadrature component's effective lag from the Hilbert Transform FIR. ## Architecture & Physics -The calculation pipeline extracts the analytic signal's real and imaginary components. - ### 1. WMA Smoothing -$$ -SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10} -$$ +$$SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}$$ -### 2. Hilbert Transform +### 2. Hilbert Transform FIR -Applied to smoothed price with adaptive bandwidth to generate fundamental components. +Using Ehlers' coefficients ($A = 0.0962$, $B = 0.5769$), the 4-tap discrete Hilbert approximation generates the detrender, and from it the fundamental In-Phase and Quadrature components ($I_1$, $Q_1$). Further Hilbert transforms of these produce $jI$ and $jQ$. ### 3. Phasor Components -$$ -I2_t = I1_t - jQ_t -$$ +$$I_{2,t} = I_{1,t} - jQ_t, \qquad Q_{2,t} = Q_{1,t} + jI_t$$ -$$ -Q2_t = Q1_t + jI_t -$$ +Both smoothed with EMA ($\alpha = 0.2$): -Where: +$$I_t = 0.2 \cdot I_{2,t} + 0.8 \cdot I_{t-1}$$ -- **InPhase (I)**: Smoothed I2—cycle signal aligned with price -- **Quadrature (Q)**: Smoothed Q2—rate of change (velocity) of cycle - -*Note: InPhase output is delayed by 3 bars to align with Quadrature's effective lag.* +$$Q_t = 0.2 \cdot Q_{2,t} + 0.8 \cdot Q_{t-1}$$ ### 4. Phase Relationship -- Q leads I by 90° -- When I peaks, Q crosses zero (downward) -- When I crosses zero (upward), Q peaks +$Q$ leads $I$ by $90°$. When $I$ peaks, $Q$ crosses zero downward. When $I$ crosses zero upward, $Q$ peaks. The instantaneous amplitude is $A = \sqrt{I^2 + Q^2}$ and the instantaneous phase is $\phi = \arctan(Q/I)$. -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, per Bar) +$O(1)$ per bar. Fixed Hilbert cascade with circular buffers. Warmup: 32 bars (TA-Lib lookback). -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| MUL (Hilbert taps) | 28 | 3 | 84 | -| MUL (phasor calc) | 8 | 3 | 24 | -| ADD/SUB | 35 | 1 | 35 | -| EMA smoothing | 4 | 4 | 16 | -| **Total** | **75** | — | **~159 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(1) per bar—fixed Hilbert cascade -- **Memory:** ~1.2 KB per instance (circular buffers) -- **Warmup:** 32 bars (TA-Lib lookback) +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -## Validation +### Pseudo-code -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | ✅ | Matches `TALib.Functions.HtPhasor()` | -| Skender | N/A | Not implemented | -| PineScript | ✅ | Matches `phasor.pine` | +``` +function HT_PHASOR(source): + A ← 0.0962; B ← 0.5769 + smoothBuf ← CircularBuffer(7) + detBuf, q1Buf, i1Buf ← CircularBuffers -## Usage & Pitfalls + I2 ← 0; Q2 ← 0 -- **Dual output**—InPhase (Value) and Quadrature (property) -- **32-bar warmup required**—ignore early values -- **Capture Quadrature immediately after Update()**—property updated on each call -- **Trending markets** break orthogonality—use HT_TRENDMODE to filter -- **Phasor crossover**: - - Buy: Q crosses I from below (anticipates cycle trough) - - Sell: Q crosses I from above (anticipates cycle peak) -- **For sine input** sin(ωt): InPhase ≈ sin(ωt), Quadrature ≈ cos(ωt) + for each price in source: + // WMA smooth + smooth ← (4·price + 3·p[1] + 2·p[2] + p[3]) / 10 + smoothBuf.Add(smooth) -## API + // Hilbert FIR (adaptive) + det ← A·smooth[0] + B·smooth[2] - B·smooth[4] - A·smooth[6] + Q1 ← A·det[0] + B·det[2] - B·det[4] - A·det[6] + I1 ← det[3] -```mermaid -classDiagram - class HtPhasor { - +double Value - +double Quadrature - +bool IsHot - +HtPhasor() - +HtPhasor(ITValuePublisher source) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + // Hilbert of I1 and Q1 + jI ← A·I1[0] + B·I1[2] - B·I1[4] - A·I1[6] + jQ ← A·Q1[0] + B·Q1[2] - B·Q1[4] - A·Q1[6] + + // Phasor components (EMA smoothed) + I2 ← 0.2·(I1 - jQ) + 0.8·I2 + Q2 ← 0.2·(Q1 + jI) + 0.8·Q2 + + emit InPhase = I2, Quadrature = Q2 ``` -### Class: `HtPhasor` +### Phasor Crossover Signals -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| (none) | — | — | — | No constructor parameters | +| Condition | Signal | +|-----------|--------| +| $Q$ crosses $I$ from below | Bullish (anticipates cycle trough) | +| $Q$ crosses $I$ from above | Bearish (anticipates cycle peak) | +| $\sqrt{I^2 + Q^2}$ increasing | Cycle amplitude growing | +| $\sqrt{I^2 + Q^2}$ decreasing | Cycle amplitude fading (trend or noise) | -### Properties +### Output Interpretation -- `Value` (`double`): InPhase component of phasor -- `Quadrature` (`double`): Quadrature component (90° shifted) -- `IsHot` (`bool`): Returns `true` when warmup (32 bars) is complete +| Output | Range | Meaning | +|--------|-------|---------| +| `InPhase` | unbounded | Cycle component aligned with price | +| `Quadrature` | unbounded | Rate of change (velocity) of cycle | -### Methods +## Resources -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Create HT_PHASOR -var htPhasor = new HtPhasor(); -double prevInPhase = 0, prevQuadrature = 0; - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = htPhasor.Update(new TValue(bar.Date, bar.Close)); - double inPhase = result.Value; - double quadrature = htPhasor.Quadrature; // Capture immediately! - - if (htPhasor.IsHot) - { - Console.WriteLine($"{bar.Date}: I = {inPhase:F4}, Q = {quadrature:F4}"); - - // Phasor crossover detection - if (inPhase > quadrature && prevInPhase <= prevQuadrature) - Console.WriteLine(" → Bullish crossover (anticipate trough)"); - else if (inPhase < quadrature && prevInPhase >= prevQuadrature) - Console.WriteLine(" → Bearish crossover (anticipate peak)"); - } - - prevInPhase = inPhase; - prevQuadrature = quadrature; -} - -// Batch calculation -var output = HtPhasor.Calculate(sourceSeries); -``` +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. +- **TA-Lib** `TA_HT_PHASOR()` reference implementation. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. diff --git a/lib/cycles/ht_sine/HtSine.md b/lib/cycles/ht_sine/HtSine.md index 5e172e1f..f93e6eb0 100644 --- a/lib/cycles/ht_sine/HtSine.md +++ b/lib/cycles/ht_sine/HtSine.md @@ -1,170 +1,100 @@ # HT_SINE: Ehlers Hilbert Transform SineWave -> "The Hilbert Transform gives us the phase of the dominant cycle—knowing when to buy and sell becomes a matter of trigonometry." - -The Hilbert Transform SineWave extracts the dominant market cycle phase and outputs both sine and lead sine (45° phase advance) for cycle timing. The crossover of these two waves identifies turning points in ranging markets up to 1/8th of a cycle early. +HT_SINE extracts the dominant market cycle phase and outputs both Sine and LeadSine (45° phase advance) for cycle timing. The crossover of these two waves identifies turning points in ranging markets up to one-eighth of a cycle early. Compatible with TA-Lib's `HT_SINE` function, the indicator builds on the full Hilbert Transform cascade (phasor extraction, homodyne period estimation, DFT phase accumulation) to produce dual bounded $[-1, +1]$ oscillators that track cycle position rather than price amplitude. ## Historical Context -John Ehlers introduced the Hilbert Transform SineWave in *Rocket Science for Traders* (2001) as part of his comprehensive signal processing framework for financial markets. The indicator addresses a fundamental limitation of traditional oscillators—they respond to price amplitude rather than cycle phase. - -The HT_SINE builds upon David Hilbert's 1905 mathematical transform, which creates a 90° phase-shifted (quadrature) version of a signal. In signal processing, this enables instantaneous frequency and phase extraction. Ehlers recognized that market cycles, though noisy and variable, could be analyzed using these same techniques. - -Unlike momentum oscillators that lag price action, the HT_SINE theoretically provides zero-lag cycle detection by measuring phase directly. This makes it particularly valuable in ranging markets where cycles are well-defined. The dual output (Sine and LeadSine) creates a built-in early warning system for cycle reversals. +John Ehlers introduced the Hilbert Transform SineWave in *Rocket Science for Traders* (2001) as part of his signal processing framework for financial markets. Traditional oscillators (RSI, Stochastic) respond to price amplitude, inherently lagging reversals. HT_SINE measures cycle phase directly, theoretically providing zero-lag detection of cycle turning points. The LeadSine output advances the phase by 45°, creating a built-in early warning system: when LeadSine diverges from Sine, a reversal is approaching. The dual-line design provides both confirmation (crossover) and anticipation (LeadSine leading). The indicator is most effective in ranging markets with well-defined cycles; in strong trends, the two lines travel in parallel ("snake pattern"), correctly indicating that no cyclical reversal is imminent. ## Architecture & Physics -The algorithm implements a discrete approximation of the Hilbert Transform optimized for financial time series with adaptive period estimation. +### 1. Hilbert Transform Cascade -**Step 1: WMA Smoothing** +The full TA-Lib Hilbert pipeline: 4-bar WMA smoothing, Hilbert FIR with coefficients $A = 0.0962$, $B = 0.5769$, phasor extraction ($I_2$, $Q_2$), EMA smoothing ($\alpha = 0.2$). -A 4-bar weighted moving average removes Nyquist-frequency noise: +### 2. Homodyne Period Estimation -$$\bar{P}_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}$$ +$$Re_t = 0.2(I_{2,t} \cdot I_{2,t-1} + Q_{2,t} \cdot Q_{2,t-1}) + 0.8 \cdot Re_{t-1}$$ -**Step 2: Hilbert Transform FIR** +$$Im_t = 0.2(I_{2,t} \cdot Q_{2,t-1} - Q_{2,t} \cdot I_{2,t-1}) + 0.8 \cdot Im_{t-1}$$ -The discrete Hilbert approximation generates quadrature components: +$$Period = \frac{2\pi}{\arctan(Im / Re)}$$ -$$\text{Detrender}_t = 0.0962\bar{P}_t + 0.5769\bar{P}_{t-2} - 0.5769\bar{P}_{t-4} - 0.0962\bar{P}_{t-6}$$ +Clamped to $[6, 50]$, then smoothed ($\alpha = 0.33$). -**Step 3: I/Q Component Smoothing** +### 3. DC Phase via DFT Accumulation -In-phase and quadrature components undergo exponential smoothing: +Over the smoothed period $P$: -$$Q_t = 0.2(Q1_t + JI_t) + 0.8 Q_{t-1}$$ -$$I_t = 0.2(I1_t - JQ_t) + 0.8 I_{t-1}$$ +$$RealPart = \sum_{i=0}^{P-1} \sin\!\left(\frac{2\pi i}{P}\right) \cdot SmoothPrice_{t-i}$$ -**Step 4: Homodyne Discriminator** +$$ImagPart = \sum_{i=0}^{P-1} \cos\!\left(\frac{2\pi i}{P}\right) \cdot SmoothPrice_{t-i}$$ -Period estimation uses phase rate of change: +$$\phi_t = \arctan\!\left(\frac{RealPart}{ImagPart}\right)$$ -$$Re_t = 0.2(I_t \cdot I_{t-1} + Q_t \cdot Q_{t-1}) + 0.8 Re_{t-1}$$ -$$Im_t = 0.2(I_t \cdot Q_{t-1} - Q_t \cdot I_{t-1}) + 0.8 Im_{t-1}$$ -$$\text{Period}_t = \frac{2\pi}{\arctan(Im_t / Re_t)}$$ +With quadrant correction and phase unwrapping. -**Step 5: DC Phase Calculation** +### 4. Output Generation -The dominant cycle phase sums weighted contributions: +$$Sine_t = \sin(\phi_t)$$ -$$\phi_t = \arctan\left(\frac{\sum_{i=0}^{P-1} \sin(2\pi i/P) \cdot \bar{P}_{t-i}}{\sum_{i=0}^{P-1} \cos(2\pi i/P) \cdot \bar{P}_{t-i}}\right)$$ +$$LeadSine_t = \sin(\phi_t + 45°)$$ -**Step 6: Output Generation** +### 5. Complexity -$$\text{Sine}_t = \sin(\phi_t)$$ -$$\text{LeadSine}_t = \sin(\phi_t + 45°)$$ +$O(P)$ per bar where $P$ is the smoothed period (typically 6-50), due to the DFT accumulation loop. Fixed-size circular buffers (50 + 44 + 64 elements) give $O(1)$ space. Warmup: 63 bars (31 + 32 for TA-Lib compatibility). -## Performance Profile +## Mathematical Foundation -### Operation Count (Streaming Mode, per Bar) +### Parameters -| Operation | Count | Cost (cycles) | Subtotal | -|-----------|------:|------:|------:| -| FMA | 12 | 5 | 60 | -| MUL | 18 | 4 | 72 | -| ADD/SUB | 25 | 1 | 25 | -| DIV | 2 | 15 | 30 | -| sin/cos | 2P | 40 | ~80P | -| atan | 2 | 50 | 100 | -| Buffer access | 15 | 3 | 45 | -| **Total** | — | — | **~370** | +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -### Complexity Analysis +All constants are fixed by the TA-Lib specification. -- **Time:** $O(P)$ per bar where P is smoothed period (typically 6-50) -- **Space:** $O(1)$ — fixed-size circular buffers (50 + 44 + 64 elements) -- **Latency:** 63 bars warmup (31 + 32 for TA-Lib compatibility) +### Pseudo-code -## Validation +``` +function HT_SINE(source): + // Full Hilbert cascade (same as HT_DCPHASE) + // Produces: smoothPeriod, smoothPriceBuf, dcPhase -| Library | Status | Notes | -|---------|--------|-------| -| TA-Lib | ✅ Match | `TA_HT_SINE()` reference implementation | -| PineScript | ✅ Match | Custom `ht_sine.pine` validation script | -| Quantower | ✅ Match | `HtSine.Quantower.Tests.cs` adapter tests | + for each bar (after warmup): + // Phase from DFT accumulation (see HT_DCPHASE) + φ ← computeDCPhase(smoothPeriod, smoothPriceBuf) -## Usage & Pitfalls + // Convert phase to radians + φ_rad ← φ · (π / 180) -- **Trend Failure:** Crossover signals whipsaw in strong trends; parallel "snake" pattern indicates trending mode -- **Warmup Period:** Requires 63 bars before outputs stabilize -- **Phase Lag:** Despite "zero-lag" theory, smoothing introduces 4-6 bars practical lag -- **Range-Only:** Most effective in sideways/ranging markets with clear cyclical behavior -- **LeadSine First:** LeadSine turns before Sine at reversals—watch for divergence + // Dual sine output + sine ← sin(φ_rad) + leadSine ← sin(φ_rad + π/4) // 45° lead -## API - -```mermaid -classDiagram - class AbstractBase { - <> - +Name string - +WarmupPeriod int - +IsHot bool - +Last TValue - +Update(TValue input, bool isNew) TValue - +Reset() void - } - class HtSine { - +LeadSine double - +HtSine() - +HtSine(ITValuePublisher source) - +Update(TValue input, bool isNew) TValue - +Update(TSeries source) TSeries - +Prime(ReadOnlySpan~double~ source, TimeSpan? step) void - +Reset() void - +Calculate(TSeries source)$ TSeries - +Batch(ReadOnlySpan~double~ source, Span~double~ sine, Span~double~ leadSine)$ void - } - AbstractBase <|-- HtSine + emit sine, leadSine ``` -### Class: `HtSine` +### Crossover Signals -Hilbert Transform SineWave indicator with dual output. +| Pattern | Signal | +|---------|--------| +| Sine crosses above LeadSine | Bullish: cycle turning up from trough | +| Sine crosses below LeadSine | Bearish: cycle turning down from peak | +| Lines parallel, both rising | Uptrend in progress (not cycling) | +| Lines parallel, both falling | Downtrend in progress (not cycling) | +| LeadSine diverges first | Early warning of approaching reversal | -### Properties +### Output Interpretation -| Name | Type | Description | -|------|------|-------------| -| `LeadSine` | `double` | Current LeadSine value (45° phase lead) | -| `IsHot` | `bool` | True after 63 bars warmup | -| `Last` | `TValue` | Most recent Sine output | +| Output | Range | Meaning | +|--------|-------|---------| +| `Sine` | $[-1, +1]$ | Current cycle phase position | +| `LeadSine` | $[-1, +1]$ | 45° advanced cycle phase (early warning) | -### Methods +## Resources -| Name | Returns | Description | -|------|---------|-------------| -| `Update(TValue, bool)` | `TValue` | Updates state with new price value | -| `Batch(source, sine, leadSine)` | `void` | Processes span with dual output spans | -| `Calculate(TSeries)` | `TSeries` | Static factory returning Sine series | - -## C# Example - -```csharp -using QuanTAlib; - -// Create HT_SINE indicator -var htSine = new HtSine(); - -// Process price data -foreach (var bar in bars) -{ - var result = htSine.Update(new TValue(bar.Time, bar.Close)); - - if (htSine.IsHot) - { - double sine = result.Value; - double leadSine = htSine.LeadSine; - - // Crossover detection - // Buy: Sine crosses above LeadSine - // Sell: Sine crosses below LeadSine - Console.WriteLine($"Sine: {sine:F4}, LeadSine: {leadSine:F4}"); - } -} - -// Batch processing with dual outputs -Span sineOut = stackalloc double[prices.Length]; -Span leadOut = stackalloc double[prices.Length]; -HtSine.Batch(prices, sineOut, leadOut); -``` +- **Ehlers, J.F.** *Rocket Science for Traders*. Wiley, 2001. +- **TA-Lib** `TA_HT_SINE()` reference implementation. +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Hilbert, D.** *Grundzüge einer allgemeinen Theorie der linearen Integralgleichungen*. Teubner, 1912. diff --git a/lib/cycles/lunar/Lunar.md b/lib/cycles/lunar/Lunar.md index e776078b..c157fbca 100644 --- a/lib/cycles/lunar/Lunar.md +++ b/lib/cycles/lunar/Lunar.md @@ -1,31 +1,24 @@ # LUNAR: Lunar Phase Indicator -> "The moon has been humanity's first clock for millennia—some believe it still moves markets." - -The Lunar Phase indicator calculates the Moon's illumination fraction using precise orbital mechanics and astronomical algorithms. Output ranges from 0.0 (New Moon) through 0.5 (Quarter) to 1.0 (Full Moon), enabling research into potential lunar-correlated market cycles. +LUNAR calculates the Moon's illumination fraction using precise orbital mechanics from Jean Meeus' *Astronomical Algorithms*. Output ranges from 0.0 (New Moon) through 0.5 (Quarter) to 1.0 (Full Moon), providing a continuous astronomical cycle for research into potential lunar-correlated market behavior. The indicator is purely time-based, requires no price data, and has zero warmup since the calculation is deterministic from any timestamp. ## Historical Context -Lunar cycles have guided human activity for millennia. Ancient civilizations scheduled agriculture, navigation, and commerce around the Moon's ~29.53-day synodic period. The hypothesis that lunar phases influence human behavior—and by extension, financial markets—dates to early technical analysis. - -The "lunar effect" in markets remains controversial in academic literature. Some studies find statistically significant correlations between lunar phases and market returns, while others dismiss such findings as data mining artifacts. Regardless of one's position, rigorous testing requires precise phase calculation. - -This implementation derives from Jean Meeus' *Astronomical Algorithms* (1991), the standard reference for computational positional astronomy. The algorithm accounts for major orbital perturbations including the Moon's elliptical orbit, solar perturbations, and nodal regression—achieving sub-degree accuracy sufficient for financial cycle research. +Lunar cycles have guided human activity for millennia. The hypothesis that lunar phases influence human behavior—and by extension, financial markets—dates to early technical analysis and remains a subject of academic investigation. Some studies (Dichev & Janes, 2001; Yuan, Zheng & Zhu, 2006) find statistically significant correlations between lunar phases and market returns, while others dismiss such findings as data mining artifacts. Regardless of one's position, rigorous testing requires precise phase calculation. This implementation derives from Meeus' (1991) standard reference for computational positional astronomy, accounting for major orbital perturbations including the Moon's elliptical orbit (eccentricity $e \approx 0.0549$), solar perturbations, and nodal regression, achieving sub-degree accuracy sufficient for financial cycle research. ## Architecture & Physics -The indicator implements a truncated lunar ephemeris using polynomial approximations with FMA optimization. - -**Step 1: Julian Date Conversion** +### 1. Julian Date Conversion Convert Unix timestamp to Julian centuries from J2000 epoch: -$$JD = \frac{\text{UnixMs}}{86400000} + 2440587.5$$ +$$JD = \frac{UnixMs}{86400000} + 2440587.5$$ + $$T = \frac{JD - 2451545.0}{36525.0}$$ -**Step 2: Mean Orbital Elements** +### 2. Mean Orbital Elements -Polynomial series (Horner's method) compute fundamental arguments: +Polynomial series (Horner's method) compute five fundamental arguments: $$L' = 218.3164477 + 481267.88123421T - 0.0015786T^2 + \frac{T^3}{538841}$$ @@ -37,137 +30,92 @@ $$M' = 134.9633964 + 477198.8675055T + 0.0087414T^2$$ $$F = 93.2720950 + 483202.0175233T - 0.0036539T^2$$ -**Step 3: Perturbation Corrections** +where $L'$ = mean lunar longitude, $D$ = mean elongation, $M$ = solar mean anomaly, $M'$ = lunar mean anomaly, $F$ = lunar argument of latitude. + +### 3. Perturbation Corrections Major periodic terms correct the Moon's true longitude: -$$\Sigma = 6288.016\sin M' + 1274.242\sin(2D - M') + 658.314\sin 2D$$ -$$+ 214.818\sin 2M' + 186.986\sin M + 109.154\sin 2F$$ +$$\Sigma = 6288.016 \sin M' + 1274.242 \sin(2D - M') + 658.314 \sin 2D + 214.818 \sin 2M' + 186.986 \sin M + 109.154 \sin 2F$$ -$$\lambda_{\text{Moon}} = L' + \frac{\Sigma}{10^6}$$ +$$\lambda_{Moon} = L' + \frac{\Sigma}{10^6}$$ -**Step 4: Phase Angle** +### 4. Phase Angle -The elongation between Moon and Sun determines phase: +The angular separation between Moon and Sun: -$$\psi = \lambda_{\text{Moon}} - \lambda_{\text{Sun}}$$ +$$\psi = \lambda_{Moon} - \lambda_{Sun}$$ -**Step 5: Illumination Fraction** +### 5. Illumination Fraction $$k = \frac{1 - \cos(\psi)}{2}$$ -## Performance Profile +This gives 0.0 at New Moon ($\psi = 0°$) and 1.0 at Full Moon ($\psi = 180°$). -### Operation Count (Streaming Mode, per Bar) +### 6. Complexity -| Operation | Count | Cost (cycles) | Subtotal | -|-----------|------:|------:|------:| -| FMA | 20 | 5 | 100 | -| MUL | 8 | 4 | 32 | -| ADD/SUB | 15 | 1 | 15 | -| sin/cos | 7 | 40 | 280 | -| MOD (normalize) | 6 | 10 | 60 | -| **Total** | — | — | **~490** | +$O(1)$ per timestamp. No state required (deterministic from time). Zero warmup. The synodic period is approximately 29.53 days. -### Complexity Analysis +## Mathematical Foundation -- **Time:** $O(1)$ — fixed computation per timestamp -- **Space:** $O(1)$ — no state required (deterministic from time) -- **Latency:** 0 bars warmup (always hot) +### Parameters -## Validation +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -| Library | Status | Notes | -|---------|--------|-------| -| NASA/JPL Horizons | ✅ Match | Ephemeris cross-validation to ±0.5° | -| USNO Almanac | ✅ Match | Historical phase dates verified | -| Quantower | ✅ Match | `Lunar.Quantower.Tests.cs` adapter tests | +The calculation is entirely determined by the input timestamp. -## Usage & Pitfalls +### Pseudo-code -- **Correlation ≠ Causation:** Statistical correlation with markets does not imply lunar causation -- **UTC Timestamps:** Calculation uses UTC; ensure input timestamps are properly normalized -- **No Price Data:** Ignores price entirely—output is pure function of time -- **Research Tool:** Best used for hypothesis testing, not primary trading signals -- **Synodic Period:** Full cycle is ~29.53 days; daily resolution captures phase progression +``` +function LUNAR(timestamp): + // Julian date + JD ← timestamp_to_unix_ms / 86400000 + 2440587.5 + T ← (JD - 2451545.0) / 36525.0 -## API + // Mean orbital elements (Horner evaluation) + Lp ← FMA(T, FMA(T, FMA(T, 1/538841, -0.0015786), 481267.88123421), 218.3164477) + D ← FMA(T, FMA(T, FMA(T, 1/545868, -0.0018819), 445267.1114034), 297.8501921) + M ← FMA(T, FMA(T, -0.0001536, 35999.0502909), 357.5291092) + Mp ← FMA(T, FMA(T, 0.0087414, 477198.8675055), 134.9633964) + F ← FMA(T, FMA(T, -0.0036539, 483202.0175233), 93.2720950) -```mermaid -classDiagram - class AbstractBase { - <> - +Name string - +WarmupPeriod int - +IsHot bool - +Last TValue - +Update(TValue input, bool isNew) TValue - +Reset() void - } - class Lunar { - +Lunar() - +Lunar(ITValuePublisher source) - +Update(TValue input, bool isNew) TValue - +Update(TSeries source) TSeries - +CalculatePhase(DateTime dateTime)$ double - +CalculatePhase(long unixMs)$ double - +Calculate(TSeries source)$ TSeries - +Batch(ReadOnlySpan~long~ timestamps, Span~double~ output)$ void - } - AbstractBase <|-- Lunar + // Normalize to [0°, 360°) + Lp, D, M, Mp, F ← mod(*, 360) + + // Perturbation correction (6 major terms) + Σ ← 6288016·sin(Mp) + 1274242·sin(2D - Mp) + 658314·sin(2D) + + 214818·sin(2Mp) + 186986·sin(M) + 109154·sin(2F) + λ_moon ← Lp + Σ / 1e6 + + // Solar longitude (simplified) + L0 ← 280.46646 + 36000.76983·T + M_sun ← 357.52911 + 35999.05029·T + λ_sun ← L0 + 1.9146·sin(M_sun) + 0.02·sin(2·M_sun) + + // Phase angle and illumination + ψ ← λ_moon - λ_sun + k ← (1 - cos(ψ)) / 2 + + emit k // 0.0 = New Moon, 1.0 = Full Moon ``` -### Class: `Lunar` +### Output Interpretation -Lunar phase indicator based on astronomical ephemeris calculations. +| Value | Phase | +|-------|-------| +| $k \approx 0.0$ | New Moon | +| $k$ rising, $< 0.5$ | Waxing Crescent | +| $k \approx 0.5$ (rising) | First Quarter | +| $k$ rising, $> 0.5$ | Waxing Gibbous | +| $k \approx 1.0$ | Full Moon | +| $k$ falling, $> 0.5$ | Waning Gibbous | +| $k \approx 0.5$ (falling) | Last Quarter | +| $k$ falling, $< 0.5$ | Waning Crescent | -### Properties +## Resources -| Name | Type | Description | -|------|------|-------------| -| `IsHot` | `bool` | Always `true` — no warmup required | -| `Last` | `TValue` | Most recent phase output (0.0–1.0) | - -### Methods - -| Name | Returns | Description | -|------|---------|-------------| -| `Update(TValue, bool)` | `TValue` | Calculates phase for input timestamp | -| `CalculatePhase(DateTime)` | `double` | Static phase calculation from DateTime | -| `CalculatePhase(long)` | `double` | Static phase calculation from Unix ms | -| `Batch(timestamps, output)` | `void` | Vectorized calculation over timestamp span | - -## C# Example - -```csharp -using QuanTAlib; - -// Create Lunar indicator -var lunar = new Lunar(); - -// Calculate phase for current time -var result = lunar.Update(new TValue(DateTime.UtcNow, 0)); -Console.WriteLine($"Current Moon Phase: {result.Value:P1}"); -// Output: "Current Moon Phase: 75.3%" (waxing gibbous) - -// Static calculation for specific date -double phase = Lunar.CalculatePhase(new DateTime(2024, 1, 11)); // Full moon -Console.WriteLine($"Phase: {phase:F4}"); // ~1.0 - -// Process time series for lunar research -foreach (var bar in bars) -{ - var lunarPhase = lunar.Update(new TValue(bar.Time, 0)); - - // Phase interpretation: - // 0.0 = New Moon, 0.5 = Quarter, 1.0 = Full Moon - string phaseName = lunarPhase.Value switch - { - < 0.25 => "Waxing Crescent", - < 0.50 => "First Quarter", - < 0.75 => "Waxing Gibbous", - < 1.00 => "Full Moon", - _ => "New Moon" - }; -} -``` +- **Meeus, J.** *Astronomical Algorithms*. 2nd ed., Willmann-Bell, 1998. +- **Dichev, I.D. & Janes, T.D.** "Lunar Cycle Effects in Stock Returns." *Journal of Private Equity*, 2001. +- **Yuan, K., Zheng, L. & Zhu, Q.** "Are Investors Moonstruck? Lunar Phases and Stock Returns." *Journal of Empirical Finance*, 2006. diff --git a/lib/cycles/sine/Sine.md b/lib/cycles/sine/Sine.md index 9ad418c4..e12cc9cb 100644 --- a/lib/cycles/sine/Sine.md +++ b/lib/cycles/sine/Sine.md @@ -1,166 +1,127 @@ # SINE: Ehlers Sine Wave -> "The sine wave extraction reveals what moving averages obscure—the pure rhythmic heartbeat of price action." - -The Ehlers Sine Wave extracts the dominant cycle from price data using cascaded signal processing: high-pass detrending, super-smoother noise reduction, and Hilbert Transform quadrature decomposition. Output oscillates between -1 and +1, representing the normalized position within the current cycle. +SINE extracts the dominant cycle from price data using cascaded signal processing: a high-pass filter removes the trend, a Super-Smoother filter removes noise, and a Hilbert Transform FIR decomposes the filtered signal into In-Phase and Quadrature components for power-normalized sine wave output. The result oscillates between $-1$ and $+1$, representing the normalized position within the current cycle. Unlike HT_SINE which derives phase from the full TA-Lib Hilbert cascade, this Ehlers implementation uses explicit detrending and bandpass stages for cleaner cycle isolation. ## Historical Context -John Ehlers introduced the Sine Wave indicator in *Cybernetic Analysis for Stocks and Futures* (2004) as a refined approach to cycle extraction. Unlike the HT_SINE which derives phase from raw Hilbert Transform output, this implementation adds explicit detrending and smoothing stages for cleaner cycle isolation. - -The design philosophy separates three signal processing concerns: (1) trend removal via high-pass filtering, (2) aliasing prevention via super-smoothing, and (3) cycle extraction via Hilbert Transform. This staged approach produces cleaner output than attempting all three simultaneously. - -The Sine Wave is particularly valuable in mean-reverting strategies. When the cycle position reaches extremes (-1 or +1), it suggests the cyclical component is stretched and likely to revert. Zero crossings indicate phase transitions—potential entry/exit points in the cycle. +John Ehlers introduced the Sine Wave indicator in *Cybernetic Analysis for Stocks and Futures* (2004) as a refined approach to cycle extraction. The design philosophy separates three signal processing concerns into distinct filter stages: (1) trend removal via high-pass filtering sets the long-wavelength cutoff, (2) aliasing prevention via Super-Smoother sets the short-wavelength cutoff, and (3) cycle extraction via Hilbert Transform generates the quadrature decomposition. This staged approach produces cleaner output than attempting all three simultaneously (as in the HT_SINE). The Sine Wave output at extremes ($\pm 1$) indicates the cyclical component is stretched and likely to revert, while zero crossings indicate phase transitions. The indicator is particularly valuable for mean-reversion strategies in ranging markets. ## Architecture & Physics -The algorithm cascades three distinct filter stages with carefully tuned frequency responses. +### 1. High-Pass Filter (Detrending) -**Step 1: High-Pass Filter (Detrending)** +A single-pole high-pass filter removes low-frequency trends below the cutoff: -A single-pole high-pass filter removes low-frequency trends below the cutoff period: - -$$\alpha_{HP} = \frac{1 - \sin(2\pi/P_{HP})}{\cos(2\pi/P_{HP})}$$ +$$\alpha_{HP} = \frac{1 - \sin(2\pi / P_{HP})}{\cos(2\pi / P_{HP})}$$ $$HP_t = \frac{1 + \alpha_{HP}}{2}(P_t - P_{t-1}) + \alpha_{HP} \cdot HP_{t-1}$$ -**Step 2: Super-Smoother Filter** +### 2. Super-Smoother Filter (Noise Removal) -A 2-pole Butterworth low-pass filter removes high-frequency noise: +A 2-pole Butterworth low-pass removes high-frequency noise: + +$$a = e^{-\sqrt{2}\pi / P_{SSF}}$$ + +$$b = 2a \cos(\sqrt{2}\pi / P_{SSF})$$ -$$a = e^{-\sqrt{2}\pi/P_{SSF}}$$ -$$b = 2a\cos(\sqrt{2}\pi/P_{SSF})$$ $$c_1 = 1 - b + a^2, \quad c_2 = b, \quad c_3 = -a^2$$ -$$\text{Filt}_t = c_1 \cdot \frac{HP_t + HP_{t-1}}{2} + c_2 \cdot \text{Filt}_{t-1} + c_3 \cdot \text{Filt}_{t-2}$$ +$$Filt_t = \frac{c_1}{2}(HP_t + HP_{t-1}) + c_2 \cdot Filt_{t-1} + c_3 \cdot Filt_{t-2}$$ -**Step 3: Hilbert Transform FIR** +### 3. Hilbert Transform FIR Discrete Hilbert approximation extracts quadrature component: -$$Q_t = 0.0962 \cdot \text{Filt}_{t-3} + 0.5769 \cdot \text{Filt}_{t-1} - 0.5769 \cdot \text{Filt}_{t-5} - 0.0962 \cdot \text{Filt}_{t-7}$$ +$$Q_t = 0.0962 \cdot Filt_{t-3} + 0.5769 \cdot Filt_{t-1} - 0.5769 \cdot Filt_{t-5} - 0.0962 \cdot Filt_{t-7}$$ -$$I_t = \text{Filt}_t$$ +$$I_t = Filt_t$$ -**Step 4: Power Normalization** +### 4. Power Normalization -$$\text{Power}_t = I_t^2 + Q_t^2$$ +$$Power_t = I_t^2 + Q_t^2$$ -$$\text{Sine}_t = \frac{I_t}{\sqrt{\text{Power}_t}}$$ +$$Sine_t = \frac{I_t}{\sqrt{Power_t}}$$ -## Performance Profile +When $Power \approx 0$, output is zero. -### Operation Count (Streaming Mode, per Bar) +### 5. Complexity -| Operation | Count | Cost (cycles) | Subtotal | -|-----------|------:|------:|------:| -| FMA | 6 | 5 | 30 | -| MUL | 8 | 4 | 32 | -| ADD/SUB | 12 | 1 | 12 | -| SQRT | 1 | 15 | 15 | -| Buffer access | 10 | 3 | 30 | -| **Total** | — | — | **~120** | +$O(1)$ per bar. Fixed filter stages with ring buffers of 2 (source) + 2 (HP) + 8 (filtered) = 12 elements. Warmup: $\max(P_{HP}, P_{SSF}) + 8$ bars. -### Complexity Analysis +## Mathematical Foundation -- **Time:** $O(1)$ per bar — fixed filter stages -- **Space:** $O(1)$ — ring buffers: 2 (src) + 2 (hp) + 8 (filt) = 12 elements -- **Latency:** max(hpPeriod, ssfPeriod) + 8 bars warmup +### Parameters -## Validation +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `hpPeriod` | High-pass filter cutoff period | 40 | $\geq 1$ | +| `ssfPeriod` | Super-smoother filter period | 10 | $\geq 1$ | -| Library | Status | Notes | -|---------|--------|-------| -| Ehlers Reference | ✅ Match | *Cybernetic Analysis* algorithm verified | -| Synthetic Chirp | ✅ Pass | Locks onto dominant frequency in passband | -| Quantower | ✅ Match | `Sine.Quantower.Tests.cs` adapter tests | +### Tuning Relationship -## Usage & Pitfalls +Typically $P_{SSF} \approx P_{HP} / 4$ to $P_{HP} / 2$. The high-pass defines the trend/cycle boundary; the super-smoother defines the noise/cycle boundary. Together they create a bandpass that isolates the frequency range of interest. -- **Trending Markets:** Strong trends cause erratic output or extremum pegging -- **Period Tuning:** hpPeriod defines trend/cycle boundary; ssfPeriod removes aliasing noise -- **Ratio Rule:** Typically ssfPeriod = hpPeriod / 4 to hpPeriod / 2 -- **Reversal Signals:** Extremes near ±1 often precede reversals in ranging markets -- **Zero Crossing:** Phase transition point—potential entry/exit signal -- **Single Output:** Unlike HT_SINE, provides only Sine (no LeadSine) +### Pseudo-code -## API +``` +function SINE(source, hpPeriod, ssfPeriod): + // Precompute HP coefficient + α_hp ← (1 - sin(2π/hpPeriod)) / cos(2π/hpPeriod) -```mermaid -classDiagram - class AbstractBase { - <> - +Name string - +WarmupPeriod int - +IsHot bool - +Last TValue - +Update(TValue input, bool isNew) TValue - +Reset() void - } - class Sine { - +HpPeriod int - +SsfPeriod int - +Sine(int hpPeriod, int ssfPeriod) - +Sine(ITValuePublisher source, int hpPeriod, int ssfPeriod) - +Update(TValue input, bool isNew) TValue - +Update(TSeries source) TSeries - +Prime(ReadOnlySpan~double~ source, TimeSpan? step) void - +Reset() void - +Calculate(TSeries source, int hpPeriod, int ssfPeriod)$ TSeries - } - AbstractBase <|-- Sine + // Precompute SSF coefficients + a ← exp(-√2·π / ssfPeriod) + b ← 2·a·cos(√2·π / ssfPeriod) + c₁ ← (1 - b + a²) / 2 + + hp_prev ← 0; p_prev ← 0 + filt_1 ← 0; filt_2 ← 0 + filtBuf ← CircularBuffer(8) + + for each price in source: + // High-pass + hp ← 0.5·(1 + α_hp)·(price - p_prev) + α_hp·hp_prev + + // Super-smoother + filt ← c₁·(hp + hp_prev) + b·filt_1 - a²·filt_2 + + // Hilbert FIR quadrature + filtBuf.Add(filt) + Q ← 0.0962·filtBuf[3] + 0.5769·filtBuf[1] + - 0.5769·filtBuf[5] - 0.0962·filtBuf[7] + I ← filt + + // Power normalization + power ← I² + Q² + sine ← (power > 0) ? I / √power : 0 + + // Shift state + hp_prev ← hp; p_prev ← price + filt_2 ← filt_1; filt_1 ← filt + + emit sine ``` -### Class: `Sine` +### SINE vs HT_SINE -Ehlers Sine Wave indicator with configurable filter periods. +| Aspect | SINE | HT_SINE | +|--------|------|---------| +| Detrending | Explicit high-pass filter | Implicit in Hilbert cascade | +| Noise removal | Explicit Super-Smoother | 4-bar WMA only | +| Period tuning | User-configurable (hpPeriod, ssfPeriod) | Fixed (TA-Lib spec) | +| Output | Single (Sine only) | Dual (Sine + LeadSine) | +| Phase source | I/Q power normalization | DFT phase accumulation | -### Properties +### Output Interpretation -| Name | Type | Description | -|------|------|-------------| -| `HpPeriod` | `int` | High-pass filter cutoff period | -| `SsfPeriod` | `int` | Super-smoother filter period | -| `IsHot` | `bool` | True after warmup complete | -| `Last` | `TValue` | Most recent Sine output (-1 to +1) | +| Condition | Meaning | +|-----------|---------| +| $Sine \approx +1$ | Cycle peak (potential short / mean-reversion) | +| $Sine \approx -1$ | Cycle trough (potential long / mean-reversion) | +| Zero crossing up | Bullish phase transition | +| Zero crossing down | Bearish phase transition | +| Erratic output | Strong trend overwhelming cycle extraction | -### Methods +## Resources -| Name | Returns | Description | -|------|---------|-------------| -| `Update(TValue, bool)` | `TValue` | Updates state with new price value | -| `Calculate(TSeries, hp, ssf)` | `TSeries` | Static factory with custom periods | -| `Reset()` | `void` | Clears all filter state | - -## C# Example - -```csharp -using QuanTAlib; - -// Create Sine indicator with default periods (40, 10) -var sine = new Sine(hpPeriod: 40, ssfPeriod: 10); - -// Process price data -foreach (var bar in bars) -{ - var result = sine.Update(new TValue(bar.Time, bar.Close)); - - if (sine.IsHot) - { - double sineValue = result.Value; - - // Cycle position interpretation - // +1.0 = cycle peak (potential short) - // -1.0 = cycle trough (potential long) - // 0.0 = mid-cycle transition - - if (sineValue > 0.9) - Console.WriteLine("Near cycle peak"); - else if (sineValue < -0.9) - Console.WriteLine("Near cycle trough"); - } -} - -// Static calculation -var sineResults = Sine.Calculate(prices, hpPeriod: 48, ssfPeriod: 12); -``` +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Ehlers, J.F.** *Cycle Analytics for Traders*. Wiley, 2013. diff --git a/lib/cycles/solar/Solar.md b/lib/cycles/solar/Solar.md index a37c2d9e..9e604ad6 100644 --- a/lib/cycles/solar/Solar.md +++ b/lib/cycles/solar/Solar.md @@ -1,163 +1,113 @@ # SOLAR: Solar Cycle Indicator -> "The sun's annual journey defines Earth's seasons—and perhaps subtle rhythms in human activity and markets." - -The Solar Cycle indicator models Earth's seasonal position relative to the Sun using astronomical ephemeris calculations. Output oscillates from -1.0 (Winter Solstice) through 0.0 (Equinoxes) to +1.0 (Summer Solstice), providing continuous seasonal phase information for econometric modeling. +SOLAR models Earth's seasonal position relative to the Sun using astronomical ephemeris calculations. Output oscillates continuously from $-1.0$ (Winter Solstice) through $0.0$ (Equinoxes) to $+1.0$ (Summer Solstice), providing a smooth, mathematically precise seasonal phase for econometric modeling. Like LUNAR, the indicator is purely time-based, requires no price data, and has zero warmup since the calculation is deterministic from any timestamp. ## Historical Context -Seasonal adjustments are fundamental to econometric analysis. Agricultural commodities, retail sales, energy consumption, and tourism all exhibit strong annual patterns. Traditional approaches use monthly dummy variables or calendar-based lookup tables, which create discontinuities at month boundaries. - -Astronomical seasonality offers a continuous, mathematically precise alternative. The Sun's ecliptic longitude provides an exact phase position within the annual cycle, smooth across all time scales. This enables more sophisticated seasonal adjustment and allows models to capture intra-month seasonal effects. - -The indicator derives from Jean Meeus' *Astronomical Algorithms*, implementing the Sun's geometric mean longitude and equation of center with sufficient precision (±0.01°) for financial applications. Unlike lunar cycles, solar seasonality is highly predictable—the tropical year varies by only seconds over centuries. +Seasonal adjustments are fundamental to econometric analysis. Agricultural commodities, retail sales, energy consumption, and tourism all exhibit strong annual patterns. Traditional approaches use monthly dummy variables or calendar-based lookup tables, creating discontinuities at month boundaries. Astronomical seasonality offers a continuous, smooth alternative: the Sun's ecliptic longitude provides an exact phase position within the annual cycle at any time resolution. The implementation derives from Jean Meeus' *Astronomical Algorithms* (1998), computing the Sun's geometric mean longitude, mean anomaly, and equation of center with sufficient precision ($\pm 0.01°$) for financial applications. Unlike lunar cycles, the tropical year's length varies by only seconds over centuries, making solar seasonality highly predictable. ## Architecture & Physics -The algorithm computes the Sun's true ecliptic longitude using low-precision ephemeris formulas optimized for seasonal indexing. +### 1. Julian Date Conversion -**Step 1: Julian Date Conversion** +$$JD = \frac{UnixMs}{86400000} + 2440587.5$$ -Convert timestamp to Julian centuries from J2000 epoch: - -$$JD = \frac{\text{UnixMs}}{86400000} + 2440587.5$$ $$T = \frac{JD - 2451545.0}{36525.0}$$ -**Step 2: Geometric Mean Longitude** +where $T$ is Julian centuries from the J2000.0 epoch. + +### 2. Geometric Mean Longitude The Sun's mean position in its apparent orbit: $$L_0 = 280.46646 + 36000.76983T + 0.0003032T^2$$ -**Step 3: Mean Anomaly** +### 3. Mean Anomaly Angular distance from perihelion: $$M = 357.52911 + 35999.05029T - 0.0001537T^2$$ -**Step 4: Equation of Center** +### 4. Equation of Center -Correction for orbital eccentricity: +Correction for orbital eccentricity ($e \approx 0.0167$): -$$C = (1.914602 - 0.004817T - 0.000014T^2)\sin M$$ -$$+ (0.019993 - 0.000101T)\sin 2M + 0.000289\sin 3M$$ +$$C = (1.914602 - 0.004817T - 0.000014T^2) \sin M + (0.019993 - 0.000101T) \sin 2M + 0.000289 \sin 3M$$ -**Step 5: True Ecliptic Longitude** +### 5. True Ecliptic Longitude -$$\lambda_{\text{Sun}} = L_0 + C$$ +$$\lambda_{Sun} = L_0 + C$$ -**Step 6: Seasonal Index** +### 6. Seasonal Index -$$\text{Solar} = \sin(\lambda_{\text{Sun}})$$ +$$Solar = \sin(\lambda_{Sun})$$ -## Performance Profile +This maps: Vernal Equinox ($\lambda = 0°$) $\to 0$, Summer Solstice ($\lambda = 90°$) $\to +1$, Autumnal Equinox ($\lambda = 180°$) $\to 0$, Winter Solstice ($\lambda = 270°$) $\to -1$. -### Operation Count (Streaming Mode, per Bar) +### 7. Complexity -| Operation | Count | Cost (cycles) | Subtotal | -|-----------|------:|------:|------:| -| FMA | 8 | 5 | 40 | -| MUL | 4 | 4 | 16 | -| ADD/SUB | 6 | 1 | 6 | -| sin | 4 | 40 | 160 | -| MOD (normalize) | 2 | 10 | 20 | -| **Total** | — | — | **~240** | +$O(1)$ per timestamp. No state required. Zero warmup. The tropical year is approximately 365.242 days. -### Complexity Analysis +## Mathematical Foundation -- **Time:** $O(1)$ — fixed computation per timestamp -- **Space:** $O(1)$ — no state required (deterministic from time) -- **Latency:** 0 bars warmup (always hot) +### Parameters -## Validation +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| (none) | No user-configurable parameters | | | -| Library | Status | Notes | -|---------|--------|-------| -| USNO Almanac | ✅ Match | Solstice/equinox dates verified | -| JPL Horizons | ✅ Match | Ecliptic longitude within ±0.01° | -| Quantower | ✅ Match | `Solar.Quantower.Tests.cs` adapter tests | +The calculation is entirely determined by the input timestamp. -## Usage & Pitfalls +### Pseudo-code -- **Hemisphere Inversion:** Output aligns with Northern Hemisphere; Southern users should negate -- **Annual Period:** ~365.242 days—extremely slow cycle, best for long-term models -- **De-seasonalizing:** Use as feature to remove annual patterns from other indicators -- **No Price Data:** Purely time-based; ignores all price input -- **UTC Timestamps:** Ensure correct timezone normalization for consistent results +``` +function SOLAR(timestamp): + // Julian date + JD ← timestamp_to_unix_ms / 86400000 + 2440587.5 + T ← (JD - 2451545.0) / 36525.0 -## API + // Geometric mean longitude + L0 ← FMA(T, FMA(T, 0.0003032, 36000.76983), 280.46646) + L0 ← mod(L0, 360) -```mermaid -classDiagram - class AbstractBase { - <> - +Name string - +WarmupPeriod int - +IsHot bool - +Last TValue - +Update(TValue input, bool isNew) TValue - +Reset() void - } - class Solar { - +Solar() - +Solar(ITValuePublisher source) - +Update(TValue input, bool isNew) TValue - +Update(TSeries source) TSeries - +CalculateCycle(DateTime dateTime)$ double - +CalculateCycle(long unixMs)$ double - +Calculate(TSeries source)$ TSeries - +Batch(ReadOnlySpan~long~ timestamps, Span~double~ output)$ void - } - AbstractBase <|-- Solar + // Mean anomaly + M ← FMA(T, FMA(T, -0.0001537, 35999.05029), 357.52911) + M ← mod(M, 360) + + // Equation of center + C ← FMA(T, FMA(T, -0.000014, -0.004817), 1.914602) · sin(M) + + FMA(T, -0.000101, 0.019993) · sin(2M) + + 0.000289 · sin(3M) + + // True ecliptic longitude + λ ← L0 + C + + // Seasonal index + solar ← sin(λ · π / 180) + + emit solar ``` -### Class: `Solar` +### Seasonal Correspondence (Northern Hemisphere) -Solar cycle indicator based on astronomical ephemeris calculations. +| Date (approx.) | $\lambda_{Sun}$ | Solar Value | Season | +|-----------------|-----------------|-------------|--------| +| March 20 | $0°$ | $0.0$ | Vernal Equinox | +| June 21 | $90°$ | $+1.0$ | Summer Solstice | +| September 22 | $180°$ | $0.0$ | Autumnal Equinox | +| December 21 | $270°$ | $-1.0$ | Winter Solstice | -### Properties +### Output Interpretation -| Name | Type | Description | -|------|------|-------------| -| `IsHot` | `bool` | Always `true` — no warmup required | -| `Last` | `TValue` | Most recent cycle output (-1.0 to +1.0) | +| Condition | Meaning | +|-----------|---------| +| $Solar \approx +1$ | Peak summer (Northern Hemisphere) | +| $Solar \approx -1$ | Peak winter (Northern Hemisphere) | +| $Solar = 0$ (rising) | Spring equinox crossing | +| $Solar = 0$ (falling) | Autumn equinox crossing | +| Southern Hemisphere | Negate the output | -### Methods +## Resources -| Name | Returns | Description | -|------|---------|-------------| -| `Update(TValue, bool)` | `TValue` | Calculates cycle for input timestamp | -| `CalculateCycle(DateTime)` | `double` | Static calculation from DateTime | -| `CalculateCycle(long)` | `double` | Static calculation from Unix ms | -| `Batch(timestamps, output)` | `void` | Vectorized calculation over timestamp span | - -## C# Example - -```csharp -using QuanTAlib; - -// Create Solar indicator -var solar = new Solar(); - -// Calculate for current time -var result = solar.Update(new TValue(DateTime.UtcNow, 0)); -Console.WriteLine($"Seasonal Index: {result.Value:F4}"); - -// Key dates interpretation: -// +1.0 = Summer Solstice (~June 21, Northern Hemisphere peak) -// 0.0 = Equinoxes (~March 20, September 22) -// -1.0 = Winter Solstice (~December 21, Northern Hemisphere minimum) - -// Static calculation for specific date -double winterSolstice = Solar.CalculateCycle(new DateTime(2024, 12, 21)); -Console.WriteLine($"Winter Solstice: {winterSolstice:F4}"); // ~-1.0 - -// Use for seasonal adjustment -foreach (var bar in bars) -{ - var solarPhase = solar.Update(new TValue(bar.Time, 0)); - - // Seasonal adjustment: remove annual pattern - double deseasonalized = bar.Close * (1.0 - 0.02 * solarPhase.Value); -} -``` +- **Meeus, J.** *Astronomical Algorithms*. 2nd ed., Willmann-Bell, 1998. +- **USNO** *Astronomical Almanac*. U.S. Government Publishing Office (annual reference for solstice/equinox verification). diff --git a/lib/cycles/ssfdsp/Ssfdsp.md b/lib/cycles/ssfdsp/Ssfdsp.md index a4612057..7e609c4d 100644 --- a/lib/cycles/ssfdsp/Ssfdsp.md +++ b/lib/cycles/ssfdsp/Ssfdsp.md @@ -1,149 +1,125 @@ # SSFDSP: Ehlers SSF Detrended Synthetic Price -> "The Super-Smoother filter provides Butterworth-quality noise rejection—combine two of them and you isolate cycles with surgical precision." - -The SSF-Based Detrended Synthetic Price (SSFDSP) is an advanced oscillator by John Ehlers. It creates a synthetic, detrended price series by subtracting a half-cycle Super-Smoother from a quarter-cycle Super-Smoother, providing superior noise rejection and reduced lag compared to EMA-based DSP. +SSFDSP isolates the dominant cycle by subtracting a half-cycle Super-Smoother from a quarter-cycle Super-Smoother, producing a zero-centered oscillator with superior noise rejection compared to the EMA-based DSP. The 2-pole Butterworth characteristic of the Super-Smoother filter provides zero phase lag at the cutoff frequency and sharper rolloff than exponential smoothing, making SSFDSP the preferred variant for cycle-aware trading when the approximate dominant period is known. ## Historical Context -Ehlers introduced the concept of "Synthetic Price" to remove the DC (trend) component from market data, isolating cyclic energy. While earlier versions used EMAs, the SSF variant exploits the 2-pole Butterworth characteristics of the Super-Smoother Filter to achieve cleaner separation between trend and cycle. - -The SSF provides zero phase lag at the cutoff frequency, making it ideal for cycle isolation in noisy market data. +John Ehlers introduced the concept of Detrended Synthetic Price in *Cybernetic Analysis for Stocks and Futures* (2004) as a principled method for removing the DC (trend) component while preserving cyclical energy. The original DSP used EMAs, which have a gradual frequency rolloff and non-zero phase lag. The SSF variant substitutes Super-Smoother filters, which are 2-pole Butterworth low-pass designs with matched coefficients that eliminate the Gibbs phenomenon (ringing) common in sharper filters. The result is a cleaner cycle extraction: the SSF's steeper rolloff better separates the quarter-cycle and half-cycle frequency bands, producing tighter zero crossings and more reliable turning point identification than EMA-DSP. ## Architecture & Physics -The indicator computes the difference between two Super-Smoother filters tuned to fractions of the dominant cycle period. - ### 1. Filter Periods -$$ -P_{fast} = \max(2, \text{round}(P / 4)) -$$ +From the user-specified dominant cycle period $P$: -$$ -P_{slow} = \max(3, \text{round}(P / 2)) -$$ +$$P_{fast} = \max(2, \lfloor P / 4 + 0.5 \rfloor)$$ + +$$P_{slow} = \max(3, \lfloor P / 2 + 0.5 \rfloor)$$ ### 2. Super-Smoother Coefficients -$$ -\alpha = \frac{\pi\sqrt{2}}{period} -$$ +For each filter period $p$: -$$ -c_2 = 2e^{-\alpha}\cos(\alpha) -$$ +$$\alpha = \frac{\pi\sqrt{2}}{p}$$ -$$ -c_3 = -e^{-2\alpha} -$$ +$$c_2 = 2 e^{-\alpha} \cos(\alpha)$$ -$$ -c_1 = 1 - c_2 - c_3 -$$ +$$c_3 = -e^{-2\alpha}$$ + +$$c_1 = 1 - c_2 - c_3$$ ### 3. SSF Recursion -$$ -SSF_t = c_1 \cdot \frac{P_t + P_{t-1}}{2} + c_2 \cdot SSF_{t-1} + c_3 \cdot SSF_{t-2} -$$ +$$SSF_t = c_1 \cdot \frac{P_t + P_{t-1}}{2} + c_2 \cdot SSF_{t-1} + c_3 \cdot SSF_{t-2}$$ + +The 2-bar input averaging provides an additional anti-aliasing stage. ### 4. SSFDSP Output -$$ -SSFDSP = SSF_{fast} - SSF_{slow} -$$ +$$SSFDSP_t = SSF_{fast,t} - SSF_{slow,t}$$ -## Performance Profile +### 5. Complexity -### Operation Count (Streaming Mode, per Bar) +$O(1)$ per bar. Two independent 2-pole IIR filters with $O(1)$ memory. Warmup: approximately $2 \times P_{slow}$ for convergence. Recursive dependencies prevent SIMD vectorization. -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| FMA (SSF updates) | 4 | 4 | 16 | -| MUL (coefficients) | 2 | 3 | 6 | -| ADD/SUB (input avg, output) | 3 | 1 | 3 | -| **Total** | **9** | — | **~25 cycles** | +## Mathematical Foundation -### Complexity Analysis +### Parameters -- **Streaming:** O(1) per bar—fixed 2-pole IIR filters -- **Memory:** O(1)—only filter state variables -- **Warmup:** ~2 × slow period for convergence -- **Note:** Recursive dependencies prevent SIMD vectorization +| Parameter | Description | Default | Constraint | +|-----------|-------------|---------|------------| +| `period` | Expected dominant cycle period | 40 | $\geq 4$ | -## Validation +### Super-Smoother Frequency Response -| Library | Status | Notes | -| :--- | :---: | :--- | -| TA-Lib | N/A | Not standard | -| Skender | N/A | Not standard | -| PineScript | ✅ | Matches Ehlers' reference logic | +The SSF has $-3$ dB attenuation at the cutoff period, $-12$ dB/octave rolloff (2-pole), and zero phase lag at the cutoff. This is equivalent to a critically-damped Butterworth filter. -## Usage & Pitfalls +### Pseudo-code -- **Oscillates around zero**—positive values indicate bullish cycle phase -- **Zero crossings** signal cycle phase changes—entry points in direction of cross -- **Period mismatch** degrades amplitude and phase accuracy -- **Smoother than EMA-DSP** with sharper turning points -- **Divergence** (price highs vs DSP highs) indicates trend exhaustion -- **Pre-smooth input** for extremely noisy data +``` +function SSFDSP(source, period): + pFast ← max(2, round(period / 4)) + pSlow ← max(3, round(period / 2)) -## API + // Fast SSF coefficients + αf ← √2·π / pFast + c2f ← 2·exp(-αf)·cos(αf) + c3f ← -exp(-2·αf) + c1f ← 1 - c2f - c3f -```mermaid -classDiagram - class Ssfdsp { - +int Period - +double Value - +bool IsHot - +Ssfdsp(int period) - +Ssfdsp(ITValuePublisher source, int period) - +TValue Update(TValue input, bool isNew) - +void Reset() - } + // Slow SSF coefficients + αs ← √2·π / pSlow + c2s ← 2·exp(-αs)·cos(αs) + c3s ← -exp(-2·αs) + c1s ← 1 - c2s - c3s + + ssfFast_1 ← 0; ssfFast_2 ← 0 + ssfSlow_1 ← 0; ssfSlow_2 ← 0 + p_prev ← 0 + + for each price in source: + // Input averaging + avg ← (price + p_prev) / 2 + + // Fast SSF update + ssfFast ← c1f·avg + c2f·ssfFast_1 + c3f·ssfFast_2 + + // Slow SSF update + ssfSlow ← c1s·avg + c2s·ssfSlow_1 + c3s·ssfSlow_2 + + // SSFDSP + ssfdsp ← ssfFast - ssfSlow + + // Shift state + ssfFast_2 ← ssfFast_1; ssfFast_1 ← ssfFast + ssfSlow_2 ← ssfSlow_1; ssfSlow_1 ← ssfSlow + p_prev ← price + + emit ssfdsp ``` -### Class: `Ssfdsp` +### DSP vs SSFDSP -| Parameter | Type | Default | Range | Description | -| :--- | :--- | :--- | :--- | :--- | -| `period` | `int` | `40` | `≥4` | Expected dominant cycle period | +| Aspect | DSP (EMA-based) | SSFDSP (Super-Smoother) | +|--------|-----------------|------------------------| +| Filter type | 1-pole IIR (exponential) | 2-pole Butterworth | +| Rolloff | $-6$ dB/octave | $-12$ dB/octave | +| Phase lag at cutoff | Non-zero | Zero | +| Noise rejection | Moderate | Superior | +| Turning points | Rounded | Sharper | -### Properties +### Output Interpretation -- `Value` (`double`): The current SSFDSP value (oscillates around 0) -- `IsHot` (`bool`): Returns `true` when warmup is complete +| Condition | Meaning | +|-----------|---------| +| $SSFDSP > 0$ | Bullish cycle phase | +| $SSFDSP < 0$ | Bearish cycle phase | +| Zero crossing | Cycle phase transition | +| Divergence with price | Cycle energy waning; trend exhaustion | +| Amplitude shrinking | Cycle losing dominance; transition to trend | -### Methods +## Resources -- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point - -## C# Example - -```csharp -using QuanTAlib; - -// Initialize with a 40-bar dominant cycle assumption -var ssfdsp = new Ssfdsp(period: 40); - -// Update with streaming data -foreach (var bar in quotes) -{ - var result = ssfdsp.Update(new TValue(bar.Date, bar.Close)); - - if (ssfdsp.IsHot) - { - Console.WriteLine($"{bar.Date}: SSF-DSP = {result.Value:F4}"); - - // Zero crossing detection - if (result.Value > 0 && ssfdsp.Previous.Value <= 0) - Console.WriteLine(" → Bullish cycle phase"); - else if (result.Value < 0 && ssfdsp.Previous.Value >= 0) - Console.WriteLine(" → Bearish cycle phase"); - } -} - -// Batch calculation -var output = Ssfdsp.Calculate(sourceSeries, period: 40); -``` +- **Ehlers, J.F.** *Cybernetic Analysis for Stocks and Futures*. Wiley, 2004. +- **Ehlers, J.F.** *Cycle Analytics for Traders*. Wiley, 2013. +- **Butterworth, S.** "On the Theory of Filter Amplifiers." *Experimental Wireless*, 7, 1930. diff --git a/lib/cycles/stc/stc.md b/lib/cycles/stc/stc.md index c23b2be1..377e2629 100644 --- a/lib/cycles/stc/stc.md +++ b/lib/cycles/stc/stc.md @@ -1,195 +1,149 @@ # STC: Schaff Trend Cycle -> "By applying the Stochastic twice to MACD, we reveal the cycle hidden within the trend itself." - -The Schaff Trend Cycle is a cyclometric oscillator that improves upon MACD by passing it through a double-Stochastic process. This recursive normalization detects market cycles with greater speed and accuracy, producing a bounded 0-100 indicator that reaches extremes earlier than MACD while avoiding Stochastic jitter. +The Schaff Trend Cycle is a cyclometric oscillator that applies double-Stochastic normalization to MACD, extracting the cyclical phase hidden within the trend itself. The recursive normalization produces a bounded 0–100 output that reaches extremes earlier than raw MACD while suppressing Stochastic jitter. Developed for currency markets, STC's tendency to flatline at 0 or 100 during strong trends signals continuation rather than reversal — a feature that distinguishes it from conventional momentum oscillators. Output converges toward a square wave in steady-state trending conditions. ## Historical Context -Doug Schaff developed the STC in the 1990s while trading currency markets. He observed that the MACD, while excellent at identifying trends, suffered from lag—by the time it signaled, much of the move had already occurred. Conversely, the Stochastic oscillator was fast but noisy, generating numerous false signals. - -Schaff's insight was that trends themselves move in cycles. By applying the Stochastic normalization formula recursively to MACD values, he could extract the cyclical phase of the trend. The "Stochastic of a Stochastic" creates a self-normalizing oscillator that converges toward a square wave in steady-state conditions. - -The STC found particular popularity in forex trading where its speed advantage over MACD proved valuable in the 24-hour market. The indicator's tendency to "flatline" at extremes (0 or 100) during strong trends—initially seen as a limitation—became recognized as a feature: it signals trend continuation rather than reversal. +Doug Schaff developed STC in the 1990s while trading currency markets. His diagnosis: MACD identified trends correctly but with unacceptable lag — by signal time, much of the move had elapsed. The Stochastic oscillator was fast but noisy, generating false signals in trending markets. Schaff's synthesis recognized that trends themselves move in cycles. Rather than choosing between lagging trend detection and noisy cycle extraction, he piped MACD through the Stochastic twice. The first pass normalizes MACD within its recent range, collapsing the unbounded trend signal into a 0–100 band. The second pass normalizes the smoothed first pass, further compressing the cycle information and creating a self-normalizing oscillator. The double normalization acts as a nonlinear filter that amplifies transitions and suppresses noise during sustained moves. STC found particular traction in forex trading where the 24-hour market rewarded speed advantages over MACD. The flatline behavior at extremes — initially dismissed as a limitation — became recognized as a defining feature: sustained 0 or 100 readings indicate trend continuation with high confidence, equivalent to a digital "trend on" signal. ## Architecture & Physics -The algorithm implements a deep signal processing pipeline with recursive Stochastic normalization. +### 1. MACD Construction -**Step 1: MACD Construction** - -Fast and slow EMAs generate the trend signal: +Fast and slow EMAs generate the raw trend signal: $$\alpha_f = \frac{2}{\text{fastLength} + 1}, \quad \alpha_s = \frac{2}{\text{slowLength} + 1}$$ -$$\text{EMA}_f = \alpha_f P_t + (1 - \alpha_f)\text{EMA}_{f,t-1}$$ -$$\text{EMA}_s = \alpha_s P_t + (1 - \alpha_s)\text{EMA}_{s,t-1}$$ +$$\text{EMA}_{f,t} = \alpha_f \cdot P_t + (1 - \alpha_f) \cdot \text{EMA}_{f,t-1}$$ -$$\text{MACD}_t = \text{EMA}_f - \text{EMA}_s$$ +$$\text{EMA}_{s,t} = \alpha_s \cdot P_t + (1 - \alpha_s) \cdot \text{EMA}_{s,t-1}$$ -**Step 2: First Stochastic (%K₁)** +$$\text{MACD}_t = \text{EMA}_{f,t} - \text{EMA}_{s,t}$$ -Normalize MACD within its recent range: +### 2. First Stochastic (%K₁) -$$\%K_1 = 100 \times \frac{\text{MACD}_t - \min(\text{MACD}_{t-k:t})}{\max(\text{MACD}_{t-k:t}) - \min(\text{MACD}_{t-k:t})}$$ +Normalize MACD within its recent $k$-bar range: -**Step 3: First Smoothing (%D₁)** +$$\%K_1 = 100 \times \frac{\text{MACD}_t - \min(\text{MACD}_{t-k+1:t})}{\max(\text{MACD}_{t-k+1:t}) - \min(\text{MACD}_{t-k+1:t})}$$ -EMA smooth the first Stochastic: +When $\max = \min$ (flat MACD), $\%K_1$ holds its previous value. This collapses the unbounded MACD into [0, 100]. -$$\%D_1 = \alpha_d \cdot \%K_1 + (1 - \alpha_d) \cdot \%D_{1,t-1}$$ +### 3. First Smoothing (%D₁) -**Step 4: Second Stochastic (%K₂)** +EMA smooth the first Stochastic to reduce whipsaw: -Apply Stochastic normalization again to %D₁: +$$\alpha_d = \frac{2}{d\text{Period} + 1}$$ -$$\%K_2 = 100 \times \frac{\%D_1 - \min(\%D_{1,t-k:t})}{\max(\%D_{1,t-k:t}) - \min(\%D_{1,t-k:t})}$$ +$$\%D_{1,t} = \alpha_d \cdot \%K_{1,t} + (1 - \alpha_d) \cdot \%D_{1,t-1}$$ -**Step 5: Final Output** +### 4. Second Stochastic (%K₂) + +Apply Stochastic normalization again to %D₁, using the same $k$-bar window: + +$$\%K_2 = 100 \times \frac{\%D_{1,t} - \min(\%D_{1,t-k+1:t})}{\max(\%D_{1,t-k+1:t}) - \min(\%D_{1,t-k+1:t})}$$ + +This second pass further compresses the signal, amplifying transitions between trend phases. + +### 5. Final Smoothing Apply selected smoothing method to %K₂: -$$\text{STC}_t = \text{Smooth}(\%K_2)$$ +$$\text{STC}_t = \text{Smooth}(\%K_{2,t})$$ -Smoothing options: None, EMA, Sigmoid, Digital (threshold-based) +Smoothing options: -## Performance Profile +- **None:** Raw %K₂ output +- **EMA:** Standard EMA smoothing with $\alpha_d$ +- **Sigmoid:** $S(x) = \frac{100}{1 + e^{-0.1(x - 50)}}$ — S-curve compression +- **Digital:** Threshold at 50 → output snaps to 0 or 100 (square wave) -### Operation Count (Streaming Mode, per Bar) +### 6. Complexity -| Operation | Count | Cost (cycles) | Subtotal | -|-----------|------:|------:|------:| -| FMA | 8 | 5 | 40 | -| MUL | 12 | 4 | 48 | -| ADD/SUB | 20 | 1 | 20 | -| DIV | 4 | 15 | 60 | -| MIN/MAX scan | 2×k | 2 | ~40 | -| Clamp | 4 | 3 | 12 | -| **Total** | — | — | **~220** | +- **Time:** $O(k)$ per bar for min/max scanning over both Stochastic windows +- **Space:** $O(k)$ — two ring buffers of size kPeriod (MACD values and %D₁ values) +- **Warmup:** slowLength + kPeriod bars before output stabilizes -### Complexity Analysis +## Mathematical Foundation -- **Time:** $O(k)$ per bar for min/max scanning (optimized with incremental tracking) -- **Space:** $O(k)$ — two ring buffers of size kPeriod -- **Latency:** slowLength + kPeriod bars warmup +### Parameters -## Validation +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $k$ | kPeriod | 10 | $k \geq 2$ | +| $d$ | dPeriod | 3 | $d \geq 1$ | +| $f$ | fastLength | 23 | $f \geq 1$ | +| $s$ | slowLength | 50 | $s > f$ | +| — | smoothing | EMA | None / EMA / Sigmoid / Digital | -| Library | Status | Notes | -|---------|--------|-------| -| Manual Calculation | ✅ Match | Step-by-step pipeline verified | -| TradingView | ✅ Match | Cross-validated against TV implementation | -| Quantower | ✅ Match | `Stc.Quantower.Tests.cs` adapter tests | +### Pseudo-code -## Usage & Pitfalls +``` +Initialize: + ema_fast = ema_slow = first price + α_f = 2 / (fastLength + 1) + α_s = 2 / (slowLength + 1) + α_d = 2 / (dPeriod + 1) + macd_buf = RingBuffer(kPeriod) + d1_buf = RingBuffer(kPeriod) + %D₁ = 0 + bar_count = 0 -- **Flatlining Expected:** STC stays at 0 or 100 during strong trends—this is trend continuation, not broken data -- **Cycle Length:** kPeriod ≈ fastLength/2 targets the cycle within the MACD trend -- **Threshold Zones:** Below 25 = oversold, above 75 = overbought -- **Smoothing Modes:** EMA (default), Sigmoid (S-curve), Digital (square wave), None -- **Recursive Dependencies:** Cannot be vectorized with SIMD due to sequential state -- **Square Wave Convergence:** In steady trends, output approaches binary 0/100 behavior +On each bar (price, isNew): + if !isNew: restore previous state -## API + // Step 1: MACD + ema_fast = FMA(ema_fast, 1 - α_f, α_f × price) + ema_slow = FMA(ema_slow, 1 - α_s, α_s × price) + macd = ema_fast - ema_slow -```mermaid -classDiagram - class AbstractBase { - <> - +Name string - +WarmupPeriod int - +IsHot bool - +Last TValue - +Update(TValue input, bool isNew) TValue - +Reset() void - } - class Stc { - +IsNew bool - +Stc(int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing) - +Stc(ITValuePublisher source, int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing) - +Update(TValue input, bool isNew) TValue - +Update(TSeries source) TSeries - +Prime(ReadOnlySpan~double~ source, TimeSpan? step) void - +Reset() void - +Calculate(TSeries source, int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing)$ TSeries - +Calculate(ReadOnlySpan~double~ source, Span~double~ output, ...)$ void - } - class StcSmoothing { - <> - None - Ema - Sigmoid - Digital - } - AbstractBase <|-- Stc - Stc ..> StcSmoothing + // Step 2: First Stochastic + macd_buf.Add(macd) + macd_max = Max(macd_buf) + macd_min = Min(macd_buf) + range1 = macd_max - macd_min + %K₁ = range1 > 0 ? 100 × (macd - macd_min) / range1 : prev_%K₁ + + // Step 3: First Smoothing + %D₁ = FMA(%D₁, 1 - α_d, α_d × %K₁) + + // Step 4: Second Stochastic + d1_buf.Add(%D₁) + d1_max = Max(d1_buf) + d1_min = Min(d1_buf) + range2 = d1_max - d1_min + %K₂ = range2 > 0 ? 100 × (%D₁ - d1_min) / range2 : prev_%K₂ + + // Step 5: Final Smoothing + switch smoothing: + None: STC = %K₂ + EMA: STC = FMA(prev_STC, 1 - α_d, α_d × %K₂) + Sigmoid: STC = 100 / (1 + exp(-0.1 × (%K₂ - 50))) + Digital: STC = %K₂ ≥ 50 ? 100 : 0 + + output = Clamp(STC, 0, 100) ``` -### Class: `Stc` +### Signal Characteristics -Schaff Trend Cycle oscillator with configurable smoothing. +| Condition | Output Behavior | +|-----------|----------------| +| Strong uptrend | Flatlines at 100 (square wave high) | +| Strong downtrend | Flatlines at 0 (square wave low) | +| Trend transition | Rapid swing between extremes | +| Ranging market | Oscillates mid-range (25–75) | +| Above 75 | Overbought zone | +| Below 25 | Oversold zone | -### Properties +### Cycle Length Heuristic -| Name | Type | Description | -|------|------|-------------| -| `IsHot` | `bool` | True after warmup complete | -| `IsNew` | `bool` | Whether last update was a new bar | -| `Last` | `TValue` | Most recent STC output (0-100) | +Setting $k \approx f/2$ targets the half-cycle of the MACD's dominant frequency, aligning the Stochastic window with the trend's internal oscillation period. -### Methods +### SIMD Applicability -| Name | Returns | Description | -|------|---------|-------------| -| `Update(TValue, bool)` | `TValue` | Updates state with new price value | -| `Calculate(TSeries, ...)` | `TSeries` | Static factory with all parameters | -| `Calculate(span, span, ...)` | `void` | Zero-allocation span-based calculation | -| `Reset()` | `void` | Clears all internal state | +The recursive EMA dependencies and sequential min/max ring buffer updates prevent SIMD vectorization of the streaming path. The `Calculate(Span)` path can parallelize independent MACD computations but must serialize the double-Stochastic pipeline. -## C# Example +## Resources -```csharp -using QuanTAlib; - -// Create STC with standard parameters -var stc = new Stc( - kPeriod: 10, // Stochastic lookback - dPeriod: 3, // Smoothing period - fastLength: 23, // Fast EMA for MACD - slowLength: 50, // Slow EMA for MACD - smoothing: StcSmoothing.Ema -); - -// Process price data -foreach (var bar in bars) -{ - var result = stc.Update(new TValue(bar.Time, bar.Close)); - - if (stc.IsHot) - { - double value = result.Value; - - // Signal interpretation - if (value > 75) - Console.WriteLine("Overbought zone"); - else if (value < 25) - Console.WriteLine("Oversold zone"); - - // Note: Flatlining at 0 or 100 indicates strong trend - if (value == 100) - Console.WriteLine("Strong uptrend continuation"); - else if (value == 0) - Console.WriteLine("Strong downtrend continuation"); - } -} - -// Static calculation with different smoothing -var results = Stc.Calculate( - prices, - kPeriod: 10, - dPeriod: 3, - fastLength: 23, - slowLength: 50, - smoothing: StcSmoothing.Digital // Square wave output -); -``` +- Schaff, D. — "Schaff Trend Cycle" (currency trading methodology, 1990s) +- PineScript reference: `stc.pine` in indicator directory +- Ehlers, J.F. — *Cybernetic Analysis for Stocks and Futures* (cycle extraction theory) diff --git a/lib/dynamics/adx/Adx.md b/lib/dynamics/adx/Adx.md index 615e51f6..41710c9e 100644 --- a/lib/dynamics/adx/Adx.md +++ b/lib/dynamics/adx/Adx.md @@ -1,93 +1,127 @@ # ADX: Average Directional Index -> "Is the market trending?" is the only question that matters. ADX answers it, loudly. - -The Average Directional Index (ADX) is the industry-standard filter for trend strength. It ignores direction entirely, focusing solely on the velocity of price expansion. It allows systems to switch context: deploying trend-following logic when the market moves, and mean-reversion logic when it chops. +The Average Directional Index is the industry-standard measure of trend strength, ignoring direction entirely to focus on the velocity of price expansion. Wilder's pipeline decomposes range into directional movement (+DM, -DM), normalizes against True Range to produce directional indicators (+DI, -DI), derives a directional index (DX) from their ratio, then smooths DX with a final RMA pass. The double-smoothed architecture creates significant lag but exceptional noise rejection, making ADX a regime filter rather than a timing tool. Output is unbounded above 0, with readings above 25 conventionally indicating trending conditions and below 20 indicating choppy markets. ## Historical Context -J. Welles Wilder Jr. was a mechanical engineer, and it shows. Introduced in *New Concepts in Technical Trading Systems* (1978), the ADX is a machine built from moving parts. It doesn't just smooth price; it deconstructs range expansion, normalizes it against volatility, and then smooths the result twice. +J. Welles Wilder Jr. introduced ADX in *New Concepts in Technical Trading Systems* (1978). Wilder was a mechanical engineer, and the design reflects that discipline: a machine built from modular components where each stage has a defined transfer function. The indicator does not attempt to predict direction. It answers a single question — "Is the market trending?" — and answers it with ruthless indifference to which way. -It is not a modern, low-lag indicator. It is a heavy, momentum-based flywheel that takes time to spin up and time to spin down. +ADX is a "derivative of a derivative." The calculation pipeline is deep: price range decomposes into directional movement, directional movement normalizes into directional indicators, directional indicators compress into DX, and DX smooths into ADX. Each layer strips noise at the cost of latency. A "cold" start requires at least $2N$ bars to produce statistically meaningful output, and often $3\text{--}4N$ bars to converge to within 4 decimal places of a mature series. The QuanTAlib implementation tracks warmup state explicitly — garbage is not published during convergence. ## Architecture & Physics -The ADX is a "derivative of a derivative." The calculation pipeline is deep, which creates significant lag but offers exceptional noise reduction. +### 1. Directional Movement -1. **Decomposition**: Price action is broken into Directional Movement (+DM, -DM) and Volatility (True Range). -2. **Normalization**: Raw movement is meaningless without context. DM is normalized by TR to get Directional Indicators (+DI, -DI). -3. **Oscillation**: The Directional Index (DX) is derived from the ratio of the difference to the sum of the DIs. -4. **Smoothing**: Finally, the DX is smoothed to get ADX. +Today's range expansion is compared to yesterday's: -### The Stability Problem +$$\text{UpMove} = H_t - H_{t-1}$$ -Because ADX relies on recursive smoothing (RMA) at multiple stages, it is notoriously slow to converge. A "cold" start requires at least $2 \times Period$ bars to produce data that even remotely resembles a mature series, and often $3-4 \times Period$ to match external libraries (like TA-Lib) within 4 decimal places. +$$\text{DownMove} = L_{t-1} - L_t$$ -The QuanTAlib implementation handles this by tracking the "warmup" state explicitly. Garbage is not output during the convergence phase if it can be avoided, but users must be aware that ADX is history-dependent. +$$+DM = \begin{cases} \text{UpMove} & \text{if UpMove} > \text{DownMove and UpMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ + +$$-DM = \begin{cases} \text{DownMove} & \text{if DownMove} > \text{UpMove and DownMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ + +Only one of +DM or -DM can be non-zero per bar — the dominant direction wins. + +### 2. Wilder Smoothing (RMA) + +All three series (+DM, -DM, TR) are smoothed using Wilder's Moving Average with $\alpha = 1/N$: + +$$+DM_{\text{smooth}} = \text{RMA}(+DM, N)$$ + +$$-DM_{\text{smooth}} = \text{RMA}(-DM, N)$$ + +$$TR_{\text{smooth}} = \text{RMA}(TR, N)$$ + +### 3. Directional Indicators + +Normalize smoothed movement against smoothed volatility: + +$$+DI = 100 \times \frac{+DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +$$-DI = 100 \times \frac{-DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +### 4. Directional Index and ADX + +$$DX = 100 \times \frac{|+DI - (-DI)|}{+DI + (-DI)}$$ + +$$ADX = \text{RMA}(DX, N)$$ + +### 5. Complexity + +- **Time:** $O(1)$ per bar — all RMA updates are recursive +- **Space:** $O(1)$ — scalar state only (no buffers) +- **Warmup:** $\approx 2N$ bars minimum; $3\text{--}4N$ for full convergence ## Mathematical Foundation -The math is classic Wilder: recursive, stateful, and robust. +### Parameters -### 1. Directional Movement (DM) +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | -Today's range is compared to yesterday's. -$$ \text{UpMove} = H_t - H_{t-1} $$ -$$ \text{DownMove} = L_{t-1} - L_t $$ +### Pseudo-code -$$ +DM = \begin{cases} \text{UpMove} & \text{if } \text{UpMove} > \text{DownMove} \text{ and } \text{UpMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +``` +Initialize: + α = 1 / period + smoothPlusDM = smoothMinusDM = smoothTR = 0 + adx = 0 + prevHigh = prevLow = NaN + bar_count = 0 -$$ -DM = \begin{cases} \text{DownMove} & \text{if } \text{DownMove} > \text{UpMove} \text{ and } \text{DownMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +On each bar (high, low, close, isNew): + if !isNew: restore previous state -### 2. Smoothing (RMA) + TR = max(high - low, |high - prevClose|, |low - prevClose|) -Wilder's Moving Average (RMA) is an exponential moving average with $\alpha = 1/N$. The series $+DM$, $-DM$, and $TR$ (True Range) are smoothed using this operator. + upMove = high - prevHigh + downMove = prevLow - low -$$ +DM_{smoothed} = RMA(+DM, N) $$ -$$ -DM_{smoothed} = RMA(-DM, N) $$ -$$ TR_{smoothed} = RMA(TR, N) $$ + +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 + -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 -### 3. Directional Indicators (DI) + // Wilder smoothing (RMA) + smoothPlusDM = FMA(smoothPlusDM, 1 - α, α × +DM) + smoothMinusDM = FMA(smoothMinusDM, 1 - α, α × -DM) + smoothTR = FMA(smoothTR, 1 - α, α × TR) -$$ +DI = 100 \times \frac{+DM_{smoothed}}{TR_{smoothed}} $$ -$$ -DI = 100 \times \frac{-DM_{smoothed}}{TR_{smoothed}} $$ + // Directional Indicators + +DI = 100 × smoothPlusDM / smoothTR + -DI = 100 × smoothMinusDM / smoothTR -### 4. The Index (DX and ADX) + // Directional Index + diSum = +DI + -DI + DX = diSum > 0 ? 100 × |+DI - -DI| / diSum : 0 -$$ DX = 100 \times \frac{|+DI - -DI|}{+DI + -DI} $$ -$$ ADX = RMA(DX, N) $$ + // Final smoothing + ADX = FMA(ADX, 1 - α, α × DX) -## Performance Profile + prevHigh = high + prevLow = low + prevClose = close + output = ADX +``` -Throughput is optimized. The recursive nature of RMA allows for O(1) updates, but the initial calculation over a span requires O(N). +### The Stability Problem -### Zero-Allocation Design +Because ADX relies on recursive RMA at multiple stages, convergence is slow. Period 14 needs roughly 40-56 bars before matching TA-Lib to 4 decimal places. The first $2N$ values are mathematically correct but statistically immature — treat them as warmup artifacts. -The implementation uses `stackalloc` for internal buffers when processing spans, ensuring no heap allocations occur during the calculation. The hot path for streaming updates is purely scalar and allocation-free. +### ADX Interpretation -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 5ns | 5ns / bar (Apple M1 Max). | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(1) | Constant time for streaming updates. | -| **Accuracy** | 10/10 | Matches TA-Lib to 1e-9. | -| **Timeliness** | 2/10 | Significant lag due to double smoothing. | -| **Overshoot** | 10/10 | Very stable; rarely overshoots. | -| **Smoothness** | 10/10 | Exceptional noise reduction. | +| ADX Value | Market Regime | +|-----------|---------------| +| < 20 | Absent or weak trend (range-bound) | +| 20–25 | Emerging trend | +| 25–50 | Strong trend | +| 50–75 | Very strong trend | +| > 75 | Extremely strong (rare) | -## Validation +ADX peaks *after* the trend has exhausted — it is a lagging indicator of trend strength, not a leading indicator of reversal. -Validation is performed against industry-standard libraries. +## Resources -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | ✅ | Matches `TA_ADX` to 1e-9. | -| **Skender** | ✅ | Matches `GetAdx`. | -| **Tulip** | ✅ | Matches `ti.adx` (with offset adjustment). | -| **Ooples** | ❌ | Deviates significantly (10.7 vs 25.2). | - -### Common Pitfalls - -* **Period Sensitivity**: The standard period is 14. Lowering it (e.g., 7) makes ADX twitchy and prone to false positives. Raising it (e.g., 30) turns it into a geological indicator—accurate, but late. -* **The "Turn"**: ADX peaks *after* the trend has exhausted. It is a lagging indicator of trend strength, not a leading indicator of price reversal. -* **Convergence**: Do not trust the first $2 \times N$ values. They are mathematically correct but statistically immature. +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `adx.pine` in indicator directory diff --git a/lib/dynamics/adxr/Adxr.md b/lib/dynamics/adxr/Adxr.md index 9a1a83fe..0d4b1721 100644 --- a/lib/dynamics/adxr/Adxr.md +++ b/lib/dynamics/adxr/Adxr.md @@ -1,77 +1,93 @@ # ADXR: Average Directional Movement Rating -> If ADX is the speedometer, ADXR is the cruise control setting. It smooths out the acceleration to tell you if the trend has staying power. - -The Average Directional Movement Rating (ADXR) is a smoothed version of the ADX. It dampens the volatility of the ADX itself, providing a more stable—albeit significantly more lagging—measure of trend strength. It is primarily used to rate the efficacy of trend-following strategies before capital is committed. +The Average Directional Movement Rating is a smoothed version of ADX that dampens short-term fluctuations in trend strength by averaging the current ADX with a historical ADX value. This creates a doubly-lagged metric that sacrifices all timing utility in exchange for stable regime classification. ADXR answers one question: does the current market environment reward trend-following strategies? If ADXR is high, deploy momentum logic. If low, deploy mean-reversion. It is a strategic filter, not a tactical signal. ## Historical Context -J. Welles Wilder Jr. introduced ADXR alongside ADX in *New Concepts in Technical Trading Systems* (1978). His goal was simple: ADX can be erratic. By averaging the current ADX with a past ADX, he created a metric that ignores short-term fluctuations in trend strength. - -It is effectively a "momentum of momentum" indicator, smoothed to the point of geological stability. +J. Welles Wilder Jr. introduced ADXR alongside ADX in *New Concepts in Technical Trading Systems* (1978). His reasoning was pragmatic: ADX itself can be erratic during transitions between trending and ranging regimes, producing whipsaw readings that confuse systematic allocation. By averaging the current ADX with its value from $N-1$ bars ago, Wilder created a "momentum of momentum" indicator smoothed to geological stability. The ADXR found its architectural niche not as a trading signal but as a capital allocation filter — determining whether a trend-following system should be active at all. Its double lag (ADX already lags price; ADXR lags ADX) makes it useless for entry timing by design. ## Architecture & Physics -ADXR is a composite indicator. It does not interact with price directly; it interacts with the output of the ADX. +### 1. ADX Dependency -1. **Dependency**: It instantiates and maintains a full `Adx` indicator internally. -2. **History**: It maintains a circular buffer of historical ADX values. -3. **Averaging**: It computes the arithmetic mean of the current ADX and the ADX from `Period - 1` bars ago. +ADXR is a composite indicator that does not interact with price directly. It instantiates and maintains a full ADX pipeline internally: -### The Lag Trade-off +$$\text{Price} \rightarrow \text{DM/TR} \rightarrow \text{RMA} \rightarrow \text{DI} \rightarrow \text{DX} \rightarrow \text{ADX} \rightarrow \text{ADXR}$$ -ADXR is intentionally slow. +### 2. Historical Buffer -* **ADX** lags price because of its multiple smoothing layers. -* **ADXR** lags ADX because it averages the current value with a value from the distant past. +A circular buffer of size $N$ stores historical ADX values, providing $O(1)$ access to the value from $N-1$ bars ago. -This double lag makes ADXR useless for entry timing. Its only valid architectural purpose is **regime filtering**: determining *if* a trend-following system should be active, not *when* it should trade. +### 3. Rating Calculation + +$$ADXR_t = \frac{ADX_t + ADX_{t-(N-1)}}{2}$$ + +The $N-1$ lag (rather than $N$) matches TA-Lib's reference implementation exactly. + +### 4. Complexity + +- **Time:** $O(1)$ per bar — ADX update plus one buffer lookup and one average +- **Space:** $O(N)$ — circular buffer for ADX history +- **Warmup:** $\approx 3N$ bars (ADX convergence + buffer fill) ## Mathematical Foundation -The formula is deceptively simple, but relies on the complex ADX calculation underneath. +### Parameters -$$ ADXR_t = \frac{ADX_t + ADX_{t-(n-1)}}{2} $$ +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | -Where: +The period controls both the internal ADX calculation and the historical lookback depth. -* $ADX_t$ is the current ADX value. -* $n$ is the Period (typically 14). -* $ADX_{t-(n-1)}$ is the ADX value from `n-1` periods ago. +### Pseudo-code -*Note: The `n-1` lag is used to match TA-Lib's implementation exactly. Some sources cite `n`, but standard reference implementations use `n-1`.* +``` +Initialize: + adx = new Adx(period) + adxBuffer = RingBuffer(period) + bar_count = 0 -## Performance Profile +On each bar (high, low, close, isNew): + if !isNew: restore previous state -The performance cost is dominated by the underlying ADX calculation. The ADXR step itself is trivial. + // Full ADX pipeline + adxValue = adx.Update(high, low, close, isNew) -### Zero-Allocation Design + // Store in history + adxBuffer.Add(adxValue) + bar_count++ -The implementation uses a circular buffer (`RingBuffer`) to store historical ADX values, ensuring O(1) access and zero heap allocations during the update cycle. + // ADXR = average of current and (N-1)-lagged ADX + if bar_count >= period: + historicalAdx = adxBuffer[0] // oldest value in buffer + ADXR = (adxValue + historicalAdx) / 2.0 + else: + ADXR = adxValue // insufficient history -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 6ns | 6ns / bar (Apple M1 Max). | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(1) | Ring buffer access is constant time. | -| **Accuracy** | 10/10 | Matches TA-Lib to 1e-9. | -| **Timeliness** | 1/10 | Double lag (ADX + History). | -| **Overshoot** | 10/10 | Extremely stable. | -| **Smoothness** | 10/10 | Extremely stable trend rating. | + output = ADXR +``` -## Validation +### Lag Analysis -Validation is performed against industry-standard libraries. +| Component | Lag Source | +|-----------|-----------| +| DM → RMA | $\approx N$ bars (Wilder smoothing) | +| DX → ADX | $\approx N$ bars (second RMA) | +| ADX → ADXR | $N-1$ bars (historical average) | +| **Total effective lag** | $\approx 3N - 1$ bars | -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **TA-Lib** | ✅ | Matches `TA_ADXR` to 1e-9. | -| **Skender** | N/A | Not implemented in Skender. | -| **Tulip** | ✅ | Matches `ti.adxr`. | -| **Ooples** | N/A | Not implemented. | +For the default period of 14, ADXR carries roughly 41 bars of effective lag. This is a feature, not a limitation — it ensures that only sustained regime changes register in the output. -### Common Pitfalls +### Regime Classification -* **Using for Entries**: Do not use ADXR crossovers for entries. The signal is too late. -* **Short Periods**: Using a short period (e.g., 3) defeats the purpose of ADXR. If you want responsiveness, use ADX. ADXR is for stability. +| ADXR Value | Interpretation | +|------------|----------------| +| < 20 | Sustained range-bound; favor mean-reversion | +| 20–25 | Ambiguous regime; reduce position sizing | +| > 25 | Sustained trending; favor momentum strategies | + +## Resources + +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `adxr.pine` in indicator directory diff --git a/lib/dynamics/alligator/Alligator.md b/lib/dynamics/alligator/Alligator.md index c9d352cf..d1a30f1e 100644 --- a/lib/dynamics/alligator/Alligator.md +++ b/lib/dynamics/alligator/Alligator.md @@ -1,137 +1,113 @@ -# Alligator +# ALLIGATOR: Williams Alligator -> The market is a beast. When it sleeps, stay out. When it wakes, ride the momentum. - -The Williams Alligator is a trend-following indicator developed by Bill Williams. It uses three smoothed moving averages (SMMA) with different periods and forward offsets to visualize market phases: sleeping (consolidation), awakening (trend start), and eating (strong trend). +The Williams Alligator is a trend-following system that uses three Smoothed Moving Averages (SMMA/RMA) with different periods and forward display offsets to visualize market phases. The Jaw (13-period, offset 8), Teeth (8-period, offset 5), and Lips (5-period, offset 3) create a layered structure where intertwined lines indicate consolidation ("sleeping") and separated, aligned lines indicate trending conditions ("eating"). The metaphor maps directly to position management: stay out when the alligator sleeps, ride when it eats. Each line uses Wilder's smoothing ($\alpha = 1/N$), which is heavier than standard EMA, providing superior noise rejection at the cost of additional lag. ## Historical Context -Bill Williams introduced the Alligator in his 1995 book *Trading Chaos*. The metaphor is vivid: the three lines represent the Jaw (blue), Teeth (red), and Lips (green) of an alligator. When the lines are intertwined, the alligator is "sleeping" and the market is in consolidation. When the lines separate and align, the alligator is "awake" and "eating," indicating a strong trend. +Bill Williams introduced the Alligator in *Trading Chaos* (1995) as part of his broader chaos theory framework for trading. The metaphor is biological: markets alternate between feeding (trending) and sleeping (ranging) states, and the three moving averages at different timescales reveal which phase is active. The Jaw represents the long-term balance line (the "blue line" on most charting platforms), the Teeth the intermediate balance (red), and the Lips the short-term momentum (green). Williams paired the Alligator with Fractals for entry timing and the Awesome Oscillator for momentum confirmation, creating a complete systematic framework. The forward offsets are display-only transformations — the underlying SMMA calculation uses the current bar's price — but they create visual separation that makes trend direction immediately apparent on charts. ## Architecture & Physics -The Alligator uses three SMMA (Smoothed Moving Average) lines, each with a different period and forward offset: +### 1. Three-Line SMMA Structure -| Line | Period | Offset | Color | Role | -|------|--------|--------|-------|------| -| **Jaw** | 13 | 8 | Blue | Slowest; shows long-term trend | -| **Teeth** | 8 | 5 | Red | Medium; shows intermediate trend | -| **Lips** | 5 | 3 | Green | Fastest; shows short-term momentum | +Each line is an independent SMMA (Wilder's RMA) with $\alpha = 1/N$: -### SMMA (Wilder's Smoothing) +| Line | Period ($N$) | Display Offset | Role | +|------|-------------|----------------|------| +| Jaw | 13 | 8 bars forward | Long-term trend (slowest) | +| Teeth | 8 | 5 bars forward | Intermediate trend | +| Lips | 5 | 3 bars forward | Short-term momentum (fastest) | -Each line uses Wilder's smoothing (also called RMA or SMMA), which is an EMA variant with $\alpha = 1/\text{period}$ instead of the standard $2/(\text{period}+1)$. +### 2. SMMA Recursion -$$ \text{SMMA}_t = \alpha \cdot \text{Price} + (1 - \alpha) \cdot \text{SMMA}_{t-1} $$ +$$\text{SMMA}_t = \frac{1}{N} \cdot P_t + \frac{N-1}{N} \cdot \text{SMMA}_{t-1}$$ -where $\alpha = 1/\text{period}$ +Equivalently using FMA notation: -### Forward Offset +$$\text{SMMA}_t = \text{FMA}(\text{SMMA}_{t-1},\; \tfrac{N-1}{N},\; \tfrac{1}{N} \cdot P_t)$$ -The offsets shift each line forward in time, creating visual separation that makes trend direction more apparent. This is a display-only transformation—the underlying SMMA calculation uses the current bar's price. +### 3. Default Input + +Typical price (HLC/3): + +$$\text{Source} = \frac{H + L + C}{3}$$ + +### 4. Forward Offset + +The offsets shift plotted values forward in time for display purposes only. The calculation itself is not shifted — the current SMMA value represents the current bar's computation. + +### 5. Complexity + +- **Time:** $O(1)$ per bar — three parallel SMMA updates +- **Space:** $O(1)$ — three scalar states (no buffers needed) +- **Warmup:** 13 bars (Jaw period, the slowest line) ## Mathematical Foundation -For each line (Jaw, Teeth, Lips): +### Parameters -$$ \text{SMMA}(P, N) = \frac{\text{Price} + \text{SMMA}_{t-1} \cdot (N - 1)}{N} $$ +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N_j$ | jawPeriod | 13 | $N_j \geq 1$ | +| $O_j$ | jawOffset | 8 | $O_j \geq 0$ | +| $N_t$ | teethPeriod | 8 | $N_t \geq 1$ | +| $O_t$ | teethOffset | 5 | $O_t \geq 0$ | +| $N_l$ | lipsPeriod | 5 | $N_l \geq 1$ | +| $O_l$ | lipsOffset | 3 | $O_l \geq 0$ | -Or equivalently using the recursive form: +### Pseudo-code -$$ \text{SMMA}_t = \frac{1}{N} \cdot \text{Price} + \frac{N-1}{N} \cdot \text{SMMA}_{t-1} $$ +``` +Initialize: + α_jaw = 1 / jawPeriod + α_teeth = 1 / teethPeriod + α_lips = 1 / lipsPeriod + jaw = teeth = lips = first source value + e_jaw = e_teeth = e_lips = 1.0 // bias compensation -Default input is HLC/3 (typical price): +On each bar (high, low, close, isNew): + if !isNew: restore previous state -$$ \text{Source} = \frac{\text{High} + \text{Low} + \text{Close}}{3} $$ + source = (high + low + close) / 3.0 -## Performance Profile + // SMMA updates with bias compensation + jaw = FMA(jaw, 1 - α_jaw, α_jaw × source) + e_jaw = e_jaw × (1 - α_jaw) + jaw_compensated = jaw / (1 - e_jaw) -The implementation uses inline SMMA calculations with bias compensation for accurate warmup behavior. + teeth = FMA(teeth, 1 - α_teeth, α_teeth × source) + e_teeth = e_teeth × (1 - α_teeth) + teeth_compensated = teeth / (1 - e_teeth) -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 5ns | Per bar, all three lines. | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(1) | Three parallel SMMA updates. | -| **Accuracy** | 10/10 | Matches TradingView exactly. | -| **Timeliness** | 7/10 | SMMA is slower than standard EMA. | -| **Overshoot** | 3/10 | Minimal overshoot; smooth response. | -| **Smoothness** | 9/10 | SMMA provides excellent smoothing. | + lips = FMA(lips, 1 - α_lips, α_lips × source) + e_lips = e_lips × (1 - α_lips) + lips_compensated = lips / (1 - e_lips) -## Trading Interpretation - -### Market Phases - -1. **Sleeping Alligator**: Lines are intertwined, crossing each other. The market is in consolidation. Avoid trading. - -2. **Awakening**: Lines begin to separate and align (Lips crosses Teeth crosses Jaw). A trend is starting. - -3. **Eating**: Lines are parallel and widely separated. Strong trend in progress. Follow the direction. - -4. **Sated**: Lines begin to converge again. The trend is weakening. Consider taking profits. - -### Entry Signals - -- **Buy**: Lips > Teeth > Jaw (all ascending, widely separated) -- **Sell**: Lips < Teeth < Jaw (all descending, widely separated) - -### Filters - -- Avoid trading when lines are intertwined (sleeping) -- Wait for clear separation before entering -- Exit when lines begin to converge - -## Validation - -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **TradingView** | ✅ | Matches built-in Alligator. | -| **MT4/MT5** | ✅ | Matches standard implementation. | -| **Skender** | N/A | Not implemented. | -| **TA-Lib** | N/A | Not implemented. | - -## Usage - -```csharp -// Default parameters: Jaw(13,8), Teeth(8,5), Lips(5,3) -var alligator = new Alligator(); - -// Custom parameters -var alligator = new Alligator( - jawPeriod: 13, jawOffset: 8, - teethPeriod: 8, teethOffset: 5, - lipsPeriod: 5, lipsOffset: 3 -); - -// Update with price bar -alligator.Update(bar); - -// Access the three lines -double jaw = alligator.Jaw.Value; -double teeth = alligator.Teeth.Value; -double lips = alligator.Lips.Value; - -// Offsets for plotting -int jawOffset = alligator.JawOffset; // 8 -int teethOffset = alligator.TeethOffset; // 5 -int lipsOffset = alligator.LipsOffset; // 3 + output: + Jaw = jaw_compensated (plot at bar + jawOffset) + Teeth = teeth_compensated (plot at bar + teethOffset) + Lips = lips_compensated (plot at bar + lipsOffset) ``` -## Related Indicators +### Market Phase Detection -- **Gator Oscillator**: Histogram showing separation between Alligator lines -- **Fractals**: Williams' fractal patterns for entry timing -- **AO (Awesome Oscillator)**: Momentum confirmation -- **AC (Acceleration/Deceleration)**: Momentum acceleration +| Phase | Line Configuration | Action | +|-------|-------------------|--------| +| Sleeping | Lines intertwined, crossing | No position; market is consolidating | +| Awakening | Lines begin separating | Prepare for entry | +| Eating (bullish) | Lips > Teeth > Jaw, all rising | Long; trend is strong | +| Eating (bearish) | Lips < Teeth < Jaw, all falling | Short; trend is strong | +| Sated | Lines converging | Take profits; trend weakening | -## Common Pitfalls +### Output Interpretation -- **Ignoring the offset**: The offset is for plotting only. The current SMMA value represents the current bar's calculation, shifted forward for display. -- **Trading during sleep**: Most losses occur when trading during consolidation phases. -- **Premature entry**: Wait for clear separation, not just the first cross. +- **Three values per bar:** Jaw, Teeth, Lips (each a smoothed price level) +- **Separation width:** Proportional to trend strength +- **Line ordering:** Determines trend direction +- **Intertwining:** Signals consolidation — the highest-probability losing zone for trend followers -## References +## Resources -- Williams, Bill. *Trading Chaos: Applying Expert Techniques to Maximize Your Profits*. John Wiley & Sons, 1995. -- Williams, Bill. *New Trading Dimensions*. John Wiley & Sons, 1998. +- Williams, B. — *Trading Chaos* (John Wiley & Sons, 1995) +- Williams, B. — *New Trading Dimensions* (John Wiley & Sons, 1998) +- PineScript reference: `alligator.pine` in indicator directory diff --git a/lib/dynamics/amat/Amat.md b/lib/dynamics/amat/Amat.md index d3c49990..94e1220a 100644 --- a/lib/dynamics/amat/Amat.md +++ b/lib/dynamics/amat/Amat.md @@ -1,195 +1,117 @@ # AMAT: Archer Moving Averages Trends -> "Markets trend about 30% of the time. The trick isn't just finding trends—it's confirming them before your stops get hit." - -AMAT (Archer Moving Averages Trends) is a trend identification system that uses dual EMAs to provide clear directional signals. Unlike simple moving average crossovers that generate signals on any intersection, AMAT requires **alignment** of both fast and slow averages moving in the same direction—reducing false signals during choppy, sideways markets. +The Archer Moving Averages Trends indicator is a triple-confirmation trend identification system that uses dual EMAs to produce discrete directional signals (+1 bullish, -1 bearish, 0 neutral). Unlike simple crossover systems that trigger on any intersection, AMAT requires alignment of three conditions: relative position (fast above/below slow), fast EMA direction (rising/falling), and slow EMA direction (rising/falling). This triple gate filters out the whipsaw endemic to single-condition crossover systems in ranging markets. A secondary output quantifies trend strength as the percentage separation between EMAs, providing a conviction metric for position sizing. ## Historical Context -AMAT emerged from concepts developed by Mark Whistler (known as "Archer" in trading circles) and was formalized by Tom Joseph in 2009. The indicator addresses a fundamental problem with traditional crossover systems: they generate excessive whipsaws in ranging markets because a crossover only measures relative position, not directional agreement. - -The innovation lies in requiring **three conditions** for a trend signal: - -1. Relative position (fast above/below slow) -2. Fast EMA direction (rising/falling) -3. Slow EMA direction (rising/falling) - -This triple-confirmation approach filters out the noise inherent in single-condition systems. +AMAT emerged from concepts attributed to Mark Whistler ("Archer" in trading circles) and was formalized by Tom Joseph in 2009. The indicator addresses a specific failure mode of traditional MA crossover systems: they generate excessive false signals during sideways markets because a crossover only measures relative position, not directional agreement. A fast EMA can cross above a slow EMA while both are falling — technically a "bullish crossover" but practically meaningless. AMAT's innovation is requiring all three conditions to align before committing to a directional call. The neutral state (output = 0) captures market indecision explicitly: when EMAs disagree on direction or their relative position contradicts their momentum, AMAT stays flat. Markets trend roughly 30% of the time. AMAT is designed to identify that 30% with high confidence and stay silent the other 70%. ## Architecture & Physics -AMAT operates on dual EMA calculations with directional analysis. The computational flow: +### 1. Dual EMA Computation -``` -Input Price - │ - ├──► Fast EMA ───► Direction (rising/falling) - │ │ - │ ▼ - └──► Slow EMA ───► Direction (rising/falling) - │ - ▼ - Trend Logic (+1, -1, 0) - │ - ▼ - Strength = |Fast - Slow| / Slow × 100 -``` +Two independent EMAs with bias compensation during warmup: -### Trend State Machine +$$\text{EMA}_t = \alpha \cdot P_t + (1 - \alpha) \cdot \text{EMA}_{t-1}$$ -| State | Fast vs Slow | Fast Direction | Slow Direction | -|:------|:------------|:---------------|:---------------| -| **Bullish (+1)** | Fast > Slow | Rising | Rising | -| **Bearish (-1)** | Fast < Slow | Falling | Falling | -| **Neutral (0)** | Any | Mixed | Mixed | +where $\alpha = \frac{2}{N + 1}$ -The neutral state captures market indecision: when EMAs disagree on direction or their relative position contradicts their momentum, AMAT stays flat. This is a feature, not a limitation. +Bias compensation removes initialization distortion: -### EMA Bias Compensation - -QuanTAlib's implementation uses bias-compensated EMAs during the warmup phase. Traditional EMA initialization assumes the first price equals the true average—a convenient fiction. The compensator factor `e` decays exponentially: - -$$e_{t} = e_{t-1} \times (1 - \alpha)$$ - -Until convergence, the EMA is divided by $(1 - e)$ to remove initialization bias. - -## Mathematical Foundation - -### 1. EMA Calculation - -$$\text{EMA}_t = \alpha \times P_t + (1 - \alpha) \times \text{EMA}_{t-1}$$ - -Where $\alpha = \frac{2}{n + 1}$ and $n$ is the period. +$$e_t = e_{t-1} \times (1 - \alpha), \quad \text{EMA}_{\text{comp}} = \frac{\text{EMA}_t}{1 - e_t}$$ ### 2. Direction Detection -$$\text{Direction}_t = \begin{cases} \text{rising} & \text{if } \text{EMA}_t > \text{EMA}_{t-1} \\ \text{falling} & \text{if } \text{EMA}_t < \text{EMA}_{t-1} \\ \text{flat} & \text{otherwise} \end{cases}$$ +$$\text{Dir}_t = \begin{cases} +1 & \text{if } \text{EMA}_t > \text{EMA}_{t-1} \\ -1 & \text{if } \text{EMA}_t < \text{EMA}_{t-1} \\ 0 & \text{otherwise} \end{cases}$$ -### 3. Trend Signal +### 3. Triple-Confirmation Logic -$$\text{Trend}_t = \begin{cases} +1 & \text{if } \text{FastEMA}_t > \text{SlowEMA}_t \land \text{FastRising} \land \text{SlowRising} \\ -1 & \text{if } \text{FastEMA}_t < \text{SlowEMA}_t \land \text{FastFalling} \land \text{SlowFalling} \\ 0 & \text{otherwise} \end{cases}$$ +$$\text{Trend}_t = \begin{cases} +1 & \text{if Fast} > \text{Slow} \;\land\; \text{FastDir} = +1 \;\land\; \text{SlowDir} = +1 \\ -1 & \text{if Fast} < \text{Slow} \;\land\; \text{FastDir} = -1 \;\land\; \text{SlowDir} = -1 \\ 0 & \text{otherwise} \end{cases}$$ ### 4. Trend Strength -$$\text{Strength}_t = \frac{|\text{FastEMA}_t - \text{SlowEMA}_t|}{\text{SlowEMA}_t} \times 100$$ +$$\text{Strength}_t = \frac{|\text{Fast}_t - \text{Slow}_t|}{\text{Slow}_t} \times 100$$ -Strength quantifies the separation between EMAs as a percentage of the slow EMA—useful for gauging trend conviction or filtering weak signals. +### 5. Complexity -## Usage +- **Time:** $O(1)$ per bar — two EMA updates plus comparisons +- **Space:** $O(1)$ — scalar state only +- **Warmup:** slowPeriod bars -```csharp -// Standard instantiation -var amat = new Amat(fastPeriod: 10, slowPeriod: 50); +## Mathematical Foundation -// Process streaming data -foreach (var price in prices) -{ - amat.Update(new TValue(DateTime.UtcNow, price)); +### Parameters - if (amat.Last.Value == 1.0) - Console.WriteLine($"Bullish - Strength: {amat.Strength.Value:F2}%"); - else if (amat.Last.Value == -1.0) - Console.WriteLine($"Bearish - Strength: {amat.Strength.Value:F2}%"); - else - Console.WriteLine("Neutral"); -} +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N_f$ | fastPeriod | 10 | $N_f \geq 1$ | +| $N_s$ | slowPeriod | 50 | $N_s > N_f$ | -// Access individual EMAs -double fastEma = amat.FastEma.Value; -double slowEma = amat.SlowEma.Value; +### Pseudo-code -// Batch processing -var results = Amat.Batch(priceSeries, fastPeriod: 10, slowPeriod: 50); +``` +Initialize: + α_fast = 2 / (fastPeriod + 1) + α_slow = 2 / (slowPeriod + 1) + ema_fast = ema_slow = 0 + e_fast = e_slow = 1.0 + prev_fast = prev_slow = 0 + bar_count = 0 -// Span-based high-performance -double[] trend = new double[prices.Length]; -double[] strength = new double[prices.Length]; -Amat.Calculate(prices.AsSpan(), trend, strength, fastPeriod: 10, slowPeriod: 50); +On each bar (price, isNew): + if !isNew: restore previous state + + // EMA updates + ema_fast = FMA(ema_fast, 1 - α_fast, α_fast × price) + e_fast = e_fast × (1 - α_fast) + fast = ema_fast / (1 - e_fast) + + ema_slow = FMA(ema_slow, 1 - α_slow, α_slow × price) + e_slow = e_slow × (1 - α_slow) + slow = ema_slow / (1 - e_slow) + + // Direction detection + fastDir = fast > prev_fast ? +1 : fast < prev_fast ? -1 : 0 + slowDir = slow > prev_slow ? +1 : slow < prev_slow ? -1 : 0 + + // Triple-confirmation + if fast > slow AND fastDir == +1 AND slowDir == +1: + trend = +1 + else if fast < slow AND fastDir == -1 AND slowDir == -1: + trend = -1 + else: + trend = 0 + + // Strength + strength = slow > 0 ? |fast - slow| / slow × 100 : 0 + + prev_fast = fast + prev_slow = slow + + output: + Trend = trend // +1, -1, or 0 + Strength = strength // percentage ``` -### Event-Driven (Chained) +### Period Selection Guidelines -```csharp -var source = new TSeries(); -var amat = new Amat(source, fastPeriod: 10, slowPeriod: 50); +| Use Case | Fast | Slow | Ratio | +|----------|------|------|-------| +| Scalping | 5 | 13 | 1:2.6 | +| Swing | 10 | 50 | 1:5 | +| Position | 20 | 100 | 1:5 | +| Investment | 50 | 200 | 1:4 | -// AMAT automatically updates when source publishes -source.Add(new TValue(DateTime.UtcNow, 100.0)); -Console.WriteLine($"Trend: {amat.Last.Value}"); -``` +Fast periods too close to slow periods produce excessive neutral readings. A ratio of 1:4 to 1:5 provides effective separation. -## Parameters +### Discrete Output Properties -| Parameter | Type | Default | Description | -|:----------|:-----|:--------|:------------| -| `fastPeriod` | int | 10 | Fast EMA period (must be > 0) | -| `slowPeriod` | int | 50 | Slow EMA period (must be > fastPeriod) | +- **+1:** All three conditions align bullish — high-confidence uptrend +- **-1:** All three conditions align bearish — high-confidence downtrend +- **0:** Any disagreement — indeterminate; no position recommended +- **Strength:** Quantifies EMA separation as percentage of slow EMA; useful for position sizing but not directional signal -### Common Period Combinations +## Resources -| Use Case | Fast | Slow | Notes | -|:---------|:-----|:-----|:------| -| **Scalping** | 5 | 13 | High responsiveness, more signals | -| **Swing** | 10 | 50 | Balanced, classic configuration | -| **Position** | 20 | 100 | Filtered for major trends | -| **Investment** | 50 | 200 | Long-term directional bias | - -## Output Properties - -| Property | Type | Description | -|:---------|:-----|:------------| -| `Last` | TValue | Trend direction: +1 (bullish), -1 (bearish), 0 (neutral) | -| `Strength` | TValue | Trend strength as percentage | -| `FastEma` | TValue | Current fast EMA value | -| `SlowEma` | TValue | Current slow EMA value | -| `IsHot` | bool | True when both EMAs are fully warmed | -| `WarmupPeriod` | int | Equal to slowPeriod | - -## Performance Profile - -| Metric | Score | Notes | -|:-------|:------|:------| -| **Throughput** | ~15 ns/bar | Dual EMA + direction check | -| **Allocations** | 0 | Streaming mode is allocation-free | -| **Complexity** | O(1) | Constant time per update | -| **Accuracy** | 9/10 | Bias-compensated EMAs match external libs | -| **Timeliness** | 7/10 | Triple-confirmation adds slight lag | -| **Overshoot** | 8/10 | No overshoot; discrete {-1, 0, +1} output | -| **Smoothness** | 6/10 | State transitions can be abrupt | - -## Validation - -AMAT is a custom indicator not present in standard TA libraries. Validation confirms: - -| Component | Library | Status | Notes | -|:----------|:--------|:-------|:------| -| **Fast EMA** | TA-Lib | ✅ | Matches `TA_EMA` | -| **Fast EMA** | Skender | ✅ | Matches `GetEma` | -| **Slow EMA** | TA-Lib | ✅ | Matches `TA_EMA` | -| **Slow EMA** | Skender | ✅ | Matches `GetEma` | -| **Trend Logic** | Manual | ✅ | Verified against known patterns | -| **Strength** | Manual | ✅ | Formula verification | - -## Common Pitfalls - -### 1. Expecting Continuous Signals - -AMAT returns 0 (neutral) frequently. This is intentional—choppy markets produce neutral signals. Trading systems should respect neutral states rather than forcing a directional bias. - -### 2. Period Selection - -Fast periods that are too close to slow periods produce excessive neutral readings. A ratio of 1:5 (e.g., 10/50) provides reasonable separation. - -### 3. Strength Interpretation - -High strength doesn't guarantee trend continuation. It measures current separation, not momentum. A declining strength during a +1 trend may indicate weakening conviction. - -### 4. Initialization Phase - -Until `IsHot` returns true, trend signals may be unreliable. The indicator needs `slowPeriod` bars to stabilize both EMAs. - -## See Also - -- [EMA](../trends/ema/Ema.md) - Exponential Moving Average (AMAT's building block) -- [MACD](../momentum/macd/Macd.md) - Another dual-EMA system with different logic -- [ADX](../momentum/adx/Adx.md) - Trend strength without directional bias +- Joseph, T. — AMAT trend confirmation methodology (2009) +- PineScript reference: `amat.pine` in indicator directory diff --git a/lib/dynamics/aroon/Aroon.md b/lib/dynamics/aroon/Aroon.md index 670528de..88d48631 100644 --- a/lib/dynamics/aroon/Aroon.md +++ b/lib/dynamics/aroon/Aroon.md @@ -1,73 +1,113 @@ -# Aroon +# AROON: Aroon Indicator -> Price levels are irrelevant. The only thing that matters is *when* they happened. Aroon is a stopwatch for trends. - -The Aroon indicator measures the temporal freshness of price extremes. Unlike oscillators that obsess over *how much* price has moved, Aroon asks *how long* it has been since a new high or low. It quantifies the "staleness" of a trend, providing an early warning system for consolidation and reversals. +The Aroon indicator measures the temporal freshness of price extremes, answering not "how much did price move?" but "how long ago did it make a new high or low?" Aroon Up tracks the recency of the highest high within the lookback window; Aroon Down tracks the recency of the lowest low. Both are normalized to 0-100 where 100 means the extreme occurred on the current bar and 0 means it occurred at the far edge of the window. A companion Aroon Oscillator (Up minus Down) provides a single zero-centered metric for trend bias. Unlike recursive indicators that accumulate floating-point drift, Aroon is purely windowed — its value depends only on data within the lookback period, making it immune to initialization artifacts. ## Historical Context -Tushar Chande introduced Aroon in *Beyond Technical Analysis* (1995). The name comes from the Sanskrit word for "Dawn's Early Light." Chande's insight was that trends don't just stop; they age. By measuring the time elapsed since the last extreme, Aroon attempts to spot the "dawn" of a new trend rather than just confirming an existing one. +Tushar Chande introduced Aroon in *Beyond Technical Analysis* (1995). The name comes from the Sanskrit word for "Dawn's Early Light," reflecting the indicator's purpose: to spot the dawn of a new trend rather than merely confirm an existing one. Chande's insight was that trends do not simply stop; they age. A trend that has not made a new high in 20 of the last 25 bars is statistically moribund, regardless of how strong the original breakout was. The temporal perspective inverts the usual analysis framework: instead of asking whether price is above or below some average, Aroon asks whether the market is still making progress in a given direction. This makes it particularly effective at identifying the transition zone between trending and ranging regimes. ## Architecture & Physics -Aroon is purely time-based. It normalizes the "days since" metric into a 0-100 oscillator. +### 1. Sliding Window -1. **Time Tracking**: A sliding window of the last $N$ bars is maintained. -2. **Extremum Search**: The index of the highest high and lowest low within that window is located. -3. **Normalization**: The distance (in bars) is converted into a percentage. +A circular buffer of size $N+1$ stores the last $N+1$ bars of High and Low values (the current bar plus $N$ historical bars). -### The Logic of Freshness +### 2. Extremum Search -* **Aroon Up**: Quantifies the recency of the High. - * 100: New high today. - * 0: No new high for the entire period. -* **Aroon Down**: Quantifies the recency of the Low. - * 100: New low today. - * 0: No new low for the entire period. -* **Oscillator**: The net difference ($Up - Down$), showing the dominant temporal force. +On each bar, the buffer is scanned to find the index of the highest high and the index of the lowest low within the window. + +### 3. Aroon Up + +$$\text{AroonUp} = \frac{N - \text{barsSinceHigh}}{N} \times 100$$ + +where barsSinceHigh is the number of bars elapsed since the highest high. + +### 4. Aroon Down + +$$\text{AroonDown} = \frac{N - \text{barsSinceLow}}{N} \times 100$$ + +### 5. Aroon Oscillator + +$$\text{AroonOsc} = \text{AroonUp} - \text{AroonDown}$$ + +Range: $[-100, +100]$. + +### 6. Complexity + +- **Time:** $O(N)$ per bar for the min/max linear scan (monotonic deque optimization possible for amortized $O(1)$) +- **Space:** $O(N)$ — ring buffers for High and Low +- **Warmup:** $N$ bars to fill the window ## Mathematical Foundation -The math is a linear decay function based on time. +### Parameters -$$ \text{Aroon Up} = \frac{Period - \text{Days Since High}}{Period} \times 100 $$ +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 25 | $N \geq 1$ | -$$ \text{Aroon Down} = \frac{Period - \text{Days Since Low}}{Period} \times 100 $$ +### Pseudo-code -$$ \text{Oscillator} = \text{Aroon Up} - \text{Aroon Down} $$ +``` +Initialize: + highBuf = RingBuffer(period + 1) + lowBuf = RingBuffer(period + 1) + bar_count = 0 -## Performance Profile +On each bar (high, low, isNew): + if !isNew: restore previous state -While memory is O(P), computational complexity is linear with respect to the period due to the min/max search. + highBuf.Add(high) + lowBuf.Add(low) + bar_count++ -### Zero-Allocation Design + // Find index of highest high in buffer + maxIdx = 0 + maxVal = -∞ + for i = 0 to min(bar_count, period): + if highBuf[i] >= maxVal: + maxVal = highBuf[i] + maxIdx = i -The implementation uses a circular buffer (`RingBuffer`) to store historical highs and lows, ensuring O(1) access and zero heap allocations during the update cycle. The min/max search is performed in-place on the buffer. + // Find index of lowest low in buffer + minIdx = 0 + minVal = +∞ + for i = 0 to min(bar_count, period): + if lowBuf[i] <= minVal: + minVal = lowBuf[i] + minIdx = i -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 10ns | 10ns / bar. | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(P) | Linear scan for extremes. | -| **Accuracy** | 10/10 | Matches standard implementations. | -| **Timeliness** | 10/10 | Reacts immediately to new extremes. | -| **Overshoot** | 0/10 | Bounded 0-100. | -| **Smoothness** | 2/10 | Step-function behavior. | + len = min(bar_count, period) + barsSinceHigh = len - maxIdx + barsSinceLow = len - minIdx -## Validation + AroonUp = (len - barsSinceHigh) / len × 100 + AroonDown = (len - barsSinceLow) / len × 100 + AroonOsc = AroonUp - AroonDown -Validation is performed against industry-standard libraries. + output: + Up = AroonUp + Down = AroonDown + Oscillator = AroonOsc +``` -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **Skender** | ✅ | Matches `GetAroon`. | -| **TA-Lib** | ✅ | Matches `TA_AROON` and `TA_AROONOSC`. | -| **Tulip** | ✅ | Matches `ti.aroon` and `ti.aroonosc`. | +### Interpretation -| **Ooples** | N/A | Not implemented. | +| Condition | Signal | +|-----------|--------| +| AroonUp > 70, AroonDown < 30 | Strong uptrend (recent highs, stale lows) | +| AroonDown > 70, AroonUp < 30 | Strong downtrend (recent lows, stale highs) | +| Both > 70 | Volatile; both extremes are fresh | +| Both < 30 | Consolidation; both extremes are stale | +| AroonOsc > 0 | Bullish bias | +| AroonOsc < 0 | Bearish bias | -### Common Pitfalls +### Step-Function Behavior -* **Single Value Updates**: If you feed Aroon only `Close` prices (instead of High/Low), it degrades into a "Time Since Highest Close" metric. It works, but it loses the nuance of intraday extremes. -* **The 70/30 Rule**: A common interpretation is that a trend is strong only if the primary line is > 70. Values between 30 and 70 often indicate noise or consolidation. +Aroon produces discrete jumps rather than smooth curves. When a new extreme occurs, the corresponding line snaps to 100. Between new extremes, the line decays linearly by $100/N$ per bar. This staircase pattern is a natural consequence of the temporal measurement and should not be smoothed away — it carries information about the periodicity of extremes. + +## Resources + +- Chande, T.S. — *Beyond Technical Analysis* (John Wiley & Sons, 1995) +- Chande, T.S. — *The New Technical Trader* (John Wiley & Sons, 1995) +- PineScript reference: `aroon.pine` in indicator directory diff --git a/lib/dynamics/aroonosc/AroonOsc.md b/lib/dynamics/aroonosc/AroonOsc.md index f2e5c1c9..64ec4a3f 100644 --- a/lib/dynamics/aroonosc/AroonOsc.md +++ b/lib/dynamics/aroonosc/AroonOsc.md @@ -1,72 +1,95 @@ -# AroonOsc: Aroon Oscillator +# AROONOSC: Aroon Oscillator -> Tushar Chande's Aroon system is a dual-line argument. The Oscillator is the verdict. - -The Aroon Oscillator condenses the struggle between the "Aroon Up" and "Aroon Down" lines into a single, normalized value. It quantifies not just the existence of a trend, but its freshness. It answers the question: "Are new highs appearing faster than new lows?" +The Aroon Oscillator condenses the dual-line Aroon system into a single zero-centered value by computing $\text{AroonUp} - \text{AroonDown}$. This distills the temporal battle between fresh highs and fresh lows into a bounded $[-100, +100]$ metric where positive values indicate bullish recency dominance and negative values indicate bearish. Unlike recursive indicators that accumulate floating-point drift, the Aroon Oscillator is purely windowed — its value depends only on data within the lookback period, making it stateless in the long term and immune to initialization poisoning. The step-function output reflects discrete events (new extremes appearing or aging out) rather than smooth price trajectories. ## Historical Context -Introduced by Tushar Chande in *The New Technical Trader* (1995), the Aroon system was a departure from price-based momentum. It focused on *time*. While RSI asks "how much did price move?", Aroon asks "how long has it been since the last extreme?". The Oscillator is simply the arithmetic difference between the two, providing a zero-centered metric for trend bias. +Tushar Chande introduced the Aroon system in *The New Technical Trader* (1995) as a departure from price-magnitude momentum. While RSI and MACD ask "how much did price move?", Aroon asks "how long has it been since the last extreme?" The Oscillator is the net verdict of this temporal argument. Chande's key observation was that the recency of extremes carries more information about trend health than the magnitude of movements. A market making new highs every few bars is trending up regardless of the size of each increment. The Oscillator pegs at +100 when a new high appears on every bar within the window (maximum bullish freshness), and at -100 when new lows dominate. The middle ground (values near zero) indicates neither extreme is particularly fresh — the temporal signature of consolidation. ## Architecture & Physics -The physics of Aroon are temporal, not spatial. It measures the decay of "recency." +### 1. Sliding Window Buffers -1. **Time Measurement**: The bars since the highest high and lowest low within the period are counted. -2. **Normalization**: These counts are converted to a 0-100 scale (100 = happened right now, 0 = happened `Period` bars ago). -3. **Differential**: The Oscillator is `Up - Down`. +Two ring buffers of size $N+1$ store the last $N+1$ bars of High and Low values. -### The Drift Resistance +### 2. Extremum Location -Unlike recursive indicators (EMA, RSI) which accumulate floating-point errors over time, Aroon is stateless in the long term. Its value depends *only* on the data within the lookback window. This makes it mathematically robust and immune to "poisoning" from bad data in the distant past. +On each bar, scan the buffers to locate the index of the highest high and the lowest low. + +### 3. Aroon Components + +$$\text{AroonUp} = \frac{N - \text{barsSinceHigh}}{N} \times 100$$ + +$$\text{AroonDown} = \frac{N - \text{barsSinceLow}}{N} \times 100$$ + +### 4. Oscillator + +$$\text{AroonOsc} = \text{AroonUp} - \text{AroonDown}$$ + +### 5. Complexity + +- **Time:** $O(N)$ per bar for min/max scanning +- **Space:** $O(N)$ — two ring buffers +- **Warmup:** $N$ bars to fill the window ## Mathematical Foundation -The math is purely arithmetic. +### Parameters -### 1. Aroon Up +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 25 | $N \geq 1$ | -$$ \text{AroonUp} = \frac{\text{Period} - \text{Days Since High}}{\text{Period}} \times 100 $$ +### Pseudo-code -### 2. Aroon Down +``` +Initialize: + highBuf = RingBuffer(period + 1) + lowBuf = RingBuffer(period + 1) + bar_count = 0 -$$ \text{AroonDown} = \frac{\text{Period} - \text{Days Since Low}}{\text{Period}} \times 100 $$ +On each bar (high, low, isNew): + if !isNew: restore previous state -### 3. The Oscillator + highBuf.Add(high) + lowBuf.Add(low) + bar_count++ -$$ \text{AroonOsc} = \text{AroonUp} - \text{AroonDown} $$ + len = min(bar_count, period) -## Performance Profile + // Scan for extremes + maxIdx = index of maximum in highBuf over last (len + 1) entries + minIdx = index of minimum in lowBuf over last (len + 1) entries -The algorithm is $O(N)$ where $N$ is the period, as the window must be scanned for extremes. However, for typical periods (14-25), this is negligible. + barsSinceHigh = len - maxIdx + barsSinceLow = len - minIdx -### Zero-Allocation Design + AroonUp = (len - barsSinceHigh) / len × 100 + AroonDown = (len - barsSinceLow) / len × 100 -The implementation uses a circular buffer (`RingBuffer`) to store historical highs and lows, ensuring O(1) access and zero heap allocations during the update cycle. The min/max search is performed in-place on the buffer. + output = AroonUp - AroonDown +``` -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 10ns | 10ns / bar. | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(P) | Linear scan of the lookback window. | -| **Accuracy** | 10/10 | Matches standard implementations. | -| **Timeliness** | 10/10 | Reacts immediately to new extremes. | -| **Overshoot** | 0/10 | Bounded -100 to +100. | -| **Smoothness** | 2/10 | Step-function behavior. | +### Drift Immunity -## Validation +Unlike EMA-based oscillators that accumulate rounding errors across thousands of bars, the Aroon Oscillator is computed fresh each bar from a finite window. There is no recursive state that can diverge. This makes it architecturally robust for long-running production systems where indicator drift is a concern. -Validation is performed against industry-standard libraries. +### Interpretation -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **Skender** | ✅ | Matches `GetAroon` (Oscillator). | -| **TA-Lib** | ✅ | Matches `TA_AROONOSC`. | -| **Tulip** | ✅ | Matches `ti.aroonosc`. | -| **Ooples** | ❌ | Deviates significantly from standard. | +| AroonOsc Value | Meaning | +|----------------|---------| +| +100 | New high every bar; no new lows (maximum bullish) | +| +50 to +100 | Strong bullish bias; highs are fresh | +| 0 | Balanced; both extremes equally stale/fresh | +| -50 to -100 | Strong bearish bias; lows are fresh | +| -100 | New low every bar; no new highs (maximum bearish) | -### Common Pitfalls +### Flatlining Behavior -* **Lag**: Because it looks back `Period` bars, it will not signal a reversal until the previous extreme "ages out" or is superseded. It is a lagging indicator of trend changes. -* **Flatlining**: In strong trends, the oscillator can peg at +100 or -100 for extended periods. This is a feature, not a bug—it indicates a "fresh" extreme on every bar. +In strong trends, the oscillator can hold +100 or -100 for sustained periods. This indicates a continuously refreshing extreme — the market is making a new high (or low) on virtually every bar. This is not saturation; it is the temporal signature of a parabolic move. + +## Resources + +- Chande, T.S. — *The New Technical Trader* (John Wiley & Sons, 1995) +- Chande, T.S. — *Beyond Technical Analysis* (John Wiley & Sons, 1995) +- PineScript reference: `aroonosc.pine` in indicator directory diff --git a/lib/dynamics/chop/Chop.md b/lib/dynamics/chop/Chop.md index 6c1819a4..6c8a0d50 100644 --- a/lib/dynamics/chop/Chop.md +++ b/lib/dynamics/chop/Chop.md @@ -1,128 +1,107 @@ -# Choppiness Index (CHOP) +# CHOP: Choppiness Index -The **Choppiness Index** is a non-directional volatility indicator developed by Australian commodity trader **E.W. Dreiss**. It measures whether the market is trending or trading sideways (choppy), helping traders identify optimal conditions for trend-following or range-trading strategies. +The Choppiness Index is a non-directional regime indicator that measures whether the market is trending or trading sideways. It compares total price movement (sum of True Range) to net price movement (high-low channel width) using a logarithmic ratio, producing a bounded value where high readings indicate choppy/consolidating conditions and low readings indicate trending conditions. CHOP does not indicate direction — only whether directional strategies are likely to succeed. The logarithmic scaling normalizes the output to approximately 0-100 regardless of price level or volatility magnitude. ## Historical Context -E.W. Dreiss created the Choppiness Index to help traders avoid whipsaw losses by identifying market conditions unsuitable for trend-following strategies. The indicator uses a logarithmic relationship between True Range sums and price channel width to quantify market "trendiness." +Australian commodity trader E.W. Dreiss created the Choppiness Index to help traders avoid whipsaw losses by identifying market conditions unsuitable for trend-following strategies. The core insight is geometric: in a perfect trend, total bar-by-bar movement (sum of True Range) roughly equals the net distance traveled (channel width). In a choppy market, total movement greatly exceeds net progress — the market thrashes back and forth, accumulating True Range while the net channel stays narrow. The ratio between these two quantities, log-scaled to normalize across instruments and timeframes, produces a clean regime classifier. The conventional thresholds (38.2 and 61.8) are deliberately chosen as Fibonacci levels, though their efficacy is empirical rather than mathematical. ## Architecture & Physics -### The Physics of Market Trendiness +### 1. True Range Accumulation -The Choppiness Index compares the sum of True Range values (total price movement) to the overall price channel (net movement). In a perfect trend, these would be nearly equal—price moves efficiently in one direction. In a choppy market, True Range accumulates rapidly while net movement (price channel) remains small. +$$TR_t = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|)$$ -``` -Trending: Sum(TR) ≈ Price Channel → Low CHOP -Choppy: Sum(TR) >> Price Channel → High CHOP -``` +A rolling sum maintains $\sum_{i=1}^{N} TR_i$ over the lookback window. -### Logarithmic Scaling +### 2. Price Channel Width -The use of LOG10 normalizes the indicator to a 0-100 scale regardless of price level or volatility magnitude: +The net price movement over the same window: -$$\text{CHOP} = 100 \times \frac{\log_{10}\left(\frac{\sum_{i=1}^{n} TR_i}{\text{MaxHigh}_n - \text{MinLow}_n}\right)}{\log_{10}(n)}$$ +$$\text{Channel} = \max(H_{t-N+1:t}) - \min(L_{t-N+1:t})$$ + +### 3. Choppiness Index + +$$\text{CHOP} = 100 \times \frac{\log_{10}\!\left(\dfrac{\sum TR_N}{\text{Channel}}\right)}{\log_{10}(N)}$$ + +The denominator $\log_{10}(N)$ normalizes the output so that the theoretical maximum approaches 100 (when $\sum TR = N \times \text{Channel}$, which occurs when every bar traverses the full channel). + +### 4. Complexity + +- **Time:** $O(N)$ per bar for min/max scanning of high/low buffers; rolling sum is $O(1)$ +- **Space:** $O(N)$ — three ring buffers (TR, highs, lows) +- **Warmup:** $N$ bars ## Mathematical Foundation -**True Range (TR):** -$$TR = \max(H - L, |H - C_{prev}|, |L - C_{prev}|)$$ +### Parameters -**Choppiness Index:** -$$CHOP = 100 \times \frac{\log_{10}\left(\frac{\sum TR_n}{H_{\max} - L_{\min}}\right)}{\log_{10}(n)}$$ +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | -Where: -- $n$ = Lookback period -- $\sum TR_n$ = Sum of True Range over n bars -- $H_{\max}$ = Highest high over n bars -- $L_{\min}$ = Lowest low over n bars +### Pseudo-code -## Performance Profile +``` +Initialize: + trBuf = RingBuffer(period) + highBuf = RingBuffer(period) + lowBuf = RingBuffer(period) + trSum = 0 + prevClose = NaN + logPeriod = log10(period) -| Metric | Value | -|--------|-------| -| Time Complexity | O(n) per update | -| Space Complexity | O(n) ring buffers | -| Memory per Instance | ~24n bytes | -| Allocations | Zero in hot path | +On each bar (high, low, close, isNew): + if !isNew: restore previous state -### Zero-Allocation Design + // True Range + if prevClose is valid: + TR = max(high - low, |high - prevClose|, |low - prevClose|) + else: + TR = high - low -The implementation uses three ring buffers for TR values, highs, and lows. Rolling sum for TR values avoids recalculation. Min/max search is O(n) but cache-friendly due to sequential memory access. + // Rolling sum update + if trBuf is full: + trSum -= trBuf.Oldest + trBuf.Add(TR) + trSum += TR -## Interpretation + highBuf.Add(high) + lowBuf.Add(low) -| Level | Meaning | Strategy | -|-------|---------|----------| -| > 61.8 | High choppiness | Avoid trend strategies, use range trading | -| 38.2 - 61.8 | Neutral | Mixed conditions | -| < 38.2 | Low choppiness | Market trending, use trend-following | + // Channel width + maxHigh = Max(highBuf) + minLow = Min(lowBuf) + channel = maxHigh - minLow -**Key Insight:** CHOP does not indicate direction—only whether the market is trending or consolidating. + // Choppiness Index + if channel > 0 AND trSum > 0: + CHOP = 100 × log10(trSum / channel) / logPeriod + else: + CHOP = 50 // neutral fallback -## Usage - -### Streaming (Bar-by-Bar) -```csharp -var chop = new Chop(14); - -foreach (var bar in bars) -{ - TValue result = chop.Update(bar); - - if (chop.IsHot) - { - if (result.Value < 38.2) - Console.WriteLine("Trending market - look for trend entries"); - else if (result.Value > 61.8) - Console.WriteLine("Choppy market - avoid trend trades"); - } -} + prevClose = close + output = Clamp(CHOP, 0, 100) ``` -### Batch Processing -```csharp -var bars = dataSource.GetBars(100); -var chopSeries = Chop.Batch(bars, period: 14); +### Interpretation -// Access results -foreach (var value in chopSeries) -{ - Console.WriteLine($"CHOP: {value.Value:F2}"); -} -``` +| CHOP Value | Market Regime | Strategy Implication | +|------------|---------------|---------------------| +| > 61.8 | High choppiness | Avoid trend-following; favor range strategies | +| 38.2 - 61.8 | Ambiguous | Mixed conditions; reduced position sizing | +| < 38.2 | Low choppiness | Market trending; favor momentum/breakout strategies | -### Bar Correction -```csharp -var chop = new Chop(14); +### Geometric Intuition -// New bar arrives -chop.Update(bar, isNew: true); +- **Perfect trend (straight line):** $\sum TR \approx \text{Channel}$, so $\log_{10}(1) = 0$, CHOP $\to 0$ +- **Maximum chop (full traversal every bar):** $\sum TR \approx N \times \text{Channel}$, so $\log_{10}(N) / \log_{10}(N) = 1$, CHOP $\to 100$ -// Bar updates (same bar, corrected values) -chop.Update(correctedBar, isNew: false); -``` +### Non-Directional Property -## Validation +CHOP is completely direction-agnostic. A strong uptrend and a strong downtrend produce identical low CHOP readings. Direction must be determined by a separate indicator (AMAT, ADX directional components, or simple price comparison). -| Reference | Match | Notes | -|-----------|-------|-------| -| TradingView | ✓ | Standard implementation | -| PineScript | ✓ | Matches chop.pine reference | +## Resources -## Common Pitfalls - -1. **Directional Bias**: CHOP does not indicate trend direction—use with directional indicators. -2. **Lag**: Like all indicators, CHOP lags price action; trend may start before CHOP confirms. -3. **Threshold Sensitivity**: 38.2 and 61.8 are guidelines; optimal levels vary by market. - -## Related Indicators - -- **ADX**: Another trend strength indicator (directional) -- **ATR**: True Range smoothed (volatility) -- **Aroon**: Trend timing based on high/low recency - -## References - -- Dreiss, E.W. - Original Choppiness Index development -- [TradingView CHOP Documentation](https://www.tradingview.com/support/solutions/43000501980) +- Dreiss, E.W. — Choppiness Index (original development) +- PineScript reference: `chop.pine` in indicator directory diff --git a/lib/dynamics/dmx/Dmx.md b/lib/dynamics/dmx/Dmx.md index 463f6a79..1dee68f4 100644 --- a/lib/dynamics/dmx/Dmx.md +++ b/lib/dynamics/dmx/Dmx.md @@ -1,86 +1,116 @@ -# DMX: Directional Movement Index +# DMX: Directional Movement Index (Jurik) -> DMX is what happens when you take Welles Wilder's 1978 engine and swap the carburetor for fuel injection. - -The DMX is Mark Jurik's ultra-smooth, low-lag overhaul of the classic Directional Movement system. It replaces Wilder's sluggish smoothing algorithms with the Jurik Moving Average (JMA), resulting in a directional indicator that reacts faster to trend changes while filtering out more noise. +The DMX is Mark Jurik's modernized overhaul of Wilder's Directional Movement system, replacing the sluggish RMA smoothing with the Jurik Moving Average (JMA) to achieve faster trend detection with superior noise rejection. The core directional movement logic (+DM, -DM, True Range) is preserved faithfully from Wilder, but the three parallel smoothing passes use JMA's adaptive bandwidth instead of RMA's fixed $\alpha = 1/N$. The result is a directional indicator that reacts 3-5 bars earlier to trend changes than standard DMI while filtering out more noise during consolidation. Output is the difference between smoothed directional indicators: $DMX = DI^+ - DI^-$, positive for uptrends and negative for downtrends. ## Historical Context -Wilder's original ADX/DMI system is legendary but mathematically primitive; it relies on simple recursive smoothing (RMA) that introduces significant lag. DMX retains the core logic of directional movement ($DM+$ and $DM-$) but upgrades the engine that processes them. By using JMA, DMX achieves the "holy grail" of signal processing: smoothness without lag. +Wilder's original ADX/DMI system (1978) is foundational but mathematically primitive — its RMA smoothing introduces substantial lag that delays trend detection. Jurik's contribution was recognizing that the directional movement decomposition itself is sound; only the smoothing pipeline needed upgrading. JMA is an adaptive filter that tracks signal closely during transitions (low lag) and smooths aggressively during stable periods (high noise reduction). This dynamic behavior means DMX signals trend changes significantly earlier than DMI without the whipsaw penalty typically associated with faster indicators. DMX is not available in standard TA libraries (TA-Lib, Skender, Tulip) since JMA is a proprietary algorithm. The QuanTAlib implementation uses its own JMA recreation. ## Architecture & Physics -The physics of DMX are identical to DMI, but the friction is removed. +### 1. Directional Movement (Wilder's Original) -1. **Decomposition**: Raw Directional Movement ($DM$) and True Range ($TR$) are calculated exactly as Wilder did. -2. **Smoothing**: Instead of the laggy RMA, these raw signals are fed into three parallel JMA filters. -3. **Normalization**: The smoothed DM is normalized by the smoothed TR to get Directional Indicators ($DI$). -4. **Differential**: The DMX is simply $DI^+ - DI^-$. +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ -### The Lag Reduction +$$+DM = \begin{cases} \text{UpMove} & \text{if UpMove} > \text{DownMove and UpMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ -JMA is an adaptive filter. It tracks the signal closely when it moves (low lag) and smooths it aggressively when it stalls (high noise reduction). This dynamic behavior means DMX signals trend changes significantly earlier than standard DMI—often by 3-5 bars—without the "whipsaw" penalty usually associated with faster indicators. +$$-DM = \begin{cases} \text{DownMove} & \text{if DownMove} > \text{UpMove and DownMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ + +### 2. True Range + +$$TR = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|)$$ + +### 3. JMA Smoothing (Replaces RMA) + +Three parallel JMA filters replace Wilder's three RMA passes: + +$$+DM_{\text{smooth}} = \text{JMA}(+DM, N)$$ + +$$-DM_{\text{smooth}} = \text{JMA}(-DM, N)$$ + +$$TR_{\text{smooth}} = \text{JMA}(TR, N)$$ + +### 4. Directional Indicators + +$$DI^+ = 100 \times \frac{+DM_{\text{smooth}}}{TR_{\text{smooth}}}, \quad DI^- = 100 \times \frac{-DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +### 5. DMX Output + +$$DMX = DI^+ - DI^-$$ + +Positive values indicate bullish directional dominance; negative values indicate bearish. + +### 6. Complexity + +- **Time:** $O(1)$ per bar — three JMA updates (each $O(1)$) +- **Space:** $O(1)$ — JMA maintains fixed-size internal state +- **Warmup:** $\approx N$ bars (JMA converges faster than RMA) ## Mathematical Foundation -The core directional logic remains faithful to Wilder. +### Parameters -### 1. Raw Directional Movement +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | -$$ \text{UpMove} = H_t - H_{t-1} $$ -$$ \text{DownMove} = L_{t-1} - L_t $$ +### Pseudo-code -$$ DM^+ = \begin{cases} \text{UpMove} & \text{if } \text{UpMove} > \text{DownMove} \text{ and } \text{UpMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +``` +Initialize: + jmaPlusDM = new JMA(period) + jmaMinusDM = new JMA(period) + jmaTR = new JMA(period) + prevHigh = prevLow = prevClose = NaN -$$ DM^- = \begin{cases} \text{DownMove} & \text{if } \text{DownMove} > \text{UpMove} \text{ and } \text{DownMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +On each bar (high, low, close, isNew): + if !isNew: restore previous state -### 2. Jurik Smoothing + // Wilder's directional movement decomposition + TR = max(high - low, |high - prevClose|, |low - prevClose|) -$$ SmoothDM^+ = JMA(DM^+, \text{Period}) $$ -$$ SmoothDM^- = JMA(DM^-, \text{Period}) $$ -$$ SmoothTR = JMA(TR, \text{Period}) $$ + upMove = high - prevHigh + downMove = prevLow - low -### 3. Directional Indicators + +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 + -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 -$$ DI^+ = \frac{SmoothDM^+}{SmoothTR} \times 100 $$ -$$ DI^- = \frac{SmoothDM^-}{SmoothTR} \times 100 $$ + // Jurik smoothing (replaces Wilder's RMA) + smoothPlusDM = jmaPlusDM.Update(+DM) + smoothMinusDM = jmaMinusDM.Update(-DM) + smoothTR = jmaTR.Update(TR) -### 4. DMX + // Directional indicators + if smoothTR > 0: + DI_plus = 100 × smoothPlusDM / smoothTR + DI_minus = 100 × smoothMinusDM / smoothTR + else: + DI_plus = DI_minus = 0 -$$ DMX = DI^+ - DI^- $$ + DMX = DI_plus - DI_minus -## Performance Profile + prevHigh = high + prevLow = low + prevClose = close + output = DMX +``` -The complexity is dominated by the three JMA calculations. +### DMX vs DMI Comparison -### Zero-Allocation Design +| Property | DMI (Wilder) | DMX (Jurik) | +|----------|-------------|-------------| +| Smoothing | RMA ($\alpha = 1/N$) | JMA (adaptive) | +| Lag | $\approx N$ bars | $\approx N/2$ bars | +| Whipsaw rejection | Moderate | High | +| Available in TA-Lib | Yes | No | +| Overshoot | Low | Can overshoot in extreme volatility | -The implementation relies on the zero-allocation design of the underlying `Jma` indicators. All internal state is pre-allocated. +### Period Selection -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 15ns | 3x JMA updates. | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(1) | Constant time per update. | -| **Accuracy** | 10/10 | Matches Jurik's methodology. | -| **Timeliness** | 9/10 | Significantly faster than ADX. | -| **Overshoot** | 2/10 | Can overshoot in extreme volatility. | -| **Smoothness** | 9/10 | JMA filtering removes noise. | +Because JMA is more efficient than RMA, slightly longer periods (e.g., 20 instead of 14) can be used without incurring a lag penalty, producing smoother results while maintaining responsiveness. -## Validation +## Resources -Validation is performed against internal consistency checks and Jurik's published methodology. - -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Internal consistency (Batch vs Streaming). | -| **TA-Lib** | N/A | Not implemented in TA-Lib. | -| **Skender** | N/A | Not implemented in Skender. | -| **Tulip** | N/A | Not implemented in Tulip. | - -| **Ooples** | N/A | Not implemented. | - -### Common Pitfalls - -* **Period Selection**: Because JMA is so efficient, you can often use slightly longer periods than you would with DMI (e.g., 20 instead of 14) to get even smoother results without incurring a lag penalty. -* **Dependency**: This indicator depends on the `Jma` class. Ensure `Jma` is validated and performant. +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- Jurik, M. — JMA adaptive smoothing methodology +- PineScript reference: `dmx.pine` in indicator directory diff --git a/lib/dynamics/dx/Dx.md b/lib/dynamics/dx/Dx.md index 3e0fd2fb..abcad58f 100644 --- a/lib/dynamics/dx/Dx.md +++ b/lib/dynamics/dx/Dx.md @@ -1,169 +1,126 @@ # DX: Directional Movement Index -> "ADX tells you how strong the trend is; DX tells you how strong it is *right now*, without the smoothing delay." - -The Directional Movement Index (DX) measures the strength of directional movement in a market, regardless of whether that movement is up or down. Unlike its more famous cousin ADX (Average Directional Index), DX is the raw, unsmoothed version—more responsive but also more noisy. +The Directional Movement Index is the raw, unsmoothed measure of trend strength from Wilder's directional movement system. It decomposes price expansion into +DM and -DM, normalizes against True Range using RMA smoothing to produce +DI and -DI, then computes the ratio $DX = 100 \times |{+DI - {-DI}}| / ({+DI + {-DI}})$. Unlike ADX, which applies a final RMA pass to DX, the raw DX responds immediately to changes in directional dominance — making it noisier but approximately one full period faster. Output ranges from 0 to 100, where high values indicate strong directional movement regardless of up/down direction. DX is the building block from which ADX is derived. ## Historical Context -J. Welles Wilder Jr. introduced the Directional Movement System in his 1978 book *New Concepts in Technical Trading Systems*. The system decomposes price action into three components: upward movement (+DM), downward movement (-DM), and volatility (True Range). These components are then normalized and combined to create directional indicators (+DI, -DI) and the index itself (DX). - -DX is often overlooked in favor of ADX, which applies an additional smoothing layer. However, DX provides faster signals for traders who can tolerate more noise in exchange for reduced lag. +J. Welles Wilder Jr. introduced the complete Directional Movement System in *New Concepts in Technical Trading Systems* (1978). The system's pipeline produces several intermediate values — +DM, -DM, TR, +DI, -DI, DX — before reaching the final ADX. Most traders skip directly to ADX, but DX occupies a useful middle ground: it contains all the directional normalization logic (the hard part) without the final smoothing layer (which adds lag). For traders who can tolerate more noise in exchange for faster response, DX provides trend strength signals roughly $N$ bars ahead of ADX. The tradeoff is straightforward: DX spikes on volatile bars and can produce false readings during whipsaw, while ADX absorbs these transients through its additional RMA pass. ## Architecture & Physics -The DX calculation is a multi-stage pipeline: +### 1. Directional Movement -1. **Directional Movement Decomposition**: Price expansion is broken into +DM (upward) and -DM (downward) components -2. **Volatility Normalization**: Raw movements are normalized by True Range to create +DI and -DI -3. **Index Calculation**: The absolute difference of the DIs is divided by their sum, scaled to 0-100 +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ -### Key Difference from ADX +$$+DM = \begin{cases} \text{UpMove} & \text{if UpMove} > \text{DownMove and UpMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ -- **DX**: Raw directional strength, updated every bar -- **ADX**: DX smoothed with RMA (Wilder's Moving Average) +$$-DM = \begin{cases} \text{DownMove} & \text{if DownMove} > \text{UpMove and DownMove} > 0 \\ 0 & \text{otherwise} \end{cases}$$ -DX responds immediately to changes in trend strength; ADX lags by approximately one period. +### 2. True Range + +$$TR = \max(H_t - L_t,\; |H_t - C_{t-1}|,\; |L_t - C_{t-1}|)$$ + +### 3. Wilder Smoothing (RMA) + +All three series use Wilder's smoothing with $\alpha = 1/N$: + +$$+DM_{\text{smooth}} = \text{RMA}(+DM, N)$$ + +$$-DM_{\text{smooth}} = \text{RMA}(-DM, N)$$ + +$$TR_{\text{smooth}} = \text{RMA}(TR, N)$$ + +### 4. Directional Indicators + +$$+DI = 100 \times \frac{+DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +$$-DI = 100 \times \frac{-DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +### 5. DX (No Final Smoothing) + +$$DX = 100 \times \frac{|+DI - (-DI)|}{+DI + (-DI)}$$ + +When $+DI + (-DI) = 0$ (no directional movement), DX = 0. + +### 6. Complexity + +- **Time:** $O(1)$ per bar — all RMA updates are recursive +- **Space:** $O(1)$ — scalar state only +- **Warmup:** $N$ bars ## Mathematical Foundation -### 1. Directional Movement (DM) +### Parameters -Today's high/low expansion is compared to yesterday's: +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | -$$ \text{UpMove} = H_t - H_{t-1} $$ -$$ \text{DownMove} = L_{t-1} - L_t $$ +### Pseudo-code -$$ +DM = \begin{cases} \text{UpMove} & \text{if } \text{UpMove} > \text{DownMove} \text{ and } \text{UpMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +``` +Initialize: + α = 1 / period + smoothPlusDM = smoothMinusDM = smoothTR = 0 + prevHigh = prevLow = prevClose = NaN -$$ -DM = \begin{cases} \text{DownMove} & \text{if } \text{DownMove} > \text{UpMove} \text{ and } \text{DownMove} > 0 \\ 0 & \text{otherwise} \end{cases} $$ +On each bar (high, low, close, isNew): + if !isNew: restore previous state -### 2. True Range (TR) + // True Range + TR = max(high - low, |high - prevClose|, |low - prevClose|) -$$ TR = \max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|) $$ + upMove = high - prevHigh + downMove = prevLow - low -### 3. Smoothing (RMA) + +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 + -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 -Wilder's Moving Average is applied to +DM, -DM, and TR: + // Wilder smoothing + smoothPlusDM = FMA(smoothPlusDM, 1 - α, α × +DM) + smoothMinusDM = FMA(smoothMinusDM, 1 - α, α × -DM) + smoothTR = FMA(smoothTR, 1 - α, α × TR) -$$ +DM_{smoothed} = RMA(+DM, N) $$ -$$ -DM_{smoothed} = RMA(-DM, N) $$ -$$ TR_{smoothed} = RMA(TR, N) $$ + // Directional Indicators + +DI = smoothTR > 0 ? 100 × smoothPlusDM / smoothTR : 0 + -DI = smoothTR > 0 ? 100 × smoothMinusDM / smoothTR : 0 -Where RMA uses $\alpha = 1/N$ (equivalent to EMA with period $2N-1$). + // DX (raw, no final RMA) + diSum = +DI + -DI + DX = diSum > 0 ? 100 × |+DI - -DI| / diSum : 0 -### 4. Directional Indicators (DI) + prevHigh = high + prevLow = low + prevClose = close -$$ +DI = 100 \times \frac{+DM_{smoothed}}{TR_{smoothed}} $$ -$$ -DI = 100 \times \frac{-DM_{smoothed}}{TR_{smoothed}} $$ - -### 5. Directional Index (DX) - -$$ DX = 100 \times \frac{|+DI - -DI|}{+DI + -DI} $$ - -### 6. Wilder's Smoothing - -The smoothing uses Wilder's original method (not standard RMA/EMA): - -$$ Smooth_{t} = Smooth_{t-1} - \frac{Smooth_{t-1}}{N} + Input_{t} $$ - -This differs from standard RMA which divides the input by N. - -## Performance Profile - -The implementation uses O(1) updates with aggressive inlining and FMA operations. - -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 3ns | Per-bar update (Apple M1 Max) | -| **Allocations** | 0 | Hot path is allocation-free | -| **Complexity** | O(1) | Constant time for streaming updates | -| **Accuracy** | 10/10 | Matches TA-Lib to 1e-9 | -| **Timeliness** | 6/10 | Less lag than ADX due to no final smoothing | -| **Overshoot** | 5/10 | More volatile than ADX | -| **Smoothness** | 4/10 | Raw signal, noisy | - -### Quality Metrics - -| Quality | Score | Justification | -| :--- | :---: | :--- | -| Accuracy | 9 | Preserves trend structure | -| Timeliness | 6 | One period faster than ADX | -| Overshoot | 5 | Can spike on volatile bars | -| Smoothness | 4 | Unsmoothed, reflects bar-to-bar changes | - -## Usage - -### Scalar (Streaming) - -```csharp -var dx = new Dx(14); - -foreach (var bar in bars) -{ - dx.Update(bar); - Console.WriteLine($"DX: {dx.Last.Value:F2}, +DI: {dx.DiPlus.Value:F2}, -DI: {dx.DiMinus.Value:F2}"); -} + output: + DX = DX // trend strength (0-100) + DiPlus = +DI // bullish directional indicator + DiMinus = -DI // bearish directional indicator ``` -### Batch (Span-based) +### DX vs ADX -```csharp -Span output = stackalloc double[close.Length]; -Dx.Calculate(high, low, close, 14, output); -``` +| Property | DX | ADX | +|----------|-----|------| +| Smoothing | RMA on components only | RMA on components + RMA on DX | +| Response | Immediate to bar-level changes | Lagged by $\approx N$ bars | +| Noise | High; can spike on volatile bars | Low; smooth, stable signal | +| Use case | Fast trend detection, signal generation | Regime classification, filter | -### With Bar Correction - -```csharp -// New bar arrives -dx.Update(bar, isNew: true); - -// Same bar updates (intra-bar corrections) -dx.Update(modifiedBar, isNew: false); -``` - -## Interpretation +### Interpretation | DX Value | Trend Strength | -| :---: | :--- | -| 0-15 | Weak or no trend | +|----------|----------------| +| 0-15 | No meaningful trend | | 15-25 | Developing trend | | 25-50 | Strong trend | | 50-75 | Very strong trend | -| 75-100 | Extreme trend (rare) | +| 75-100 | Extreme (rare, usually transient) | -### Trading Signals +DX measures trend *strength*, not direction. Direction is determined by comparing +DI vs -DI: if $+DI > -DI$, the trend is up; if $-DI > +DI$, the trend is down. DI crossovers signal potential trend reversals. -- **DX Rising**: Trend is strengthening -- **DX Falling**: Trend is weakening -- **+DI > -DI**: Uptrend dominates -- **-DI > +DI**: Downtrend dominates -- **DI Crossover**: Potential trend reversal +## Resources -## Validation - -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | ✅ | Matches `TA_DX` | -| **Skender** | ✅ | Matches `GetDx` | -| **Tulip** | ✅ | Matches `ti.dx` | -| **TradingView** | ✅ | Matches Pine Script `ta.dm` components | - -## Common Pitfalls - -1. **Confusing DX with ADX**: DX is unsmoothed; ADX is RMA(DX). If you want the classic ADX behavior, use the ADX indicator. - -2. **Period Too Short**: Periods below 7 make DX extremely noisy. The standard is 14. - -3. **First N Bars**: The first `period` bars output 0 as they're needed for warmup. Don't trade on these values. - -4. **DI Sum Near Zero**: When both +DI and -DI approach zero (no directional movement), DX becomes unstable. The implementation guards against division by zero. - -5. **Not a Direction Indicator**: DX measures trend *strength*, not direction. Use +DI vs -DI for direction. - -## References - -- Wilder, J. W. (1978). *New Concepts in Technical Trading Systems* -- [TradingView DX Documentation](https://www.tradingview.com/support/solutions/43000502250-directional-movement-dm/) -- [StockCharts ADX/DX](https://school.stockcharts.com/doku.php?id=technical_indicators:average_directional_index_adx) +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `dx.pine` in indicator directory diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.md b/lib/dynamics/ht_trendmode/HtTrendmode.md index 1f6fae2e..d600b8e3 100644 --- a/lib/dynamics/ht_trendmode/HtTrendmode.md +++ b/lib/dynamics/ht_trendmode/HtTrendmode.md @@ -1,230 +1,159 @@ -# HT_TRENDMODE: Ehlers Hilbert Transform Trend vs Cycle Mode +# HT_TRENDMODE: Hilbert Transform Trend vs Cycle Mode + +The Hilbert Transform Trend Mode indicator is a binary regime classifier that determines whether price action is dominated by trending behavior (output = 1) or cyclical/mean-reverting behavior (output = 0). It uses the full Ehlers Hilbert Transform pipeline — 4-bar WMA smoothing, Hilbert FIR filters, homodyne discriminator for period estimation, DC phase extraction, and SineWave indicators — then applies four decision criteria to classify the current regime. The implementation follows TA-Lib's Ehlers-faithful algorithm from the February 2002 publication. Output is discrete {0, 1}, making it a direct strategy selector: deploy trend-following logic when mode = 1, and mean-reversion logic when mode = 0. ## Historical Context -The Hilbert Transform Trend Mode indicator was developed by **John Ehlers** as part of his cycle analysis toolkit. It uses the Hilbert Transform—a signal processing technique—to determine whether price action is dominated by **trending behavior** or **cyclical/mean-reverting behavior**. - -This implementation follows **TA-Lib's Ehlers-faithful algorithm** from his February 2002 publication "The Instantaneous Trendline." The key insight: trend mode is detected via multiple criteria including SineWave crossings, phase rate analysis, and price-trendline deviation. +John Ehlers developed the Trend Mode indicator as part of his cycle analysis toolkit, published in "The Instantaneous Trendline" (February 2002) and expanded in *MESA and Trading Market Cycles* (2002). Ehlers recognized that traders face two fundamentally different market regimes requiring opposite strategies. Applying a trend-following system to a cycling market produces losses, and applying a mean-reversion system to a trending market produces losses. The Hilbert Transform provides the mathematical machinery to distinguish these states by analyzing the phase behavior of the dominant cycle. When phase advances at a regular rate (consistent with a sinusoidal cycle), the market is in cycle mode. When phase rate becomes irregular or price deviates significantly from its trendline, the market is trending. The four-criteria decision logic prevents rapid mode flipping during transitional periods by requiring sustained evidence before declaring a regime change. ## Architecture & Physics -### The Trend/Cycle Duality +### 1. Hilbert Transform Core -Markets alternate between two fundamental states: +The same pipeline as HT_DCPERIOD and HT_SINE: -| State | Characteristic | Strategy | -|-------|---------------|----------| -| **Trend Mode (1)** | Directional momentum | Trend-following | -| **Cycle Mode (0)** | Mean-reverting oscillation | Range-trading | +$$\text{smooth} = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}$$ -The TA-Lib algorithm uses **four criteria** to determine trend mode: +Hilbert FIR filters extract InPhase and Quadrature components, which feed the homodyne discriminator for period estimation: -1. **SineWave Crossings**: Reset trend counter when Sine crosses LeadSine -2. **Days in Trend**: Must exceed half the smooth period -3. **Phase Rate Check**: Normal phase change rate indicates cycle mode -4. **Price-Trendline Deviation**: ≥1.5% deviation forces trend mode +$$Re = 0.2(I_2 \cdot I_{2,t-1} + Q_2 \cdot Q_{2,t-1}) + 0.8 \cdot Re_{t-1}$$ + +$$Im = 0.2(I_2 \cdot Q_{2,t-1} - Q_2 \cdot I_{2,t-1}) + 0.8 \cdot Im_{t-1}$$ + +$$\text{period} = \frac{360}{\arctan(Im/Re) \times \frac{180}{\pi}}$$ + +$$\text{smoothPeriod} = 0.33 \times \text{period} + 0.67 \times \text{smoothPeriod}_{t-1}$$ + +### 2. DC Phase and SineWave + +DFT accumulation over the dominant cycle period extracts the DC phase: + +$$\text{dcPhase} = \arctan\!\left(\frac{\sum \sin(\omega i) \cdot \text{smooth}_i}{\sum \cos(\omega i) \cdot \text{smooth}_i}\right) + 90° + \text{lagComp}$$ + +$$\text{sine} = \sin(\text{dcPhase}), \quad \text{leadSine} = \sin(\text{dcPhase} + 45°)$$ + +### 3. Trendline + +An SMA over the dominant cycle period, further smoothed with a 4-bar WMA: + +$$\text{sma} = \text{Average}(\text{price}, \lfloor\text{dcPeriod}\rfloor)$$ + +$$\text{trendline} = \frac{4 \cdot \text{sma}_0 + 3 \cdot \text{sma}_1 + 2 \cdot \text{sma}_2 + \text{sma}_3}{10}$$ + +### 4. Four-Criteria Decision Logic + +``` +trend = 1 (assume trend by default) + +Criterion 1: SineWave crossing resets counter + if sine crosses leadSine → daysInTrend = 0, trend = 0 + +Criterion 2: Duration threshold + daysInTrend++ + if daysInTrend < 0.5 × smoothPeriod → trend = 0 + +Criterion 3: Phase rate check + phaseChange = dcPhase - prevDcPhase + expected = 360 / smoothPeriod + if 0.67 × expected < phaseChange < 1.5 × expected → trend = 0 + +Criterion 4: Price deviation override + if |smoothPrice - trendline| / trendline ≥ 0.015 → trend = 1 +``` + +### 5. Complexity + +- **Time:** $O(P)$ per bar for the SMA over dominant cycle period; Hilbert pipeline is $O(1)$ +- **Space:** $O(P_{\max})$ — circular buffers for price history and Hilbert state ($P_{\max} = 50$) +- **Warmup:** 63 bars (TA-Lib compatible) ## Mathematical Foundation -### 1. Hilbert Transform Components +### Parameters -The indicator uses the same Hilbert Transform core as HT_DCPERIOD: +No user-configurable parameters. The algorithm self-tunes based on the detected dominant cycle period (clamped to 6-50 bars). + +### Pseudo-code ``` -smooth_price = (4×P₀ + 3×P₁ + 2×P₂ + P₃) / 10 +Initialize: + circBuffer = array for Hilbert state + smoothPrice = priceHistory = arrays + daysInTrend = 0 + smoothPeriod = 0 + prevDcPhase = 0 + bar_count = 0 -detrender = FIR(smooth_price) × bandwidth -Q1 = FIR(detrender) × bandwidth -I1 = detrender[3] +On each bar (price, isNew): + if !isNew: restore previous state -// Phasor rotation -I2 = I1 - jQ -Q2 = Q1 + jI + // Step 1: 4-bar WMA smooth + smooth = (4×price[0] + 3×price[1] + 2×price[2] + price[3]) / 10 + + // Step 2: Hilbert Transform (FIR filters) + detrender = HilbertFIR(smooth) × adjustedBandwidth + Q1 = HilbertFIR(detrender) × adjustedBandwidth + I1 = detrender[3] + + // Step 3: Phasor rotation + I2 = I1 - jQ_prev; Q2 = Q1 + jI_prev + I2 = 0.2×I2 + 0.8×I2_prev; Q2 = 0.2×Q2 + 0.8×Q2_prev + + // Step 4: Homodyne discriminator → period + Re = 0.2×(I2×I2_prev + Q2×Q2_prev) + 0.8×Re_prev + Im = 0.2×(I2×Q2_prev - Q2×I2_prev) + 0.8×Im_prev + period = clamp(360 / (atan(Im/Re) × RAD2DEG), 6, 50) + smoothPeriod = 0.33×period + 0.67×smoothPeriod_prev + + // Step 5: DC Phase via DFT + dcPeriodInt = floor(smoothPeriod + 0.5) + realPart = Σ sin(i × 360/dcPeriodInt) × smooth[i] for i=0..dcPeriodInt-1 + imagPart = Σ cos(i × 360/dcPeriodInt) × smooth[i] + dcPhase = atan(realPart/imagPart)×RAD2DEG + 90 + lagCompensation + + // Step 6: SineWave indicators + sine = sin(dcPhase × DEG2RAD) + leadSine = sin((dcPhase + 45) × DEG2RAD) + + // Step 7: Trendline (SMA smoothed with WMA) + sma = average(price, dcPeriodInt) + trendline = (4×sma[0] + 3×sma[1] + 2×sma[2] + sma[3]) / 10 + + // Step 8: Four-criteria trend decision + trend = 1 + if sine crosses leadSine: daysInTrend = 0; trend = 0 + daysInTrend++ + if daysInTrend < 0.5 × smoothPeriod: trend = 0 + phaseChange = dcPhase - prevDcPhase + expected = 360 / smoothPeriod + if phaseChange > 0.67×expected AND phaseChange < 1.5×expected: trend = 0 + if |smooth - trendline| / trendline >= 0.015: trend = 1 + + prevDcPhase = dcPhase + output = trend // 1 = trending, 0 = cycling ``` -### 2. Period and DC Phase +### Decision Criteria Summary -``` -Re = 0.2×(I2×I2[1] + Q2×Q2[1]) + 0.8×Re[1] -Im = 0.2×(I2×Q2[1] - Q2×I2[1]) + 0.8×Im[1] +| Criterion | Purpose | +|-----------|---------| +| SineWave crossing | Resets trend counter — new cycle detected | +| Duration threshold | Requires sustained trending before declaration | +| Phase rate check | Normal phase advance indicates cycle mode | +| Price deviation | Large deviation from trendline forces trend mode | -period = 360 / (atan(Im/Re) × RAD2DEG) -smooth_period = 0.33×period + 0.67×smooth_period[1] +### Mode Transition Patterns -// DC Phase calculation -realPart = Σ sin(i × 360/dcPeriod) × smoothPrice[i] -imagPart = Σ cos(i × 360/dcPeriod) × smoothPrice[i] -dcPhase = atan(realPart/imagPart) × RAD2DEG + 90 + lag_compensation -``` +| Pattern | Interpretation | +|---------|---------------| +| 0→1 after breakout | Trend confirmed; deploy momentum strategy | +| 1→0 at extremes | Cycle started; switch to mean-reversion | +| Long run of 1s | Strong, sustained trend | +| Rapid 0/1 flipping | Transitional/choppy — reduce exposure | -### 3. SineWave Indicators +## Resources -``` -sine = sin(dcPhase × DEG2RAD) -leadSine = sin((dcPhase + 45) × DEG2RAD) -``` - -### 4. Trendline Calculation - -``` -// SMA over dominant cycle period -sma = average(price, dcPeriodInt) - -// WMA smoothing -trendline = (4×sma₀ + 3×sma₁ + 2×sma₂ + sma₃) / 10 -``` - -### 5. Trend Mode Decision (TA-Lib Algorithm) - -``` -trend = 1 // Assume trend by default - -// Criterion 1: SineWave crossing resets counter -if (sine crosses leadSine): - daysInTrend = 0 - trend = 0 - -daysInTrend++ - -// Criterion 2: Must be trending for half a cycle -if (daysInTrend < 0.5 × smoothPeriod): - trend = 0 - -// Criterion 3: Normal phase rate → cycle mode -phaseChange = dcPhase - prevDcPhase -expectedChange = 360 / smoothPeriod -if (phaseChange > 0.67×expectedChange AND phaseChange < 1.5×expectedChange): - trend = 0 - -// Criterion 4: Price deviation override -if (abs((smoothPrice - trendline) / trendline) >= 0.015): - trend = 1 -``` - -## Performance Profile - -- **Complexity**: O(1) per update -- **Memory**: ~450 bytes state + circular buffers -- **Lookback**: 63 bars (TA-Lib compatible) - -### Zero-Allocation Design - -```csharp -[SkipLocalsInit] -public sealed class HtTrendmode : AbstractBase -{ - // All state in value types - private State _state; - private State _p_state; - - // Pre-allocated buffers for Hilbert Transform - private readonly double[] _circBuffer; - private readonly double[] _smoothPrice; - private readonly double[] _priceHistory; -} -``` - -### Bar Correction Pattern - -Supports streaming updates with correction: - -```csharp -// New bar -var result = indicator.Update(price, isNew: true); - -// Same bar, corrected price -var corrected = indicator.Update(newPrice, isNew: false); -``` - -## Usage - -### Streaming - -```csharp -var indicator = new HtTrendmode(); - -foreach (var bar in bars) -{ - var result = indicator.Update(bar.Close, isNew: true); - - if (indicator.TrendMode == 1) - { - // Use trend-following strategy - ApplyMomentumStrategy(); - } - else - { - // Use mean-reversion strategy - ApplyRangeStrategy(); - } -} -``` - -### Batch - -```csharp -var result = HtTrendmode.Calculate(closePrices); -``` - -### Properties - -| Property | Type | Description | -|----------|------|-------------| -| `TrendMode` | int | Current mode: 1=trend, 0=cycle | -| `SmoothPeriod` | double | Smoothed dominant cycle period [6-50] | -| `InstPeriod` | double | Instantaneous (unsmoothed) period | -| `DCPhase` | double | Dominant cycle phase in degrees | -| `Trendline` | double | WMA-smoothed SMA over cycle period | -| `DaysInTrend` | int | Days since last SineWave crossing | - -## Interpretation - -### Signal Interpretation - -| Value | Mode | Interpretation | -|-------|------|----------------| -| **1** | Trend | Price is trending; momentum strategies preferred | -| **0** | Cycle | Price is oscillating; mean-reversion preferred | - -### Common Patterns - -1. **Trend Confirmation**: When TrendMode flips from 0→1 after a breakout -2. **Cycle Entry**: When TrendMode flips from 1→0 at potential reversal zones -3. **Mode Persistence**: Long runs of 1s indicate strong trends -4. **Mode Oscillation**: Rapid flipping indicates choppy markets - -### Using Auxiliary Properties - -```csharp -// Access the trendline for support/resistance -double trend = indicator.Trendline; - -// Check how long in current trend -int duration = indicator.DaysInTrend; - -// Use phase for timing entries -double phase = indicator.DCPhase; -``` - -## Validation - -### Cross-Library Comparison - -| Library | Function | Notes | -|---------|----------|-------| -| TA-Lib | `HT_TRENDMODE` | Reference implementation (matched) | -| TradingView | Built-in | PineScript version (differs) | - -### Common Pitfalls - -1. **Lag**: Hilbert Transform has inherent lag (~32-63 bars for reliable signal) -2. **Whipsaws**: Mode can flip rapidly in transitional markets -3. **Warmup**: Requires 63+ bars before valid output -4. **Division Safety**: Use epsilon checks to avoid division by zero - -## References - -- Ehlers, J.F. "The Instantaneous Trendline" (February 2002) -- Ehlers, J.F. "MESA and Trading Market Cycles" (2002) -- Ehlers, J.F. "Rocket Science for Traders" (2001) -- [TA-Lib HT_TRENDMODE Source](https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_HT_TRENDMODE.c) +- Ehlers, J.F. — "The Instantaneous Trendline" (February 2002) +- Ehlers, J.F. — *MESA and Trading Market Cycles* (John Wiley & Sons, 2002) +- Ehlers, J.F. — *Rocket Science for Traders* (John Wiley & Sons, 2001) +- PineScript reference: `ht_trendmode.pine` in indicator directory diff --git a/lib/dynamics/ichimoku/Ichimoku.md b/lib/dynamics/ichimoku/Ichimoku.md index 7b989f47..a2b95f43 100644 --- a/lib/dynamics/ichimoku/Ichimoku.md +++ b/lib/dynamics/ichimoku/Ichimoku.md @@ -1,168 +1,132 @@ -# ICHIMOKU: Ichimoku Kinko Hyo (One Glance Equilibrium Chart) +# ICHIMOKU: Ichimoku Kinko Hyo -> "Five lines, two clouds, one chart. Goichi Hosoda spent thirty years developing a system that tells you trend, momentum, support, and resistance simultaneously. Most traders use it for about thirty seconds before getting confused." - -Ichimoku Kinko Hyo is a comprehensive trend-following system developed by Japanese journalist Goichi Hosoda (pen name Ichimoku Sanjin), published in 1969 after three decades of research. It provides five distinct components that together reveal trend direction, momentum, support/resistance levels, and potential future price zones. The "cloud" (Kumo) formed between Senkou Span A and B is particularly valued for identifying trend strength and equilibrium zones. +Ichimoku Kinko Hyo ("One Glance Equilibrium Chart") is a comprehensive trend-following system that provides five distinct components revealing trend direction, momentum, support/resistance levels, and potential future price zones simultaneously. The Tenkan-sen and Kijun-sen are midpoints of high-low ranges at different timescales (not moving averages of closes). Senkou Span A and B form the "cloud" (Kumo) — a projected equilibrium zone displaced forward in time. Chikou Span is simply the current close displaced backward. All components use sliding window min/max arithmetic, producing step-function behavior on breakouts rather than the smooth curves of EMA-based systems. The system requires OHLC bar input. ## Historical Context -Hosoda began developing Ichimoku in 1935, enlisting university students to hand-calculate the indicator across historical data long before computers made such work trivial. He published the complete system in a seven-volume series in 1969. The indicator remained largely unknown outside Japan until the 1990s, when it gained international recognition through the work of traders and analysts who translated and popularized the system. - -The original parameters (9, 26, 52) were calibrated to the Japanese trading week: 9 days (1.5 weeks), 26 days (one month of 6-day trading weeks), and 52 days (two months). While modern 5-day trading weeks would suggest different values, the original parameters remain standard due to their widespread adoption and self-fulfilling nature in liquid markets. - -The system is unique among technical indicators in projecting values into the future (Senkou spans displaced 26 periods forward) and into the past (Chikou span displaced 26 periods back), providing a temporal dimension that most indicators lack. +Japanese journalist Goichi Hosoda (pen name "Ichimoku Sanjin") began developing the system in 1935, enlisting university students to hand-calculate the indicator across historical data decades before computers. He published the complete system in a seven-volume series in 1969. Ichimoku remained largely unknown outside Japan until the 1990s when international traders translated and popularized it. The original parameters (9, 26, 52) were calibrated to the Japanese six-day trading week: 9 days (1.5 weeks), 26 days (one month), and 52 days (two months). While modern five-day weeks would suggest different values, the original parameters remain standard due to widespread adoption and their self-fulfilling nature in liquid markets. Some practitioners use (7, 22, 44) for cryptocurrency markets with seven-day trading weeks. The system is unique among technical indicators in projecting values into the future (Senkou spans displaced 26 periods forward) and into the past (Chikou span displaced 26 periods back), providing a temporal dimension most indicators lack. ## Architecture & Physics ### 1. Tenkan-sen (Conversion Line) -The short-term equilibrium, calculated as the midpoint of the highest high and lowest low over the Tenkan period: +Short-term equilibrium — midpoint of the highest high and lowest low over the Tenkan period: -$$ -\text{Tenkan}_t = \frac{\max(H, \text{tenkanPeriod}) + \min(L, \text{tenkanPeriod})}{2} -$$ +$$\text{Tenkan}_t = \frac{\max(H_{t-8:t}) + \min(L_{t-8:t})}{2}$$ -This is not a moving average but a midpoint of the price range, making it responsive to breakouts rather than smooth trends. +This is a range midpoint, not a moving average. It responds to breakouts (new highs or lows entering the window) rather than gradual price changes. ### 2. Kijun-sen (Base Line) -The medium-term equilibrium, calculated identically but over a longer period: +Medium-term equilibrium — same formula over a longer period: -$$ -\text{Kijun}_t = \frac{\max(H, \text{kijunPeriod}) + \min(L, \text{kijunPeriod})}{2} -$$ +$$\text{Kijun}_t = \frac{\max(H_{t-25:t}) + \min(L_{t-25:t})}{2}$$ -Kijun-sen serves as the primary trend reference and key support/resistance level. +Kijun-sen serves as the primary trend reference and key support/resistance level. Flat Kijun indicates a balanced market within that timeframe. ### 3. Senkou Span A (Leading Span A) -The average of Tenkan and Kijun, displaced forward: +The average of Tenkan and Kijun, displayed forward: -$$ -\text{SenkouA}_t = \frac{\text{Tenkan}_t + \text{Kijun}_t}{2} \quad \text{(plotted at } t + \text{displacement)} -$$ +$$\text{SenkouA}_t = \frac{\text{Tenkan}_t + \text{Kijun}_t}{2} \quad \text{(plotted at } t + \text{displacement)}$$ -Forms the first boundary of the cloud. More reactive than Senkou Span B. +The more reactive cloud boundary. Changes faster due to Tenkan's shorter period. ### 4. Senkou Span B (Leading Span B) -The long-term midpoint, displaced forward: +Long-term midpoint, displayed forward: -$$ -\text{SenkouB}_t = \frac{\max(H, \text{senkouBPeriod}) + \min(L, \text{senkouBPeriod})}{2} \quad \text{(plotted at } t + \text{displacement)} -$$ +$$\text{SenkouB}_t = \frac{\max(H_{t-51:t}) + \min(L_{t-51:t})}{2} \quad \text{(plotted at } t + \text{displacement)}$$ -Forms the second boundary of the cloud. Flatter and more stable than Span A. +The slower, flatter cloud boundary. Provides strong support/resistance levels. ### 5. Chikou Span (Lagging Span) -The current close price, displaced backward: +Current close plotted backward: -$$ -\text{Chikou}_t = P_t \quad \text{(plotted at } t - \text{displacement)} -$$ +$$\text{Chikou}_t = C_t \quad \text{(plotted at } t - \text{displacement)}$$ -Confirms trend by comparing current price to historical context. +Confirms trend by comparing current price to the price from 26 bars ago. -### 6. State Management +### 6. Complexity -The indicator implements `ITValuePublisher` directly (not `AbstractBase`) and manages its own ring buffer arrays for high/low tracking. State rollback uses `_state` / `_p_state` with full buffer snapshots (`_highBuffer` / `_p_highBuffer`, `_lowBuffer` / `_p_lowBuffer`). +- **Time:** $O(N_{\text{senkou}})$ per bar for min/max scanning over the longest window (52) +- **Space:** $O(N_{\text{senkou}})$ — ring buffers for high and low histories +- **Warmup:** $\max(N_{\text{tenkan}}, N_{\text{kijun}}, N_{\text{senkou}}) = 52$ bars ## Mathematical Foundation -### Component Summary +### Parameters -| Component | Formula | Default Period | Displacement | -|-----------|---------|---------------|-------------| -| Tenkan-sen | $(HH_9 + LL_9) / 2$ | 9 | None | -| Kijun-sen | $(HH_{26} + LL_{26}) / 2$ | 26 | None | -| Senkou A | $(\text{Tenkan} + \text{Kijun}) / 2$ | N/A | +26 forward | -| Senkou B | $(HH_{52} + LL_{52}) / 2$ | 52 | +26 forward | -| Chikou | Close price | N/A | -26 backward | +| Symbol | Parameter | Default | Original Meaning | +|--------|-----------|---------|-----------------| +| $N_t$ | tenkanPeriod | 9 | 1.5 trading weeks | +| $N_k$ | kijunPeriod | 26 | 1 trading month | +| $N_s$ | senkouBPeriod | 52 | 2 trading months | +| $d$ | displacement | 26 | 1 trading month forward/back | -where $HH_n$ = highest high over $n$ periods, $LL_n$ = lowest low over $n$ periods. +### Pseudo-code + +``` +Initialize: + highBuf = RingBuffer(senkouBPeriod) // covers all windows + lowBuf = RingBuffer(senkouBPeriod) + bar_count = 0 + +On each bar (high, low, close, isNew): + if !isNew: restore previous state (including buffer snapshots) + + highBuf.Add(high) + lowBuf.Add(low) + bar_count++ + + // Tenkan-sen (shortest window) + tenkan = (Max(highBuf, tenkanPeriod) + Min(lowBuf, tenkanPeriod)) / 2 + + // Kijun-sen (medium window) + kijun = (Max(highBuf, kijunPeriod) + Min(lowBuf, kijunPeriod)) / 2 + + // Senkou Span A (computed now, plotted displacement bars forward) + senkouA = (tenkan + kijun) / 2 + + // Senkou Span B (longest window, plotted displacement bars forward) + senkouB = (Max(highBuf, senkouBPeriod) + Min(lowBuf, senkouBPeriod)) / 2 + + // Chikou Span (current close, plotted displacement bars backward) + chikou = close + + output: + Tenkan = tenkan // plot at current bar + Kijun = kijun // plot at current bar + SenkouA = senkouA // plot at bar + displacement + SenkouB = senkouB // plot at bar + displacement + Chikou = chikou // plot at bar - displacement +``` ### Cloud (Kumo) Interpretation -$$ -\text{Cloud}_{bullish} \iff \text{SenkouA} > \text{SenkouB} -$$ +| Condition | Meaning | +|-----------|---------| +| SenkouA > SenkouB | Bullish cloud (green) — uptrend structure | +| SenkouA < SenkouB | Bearish cloud (red) — downtrend structure | +| Cloud twist (A crosses B) | Potential trend reversal (leading signal) | +| Thick cloud | Strong support/resistance zone | +| Thin cloud | Weak equilibrium — vulnerable to breakout | +| Price above cloud | Bullish bias | +| Price below cloud | Bearish bias | +| Price inside cloud | Indeterminate — consolidation | -$$ -\text{Cloud}_{bearish} \iff \text{SenkouA} < \text{SenkouB} -$$ +### Displacement Note -$$ -\text{Cloud thickness} = |\text{SenkouA} - \text{SenkouB}| -$$ +Senkou Span A and B are computed at the current bar but displayed shifted forward by `displacement` bars on charts. Chikou Span is the current close displayed shifted backward. The implementation computes current-bar values only — the charting layer handles the visual displacement. -### Default Parameters +### Multi-Output Structure -| Parameter | Default | Original Meaning | -|-----------|---------|-----------------| -| tenkanPeriod | 9 | 1.5 trading weeks (6-day week) | -| kijunPeriod | 26 | 1 trading month | -| senkouBPeriod | 52 | 2 trading months | -| displacement | 26 | 1 trading month forward/back | +The indicator produces five simultaneous values per bar. The primary output (`Last`) returns Kijun-sen as the default reference line. -### Warmup +## Resources -$$ -\text{WarmupPeriod} = \max(\text{tenkanPeriod}, \text{kijunPeriod}, \text{senkouBPeriod}) -$$ - -## Performance Profile - -### Operation Count (Streaming Mode) - -| Operation | Count | Notes | -| :--- | :---: | :--- | -| Min/Max scan | 3 | Tenkan, Kijun, SenkouB windows | -| ADD | 3 | Midpoint calculations | -| DIV | 3 | Midpoint /2 | -| Buffer update | 2 | High + low ring buffers | -| State copy | 2 | Buffer snapshots for rollback | -| **Total** | **~11 ops** | Plus O(n) for min/max scans | - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Exact min/max arithmetic | -| **Timeliness** | 6/10 | Midpoint-based, moderate lag | -| **Smoothness** | 5/10 | Step-like behavior on breakouts | -| **Complexity** | 4/10 | Five components, displacement logic | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **Skender** | ✅ | All 5 components match | -| **TA-Lib** | N/A | No Ichimoku function | -| **Tulip** | N/A | No Ichimoku function | -| **Ooples** | ✅ | Matches within tolerance | -| **TradingView** | ✅ | Matches PineScript reference | - -## Common Pitfalls - -1. **Displacement is charting-only**: The implementation computes current values for Senkou A/B. The forward displacement (plotting these values 26 bars ahead) is the responsibility of the charting layer, not the indicator. - -2. **TBar input required**: Ichimoku operates on OHLC bars, not single values. It implements `ITValuePublisher` directly and accepts `TBar` input, not `TValue`. - -3. **Multiple outputs**: The indicator exposes five separate properties (Tenkan, Kijun, SenkouA, SenkouB, Chikou). The `Last` property returns Kijun-sen as the primary reference. - -4. **Not a moving average**: Tenkan and Kijun are midpoints of high-low ranges, not averages of closing prices. They respond to range breakouts, not gradual price changes. - -5. **Cloud twist**: When Senkou A crosses Senkou B, the cloud color changes. This is a leading signal because the spans are displaced forward. - -6. **Chikou confirmation**: Chikou Span is simply the current close plotted 26 bars back. When Chikou is above the price from 26 bars ago, the trend is confirmed bullish. - -7. **Parameter sensitivity**: Changing from the standard (9, 26, 52) parameters alters the equilibrium model. Some practitioners use (7, 22, 44) for crypto markets with 7-day trading weeks. - -## References - -- Hosoda, G. (1969). "Ichimoku Kinko Hyo." Tokyo, Japan. -- Patel, M. (2010). "Trading with Ichimoku Clouds." Wiley. -- Elliott, N. (2007). "Ichimoku Charts: An Introduction to Ichimoku Kinko Clouds." Harriman House. -- StockCharts.com: "Ichimoku Cloud" Technical Analysis documentation. -- Investopedia: "Ichimoku Cloud Definition and Uses." +- Hosoda, G. — *Ichimoku Kinko Hyo* (7-volume series, Tokyo, 1969) +- Patel, M. — *Trading with Ichimoku Clouds* (John Wiley & Sons, 2010) +- Elliott, N. — *Ichimoku Charts: An Introduction* (Harriman House, 2007) +- PineScript reference: `ichimoku.pine` in indicator directory diff --git a/lib/dynamics/imi/Imi.md b/lib/dynamics/imi/Imi.md index 92124ddc..4e94024a 100644 --- a/lib/dynamics/imi/Imi.md +++ b/lib/dynamics/imi/Imi.md @@ -1,16 +1,10 @@ # IMI: Intraday Momentum Index -> "RSI measures close-to-close momentum. IMI measures open-to-close momentum. One tracks what happened between sessions; the other tracks what happened inside them." - -IMI (Intraday Momentum Index), developed by Tushar Chande, combines candlestick analysis with RSI-like overbought/oversold signals. Unlike RSI, which uses close-to-close price changes, IMI measures the relationship between each bar's open and close prices. This makes it particularly effective for detecting intraday buying/selling pressure and candlestick pattern strength. The result oscillates between 0 and 100, with readings above 70 indicating overbought conditions and below 30 indicating oversold. +The Intraday Momentum Index measures buying and selling pressure using the open-to-close relationship within each bar, rather than the close-to-close changes used by RSI. Each bar is classified as a gain (close > open) or loss (close < open), with the magnitude being the absolute open-close difference. Rolling sums of gains and losses over the lookback period produce an RSI-like ratio scaled to 0-100. This bridges Japanese candlestick analysis with Western oscillator theory: bullish candles contribute to the gain sum, bearish candles contribute to the loss sum. Unlike RSI, IMI does not require a previous close and uses simple rolling sums rather than exponential smoothing, making it more responsive but noisier. Output is bounded 0-100 with conventional overbought (>70) and oversold (<30) zones. ## Historical Context -Tushar Chande introduced the Intraday Momentum Index in "The New Technical Trader" (1994), alongside other innovations like the Chande Momentum Oscillator (CMO). Chande observed that traditional momentum indicators like RSI ignored the intraday price action captured by candlestick patterns. By using the open-close relationship instead of close-close changes, IMI bridges the gap between Japanese candlestick analysis and Western oscillator theory. - -The indicator is particularly useful on daily charts where the open-close relationship has clear meaning (overnight gap vs session direction). On intraday timeframes, its interpretation shifts to measuring buying pressure within each bar. Unlike RSI, IMI does not require a previous close, making it self-contained within each bar. - -The formula structure mirrors RSI: sum of gains over sum of gains plus losses, scaled to 0-100. This provides familiar overbought/oversold levels while measuring a fundamentally different quantity. +Tushar Chande introduced the Intraday Momentum Index in *The New Technical Trader* (1994), alongside innovations like the Chande Momentum Oscillator. Chande observed that traditional momentum indicators like RSI ignored the intraday price action captured by candlestick patterns. By using the open-close relationship instead of close-close changes, IMI measures a fundamentally different quantity: the directional conviction *within* each bar rather than the change *between* bars. On daily charts, the open-close relationship has clear meaning — it captures overnight positioning gaps plus session direction. The indicator is self-contained within each bar, requiring no previous bar's close, which makes it particularly clean for session-based analysis. The formula structure deliberately mirrors RSI (sum of gains over total) to provide familiar overbought/oversold levels while measuring intra-session momentum. ## Architecture & Physics @@ -18,139 +12,105 @@ The formula structure mirrors RSI: sum of gains over sum of gains plus losses, s Each bar is classified based on the open-close relationship: -$$ -\text{Gain}_t = \begin{cases} \text{Close}_t - \text{Open}_t & \text{if Close} > \text{Open} \\ 0 & \text{otherwise} \end{cases} -$$ +$$G_t = \begin{cases} C_t - O_t & \text{if } C_t > O_t \\ 0 & \text{otherwise} \end{cases}$$ -$$ -\text{Loss}_t = \begin{cases} \text{Open}_t - \text{Close}_t & \text{if Close} < \text{Open} \\ 0 & \text{otherwise} \end{cases} -$$ +$$L_t = \begin{cases} O_t - C_t & \text{if } C_t < O_t \\ 0 & \text{otherwise} \end{cases}$$ -### 2. Rolling Sum Calculation +Doji bars ($C = O$) contribute zero to both sums. -The indicator uses O(1) rolling sums via ring buffers: +### 2. Rolling Sums -$$ -\text{SumGains}_t = \sum_{i=t-n+1}^{t} \text{Gain}_i -$$ +Simple rolling sums over the lookback window (no exponential smoothing): -$$ -\text{SumLosses}_t = \sum_{i=t-n+1}^{t} \text{Loss}_i -$$ +$$\text{SumGains}_t = \sum_{i=t-N+1}^{t} G_i$$ + +$$\text{SumLosses}_t = \sum_{i=t-N+1}^{t} L_i$$ + +Implemented with ring buffers and incremental add/subtract for $O(1)$ per bar. ### 3. IMI Value -$$ -\text{IMI}_t = 100 \times \frac{\text{SumGains}_t}{\text{SumGains}_t + \text{SumLosses}_t} -$$ +$$\text{IMI}_t = 100 \times \frac{\text{SumGains}_t}{\text{SumGains}_t + \text{SumLosses}_t}$$ -When both sums are zero (flat bars only), IMI defaults to 50.0 (neutral). +When both sums are zero (all doji bars in window), IMI defaults to 50.0 (neutral). -### 4. State Management +### 4. Complexity -The indicator implements `ITValuePublisher` directly (not `AbstractBase`) because it requires `TBar` input (OHLC data). Rolling sums (`_gainSum`, `_lossSum`) are saved/restored for bar correction via `_savedGainSum` / `_savedLossSum`. +- **Time:** $O(1)$ per bar — rolling sum add/subtract +- **Space:** $O(N)$ — two ring buffers for gain and loss history +- **Warmup:** $N$ bars ## Mathematical Foundation -### Core Formula +### Parameters -$$ -\text{IMI} = 100 \times \frac{\sum_{i=1}^{n} G_i}{\sum_{i=1}^{n} G_i + \sum_{i=1}^{n} L_i} -$$ +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 1$ | -where: +### Pseudo-code -- $G_i = \max(C_i - O_i, 0)$ (gain on bullish bars) -- $L_i = \max(O_i - C_i, 0)$ (loss on bearish bars) -- $n$ = lookback period (default 14) +``` +Initialize: + gainBuf = RingBuffer(period) + lossBuf = RingBuffer(period) + gainSum = lossSum = 0 + bar_count = 0 -### Key Levels +On each bar (open, close, isNew): + if !isNew: restore previous state -| Level | Interpretation | -|-------|---------------| -| > 70 | Overbought: strong bullish intraday pressure | -| < 30 | Oversold: strong bearish intraday pressure | -| 50 | Neutral: balanced buying/selling pressure | + // Classify bar + diff = close - open + gain = diff > 0 ? diff : 0 + loss = diff < 0 ? -diff : 0 -### Comparison with RSI + // Update rolling sums + if gainBuf is full: + gainSum -= gainBuf.Oldest + lossSum -= lossBuf.Oldest + gainBuf.Add(gain) + lossBuf.Add(loss) + gainSum += gain + lossSum += loss + + // IMI calculation + total = gainSum + lossSum + IMI = total > 0 ? 100 × gainSum / total : 50.0 + + output = IMI +``` + +### IMI vs RSI Comparison | Property | RSI | IMI | |----------|-----|-----| | Input | Close-to-close change | Open-to-close change | | Measures | Inter-session momentum | Intra-session momentum | -| Requires previous bar | Yes | No (self-contained) | -| Smoothing | Wilder's smoothing (EMA) | Simple sum (no smoothing) | +| Smoothing | Wilder's RMA (exponential) | Simple rolling sum | +| Previous bar | Required ($C_{t-1}$) | Not required (self-contained) | +| Response | Smoother, more lag | More responsive, noisier | | Range | 0-100 | 0-100 | -| Default period | 14 | 14 | -### Default Parameters +### Interpretation -| Parameter | Default | Purpose | -|-----------|---------|---------| -| period | 14 | Lookback window for gain/loss sums | +| IMI Value | Meaning | +|-----------|---------| +| > 70 | Overbought — strong bullish intra-session pressure | +| < 30 | Oversold — strong bearish intra-session pressure | +| 50 | Neutral — balanced buying/selling within bars | +| Rising toward 70 | Increasing proportion of bullish candles | +| Falling toward 30 | Increasing proportion of bearish candles | -### Warmup +### Timeframe Sensitivity -$$ -\text{WarmupPeriod} = \text{period} -$$ +On daily charts, the open-close relationship captures overnight gaps plus session direction — the most informative timeframe for IMI. On very short intraday charts (1-minute), the open-close relationship carries less structural information since the open price has minimal gap significance. Choose timeframes where the opening price carries genuine information about session sentiment. -## Performance Profile +### OHLC Requirement -### Operation Count (Streaming Mode) +IMI requires both Open and Close prices per bar. It implements `ITValuePublisher` directly rather than `AbstractBase` since it operates on `TBar` (OHLC) input, not single `TValue` input. -| Operation | Count | Notes | -| :--- | :---: | :--- | -| SUB | 1 | close - open | -| CMP | 1 | classify gain vs loss | -| ADD/SUB | 2 | rolling sum update | -| DIV | 1 | IMI ratio | -| MUL | 1 | scale to 100 | -| **Total** | **~6 ops** | O(1) per bar | +## Resources -### Batch Mode - -| Operation | Complexity | Notes | -| :--- | :---: | :--- | -| Per-element | O(1) | Rolling sum, no re-scan | -| Total | O(n) | Linear scan | -| Memory | O(period) | Two ring buffers | - -### Quality Metrics - -| Metric | Score | Notes | -| :--- | :---: | :--- | -| **Accuracy** | 10/10 | Exact arithmetic, no approximation | -| **Timeliness** | 8/10 | No smoothing lag beyond window | -| **Smoothness** | 5/10 | Can be choppy in ranging markets | -| **Simplicity** | 8/10 | Straightforward gain/loss ratio | - -## Validation - -| Library | Status | Notes | -| :--- | :---: | :--- | -| **Skender** | ✅ | Matches within tolerance | -| **TA-Lib** | N/A | No IMI function | -| **Tulip** | N/A | No IMI function | -| **Ooples** | ✅ | Matches within tolerance | -| **CQG** | ✅ | Reference implementation matches | - -## Common Pitfalls - -1. **TBar input required**: IMI needs Open and Close prices. Passing single values (TValue) is not supported. The indicator implements `ITValuePublisher` directly, not `AbstractBase`. - -2. **Doji bars**: When Open equals Close, both Gain and Loss are zero. These bars contribute nothing to either sum but still age out of the window. - -3. **All-zero edge case**: If all bars in the window are Dojis, both sums are zero. The implementation returns 50.0 (neutral) to avoid division by zero. - -4. **Not smoothed**: Unlike RSI, which uses Wilder's smoothing (exponential), IMI uses simple sums. This makes it more responsive but also noisier. - -5. **Timeframe sensitivity**: On daily charts, open-close captures overnight gaps plus session direction. On 1-minute charts, the open-close relationship is less meaningful. Choose timeframes where the open price carries information. - -6. **NaN handling**: Non-finite Open or Close values cause the bar to be skipped, preserving the last valid IMI value. - -## References - -- Chande, T. S., & Kroll, S. (1994). "The New Technical Trader." Wiley. -- Investopedia: "Intraday Momentum Index (IMI) Definition." -- CQG: "Intraday Momentum Index (IMI)" Technical Reference. +- Chande, T.S. & Kroll, S. — *The New Technical Trader* (John Wiley & Sons, 1994) +- PineScript reference: `imi.pine` in indicator directory diff --git a/lib/dynamics/impulse/Impulse.md b/lib/dynamics/impulse/Impulse.md index 5c066898..6aefca0b 100644 --- a/lib/dynamics/impulse/Impulse.md +++ b/lib/dynamics/impulse/Impulse.md @@ -2,136 +2,109 @@ > "The Impulse System identifies inflection points where a trend speeds up or slows down." -- Alexander Elder, *Come Into My Trading Room* -The Elder Impulse System combines a 13-period exponential moving average (trend inertia) with the MACD(12,26,9) histogram (momentum) to classify each price bar into one of three states: bullish, bearish, or neutral. It is the rare indicator that answers "should I be trading this direction right now?" with a single color. +The Elder Impulse System combines a 13-period EMA (trend inertia) with the MACD(12,26,9) histogram (momentum acceleration) to classify each bar as bullish (+1), bearish (-1), or neutral (0). Both EMA slope and histogram slope must agree for a directional signal; disagreement forces neutral. The system functions as a permission filter rather than a signal generator, requiring 34 bars warmup and running at O(1) per bar through composition of two child indicators. ## Historical Context -Alexander Elder introduced the Impulse System in his 2002 book *Come Into My Trading Room*. Elder, a psychiatrist turned trader, designed it as a censorship system: green bars permit long entries, red bars permit short entries, and blue bars prohibit new positions in either direction. The system enforces discipline by requiring both trend and momentum to align before committing capital. +Alexander Elder introduced the Impulse System in *Come Into My Trading Room* (2002). A psychiatrist turned trader, Elder designed it as a discipline enforcement mechanism: green bars permit long entries, red bars permit short entries, blue bars prohibit new positions in either direction. The intellectual lineage runs through Gerald Appel's MACD (1979) and Thomas Aspray's MACD Histogram (1986). Elder's contribution was combining first-derivative (EMA slope) and second-derivative (histogram slope) filters into a single ternary decision gate. Most implementations treat this as a visual color overlay; QuanTAlib exposes it as a programmatic discrete signal for algorithmic consumption. -The intellectual lineage runs through Gerald Appel's MACD (1979), which separated trend from momentum, and Thomas Aspray's MACD Histogram (1986), which revealed the rate of change within the MACD itself. Elder's contribution was recognizing that combining EMA slope (inertia proxy) with histogram slope (momentum proxy) produces a decision filter superior to either component alone. +## Architecture & Physics -Most implementations treat this as a visual overlay with colored bars. QuanTAlib exposes it as a programmatic signal (+1, 0, -1) for algorithmic consumption, while the Quantower adapter provides the traditional color-coded visualization. +### 1. EMA Component (Trend Inertia) -## Architecture - -### Component Composition - -The Impulse indicator composes two child indicators internally: - -1. **EMA(13)** on close price: measures trend inertia via exponential smoothing -2. **MACD(12,26,9)**: provides the histogram (MACD Line minus Signal Line) for momentum measurement - -Both child indicators manage their own state, warmup compensation, and bar correction. The Impulse class tracks only the previous EMA value and previous histogram value for slope comparison. - -### Signal Classification - -```text -EMA_slope = sign(EMA_current - EMA_previous) -Hist_slope = sign(Histogram_current - Histogram_previous) - -Signal = +1 (Green/Bullish): EMA_slope > 0 AND Hist_slope > 0 -Signal = -1 (Red/Bearish): EMA_slope < 0 AND Hist_slope < 0 -Signal = 0 (Blue/Neutral): otherwise -``` - -The neutral state fires whenever the two components disagree. This is the system's core value proposition: it identifies transition zones where conviction is insufficient for new entries. - -## Mathematical Foundation - -### EMA Component - -The 13-period EMA uses the standard recursive formulation: +The 13-period EMA tracks trend direction via exponential smoothing: $$\alpha = \frac{2}{n + 1} = \frac{2}{14} \approx 0.1429$$ $$\text{EMA}_t = \alpha \cdot \text{Close}_t + (1 - \alpha) \cdot \text{EMA}_{t-1}$$ -QuanTAlib's EMA implementation includes warmup bias compensation: +With warmup bias compensation: -$$\text{EMA}_{\text{corrected}} = \frac{\text{EMA}_{\text{raw}}}{1 - (1-\alpha)^n}$$ +$$\text{EMA}_{\text{corrected}} = \frac{\text{EMA}_{\text{raw}}}{1 - (1 - \alpha)^t}$$ -### MACD Histogram Component +The EMA slope $\Delta_{\text{EMA}} = \text{sign}(\text{EMA}_t - \text{EMA}_{t-1})$ represents smoothed trend direction (first derivative of price). + +### 2. MACD Histogram Component (Momentum Acceleration) $$\text{MACD Line} = \text{EMA}(12) - \text{EMA}(26)$$ -$$\text{Signal Line} = \text{EMA}(9, \text{MACD Line})$$ +$$\text{Signal} = \text{EMA}(9,\ \text{MACD Line})$$ -$$\text{Histogram} = \text{MACD Line} - \text{Signal Line}$$ +$$\text{Histogram} = \text{MACD Line} - \text{Signal}$$ -The histogram is the second derivative of price (acceleration), making the Impulse System a combined first-derivative (EMA slope) and second-derivative (histogram slope) filter. +The histogram is the second derivative of price (acceleration). Its slope $\Delta_{\text{Hist}} = \text{sign}(\text{Hist}_t - \text{Hist}_{t-1})$ indicates whether momentum is building or fading. -### Warmup Period +### 3. Signal Classification -$$W = \max(\text{emaPeriod}, \text{macdSlow}) + \text{macdSignal} - 1 = \max(13, 26) + 9 - 1 = 34$$ +$$\text{Impulse} = \begin{cases} +1 & \text{if } \Delta_{\text{EMA}} > 0 \text{ and } \Delta_{\text{Hist}} > 0 \\ -1 & \text{if } \Delta_{\text{EMA}} < 0 \text{ and } \Delta_{\text{Hist}} < 0 \\ 0 & \text{otherwise} \end{cases}$$ -The indicator requires 34 bars before producing valid signals. IsHot becomes true when both child indicators are warmed up and at least two comparison values exist. +The neutral state fires whenever the two components disagree, identifying transition zones where conviction is insufficient for new entries. -## Performance Profile +### 4. Warmup + +$$W = \max(13, 26) + 9 - 1 = 34 \text{ bars}$$ + +Both child indicators must be warmed up and at least two comparison values must exist before valid signals emerge. + +### 5. Complexity | Metric | Value | -| :----- | :---- | -| Update complexity | O(1) per bar | -| Memory | 3 internal EMA states + MACD state + 4 doubles for comparison | -| Allocations in Update | Zero (delegates to child indicator Update methods) | -| SIMD potential | None (serial comparison logic) | +|:-------|:------| +| Time | O(1) per bar (delegates to child EMA/MACD updates) | +| Space | O(1) (3 internal EMA states + 4 doubles for slope comparison) | +| Allocations | Zero in hot path | -### Quality Metrics +## Mathematical Foundation -| Metric | Score (1-10) | -| :----- | :----------- | -| Lag | 4 (moderate; EMA(13) + histogram smoothing introduce delay) | -| Noise rejection | 7 (requires dual confirmation) | -| Signal clarity | 9 (ternary output with no ambiguity) | -| Computational efficiency | 9 (pure O(1) composition) | -| Parameter sensitivity | 6 (Elder's defaults are widely used; customization possible but rarely needed) | +### Parameters -## Validation +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| emaPeriod | int | 13 | > 0 | EMA period for trend inertia | +| macdFast | int | 12 | > 0 | MACD fast EMA period | +| macdSlow | int | 26 | > macdFast | MACD slow EMA period | +| macdSignal | int | 9 | > 0 | MACD signal smoothing period | -No external libraries (TA-Lib, Skender, Tulip, OoplesFinance) implement the Elder Impulse System as a standalone indicator. Validation uses self-consistency checks: +### Pseudo-code -| Test | Method | -| :--- | :----- | -| EMA identity | Impulse EMA output matches standalone EMA(13) | -| Signal correctness | Manual EMA + MACD comparison produces identical signals | -| Streaming == Batch | Streaming and batch modes produce identical EMA values | -| Determinism | Same input sequence produces identical output | -| Directional | Steady uptrend produces +1; steady downtrend produces -1 | +``` +IMPULSE(close, emaPeriod=13, macdFast=12, macdSlow=26, macdSignal=9): -## Interpretation + // Child indicator updates + ema_val = EMA.Update(close, emaPeriod) + macd_result = MACD.Update(close, macdFast, macdSlow, macdSignal) + hist_val = macd_result.Histogram -The Impulse System operates as a **permission filter**, not a signal generator: + // Slope computation (requires previous values) + ema_slope = sign(ema_val - prev_ema) + hist_slope = sign(hist_val - prev_hist) -- **Green (+1):** Both inertia and momentum favor bulls. Long entries permitted; short entries prohibited. -- **Red (-1):** Both inertia and momentum favor bears. Short entries permitted; long entries prohibited. -- **Blue (0):** Disagreement between trend and momentum. No new entries in either direction; existing positions may be held or tightened. + // Classification + if ema_slope > 0 AND hist_slope > 0: + signal = +1 // Bullish: both inertia and momentum rising + else if ema_slope < 0 AND hist_slope < 0: + signal = -1 // Bearish: both inertia and momentum falling + else: + signal = 0 // Neutral: disagreement between components -### Multi-Timeframe Application + // State update + prev_ema = ema_val + prev_hist = hist_val -Elder recommends using the Impulse System across two timeframes (5:1 ratio): + return signal +``` -1. Weekly chart: determines the "big picture" trend direction -2. Daily chart: identifies entry points aligned with the weekly trend +### Derivative Interpretation -Trade only when the higher timeframe is not red (for longs) or not green (for shorts). +The system combines two derivatives: -## Common Pitfalls +- **First derivative** (EMA slope): Is the smoothed trend rising or falling? +- **Second derivative** (histogram slope): Is the rate of MACD convergence/divergence accelerating or decelerating? -1. **Using Impulse as an entry signal instead of a filter.** The system identifies when trading is permitted, not when to trade. Combine with entry triggers (pullbacks, breakouts). +Both must confirm for a directional signal. This dual-confirmation suppresses false signals during transitions but introduces lag at inflection points. -2. **Ignoring the neutral state.** Blue bars are not "do nothing" -- they indicate transitions. Watch for the sequence blue-then-green or blue-then-red for early signals. - -3. **Overriding the prohibition.** Going long on a red bar or short on a green bar defeats the system's purpose. The whole point is discipline enforcement. - -4. **Expecting the system to catch tops and bottoms.** The EMA and histogram lag price. By design, the system trades the middle of moves, not the extremes. - -5. **Modifying the default parameters without understanding the impact.** The 13-period EMA and 12/26/9 MACD are Elder's specific design choices. Shorter periods increase noise; longer periods increase lag. The defaults balance both. - -6. **Confusing EMA direction with price direction.** Price can close higher while EMA still falls (or vice versa). The EMA slope represents smoothed trend, not raw price movement. - -## References +## Resources - Elder, A. (2002). *Come Into My Trading Room*. John Wiley and Sons. -- Elder, A. (1993). *Trading for a Living*. John Wiley and Sons. - Appel, G. (1979). "The Moving Average Convergence-Divergence Method." - Aspray, T. (1986). "MACD Histogram." *Technical Analysis of Stocks and Commodities*. -- StockCharts.com. "Elder Impulse System." ChartSchool. diff --git a/lib/dynamics/qstick/Qstick.md b/lib/dynamics/qstick/Qstick.md index 147a7052..6418fa96 100644 --- a/lib/dynamics/qstick/Qstick.md +++ b/lib/dynamics/qstick/Qstick.md @@ -2,142 +2,91 @@ > "The average candlestick body reveals the market's true conviction." -Developed by Tushar Chande, the Qstick indicator measures the average difference between closing and opening prices over a lookback period. It quantifies whether bars are predominantly bullish (closing above opens) or bearish (closing below opens), providing a smoothed view of candlestick body direction and magnitude. +The Qstick indicator, developed by Tushar Chande, computes a moving average of the close-minus-open difference over a lookback period, quantifying whether bars are predominantly bullish or bearish. Positive values indicate closes above opens (buying pressure); negative values indicate closes below opens (selling pressure). It supports both SMA (O(N) space via ring buffer) and EMA (O(1) space) smoothing modes and requires TBar input for open/close access. ## Historical Context -Tushar Chande introduced the Qstick as part of his work on candlestick pattern quantification in the early 1990s. While traditional candlestick analysis relies on visual pattern recognition, Qstick provides a numerical measure that can be systematically tracked and used for algorithmic trading. +Tushar Chande introduced Qstick as part of his candlestick quantification work in *The New Technical Trader* (1994, co-authored with Stanley Kroll). Traditional candlestick analysis relies on visual pattern recognition; Qstick reduces bar body direction and magnitude to a single continuous number suitable for systematic tracking. The indicator addresses a specific gap: close-to-close momentum indicators miss intrabar dynamics captured by the open-to-close differential. The name "Qstick" reflects the "quick stick" reading of candlestick conviction. -The indicator addresses a fundamental question: "On average, are prices closing higher or lower than they open?" This simple metric captures intrabar momentum that other indicators measuring close-to-close changes may miss. +## Architecture & Physics -## Architecture +### 1. Body Difference -### 1. Body Difference Calculation +$$d_t = \text{Close}_t - \text{Open}_t$$ -The core input is the difference between close and open: - -``` -diff = Close - Open -``` - -- **Positive diff**: Bullish bar (white/green candle) -- **Negative diff**: Bearish bar (black/red candle) -- **Zero diff**: Doji (open equals close) +Positive $d_t$ represents a bullish bar (close above open), negative represents bearish, zero represents a doji. ### 2. Moving Average Smoothing -The raw differences are smoothed using either SMA or EMA: +**SMA mode:** Maintains a ring buffer of $N$ differences and a running sum for O(1) incremental updates: -**SMA Mode:** -$$\text{Qstick} = \frac{1}{n} \sum_{i=0}^{n-1} (Close_i - Open_i)$$ +$$\text{Qstick}_t = \frac{1}{N} \sum_{i=0}^{N-1} d_{t-i}$$ -**EMA Mode:** -$$\text{Qstick}_t = \alpha \cdot diff_t + (1 - \alpha) \cdot \text{Qstick}_{t-1}$$ +**EMA mode:** Standard recursive filter with decay $\alpha = 2/(N+1)$: -where $\alpha = \frac{2}{period + 1}$ +$$\text{Qstick}_t = \alpha \cdot d_t + (1 - \alpha) \cdot \text{Qstick}_{t-1}$$ -### 3. State Management +EMA mode uses O(1) space but weights recent bars more heavily than SMA. -For real-time bar correction (isNew=false), the indicator maintains: -- `_sum` / `_savedSum`: Running sum for SMA -- `_emaValue` / `_savedEmaValue`: Current EMA value -- `_count` / `_savedCount`: Bar count for warmup +### 3. Complexity -## Parameters - -| Parameter | Type | Default | Valid Range | Description | -|-----------|------|---------|-------------|-------------| -| `period` | int | 14 | ≥ 1 | Lookback period for moving average | -| `useEma` | bool | false | true/false | Use EMA (true) or SMA (false) | +| Metric | SMA Mode | EMA Mode | +|:-------|:---------|:---------| +| Time | O(1) per bar | O(1) per bar | +| Space | O(N) ring buffer | O(1) | +| Ops | 1 add, 1 sub, 1 div | 1 sub, 1 mul, 1 FMA | ## Mathematical Foundation -### Formula +### Parameters + +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| period | int | 14 | > 0 | Lookback period for moving average | +| useEma | bool | false | — | Use EMA (true) or SMA (false) | + +### Pseudo-code ``` -Qstick = MA(Close - Open, period) +QSTICK(bar, period=14, useEma=false): + + diff = bar.Close - bar.Open + + if useEma: + // EMA mode + alpha = 2.0 / (period + 1) + if count == 0: + ema_val = diff + else: + ema_val = FMA(alpha, diff - ema_val, ema_val) // alpha*(diff-ema)+ema + result = ema_val + + else: + // SMA mode with ring buffer + if buffer is full: + running_sum -= buffer.oldest + buffer.add(diff) + running_sum += diff + result = running_sum / min(count, period) + + return result ``` -### Interpretation +### Zero-Crossing Interpretation -| Qstick Value | Market Condition | -|--------------|------------------| -| > 0 | Bullish momentum (closes above opens) | -| < 0 | Bearish momentum (closes below opens) | -| = 0 | Neutral (balanced open/close) | -| Rising | Increasing bullish pressure | -| Falling | Increasing bearish pressure | +| Condition | Meaning | +|:----------|:--------| +| Qstick > 0 | Closes above opens dominate (net buying pressure) | +| Qstick < 0 | Closes below opens dominate (net selling pressure) | +| Qstick crosses zero | Shift in intrabar momentum direction | +| Qstick rising | Increasing bullish pressure regardless of sign | +| Qstick falling | Increasing bearish pressure regardless of sign | -### Signal Generation +### Scale Dependence -- **Buy Signal**: Qstick crosses above zero -- **Sell Signal**: Qstick crosses below zero -- **Divergence**: Price making new highs while Qstick making lower highs suggests weakening momentum +Qstick values are in absolute price units, not normalized. Cross-instrument comparison requires normalization (e.g., divide by ATR or price level). Short periods (5-8) suit trading signals; longer periods (20+) suit trend identification. -## Performance Profile +## Resources -### Operation Count (Streaming Mode) - -| Operation | SMA Mode | EMA Mode | -|-----------|----------|----------| -| ADD/SUB | 3 | 2 | -| MUL | 0 | 1 | -| DIV | 1 | 0 | -| FMA | 0 | 1 | -| Memory | O(period) | O(1) | - -### Complexity - -- **Time**: O(1) per bar for both modes -- **Space**: O(period) for SMA, O(1) for EMA - -### Quality Metrics - -| Metric | Score | Notes | -|--------|-------|-------| -| Accuracy | 10/10 | Exact calculation | -| Timeliness | 8/10 | Lag proportional to period | -| Overshoot | 2/10 | Smooth, no overshoot | -| Smoothness | 8/10 | SMA smoother than EMA | - -## Validation - -| Library | Status | Notes | -|---------|--------|-------| -| TA-Lib | ✓ | Not available (implement locally) | -| Skender | ✓ | Validated against Qstick | -| OoplesFinance | ✓ | Validated | - -## Common Pitfalls - -1. **Ignoring Volume**: Qstick weights all bars equally; consider volume-weighted variants for more accuracy -2. **Range Dependence**: Absolute values depend on price scale; normalize for comparison across instruments -3. **Period Selection**: Short periods (5-8) for trading signals; long periods (20+) for trend identification -4. **Gap Sensitivity**: Large gaps (open ≠ previous close) can distort readings -5. **Flat Markets**: Near-zero readings indicate indecision, not necessarily reversal - -## Usage Example - -```csharp -// Create Qstick with 14-period SMA -var qstick = new Qstick(14); - -// Update with bar data -foreach (var bar in bars) -{ - var result = qstick.Update(bar); - if (qstick.IsHot) - { - Console.WriteLine($"Qstick: {result.Value:F4}"); - } -} - -// Or use EMA mode -var qstickEma = new Qstick(14, useEma: true); -``` - -## References - -1. Chande, T. S. (1994). *The New Technical Trader*. John Wiley & Sons. -2. Chande, T. S., & Kroll, S. (1994). *Beyond Technical Analysis*. John Wiley & Sons. -3. Kirkpatrick, C. D., & Dahlquist, J. R. (2015). *Technical Analysis: The Complete Resource for Financial Market Technicians*. FT Press. +- Chande, T. S. & Kroll, S. (1994). *The New Technical Trader*. John Wiley and Sons. +- Kirkpatrick, C. D. & Dahlquist, J. R. (2015). *Technical Analysis: The Complete Resource for Financial Market Technicians*. FT Press. diff --git a/lib/dynamics/super/Super.md b/lib/dynamics/super/Super.md index e13de43c..549cdd0d 100644 --- a/lib/dynamics/super/Super.md +++ b/lib/dynamics/super/Super.md @@ -1,65 +1,113 @@ # SUPER: SuperTrend -> "It's not an indicator; it's a trailing stop with a marketing budget. Perfect for traders who want to catch the trend but lack the emotional discipline to hold on." +> "It's not an indicator; it's a trailing stop with a marketing budget." -SuperTrend is a trend-following indicator that overlays the price chart. It uses the Average True Range (ATR) to calculate upper and lower volatility bands, switching between them based on the direction of the closing price. It effectively functions as a trailing stop-loss that adapts to market volatility. +SuperTrend is a trend-following overlay that uses ATR-scaled bands around the HL2 midpoint, switching between upper and lower bands based on close price breakouts. A ratchet mechanism prevents the active band from moving against the trend, creating a step-like trailing stop that adapts to volatility. The indicator is a two-state machine (bullish/bearish) with O(1) per-bar updates and zero allocations in the hot path. ## Historical Context -Created by Olivier Seban. It gained massive popularity in the retail trading community for its visual simplicity: Green line = Buy, Red line = Sell. It combines the volatility measurement of Wilder's ATR with a simple breakout logic. +Olivier Seban created SuperTrend, which gained massive popularity in the retail trading community for its visual simplicity: a single line that is green during uptrends and red during downtrends. The construction combines Wilder's ATR volatility measurement (1978) with a breakout/ratchet mechanism. Unlike moving-average crossover systems that produce continuous values, SuperTrend outputs a binary trend state with a concrete stop level, making it directly actionable as a trailing stop-loss. The indicator does not repaint historical values, though the current bar's value can oscillate until the close is finalized. ## Architecture & Physics -SuperTrend is a state machine. It maintains two theoretical bands (Upper and Lower) and a boolean state (`IsBullish`). +### 1. Basic Bands -### The Ratchet Mechanism +The raw bands center on the HL2 midpoint, offset by ATR times a multiplier: -The bands act as a ratchet: +$$\text{Upper}_{\text{basic}} = \frac{H_t + L_t}{2} + m \cdot \text{ATR}(N)$$ -* **Bullish Mode**: The Lower Band (Stop Loss) can only move up. If the calculated Lower Band drops, the indicator ignores it and keeps the previous value. -* **Bearish Mode**: The Upper Band (Stop Loss) can only move down. +$$\text{Lower}_{\text{basic}} = \frac{H_t + L_t}{2} - m \cdot \text{ATR}(N)$$ -The trend flips when the Close price crosses the active band. +where $m$ is the multiplier (default 3.0) and $N$ is the ATR period (default 10). + +### 2. Ratchet Logic + +The bands act as a one-way ratchet that prevents regression against the trend: + +**Upper band** (bearish stop) can only move down: + +$$\text{Upper}_{\text{final}} = \begin{cases} \min(\text{Upper}_{\text{basic}},\ \text{Upper}_{\text{prev}}) & \text{if } C_{t-1} \leq \text{Upper}_{\text{prev}} \\ \text{Upper}_{\text{basic}} & \text{otherwise} \end{cases}$$ + +**Lower band** (bullish stop) can only move up: + +$$\text{Lower}_{\text{final}} = \begin{cases} \max(\text{Lower}_{\text{basic}},\ \text{Lower}_{\text{prev}}) & \text{if } C_{t-1} \geq \text{Lower}_{\text{prev}} \\ \text{Lower}_{\text{basic}} & \text{otherwise} \end{cases}$$ + +### 3. Trend State Machine + +$$\text{Trend}_t = \begin{cases} \text{Bullish} & \text{if } C_t > \text{Upper}_{\text{final}} \\ \text{Bearish} & \text{if } C_t < \text{Lower}_{\text{final}} \\ \text{Trend}_{t-1} & \text{otherwise (hysteresis)} \end{cases}$$ + +$$\text{SuperTrend} = \begin{cases} \text{Lower}_{\text{final}} & \text{if Bullish} \\ \text{Upper}_{\text{final}} & \text{if Bearish} \end{cases}$$ + +The output is the active stop level. Crossing the active band flips the state. + +### 4. Complexity + +| Metric | Value | +|:-------|:------| +| Time | O(1) per bar | +| Space | O(1) (ATR state + 2 band values + 1 trend boolean) | +| Allocations | Zero in hot path | +| Warmup | N bars (ATR stabilization) | ## Mathematical Foundation -### 1. Basic Bands +### Parameters -$$ Upper_{basic} = \frac{High + Low}{2} + (Multiplier \times ATR) $$ -$$ Lower_{basic} = \frac{High + Low}{2} - (Multiplier \times ATR) $$ +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| atrPeriod | int | 10 | > 0 | ATR lookback period | +| multiplier | double | 3.0 | > 0 | ATR multiplier for band width | -### 2. Ratchet Logic (Bullish Example) +### Pseudo-code -$$ Lower_{final} = \begin{cases} Lower_{basic} & \text{if } Lower_{basic} > Lower_{prev} \text{ or } Close_{prev} < Lower_{prev} \\ Lower_{prev} & \text{otherwise} \end{cases} $$ +``` +SUPERTREND(bar, atrPeriod=10, multiplier=3.0): -### 3. Trend Logic + // ATR update (Wilder's smoothing or SMA) + atr = ATR.Update(bar, atrPeriod) -$$ SuperTrend = \begin{cases} Lower_{final} & \text{if Bullish} \\ Upper_{final} & \text{if Bearish} \end{cases} $$ + // Basic bands + hl2 = (bar.High + bar.Low) / 2 + upper_basic = hl2 + multiplier * atr + lower_basic = hl2 - multiplier * atr -## Performance Profile + // Ratchet: upper can only decrease, lower can only increase + if prev_close <= prev_upper_final: + upper_final = min(upper_basic, prev_upper_final) + else: + upper_final = upper_basic -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 9 | High; O(1) calculation with minimal overhead. | -| **Allocations** | 0 | Zero-allocation in hot paths. | -| **Complexity** | O(1) | Constant time regardless of period. | -| **Accuracy** | 10 | Matches standard implementations exactly. | -| **Timeliness** | 5 | Lag depends on ATR period and multiplier. | -| **Overshoot** | 0 | Bands are constrained by price action. | -| **Smoothness** | 2 | Step-like behavior; not a smooth curve. | + if prev_close >= prev_lower_final: + lower_final = max(lower_basic, prev_lower_final) + else: + lower_final = lower_basic -## Validation + // State machine transition + if bar.Close > upper_final: + is_bullish = true + else if bar.Close < lower_final: + is_bullish = false + // else: retain previous state (hysteresis) -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **TA-Lib** | N/A | Not implemented. | -| **Skender** | ✅ | Matches `GetSuperTrend` exactly. | -| **Tulip** | N/A | Not implemented. | -| **Ooples** | ❌ | Diverges significantly due to initialization logic. | + // Output active stop level + if is_bullish: + supertrend = lower_final + else: + supertrend = upper_final -### Common Pitfalls + return supertrend +``` -1. **Repainting**: SuperTrend does not repaint historical values, but the current bar's value can flip back and forth until the Close is finalized. -2. **Whipsaws**: In ranging markets, SuperTrend will generate frequent false signals, buying the top and selling the bottom. It requires a trend filter (like ADX). -3. **ATR Warmup**: The indicator requires $N$ bars to stabilize the ATR before the bands become accurate. +### Band Behavior by State + +| State | Active Band | Ratchet Direction | Flip Condition | +|:------|:------------|:------------------|:---------------| +| Bullish | Lower (support) | Can only rise | Close < Lower | +| Bearish | Upper (resistance) | Can only fall | Close > Upper | + +The step-like output results from the ratchet constraint: the band remains flat until a new extremum pushes it in the trend direction. Whipsaws occur in ranging markets where close repeatedly crosses both bands. + +## Resources + +- Seban, O. SuperTrend indicator documentation. +- Wilder, J. W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. diff --git a/lib/dynamics/ttm_squeeze/TtmSqueeze.md b/lib/dynamics/ttm_squeeze/TtmSqueeze.md index bcd73fae..df98c59d 100644 --- a/lib/dynamics/ttm_squeeze/TtmSqueeze.md +++ b/lib/dynamics/ttm_squeeze/TtmSqueeze.md @@ -1,61 +1,119 @@ # TTM_SQUEEZE: TTM Squeeze -> **Pending Implementation** - Placeholder for John Carter's TTM Squeeze indicator +> "Volatility compression is the market holding its breath before screaming." + +John Carter's TTM Squeeze detects low-volatility compression by comparing Bollinger Band width against Keltner Channel width: when BB fits inside KC, a "squeeze" is on, signaling imminent breakout. The momentum component uses linear regression of price deviation from the Donchian midline to indicate direction. The indicator outputs a boolean squeeze state plus a continuous momentum histogram, requiring BB(20,2.0) and KC(20,1.5) as default parameters with a combined warmup of 20 bars. ## Historical Context -John Carter developed TTM Squeeze as his signature volatility breakout indicator, popularized through his book *Mastering the Trade* and thinkorswim platform. The indicator combines Bollinger Bands and Keltner Channels to identify low-volatility "squeeze" conditions that typically precede explosive price moves. +John Carter developed TTM Squeeze as his signature volatility breakout indicator, popularized through *Mastering the Trade* (2005) and the thinkorswim platform. The core insight combines two independent volatility measures: Bollinger's standard-deviation bands and Keltner's ATR-based channels. When the faster-reacting BB contracts inside the slower KC, it signals unusually low volatility, a condition that reliably precedes explosive directional moves. Carter added a momentum oscillator based on linear regression to provide directional bias during squeeze releases. The indicator became one of the most widely used proprietary tools in retail trading. -## Algorithm +## Architecture & Physics -### Squeeze Detection -- **Squeeze On (●):** Bollinger Bands inside Keltner Channel -- **Squeeze Off (○):** Bollinger Bands outside Keltner Channel +### 1. Bollinger Band Width + +$$\text{BB}_{\text{upper}} = \text{SMA}(C, N_{\text{BB}}) + k_{\text{BB}} \cdot \sigma(C, N_{\text{BB}})$$ + +$$\text{BB}_{\text{lower}} = \text{SMA}(C, N_{\text{BB}}) - k_{\text{BB}} \cdot \sigma(C, N_{\text{BB}})$$ + +where $N_{\text{BB}} = 20$, $k_{\text{BB}} = 2.0$, and $\sigma$ is population standard deviation. + +### 2. Keltner Channel Width + +$$\text{KC}_{\text{upper}} = \text{EMA}(C, N_{\text{KC}}) + k_{\text{KC}} \cdot \text{ATR}(N_{\text{KC}})$$ + +$$\text{KC}_{\text{lower}} = \text{EMA}(C, N_{\text{KC}}) - k_{\text{KC}} \cdot \text{ATR}(N_{\text{KC}})$$ + +where $N_{\text{KC}} = 20$, $k_{\text{KC}} = 1.5$. + +### 3. Squeeze Detection + +$$\text{SqueezeOn} = (\text{BB}_{\text{lower}} > \text{KC}_{\text{lower}}) \text{ and } (\text{BB}_{\text{upper}} < \text{KC}_{\text{upper}})$$ + +When BB fits entirely inside KC, the squeeze is active. The first bar where squeeze transitions from on to off ("squeeze fires") signals the breakout. + +### 4. Momentum Histogram + +$$\text{midline} = \frac{\text{Highest}(H, N) + \text{Lowest}(L, N)}{2}$$ + +$$\delta_t = C_t - \frac{\text{midline}_t + \text{SMA}(C, N)}{2}$$ + +$$\text{Momentum} = \text{LinReg}(\delta, N)$$ + +The linear regression extracts the trend component of the deviation, filtering noise. Momentum sign indicates direction; slope indicates acceleration. + +### 5. Momentum Color States + +| Color | Condition | +|:------|:----------| +| Cyan | Momentum > 0 and rising | +| Blue | Momentum > 0 and falling | +| Red | Momentum < 0 and falling | +| Yellow | Momentum < 0 and rising | + +### 6. Complexity + +| Metric | Value | +|:-------|:------| +| Time | O(1) per bar (incremental BB, KC, LinReg updates) | +| Space | O(N) for sliding window buffers (SMA, StdDev, ATR, high/low, LinReg) | +| Warmup | N bars (default 20) | + +## Mathematical Foundation + +### Parameters + +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| bbLength | int | 20 | > 1 | Bollinger Band period | +| bbMult | double | 2.0 | > 0 | BB standard deviation multiplier | +| kcLength | int | 20 | > 1 | Keltner Channel period | +| kcMult | double | 1.5 | > 0 | KC ATR multiplier | + +### Pseudo-code -### Momentum Histogram -Linear regression of price deviation from 20-period midline: ``` -midline = (Highest(20) + Lowest(20)) / 2 -momentum = LinReg(close - midline, 20) +TTM_SQUEEZE(bar, bbLen=20, bbMult=2.0, kcLen=20, kcMult=1.5): + + // Bollinger Bands + sma_val = SMA(close, bbLen) + stddev = StdDev(close, bbLen) + bb_upper = sma_val + bbMult * stddev + bb_lower = sma_val - bbMult * stddev + + // Keltner Channel + ema_val = EMA(close, kcLen) + atr_val = ATR(bar, kcLen) + kc_upper = ema_val + kcMult * atr_val + kc_lower = ema_val - kcMult * atr_val + + // Squeeze state + squeeze_on = (bb_lower > kc_lower) AND (bb_upper < kc_upper) + + // Momentum via linear regression of deviation + highest_high = Highest(high, bbLen) + lowest_low = Lowest(low, bbLen) + midline = (highest_high + lowest_low) / 2 + delta = close - (midline + sma_val) / 2 + momentum = LinReg(delta, bbLen) + + // Momentum direction + momentum_rising = momentum > prev_momentum + momentum_positive = momentum > 0 + + return (momentum, squeeze_on, momentum_rising, momentum_positive) ``` -### Color Coding -- **Cyan:** Momentum rising above zero -- **Blue:** Momentum falling but above zero -- **Red:** Momentum falling below zero -- **Yellow:** Momentum rising but below zero +### Squeeze-Fire Signal -## Default Parameters +The critical trading signal occurs on the transition bar: -| Parameter | Value | Description | -|:----------|:------|:------------| -| BB Length | 20 | Bollinger Band period | -| BB Mult | 2.0 | Bollinger Band standard deviation multiplier | -| KC Length | 20 | Keltner Channel period | -| KC Mult | 1.5 | Keltner Channel ATR multiplier | +$$\text{SqueezeFired}_t = \text{SqueezeOn}_{t-1} \text{ and } \neg\text{SqueezeOn}_t$$ -## Outputs +Combined with momentum direction, this yields entry signals: long when squeeze fires with positive rising momentum, short when squeeze fires with negative falling momentum. -| Output | Type | Description | -|:-------|:-----|:------------| -| Momentum | double | Linear regression momentum value | -| SqueezeOn | bool | True when BB inside KC | -| MomentumRising | bool | True when momentum increasing | -| MomentumPositive | bool | True when momentum > 0 | +## Resources -## Trading Signals - -1. **Squeeze Fired:** First bar where SqueezeOn transitions to false -2. **Long Entry:** Squeeze fires + momentum positive + momentum rising -3. **Short Entry:** Squeeze fires + momentum negative + momentum falling - -## Category - -**Dynamics** - Measures volatility compression and subsequent momentum release. - -## See Also - -- [BBS: Bollinger Band Squeeze](../../oscillators/bbs/Bbs.md) -- [BBW: Bollinger Band Width](../../volatility/bbw/Bbw.md) -- [KCHANNEL: Keltner Channel](../../channels/kchannel/Kchannel.md) -- [TTM: TTM Trend](../ttm/Ttm.md) +- Carter, J. (2005). *Mastering the Trade*. McGraw-Hill. +- Bollinger, J. (2001). *Bollinger on Bollinger Bands*. McGraw-Hill. +- Keltner, C. (1960). *How to Make Money in Commodities*. The Keltner Statistical Service. diff --git a/lib/dynamics/ttm_trend/TtmTrend.md b/lib/dynamics/ttm_trend/TtmTrend.md index 849406ac..58c6b1da 100644 --- a/lib/dynamics/ttm_trend/TtmTrend.md +++ b/lib/dynamics/ttm_trend/TtmTrend.md @@ -1,113 +1,98 @@ -# TTM_TREND: TTM Trend Indicator +# TTM_TREND: TTM Trend -> John Carter's TTM Trend - A fast EMA-based trend indicator with color-coded direction. +> "The simplest trend indicator is the one you actually follow." + +John Carter's TTM Trend uses a fast EMA (default period 6) applied to typical price (HLC/3) to determine short-term trend direction via slope sign. Output is a ternary trend state: +1 (bullish, EMA rising), -1 (bearish, EMA falling), or 0 (neutral, EMA unchanged). The indicator requires only 2 bars warmup, runs at O(1) per bar with O(1) space, and produces zero allocations in the hot path. ## Historical Context -John Carter developed the TTM (Trade the Markets) Trend indicator as a clean visual tool for identifying short-term trend direction. Popularized through his book *Mastering the Trade* and the thinkorswim platform, it provides a simple but effective way to see trend changes at a glance using color-coded lines. +John Carter developed the TTM (Trade the Markets) Trend indicator as a clean visual tool for identifying short-term trend direction, popularized through *Mastering the Trade* and the thinkorswim platform. Unlike complex multi-component trend systems, TTM Trend reduces trend detection to its minimum viable form: the slope of a fast exponential moving average. The very short default period (6) makes it responsive to recent price action, positioning it as a "first responder" trend filter meant to be combined with Carter's other TTM tools (Squeeze, Wave, LRC). The color-coded output (green/red/gray) provides at-a-glance trend assessment. -## Algorithm +## Architecture & Physics -### Core Calculation -``` -alpha = 2 / (period + 1) -EMA = alpha × source + (1 - alpha) × prevEMA -``` +### 1. Typical Price -Or equivalently: -``` -EMA = alpha × (source - EMA) + EMA -``` +$$\text{TP}_t = \frac{H_t + L_t + C_t}{3}$$ -### Trend Detection -``` -trend = sign(EMA - prevEMA) - +1 = bullish (EMA rising) - -1 = bearish (EMA falling) - 0 = neutral (EMA unchanged) -``` +Using typical price rather than close reduces susceptibility to closing-tick noise. -### Strength Measurement -``` -strength = |EMA - prevEMA| / prevEMA × 100% -``` +### 2. EMA Recursion -## Default Parameters +$$\alpha = \frac{2}{N + 1}$$ -| Parameter | Value | Description | -|:----------|:------|:------------| -| Period | 6 | EMA lookback period (very fast) | -| Source | HLC/3 | Typical price (High + Low + Close) / 3 | +$$\text{EMA}_t = \alpha \cdot \text{TP}_t + (1 - \alpha) \cdot \text{EMA}_{t-1}$$ -## Outputs +Or equivalently via FMA: -| Output | Type | Description | -|:-------|:-----|:------------| -| Value | double | Current EMA value | -| Trend | int | Trend direction: +1, -1, or 0 | -| Strength | double | Percent change between EMA values | -| IsHot | bool | True after warming up (2 bars) | +$$\text{EMA}_t = \text{FMA}(\alpha,\ \text{TP}_t - \text{EMA}_{t-1},\ \text{EMA}_{t-1})$$ -## Color Coding +### 3. Trend Classification -| Color | Condition | Meaning | -|:------|:----------|:--------| -| 🟢 Green | Trend > 0 | EMA rising (bullish) | -| 🔴 Red | Trend < 0 | EMA falling (bearish) | -| ⚫ Gray | Trend = 0 | EMA unchanged (neutral) | +$$\text{Trend}_t = \text{sign}(\text{EMA}_t - \text{EMA}_{t-1})$$ -## Performance +| Value | State | Color | +|:------|:------|:------| +| +1 | Bullish | Green | +| -1 | Bearish | Red | +| 0 | Neutral | Gray | + +### 4. Strength Measurement + +$$\text{Strength}_t = \frac{|\text{EMA}_t - \text{EMA}_{t-1}|}{\text{EMA}_{t-1}} \times 100\%$$ + +This percentage rate-of-change quantifies how aggressively the trend is moving. High strength values indicate strong conviction; near-zero values suggest potential reversal. + +### 5. Complexity | Metric | Value | |:-------|:------| -| Time complexity | O(1) per bar | -| Space complexity | O(1) | -| Warmup period | 2 bars | +| Time | O(1) per bar | +| Space | O(1) (one EMA state + one previous value) | +| Warmup | 2 bars | | Allocations | Zero in hot path | -## Usage Examples +## Mathematical Foundation -### Basic Usage -```csharp -var ttm = new TtmTrend(period: 6); +### Parameters -// Update with typical price -var result = ttm.Update(new TValue(time, typicalPrice)); +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| period | int | 6 | > 0 | EMA lookback period (very fast by default) | -// Or update with bar (uses HLC/3 automatically) -var result = ttm.Update(bar); +### Pseudo-code -// Access trend direction -if (ttm.Trend > 0) { /* bullish */ } -else if (ttm.Trend < 0) { /* bearish */ } +``` +TTM_TREND(bar, period=6): + + tp = (bar.High + bar.Low + bar.Close) / 3 + alpha = 2.0 / (period + 1) + + if count == 0: + ema_val = tp + else: + ema_val = FMA(alpha, tp - ema_val, ema_val) + + // Trend direction from slope sign + if count >= 1: + if ema_val > prev_ema: + trend = +1 + else if ema_val < prev_ema: + trend = -1 + else: + trend = 0 + + strength = abs(ema_val - prev_ema) / prev_ema * 100 + + prev_ema = ema_val + count += 1 + + return (ema_val, trend, strength) ``` -### Batch Processing -```csharp -var results = TtmTrend.Batch(barSeries, period: 6); -``` +### Period Selection -### With Indicator Instance -```csharp -var (results, indicator) = TtmTrend.Calculate(barSeries, period: 6); -bool isBullish = indicator.Trend > 0; -double strength = indicator.Strength; -``` +The default period of 6 makes TTM Trend extremely fast-reacting. The EMA half-life is approximately $\ln(2) / \ln(1 + 2/N) \approx 2.4$ bars for $N = 6$. This means the indicator responds within 2-3 bars of a price shift. Longer periods (12, 20) reduce whipsaws but delay detection. Carter's design intent was maximum responsiveness, with noise filtering delegated to companion indicators (Squeeze, Wave). -## Trading Applications +## Resources -1. **Trend Following**: Trade in the direction of the EMA color -2. **Trend Confirmation**: Use with other TTM indicators (Squeeze, Wave) -3. **Entry Timing**: Enter on color change with confirmation -4. **Exit Signal**: Exit when color changes against position - -## Category - -**Dynamics** - Measures trend direction and momentum using fast EMA smoothing. - -## See Also - -- [TTM_SQUEEZE: TTM Squeeze](../ttm_squeeze/TtmSqueeze.md) -- [TTM_WAVE: TTM Wave](../../oscillators/ttm_wave/TtmWave.md) -- [TTM_LRC: TTM Linear Regression Channel](../../channels/ttm_lrc/TtmLrc.md) -- [SUPER: SuperTrend](../super/Super.md) +- Carter, J. (2005). *Mastering the Trade*. McGraw-Hill. diff --git a/lib/dynamics/vortex/Vortex.md b/lib/dynamics/vortex/Vortex.md index 262b4670..fc181df3 100644 --- a/lib/dynamics/vortex/Vortex.md +++ b/lib/dynamics/vortex/Vortex.md @@ -1,112 +1,125 @@ -# Vortex Indicator +# VORTEX: Vortex Indicator -> When bulls and bears clash, the Vortex measures the violence. Two opposing forces, one decisive signal. +> "When bulls and bears clash, the Vortex measures the violence." -The Vortex Indicator captures the directional momentum of price movement by measuring positive and negative trend movements relative to true range. Unlike directional indicators that rely on smoothing, Vortex uses pure ratio analysis over a rolling period, making it responsive yet stable. +The Vortex Indicator measures upward and downward trend momentum by computing the ratio of positive and negative vortex movements to true range over a rolling window. VI+ captures the distance from current high to previous low (upward force); VI- captures the distance from current low to previous high (downward force). Both are normalized by summed true range, producing two lines that oscillate around 1.0. Crossovers signal trend changes. The implementation uses three ring buffers with running sums for O(1) streaming updates. ## Historical Context -Etienne Botes and Douglas Siepman introduced the Vortex Indicator in a 2010 article for *Technical Analysis of Stocks & Commodities*. Inspired by the natural vortex patterns in water flow and the work of Viktor Schauberger, they designed a dual-line indicator that captures the essence of trend direction through geometric relationships between consecutive bars. +Etienne Botes and Douglas Siepman introduced the Vortex Indicator in a January 2010 article for *Technical Analysis of Stocks and Commodities*. Inspired by Viktor Schauberger's observations of natural vortex patterns in water flow, they designed a dual-line indicator that captures directional momentum through geometric relationships between consecutive bars. The indicator is conceptually related to Wilder's Directional Movement (DM) system but uses a simpler construction: raw absolute distances rather than conditional directional selection. This makes Vortex more responsive to sharp reversals but more susceptible to gap noise. The typical period range is 14-21 bars, with 14 being the most common default. ## Architecture & Physics -The Vortex Indicator is built on a simple geometric insight: in a strong uptrend, the current high tends to be far from the previous low. In a strong downtrend, the current low tends to be far from the previous high. +### 1. Vortex Movement -1. **Vortex Movement (VM)**: Measures directional distance. - * **VM+**: Distance from current high to previous low (upward force). - * **VM-**: Distance from current low to previous high (downward force). +Positive vortex movement measures the "reach" of bullish activity: -2. **True Range (TR)**: The denominator that normalizes the movements. +$$VM^+_t = |H_t - L_{t-1}|$$ -3. **Vortex Index**: The ratio of summed VM to summed TR over $N$ periods. +Negative vortex movement measures the "reach" of bearish activity: -### The Physics of Trend +$$VM^-_t = |L_t - H_{t-1}|$$ -* **VI+ > VI-**: Bullish momentum dominates. The market is reaching up. -* **VI- > VI+**: Bearish momentum dominates. The market is reaching down. -* **VI+ ≈ VI-**: Equilibrium. No clear trend; potential consolidation or reversal. -* **Crossover**: When VI+ crosses VI-, a trend change is signaled. +In a strong uptrend, the current high is far from the previous low ($VM^+$ large). In a strong downtrend, the current low is far from the previous high ($VM^-$ large). + +### 2. True Range Normalization + +$$TR_t = \max(H_t - L_t,\ |H_t - C_{t-1}|,\ |L_t - C_{t-1}|)$$ + +True range serves as the denominator that normalizes vortex movements to the prevailing volatility regime. + +### 3. Vortex Indicators + +$$VI^+_t = \frac{\sum_{i=1}^{N} VM^+_{t-i+1}}{\sum_{i=1}^{N} TR_{t-i+1}}$$ + +$$VI^-_t = \frac{\sum_{i=1}^{N} VM^-_{t-i+1}}{\sum_{i=1}^{N} TR_{t-i+1}}$$ + +The summation window creates period-based smoothing without introducing the lag of recursive (IIR) filters. + +### 4. Running Sum Implementation + +Three ring buffers store $VM^+$, $VM^-$, and $TR$ values. Running sums update incrementally: + +``` +sum_vm_plus += new_vm_plus - oldest_vm_plus +sum_vm_minus += new_vm_minus - oldest_vm_minus +sum_tr += new_tr - oldest_tr +``` + +This yields O(1) per-bar updates after the initial warmup fill. + +### 5. Complexity + +| Metric | Value | +|:-------|:------| +| Time | O(1) per bar (running sum updates) | +| Space | O(N) (three ring buffers of size N) | +| Warmup | N bars | +| Allocations | Zero in hot path | ## Mathematical Foundation -The calculations are straightforward geometric relationships. +### Parameters -### Vortex Movement +| Parameter | Type | Default | Constraint | Description | +|:----------|:-----|:--------|:-----------|:------------| +| period | int | 14 | > 0 | Rolling window for VM and TR sums | -$$ VM^+ = |High_t - Low_{t-1}| $$ +### Pseudo-code -$$ VM^- = |Low_t - High_{t-1}| $$ +``` +VORTEX(bar, period=14): -### True Range + // Vortex movements (require previous bar) + vm_plus = abs(bar.High - prev_bar.Low) + vm_minus = abs(bar.Low - prev_bar.High) -$$ TR = \max(High_t - Low_t, |High_t - Close_{t-1}|, |Low_t - Close_{t-1}|) $$ + // True range + tr = max(bar.High - bar.Low, + abs(bar.High - prev_bar.Close), + abs(bar.Low - prev_bar.Close)) -### Vortex Indicator + // Ring buffer updates with running sums + if buffer_full: + sum_vm_plus -= vm_plus_buffer.oldest + sum_vm_minus -= vm_minus_buffer.oldest + sum_tr -= tr_buffer.oldest -$$ VI^+ = \frac{\sum_{i=1}^{N} VM^+_i}{\sum_{i=1}^{N} TR_i} $$ + vm_plus_buffer.add(vm_plus) + vm_minus_buffer.add(vm_minus) + tr_buffer.add(tr) -$$ VI^- = \frac{\sum_{i=1}^{N} VM^-_i}{\sum_{i=1}^{N} TR_i} $$ + sum_vm_plus += vm_plus + sum_vm_minus += vm_minus + sum_tr += tr -## Performance Profile + // Vortex indicators + if sum_tr > 0: + vi_plus = sum_vm_plus / sum_tr + vi_minus = sum_vm_minus / sum_tr + else: + vi_plus = 0 + vi_minus = 0 -The implementation uses running sums for O(1) updates after the initial warmup period. + return (vi_plus, vi_minus) +``` -### Zero-Allocation Design - -Three circular buffers maintain the VM+, VM-, and TR values. Running sums are updated incrementally: -- Add new value -- Subtract oldest value when buffer is full -- Compute ratio - -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | 8ns | 8ns / bar after warmup. | -| **Allocations** | 0 | Hot path is allocation-free. | -| **Complexity** | O(1) | Constant time updates with running sums. | -| **Accuracy** | 10/10 | Matches Skender reference implementation. | -| **Timeliness** | 9/10 | Responsive to trend changes. | -| **Overshoot** | 3/10 | Values typically 0.5-1.5, rarely extreme. | -| **Smoothness** | 7/10 | Period-based smoothing via summation. | - -## Interpretation - -### Crossover Signals - -* **Bullish Crossover**: VI+ crosses above VI-. Indicates potential uptrend beginning. -* **Bearish Crossover**: VI- crosses above VI+. Indicates potential downtrend beginning. - -### Reference Line +### Reference Line at 1.0 The value 1.0 serves as a natural reference: -- **VI+ > 1**: Strong upward pressure exceeds average true range. -- **VI- > 1**: Strong downward pressure exceeds average true range. -- **Both < 1**: Subdued market activity. -### Threshold Strategy +- $VI^+ > 1$: Upward reach exceeds average true range (strong bullish pressure) +- $VI^- > 1$: Downward reach exceeds average true range (strong bearish pressure) +- Both < 1: Subdued directional activity -Some practitioners use thresholds for confirmation: -- **Strong Trend**: VI+ > 1.1 and VI+ > VI- (bullish) or VI- > 1.1 and VI- > VI+ (bearish). -- **Weak/No Trend**: Both VI+ and VI- below 0.9 or very close to each other. +### Crossover Signal -## Validation +$$\text{Bullish} = VI^+ > VI^- \quad (\text{and } VI^+_{\text{prev}} \leq VI^-_{\text{prev}})$$ -Validation is performed against industry-standard libraries. +$$\text{Bearish} = VI^- > VI^+ \quad (\text{and } VI^-_{\text{prev}} \leq VI^+_{\text{prev}})$$ -| Library | Status | Notes | -| :--- | :--- | :--- | -| **QuanTAlib** | ✅ | Validated. | -| **Skender** | ✅ | Matches `GetVortex` (Pvi, Nvi). | -| **TA-Lib** | N/A | Not implemented in TA-Lib. | -| **Tulip** | N/A | Not implemented in Tulip. | +Period selection: too short (< 7) creates noise; too long (> 28) introduces excessive lag. The 14-21 range balances responsiveness and stability. -### Common Pitfalls +## Resources -* **Period Selection**: Too short a period (< 7) creates noise; too long (> 28) creates excessive lag. 14-21 is typical. -* **False Crossovers**: In choppy markets, VI+ and VI- oscillate around each other, creating whipsaws. Use with trend filters. -* **Single Line Trading**: Don't use VI+ or VI- in isolation. The relationship between them is the signal. -* **Ignoring True Range**: Low TR periods (consolidation) can cause extreme VI values. Always consider the market context. - -## References - -* Botes, E., & Siepman, D. (2010). "The Vortex Indicator." *Technical Analysis of Stocks & Commodities*, January 2010. -* Wikipedia: [Vortex Indicator](https://en.wikipedia.org/wiki/Vortex_indicator) +- Botes, E. & Siepman, D. (2010). "The Vortex Indicator." *Technical Analysis of Stocks and Commodities*, January 2010. diff --git a/lib/filters/medf/Medf.md b/lib/filters/medf/Medf.md new file mode 100644 index 00000000..25dfcf28 --- /dev/null +++ b/lib/filters/medf/Medf.md @@ -0,0 +1,71 @@ +# MEDF: Moving Median Filter + +> "The median is the only filter that can remove a spike without flinching. SMA smears it, EMA decays it over time, but the median simply ignores it. For impulse noise in financial data — bad ticks, flash crashes, fat-finger errors — the median is the correct tool." + +MEDF outputs the median of the most recent $N$ values in a sliding window, providing a nonlinear filter that is robust to impulse noise and outliers while preserving edges and steps better than any linear filter. Unlike SMA or EMA, which spread the effect of a single outlier across the entire window (SMA) or decay it exponentially (EMA), the median completely rejects outliers that do not constitute a majority of the window. This makes MEDF the filter of choice for cleaning price data contaminated with bad ticks or anomalous prints. + +## Historical Context + +The running median was introduced by John Tukey in *Exploratory Data Analysis* (1977) as a fundamental tool for resistant smoothing. Tukey recognized that the arithmetic mean (and by extension, linear filters like SMA and EMA) is highly sensitive to outliers: a single extreme value can shift the mean arbitrarily far from the "typical" value. The median, being the 50th percentile, requires more than $N/2$ values to be corrupted before it fails. + +In signal processing, median filters gained prominence in image processing (Huang, Yang, and Tang, 1979), where they excel at removing "salt and pepper" noise while preserving sharp edges. The same property applies to financial time series: price levels often exhibit step-like behavior (e.g., after a gap or news event), and the median preserves these steps while linear filters blur them. + +The computational cost of a naive median filter is $O(N \log N)$ per bar (sort the window, extract the middle). More efficient algorithms exist: the rolling median via two heaps achieves $O(\log N)$ per bar, and Huang's histogram method achieves $O(1)$ amortized for integer-valued data. The Pine implementation uses a sort-based approach. + +## Architecture & Physics + +### 1. Circular Buffer + +A ring buffer of size $N$ stores the most recent $N$ values. + +### 2. Window Extraction and Sort + +Each bar, the buffer contents are copied to a temporary array and sorted. This is $O(N \log N)$ via array sort. + +### 3. Median Extraction + +For odd $N$: the middle element is the median. For even $N$: the average of the two middle elements. + +## Mathematical Foundation + +The median of a set $\{x_1, x_2, \ldots, x_N\}$ is: + +$$ +\text{median}(X) = \begin{cases} X_{[(N+1)/2]} & N \text{ odd} \\ \frac{X_{[N/2]} + X_{[N/2+1]}}{2} & N \text{ even} \end{cases} +$$ + +where $X_{[k]}$ denotes the $k$-th order statistic (sorted value). + +**Key properties:** + +| Property | Median | SMA | EMA | +| :--- | :---: | :---: | :---: | +| Outlier rejection | Complete (if $< N/2$ outliers) | None | Partial (decays) | +| Edge preservation | Yes | Blurs edges | Blurs edges | +| Linearity | Nonlinear | Linear | Linear | +| Frequency response | No closed form | Sinc | Exponential decay | +| Idempotent | No | No | No | + +**Breakdown point:** The median has a 50% breakdown point, meaning up to $\lfloor N/2 \rfloor$ values can be arbitrarily corrupted without affecting the output (assuming the remaining values are within the signal range). This is the highest possible breakdown point for any estimator. + +**Default parameters:** `period = 5`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +buffer[head] = src +head = (head + 1) % period +count = min(count + 1, period) + +sorted = sort(buffer[0..count-1]) +if count is odd: + return sorted[count / 2] +else: + return (sorted[count/2 - 1] + sorted[count/2]) / 2 +``` + +## Resources + +- Tukey, J.W. (1977). *Exploratory Data Analysis*. Addison-Wesley. Chapter 7: Resistant Smoothing. +- Huang, T.S., Yang, G.J., & Tang, G.Y. (1979). "A Fast Two-Dimensional Median Filtering Algorithm." *IEEE Trans. Acoust., Speech, Signal Process.*, 27(1), 13-18. +- Yin, L. et al. (1996). "Weighted Median Filters: A Tutorial." *IEEE Trans. Circuits and Systems II*, 43(3), 157-192. diff --git a/lib/filters/modf/Modf.md b/lib/filters/modf/Modf.md new file mode 100644 index 00000000..56b3b2a0 --- /dev/null +++ b/lib/filters/modf/Modf.md @@ -0,0 +1,114 @@ +# MODF: Modular Filter + +> "alexgrover designed a filter with two paths — one tracks uptrends, one tracks downtrends — and a state machine that picks between them. Add a beta knob for aggression and an optional feedback loop, and you get one of the most versatile adaptive filters on TradingView." + +MODF is a dual-path adaptive filter that maintains separate upper and lower EMA bands with conditional state selection. The upper band snaps up to price when price exceeds it (tracking rallies), while the lower band snaps down when price drops below it (tracking selloffs). An oscillator state variable determines which band is active, and a beta parameter controls the blend between filter mode (smooth tracking) and trailing-stop mode (step-like following). An optional feedback loop blends the filter's output back into its input for additional smoothing. Developed by alexgrover (CPO at LuxAlgo). + +## Historical Context + +MODF was published by alexgrover on TradingView as a novel approach to adaptive filtering that combines elements of trailing stops, envelope filters, and state machines. The "modular" name refers to the composable design: the beta parameter morphs the filter continuously between two behaviors (smooth average at $\beta = 1$ and trailing stop at $\beta = 0$), and the feedback option adds a third dimension of control. + +The dual-band architecture is reminiscent of Keltner channels and Donchian channels, where upper and lower bands track extremes. MODF's innovation is the conditional snap-to-price behavior: the upper band only updates via EMA when price is below it, but jumps instantly to price when price exceeds it. This creates a band that ratchets upward during trends and smoothly decays during pullbacks, the opposite of a trailing stop but with the same structural mechanism. + +The state machine ($os = 1$ when price touches the upper band, $os = 0$ when it touches the lower band) provides regime detection without any lookback or explicit trend measurement. The filter naturally enters "bullish" (upper band active) or "bearish" (lower band active) mode based purely on which extreme price has most recently visited. + +## Architecture & Physics + +### 1. Dual EMA Bands + +- **Upper band ($b$):** EMA of input, but snaps up to input when input exceeds EMA. +- **Lower band ($c$):** EMA of input, but snaps down to input when input falls below EMA. + +### 2. Oscillator State + +Binary state $os$: 1 if price last touched the upper band, 0 if it last touched the lower band. + +### 3. Beta-Weighted Combination + +$$ +\text{upper\_mix} = \beta \cdot b + (1 - \beta) \cdot c +$$ + +$$ +\text{lower\_mix} = \beta \cdot c + (1 - \beta) \cdot b +$$ + +### 4. State-Selected Output + +$$ +\text{MODF} = os \cdot \text{upper\_mix} + (1 - os) \cdot \text{lower\_mix} +$$ + +### 5. Optional Feedback + +When enabled, the input becomes a blend of source and previous output: + +$$ +a = w \cdot \text{source} + (1 - w) \cdot \text{MODF}_{t-1} +$$ + +## Mathematical Foundation + +With $\alpha = 2/(N+1)$: + +**Band updates:** + +$$ +b_t = \begin{cases} a_t & \text{if } a_t > \alpha \cdot a_t + (1-\alpha) \cdot b_{t-1} \\ \alpha \cdot a_t + (1-\alpha) \cdot b_{t-1} & \text{otherwise} \end{cases} +$$ + +$$ +c_t = \begin{cases} a_t & \text{if } a_t < \alpha \cdot a_t + (1-\alpha) \cdot c_{t-1} \\ \alpha \cdot a_t + (1-\alpha) \cdot c_{t-1} & \text{otherwise} \end{cases} +$$ + +**State transition:** + +$$ +os_t = \begin{cases} 1 & \text{if } a_t = b_t \\ 0 & \text{if } a_t = c_t \\ os_{t-1} & \text{otherwise} \end{cases} +$$ + +**Output:** + +$$ +\text{MODF}_t = os_t \cdot [\beta b_t + (1-\beta) c_t] + (1-os_t) \cdot [\beta c_t + (1-\beta) b_t] +$$ + +**Beta interpretation:** + +| $\beta$ | Behavior | +| :---: | :--- | +| 1.0 | Pure filter: tracks active band smoothly | +| 0.5 | Balanced: midpoint of both bands | +| 0.0 | Pure trailing stop: follows inactive band | + +**Default parameters:** `period = 14`, `beta = 0.8`, `feedback = false`, `fbWeight = 0.5`. + +**Pseudo-code (streaming):** + +``` +alpha = 2/(period+1) + +// Optional feedback blend +a = feedback ? fbWeight*src + (1-fbWeight)*ts : src + +// Upper band (snaps up) +ema_b = alpha*a + (1-alpha)*b +b = (a > ema_b) ? a : ema_b + +// Lower band (snaps down) +ema_c = alpha*a + (1-alpha)*c +c = (a < ema_c) ? a : ema_c + +// State machine +os = (a == b) ? 1 : (a == c) ? 0 : os + +// Beta-weighted output +upper = beta*b + (1-beta)*c +lower = beta*c + (1-beta)*b +ts = os*upper + (1-os)*lower +``` + +## Resources + +- alexgrover (LuxAlgo). "Modular Filter" indicator. Published on TradingView. +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 6: Adaptive Filters (general framework). diff --git a/lib/filters/nw/Nw.md b/lib/filters/nw/Nw.md new file mode 100644 index 00000000..ba3deaf1 --- /dev/null +++ b/lib/filters/nw/Nw.md @@ -0,0 +1,85 @@ +# NW: Nadaraya-Watson Kernel Regression + +> "Nadaraya and Watson independently discovered the same thing in 1964: weight each observation by how close it is, normalize, and average. Fifty years later, it became one of the most popular nonparametric smoothers on TradingView. The math did not change; only our ability to compute it in real time." + +NW computes the Nadaraya-Watson kernel regression estimator with a Gaussian kernel, producing a nonparametric smooth of the price series. For each bar, every observation in the lookback window is weighted by a Gaussian function of its temporal distance, with the bandwidth parameter $h$ controlling the effective smoothing radius. Small $h$ tracks price tightly (low bias, high variance); large $h$ smooths heavily (high bias, low variance). This implementation is non-repainting (backward-looking only). + +## Historical Context + +Elizbar Nadaraya (1964) and Geoffrey Watson (1964) independently published the same kernel regression estimator, now universally known as the Nadaraya-Watson estimator. It is the foundational method of nonparametric regression: given paired observations $(x_i, y_i)$, estimate $\hat{y}(x) = \sum w_i y_i / \sum w_i$ where $w_i = K((x - x_i)/h)$ and $K$ is a kernel function. + +In the time-series context, the $x$-values are bar indices and the kernel reduces to a temporal weighting function. The Gaussian kernel $K(u) = e^{-u^2/2}$ produces smooth, infinitely differentiable output and has the theoretical property of minimizing the asymptotic mean integrated squared error (MISE) under certain regularity conditions. + +The NW estimator became popular on TradingView around 2022, when several prominent indicator authors (including LuxAlgo) published implementations. Most TradingView versions use centered or forward-looking kernels that repaint as new bars arrive. This implementation is strictly backward-looking (endpoint mode), meaning the estimate at bar $t$ uses only bars $t, t-1, \ldots, t-N+1$. The nonrepainting property is essential for backtesting validity. + +The bandwidth $h$ plays the role that "period" plays in traditional MAs, but with different semantics: $h$ controls the width of the Gaussian bell, and bars beyond $\sim 3h$ contribute negligible weight regardless of the lookback window size. + +## Architecture & Physics + +### 1. Gaussian Kernel Weights + +For each bar $i$ in the lookback window (where $i = 0$ is newest): + +$$ +w_i = \exp\!\left(-\frac{i^2}{2h^2}\right) +$$ + +### 2. Normalized Weighted Average + +$$ +\text{NW}_t = \frac{\sum_{i=0}^{N-1} w_i \cdot x_{t-i}}{\sum_{i=0}^{N-1} w_i} +$$ + +### 3. Bandwidth-Period Relationship + +Observations beyond $3h$ bars of lag contribute $< 1.1\%$ of peak weight. Setting $N \geq 4h$ captures $> 99.97\%$ of the kernel mass. + +## Mathematical Foundation + +The Nadaraya-Watson estimator for time series: + +$$ +\hat{m}(t) = \frac{\sum_{i=0}^{N-1} K_h(i) \cdot x_{t-i}}{\sum_{i=0}^{N-1} K_h(i)} +$$ + +with Gaussian kernel: + +$$ +K_h(u) = \frac{1}{\sqrt{2\pi}h}\exp\!\left(-\frac{u^2}{2h^2}\right) +$$ + +(The normalizing constant $1/(\sqrt{2\pi}h)$ cancels in the ratio and is omitted in practice.) + +**Bias-variance trade-off:** + +| $h$ (relative to $N$) | Bias | Variance | Behavior | +| :---: | :---: | :---: | :--- | +| $h \ll N$ | Low | High | Tracks noise, overfits | +| $h \approx N/4$ | Balanced | Balanced | Good default | +| $h \gg N$ | High | Low | Over-smooths, flat | + +**Effective number of observations:** The kernel entropy $N_{\text{eff}} = (\sum w_i)^2 / \sum w_i^2 \approx \sqrt{2\pi} \cdot h$ for the Gaussian. + +**Group delay:** Approximately $h \cdot \sqrt{\pi/2} \approx 1.25h$ for the Gaussian kernel. + +**Default parameters:** `period = 64`, `bandwidth = 8.0`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +h2x2 = 2 * bandwidth² +num = 0; den = 0 + +for i = 0 to min(bar_count, period) - 1: + w = exp(-i² / h2x2) + num += w * source[i] + den += w + +return den > 0 ? num/den : source +``` + +## Resources + +- Nadaraya, E.A. (1964). "On Estimating Regression." *Theory of Probability and Its Applications*, 9(1), 141-142. +- Watson, G.S. (1964). "Smooth Regression Analysis." *Sankhyā: The Indian Journal of Statistics*, Series A, 26(4), 359-372. +- Wand, M.P. & Jones, M.C. (1995). *Kernel Smoothing*. Chapman & Hall/CRC. Chapter 2: The Density Estimator. diff --git a/lib/filters/reflex/Reflex.md b/lib/filters/reflex/Reflex.md new file mode 100644 index 00000000..ebb19634 --- /dev/null +++ b/lib/filters/reflex/Reflex.md @@ -0,0 +1,103 @@ +# REFLEX: Ehlers Reflex Indicator + +> "John Ehlers measured how much a filtered price deviates from its own linear extrapolation. The result is a zero-lag oscillator that catches reversals before they happen, because the deviation is largest precisely when the trend is bending." + +REFLEX is a zero-lag oscillator that measures the reversal tendency of price by comparing a Super-Smoother-filtered price against a linear extrapolation from $N$ bars ago. The filter computes the slope of the filtered series over the lookback window, projects a straight line, and sums the deviations of the actual filtered values from this projected line. The sum is normalized by an exponential RMS estimate to produce values in roughly $\pm \sigma$ scale. Values above 0 indicate uptrend, below 0 indicate downtrend; crossovers signal potential reversals. + +## Historical Context + +John F. Ehlers published REFLEX in "Reflex: A New Zero-Lag Indicator" (*Technical Analysis of Stocks & Commodities*, February 2020). Ehlers' motivation was to create a cycle-based oscillator that responds to trend reversals with zero lag, unlike traditional oscillators (RSI, stochastic) that inherently lag price due to their smoothing components. + +The core idea is that linear extrapolation of a smoothed series will overshoot (undershoot) when the trend is decelerating (accelerating). By measuring the sum of these overshoots, REFLEX detects curvature changes — exactly the inflection points where trends reverse. This is mathematically similar to measuring the second derivative (acceleration), but the linear-extrapolation approach is more numerically stable and naturally adapts to the trend's own slope. + +The 2-pole Super Smoother pre-filter (at half the specified period) removes high-frequency noise before the reflex computation, preventing false signals from bar-to-bar price noise. The exponential RMS normalization ensures the output has consistent scale regardless of the instrument's volatility. + +## Architecture & Physics + +### 1. Super Smoother Pre-Filter + +A 2-pole IIR low-pass filter with cutoff at half the specified period: + +$$ +\text{Filt} = c_1 \cdot \frac{x_t + x_{t-1}}{2} + c_2 \cdot \text{Filt}_{t-1} + c_3 \cdot \text{Filt}_{t-2} +$$ + +where $a_1 = e^{-\sqrt{2}\pi / (N/2)}$, $c_2 = 2a_1\cos(\sqrt{2}\pi/(N/2))$, $c_3 = -a_1^2$, $c_1 = 1-c_2-c_3$. + +### 2. Linear Extrapolation Slope + +$$ +\text{slope} = \frac{\text{Filt}_{t-N} - \text{Filt}_t}{N} +$$ + +### 3. Deviation Summation + +$$ +\text{Sum} = \frac{1}{N}\sum_{i=1}^{N}\left[(\text{Filt}_t + i \cdot \text{slope}) - \text{Filt}_{t-i}\right] +$$ + +### 4. Exponential RMS Normalization + +$$ +\text{MS} = 0.04 \cdot \text{Sum}^2 + 0.96 \cdot \text{MS}_{t-1} +$$ + +$$ +\text{REFLEX} = \frac{\text{Sum}}{\sqrt{\text{MS}}} +$$ + +## Mathematical Foundation + +**Super Smoother coefficients (half-period cutoff):** + +$$ +a_1 = e^{-\sqrt{2}\pi / (N/2)}, \quad c_2 = 2a_1\cos\!\left(\frac{\sqrt{2}\pi}{N/2}\right), \quad c_3 = -a_1^2, \quad c_1 = 1-c_2-c_3 +$$ + +**Deviation from linear trend:** + +$$ +D_i = (\text{Filt}_t + i \cdot \text{slope}) - \text{Filt}_{t-i}, \quad i = 1, \ldots, N +$$ + +**Mean deviation:** + +$$ +\text{Sum} = \frac{1}{N}\sum_{i=1}^{N} D_i +$$ + +**Interpretation:** + +- $\text{Sum} > 0$: filtered price is above its linear extrapolation (upward curvature, potential uptrend) +- $\text{Sum} < 0$: filtered price is below its linear extrapolation (downward curvature, potential downtrend) +- Zero crossings signal inflection points (trend reversals) + +**Default parameters:** `period = 20`, `minPeriod = 2`. Output is an oscillator (not overlay). + +**Pseudo-code (streaming):** + +``` +// Super Smoother (2-pole IIR) +filt = c1*(price + price[1])/2 + c2*filt[1] + c3*filt[2] + +// Store in circular buffer +buf[head] = filt + +// Slope from N-bar-ago to current +slope = (filt_lag_N - filt) / N + +// Sum deviations from linear extrapolation +sum = 0 +for i = 1 to N: + sum += (filt + i*slope) - filt[i] +sum /= N + +// Normalize by exponential RMS +ms = 0.04 * sum² + 0.96 * ms[1] +return ms > 0 ? sum / sqrt(ms) : 0 +``` + +## Resources + +- Ehlers, J.F. (2020). "Reflex: A New Zero-Lag Indicator." *Technical Analysis of Stocks & Commodities*, February 2020. +- Ehlers, J.F. (2013). *Cycle Analytics for Traders*. Wiley. Chapter 3: Super Smoothers. diff --git a/lib/filters/rmed/Rmed.md b/lib/filters/rmed/Rmed.md new file mode 100644 index 00000000..e97d12b3 --- /dev/null +++ b/lib/filters/rmed/Rmed.md @@ -0,0 +1,92 @@ +# RMED: Ehlers Recursive Median Filter + +> "John Ehlers combined two tools that rarely meet: the median (nonlinear, spike-resistant) and the EMA (smooth, recursive). The median kills the spikes, the EMA smooths the survivors. Together they produce a filter that is both resistant and smooth." + +RMED applies exponential smoothing to a 5-bar running median, creating a nonlinear IIR filter that rejects impulsive spike noise while providing smooth recursive tracking. The median component eliminates outliers that would corrupt any linear filter, while the EMA provides the recursive continuity that a pure median lacks. The EMA constant $\alpha$ is derived from Ehlers' cycle-period formula, connecting the smoothing rate to the dominant cycle length of the data. + +## Historical Context + +John F. Ehlers published "Recursive Median Filters" in *Technical Analysis of Stocks & Commodities* (March 2018). The article addressed a fundamental limitation of linear filters: no matter how sophisticated an EMA, DEMA, or Butterworth design is, a single bad tick or flash-crash spike will corrupt the output for its entire impulse response duration. + +Median filters solve this problem completely for impulse noise, but traditional median filters are non-recursive (pure FIR), which means they have no "memory" between bars — each output depends only on the current window, creating a choppy, step-like output. Ehlers' innovation was to follow the median with an exponential average, combining the spike rejection of the median with the smooth continuity of the EMA. + +The 5-bar median window is a design choice: it can reject up to 2 simultaneous bad ticks in a row (the breakdown point is $\lfloor 5/2 \rfloor = 2$), while having minimal lag (centered at bar 2 of 5). Wider median windows would reject more spikes but add more lag. + +The EMA constant $\alpha = (\cos\theta + \sin\theta - 1)/\cos\theta$ where $\theta = 2\pi/P$ is Ehlers' standard cycle-period-to-smoothing mapping, which produces a critically damped response at the specified period. + +## Architecture & Physics + +### 1. Five-Bar Median + +A circular buffer of 5 values is sorted each bar; the middle element is extracted as the median. + +### 2. Ehlers EMA Constant + +$$ +\alpha = \frac{\cos(2\pi/P) + \sin(2\pi/P) - 1}{\cos(2\pi/P)} +$$ + +Clamped to $[0, 1]$ for numerical safety. + +### 3. Recursive Smoothing + +$$ +\text{RMED}_t = \alpha \cdot \text{Median5}_t + (1 - \alpha) \cdot \text{RMED}_{t-1} +$$ + +## Mathematical Foundation + +**Five-bar median:** + +$$ +\text{Med5}_t = \text{median}(x_t, x_{t-1}, x_{t-2}, x_{t-3}, x_{t-4}) +$$ + +**Ehlers smoothing constant (from cycle period $P$):** + +$$ +\theta = \frac{2\pi}{P}, \quad \alpha = \frac{\cos\theta + \sin\theta - 1}{\cos\theta} +$$ + +For common periods: + +| $P$ | $\alpha$ | Equivalent EMA period | +| :---: | :---: | :---: | +| 5 | 0.72 | ~3.6 | +| 10 | 0.38 | ~4.3 | +| 20 | 0.20 | ~9.0 | +| 40 | 0.10 | ~19 | + +**Recursive filter:** + +$$ +\text{RMED}_t = \alpha \cdot \text{Med5}_t + (1-\alpha) \cdot \text{RMED}_{t-1} +$$ + +**Spike rejection:** A single outlier in the 5-bar window is always rejected by the median (it cannot be the middle value). Two consecutive outliers are also rejected. Three or more consecutive outliers breach the breakdown point. + +**Default parameters:** `period = 12`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +// Ehlers alpha from cycle period +angle = 2π / period +alpha = (cos(angle) + sin(angle) - 1) / cos(angle) +alpha = clamp(alpha, 0, 1) + +// 5-bar median +buf[head] = price +head = (head + 1) % 5 +sorted = sort(buf) +med5 = sorted[2] + +// Recursive EMA of median +rm = alpha * med5 + (1-alpha) * rm +``` + +## Resources + +- Ehlers, J.F. (2018). "Recursive Median Filters." *Technical Analysis of Stocks & Commodities*, March 2018. +- Tukey, J.W. (1977). *Exploratory Data Analysis*. Addison-Wesley. Chapter 7: Resistant Smoothing. +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 3: Smoothing Constants from Cycle Period. diff --git a/lib/filters/sak/Sak.md b/lib/filters/sak/Sak.md new file mode 100644 index 00000000..844a418c --- /dev/null +++ b/lib/filters/sak/Sak.md @@ -0,0 +1,92 @@ +# SAK: Swiss Army Knife Indicator + +> "John Ehlers unified nine filter types into one second-order IIR framework. Change the coefficients and you get EMA, SMA, Gaussian, Butterworth, smoother, high-pass, 2-pole high-pass, band-pass, or band-stop. One formula to implement them all." + +SAK is a unified second-order IIR filter framework where five coefficient sets ($c_0$, $b_0$, $b_1$, $b_2$, $a_1$, $a_2$) determine the filter type. The general form $\text{Filt} = c_0(b_0 x + b_1 x_{t-1} + b_2 x_{t-2}) + a_1 \text{Filt}_{t-1} + a_2 \text{Filt}_{t-2}$ can instantiate nine different filters by selecting the appropriate coefficient derivation. Published by John Ehlers in "Swiss Army Knife Indicator" (*Technical Analysis of Stocks & Commodities*, January 2006). + +## Historical Context + +John F. Ehlers published the Swiss Army Knife indicator in TASC (January 2006), motivated by the observation that most common technical analysis filters (EMA, SMA, Gaussian, Butterworth, high-pass, band-pass) share the same second-order difference equation structure. Only the coefficients differ. By parameterizing the coefficient derivation, a single implementation can serve as any of nine filter types. + +This unification has both practical and theoretical value. Practically, it reduces code duplication: one function with a mode selector replaces nine separate implementations. Theoretically, it reveals the deep connection between seemingly different filters: they are all members of the same family of second-order IIR filters, differing only in their pole and zero placements in the z-plane. + +Ehlers derives the coefficients from the cycle period $P$ using trigonometric formulas that place poles/zeros at specific frequencies, ensuring each filter type has its cutoff or center frequency aligned with the user-specified period. + +## Architecture & Physics + +### 1. Unified Second-Order IIR + +$$ +\text{Filt}_t = c_0(b_0 x_t + b_1 x_{t-1} + b_2 x_{t-2}) + a_1 \text{Filt}_{t-1} + a_2 \text{Filt}_{t-2} +$$ + +### 2. Coefficient Derivation by Mode + +Three smoothing parameters are computed from the period: +- **EMA/HP/SMA/Smooth modes:** $\alpha = (\cos\theta + \sin\theta - 1)/\cos\theta$, $\theta = 2\pi/P$ +- **Gauss/Butter/2PHP modes:** $\beta = 2.415(1 - \cos\theta)$, $\alpha = -\beta + \sqrt{\beta^2 + 2\beta}$ +- **BP/BS modes:** $\gamma = 1/\cos(2\pi\delta/P)$, $\beta = \cos(2\pi/P)$, $\alpha = \gamma - \sqrt{\gamma^2 - 1}$ + +### 3. Nine Filter Types + +| Mode | Type | Overlay? | +| :--- | :--- | :---: | +| EMA | Low-pass (1-pole) | Yes | +| SMA | Low-pass (running sum) | Yes | +| Gauss | Low-pass (2-pole Gaussian) | Yes | +| Butter | Low-pass (2-pole Butterworth) | Yes | +| Smooth | Low-pass (FIR-like) | Yes | +| HP | High-pass (1-pole) | No | +| 2PHP | High-pass (2-pole) | No | +| BP | Band-pass | No | +| BS | Band-stop (notch) | No | + +## Mathematical Foundation + +**Unified transfer function (z-domain):** + +$$ +H(z) = \frac{c_0(b_0 + b_1 z^{-1} + b_2 z^{-2})}{1 - a_1 z^{-1} - a_2 z^{-2}} +$$ + +**Coefficient table:** + +| Mode | $c_0$ | $b_0$ | $b_1$ | $b_2$ | $a_1$ | $a_2$ | +| :--- | :--- | :---: | :---: | :---: | :--- | :--- | +| EMA | 1 | $\alpha$ | 0 | 0 | $1-\alpha$ | 0 | +| SMA | $1/n$ | 1 | 0 | 0 | 1 | 0 | +| Gauss | $\alpha^2$ | 1 | 0 | 0 | $2(1-\alpha)$ | $-(1-\alpha)^2$ | +| Butter | $\alpha^2/4$ | 1 | 2 | 1 | $2(1-\alpha)$ | $-(1-\alpha)^2$ | +| Smooth | $\alpha^2/4$ | 1 | 2 | 1 | 0 | 0 | +| HP | $1-\alpha/2$ | 1 | $-1$ | 0 | $1-\alpha$ | 0 | +| 2PHP | $(1-\alpha/2)^2$ | 1 | $-2$ | 1 | $2(1-\alpha)$ | $-(1-\alpha)^2$ | +| BP | $(1-\alpha)/2$ | 1 | 0 | $-1$ | $\beta(1+\alpha)$ | $-\alpha$ | +| BS | $(1+\alpha)/2$ | 1 | $-2\beta$ | 1 | $\beta(1+\alpha)$ | $-\alpha$ | + +**SMA special path:** Uses $\text{Filt} = \frac{1}{n}x_t + \text{Filt}_{t-1} - \frac{1}{n}x_{t-n}$ (running sum). + +**Stability:** All modes produce stable filters for $P > 2$. The Gauss and Butter modes have conjugate poles inside the unit circle; BP/BS modes have poles on the real axis for the specified bandwidth. + +**Default parameters:** `filterType = "BP"`, `period = 20`, `n = 10` (SMA only), `delta = 0.1` (BP/BS), `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +// Compute alpha, beta, gamma from period and mode +[alpha, beta, gamma] = derive_params(filterType, period, delta) + +// Select coefficients by mode +[c0, b0, b1, b2, a1, a2] = select_coeffs(filterType, alpha, beta, gamma, n) + +// Apply unified 2nd-order IIR +if filterType == "SMA": + result = (1/n)*src + result[1] - (1/n)*src[n] +else: + result = c0*(b0*src + b1*src[1] + b2*src[2]) + a1*result[1] + a2*result[2] +``` + +## Resources + +- Ehlers, J.F. (2006). "Swiss Army Knife Indicator." *Technical Analysis of Stocks & Commodities*, January 2006. +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapters 3-4: IIR and FIR filter design. +- Ehlers, J.F. (2004). *Cybernetic Analysis for Stocks and Futures*. Wiley. Chapter 2: Filters. diff --git a/lib/momentum/sam/Sam.md b/lib/momentum/sam/Sam.md new file mode 100644 index 00000000..813e2c73 --- /dev/null +++ b/lib/momentum/sam/Sam.md @@ -0,0 +1,96 @@ +# SAM: Smoothed Adaptive Momentum + +The Smoothed Adaptive Momentum oscillator measures price momentum over an adaptively determined lookback period equal to the dominant cycle length, then smooths the result with a 2-pole Super Smoother filter. Unlike fixed-period momentum indicators (ROC, TRIX) that use an arbitrary lookback, SAM measures the dominant cycle via Ehlers' Homodyne Discriminator and uses that cycle length as the momentum window, ensuring that the momentum measurement always spans exactly one full cycle. This eliminates the half-cycle phase distortion that plagues fixed-period momentum, producing a zero-lag momentum oscillator that naturally adapts to changing market rhythm. + +## Historical Context + +SAM was developed by John F. Ehlers and presented in Chapter 12 ("Adapting to the Trend") of "Cybernetic Analysis for Stocks and Futures" (2004). It represents the synthesis of two Ehlers innovations: the Homodyne Discriminator for cycle measurement and the Super Smoother for noise reduction. + +The key insight is that momentum measured over exactly one dominant cycle period produces a nearly zero-mean oscillator with minimal spectral leakage. If the dominant cycle is 20 bars, then `close - close[20]` captures one full oscillation, with the difference being zero at the start and end of the cycle (when phase completes 360°). A fixed 14-bar momentum, by contrast, may measure a fractional cycle, producing a biased oscillator with a non-zero mean that varies as the cycle length drifts. + +The Homodyne Discriminator (named after the homodyne detection technique from radio engineering) measures the instantaneous frequency by correlating the analytic signal with a one-bar-delayed version of itself. The phase change between bars gives the instantaneous frequency, which is smoothed and clamped to produce a stable period estimate in the 6-50 bar range. + +The Super Smoother is Ehlers' preferred final-stage filter: a 2-pole IIR low-pass with better amplitude response than a Butterworth of the same order, specifically designed to minimize lag while suppressing high-frequency noise. + +## Architecture and Physics + +The pipeline has five stages: + +**Stage 1: 4-bar FIR smoother** applies a [1, 2, 2, 1]/6 weighted average to eliminate 2-bar and 3-bar cycle noise. This is a standard Ehlers preprocessing step that removes aliasing artifacts without introducing significant lag. + +**Stage 2: Hilbert Transform** extracts the analytic signal from the smoothed price using Ehlers' modified Hilbert Transform. The detrender and quadrature components $I_1$ and $Q_1$ are derived via 7-tap FIR filters with empirically chosen coefficients that approximate the Hilbert Transform over financial cycle frequencies. + +**Stage 3: Phase advance** applies the Hilbert Transform to $I_1$ and $Q_1$ themselves, producing $JI$ and $JQ$ (90° phase-advanced versions). The phasor addition $I_2 = I_1 - JQ$ and $Q_2 = Q_1 + JI$ creates the forward-rotated analytic signal needed for homodyne detection. + +**Stage 4: Homodyne Discriminator** correlates the current phasor $(I_2, Q_2)$ with the previous bar's phasor to extract the instantaneous frequency: + +$$\text{Re} = I_2 \cdot I_2[1] + Q_2 \cdot Q_2[1], \quad \text{Im} = I_2 \cdot Q_2[1] - Q_2 \cdot I_2[1]$$ + +The period is $2\pi / \arctan(\text{Im}/\text{Re})$, clamped to $[6, 50]$ and smoothed via two cascaded EMA stages to produce the dominant cycle period. + +**Stage 5: Adaptive momentum + Super Smoother** computes `source - source[dcPeriod]` where `dcPeriod` is the rounded dominant cycle, then applies a 2-pole Super Smoother with the user-specified cutoff period. + +## Mathematical Foundation + +**4-bar FIR smoother**: + +$$s[n] = \frac{x[n] + 2x[n-1] + 2x[n-2] + x[n-3]}{6}$$ + +**Ehlers Hilbert Transform** (7-tap approximation): + +$$H[n] = 0.0962\,s[n] + 0.5769\,s[n-2] - 0.5769\,s[n-4] - 0.0962\,s[n-6]$$ + +scaled by the adaptive gain factor $(0.075\,P[n-1] + 0.54)$. + +**Homodyne Discriminator**: + +$$\text{Re}[n] = I_2[n] \cdot I_2[n-1] + Q_2[n] \cdot Q_2[n-1]$$ + +$$\text{Im}[n] = I_2[n] \cdot Q_2[n-1] - Q_2[n] \cdot I_2[n-1]$$ + +$$P = \frac{2\pi}{\arctan(\text{Im}/\text{Re})}, \quad P \in [6, 50]$$ + +**Dominant Cycle Period** (double-smoothed): + +$$P_{\text{inst}} = 0.33 \cdot P + 0.67 \cdot P_{\text{inst}}[1]$$ + +$$P_{\text{DC}} = 0.15 \cdot P_{\text{inst}} + 0.85 \cdot P_{\text{DC}}[1]$$ + +**Adaptive momentum**: $M[n] = x[n] - x[n - \lfloor P_{\text{DC}} \rfloor]$ + +**2-pole Super Smoother** with cutoff $C$: + +$$a_1 = e^{-\sqrt{2}\pi/C}, \quad b_1 = 2a_1\cos(\sqrt{2}\pi/C)$$ + +$$\text{filt}[n] = \frac{1 - b_1 + a_1^2}{2}(M[n] + M[n-1]) + b_1\,\text{filt}[n-1] - a_1^2\,\text{filt}[n-2]$$ + +**Parameter constraints**: $\alpha \in (0, 1)$, `cutoff` $\ge 2$. + +``` +SAM(source, alpha, cutoff): + smooth = (src + 2*src[1] + 2*src[2] + src[3]) / 6 + + // Hilbert Transform -> I1, Q1 + // Phase advance -> JI, JQ + // Phasor addition -> I2, Q2 + I2 = I1 - JQ; Q2 = Q1 + JI + I2 = alpha*I2 + (1-alpha)*I2[1] // smooth + Q2 = alpha*Q2 + (1-alpha)*Q2[1] + + // Homodyne Discriminator + Re = I2*I2[1] + Q2*Q2[1] + Im = I2*Q2[1] - Q2*I2[1] + period = 2*pi / atan(Im/Re), clamped [6,50] + dcPeriod = double_smooth(period) + + // Adaptive momentum + Super Smoother + momentum = source - source[dcPeriod] + return superSmoother(momentum, cutoff) +``` + +## Resources + +- Ehlers, J.F. "Cybernetic Analysis for Stocks and Futures." Wiley, 2004. Chapter 12, p.166. +- Ehlers, J.F. "Rocket Science for Traders." Wiley, 2001. Chapters on Hilbert Transform and cycle measurement. +- Ehlers, J.F. "MESA and Trading Market Cycles." 2nd edition, Wiley, 2002. +- Oppenheim, A.V. & Schafer, R.W. "Discrete-Time Signal Processing." 3rd edition, Pearson, 2010. Chapter on Hilbert Transform. diff --git a/lib/numerics/betadist/Betadist.md b/lib/numerics/betadist/Betadist.md new file mode 100644 index 00000000..9046e776 --- /dev/null +++ b/lib/numerics/betadist/Betadist.md @@ -0,0 +1,49 @@ +# BETADIST: Beta Distribution CDF + +BETADIST computes the cumulative distribution function of the Beta distribution applied to a min-max normalized price series. The source price is first normalized to $[0, 1]$ over a lookback window, then passed through the regularized incomplete beta function $I_x(\alpha, \beta)$ to produce a probability-mapped oscillator. The two shape parameters $\alpha$ and $\beta$ control the nonlinear mapping: symmetric parameters ($\alpha = \beta$) produce a sigmoid-like transformation centered at 0.5, while asymmetric parameters skew the mapping to emphasize extremes in either direction. + +## Historical Context + +The Beta distribution is one of the fundamental distributions in Bayesian statistics, serving as the conjugate prior for Bernoulli and binomial processes. Its application to financial time series normalization leverages the distribution's unique property of being defined on the bounded interval $[0, 1]$, making it a natural fit for min-max normalized price data. The CDF transformation converts a uniformly-distributed normalized price into a probability-weighted oscillator where the shape parameters control sensitivity to price levels within the range. When $\alpha = \beta = 1$, the Beta distribution reduces to the uniform distribution (no transformation); when $\alpha = \beta = 2$, it produces a smooth S-curve that compresses extremes and expands the midrange. The regularized incomplete beta function required for the CDF has no elementary closed form and requires numerical methods — this implementation uses Lentz's continued fraction algorithm, the standard approach in numerical libraries (NAG, CEPHES, Numerical Recipes). + +## Architecture & Physics + +### Three-Stage Pipeline + +1. **Min-Max Normalization:** Scans the lookback window to find minimum and maximum values, then maps the current source to $x \in [0, 1]$. If the range is zero (flat price), defaults to 0.5. + +2. **Lanczos Log-Gamma:** The Lanczos approximation with $g = 7$ and 9 coefficients computes $\ln\Gamma(z)$ for any positive $z$. This is used internally by the continued fraction to compute the prefactor of the incomplete beta function. + +3. **Lentz Continued Fraction:** The regularized incomplete beta function $I_x(a, b)$ is evaluated via the modified Lentz algorithm. A symmetry flip is applied when $x > (a+1)/(a+b+2)$ to ensure convergence of the continued fraction from the correct side. Convergence typically requires 10-20 iterations for standard parameter ranges. + +## Mathematical Foundation + +**Min-max normalization:** + +$$x_t = \frac{S_t - \min_{i \in [t-n, t]} S_i}{\max_{i \in [t-n, t]} S_i - \min_{i \in [t-n, t]} S_i}$$ + +**Beta CDF (regularized incomplete beta function):** + +$$I_x(\alpha, \beta) = \frac{B(x; \alpha, \beta)}{B(\alpha, \beta)} = \frac{\int_0^x t^{\alpha-1}(1-t)^{\beta-1}\,dt}{B(\alpha, \beta)}$$ + +**Lentz continued fraction** for $I_x(a, b)$: + +$$I_x(a,b) = \frac{x^a (1-x)^b}{a \cdot B(a,b)} \cdot \cfrac{1}{1+\cfrac{d_1}{1+\cfrac{d_2}{1+\cdots}}}$$ + +where $d_{2m} = \frac{m(b-m)x}{(a+2m-1)(a+2m)}$ and $d_{2m+1} = \frac{-(a+m)(a+b+m)x}{(a+2m)(a+2m+1)}$ + +**Symmetry flip:** If $x > \frac{a+1}{a+b+2}$, compute $I_x(a,b) = 1 - I_{1-x}(b,a)$ + +**Lanczos log-gamma** ($g = 7$, 9 coefficients): + +$$\ln\Gamma(z) = \frac{1}{2}\ln(2\pi) + (z - \tfrac{1}{2})\ln(t) - t + \ln\left(\sum_{k=0}^{8} \frac{c_k}{z+k}\right)$$ + +where $t = z + g - \frac{1}{2}$ + +**Default parameters:** period = 50, alpha = 2.0, beta = 2.0. + +## Resources + +- Abramowitz, M. & Stegun, I. (1964). *Handbook of Mathematical Functions*, Chapter 26 +- Press, W. et al. (2007). *Numerical Recipes*, 3rd ed. Cambridge, §6.4 (Incomplete Beta Function) +- PineScript reference: [`betadist.pine`](betadist.pine) diff --git a/lib/numerics/binomdist/Binomdist.md b/lib/numerics/binomdist/Binomdist.md new file mode 100644 index 00000000..76f386d5 --- /dev/null +++ b/lib/numerics/binomdist/Binomdist.md @@ -0,0 +1,43 @@ +# BINOMDIST: Binomial Distribution CDF + +BINOMDIST computes the cumulative distribution function of the Binomial distribution, mapping a min-max normalized price to a success probability $p$ and evaluating $P(X \leq k)$ for $X \sim \text{Binomial}(n, p)$. The normalized price position within its lookback range determines the probability of success per trial, while the trial count $n$ and threshold $k$ control the shape of the CDF response. The output is a $[0, 1]$ bounded oscillator where values near 0 indicate the price-derived probability makes $k$ or fewer successes very unlikely (bullish pressure), and values near 1 indicate $k$ successes are very likely (established range). + +## Historical Context + +The Binomial distribution, formalized by Jakob Bernoulli in 1713 and refined by Abraham de Moivre, is the foundational discrete probability distribution for counting successes in independent trials. Its CDF application to financial time series transforms the continuous price position into a discrete probabilistic framework: "given the current price's relative position as a probability, how likely is it that at most $k$ out of $n$ events would succeed?" This reframing provides a nonlinear transformation that is particularly sensitive around the probability values where $k/n$ transitions from unlikely to likely. The log-space summation technique used here avoids factorial overflow for large $n$, leveraging the Lanczos log-gamma approximation for $\ln(n!)$ computation. + +## Architecture & Physics + +### Two-Stage Pipeline + +1. **Min-Max Normalization:** The source is normalized to $p \in [0, 1]$ over the lookback window. This probability represents the "success rate" implied by the price's position within its recent range. + +2. **Binomial CDF Summation:** The CDF $P(X \leq k)$ is computed as a direct sum of binomial probabilities from $i = 0$ to $k$. Each term is computed in log-space to avoid overflow: $\ln\binom{n}{i} + i\ln(p) + (n-i)\ln(1-p)$, then exponentiated and accumulated. The log-binomial coefficient uses the Lanczos log-gamma function. + +### Edge Cases + +- $p \leq 0$: All mass at $X = 0$, so $P(X \leq k) = 1$ for any $k \geq 0$ +- $p \geq 1$: All mass at $X = n$, so $P(X \leq k) = 1$ only if $k \geq n$ +- Result is clamped to $[0, 1]$ to guard against floating-point accumulation drift + +## Mathematical Foundation + +**Binomial CDF:** + +$$P(X \leq k) = \sum_{i=0}^{k} \binom{n}{i} p^i (1-p)^{n-i}$$ + +**Log-space computation** (avoids factorial overflow): + +$$\ln\binom{n}{i} = \ln\Gamma(n+1) - \ln\Gamma(i+1) - \ln\Gamma(n-i+1)$$ + +$$P(X \leq k) = \sum_{i=0}^{k} \exp\!\left[\ln\binom{n}{i} + i\ln(p) + (n-i)\ln(1-p)\right]$$ + +**Lanczos log-gamma** ($g = 7$, 9 coefficients): same as BETADIST. + +**Default parameters:** period = 50, trials = 20, threshold = 10 (symmetric: $k = n/2$). + +## Resources + +- Bernoulli, J. (1713). *Ars Conjectandi* +- Press, W. et al. (2007). *Numerical Recipes*, 3rd ed., §6.2 (Incomplete Beta as alternative) +- PineScript reference: [`binomdist.pine`](binomdist.pine) diff --git a/lib/numerics/cwt/Cwt.md b/lib/numerics/cwt/Cwt.md new file mode 100644 index 00000000..8f0cb434 --- /dev/null +++ b/lib/numerics/cwt/Cwt.md @@ -0,0 +1,55 @@ +# CWT: Continuous Wavelet Transform + +CWT computes the magnitude of the Continuous Wavelet Transform at a specified scale using the Morlet wavelet, providing a time-frequency decomposition that measures the energy content of a specific frequency band at each point in time. Unlike Fourier analysis which loses time localization, the wavelet transform maintains both time and frequency information simultaneously. The output is a non-negative magnitude series where peaks indicate strong presence of the target frequency (determined by the scale parameter) and troughs indicate absence of that frequency component. + +## Historical Context + +The wavelet transform emerged from seismology and signal processing in the 1980s, with foundational work by Jean Morlet (a geophysicist analyzing seismic reflections) and Alex Grossmann. The Morlet wavelet — a complex sinusoid modulated by a Gaussian envelope — became the standard analyzing wavelet due to its optimal time-frequency resolution (it achieves the Heisenberg uncertainty lower bound). In financial applications, CWT provides multi-resolution analysis: by varying the scale parameter, traders can identify dominant cycles at different timeframes without the windowing artifacts of short-time Fourier transforms. The scale parameter directly controls which frequency band is analyzed: larger scales capture lower frequencies (longer cycles), smaller scales capture higher frequencies (shorter cycles). The relationship between scale $s$ and approximate cycle period is $P \approx \frac{2\pi s}{\omega_0}$ where $\omega_0$ is the central frequency (default 6.0). + +## Architecture & Physics + +### Morlet Wavelet Convolution + +The CWT at scale $s$ is computed as the inner product of the signal with a scaled, translated Morlet wavelet: + +$$W(t, s) = \frac{1}{\sqrt{s}} \sum_{k=-K}^{K} x(t-k) \cdot \psi^*\!\left(\frac{k}{s}\right)$$ + +The Morlet wavelet $\psi(t) = e^{-t^2/2} e^{i\omega_0 t}$ decomposes into real (cosine) and imaginary (sine) parts, both modulated by a Gaussian envelope. + +### Implementation Details + +- **Half-window:** $K = \text{round}(3s)$, ensuring the Gaussian envelope decays to $<0.01$ at the edges ($e^{-4.5} \approx 0.011$). +- **Real and imaginary sums:** Computed separately, then combined as $|W| = \sqrt{\text{Re}^2 + \text{Im}^2}$. +- **Normalization:** The $1/\sqrt{s}$ factor ensures energy preservation across scales. + +### Complexity + +$O(K)$ per bar where $K = 6s + 1$. For scale = 10, this is 61 multiply-adds per bar. + +## Mathematical Foundation + +**Morlet wavelet:** + +$$\psi(t) = e^{-t^2/2} \cdot e^{i\omega_0 t}$$ + +**CWT at scale $s$ and time $t$:** + +$$W(t, s) = \frac{1}{\sqrt{s}} \sum_{k=-K}^{K} x_{t+k} \cdot e^{-k^2/(2s^2)} \cdot e^{-i\omega_0 k/s}$$ + +**Magnitude (power at scale $s$):** + +$$|W(t,s)| = \sqrt{\left(\sum_k x_k \cdot g_k \cos\theta_k\right)^2 + \left(\sum_k x_k \cdot g_k \sin\theta_k\right)^2} \cdot \frac{1}{\sqrt{s}}$$ + +where $g_k = e^{-k^2/(2s^2)}$ and $\theta_k = \omega_0 k / s$ + +**Scale-to-period relationship:** + +$$P \approx \frac{2\pi s}{\omega_0}$$ + +**Default parameters:** scale = 10.0, omega = 6.0 (corresponding to period $\approx 10.5$ bars). + +## Resources + +- Morlet, J. et al. (1982). "Wave propagation and sampling theory." *Geophysics*, 47(2): 203-236 +- Torrence, C. & Compo, G.P. (1998). "A Practical Guide to Wavelet Analysis." *Bulletin of the American Meteorological Society* +- PineScript reference: [`cwt.pine`](cwt.pine) diff --git a/lib/numerics/dwt/Dwt.md b/lib/numerics/dwt/Dwt.md new file mode 100644 index 00000000..4402e3bd --- /dev/null +++ b/lib/numerics/dwt/Dwt.md @@ -0,0 +1,69 @@ +# DWT: Discrete Wavelet Transform + +The Discrete Wavelet Transform decomposes a price series into multi-resolution frequency components using the a trous (with holes) stationary Haar wavelet. Unlike decimated DWT, the stationary variant preserves time alignment at every scale, producing an approximation (trend) and detail coefficients (noise/cycles) at each decomposition level. Each level doubles the effective receptive field: level $L$ captures structure at $2^L$ bars. With 1-8 levels and $O(L)$ per-bar cost, DWT provides a complete multi-scale decomposition that cleanly separates trend from noise without the phase distortion inherent in moving-average cascades. + +## Historical Context + +Classical wavelet analysis traces to Jean Morlet's 1980s seismology work, with formal DWT construction by Stephane Mallat (1989) and Ingrid Daubechies (1988). The a trous algorithm (Holschneider et al., 1989) emerged as a shift-invariant alternative to Mallat's decimated pyramid, sacrificing orthogonality for translation invariance. For financial series where exact bar alignment matters more than basis orthogonality, the stationary variant dominates. + +The Haar wavelet is the simplest possible mother wavelet: a step function that computes local averages and differences. While it lacks the smoothness of Daubechies-N wavelets, its simplicity means zero multiplications beyond the 0.5 scaling factor, and its compact support (2 taps) minimizes boundary artifacts. For price series where discontinuities (gaps, jumps) are common, the Haar basis actually outperforms smoother wavelets that assume continuous derivatives that do not exist in market data. + +The multi-resolution analysis (MRA) framework guarantees perfect reconstruction: summing the approximation at any level with all detail coefficients from that level back to level 1 recovers the original signal exactly. This property is critical for attribution: the energy (variance) at each scale sums to the total variance, providing a complete variance decomposition across time scales. + +## Architecture and Physics + +The implementation uses an unrolled cascade of 8 levels, each conditionally executed based on the `levels` parameter. At level $j$, the approximation is the average of the current approximation and its value $2^{j-1}$ bars ago, with the detail coefficient being their difference. + +**Pipeline structure:** + +1. **Level 1**: Average source with 1-bar-ago source (2-bar window) +2. **Level 2**: Average level-1 approx with its 2-bar-ago value (4-bar effective window) +3. **Level $j$**: Average level-$(j-1)$ approx with its $2^{j-1}$-bar-ago value ($2^j$-bar effective window) + +The `output` selector chooses which component to return: 0 for the deepest approximation (smooth trend), or 1-8 for the detail coefficient at that level. Detail level 1 captures the highest-frequency noise (2-bar oscillations); detail level $L$ captures oscillations at the $2^L$-bar scale. + +**Boundary handling** uses `nz()` substitution: when historical data is unavailable at the required lag, the algorithm uses the current approximation value. This introduces a warm-up transient of $2^L$ bars for level $L$, after which the decomposition stabilizes. + +**Stationarity property**: Because no downsampling occurs, every output sample aligns exactly with its input bar. This permits direct overlay of approximation on price and meaningful bar-by-bar analysis of detail coefficients. + +## Mathematical Foundation + +The a trous Haar wavelet decomposition at level $j$: + +$$c_j[n] = \frac{1}{2}\bigl(c_{j-1}[n] + c_{j-1}[n - 2^{j-1}]\bigr)$$ + +$$d_j[n] = c_{j-1}[n] - c_j[n]$$ + +where $c_0[n] = x[n]$ is the input source. + +**Perfect reconstruction** at any level $L$: + +$$x[n] = c_L[n] + \sum_{j=1}^{L} d_j[n]$$ + +**Effective window** at level $j$ is $2^j$ bars. The Haar scaling function at level $j$ is: + +$$\phi_j[n] = 2^{-j/2} \cdot \mathbf{1}_{[0,\, 2^j)}(n)$$ + +**Variance decomposition**: Since detail coefficients at different levels are uncorrelated: + +$$\text{Var}(x) = \text{Var}(c_L) + \sum_{j=1}^{L} \text{Var}(d_j)$$ + +**Parameter ranges**: `levels` $\in [1, 8]$, `output` $\in [0, \text{levels}]$. Maximum lookback is $2^{\text{levels}}$ bars (256 bars at level 8). + +``` +DWT(source, levels, output): + c[0] = source + for j = 1 to levels: + c[j] = 0.5 * (c[j-1] + c[j-1][2^(j-1)]) + d[j] = c[j-1] - c[j] + if output == 0: return c[levels] // approximation (trend) + else: return d[output] // detail at selected level +``` + +## Resources + +- Mallat, S. "A Theory for Multiresolution Signal Decomposition: The Wavelet Representation." IEEE Trans. PAMI, 1989. +- Daubechies, I. "Ten Lectures on Wavelets." SIAM, 1992. +- Holschneider, M. et al. "A Real-Time Algorithm for Signal Analysis with the Help of the Wavelet Transform." Wavelets: Time-Frequency Methods and Phase Space, 1989. +- Percival, D. & Walden, A. "Wavelet Methods for Time Series Analysis." Cambridge University Press, 2000. +- Gencay, R., Selcuk, F. & Whitcher, B. "An Introduction to Wavelets and Other Filtering Methods in Finance and Economics." Academic Press, 2002. diff --git a/lib/numerics/expdist/Expdist.md b/lib/numerics/expdist/Expdist.md new file mode 100644 index 00000000..b09d6512 --- /dev/null +++ b/lib/numerics/expdist/Expdist.md @@ -0,0 +1,71 @@ +# EXPDIST: Exponential Distribution CDF + +The Exponential Distribution CDF transforms a min-max normalized price into the cumulative distribution function of the exponential distribution, producing an output in $[0, 1]$. The exponential distribution models memoryless waiting times: the probability that a normalized value falls below a threshold depends only on the rate parameter $\lambda$, not on any history. Higher $\lambda$ values compress the CDF curve toward zero, making the indicator more sensitive to small normalized deviations. With $O(N)$ normalization and $O(1)$ CDF evaluation, EXPDIST provides a nonlinear percentile ranking that emphasizes the lower end of the price range while compressing the upper end. + +## Historical Context + +The exponential distribution is the continuous analog of the geometric distribution, first studied systematically by Agner Krarup Erlang (1909) in the context of telephone call modeling. Its defining property is memorylessness: $P(X > s + t \mid X > s) = P(X > t)$, making it the unique continuous distribution where the conditional probability of waiting another $t$ units is independent of time already elapsed. + +In quantitative finance, the exponential CDF appears in several contexts: modeling inter-arrival times of trades (market microstructure), as a probability integral transform for goodness-of-fit testing, and as a nonlinear rescaling that emphasizes proximity to recent lows. The min-max normalization step maps raw prices into $[0, 1]$, and the CDF then provides a probabilistic interpretation: the output represents the probability that an exponentially-distributed random variable with rate $\lambda$ would fall at or below the normalized price level. + +Unlike the normal or Student-t CDFs, the exponential CDF has a closed-form expression requiring only a single `exp()` call. This makes it computationally attractive for real-time applications where the heavier special-function machinery (incomplete beta, error function) of other distributions is unnecessary. + +## Architecture and Physics + +The indicator follows the standard two-phase pattern used across all distribution CDF indicators in this library: + +**Phase 1: Min-max normalization** scans the lookback window of `period` bars to find the minimum and maximum values, then maps the current source value to $[0, 1]$: + +$$x = \frac{\text{source} - \text{min}}{\text{max} - \text{min}}$$ + +If the range is zero (flat price), $x$ defaults to 0.5. This normalization is $O(N)$ per bar where $N$ is the period. + +**Phase 2: CDF evaluation** applies the exponential CDF in $O(1)$: + +$$F(x) = 1 - e^{-\lambda x}$$ + +with the boundary condition $F(x) = 0$ for $x \le 0$. + +**Rate parameter effects**: $\lambda = 1$ gives a gentle S-curve with $F(0.5) \approx 0.39$. $\lambda = 3$ (default) gives $F(0.5) \approx 0.78$, strongly biasing toward 1.0 for values in the upper half of the range. $\lambda = 10$ saturates near 1.0 for almost any positive normalized value, functioning as a near-binary above/below-midpoint indicator. + +## Mathematical Foundation + +The exponential distribution with rate parameter $\lambda > 0$ has PDF and CDF: + +$$f(x; \lambda) = \lambda e^{-\lambda x}, \quad x \ge 0$$ + +$$F(x; \lambda) = 1 - e^{-\lambda x}, \quad x \ge 0$$ + +**Moments of the exponential distribution:** + +$$E[X] = \frac{1}{\lambda}, \quad \text{Var}(X) = \frac{1}{\lambda^2}, \quad \text{Skew} = 2, \quad \text{Kurt} = 6$$ + +**Inverse CDF** (quantile function): + +$$F^{-1}(p) = -\frac{\ln(1 - p)}{\lambda}$$ + +The **memoryless property**: + +$$P(X > s + t \mid X > s) = P(X > t) = e^{-\lambda t}$$ + +**Parameter constraints**: `period` $> 0$, $\lambda > 0$. Output is bounded $[0, 1]$. + +``` +EXPDIST(source, period, lambda): + // Phase 1: min-max normalization + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + + // Phase 2: exponential CDF + if x <= 0: return 0.0 + return 1.0 - exp(-lambda * x) +``` + +## Resources + +- Erlang, A.K. "The Theory of Probabilities and Telephone Conversations." Nyt Tidsskrift for Matematik B, 1909. +- Johnson, N.L., Kotz, S. & Balakrishnan, N. "Continuous Univariate Distributions, Vol. 1." Wiley, 1994. +- Ross, S. "Introduction to Probability Models." Academic Press, 12th edition, 2019. +- Cont, R. "Empirical Properties of Asset Returns: Stylized Facts and Statistical Issues." Quantitative Finance, 2001. diff --git a/lib/numerics/fdist/Fdist.md b/lib/numerics/fdist/Fdist.md new file mode 100644 index 00000000..57cb0515 --- /dev/null +++ b/lib/numerics/fdist/Fdist.md @@ -0,0 +1,73 @@ +# FDIST: F-Distribution CDF + +The F-Distribution CDF transforms a min-max normalized price into the cumulative distribution function of the F-distribution (Fisher-Snedecor distribution), producing an output in $[0, 1]$. The F-distribution arises as the ratio of two chi-squared random variables divided by their respective degrees of freedom, making it the natural distribution for variance ratio tests. By mapping normalized price through the regularized incomplete beta function with parameters tied to degrees of freedom $d_1$ and $d_2$, FDIST provides a probabilistic ranking that is asymmetric: the CDF shape changes qualitatively depending on whether $d_1 < d_2$, $d_1 = d_2$, or $d_1 > d_2$, giving traders control over the nonlinear response curve. + +## Historical Context + +The F-distribution was developed independently by George Snedecor (1934) and Ronald Fisher (1924), though Fisher's earlier work on variance ratios laid the theoretical foundation. The distribution is named in Fisher's honor by Snedecor. Its primary statistical application is the F-test for comparing variances of two populations, and it forms the backbone of ANOVA (Analysis of Variance), one of the most widely used statistical procedures. + +In financial applications, the F-distribution appears in variance ratio tests (Lo and MacKinlay, 1988) used to test the random walk hypothesis. The CDF form used here repurposes the distribution's shape as a nonlinear mapping: with equal degrees of freedom ($d_1 = d_2$), the CDF is approximately symmetric around 0.5; with $d_1 \gg d_2$, the curve shifts left (more probability mass near zero); with $d_1 \ll d_2$, it shifts right. This parameter-controlled asymmetry distinguishes FDIST from simpler sigmoid-like transformations. + +The implementation uses the same Lanczos log-gamma and Lentz continued fraction machinery as BETADIST, since the F-distribution CDF reduces to a regularized incomplete beta function through a variable substitution. + +## Architecture and Physics + +The computation follows a three-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars to find extrema, then maps the current source to $x \in [0, 1]$. Zero-range defaults to 0.5. + +**Phase 2: Variable transformation** converts the normalized value $x$ to the beta function argument: + +$$t = \frac{d_1 \cdot x}{d_1 \cdot x + d_2}$$ + +This maps $x \in [0, \infty)$ to $t \in [0, 1)$, which is the domain of the regularized incomplete beta function. Since input $x$ is already in $[0, 1]$, the effective range of $t$ is $[0, d_1/(d_1 + d_2)]$. + +**Phase 3: Regularized incomplete beta** evaluates $I_t(d_1/2, d_2/2)$ using the Lentz continued fraction algorithm. The implementation includes a reflection step when $x > (a+1)/(a+b+2)$ to ensure the continued fraction converges from the faster side. Convergence typically requires 10-20 iterations to reach $\epsilon = 10^{-10}$. + +**Shared infrastructure**: The `lnGamma()` function uses the Lanczos approximation with $g = 7$ and 9 coefficients, identical to the implementation in BETADIST and other distribution indicators. The `betaReg()` continued fraction is likewise shared. + +## Mathematical Foundation + +The F-distribution with $d_1$ numerator and $d_2$ denominator degrees of freedom has PDF: + +$$f(x; d_1, d_2) = \frac{1}{B(d_1/2, d_2/2)} \cdot \left(\frac{d_1}{d_2}\right)^{d_1/2} \cdot \frac{x^{d_1/2 - 1}}{(1 + d_1 x / d_2)^{(d_1+d_2)/2}}$$ + +The CDF is expressed via the regularized incomplete beta function: + +$$F(x; d_1, d_2) = I_t\!\left(\frac{d_1}{2}, \frac{d_2}{2}\right), \quad t = \frac{d_1 x}{d_1 x + d_2}$$ + +where the **regularized incomplete beta function** is: + +$$I_x(a, b) = \frac{B(x; a, b)}{B(a, b)} = \frac{1}{B(a, b)} \int_0^x t^{a-1}(1-t)^{b-1}\,dt$$ + +**Lentz continued fraction** for $I_x(a, b)$: + +$$I_x(a,b) = \frac{x^a (1-x)^b}{a \cdot B(a,b)} \cdot \cfrac{1}{1 + \cfrac{d_1}{1 + \cfrac{d_2}{1 + \cdots}}}$$ + +with convergents $d_m$ defined by the even/odd recurrence involving $a$, $b$, and $x$. + +**Parameter constraints**: `period` $> 0$, $d_1 > 0$, $d_2 > 0$. Output is bounded $[0, 1]$. + +``` +FDIST(source, period, d1, d2): + // Phase 1: min-max normalization + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + + // Phase 2: variable transformation + safe_x = max(0, x) + t = d1 * safe_x / (d1 * safe_x + d2) + + // Phase 3: regularized incomplete beta via Lentz CF + return betaReg(t, d1/2, d2/2) +``` + +## Resources + +- Fisher, R.A. "On a Distribution Yielding the Error Functions of Several Well Known Statistics." Proc. International Mathematical Congress, Toronto, 1924. +- Snedecor, G.W. "Calculation and Interpretation of Analysis of Variance and Covariance." Collegiate Press, 1934. +- Press, W.H. et al. "Numerical Recipes: The Art of Scientific Computing." 3rd edition, Cambridge University Press, 2007. Chapter 6.4 (Incomplete Beta Function). +- Lo, A. & MacKinlay, A.C. "Stock Market Prices Do Not Follow Random Walks: Evidence from a Simple Specification Test." Review of Financial Studies, 1988. +- Lentz, W.J. "Generating Bessel Functions in Mie Scattering Calculations Using Continued Fractions." Applied Optics, 1976. diff --git a/lib/numerics/fft/Fft.md b/lib/numerics/fft/Fft.md new file mode 100644 index 00000000..996a9e20 --- /dev/null +++ b/lib/numerics/fft/Fft.md @@ -0,0 +1,94 @@ +# FFT: Fast Fourier Transform (Dominant Cycle Detector) + +The FFT indicator computes the dominant cycle period in a price series using a Discrete Fourier Transform with a Hanning window. Rather than outputting frequency-domain magnitudes, it returns the estimated dominant cycle period in bars, making it directly usable as an adaptive period input for other indicators. The implementation uses a brute-force DFT over a constrained frequency band (not a radix-2 FFT), with parabolic interpolation on the magnitude spectrum to achieve sub-bin frequency resolution. With window sizes of 32, 64, or 128 and $O(N \cdot N/2)$ complexity per bar, the indicator trades computational cost for precise cycle detection within user-specified period bounds. + +## Historical Context + +The Fourier transform, formalized by Joseph Fourier (1822), decomposes any periodic signal into sinusoidal components. The Fast Fourier Transform algorithm (Cooley and Tukey, 1965) reduced the DFT from $O(N^2)$ to $O(N \log N)$, enabling real-time spectral analysis. However, for the small window sizes used in financial cycle detection (32-128 samples), the asymptotic advantage of FFT over DFT is minimal, and the DFT avoids the power-of-two length constraint. + +John Ehlers pioneered the application of spectral analysis to financial markets in the 1990s and 2000s, using DFT-based cycle measurement to create adaptive indicators. His work demonstrated that financial time series contain quasi-periodic cycles with time-varying periods, typically in the 6-40 bar range. The dominant cycle period, extracted via spectral peak detection, can drive adaptive moving averages (MAMA, FAMA), adaptive RSI, and other indicators that benefit from knowing the current market rhythm. + +The Hanning window (also called Hann window, after Julius von Hann) is applied to reduce spectral leakage. Without windowing, the sharp truncation of a finite data segment creates artificial high-frequency components that contaminate the spectrum. The Hanning window tapers the data to zero at both ends, suppressing sidelobes at the cost of slightly wider main lobes (reduced frequency resolution). + +## Architecture and Physics + +The computation pipeline has four stages: + +**Stage 1: Windowed DFT** computes the real and imaginary components of the Fourier coefficients for frequency bins $k$ ranging from `minBin` to `maxBin`: + +$$X[k] = \sum_{n=0}^{N-1} x[n] \cdot w[n] \cdot e^{-j 2\pi k n / N}$$ + +where $w[n] = 0.5 - 0.5\cos(2\pi n/N)$ is the Hanning window. Only bins corresponding to periods in `[minPeriod, maxPeriod]` are evaluated, reducing computation. + +**Stage 2: Power spectrum peak** finds the bin $k^*$ with maximum squared magnitude $|X[k]|^2 = \text{Re}^2 + \text{Im}^2$. During the search, the magnitudes of the bins adjacent to the peak (one before, one after) are captured for interpolation. + +**Stage 3: Parabolic interpolation** refines the peak location using a three-point parabola fit on the magnitudes at bins $k^*-1$, $k^*$, $k^*+1$: + +$$\delta = \frac{M_{k^*-1} - M_{k^*+1}}{M_{k^*-1} + 2 M_{k^*} + M_{k^*+1}}$$ + +The refined dominant period is $N / (k^* + \delta)$. + +**Stage 4: Clamping** ensures the output stays within `[minPeriod, maxPeriod]`. + +**Window size trade-offs**: $N = 32$ gives coarse resolution (period bins spaced ~1 bar apart) but fast response; $N = 128$ gives fine resolution (~0.25 bar spacing) but sluggish adaptation. The default $N = 64$ balances resolution and responsiveness. + +## Mathematical Foundation + +The **Discrete Fourier Transform** for $N$ samples: + +$$X[k] = \sum_{n=0}^{N-1} x[n] \cdot e^{-j 2\pi k n / N}, \quad k = 0, 1, \ldots, N-1$$ + +**Hanning window**: + +$$w[n] = 0.5 - 0.5\cos\!\left(\frac{2\pi n}{N}\right)$$ + +**Frequency-to-period** mapping: bin $k$ corresponds to period $T = N/k$ bars. + +**Bin range** from period bounds: + +$$k_{\min} = \max\!\left(1,\; \left\lfloor\frac{N}{T_{\max}}\right\rfloor\right), \quad k_{\max} = \min\!\left(\frac{N}{2},\; \left\lfloor\frac{N}{T_{\min}}\right\rfloor\right)$$ + +**Power spectrum**: $P[k] = \text{Re}(X[k])^2 + \text{Im}(X[k])^2$ + +**Parabolic interpolation** for sub-bin precision: + +$$\hat{k} = k^* + \frac{P[k^*-1] - P[k^*+1]}{P[k^*-1] + 2P[k^*] + P[k^*+1]}$$ + +$$T_{\text{dominant}} = \frac{N}{\hat{k}}$$ + +**Parameter constraints**: `windowSize` $\in \{32, 64, 128\}$, `minPeriod` $\ge 2$, `maxPeriod` $\le N/2$. + +``` +FFT(source, windowSize, minPeriod, maxPeriod): + N = windowSize + twoPiOverN = 2 * pi / N + minBin = max(1, N / maxPeriod) + maxBin = min(N/2, N / minPeriod) + + maxMag = 0; peakBin = 0 + for k = minBin to maxBin: + re = 0; im = 0 + for n = 0 to N-1: + w = 0.5 - 0.5 * cos(twoPiOverN * n) // Hanning + xw = source[n] * w + angle = twoPiOverN * k * n + re += xw * cos(angle) + im -= xw * sin(angle) + mag = re*re + im*im + if mag > maxMag: + track neighbor magnitudes + maxMag = mag; peakBin = k + + // Parabolic interpolation + shift = (magBefore - magAfter) / (magBefore + 2*maxMag + magAfter) + dominantPeriod = N / (peakBin + shift) + return clamp(dominantPeriod, minPeriod, maxPeriod) +``` + +## Resources + +- Cooley, J.W. & Tukey, J.W. "An Algorithm for the Machine Calculation of Complex Fourier Series." Mathematics of Computation, 1965. +- Ehlers, J.F. "Cycle Analytics for Traders." Wiley, 2013. +- Ehlers, J.F. "Rocket Science for Traders." Wiley, 2001. +- Harris, F.J. "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform." Proc. IEEE, 1978. +- Oppenheim, A.V. & Schafer, R.W. "Discrete-Time Signal Processing." 3rd edition, Pearson, 2010. diff --git a/lib/numerics/gammadist/Gammadist.md b/lib/numerics/gammadist/Gammadist.md new file mode 100644 index 00000000..5ff27f8f --- /dev/null +++ b/lib/numerics/gammadist/Gammadist.md @@ -0,0 +1,78 @@ +# GAMMADIST: Gamma Distribution CDF + +The Gamma Distribution CDF transforms a min-max normalized price into the cumulative distribution function of the gamma distribution, producing an output in $[0, 1]$. The gamma distribution generalizes the exponential distribution by adding a shape parameter $\alpha$ that controls whether the PDF is monotonically decreasing ($\alpha < 1$), exponential ($\alpha = 1$), or bell-shaped with a right skew ($\alpha > 1$). Combined with a rate parameter $\beta$ that scales the normalized input, GAMMADIST provides a flexible nonlinear mapping with controllable asymmetry. The CDF is computed via the regularized lower incomplete gamma function using series expansion or Lentz continued fraction, selecting the faster-converging method based on the argument relative to the shape parameter. + +## Historical Context + +The gamma distribution was first studied by Leonard Euler (1729) through his generalization of the factorial function to the gamma function $\Gamma(z)$. The distribution itself was formalized by Karl Pearson (1893) as part of his system of frequency curves, where it appears as a Type III distribution. The incomplete gamma function, central to computing the CDF, was tabulated extensively by Pearson (1922) before computational methods made tables obsolete. + +In finance, the gamma distribution models positively-skewed quantities: waiting times between events (generalizing the exponential), aggregate claim sizes in insurance (actuarial science), and the distribution of realized volatility (which is approximately gamma-distributed under certain stochastic volatility models). The chi-squared distribution is a special case with $\alpha = k/2$ and $\beta = 2$ (where $k$ is degrees of freedom), connecting GAMMADIST to variance-based statistical tests. + +The implementation uses two complementary algorithms for the regularized incomplete gamma function: a series expansion that converges rapidly for $x < \alpha + 1$, and a Lentz continued fraction for $x \ge \alpha + 1$. This split ensures convergence in approximately 10-30 iterations across the entire parameter space. + +## Architecture and Physics + +The computation follows a three-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars for extrema, maps the current source to $x \in [0, 1]$, then scales by the rate parameter: $\text{scaled} = \max(0, x \cdot \beta)$. + +**Phase 2: Algorithm selection** chooses between series and continued fraction based on the relationship between the scaled input and the shape parameter: +- If $\text{scaled} < \alpha + 1$: use the series expansion (converges from below) +- If $\text{scaled} \ge \alpha + 1$: use $1 - Q(\alpha, \text{scaled})$ via continued fraction (converges from above) + +**Phase 3: CDF evaluation** computes the regularized lower incomplete gamma function $P(\alpha, \text{scaled})$. + +The **series expansion** accumulates terms $\delta_n = x^n / (\alpha(\alpha+1)\cdots(\alpha+n))$ until the relative change drops below $10^{-10}$, then multiplies by the normalization factor $x^\alpha e^{-x} / \Gamma(\alpha)$. + +The **continued fraction** (Lentz algorithm) evaluates the complementary function $Q(\alpha, x) = 1 - P(\alpha, x)$ using the recurrence with convergents $a_i = -i(i - \alpha)$ and $b_i = x + 2i + 1 - \alpha$. + +**Shape parameter effects**: $\alpha = 1$ reduces to exponential distribution. $\alpha = 2, \beta = 3$ (default) gives a moderate right-skewed S-curve. Large $\alpha$ approaches a normal CDF shape. + +## Mathematical Foundation + +The gamma distribution with shape $\alpha > 0$ and rate $\beta > 0$ has PDF: + +$$f(x; \alpha, \beta) = \frac{\beta^\alpha}{\Gamma(\alpha)} x^{\alpha-1} e^{-\beta x}, \quad x > 0$$ + +The CDF is the **regularized lower incomplete gamma function**: + +$$F(x; \alpha, \beta) = P(\alpha, \beta x) = \frac{\gamma(\alpha, \beta x)}{\Gamma(\alpha)} = \frac{1}{\Gamma(\alpha)} \int_0^{\beta x} t^{\alpha-1} e^{-t}\,dt$$ + +**Series expansion** for $P(a, x)$ when $x < a + 1$: + +$$P(a, x) = e^{-x} x^a \sum_{n=0}^{\infty} \frac{x^n}{a(a+1)\cdots(a+n)}$$ + +**Continued fraction** for $Q(a, x) = 1 - P(a, x)$ when $x \ge a + 1$: + +$$Q(a, x) = e^{-x} x^a \cdot \cfrac{1}{x + 1 - a + \cfrac{1 \cdot (1-a)}{x + 3 - a + \cfrac{2 \cdot (2-a)}{x + 5 - a + \cdots}}}$$ + +**Log-gamma** via Lanczos approximation ($g = 7$, 9 coefficients): + +$$\ln\Gamma(z) = \frac{1}{2}\ln(2\pi) + \left(z - \frac{1}{2}\right)\ln(z + g - \frac{1}{2}) - (z + g - \frac{1}{2}) + \ln\!\left(\sum_{k=0}^{8} \frac{c_k}{z + k}\right)$$ + +**Parameter constraints**: `period` $> 0$, $\alpha > 0$, $\beta > 0$. Output is bounded $[0, 1]$. + +``` +GAMMADIST(source, period, shape, rate): + // Phase 1: min-max normalization + scaling + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + scaled = max(0, x * rate) + + // Phase 2-3: regularized lower incomplete gamma + if scaled <= 0: return 0.0 + if scaled < shape + 1: + return gammaSeries(shape, scaled) // series expansion + else: + return 1.0 - gammaCF(shape, scaled) // continued fraction +``` + +## Resources + +- Pearson, K. "Contributions to the Mathematical Theory of Evolution." Phil. Trans. Royal Society, 1893. +- Lanczos, C. "A Precision Approximation of the Gamma Function." SIAM J. Numerical Analysis B, 1964. +- Press, W.H. et al. "Numerical Recipes: The Art of Scientific Computing." 3rd edition, Cambridge University Press, 2007. Chapter 6.2 (Incomplete Gamma Function). +- Lentz, W.J. "Generating Bessel Functions in Mie Scattering Calculations Using Continued Fractions." Applied Optics, 1976. +- Johnson, N.L., Kotz, S. & Balakrishnan, N. "Continuous Univariate Distributions, Vol. 1." Wiley, 1994. diff --git a/lib/numerics/ifft/Ifft.md b/lib/numerics/ifft/Ifft.md new file mode 100644 index 00000000..50baddf6 --- /dev/null +++ b/lib/numerics/ifft/Ifft.md @@ -0,0 +1,90 @@ +# IFFT: Inverse Fast Fourier Transform (Spectral Filter) + +The Inverse FFT indicator reconstructs a smoothed version of the price series by performing a forward DFT, retaining only the lowest-frequency harmonics, and synthesizing the output via inverse transform. The result is a spectral low-pass filter that preserves the dominant cyclical components while discarding high-frequency noise. By controlling the number of retained harmonics $H$, the user adjusts the smoothness/responsiveness trade-off: $H = 1$ yields a near-sinusoidal trend, while $H = N/2$ reproduces the original (windowed) signal. The indicator overlays on price and provides a frequency-domain alternative to conventional moving averages. + +## Historical Context + +Spectral filtering via Fourier decomposition dates to Joseph Fourier's 1822 work on heat conduction, where he showed that any periodic function can be represented as a sum of sinusoids. The idea of reconstructing a signal from a subset of its Fourier coefficients is foundational to signal compression (JPEG, MP3) and has been applied to financial time series since the 1970s. + +John Ehlers brought spectral methods to mainstream technical analysis through his books on cycle analytics. His approach typically uses the DFT to identify the dominant cycle, then constructs adaptive filters tuned to that cycle. The IFFT indicator takes the complementary approach: rather than extracting a single cycle period, it reconstructs the signal from the $H$ lowest-frequency components, producing a multi-harmonic trend estimate. + +The Hanning window applied before the forward DFT reduces spectral leakage, ensuring that the retained harmonics accurately represent the true low-frequency content rather than artifacts of the window boundary. The inverse step only uses the real part of the synthesis (cosine terms), since the output must be a real-valued price estimate. The factor of 2 in the inverse accounts for the conjugate symmetry of real-valued DFT coefficients. + +## Architecture and Physics + +The computation has three stages executed per bar: + +**Stage 1: DC component** computes the windowed mean of the source over the window. This is the zero-frequency (average level) component: + +$$\text{DC} = \frac{1}{N}\sum_{n=0}^{N-1} x[n] \cdot w[n]$$ + +**Stage 2: Forward DFT for harmonics $k = 1$ to $H$** computes the real and imaginary Fourier coefficients for each retained harmonic. The Hanning window $w[n] = 0.5 - 0.5\cos(2\pi n/N)$ is applied to every sample. + +**Stage 3: Inverse synthesis** reconstructs the current bar's value by summing the DC component plus twice the real part of each harmonic evaluated at $n = 0$ (the current bar): + +$$\hat{x}[0] = \frac{\text{DC}_{\text{Re}}}{N} + \sum_{k=1}^{H} \frac{2 \cdot \text{Re}(X[k])}{N}$$ + +The factor $2/N$ accounts for: (1) the $1/N$ normalization of the inverse DFT, and (2) the factor of 2 from collapsing the conjugate-symmetric negative frequencies. + +**Complexity**: The forward DFT for $H$ harmonics costs $O(N \cdot H)$ multiply-adds per bar. With $N = 64$ and $H = 5$ (defaults), this is ~320 multiply-adds per bar. The inverse synthesis at $n = 0$ reduces to just summing the real components, costing $O(H)$. + +**Smoothness control**: Fewer harmonics produce smoother output but introduce more lag and lose detail. The relationship between harmonics and equivalent moving average length is roughly: $H$ harmonics approximate the smoothness of an $N/(2H)$-period moving average, but with better frequency selectivity (sharper cutoff). + +## Mathematical Foundation + +The **forward DFT** with Hanning window: + +$$X[k] = \sum_{n=0}^{N-1} x[n] \cdot w[n] \cdot e^{-j 2\pi k n / N}$$ + +where $w[n] = 0.5 - 0.5\cos(2\pi n / N)$. + +The **inverse DFT** evaluated at the current bar ($n = 0$): + +$$\hat{x}[0] = \frac{1}{N}\sum_{k=0}^{N-1} X[k] \cdot e^{j 2\pi k \cdot 0 / N} = \frac{1}{N}\sum_{k=0}^{N-1} X[k]$$ + +Since $e^{j \cdot 0} = 1$, the inverse at $n = 0$ is simply the sum of all retained coefficients divided by $N$. + +For a real-valued signal, $X[N-k] = X[k]^*$, so: + +$$\hat{x}[0] = \frac{X[0]}{N} + \frac{2}{N}\sum_{k=1}^{H} \text{Re}(X[k])$$ + +**Parseval's theorem** relates the energy retained: + +$$\frac{\sum_{k=0}^{H} |X[k]|^2}{\sum_{k=0}^{N/2} |X[k]|^2} = \text{fraction of signal energy preserved}$$ + +**Parameter constraints**: `windowSize` $\in \{32, 64, 128\}$, `numHarmonics` $\ge 1$ (clamped to $N/2$). + +``` +IFFT(source, windowSize, numHarmonics): + N = windowSize + H = min(numHarmonics, N/2) + twoPiOverN = 2 * pi / N + + // DC component (k=0) + dcRe = 0 + for n = 0 to N-1: + w = 0.5 - 0.5 * cos(twoPiOverN * n) + dcRe += source[n] * w + result = dcRe / N + + // Harmonics k=1..H + for k = 1 to H: + re = 0; im = 0 + for n = 0 to N-1: + w = 0.5 - 0.5 * cos(twoPiOverN * n) + xw = source[n] * w + angle = twoPiOverN * k * n + re += xw * cos(angle) + im -= xw * sin(angle) + result += 2 * re / N // inverse at n=0 + + return result +``` + +## Resources + +- Fourier, J.B.J. "Theorie Analytique de la Chaleur." Firmin Didot, 1822. +- Ehlers, J.F. "Cycle Analytics for Traders." Wiley, 2013. +- Oppenheim, A.V. & Schafer, R.W. "Discrete-Time Signal Processing." 3rd edition, Pearson, 2010. +- Bloomfield, P. "Fourier Analysis of Time Series: An Introduction." 2nd edition, Wiley, 2000. +- Priestley, M.B. "Spectral Analysis and Time Series." Academic Press, 1981. diff --git a/lib/numerics/lognormdist/Lognormdist.md b/lib/numerics/lognormdist/Lognormdist.md new file mode 100644 index 00000000..0253abaa --- /dev/null +++ b/lib/numerics/lognormdist/Lognormdist.md @@ -0,0 +1,75 @@ +# LOGNORMDIST: Log-Normal Distribution CDF + +The Log-Normal Distribution CDF transforms a min-max normalized price into the cumulative distribution function of the log-normal distribution, producing an output in $[0, 1]$. A random variable $X$ is log-normally distributed when $\ln(X)$ follows a normal distribution. This makes the log-normal CDF natural for financial data, where multiplicative returns (log-returns) are approximately normally distributed. The indicator min-max normalizes the source to $(0, 1]$, takes the natural logarithm, standardizes by parameters $\mu$ and $\sigma$, then evaluates the standard normal CDF. The result emphasizes values near the bottom of the recent range (where the logarithm diverges) and compresses values near the top. + +## Historical Context + +The log-normal distribution was first described by Francis Galton (1879) and formalized by Donald McAlister (1879) in a paper read to the Royal Society. It gained prominence in finance through Louis Bachelier's thesis (1900) on price speculation and was later adopted as the foundation of the Black-Scholes option pricing model (1973), where stock prices are assumed to follow geometric Brownian motion, making the price at any future time log-normally distributed. + +The log-normal assumption remains the default model in quantitative finance despite well-documented violations (fat tails, volatility clustering). Its mathematical tractability and the economic argument that prices cannot go negative (the log-normal support is $(0, \infty)$) make it a reasonable first approximation. The CDF form used here provides a probability integral transform: if the normalized price truly followed a log-normal distribution with parameters $\mu$ and $\sigma$, the output would be uniformly distributed on $[0, 1]$. + +The implementation reduces the log-normal CDF to the standard normal CDF through the substitution $z = (\ln x - \mu)/\sigma$, then uses the Abramowitz and Stegun rational approximation (formula 7.1.26) for $\Phi(z)$, achieving accuracy of approximately $1.5 \times 10^{-7}$. + +## Architecture and Physics + +The computation follows a three-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars for extrema, maps the current source to $x \in [0, 1]$. A floor of $10^{-10}$ is applied to prevent $\ln(0)$. + +**Phase 2: Log-standardization** computes $z = (\ln x - \mu) / \sigma$. With default $\mu = 0, \sigma = 1$, this simplifies to $z = \ln(x)$. Since $x \in (0, 1]$, $z \in (-\infty, 0]$, so default parameters place most output in $[0, 0.5]$. Shifting $\mu$ negative or increasing $\sigma$ spreads the output across the full $[0, 1]$ range. + +**Phase 3: Normal CDF** evaluates $\Phi(z)$ using the Abramowitz and Stegun approximation with 5 polynomial coefficients: + +$$\Phi(z) = 1 - \phi(|z|) \cdot (b_1 t + b_2 t^2 + b_3 t^3 + b_4 t^4 + b_5 t^5)$$ + +where $t = 1/(1 + 0.2316419|z|)$ and $\phi(z) = e^{-z^2/2}/\sqrt{2\pi}$. + +**Parameter effects**: $\mu$ shifts the inflection point of the S-curve along the logarithmic axis. $\sigma$ controls the steepness: small $\sigma$ produces a sharp transition, large $\sigma$ produces a gradual one. For financial applications, $\mu = -1, \sigma = 0.5$ centers the CDF near the geometric midpoint of the $[0, 1]$ range. + +## Mathematical Foundation + +If $X \sim \text{LogNormal}(\mu, \sigma^2)$, then $\ln(X) \sim N(\mu, \sigma^2)$, and the CDF is: + +$$F(x; \mu, \sigma) = \Phi\!\left(\frac{\ln x - \mu}{\sigma}\right), \quad x > 0$$ + +where $\Phi$ is the standard normal CDF. + +**Moments of the log-normal distribution:** + +$$E[X] = e^{\mu + \sigma^2/2}$$ + +$$\text{Var}(X) = (e^{\sigma^2} - 1) \cdot e^{2\mu + \sigma^2}$$ + +$$\text{Skew} = (e^{\sigma^2} + 2)\sqrt{e^{\sigma^2} - 1}$$ + +**Standard normal CDF** (Abramowitz and Stegun 7.1.26): + +$$\Phi(z) = 1 - \frac{e^{-z^2/2}}{\sqrt{2\pi}} \sum_{i=1}^{5} b_i t^i, \quad t = \frac{1}{1 + 0.2316419|z|}$$ + +with $b_1 = 0.319381530$, $b_2 = -0.356563782$, $b_3 = 1.781477937$, $b_4 = -1.821255978$, $b_5 = 1.330274429$. + +**Parameter constraints**: `period` $> 0$, $\sigma > 0$, $\mu \in \mathbb{R}$. Output is bounded $[0, 1]$. + +``` +LOGNORMDIST(source, period, mu, sigma): + // Phase 1: min-max normalization + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + safe_x = max(1e-10, x) + + // Phase 2: log-standardization + z = (ln(safe_x) - mu) / sigma + + // Phase 3: standard normal CDF + return normalCdf(z) +``` + +## Resources + +- Galton, F. "The Geometric Mean, in Vital and Social Statistics." Proc. Royal Society, 1879. +- Aitchison, J. & Brown, J.A.C. "The Lognormal Distribution." Cambridge University Press, 1957. +- Black, F. & Scholes, M. "The Pricing of Options and Corporate Liabilities." Journal of Political Economy, 1973. +- Abramowitz, M. & Stegun, I. "Handbook of Mathematical Functions." NBS Applied Mathematics Series 55, 1964. Formula 7.1.26. +- Limpert, E., Stahel, W. & Abbt, M. "Log-normal Distributions across the Sciences: Keys and Clues." BioScience, 2001. diff --git a/lib/numerics/normdist/Normdist.md b/lib/numerics/normdist/Normdist.md new file mode 100644 index 00000000..1153cc51 --- /dev/null +++ b/lib/numerics/normdist/Normdist.md @@ -0,0 +1,95 @@ +# NORMDIST: Normal Distribution CDF + +The Normal Distribution CDF transforms a z-score normalized price into the cumulative distribution function of the Gaussian distribution, producing an output in $[0, 1]$. Unlike other distribution indicators in this library that use min-max normalization, NORMDIST computes a rolling mean and standard deviation over the lookback window, converting the raw price to a z-score, then applies optional $\mu$ and $\sigma$ parameters for further shaping. The result represents the probability that a standard normal random variable would fall at or below the observed z-score. This makes NORMDIST a direct percentile ranking under the assumption of normally distributed returns, with the output naturally centered at 0.5 when the price is at its rolling mean. + +## Historical Context + +The normal distribution was discovered independently by Abraham de Moivre (1733) as a limit of the binomial distribution, and by Carl Friedrich Gauss (1809) in the context of astronomical measurement errors. Pierre-Simon Laplace (1812) proved the central limit theorem, establishing that sums of independent random variables converge to the normal distribution regardless of the underlying distribution. + +In finance, the normal distribution assumption for asset returns was formalized by Harry Markowitz (1952) in Modern Portfolio Theory and Louis Bachelier (1900) in his thesis on speculation. Despite well-known departures (fat tails, skewness, volatility clustering), the normal CDF remains the most widely used probability transform in quantitative finance. It underpins the Black-Scholes formula, Value-at-Risk calculations, and the Sharpe ratio. + +The z-score normalization approach used here is more statistically grounded than the min-max normalization used by other distribution indicators: it captures the rolling distributional properties (mean, variance) of the price series rather than just the range. This means NORMDIST adapts to both the level and the volatility of the price, making readings directly interpretable as "number of standard deviations from the mean." + +## Architecture and Physics + +The computation follows a three-phase pipeline: + +**Phase 1: Rolling statistics** computes the mean and standard deviation over the lookback window using a single-pass algorithm: + +$$\bar{x} = \frac{1}{n}\sum_{i=0}^{n-1} x_i, \quad s = \sqrt{\frac{1}{n}\sum_{i=0}^{n-1} x_i^2 - \bar{x}^2}$$ + +NaN values are excluded from the count. If fewer than 2 valid values exist, the output defaults to 0.5. + +**Phase 2: Z-score with parameter adjustment** converts the price to a z-score relative to the rolling distribution, then applies the user-specified shift and scale: + +$$z = \frac{x - \bar{x}}{s}, \quad z_{\text{final}} = \frac{z - \mu}{\sigma}$$ + +With defaults $\mu = 0, \sigma = 1$, $z_{\text{final}} = z$ (standard z-score). Increasing $\sigma$ compresses the CDF curve (less sensitive to deviations); shifting $\mu$ moves the midpoint away from the rolling mean. + +**Phase 3: Error function approximation** evaluates $\Phi(z)$ using the Abramowitz and Stegun formula (7.1.26) with 3 polynomial terms in the exponential approximation of `erf`: + +$$\text{erf}(x) \approx 1 - (a_1 t + a_2 t^2 + a_3 t^3) \cdot e^{-x^2}$$ + +where $t = 1/(1 + 0.47047|x|)$. The CDF is then $\Phi(z) = 0.5(1 + \text{erf}(z/\sqrt{2}))$. + +**Accuracy**: The 3-term Abramowitz-Stegun approximation achieves maximum error of $\sim 2.5 \times 10^{-5}$, sufficient for indicator applications. For higher precision, the 5-term version (used in LOGNORMDIST) reduces error to $\sim 1.5 \times 10^{-7}$. + +## Mathematical Foundation + +The standard normal PDF and CDF: + +$$\phi(z) = \frac{1}{\sqrt{2\pi}} e^{-z^2/2}$$ + +$$\Phi(z) = \frac{1}{2}\left(1 + \text{erf}\!\left(\frac{z}{\sqrt{2}}\right)\right) = \int_{-\infty}^{z} \phi(t)\,dt$$ + +The **error function**: + +$$\text{erf}(x) = \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2}\,dt$$ + +**Abramowitz and Stegun 3-term approximation**: + +$$\text{erf}(x) \approx 1 - (a_1 t + a_2 t^2 + a_3 t^3) e^{-x^2}, \quad t = \frac{1}{1 + 0.47047\,|x|}$$ + +with $a_1 = 0.3480242$, $a_2 = -0.0958798$, $a_3 = 0.7478556$. + +**Z-score normalization** (population standard deviation, not sample): + +$$z = \frac{x - \bar{x}}{s}, \quad s = \sqrt{\frac{\sum x_i^2}{n} - \left(\frac{\sum x_i}{n}\right)^2}$$ + +**Key CDF values**: $\Phi(0) = 0.5$, $\Phi(1) \approx 0.841$, $\Phi(2) \approx 0.977$, $\Phi(-1) \approx 0.159$, $\Phi(-2) \approx 0.023$. + +**Parameter constraints**: `period` $> 0$, $\sigma > 0$, $\mu \in \mathbb{R}$. Output is bounded $[0, 1]$. + +``` +NORMDIST(source, period, mu, sigma): + // Phase 1: rolling statistics + sum = 0; sumSq = 0; count = 0 + for i = 0 to period-1: + if not NaN(source[i]): + sum += source[i] + sumSq += source[i]^2 + count += 1 + if count < 2: return 0.5 + mean = sum / count + variance = sumSq/count - mean^2 + stddev = sqrt(max(0, variance)) + + // Phase 2: z-score with parameter adjustment + z = stddev > 0 ? (source - mean) / stddev : 0 + z_final = (z - mu) / sigma + + // Phase 3: erf approximation -> CDF + x = z_final / sqrt(2) + t = 1 / (1 + 0.47047 * |x|) + erf = 1 - (0.3480242*t + (-0.0958798)*t^2 + 0.7478556*t^3) * exp(-x^2) + if x < 0: erf = -erf + return 0.5 * (1 + erf) +``` + +## Resources + +- Gauss, C.F. "Theoria Motus Corporum Coelestium." 1809. +- Abramowitz, M. & Stegun, I. "Handbook of Mathematical Functions." NBS Applied Mathematics Series 55, 1964. Formulas 7.1.25-7.1.28. +- Markowitz, H. "Portfolio Selection." Journal of Finance, 1952. +- Johnson, N.L., Kotz, S. & Balakrishnan, N. "Continuous Univariate Distributions, Vol. 1." Wiley, 1994. +- Hart, J.F. et al. "Computer Approximations." Wiley, 1968. diff --git a/lib/numerics/normdist/normdist.pine b/lib/numerics/normdist/normdist.pine new file mode 100644 index 00000000..1e972e8f --- /dev/null +++ b/lib/numerics/normdist/normdist.pine @@ -0,0 +1,80 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Normal Distribution CDF (NORMDIST)", "NORMDIST", overlay=false, precision=6) + +//@function Computes Normal Distribution CDF for a normalized price series +//@param source Series to transform +//@param period Lookback period for z-score normalization +//@param mu Mean parameter (0.0 for standard normal after z-score) +//@param sigma Standard deviation parameter (1.0 for standard normal after z-score) +//@returns CDF value in [0,1]: Φ(z) = 0.5 × (1 + erf(z / √2)) +//@optimized O(period) per bar for mean/variance scan; CDF itself is O(1) +normdist(series float source, simple int period, simple float mu, simple float sigma) => + if period <= 0 + runtime.error("Period must be greater than 0") + if sigma <= 0.0 + runtime.error("Sigma must be greater than 0") + + // Compute rolling mean and standard deviation over lookback + float sum = 0.0 + float sumSq = 0.0 + int count = 0 + for i = 0 to period - 1 + float v = source[i] + if not na(v) + sum += v + sumSq += v * v + count += 1 + + float result = 0.5 + if count >= 2 + float mean = sum / count + float variance = (sumSq / count) - (mean * mean) + float stddev = variance > 0.0 ? math.sqrt(variance) : 0.0 + + // Z-score: normalize source relative to its own rolling distribution + float z = stddev > 0.0 ? (source - mean) / stddev : 0.0 + + // Apply user-specified mu/sigma shift: z_final = (z - mu) / sigma + float z_final = (z - mu) / sigma + + // Approximate erf via Abramowitz & Stegun (max error < 1.5e-7) + // erf(x) = 1 - (a1*t + a2*t^2 + a3*t^3) * exp(-x^2) + // where t = 1 / (1 + 0.47047 * |x|) + float x = z_final / math.sqrt(2.0) + float ax = math.abs(x) + float t = 1.0 / (1.0 + 0.47047 * ax) + float t2 = t * t + float t3 = t2 * t + + float a1 = 0.3480242 + float a2 = -0.0958798 + float a3 = 0.7478556 + + float erfApprox = 1.0 - (a1 * t + a2 * t2 + a3 * t3) * math.exp(-(ax * ax)) + float erf = x >= 0.0 ? erfApprox : -erfApprox + + // CDF: Φ(z) = 0.5 * (1 + erf(z / sqrt(2))) + result := 0.5 * (1.0 + erf) + + result + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") +i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Rolling window for z-score normalization") +i_mu = input.float(0.0, "Mu (μ)", step=0.1, tooltip="Mean shift parameter (0 = standard normal)") +i_sigma = input.float(1.0, "Sigma (σ)", minval=0.01, step=0.1, tooltip="Scale parameter (1 = standard normal)") + +// Calculation +float result = normdist(i_source, i_period, i_mu, i_sigma) + +// Plot +plot(result, "NORMDIST", color=color.yellow, linewidth=2) +hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted) +hline(0.975, "Upper 2σ", color=color.red, linestyle=hline.style_dashed) +hline(0.025, "Lower 2σ", color=color.green, linestyle=hline.style_dashed) +hline(0.841, "Upper 1σ", color=color.orange, linestyle=hline.style_dashed) +hline(0.159, "Lower 1σ", color=color.teal, linestyle=hline.style_dashed) diff --git a/lib/numerics/poissondist/Poissondist.md b/lib/numerics/poissondist/Poissondist.md new file mode 100644 index 00000000..3ba5b6b4 --- /dev/null +++ b/lib/numerics/poissondist/Poissondist.md @@ -0,0 +1,73 @@ +# POISSONDIST: Poisson Distribution CDF + +The Poisson Distribution CDF computes the probability $P(X \le k)$ for a Poisson random variable whose rate parameter $\lambda$ is derived from the min-max normalized price. The Poisson distribution models the number of events in a fixed interval given a constant average rate, making it natural for count-based financial metrics (trade arrivals, tick counts, order flow). The implementation maps normalized price to $\lambda$ via a scale factor, then evaluates the CDF using the identity $P(X \le k) = 1 - P(k+1, \lambda)$ where $P(a, x)$ is the regularized lower incomplete gamma function. This reuses the same Lanczos log-gamma and series/continued-fraction infrastructure as GAMMADIST. + +## Historical Context + +The Poisson distribution was derived by Simeon Denis Poisson (1837) as a limiting case of the binomial distribution when the number of trials is large and the success probability is small. Ladislaus Bortkiewicz (1898) famously demonstrated its applicability by modeling deaths from horse kicks in the Prussian army, establishing it as the canonical distribution for rare events. + +In financial markets, Poisson processes model trade arrivals in market microstructure theory (O'Hara, 1995), jump events in Merton's jump-diffusion model (1976), and order book dynamics. The CDF form used here provides a probability-weighted indicator: for a given threshold $k$ and price-derived rate $\lambda$, the output answers "what is the probability that a Poisson process with rate proportional to the normalized price would produce at most $k$ events?" + +When the normalized price is low (near 0), $\lambda$ is small and the CDF is close to 1 (almost certainly $\le k$ events). When normalized price approaches 1, $\lambda$ is large and the CDF drops (many events expected, exceeding $k$ becomes likely). The `lambda_scale` parameter controls the dynamic range: higher values cause broader CDF variation across the $[0, 1]$ normalized range. + +## Architecture and Physics + +The computation follows a three-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars for extrema, maps the current source to $x \in [0, 1]$, then derives the rate: $\lambda = \max(0, x \cdot \text{lambda\_scale})$. + +**Phase 2: Degenerate case** handles $\lambda = 0$ by returning 1.0 (Poisson with rate 0 puts all mass at $X = 0$, so $P(X \le k) = 1$ for any $k \ge 0$). + +**Phase 3: Gamma function identity** uses the well-known relationship between the Poisson CDF and the regularized incomplete gamma function: + +$$P(X \le k) = 1 - P(k + 1, \lambda) = Q(k + 1, \lambda)$$ + +where $P(a, x)$ is the regularized lower incomplete gamma and $Q$ is its complement. The implementation delegates to `gammaP()`, which internally selects between series expansion (for $\lambda < k + 2$) and Lentz continued fraction (otherwise). + +**Threshold parameter $k$**: Integer-valued, controls the step function shape. Small $k$ (0-2) creates a steep CDF that drops rapidly as $\lambda$ increases. Large $k$ (10+) creates a gentle curve that stays near 1.0 until $\lambda$ significantly exceeds $k$. + +## Mathematical Foundation + +The Poisson distribution with rate $\lambda > 0$ has PMF: + +$$P(X = n) = \frac{\lambda^n e^{-\lambda}}{n!}, \quad n = 0, 1, 2, \ldots$$ + +The CDF is: + +$$F(k; \lambda) = P(X \le k) = e^{-\lambda} \sum_{n=0}^{k} \frac{\lambda^n}{n!}$$ + +The **gamma function identity** connects this to the incomplete gamma: + +$$P(X \le k) = 1 - P(k+1, \lambda) = \frac{\Gamma(k+1, \lambda)}{k!}$$ + +where $\Gamma(a, x) = \int_x^\infty t^{a-1} e^{-t}\,dt$ is the upper incomplete gamma function and $P(a, x) = \gamma(a, x)/\Gamma(a)$ is the regularized lower incomplete gamma. + +**Moments**: $E[X] = \lambda$, $\text{Var}(X) = \lambda$, $\text{Skew} = 1/\sqrt{\lambda}$. + +**Normal approximation**: For large $\lambda$, $\text{Poisson}(\lambda) \approx N(\lambda, \lambda)$. + +**Parameter constraints**: `period` $> 0$, $k \ge 0$ (integer), `lambda_scale` $> 0$. Output is bounded $[0, 1]$. + +``` +POISSONDIST(source, period, k, lambda_scale): + // Phase 1: min-max normalization + rate derivation + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + lambda = max(0, x * lambda_scale) + + // Phase 2: degenerate case + if lambda <= 0: return 1.0 + + // Phase 3: CDF via incomplete gamma identity + return 1.0 - gammaP(k + 1, lambda) +``` + +## Resources + +- Poisson, S.D. "Recherches sur la probabilite des jugements en matiere criminelle et en matiere civile." 1837. +- Bortkiewicz, L. "Das Gesetz der kleinen Zahlen." Teubner, 1898. +- Merton, R.C. "Option Pricing When Underlying Stock Returns Are Discontinuous." Journal of Financial Economics, 1976. +- O'Hara, M. "Market Microstructure Theory." Blackwell, 1995. +- Press, W.H. et al. "Numerical Recipes: The Art of Scientific Computing." 3rd edition, Cambridge University Press, 2007. Chapter 6.2. diff --git a/lib/numerics/tdist/Tdist.md b/lib/numerics/tdist/Tdist.md new file mode 100644 index 00000000..2724d468 --- /dev/null +++ b/lib/numerics/tdist/Tdist.md @@ -0,0 +1,86 @@ +# TDIST: Student's t-Distribution CDF + +The Student's t-Distribution CDF transforms a min-max normalized price into the cumulative distribution function of Student's t-distribution, producing an output in $[0, 1]$. The t-distribution is the normal distribution's heavier-tailed cousin: as degrees of freedom $\nu$ increase, it converges to the Gaussian; at low $\nu$ it accommodates extreme values that the normal distribution would assign negligible probability. The implementation normalizes price to $[0, 1]$, maps to a t-statistic via linear scaling to $[-3, +3]$, then evaluates the CDF through the regularized incomplete beta function. This makes TDIST a robust percentile ranking that is less sensitive to outliers than NORMDIST. + +## Historical Context + +The t-distribution was derived by William Sealy Gosset (1908), publishing under the pseudonym "Student" while employed at the Guinness brewery. Gosset needed to make statistical inferences from small sample sizes where the population variance was unknown. Ronald Fisher (1925) generalized the distribution and introduced the degrees-of-freedom parameter. + +In finance, the t-distribution has become central to fat-tailed modeling. Empirical studies consistently show that asset returns have heavier tails than the normal distribution (Mandelbrot, 1963; Fama, 1965). The t-distribution with $\nu \approx 4\text{-}6$ provides a reasonable fit to daily equity returns, and it underpins GARCH-t models, Student-t copulas in credit risk, and robust regression in factor modeling. + +The CDF is computed via the identity connecting it to the regularized incomplete beta function: + +$$F(t; \nu) = \begin{cases} 1 - \frac{1}{2} I_x\!\left(\frac{\nu}{2}, \frac{1}{2}\right) & t \ge 0 \\ \frac{1}{2} I_x\!\left(\frac{\nu}{2}, \frac{1}{2}\right) & t < 0 \end{cases}$$ + +where $x = \nu/(\nu + t^2)$. This reuses the same Lanczos log-gamma and Lentz continued fraction infrastructure as BETADIST and FDIST. + +## Architecture and Physics + +The computation follows a four-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars for extrema, maps the current source to $x \in [0, 1]$. + +**Phase 2: t-statistic mapping** transforms $x$ to a t-value via linear scaling: + +$$t = (x - 0.5) \times 6.0$$ + +This maps $[0, 1]$ to $[-3, +3]$, covering approximately 99.7% of the standard normal range and the bulk of any t-distribution with $\nu \ge 3$. + +**Phase 3: Beta function argument** converts the t-statistic to the incomplete beta argument: + +$$\text{bx} = \frac{\nu}{\nu + t^2}$$ + +For $t = 0$, $\text{bx} = 1$ and the CDF returns 0.5 (symmetric around zero). As $|t|$ grows, $\text{bx}$ approaches 0. + +**Phase 4: Regularized incomplete beta** evaluates $I_{\text{bx}}(\nu/2, 1/2)$ via the Lentz continued fraction with symmetry flip for numerical stability. The sign of $t$ determines whether the result is in the lower or upper tail. + +**Degrees-of-freedom effects**: $\nu = 1$ gives the Cauchy distribution (extremely heavy tails, no finite mean). $\nu = 5$ gives moderately heavy tails. $\nu = 30$ is nearly indistinguishable from the normal. $\nu \to \infty$ converges to $N(0, 1)$. + +## Mathematical Foundation + +The Student's t-distribution with $\nu$ degrees of freedom has PDF: + +$$f(t; \nu) = \frac{\Gamma\!\left(\frac{\nu+1}{2}\right)}{\sqrt{\nu\pi}\;\Gamma\!\left(\frac{\nu}{2}\right)} \left(1 + \frac{t^2}{\nu}\right)^{-(\nu+1)/2}$$ + +The CDF via regularized incomplete beta: + +$$F(t; \nu) = \begin{cases} 1 - \frac{1}{2} I_x\!\left(\frac{\nu}{2}, \frac{1}{2}\right) & t \ge 0 \\[4pt] \frac{1}{2} I_x\!\left(\frac{\nu}{2}, \frac{1}{2}\right) & t < 0 \end{cases}$$ + +where $x = \frac{\nu}{\nu + t^2}$. + +**Moments** (defined only when $\nu$ is sufficiently large): + +$$E[T] = 0 \;(\nu > 1), \quad \text{Var}(T) = \frac{\nu}{\nu - 2} \;(\nu > 2), \quad \text{Kurt} = \frac{6}{\nu - 4} \;(\nu > 4)$$ + +**Convergence to normal**: As $\nu \to \infty$, $t_\nu \to N(0,1)$. For practical purposes, $\nu \ge 30$ produces CDF values within $10^{-3}$ of the normal CDF. + +**Parameter constraints**: `period` $> 0$, $\nu > 0$. Output is bounded $[0, 1]$. + +``` +TDIST(source, period, df): + // Phase 1: min-max normalization + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + + // Phase 2: t-statistic mapping + t = (x - 0.5) * 6.0 + + // Phase 3: beta argument + bx = df / (df + t*t) + + // Phase 4: CDF via incomplete beta + ibeta = betaReg(bx, df/2, 0.5) + if t >= 0: return 1.0 - 0.5 * ibeta + else: return 0.5 * ibeta +``` + +## Resources + +- Student (Gosset, W.S.). "The Probable Error of a Mean." Biometrika, 1908. +- Fisher, R.A. "Statistical Methods for Research Workers." Oliver and Boyd, 1925. +- Mandelbrot, B. "The Variation of Certain Speculative Prices." Journal of Business, 1963. +- Fama, E.F. "The Behavior of Stock-Market Prices." Journal of Business, 1965. +- Bollerslev, T. "Generalized Autoregressive Conditional Heteroskedasticity." Journal of Econometrics, 1986. +- Press, W.H. et al. "Numerical Recipes: The Art of Scientific Computing." 3rd edition, Cambridge University Press, 2007. Chapter 6.4. diff --git a/lib/numerics/weibulldist/Weibulldist.md b/lib/numerics/weibulldist/Weibulldist.md new file mode 100644 index 00000000..c0b00791 --- /dev/null +++ b/lib/numerics/weibulldist/Weibulldist.md @@ -0,0 +1,86 @@ +# WEIBULLDIST: Weibull Distribution CDF + +The Weibull Distribution CDF transforms a min-max normalized price into the cumulative distribution function of the Weibull distribution, producing an output in $[0, 1]$. The Weibull distribution is a flexible two-parameter family that subsumes the exponential distribution ($k = 1$) and approximates the normal distribution ($k \approx 3.6$) as special cases. Its closed-form CDF requires only `pow` and `exp`, making it the computationally cheapest distribution indicator after EXPDIST. The shape parameter $k$ controls the CDF curvature: $k < 1$ produces a concave curve (rapid initial rise), $k = 1$ gives the exponential, $k = 2$ produces the Rayleigh distribution, and $k > 3$ creates an S-shaped curve approaching Gaussian behavior. + +## Historical Context + +The Weibull distribution was formalized by Waloddi Weibull (1951) for modeling material fatigue and breaking strength, though the mathematical form appeared earlier in work by Rosin and Rammler (1933) on particle size distributions and Frechet (1927) on extreme value theory. It is one of three extreme value distributions (alongside Gumbel and Frechet), making it theoretically grounded for modeling maxima and minima of samples. + +In engineering, the Weibull distribution dominates reliability analysis: the shape parameter $k$ (also called the Weibull modulus) characterizes the failure rate. $k < 1$ means decreasing failure rate (infant mortality), $k = 1$ means constant failure rate (random failures), and $k > 1$ means increasing failure rate (wear-out). This maps to financial interpretation: $k < 1$ emphasizes breakouts from the bottom of the range (rapid CDF rise for small normalized values), while $k > 1$ emphasizes breakouts near the top (CDF stays low until normalized value approaches the scale parameter). + +The scale parameter $\lambda$ controls the characteristic life: the value at which the CDF equals $1 - e^{-1} \approx 0.632$. With default $\lambda = 0.5$, the CDF reaches 63.2% when the normalized price is at the midpoint of the recent range. + +## Architecture and Physics + +The computation follows a two-phase pipeline: + +**Phase 1: Min-max normalization** scans `period` bars for extrema, maps the current source to $x \in [0, 1]$. Zero-range defaults to 0.5. + +**Phase 2: Closed-form CDF** evaluates: + +$$F(x) = 1 - \exp\!\left(-\left(\frac{x}{\lambda}\right)^k\right)$$ + +with a floor of $x = 0$ (negative values impossible after normalization). The computation requires one division, one `pow`, one negation, and one `exp`. No special functions, no iterations, no convergence checks. + +**Shape parameter effects on the CDF curve:** + +| $k$ | Character | Financial Interpretation | +|-----|-----------|------------------------| +| 0.5 | Steep concave | Highly sensitive to any move off the low | +| 1.0 | Exponential | Memoryless; equivalent to EXPDIST with $\lambda = 1/\text{scale}$ | +| 2.0 | Rayleigh | Linear failure rate; moderate S-curve | +| 3.6 | Near-Gaussian | Approximate normal CDF shape | +| 5.0+ | Steep sigmoid | Insensitive until price nears the scale point, then jumps | + +**Scale parameter effects**: $\lambda = 0.25$ compresses the transition zone toward low normalized values (CDF saturates quickly). $\lambda = 1.0$ spreads the transition across the entire $[0, 1]$ range (CDF is gentler). Default $\lambda = 0.5$ centers the characteristic value at the midpoint. + +## Mathematical Foundation + +The Weibull distribution with shape $k > 0$ and scale $\lambda > 0$ has PDF and CDF: + +$$f(x; k, \lambda) = \frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{k-1} \exp\!\left(-\left(\frac{x}{\lambda}\right)^k\right), \quad x \ge 0$$ + +$$F(x; k, \lambda) = 1 - \exp\!\left(-\left(\frac{x}{\lambda}\right)^k\right), \quad x \ge 0$$ + +**Inverse CDF** (quantile function): + +$$F^{-1}(p) = \lambda \left(-\ln(1 - p)\right)^{1/k}$$ + +**Moments:** + +$$E[X] = \lambda\,\Gamma\!\left(1 + \frac{1}{k}\right)$$ + +$$\text{Var}(X) = \lambda^2 \left[\Gamma\!\left(1 + \frac{2}{k}\right) - \Gamma^2\!\left(1 + \frac{1}{k}\right)\right]$$ + +**Hazard function** (failure rate): + +$$h(x) = \frac{f(x)}{1 - F(x)} = \frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{k-1}$$ + +This is increasing for $k > 1$, constant for $k = 1$, and decreasing for $k < 1$. + +**Special cases**: $k = 1 \Rightarrow \text{Exponential}(\lambda)$. $k = 2 \Rightarrow \text{Rayleigh}(\lambda/\sqrt{2})$. + +**Parameter constraints**: `period` $> 0$, $k > 0$, $\lambda > 0$. Output is bounded $[0, 1]$. + +``` +WEIBULLDIST(source, period, shape, scale): + // Phase 1: min-max normalization + min_val = min(source[0..period-1]) + max_val = max(source[0..period-1]) + range = max_val - min_val + x = range > 0 ? (source - min_val) / range : 0.5 + + // Phase 2: closed-form CDF + safe_x = max(0, x) + ratio = safe_x / scale + raised = pow(ratio, shape) + return 1.0 - exp(-raised) +``` + +## Resources + +- Weibull, W. "A Statistical Distribution Function of Wide Applicability." Journal of Applied Mechanics, 1951. +- Frechet, M. "Sur la loi de probabilite de l'ecart maximum." Ann. Soc. Polon. Math., 1927. +- Rinne, H. "The Weibull Distribution: A Handbook." CRC Press, 2009. +- Abernethy, R.B. "The New Weibull Handbook." 5th edition, 2006. +- Johnson, N.L., Kotz, S. & Balakrishnan, N. "Continuous Univariate Distributions, Vol. 1." Wiley, 1994. diff --git a/lib/oscillators/_index.md b/lib/oscillators/_index.md index 9f186dec..8e0fa1b8 100644 --- a/lib/oscillators/_index.md +++ b/lib/oscillators/_index.md @@ -14,10 +14,14 @@ Oscillators fluctuate above and below a centerline or within bounded ranges. Use | [CFO](cfo/Cfo.md) | Chande Forecast Oscillator | Percentage difference between price and linear regression forecast. | | [DECO](deco/Deco.md) | Ehlers Decycler Oscillator | Dual HP bandpass isolating intermediate-frequency market cycles. | | [DPO](dpo/Dpo.md) | Detrended Price Oscillator | Removes trend via displaced SMA. Reveals cycles. | +| [ER](er/Er.md) | Efficiency Ratio | Measures directional efficiency. Net movement / total path length. | +| [ERI](eri/Eri.md) | Elder Ray Index | Separates bull and bear power relative to EMA. | | [FISHER](fisher/Fisher.md) | Ehlers Fisher Transform | Converts prices to Gaussian distribution. Sharp reversals. | | [INERTIA](inertia/Inertia.md) | Inertia | Linear regression residual. Raw deviation from trend forecast. | | [KDJ](kdj/Kdj.md) | KDJ Indicator | Enhanced Stochastic. J = 3K - 2D provides leading signal. | +| [KRI](kri/Kri.md) | Kairi Relative Index | Percentage deviation of price from SMA. Overbought/oversold. | | [PGO](pgo/Pgo.md) | Pretty Good Oscillator | Distance from SMA normalized by ATR. Units: ATR multiples. | +| [PSL](psl/Psl.md) | Psychological Line | Ratio of up periods to total periods. Crowd sentiment gauge. | | [SMI](smi/Smi.md) | Stochastic Momentum Index | Distance from range midpoint. More sensitive than classic Stochastic. | | [STOCH](stoch/Stoch.md) | Stochastic Oscillator | Close position within N-period high-low range. Classic overbought/oversold. | | [STOCHF](stochf/Stochf.md) | Stochastic Fast | Unsmoothed Stochastic. Faster but noisier. | diff --git a/lib/oscillators/brar/Brar.md b/lib/oscillators/brar/Brar.md new file mode 100644 index 00000000..6aa45b99 --- /dev/null +++ b/lib/oscillators/brar/Brar.md @@ -0,0 +1,63 @@ +# BRAR: Atmosphere and Buying Ratio Indicator + +BRAR is a dual-output sentiment oscillator from East Asian technical analysis that decomposes intrabar price dynamics into two independent ratios: AR (Atmosphere Ratio) measuring the relationship between opening price and intrabar range, and BR (Buying Ratio) measuring buying pressure relative to the previous close. The indicator produces two lines oscillating around 100, where AR above 100 indicates bullish intrabar sentiment and BR above 100 indicates net buying pressure over the lookback window. + +## Historical Context + +BRAR originated in Japanese and Taiwanese equity analysis during the 1980s, where it became a standard feature of domestic charting software before gaining broader recognition in quantitative trading. The indicator belongs to a class of OHLC decomposition oscillators that extract directional information from the relationship between open, high, low, and close prices rather than from close-only series. Unlike Western momentum oscillators that typically operate on a single price input, BRAR requires full OHLC bars, making it structurally similar to Williams %R or Stochastic but with fundamentally different decomposition logic. The "atmosphere" terminology reflects the Japanese market philosophy that open-to-range dynamics capture collective market mood, while the "buying ratio" component captures institutional accumulation pressure relative to settlement prices. + +## Architecture & Physics + +### Dual-Component Design + +BRAR separates intrabar dynamics into two independent measurements: + +1. **AR (Atmosphere Ratio):** Measures the open's position within the intrabar range. Numerator accumulates $(H_i - O_i)$ over $n$ bars (upside from open), denominator accumulates $(O_i - L_i)$ (downside from open). The ratio, scaled by 100, indicates whether prices tend to rally or decline from the opening price. + +2. **BR (Buying Ratio):** Measures buying pressure relative to the previous close. Numerator accumulates $\max(0, H_i - C_{i-1})$ (gains above prior close), denominator accumulates $\max(0, C_{i-1} - L_i)$ (drops below prior close). The ratio captures net accumulation vs distribution. + +### Running Sum Architecture + +Both ratios maintain four independent circular buffers with running sums for O(1) streaming updates. When buffer is full, the oldest bar's contribution is subtracted before the new bar's contribution is added. The first close comparison uses open as a fallback when no previous close exists. + +### Defensive Division + +Both AR and BR return 0.0 when their respective denominators are zero, preventing division-by-zero in flat markets where open equals low (AR) or prior close equals low with no upside (BR). + +## Mathematical Foundation + +Given OHLC bars $(O_i, H_i, L_i, C_i)$ and lookback period $n$: + +**AR (Atmosphere Ratio):** + +$$AR = \frac{\sum_{i=1}^{n} (H_i - O_i)}{\sum_{i=1}^{n} (O_i - L_i)} \times 100$$ + +**BR (Buying Ratio):** + +$$BR = \frac{\sum_{i=1}^{n} \max(0,\; H_i - C_{i-1})}{\sum_{i=1}^{n} \max(0,\; C_{i-1} - L_i)} \times 100$$ + +**Streaming update** (per bar, O(1)): + +```text +arNum_new = arNum_old - oldest_arNum + (H - O) +arDen_new = arDen_old - oldest_arDen + (O - L) +brNum_new = brNum_old - oldest_brNum + max(0, H - prevClose) +brDen_new = brDen_old - oldest_brDen + max(0, prevClose - L) + +AR = (arDen ≠ 0) ? (arNum / arDen) × 100 : 0 +BR = (brDen ≠ 0) ? (brNum / brDen) × 100 : 0 +``` + +**Interpretation reference levels:** + +- AR > 100, BR > 100: Strong bullish sentiment +- AR < 100, BR < 100: Strong bearish sentiment +- AR and BR divergence: Potential trend reversal signal + +**Default parameters:** period = 26 (approximately one trading month). + +## Resources + +- Japanese Technical Analysis references on AR/BR sentiment indicators +- Taiwan Stock Exchange historical charting methodology +- PineScript reference: [`brar.pine`](brar.pine) diff --git a/lib/oscillators/coppock/Coppock.md b/lib/oscillators/coppock/Coppock.md new file mode 100644 index 00000000..08b39879 --- /dev/null +++ b/lib/oscillators/coppock/Coppock.md @@ -0,0 +1,59 @@ +# COPPOCK: Coppock Curve + +The Coppock Curve is a long-term momentum oscillator that applies a Weighted Moving Average to the sum of two Rate of Change calculations at different lookback periods. Originally designed for monthly charts to identify major market bottoms, it produces a single oscillating line where zero-line crossovers from below signal long-term buying opportunities. The dual-ROC architecture captures both intermediate and longer-term momentum dynamics in a single smoothed output. + +## Historical Context + +Edwin Sedgwick Coppock introduced this indicator in 1962 in *Barron's* magazine, originally calling it the "Trendex Model." Coppock, an economist by training, reportedly derived the 11-month and 14-month ROC periods from Episcopal clergy who told him the average mourning period for a bereavement was 11 to 14 months. He reasoned that market bottoms represented a similar psychological recovery period. The indicator was designed exclusively as a buy signal generator on monthly S&P 500 data, with zero-line crossovers from negative territory signaling major market lows. Later practitioners adapted it to weekly and daily timeframes with scaled parameters, though Coppock himself considered only the monthly application valid. The 10-period WMA smoothing was chosen to filter out intermediate noise while preserving the timing of major turning points. + +## Architecture & Physics + +### Three-Stage Pipeline + +The Coppock Curve processes data through a sequential pipeline: + +1. **ROC Stage:** Two independent Rate of Change calculations at different lookback periods extract momentum at two time horizons. Each ROC measures the percentage price change over its respective window: $\text{ROC}(n) = \frac{C_t - C_{t-n}}{C_{t-n}} \times 100$. + +2. **Summation Stage:** The two ROC values are added directly, creating a composite momentum measure that captures both intermediate and long-term price velocity. + +3. **WMA Stage:** A Weighted Moving Average smooths the combined ROC, using linearly increasing weights that emphasize recent composite momentum while suppressing noise. The WMA implementation uses the dual running sum technique for O(1) per-bar updates. + +### Circular Buffer Design + +The ROC stage stores historical prices in a circular buffer sized to the maximum of the two ROC periods. The WMA stage maintains its own circular buffer with running weighted and unweighted sums, enabling constant-time updates without recomputation. + +## Mathematical Foundation + +Given source series $x_t$, long ROC period $L$, short ROC period $S$, and WMA period $W$: + +**Rate of Change:** + +$$ROC_L(t) = \frac{x_t - x_{t-L}}{x_{t-L}} \times 100, \quad ROC_S(t) = \frac{x_t - x_{t-S}}{x_{t-S}} \times 100$$ + +**Combined ROC:** + +$$R_t = ROC_L(t) + ROC_S(t)$$ + +**Weighted Moving Average of combined ROC:** + +$$\text{Coppock}(t) = \frac{\sum_{i=0}^{W-1} (W - i) \cdot R_{t-i}}{\sum_{i=0}^{W-1} (W - i)}$$ + +The denominator equals $\frac{W(W+1)}{2}$. + +**O(1) WMA streaming update** using dual running sums: + +```text +On new value R entering buffer (oldest R_old exits): + plainSum = plainSum - R_old + R + weightedSum = weightedSum - plainSum_old + W × R + norm = W × (W + 1) / 2 + Coppock = weightedSum / norm +``` + +**Default parameters:** longRoc = 14, shortRoc = 11, wmaPeriod = 10 (original monthly values). + +## Resources + +- Coppock, E.S.C. (1962). "A Guide to the Use of Coppock Curve." *Barron's* +- Kirkpatrick, C. & Dahlquist, J. (2010). *Technical Analysis*, Chapter 15: Momentum +- PineScript reference: [`coppock.pine`](coppock.pine) diff --git a/lib/oscillators/crsi/Crsi.md b/lib/oscillators/crsi/Crsi.md new file mode 100644 index 00000000..13e97654 --- /dev/null +++ b/lib/oscillators/crsi/Crsi.md @@ -0,0 +1,63 @@ +# CRSI: Connors RSI + +Connors RSI is a composite momentum oscillator that combines three independent measurements of price behavior into a single bounded (0-100) output: a short-term RSI of price, an RSI of the consecutive up/down streak length, and a percentile rank of the current rate of change within its recent history. The equal-weighted average of these three components produces a mean-reverting oscillator where extreme readings (above 90 or below 10) identify statistically overbought or oversold conditions with higher reliability than single-component RSI alone. + +## Historical Context + +Larry Connors and Cesar Alvarez introduced Connors RSI in their 2012 publication, building on Connors' earlier research into short-term mean reversion strategies. The indicator addressed a recognized weakness of standard RSI: its tendency to remain in overbought or oversold territory during strong trends without providing actionable reversal signals. By combining three orthogonal measurements of price behavior, each capturing a different aspect of momentum, CRSI reduces the false signal rate inherent in any single oscillator. The streak RSI component was particularly novel, converting the categorical information of consecutive up/down days into a continuous oscillator via a second RSI application. The percent rank component adds a non-parametric statistical dimension that is robust to distribution assumptions. Connors' backtesting showed the composite outperformed standard RSI for mean-reversion entry timing on equity indices and ETFs. + +## Architecture & Physics + +### Three-Component Pipeline + +CRSI combines three independent calculations with equal weighting: + +1. **Price RSI** (Component 1): Standard Wilder RSI with exponential smoothing ($\alpha = 1/\text{rsiPeriod}$) applied to the source series. Uses warmup compensation via the decaying exponential $e = \beta^n$ to correct for initial bias, producing valid output from bar 1. + +2. **Streak RSI** (Component 2): First computes a consecutive streak counter (positive for up-closes, negative for down-closes, zero for unchanged), then applies the same Wilder RSI to the streak series. This converts run-length information into a bounded oscillator. + +3. **Percent Rank** (Component 3): Computes 1-bar ROC, stores in a circular buffer, then counts what percentage of historical ROC values are less than or equal to the current ROC. This is a non-parametric ranking that is distribution-free. + +### Warmup Compensation + +Both RSI stages use the "section 2" warmup pattern: track $e = \beta^n$ and apply correction factor $c = 1/(1 - e)$ to the raw exponential averages until $e$ drops below $10^{-10}$. This eliminates the startup bias that plagues naive EMA initialization. + +### Final Composition + +The three components are averaged and clamped to $[0, 100]$: + +$$\text{CRSI} = \text{clamp}\!\left(\frac{\text{PriceRSI} + \text{StreakRSI} + \text{PctRank}}{3}, 0, 100\right)$$ + +## Mathematical Foundation + +**Component 1: Price RSI** with Wilder smoothing ($\alpha = 1/p_1$): + +$$\overline{G}_t = \alpha \cdot \max(\Delta x_t, 0) + (1-\alpha) \cdot \overline{G}_{t-1}$$ + +$$\overline{L}_t = \alpha \cdot \max(-\Delta x_t, 0) + (1-\alpha) \cdot \overline{L}_{t-1}$$ + +$$RSI_1 = \frac{100 \cdot \overline{G}_t}{\overline{G}_t + \overline{L}_t}$$ + +**Component 2: Streak counter** then RSI: + +$$\text{streak}_t = \begin{cases} \text{streak}_{t-1} + 1 & \text{if } x_t > x_{t-1} \text{ and streak}_{t-1} \geq 0 \\ 1 & \text{if } x_t > x_{t-1} \text{ and streak}_{t-1} < 0 \\ \text{streak}_{t-1} - 1 & \text{if } x_t < x_{t-1} \text{ and streak}_{t-1} \leq 0 \\ -1 & \text{if } x_t < x_{t-1} \text{ and streak}_{t-1} > 0 \\ 0 & \text{otherwise} \end{cases}$$ + +$$RSI_2 = \text{Wilder\_RSI}(\text{streak}_t, p_2)$$ + +**Component 3: Percent Rank** of 1-bar ROC over window $p_3$: + +$$ROC_t = \frac{x_t - x_{t-1}}{x_{t-1}} \times 100$$ + +$$PctRank_t = \frac{|\{ROC_i : ROC_i \leq ROC_t,\; i \in \text{window}\}|}{|\text{window}|} \times 100$$ + +**Composite:** + +$$CRSI_t = \frac{RSI_1 + RSI_2 + PctRank}{3}$$ + +**Default parameters:** rsiPeriod = 3, streakPeriod = 2, rankPeriod = 100. + +## Resources + +- Connors, L. & Alvarez, C. (2012). *An Introduction to ConnorsRSI*. TradingMarkets +- Connors, L. (2009). *Short-Term Trading Strategies That Work*. TradingMarkets +- PineScript reference: [`crsi.pine`](crsi.pine) diff --git a/lib/oscillators/cti/Cti.md b/lib/oscillators/cti/Cti.md new file mode 100644 index 00000000..9752f304 --- /dev/null +++ b/lib/oscillators/cti/Cti.md @@ -0,0 +1,60 @@ +# CTI: Correlation Trend Indicator + +The Correlation Trend Indicator computes the Pearson correlation coefficient between the price series and a linear time index over a rolling window, producing a bounded oscillator in the range $[-1, +1]$. Values near $+1$ indicate a strong linear uptrend, values near $-1$ indicate a strong linear downtrend, and values near zero indicate no linear trend relationship. The implementation achieves O(1) complexity per bar through incremental running sums that avoid recomputing the full correlation on each update. + +## Historical Context + +The concept of measuring trend strength via linear correlation has roots in classical statistics, where Pearson's $r$ between an ordinal time index and a dependent variable quantifies how well a linear model fits the observed data. John Ehlers popularized this approach in trading contexts, noting that correlation-based trend detection is mathematically equivalent to the R-squared goodness-of-fit measure used in linear regression. CTI differs from slope-based indicators (like TSF or LSMA) by normalizing the result to a fixed $[-1, +1]$ range regardless of price scale or volatility, making it directly comparable across instruments and timeframes. This normalization property makes CTI particularly useful as a regime filter: values above a threshold (typically $\pm 0.5$) indicate trending conditions where trend-following strategies perform well, while values near zero suggest mean-reverting or choppy conditions. + +## Architecture & Physics + +### Incremental Pearson Correlation + +The standard Pearson correlation formula requires $\Sigma x$, $\Sigma y$, $\Sigma x^2$, $\Sigma y^2$, and $\Sigma xy$ over $n$ observations. For CTI, the $x$ values are sequential integers (time indices), which means $\Sigma x$ and $\Sigma x^2$ are deterministic closed-form functions of $n$ and do not require running sums. Only the $y$-dependent sums ($\Sigma y$, $\Sigma y^2$, $\Sigma xy$) need incremental maintenance. + +### Running Sum Trick for $\Sigma xy$ + +The key optimization is the incremental update of $\Sigma xy$. When the window slides forward by one bar: +- The oldest value exits at what was position 0 and all remaining values shift down by one position. +- Rather than recomputing all $x_i \cdot y_i$ products, the implementation subtracts $\Sigma y$ (which shifts all position indices down by 1) and adds $(n-1) \times y_{\text{new}}$ for the new value entering at the highest position. + +This reduces the $O(n)$ recomputation to $O(1)$ per bar. + +### Clamping and Edge Cases + +The output is clamped to $[-1, +1]$ to guard against floating-point drift. When the count is less than 2, the output is `NaN` (insufficient data). When either variance term is non-positive (constant price or constant time, which cannot happen for time), the output is 0. + +## Mathematical Foundation + +Given source values $y_t$ over a window of $n$ observations with time indices $x_i = 0, 1, \ldots, n-1$: + +**Closed-form sums for time indices:** + +$$\Sigma_x = \frac{n(n-1)}{2}, \quad \Sigma_{x^2} = \frac{n(n-1)(2n-1)}{6}$$ + +**Running sums for price:** + +$$\Sigma_y = \sum_{i=0}^{n-1} y_i, \quad \Sigma_{y^2} = \sum_{i=0}^{n-1} y_i^2, \quad \Sigma_{xy} = \sum_{i=0}^{n-1} i \cdot y_i$$ + +**Pearson correlation:** + +$$r = \frac{n \cdot \Sigma_{xy} - \Sigma_x \cdot \Sigma_y}{\sqrt{(n \cdot \Sigma_{x^2} - \Sigma_x^2)(n \cdot \Sigma_{y^2} - \Sigma_y^2)}}$$ + +**O(1) incremental update** (when buffer is full, oldest value $y_{\text{old}}$ exits): + +```text +Σy -= y_old; Σy += y_new +Σy² -= y_old²; Σy² += y_new² +Σxy -= Σy_before_removal // shift all positions down by 1 +Σxy += (n-1) × y_new // new value enters at position n-1 + +CTI = clamp(r, -1, +1) +``` + +**Default parameters:** period = 20. + +## Resources + +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley +- Pearson, K. (1895). "Notes on Regression and Inheritance in the Case of Two Parents." *Proceedings of the Royal Society of London* +- PineScript reference: [`cti.pine`](cti.pine) diff --git a/lib/oscillators/dosc/Dosc.md b/lib/oscillators/dosc/Dosc.md new file mode 100644 index 00000000..5f00ffbc --- /dev/null +++ b/lib/oscillators/dosc/Dosc.md @@ -0,0 +1,59 @@ +# DOSC: Derivative Oscillator + +The Derivative Oscillator applies a four-stage signal processing pipeline to extract momentum inflection points: RSI via Wilder's smoothing, double EMA smoothing of the RSI, an SMA signal line of the double-smoothed result, and finally the difference between the smoothed RSI and its signal. The histogram output crosses zero at momentum turning points, offering earlier signals than raw RSI by isolating the rate of change of the smoothed momentum rather than the momentum level itself. + +## Historical Context + +Constance Brown introduced the Derivative Oscillator in her 1994 work on advanced oscillator techniques, positioning it as a refinement of standard RSI analysis. The core insight was that RSI levels alone provide trend information but not inflection information: a rising RSI at 60 tells you momentum is bullish, but not whether it is accelerating or decelerating. By taking what amounts to the "derivative" of RSI (via the difference between the double-smoothed RSI and its moving average), the oscillator isolates the acceleration component. The double-EMA smoothing stage was borrowed from MACD-style signal extraction, while the final SMA subtraction mirrors the MACD histogram concept. Brown's original parameters (RSI 14, EMA1 5, EMA2 3, Signal 9) were calibrated for daily equity charts and remain the standard defaults. The indicator found adoption among fixed-income and commodity traders where RSI divergence analysis is common but requires confirmation of momentum turning points. + +## Architecture & Physics + +### Four-Stage Pipeline + +1. **Stage 1: Wilder RSI.** Standard RSI using Wilder's RMA smoothing ($\alpha = 1/\text{rsiPeriod}$) for average gain and average loss. Output range: 0-100. + +2. **Stage 2: First EMA.** Exponential moving average of the RSI output with $\alpha_1 = 2/(\text{ema1Period} + 1)$. This removes high-frequency RSI noise while preserving the momentum signal. + +3. **Stage 3: Second EMA (double smoothing).** A second EMA with $\alpha_2 = 2/(\text{ema2Period} + 1)$ applied to the Stage 2 output. The double smoothing creates a zero-lag-adjusted smoother that tracks RSI trends with minimal overshoot. + +4. **Stage 4: SMA signal line.** A simple moving average of the double-smoothed RSI, implemented via circular buffer with running sum for O(1) updates. The SMA period controls the signal line's responsiveness. + +### Output + +The Derivative Oscillator is the difference: $\text{DOSC} = \text{EMA2}(\text{EMA1}(\text{RSI})) - \text{SMA}(\text{EMA2}(\text{EMA1}(\text{RSI})))$. Zero crossings mark momentum inflection points. Positive values indicate accelerating RSI; negative values indicate decelerating RSI. + +## Mathematical Foundation + +**Stage 1: Wilder RSI** ($\alpha_r = 1/p_r$): + +$$\overline{G}_t = \alpha_r \cdot \max(\Delta x_t, 0) + (1-\alpha_r) \cdot \overline{G}_{t-1}$$ + +$$\overline{L}_t = \alpha_r \cdot \max(-\Delta x_t, 0) + (1-\alpha_r) \cdot \overline{L}_{t-1}$$ + +$$RSI_t = 100 - \frac{100}{1 + \overline{G}_t / \overline{L}_t}$$ + +**Stage 2: First EMA** ($\alpha_1 = 2/(p_1 + 1)$): + +$$E_1(t) = \alpha_1 \cdot RSI_t + (1-\alpha_1) \cdot E_1(t-1)$$ + +**Stage 3: Second EMA** ($\alpha_2 = 2/(p_2 + 1)$): + +$$E_2(t) = \alpha_2 \cdot E_1(t) + (1-\alpha_2) \cdot E_2(t-1)$$ + +**Stage 4: SMA signal line** (period $p_s$): + +$$S(t) = \frac{1}{\min(k, p_s)} \sum_{i=0}^{\min(k, p_s)-1} E_2(t-i)$$ + +where $k$ is the count of available values (warmup-aware). + +**Derivative Oscillator:** + +$$DOSC_t = E_2(t) - S(t)$$ + +**Default parameters:** rsiPeriod = 14, ema1Period = 5, ema2Period = 3, signalPeriod = 9. + +## Resources + +- Brown, C. (1994). *Technical Analysis for the Trading Professional*. McGraw-Hill +- Brown, C. (1999). *Technical Analysis for the Trading Professional*, 2nd ed. McGraw-Hill +- PineScript reference: [`dosc.pine`](dosc.pine) diff --git a/lib/oscillators/dosc/dosc.pine b/lib/oscillators/dosc/dosc.pine new file mode 100644 index 00000000..69594457 --- /dev/null +++ b/lib/oscillators/dosc/dosc.pine @@ -0,0 +1,82 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Derivative Oscillator (DOSC)", "DOSC", overlay=false, precision=4) + +//@function Calculates the Derivative Oscillator: double-smoothed RSI minus its SMA signal line +//@param source Series to calculate from +//@param rsiPeriod RSI lookback period +//@param ema1Period First EMA smoothing period applied to RSI +//@param ema2Period Second EMA smoothing period (double smoothing) +//@param sigPeriod SMA signal line period applied to double-smoothed RSI +//@returns DOSC value (histogram: double-smoothed RSI minus signal) +//@optimized O(1) per bar after warmup for all EMA/SMA stages +dosc(series float source, simple int rsiPeriod, simple int ema1Period, simple int ema2Period, simple int sigPeriod) => + if rsiPeriod <= 0 or ema1Period <= 0 or ema2Period <= 0 or sigPeriod <= 0 + runtime.error("All periods must be greater than 0") + + // --- Stage 1: RSI via Wilder's smoothing --- + float change_up = math.max(source - nz(source[1]), 0.0) + float change_down = math.max(nz(source[1]) - source, 0.0) + + var float avgGain = 0.0 + var float avgLoss = 0.0 + + float rsiAlpha = 1.0 / rsiPeriod + if bar_index < rsiPeriod + avgGain := change_up + avgLoss := change_down + else + avgGain := nz(avgGain[1]) * (1.0 - rsiAlpha) + change_up * rsiAlpha + avgLoss := nz(avgLoss[1]) * (1.0 - rsiAlpha) + change_down * rsiAlpha + + float rsiVal = avgLoss == 0.0 ? 100.0 : 100.0 - (100.0 / (1.0 + avgGain / avgLoss)) + + // --- Stage 2: EMA1 of RSI --- + var float ema1 = na + float alpha1 = 2.0 / (ema1Period + 1.0) + ema1 := na(ema1[1]) ? rsiVal : nz(ema1[1]) * (1.0 - alpha1) + rsiVal * alpha1 + + // --- Stage 3: EMA2 of EMA1 (double smoothing) --- + var float ema2 = na + float alpha2 = 2.0 / (ema2Period + 1.0) + ema2 := na(ema2[1]) ? ema1 : nz(ema2[1]) * (1.0 - alpha2) + ema1 * alpha2 + + // --- Stage 4: SMA signal line of EMA2 --- + var array sigBuf = array.new_float(sigPeriod, na) + var int sigHead = 0 + var int sigCount = 0 + var float sigSum = 0.0 + + float oldest = array.get(sigBuf, sigHead) + if not na(oldest) + sigSum -= oldest + sigSum += ema2 + else + sigCount += 1 + sigSum += ema2 + + array.set(sigBuf, sigHead, ema2) + sigHead := (sigHead + 1) % sigPeriod + + float signal = sigCount > 0 ? sigSum / sigCount : 0.0 + + // DOSC = double-smoothed RSI minus signal + float result = ema2 - signal + result + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") +i_rsiPeriod = input.int(14, "RSI Period", minval=1, maxval=500) +i_ema1 = input.int(5, "EMA1 Period", minval=1, maxval=500, tooltip="First EMA smoothing of RSI") +i_ema2 = input.int(3, "EMA2 Period", minval=1, maxval=500, tooltip="Second EMA smoothing (double smooth)") +i_sigPeriod = input.int(9, "Signal Period", minval=1, maxval=500, tooltip="SMA signal line period") + +// Calculation +dosc_value = dosc(i_source, i_rsiPeriod, i_ema1, i_ema2, i_sigPeriod) + +// Plot +plot(dosc_value, "DOSC", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) diff --git a/lib/oscillators/fosc/Fosc.md b/lib/oscillators/fosc/Fosc.md new file mode 100644 index 00000000..ed0ace26 --- /dev/null +++ b/lib/oscillators/fosc/Fosc.md @@ -0,0 +1,68 @@ +# FOSC: Forecast Oscillator + +The Forecast Oscillator measures the percentage deviation of the current price from its linear regression forecast value, producing a zero-centered oscillator that quantifies how far price has moved beyond what a least-squares trend projection would predict. Positive values indicate price is above the regression forecast (bullish divergence from trend), negative values indicate price is below (bearish divergence), and zero crossings mark the points where price meets its statistically expected value. The implementation achieves O(1) per-bar complexity through incremental running sums for the linear regression calculation. + +## Historical Context + +The Forecast Oscillator was introduced by Tushar Chande in his exploration of regression-based indicators during the 1990s, published in *Technical Analysis of Stocks & Commodities* magazine and later in his book on technical analysis. The indicator builds on the Time Series Forecast (TSF) concept but reframes it as an oscillator by expressing the relationship as a percentage deviation rather than an absolute price level. This normalization makes FOSC comparable across instruments of different price scales. Chande positioned FOSC as a complementary tool to his other regression-based indicators (R-Squared, Linear Regression Slope), where R-Squared measures trend quality, slope measures trend direction, and FOSC measures the current price's position relative to trend extrapolation. The percentage formulation also makes FOSC functionally similar to a detrended price series, connecting it to the broader family of detrending oscillators used in cycle analysis. + +## Architecture & Physics + +### Linear Regression via Running Sums + +FOSC requires computing a linear regression forecast at each bar. The standard OLS formula needs $\Sigma x$, $\Sigma x^2$, $\Sigma y$, and $\Sigma xy$. Since the $x$ values are sequential integers, $\Sigma x$ and $\Sigma x^2$ are closed-form functions of $n$. Only $\Sigma y$ and $\Sigma xy$ require incremental maintenance via circular buffer. + +The key optimization for $\Sigma xy$ is identical to CTI: when the window slides, subtracting $\Sigma y$ (before removal) shifts all position indices down by one, and adding $(n-1) \times y_{\text{new}}$ places the new value at the highest position. This avoids recomputing $n$ products per bar. + +### Forecast Point + +The linear regression yields slope $m$ and intercept $b$. The forecast value is evaluated at the endpoint of the window: $\hat{y} = m \cdot (n-1) + b$. This represents the trend-projected value for the current bar. + +### Percentage Deviation + +The oscillator output is $\frac{x_t - \hat{y}}{x_t} \times 100$, which normalizes the deviation by the current price. Division by zero is guarded when source equals zero. + +## Mathematical Foundation + +Given source values $y_t$ over a window of $n$ observations with indices $x_i = 0, 1, \ldots, n-1$: + +**Closed-form time sums:** + +$$\Sigma_x = \frac{n(n-1)}{2}, \quad \Sigma_{x^2} = \frac{n(n-1)(2n-1)}{6}$$ + +**Running sums (O(1) incremental):** + +$$\Sigma_y = \sum y_i, \quad \Sigma_{xy} = \sum i \cdot y_i$$ + +**OLS linear regression:** + +$$m = \frac{n \cdot \Sigma_{xy} - \Sigma_x \cdot \Sigma_y}{n \cdot \Sigma_{x^2} - \Sigma_x^2}, \quad b = \frac{\Sigma_y - m \cdot \Sigma_x}{n}$$ + +**Forecast at endpoint:** + +$$\hat{y}_t = m \cdot (n - 1) + b$$ + +**Forecast Oscillator:** + +$$FOSC_t = \frac{y_t - \hat{y}_t}{y_t} \times 100$$ + +**Streaming update pseudo-code:** + +```text +// When buffer full, oldest y_old exits: +Σy -= y_old; Σxy -= Σy_before +Σy += y_new; Σxy += (n-1) × y_new + +m = (n×Σxy - Σx×Σy) / (n×Σx² - Σx²) +b = (Σy - m×Σx) / n +forecast = m×(n-1) + b +FOSC = (y_new ≠ 0) ? (y_new - forecast) / y_new × 100 : 0 +``` + +**Default parameters:** period = 14. + +## Resources + +- Chande, T.S. (1997). *Beyond Technical Analysis*. Wiley +- Chande, T.S. & Kroll, S. (1994). *The New Technical Trader*. Wiley +- PineScript reference: [`fosc.pine`](fosc.pine) diff --git a/lib/oscillators/kst/Kst.md b/lib/oscillators/kst/Kst.md new file mode 100644 index 00000000..5c5f05d1 --- /dev/null +++ b/lib/oscillators/kst/Kst.md @@ -0,0 +1,59 @@ +# KST: Know Sure Thing Oscillator + +The Know Sure Thing is a multi-timeframe momentum oscillator that computes four Rate of Change values at progressively longer lookback periods, smooths each with an independent SMA, then combines them using linearly increasing weights (1, 2, 3, 4) to produce a single composite momentum line. A signal line (SMA of the KST) provides crossover triggers. The weighted summation ensures longer-term momentum dominates the output while shorter-term components contribute responsiveness, creating a momentum indicator that reflects multiple cycle lengths simultaneously. + +## Historical Context + +Martin Pring developed the KST oscillator in the early 1990s, publishing it in *Technical Analysis of Stocks & Commodities* and later in his comprehensive work on technical analysis. Pring's motivation was to create a single indicator that captured momentum across multiple timeframes, eliminating the need to monitor four separate ROC charts. The name "Know Sure Thing" was somewhat tongue-in-cheek, acknowledging that no indicator provides certainty, but reflecting Pring's confidence that multi-timeframe momentum confirmation produces more reliable signals than any single timeframe. The original design used monthly data with ROC periods of 9, 12, 18, 24 months and SMA periods of 6, 6, 6, 9 months, later adapted to daily timeframes using proportionally scaled periods. The linearly increasing weights (1:2:3:4) were chosen to give progressively more influence to longer-term momentum, reflecting the principle that major market trends are driven by longer-term forces while shorter-term momentum primarily adds noise. + +## Architecture & Physics + +### Parallel ROC + SMA Pipeline + +KST maintains four independent processing channels, each consisting of: + +1. **ROC calculation:** $ROC_k = \frac{x_t - x_{t-r_k}}{x_{t-r_k}} \times 100$ for lookback periods $r_1 < r_2 < r_3 < r_4$. + +2. **SMA smoothing:** Each ROC is smoothed by an independent SMA with its own circular buffer and running sum, achieving O(1) per bar. The SMA helper function encapsulates buffer management, head pointer, count, and running sum. + +### Weighted Combination + +The four smoothed ROC values are combined with fixed linear weights: + +$$KST = 1 \times SMA(ROC_1) + 2 \times SMA(ROC_2) + 3 \times SMA(ROC_3) + 4 \times SMA(ROC_4)$$ + +### Signal Line + +A fifth SMA is applied to the KST output, using its own circular buffer. Crossovers between KST and signal indicate momentum shifts. + +### Total Buffer Count + +The implementation maintains 5 independent SMA circular buffers (4 ROC smoothers + 1 signal), each with its own metadata arrays. No ROC circular buffer is needed because PineScript's `source[n]` lookback provides direct access to historical prices. + +## Mathematical Foundation + +Given source $x_t$, ROC periods $(r_1, r_2, r_3, r_4)$, SMA periods $(s_1, s_2, s_3, s_4)$, signal period $p_s$: + +**Rate of Change for each channel:** + +$$ROC_k(t) = \frac{x_t - x_{t-r_k}}{x_{t-r_k}} \times 100, \quad k \in \{1,2,3,4\}$$ + +**SMA smoothing** (O(1) circular buffer per channel): + +$$SM_k(t) = \frac{1}{s_k} \sum_{i=0}^{s_k-1} ROC_k(t-i)$$ + +**KST composite:** + +$$KST(t) = 1 \cdot SM_1(t) + 2 \cdot SM_2(t) + 3 \cdot SM_3(t) + 4 \cdot SM_4(t)$$ + +**Signal line:** + +$$Signal(t) = \frac{1}{p_s} \sum_{i=0}^{p_s-1} KST(t-i)$$ + +**Default parameters:** $r = (10, 15, 20, 30)$, $s = (10, 10, 10, 15)$, $p_s = 9$. + +## Resources + +- Pring, M.J. (1992). "The KST System." *Technical Analysis of Stocks & Commodities* +- Pring, M.J. (2002). *Technical Analysis Explained*, 4th ed. McGraw-Hill +- PineScript reference: [`kst.pine`](kst.pine) diff --git a/lib/oscillators/mstoch/Mstoch.md b/lib/oscillators/mstoch/Mstoch.md new file mode 100644 index 00000000..14f64a5f --- /dev/null +++ b/lib/oscillators/mstoch/Mstoch.md @@ -0,0 +1,60 @@ +# MSTOCH: Ehlers MESA Stochastic + +The MESA Stochastic applies John Ehlers' Roofing Filter as a preprocessing stage before computing a stochastic oscillator, then smooths the stochastic output with a Super Smoother. The Roofing Filter removes both low-frequency trend components (via highpass) and high-frequency noise (via Super Smoother), isolating the dominant cycle. The stochastic calculation on this filtered data produces a clean 0-to-1 oscillator that responds to cycle turning points rather than trend or noise, with substantially reduced whipsaw compared to conventional stochastic indicators. + +## Historical Context + +John Ehlers introduced the MESA Stochastic in his 2013 book *Cycle Analytics for Traders*, as part of his systematic framework for applying digital signal processing to market data. The "MESA" prefix references Maximum Entropy Spectral Analysis, Ehlers' preferred technique for estimating dominant cycle periods. The key innovation is the Roofing Filter preprocessing: by bandpass-filtering the data before applying the stochastic calculation, the oscillator responds to cycle extremes rather than trend extremes. Conventional stochastic indicators on raw price tend to saturate at 0 or 100 during trends (the "stochastic pop" failure mode), but the Roofing Filter removes the trend component entirely, so the stochastic operates on stationary cycle data. Ehlers demonstrated that this produces fewer false signals in trending markets while maintaining responsiveness at genuine cycle turning points. The Super Smoother stages use 2-pole Butterworth-derived coefficients that provide superior smoothing characteristics compared to simple or exponential moving averages. + +## Architecture & Physics + +### Three-Stage Pipeline + +1. **Stage 1: Roofing Filter** (Highpass + Super Smoother). The highpass is a 2-pole Butterworth filter that removes cycles longer than `hpLength`, eliminating trend. The Super Smoother is a 2-pole lowpass filter that removes cycles shorter than `ssLength`, eliminating noise. Together they form a bandpass that isolates the dominant cycle band. + +2. **Stage 2: Stochastic on filtered data.** A standard highest-high / lowest-low stochastic over `stochLength` bars of the roofing-filtered output. Because the input is zero-mean (trend removed), the stochastic operates on cycle oscillations rather than trending prices. + +3. **Stage 3: Super Smoother of stochastic.** The same 2-pole smoothing filter applied to the raw stochastic, removing stochastic noise while preserving the timing of overbought/oversold transitions. Output is clamped to $[0, 1]$. + +### IIR Filter Coefficients + +Both the highpass and Super Smoother stages use coefficients derived from 2-pole Butterworth prototypes: + +$$\text{arg} = \frac{\sqrt{2}\pi}{P}, \quad e^{-\text{arg}}, \quad c_2 = 2 e^{-\text{arg}} \cos(\text{arg}), \quad c_3 = -e^{-2\text{arg}}$$ + +The highpass uses $c_1 = (1 + c_2 - c_3)/4$ with a second-difference input $(x - 2x_{-1} + x_{-2})$. +The Super Smoother uses $c_1 = 1 - c_2 - c_3$ with an averaged input $(x + x_{-1})/2$. + +## Mathematical Foundation + +**Roofing Filter highpass** (removes trend, cutoff period $P_{hp}$): + +$$HP_t = c_1^{hp}(x_t - 2x_{t-1} + x_{t-2}) + c_2^{hp} \cdot HP_{t-1} + c_3^{hp} \cdot HP_{t-2}$$ + +where $c_1^{hp} = \frac{1 + c_2^{hp} - c_3^{hp}}{4}$ + +**Super Smoother** (removes noise, cutoff period $P_{ss}$): + +$$F_t = c_1^{ss} \cdot \frac{HP_t + HP_{t-1}}{2} + c_2^{ss} \cdot F_{t-1} + c_3^{ss} \cdot F_{t-2}$$ + +where $c_1^{ss} = 1 - c_2^{ss} - c_3^{ss}$ + +**Stochastic on filtered data:** + +$$S_t = \frac{F_t - \min(F_{t-k}, \ldots, F_t)}{\max(F_{t-k}, \ldots, F_t) - \min(F_{t-k}, \ldots, F_t)}$$ + +where $k = \text{stochLength} - 1$. If range is zero, $S_t = 0.5$. + +**Final smoothing:** + +$$MSTOCH_t = c_1^{ss} \cdot \frac{S_t + S_{t-1}}{2} + c_2^{ss} \cdot MSTOCH_{t-1} + c_3^{ss} \cdot MSTOCH_{t-2}$$ + +$$\text{Output} = \text{clamp}(MSTOCH_t, 0, 1)$$ + +**Default parameters:** stochLength = 20, hpLength = 48, ssLength = 10. + +## Resources + +- Ehlers, J.F. (2013). *Cycle Analytics for Traders*. Wiley, Chapter 6 +- Ehlers, J.F. (2004). *Cybernetic Analysis for Stocks and Futures*. Wiley +- PineScript reference: [`mstoch.pine`](mstoch.pine) diff --git a/lib/oscillators/qqe/Qqe.md b/lib/oscillators/qqe/Qqe.md new file mode 100644 index 00000000..c8b30804 --- /dev/null +++ b/lib/oscillators/qqe/Qqe.md @@ -0,0 +1,65 @@ +# QQE: Quantitative Qualitative Estimation + +Quantitative Qualitative Estimation applies a multi-stage smoothing pipeline to RSI and then constructs dynamic volatility-based trailing bands around the smoothed result. The output is a dual-line system: the QQE line (smoothed RSI) and a trailing level that follows price directionally, similar to Parabolic SAR logic. Crossovers between the QQE line and its trailing level signal momentum shifts, while crossovers of the QQE line above and below 50 indicate trend direction. The trailing level adapts to volatility through a double-EMA of RSI absolute changes, making band width contract in quiet markets and expand during volatile conditions. + +## Historical Context + +QQE emerged from the forex trading community in the mid-2000s, attributed to an anonymous developer and popularized through MetaTrader forums. The indicator extends Wilder's RSI concept by addressing two of its primary limitations: noise in the RSI signal and fixed overbought/oversold thresholds. The first problem is solved by EMA smoothing of the RSI output; the second by replacing static thresholds with adaptive trailing bands derived from RSI volatility. The "Quantitative Qualitative" name reflects the dual nature of the system: the quantitative RSI measurement combined with qualitative trend-following logic in the trailing level. The trailing level mechanism borrows from Welles Wilder's Parabolic SAR: it follows the smoothed RSI directionally, only reversing when the RSI breaks through. The default QQE factor of 4.236 (the square of the golden ratio $\phi^2 = 2.618... \times 1.618...$) has no documented mathematical justification but has become canonical through widespread adoption. + +## Architecture & Physics + +### Four-Stage Pipeline + +1. **Stage 1: Wilder RSI** via RMA ($\alpha = 1/\text{rsiPeriod}$) with warmup compensation. The exponential decay factor $e = \beta^n$ tracks convergence, applying correction $c = 1/(1-e)$ until $e < 10^{-10}$. + +2. **Stage 2: EMA smoothing** of RSI ($\alpha = 2/(\text{SF}+1)$) with the same warmup compensation. Produces `rsiMA`, the primary QQE line. + +3. **Stage 3: Dynamic Average Range (DAR).** Computes $|\Delta \text{rsiMA}|$ bar-to-bar, then applies two consecutive EMAs with period $2 \times \text{SF} - 1$. Both EMAs use warmup compensation. The double smoothing produces a stable volatility estimate analogous to ATR but operating on the RSI domain. + +4. **Stage 4: Trailing level.** Constructs upper/lower bands at $\text{rsiMA} \pm \text{qqeFactor} \times \text{DAR}$. The trailing logic follows directionally: + - If rsiMA is above the trail and was above previously: trail = max(trail, lowerBand) (ratchets up) + - If rsiMA is below the trail and was below previously: trail = min(trail, upperBand) (ratchets down) + - On crossover: trail flips to the opposite band + +## Mathematical Foundation + +**Stage 1: RSI** ($\alpha_r = 1/p_r$, $\beta_r = 1 - \alpha_r$): + +$$\hat{G}_t = \beta_r \hat{G}_{t-1} + \alpha_r \max(\Delta x_t, 0), \quad e_r = \beta_r^t$$ + +$$RSI_t = \frac{100 \cdot \hat{G}_t / (1-e_r)}{\hat{G}_t/(1-e_r) + \hat{L}_t/(1-e_r)}$$ + +**Stage 2: EMA of RSI** ($\alpha_s = 2/(SF+1)$): + +$$\hat{M}_t = \beta_s \hat{M}_{t-1} + \alpha_s \cdot RSI_t, \quad rsiMA_t = \hat{M}_t / (1 - \beta_s^t)$$ + +**Stage 3: Double EMA of |delta|** ($\alpha_d = 2/(2 \cdot SF)$): + +$$D_t = |rsiMA_t - rsiMA_{t-1}|$$ + +$$\hat{d}_1 = \beta_d \hat{d}_1 + \alpha_d D_t, \quad dar_1 = \hat{d}_1 / (1 - \beta_d^t)$$ + +$$\hat{d}_2 = \beta_d \hat{d}_2 + \alpha_d \cdot dar_1, \quad DAR_t = \hat{d}_2 / (1 - \beta_d^t)$$ + +**Stage 4: Trailing level:** + +```text +band = qqeFactor × DAR +upper = rsiMA + band +lower = rsiMA - band + +if rsiMA > trail AND prev_rsiMA > trail: + trail = max(trail, lower) +elif rsiMA < trail AND prev_rsiMA < trail: + trail = min(trail, upper) +else: + trail = (rsiMA > trail) ? lower : upper +``` + +**Default parameters:** rsiPeriod = 14, smoothFactor = 5, qqeFactor = 4.236. + +## Resources + +- Wilder, J.W. (1978). *New Concepts in Technical Trading Systems*. Trend Research (RSI foundation) +- MetaTrader community documentation on QQE implementation +- PineScript reference: [`qqe.pine`](qqe.pine) diff --git a/lib/oscillators/rvgi/Rvgi.md b/lib/oscillators/rvgi/Rvgi.md new file mode 100644 index 00000000..c2966784 --- /dev/null +++ b/lib/oscillators/rvgi/Rvgi.md @@ -0,0 +1,59 @@ +# RVGI: Relative Vigor Index + +The Relative Vigor Index measures the conviction of a price move by comparing closing strength (close minus open) to the total intrabar range (high minus low), smoothed through a symmetrically weighted moving average and then averaged over a lookback period. The premise is that in bullish markets, closes tend to occur near highs and opens near lows, producing positive RVGI values, while bearish markets show the opposite pattern. A 4-bar SWMA signal line provides crossover triggers. The indicator oscillates around zero with no fixed bounds. + +## Historical Context + +John Ehlers introduced the Relative Vigor Index in his 2002 book *Rocket Science for Traders*, drawing on the concept that price vigor (the difference between open and close) relative to the bar's range captures directional conviction more effectively than close-only momentum measures. The design reflects Ehlers' signal processing background: the SWMA (Symmetrically Weighted Moving Average) with weights $[1, 2, 2, 1]/6$ is a 4-tap FIR filter with symmetric coefficients, which guarantees zero phase shift at the cost of minimal lag. This choice was deliberate, as asymmetric weights would introduce phase distortion that corrupts the relationship between the indicator and its signal line. The SMA averaging stage serves as a secondary smoothing filter that reduces noise without further phase impact. The signal line reuses the same SWMA kernel, maintaining phase consistency throughout the entire processing chain. + +## Architecture & Physics + +### Four-Stage Pipeline + +1. **SWMA of (Close - Open):** A fixed 4-bar kernel with weights $[1, 2, 2, 1]/6$ applied to the close-minus-open series. This captures the directional conviction of each bar, smoothed symmetrically. + +2. **SWMA of (High - Low):** The same 4-bar kernel applied to the high-minus-low (range) series. This normalizes by volatility. + +3. **SMA of numerator and denominator:** Independent SMAs over the specified period, each using a circular buffer with O(1) running sum updates. The ratio $\text{SMA(numerator)} / \text{SMA(denominator)}$ produces the RVGI line. + +4. **Signal line:** A 4-bar SWMA of the RVGI output, using three history variables to store the previous three RVGI values. The SWMA kernel is hardcoded: $(rv_3 + 2 \cdot rv_2 + 2 \cdot rv_1 + rv_0) / 6$. + +### Defensive Division + +When the denominator SMA equals zero (all bars in the window have zero range, i.e., doji sequences), RVGI returns 0 rather than propagating division by zero. + +## Mathematical Foundation + +Given OHLC bars and lookback period $n$: + +**SWMA kernel** (4-tap symmetric FIR): + +$$w = \left[\frac{1}{6}, \frac{2}{6}, \frac{2}{6}, \frac{1}{6}\right]$$ + +**Numerator (closing strength):** + +$$N_t = \frac{(C_{t-3} - O_{t-3}) + 2(C_{t-2} - O_{t-2}) + 2(C_{t-1} - O_{t-1}) + (C_t - O_t)}{6}$$ + +**Denominator (bar range):** + +$$D_t = \frac{(H_{t-3} - L_{t-3}) + 2(H_{t-2} - L_{t-2}) + 2(H_{t-1} - L_{t-1}) + (H_t - L_t)}{6}$$ + +**SMA smoothing** (O(1) circular buffer): + +$$\overline{N}_t = \frac{1}{n}\sum_{i=0}^{n-1} N_{t-i}, \quad \overline{D}_t = \frac{1}{n}\sum_{i=0}^{n-1} D_{t-i}$$ + +**RVGI:** + +$$RVGI_t = \begin{cases} \overline{N}_t / \overline{D}_t & \text{if } \overline{D}_t \neq 0 \\ 0 & \text{otherwise} \end{cases}$$ + +**Signal line:** + +$$Signal_t = \frac{RVGI_{t-3} + 2 \cdot RVGI_{t-2} + 2 \cdot RVGI_{t-1} + RVGI_t}{6}$$ + +**Default parameters:** period = 10. + +## Resources + +- Ehlers, J.F. (2002). *Rocket Science for Traders*. Wiley, Chapter 12 +- Ehlers, J.F. (2001). "The Relative Vigor Index." *Technical Analysis of Stocks & Commodities* +- PineScript reference: [`rvgi.pine`](rvgi.pine) diff --git a/lib/oscillators/squeeze/Squeeze.md b/lib/oscillators/squeeze/Squeeze.md new file mode 100644 index 00000000..fcad9b20 --- /dev/null +++ b/lib/oscillators/squeeze/Squeeze.md @@ -0,0 +1,63 @@ +# SQUEEZE: Squeeze Momentum + +Squeeze Momentum combines Bollinger Band and Keltner Channel width analysis to detect low-volatility compression ("squeeze") states, while simultaneously measuring directional momentum via linear regression of a detrended price series. The dual output consists of a momentum histogram and a binary squeeze state indicator. When Bollinger Bands contract inside the Keltner Channel, the market is in a squeeze (coiling volatility); when the squeeze releases, the momentum histogram direction signals the likely breakout direction. The implementation combines five distinct computational stages, each using O(1) streaming techniques. + +## Historical Context + +John Carter popularized the Squeeze indicator in his 2005 book *Mastering the Trade*, though the core concept of BB-inside-KC squeeze detection predates his work. The underlying principle is that volatility is mean-reverting: periods of unusually low volatility (measured by BB width falling below KC width) tend to precede large directional moves. Carter combined this squeeze detection with a momentum component derived from linear regression to provide directional bias. The specific construction uses the midpoint of a Donchian Channel averaged with SMA as a center line, computes the deviation of price from this averaged midpoint, and applies linear regression to this deviation series. The regression endpoint value serves as the momentum measure. This construction effectively measures detrended momentum, isolating the directional force from the trend component. The color-coded histogram (traditionally four colors based on momentum direction and acceleration) provides visual distinction between momentum increasing and decreasing in both directions. + +## Architecture & Physics + +### Five Computational Stages + +1. **SMA + Standard Deviation** (Bollinger Bands): Circular buffer with running sum and sum-of-squares for O(1) variance computation. BB upper/lower = SMA $\pm$ bbMult $\times$ StdDev. + +2. **EMA + ATR via RMA** (Keltner Channel): EMA uses warmup-compensated exponential smoothing. ATR uses Wilder's RMA (also warmup-compensated) of True Range. KC upper/lower = EMA $\pm$ kcMult $\times$ ATR. + +3. **Squeeze detection:** Binary comparison: if BB upper < KC upper AND BB lower > KC lower, squeeze is on. This means BB has contracted inside KC. + +4. **Donchian midline + delta:** Circular buffers for highest-high and lowest-low over the period, with full O(n) scan per bar for max/min (no O(1) trick for running max). Delta = close $-$ (donchianMid + SMA) / 2. + +5. **Linear regression of delta:** Incremental running sums ($\Sigma y$, $\Sigma xy$) for O(1) regression per bar. The momentum output is the regression line evaluated at the most recent point: $\text{slope} \times (n-1) + \text{intercept}$. + +### Warmup Compensation + +EMA and RMA stages use the $e = \beta^n$ warmup tracking with correction factor $c = 1/(1-e)$ to eliminate initial bias. + +## Mathematical Foundation + +**Bollinger Bands** (SMA + StdDev via running sums): + +$$\mu = \frac{\Sigma x}{n}, \quad \sigma = \sqrt{\frac{\Sigma x^2}{n} - \mu^2}$$ + +$$BB_{upper} = \mu + m_{bb} \cdot \sigma, \quad BB_{lower} = \mu - m_{bb} \cdot \sigma$$ + +**Keltner Channel** (EMA + ATR): + +$$EMA_t = \frac{\hat{E}_t}{1 - \beta^t}, \quad ATR_t = \frac{\hat{R}_t}{1 - \beta_r^t}$$ + +$$KC_{upper} = EMA + m_{kc} \cdot ATR, \quad KC_{lower} = EMA - m_{kc} \cdot ATR$$ + +**Squeeze state:** + +$$Squeeze = \begin{cases} 1 & \text{if } BB_{upper} < KC_{upper} \text{ and } BB_{lower} > KC_{lower} \\ 0 & \text{otherwise} \end{cases}$$ + +**Detrended price (delta):** + +$$\delta_t = x_t - \frac{(\text{DonchianMid}_t + \text{SMA}_t)}{2}$$ + +where $\text{DonchianMid} = \frac{\max(H_{t-n+1..t}) + \min(L_{t-n+1..t})}{2}$ + +**Momentum (linear regression endpoint of delta):** + +$$m = \frac{n \cdot \Sigma_{xy} - \Sigma_x \cdot \Sigma_y}{n \cdot \Sigma_{x^2} - \Sigma_x^2}, \quad b = \frac{\Sigma_y - m \cdot \Sigma_x}{n}$$ + +$$Momentum_t = m \cdot (t_{\text{last}}) + b$$ + +**Default parameters:** period = 20, bbMult = 2.0, kcMult = 1.5. + +## Resources + +- Carter, J. (2005). *Mastering the Trade*. McGraw-Hill, Chapter 11 +- Bollinger, J. (2001). *Bollinger on Bollinger Bands*. McGraw-Hill +- PineScript reference: [`squeeze.pine`](squeeze.pine) diff --git a/lib/oscillators/td_seq/Td_seq.md b/lib/oscillators/td_seq/Td_seq.md new file mode 100644 index 00000000..8861073c --- /dev/null +++ b/lib/oscillators/td_seq/Td_seq.md @@ -0,0 +1,60 @@ +# TD_SEQ: TD Sequential + +TD Sequential is Tom DeMark's exhaustion counting system that identifies potential trend reversals through two phases: a 9-count Setup phase that detects overextended trends, and a 13-count Countdown phase that pinpoints probable reversal timing. Unlike oscillators that measure momentum magnitude, TD Sequential counts consecutive qualifying bars, producing integer outputs (Setup: $\pm 1$ to $\pm 9$; Countdown: $\pm 1$ to $\pm 13$) that represent the progression toward exhaustion. A completed 9-count Setup followed by a completed 13-count Countdown signals high-probability trend exhaustion. All state is maintained in O(1) scalar variables with no buffers required. + +## Historical Context + +Thomas DeMark developed TD Sequential during the 1970s-1990s as part of his comprehensive market timing framework, published in *The New Science of Technical Analysis* (1994) and *New Market Timing Techniques* (1997). The indicator was conceived as a structural alternative to momentum oscillators: rather than measuring how overbought or oversold a market is, it counts how long a directional condition has persisted and identifies specific exhaustion points. DeMark's key insight was that trends exhaust at predictable counting thresholds (9 for Setup, 13 for Countdown), a pattern he validated across equity, fixed-income, commodity, and currency markets. The indicator found significant institutional adoption, with Bloomberg terminals providing native DeMark indicators and firms like Tudor Investment Corporation licensing the methodology. The compare period (typically 4 bars) determines the lookback for the close comparison: each Setup bar requires close above/below close[4], creating a structural requirement that the trend has been sustained for at least 4 additional bars beyond the count itself. The Countdown phase adds a higher bar: the close must exceed the high or low of 2 bars ago, a condition that doesn't occur on every bar, making the Countdown non-consecutive. + +## Architecture & Physics + +### Two-Phase State Machine + +**Phase 1: Setup ($\pm 1$ to $\pm 9$)** + +The Setup counter compares the current close to the close `comparePeriod` bars ago. If close > close[comparePeriod], the sell setup count increments (positive); if close < close[comparePeriod], the buy setup count decrements (negative). The count resets to zero when the condition breaks or reverses direction. Counts are clamped to $\pm 9$. + +When the count reaches exactly $\pm 9$ for the first time (without having been reset), the setup is "complete" and Phase 2 begins. The setupComplete flag prevents re-triggering until a reset occurs. + +**Phase 2: Countdown ($\pm 1$ to $\pm 13$)** + +After a completed 9-count Setup, the Countdown phase begins. Unlike Setup, Countdown is non-consecutive: a sell countdown bar requires close > high[2]; a buy countdown bar requires close < low[2]. Only qualifying bars increment the countdown. The count progresses toward $\pm 13$, at which point the countdown completes and the directional signal resets. + +An opposite 9-count Setup during an active Countdown resets and restarts the Countdown in the new direction. + +### Zero-Buffer Design + +The entire indicator state consists of four scalar variables: `setupCount`, `countdownCount`, `countdownDir`, and `setupComplete`. No circular buffers, arrays, or sliding windows are needed. The only historical lookback dependency is PineScript's `close[comparePeriod]`, `low[2]`, and `high[2]`. + +## Mathematical Foundation + +**Setup counting** (comparePeriod = $p$): + +$$S_t = \begin{cases} S_{t-1} - 1 & \text{if } C_t < C_{t-p} \text{ and } S_{t-1} \leq 0 \\ -1 & \text{if } C_t < C_{t-p} \text{ and } S_{t-1} > 0 \\ S_{t-1} + 1 & \text{if } C_t > C_{t-p} \text{ and } S_{t-1} \geq 0 \\ +1 & \text{if } C_t > C_{t-p} \text{ and } S_{t-1} < 0 \\ 0 & \text{if } C_t = C_{t-p} \end{cases}$$ + +$$S_t = \text{clamp}(S_t, -9, +9)$$ + +**Setup completion trigger:** + +$$\text{if } |S_t| = 9 \text{ and not previously complete} \Rightarrow \text{begin Countdown, dir} = \text{sign}(S_t)$$ + +**Countdown** (non-consecutive): + +$$CD_t = \begin{cases} CD_{t-1} - 1 & \text{if dir} = -1 \text{ and } C_t < L_{t-2} \\ CD_{t-1} + 1 & \text{if dir} = +1 \text{ and } C_t > H_{t-2} \\ CD_{t-1} & \text{otherwise (no qualifying bar)} \end{cases}$$ + +**Countdown completion:** + +$$\text{if } |CD_t| \geq 13 \Rightarrow CD_t = \text{sign}(dir) \times 13, \text{ reset dir}$$ + +**Countdown reset on opposite Setup:** + +$$\text{if dir} = +1 \text{ and } S_t = -9, \text{ or dir} = -1 \text{ and } S_t = +9 \Rightarrow \text{reset CD, new dir}$$ + +**Default parameters:** comparePeriod = 4. + +## Resources + +- DeMark, T.R. (1994). *The New Science of Technical Analysis*. Wiley +- DeMark, T.R. (1997). *New Market Timing Techniques*. Wiley +- Bloomberg Terminal: DeMark Indicators (DMRK) implementation reference +- PineScript reference: [`td_seq.pine`](td_seq.pine) diff --git a/lib/statistics/polyfit/Polyfit.md b/lib/statistics/polyfit/Polyfit.md new file mode 100644 index 00000000..a36166a9 --- /dev/null +++ b/lib/statistics/polyfit/Polyfit.md @@ -0,0 +1,91 @@ +# POLYFIT: Polynomial Fitting + +Polynomial Fitting computes a rolling polynomial regression of configurable degree over a lookback window, returning the fitted value at the current bar. Degree 1 produces a linear regression endpoint (identical to LSQR), degree 2 produces a quadratic fit that captures curvature, and degree 3 produces a cubic fit that captures inflection points. The implementation solves the normal equations $\mathbf{X}^T\mathbf{X}\mathbf{a} = \mathbf{X}^T\mathbf{y}$ via Gauss-Jordan elimination with partial pivoting, evaluating the resulting polynomial at $x = 1$ (the current bar position). With $O(Nd + d^3)$ complexity per bar where $N$ is the period and $d$ is the degree, POLYFIT provides a general-purpose curve-fitting tool that subsumes linear regression and extends it to arbitrary polynomial order. + +## Historical Context + +Polynomial regression traces to Adrien-Marie Legendre (1805) and Carl Friedrich Gauss (1809), who independently developed the method of least squares. The normal equations formulation provides the minimum-sum-of-squares solution in closed form, though numerical stability requires careful implementation. Gauss-Jordan elimination with partial pivoting (Jordan, 1873) is the standard approach for small systems like those arising in polynomial fitting with degrees 1-6. + +In technical analysis, linear regression (degree 1) is well established via the Linear Regression Channel and LSQR indicators. Higher-degree fits are less common due to overfitting concerns, but degree 2 (quadratic) is useful for detecting acceleration/deceleration in trends, and degree 3 (cubic) can capture reversal patterns. The key insight is that higher degrees track price more closely but also amplify noise; the optimal degree depends on the signal-to-noise ratio and the lookback period. + +The x-normalization step (mapping time indices to $[0, 1]$) is critical for numerical stability: without it, the Vandermonde matrix entries $x^d$ would span many orders of magnitude for typical lookback periods, causing catastrophic cancellation in the normal equations. With normalization, the matrix condition number remains manageable up to degree 6. + +## Architecture and Physics + +The implementation uses a circular buffer to maintain the last `period` values, with NaN substitution via last-valid-value tracking. + +**Matrix assembly**: Constructs the $(d+1) \times (d+1)$ Gram matrix $\mathbf{G} = \mathbf{X}^T\mathbf{X}$ and right-hand side $\mathbf{r} = \mathbf{X}^T\mathbf{y}$ in a single pass over the data. The Vandermonde basis vectors are $[1, x, x^2, \ldots, x^d]$ where $x_i = i/(n-1)$ is the normalized time position. The matrix is symmetric so only the upper triangle needs explicit computation (mirrored to lower). + +**Solver**: Gauss-Jordan elimination with partial pivoting transforms the augmented matrix $[\mathbf{G} | \mathbf{r}]$ into $[\mathbf{I} | \mathbf{a}]$. Partial pivoting selects the row with the largest absolute value in the current column to minimize round-off error. Singular or near-singular matrices (pivot $< 10^{-30}$) abort gracefully. + +**Evaluation**: The polynomial $P(x) = a_0 + a_1 x + a_2 x^2 + \cdots + a_d x^d$ is evaluated at $x = 1.0$ (current bar, since time is normalized to $[0, 1]$). This gives the fitted value at the most recent observation. + +**Degree clamping**: If `degree` exceeds `period - 1`, it is automatically reduced to prevent underdetermined systems. + +## Mathematical Foundation + +The polynomial model: + +$$P(x) = \sum_{j=0}^{d} a_j x^j = a_0 + a_1 x + a_2 x^2 + \cdots + a_d x^d$$ + +The **normal equations** for least-squares fitting: + +$$\mathbf{X}^T\mathbf{X}\,\mathbf{a} = \mathbf{X}^T\mathbf{y}$$ + +where $\mathbf{X}$ is the $n \times (d+1)$ Vandermonde matrix: + +$$X_{ij} = x_i^j, \quad x_i = \frac{i}{n-1} \in [0, 1]$$ + +The Gram matrix elements: + +$$G_{jk} = \sum_{i=0}^{n-1} x_i^{j+k}$$ + +The right-hand side: + +$$r_j = \sum_{i=0}^{n-1} x_i^j \cdot y_i$$ + +**Gauss-Jordan with partial pivoting** reduces $[\mathbf{G} | \mathbf{r}]$ to $[\mathbf{I} | \mathbf{a}]$: + +1. For each column $c$: find the row $p$ in $[c, d]$ with maximum $|G_{pc}|$ +2. Swap rows $c$ and $p$ +3. Scale row $c$ so the pivot becomes 1 +4. Subtract multiples of row $c$ from all other rows + +**Output**: $\hat{y}_{\text{current}} = P(1.0) = \sum_{j=0}^{d} a_j$ + +**Parameter constraints**: `period` $\ge 2$, `degree` $\ge 1$ (clamped to `period - 1`). Computational complexity: $O(nd + d^3)$. + +``` +POLYFIT(source, period, degree): + d = min(degree, period - 1) + m = d + 1 + normalize x_i = i / (n-1) for i in [0, n-1] + + // Build normal equations + G = (m x m) matrix of zeros + r = m-vector of zeros + for each (x_i, y_i) in window: + for j = 0 to d: + r[j] += x_i^j * y_i + for k = j to d: + G[j][k] += x_i^(j+k) + G[k][j] = G[j][k] // symmetric + + // Gauss-Jordan with partial pivoting + for col = 0 to d: + pivot_row = argmax |G[row][col]| for row in [col, d] + swap rows col and pivot_row in G and r + scale row col by 1/G[col][col] + eliminate col from all other rows + + // Evaluate at x = 1.0 (current bar) + return sum(r[j] for j = 0 to d) +``` + +## Resources + +- Legendre, A.M. "Nouvelles methodes pour la determination des orbites des cometes." 1805. +- Gauss, C.F. "Theoria Motus Corporum Coelestium." 1809. +- Golub, G. & Van Loan, C. "Matrix Computations." 4th edition, Johns Hopkins University Press, 2013. +- Press, W.H. et al. "Numerical Recipes: The Art of Scientific Computing." 3rd edition, Cambridge University Press, 2007. Chapter 15 (Modeling of Data). +- Draper, N. & Smith, H. "Applied Regression Analysis." 3rd edition, Wiley, 1998. diff --git a/lib/statistics/trim/Trim.md b/lib/statistics/trim/Trim.md new file mode 100644 index 00000000..0c422d99 --- /dev/null +++ b/lib/statistics/trim/Trim.md @@ -0,0 +1,84 @@ +# TRIM: Trimmed Mean Moving Average + +The Trimmed Mean Moving Average computes a rolling average after discarding a configurable percentage of the most extreme values from each tail of the sorted lookback window. By removing the lowest and highest `trimPct%` of observations, TRIM eliminates the influence of outliers while retaining more information than a pure median. At `trimPct = 0` it degenerates to the SMA; at `trimPct = 50` it becomes the median. The default 10% trim provides a robust central tendency estimator that resists spike contamination with minimal loss of responsiveness, requiring $O(N \log N)$ for the sort plus $O(N)$ for the summation per bar. + +## Historical Context + +The trimmed mean was introduced by W.J. Dixon (1960) as part of a systematic study of robust estimators for location. John Tukey (1960, 1977) championed its use as part of his program of exploratory data analysis, arguing that no single estimator dominates all contamination models, and the trimmed mean provides a practical compromise between efficiency under normality (where the SMA is optimal) and resistance to outliers (where the median excels). + +In financial applications, the trimmed mean addresses a pervasive problem: price series contain erroneous ticks, flash crashes, and gap events that can corrupt moving average calculations. A single outlier in a 20-bar SMA shifts the average by $1/20 = 5\%$ of the outlier magnitude. The trimmed mean bounds this influence: with a 10% trim on 20 bars, the 2 lowest and 2 highest values are discarded, and the remaining 16 values contribute equally. If an outlier falls in a discarded tail, it has zero effect. + +The trimmed mean also appears in economic statistics: the Federal Reserve Bank of Cleveland publishes a 16% trimmed-mean CPI as an alternative inflation measure that filters out volatile food and energy prices. + +## Architecture and Physics + +The computation has three steps per bar: + +**Step 1: Collection** gathers the most recent `period` values into an array, substituting 0 for NaN via `nz()`. + +**Step 2: Sort** arranges the values in ascending order using Pine's built-in `array.sort()`. This is $O(N \log N)$ and dominates the per-bar cost. + +**Step 3: Trimmed average** computes the arithmetic mean of the middle `keepCount` values: + +$$\text{trimCount} = \left\lfloor \frac{\text{period} \times \text{trimPct}}{100} \right\rfloor$$ + +$$\text{keepCount} = \text{period} - 2 \times \text{trimCount}$$ + +$$\text{TRIM} = \frac{1}{\text{keepCount}} \sum_{i=\text{trimCount}}^{\text{trimCount} + \text{keepCount} - 1} x_{(i)}$$ + +where $x_{(i)}$ denotes the $i$-th order statistic. + +**Edge case**: If `keepCount` would fall below 1 (extreme trim percentage with small period), the implementation clamps it to 1 and adjusts `trimCount` accordingly, effectively returning the median. + +**Comparison with WINS**: TRIM discards extreme values entirely, reducing the effective sample size. WINS (Winsorized mean) replaces extremes with boundary values, preserving the full sample size. TRIM has a higher breakdown point for the same percentage, but WINS is more efficient when outliers are moderate rather than extreme. + +## Mathematical Foundation + +The **$\alpha$-trimmed mean** for a sample of size $n$: + +$$\bar{x}_\alpha = \frac{1}{n - 2k} \sum_{i=k+1}^{n-k} x_{(i)}$$ + +where $k = \lfloor \alpha \cdot n \rfloor$ and $\alpha = \text{trimPct}/100$. + +**Influence function**: The trimmed mean has a bounded influence function that equals zero outside the trimmed range: + +$$\text{IF}(x; \bar{x}_\alpha) = \begin{cases} 0 & \text{if } x < x_{(\alpha)} \text{ or } x > x_{(1-\alpha)} \\ \frac{x - \bar{x}_\alpha}{1 - 2\alpha} & \text{otherwise} \end{cases}$$ + +**Breakdown point**: $\alpha$ (the trim fraction). With 10% trim, up to 10% of the data can be arbitrarily corrupted without affecting the estimator. + +**Asymptotic efficiency** relative to SMA under normality: + +| Trim % | Efficiency | +|--------|-----------| +| 0% | 100% (SMA) | +| 5% | ~98% | +| 10% | ~95% | +| 25% | ~85% | +| 50% | ~64% (median) | + +**Parameter constraints**: `period` $\ge 3$, `trimPct` $\in [0, 49]$. + +``` +TRIM(source, period, trimPct): + trimCount = floor(period * trimPct / 100) + keepCount = period - 2 * trimCount + if keepCount < 1: keepCount = 1 + + // Collect and sort + vals = [source[0], source[1], ..., source[period-1]] + sort(vals, ascending) + + // Average middle portion + sum = 0 + for i = trimCount to trimCount + keepCount - 1: + sum += vals[i] + return sum / keepCount +``` + +## Resources + +- Dixon, W.J. "Simplified Estimation from Censored Normal Samples." Annals of Mathematical Statistics, 1960. +- Tukey, J.W. "Exploratory Data Analysis." Addison-Wesley, 1977. +- Huber, P.J. & Ronchetti, E. "Robust Statistics." 2nd edition, Wiley, 2009. +- Wilcox, R.R. "Fundamentals of Modern Statistical Methods." 2nd edition, Springer, 2010. +- Bryan, M. & Cecchetti, S. "Measuring Core Inflation." In Monetary Policy, NBER, 1994. diff --git a/lib/statistics/wavg/Wavg.md b/lib/statistics/wavg/Wavg.md new file mode 100644 index 00000000..2998e97b --- /dev/null +++ b/lib/statistics/wavg/Wavg.md @@ -0,0 +1,83 @@ +# WAVG: Weighted Average + +The Weighted Average computes a rolling linearly-weighted mean where the most recent observation receives weight $N$ and the oldest receives weight 1, making it mathematically identical to the Weighted Moving Average (WMA) but categorized as a statistical measure. The implementation uses a circular buffer with an $O(1)$ incremental update scheme: rather than recomputing the full weighted sum each bar, it maintains running sums and adjusts them through add/subtract operations as values enter and exit the window. This makes WAVG one of the most efficient weighted estimators available, with constant per-bar cost regardless of the lookback period. + +## Historical Context + +The linearly-weighted average is one of the oldest weighted estimators, predating formal statistical theory. The concept of assigning decreasing importance to older observations appears in early actuarial work (17th-18th centuries) and was formalized in weather forecasting by the mid-19th century. In technical analysis, the Weighted Moving Average became popular through the work of Martin Pring and other chartists who sought a middle ground between the SMA (equal weights, excessive lag) and the EMA (exponential weights, infinite memory). + +The linear weighting scheme assigns weight $w_i = i + 1$ to the $i$-th sample from oldest ($i = 0$) to newest ($i = N-1$). This produces a centroid (center of mass) that is biased toward recent data: the effective lag is $N/3$ bars compared to $(N-1)/2$ for the SMA. The triangular weight distribution means the most recent value contributes $2/(N+1)$ times the total weight, versus $1/N$ for the SMA. + +The $O(1)$ update trick used in this implementation is well known in DSP: the weighted sum $W = \sum i \cdot x_i$ can be maintained incrementally by tracking the unweighted sum $S = \sum x_i$ and noting that when all indices shift by 1, $W_{\text{new}} = W_{\text{old}} - S_{\text{old}} + N \cdot x_{\text{new}}$. + +## Architecture and Physics + +The implementation uses a circular buffer of size `period` with three state variables: + +- `weightedSum`: The current linearly-weighted sum $\sum_{i=1}^{n} i \cdot x_{(i)}$ where $(i)$ is position from oldest. +- `runningSum`: The unweighted sum $\sum x_i$ of all values in the buffer. +- `count`: The current fill level (increases during warmup, equals `period` at steady state). + +**Per-bar update** ($O(1)$ operations): + +1. **Remove departing value**: If the buffer position being overwritten contains a valid value, subtract it from `runningSum`. +2. **Shift weights down**: Subtract `runningSum` from `weightedSum`. This decrements every existing value's weight by 1 (equivalent to aging all observations). +3. **Add new value**: Add `srcVal` to `runningSum` and add `count * srcVal` to `weightedSum` (new value gets the highest weight). +4. **Store and advance**: Write to the circular buffer and advance the head pointer. + +**Normalization**: The denominator is $n(n+1)/2$ where $n$ is the current count. This handles the warmup period naturally: when only $k < N$ values have been received, the result uses $k$-based weights. + +## Mathematical Foundation + +The linearly-weighted average with window size $n$: + +$$\text{WAVG} = \frac{\sum_{i=0}^{n-1} (i + 1) \cdot x_{n-1-i}}{\sum_{i=0}^{n-1} (i + 1)} = \frac{\sum_{i=1}^{n} i \cdot x_i}{\frac{n(n+1)}{2}}$$ + +where $x_n$ is the most recent value (weight $n$) and $x_1$ is the oldest (weight 1). + +**Effective lag** (centroid offset from current bar): + +$$\text{lag} = \frac{\sum_{i=0}^{n-1} i \cdot (n - i)}{\sum_{i=0}^{n-1}(n-i)} = \frac{n-1}{3}$$ + +**O(1) incremental update** on arrival of new value $x_{\text{new}}$ and departure of $x_{\text{old}}$: + +$$S_{\text{new}} = S_{\text{old}} - x_{\text{old}} + x_{\text{new}}$$ + +$$W_{\text{new}} = W_{\text{old}} - S_{\text{old}} + n \cdot x_{\text{new}}$$ + +$$\text{WAVG} = \frac{W_{\text{new}}}{n(n+1)/2}$$ + +**Weight distribution**: Weight of position $i$ from newest is $\frac{n - i}{n(n+1)/2}$. Most recent: $\frac{2}{n+1}$. Oldest: $\frac{2}{n(n+1)}$. + +**Parameter constraints**: `period` $> 0$. + +``` +WAVG(source, period): + // State variables (persistent) + var buffer[period], head = 0, weightedSum = 0, runningSum = 0, count = 0 + + srcVal = nz(source) + oldest = buffer[head] + + if oldest is valid: + runningSum -= oldest + else: + count += 1 + + weightedSum -= runningSum // shift all weights down by 1 + runningSum += srcVal + weightedSum += count * srcVal // new value gets highest weight + + buffer[head] = srcVal + head = (head + 1) % period + + denom = count * (count + 1) / 2 + return denom > 0 ? weightedSum / denom : srcVal +``` + +## Resources + +- Pring, M.J. "Technical Analysis Explained." 5th edition, McGraw-Hill, 2014. +- Murphy, J.J. "Technical Analysis of the Financial Markets." New York Institute of Finance, 1999. +- Oppenheim, A.V. & Schafer, R.W. "Discrete-Time Signal Processing." 3rd edition, Pearson, 2010. +- Haykin, S. "Adaptive Filter Theory." 5th edition, Pearson, 2013. diff --git a/lib/statistics/wins/Wins.md b/lib/statistics/wins/Wins.md new file mode 100644 index 00000000..2bbd8991 --- /dev/null +++ b/lib/statistics/wins/Wins.md @@ -0,0 +1,89 @@ +# WINS: Winsorized Mean Moving Average + +The Winsorized Mean Moving Average computes a rolling average after replacing (not discarding) the most extreme values in each tail with the boundary values at the trim point. Unlike the trimmed mean (TRIM) which removes outliers entirely, Winsorization preserves the full sample size by clamping extreme values to the nearest non-extreme observation. At `winPct = 0` it degenerates to the SMA; at `winPct = 50` all values equal the median pair. The default 10% Winsorization provides a robust central tendency estimator that dampens outlier impact while maintaining the statistical efficiency advantages of the full sample size. + +## Historical Context + +Winsorization is named after Charles P. Winsor, a biostatistician at Harvard, though the technique was popularized by John Tukey (1962) who credited Winsor with the idea. The concept arises naturally from the question: "what if instead of throwing away extreme values, we replace them with the most extreme non-discarded value?" This produces an estimator that is more efficient than the trimmed mean under light contamination models while retaining comparable robustness. + +The distinction between trimming and Winsorizing is subtle but consequential. Consider a 20-bar window with 10% processing: TRIM discards the 2 lowest and 2 highest values, averaging the remaining 16. WINS replaces the 2 lowest with the 3rd-lowest value and the 2 highest with the 3rd-highest, averaging all 20. Both have the same breakdown point (10%), but WINS has higher asymptotic efficiency because it uses all $n$ observations in the average. + +In financial applications, Winsorization is standard practice in factor modeling: Fama-French factor returns are typically Winsorized at 1% or 5% to prevent a handful of extreme observations from dominating cross-sectional regressions. The Winsorized mean is also used in the construction of robust risk measures like the Winsorized variance and the Winsorized covariance matrix. + +## Architecture and Physics + +The computation has three steps per bar: + +**Step 1: Collection** gathers the most recent `period` values into an array, substituting 0 for NaN via `nz()`. + +**Step 2: Sort and clamp** arranges values in ascending order, then replaces the lowest `winCount` values with the value at index `winCount` (the lower boundary) and the highest `winCount` values with the value at index `period - 1 - winCount` (the upper boundary): + +$$\text{winCount} = \left\lfloor \frac{\text{period} \times \text{winPct}}{100} \right\rfloor$$ + +The clamping preserves the boundary values themselves; only values beyond them are replaced. + +**Step 3: Average** computes the arithmetic mean of all `period` values (including the replaced ones). Since replaced values equal the boundary values, this is equivalent to: + +$$\text{WINS} = \frac{\text{winCount} \cdot x_{(k+1)} + \sum_{i=k+1}^{n-k} x_{(i)} + \text{winCount} \cdot x_{(n-k)}}{n}$$ + +where $k = \text{winCount}$ and $x_{(i)}$ is the $i$-th order statistic. + +**Edge case**: If `winCount` would reach or exceed `period / 2`, it is clamped to `(period - 1) / 2`, producing the median pair (two middle values) replicated across all positions. + +## Mathematical Foundation + +The **Winsorized mean** for a sample of size $n$ with $k$ replacements per tail: + +$$\bar{x}_W = \frac{1}{n}\left[k \cdot x_{(k+1)} + \sum_{i=k+1}^{n-k} x_{(i)} + k \cdot x_{(n-k)}\right]$$ + +where $x_{(i)}$ is the $i$-th order statistic and $k = \lfloor \alpha n \rfloor$ with $\alpha = \text{winPct}/100$. + +**Winsorized variance** (used for inference on the Winsorized mean): + +$$s_W^2 = \frac{1}{n-1} \sum_{i=1}^{n} (w_i - \bar{x}_W)^2$$ + +where $w_i$ are the Winsorized values. + +**Influence function**: Bounded like TRIM, but the boundary behavior differs: + +$$\text{IF}(x; \bar{x}_W) = \begin{cases} x_{(\alpha)} - \bar{x}_W & \text{if } x \le x_{(\alpha)} \\ x - \bar{x}_W & \text{if } x_{(\alpha)} < x < x_{(1-\alpha)} \\ x_{(1-\alpha)} - \bar{x}_W & \text{if } x \ge x_{(1-\alpha)} \end{cases}$$ + +**Breakdown point**: $\alpha$ (the Winsorization fraction). + +**Asymptotic efficiency** relative to SMA under normality (higher than TRIM at same percentage): + +| Win % | WINS Efficiency | TRIM Efficiency | +|-------|----------------|-----------------| +| 0% | 100% | 100% | +| 10% | ~97% | ~95% | +| 25% | ~90% | ~85% | + +**Parameter constraints**: `period` $\ge 3$, `winPct` $\in [0, 49]$. + +``` +WINS(source, period, winPct): + winCount = floor(period * winPct / 100) + if winCount >= period/2: winCount = (period-1)/2 + + // Collect and sort + vals = [source[0], source[1], ..., source[period-1]] + sort(vals, ascending) + + // Replace tails with boundary values + lowerBound = vals[winCount] + upperBound = vals[period - 1 - winCount] + for i = 0 to winCount-1: + vals[i] = lowerBound + vals[period - 1 - i] = upperBound + + // Average all values (full sample size) + return mean(vals) +``` + +## Resources + +- Tukey, J.W. "The Future of Data Analysis." Annals of Mathematical Statistics, 1962. +- Huber, P.J. & Ronchetti, E. "Robust Statistics." 2nd edition, Wiley, 2009. +- Wilcox, R.R. "Introduction to Robust Estimation and Hypothesis Testing." 4th edition, Academic Press, 2017. +- Fama, E.F. & French, K.R. "Common Risk Factors in the Returns on Stocks and Bonds." Journal of Financial Economics, 1993. +- Dixon, W.J. & Tukey, J.W. "Approximate Behavior of the Distribution of Winsorized t." Technometrics, 1968. diff --git a/lib/trends_FIR/crma/Crma.md b/lib/trends_FIR/crma/Crma.md new file mode 100644 index 00000000..851dc9d6 --- /dev/null +++ b/lib/trends_FIR/crma/Crma.md @@ -0,0 +1,90 @@ +# CRMA: Cubic Regression Moving Average + +> "Linear regression tells you where the trend is going. Quadratic regression tells you it's curving. Cubic regression tells you the curve is changing its mind." + +CRMA fits a degree-3 polynomial $y = a_0 + a_1 x + a_2 x^2 + a_3 x^3$ to the most recent $N$ bars via ordinary least squares, then returns the fitted endpoint value $a_0$. By capturing inflection and curvature that linear and quadratic models miss, CRMA tracks S-shaped reversals and accelerating trends with measurably lower endpoint error than LSMA or QRMA on non-stationary price series. The cost is a 4x4 linear system solve per bar, which is O(1) once power sums are accumulated in O(N). + +## Historical Context + +Polynomial regression as a smoothing technique dates to Legendre (1805) and Gauss (1809), who independently developed the method of least squares. The specific application of cubic (degree-3) polynomial fitting to financial time series emerged from the broader Savitzky-Golay filtering framework published in 1964, which showed that polynomial regression over a sliding window produces FIR filter coefficients with desirable frequency-domain properties. + +CRMA occupies the sweet spot in the polynomial hierarchy. Degree-1 (LSMA) captures only linear trends. Degree-2 (QRMA) adds curvature but misses inflection points. Degree-3 (CRMA) captures inflection, the point where acceleration changes sign, which is precisely where trend reversals begin. Degree-4 and above risk Runge's phenomenon: oscillatory artifacts near window edges that amplify noise rather than suppress it. + +The key implementation difference from textbook polynomial regression is the x-indexing convention. CRMA uses $x = 0$ for the newest bar and $x = N-1$ for the oldest. This means the fitted endpoint is simply $a_0$, the intercept, avoiding the numerical instability of evaluating $a_0 + a_1(N-1) + a_2(N-1)^2 + a_3(N-1)^3$ with large $N$. + +## Architecture & Physics + +### 1. Normal Equations Assembly + +The polynomial fit requires solving $\mathbf{M} \cdot \mathbf{a} = \mathbf{r}$ where: + +$$ +M_{ij} = \sum_{k=0}^{N-1} x_k^{i+j}, \quad r_i = \sum_{k=0}^{N-1} x_k^i \cdot y_k, \quad i,j \in \{0,1,2,3\} +$$ + +Seven power sums ($S_0$ through $S_6$) and four cross-products ($r_0$ through $r_3$) are accumulated in a single O(N) pass over the circular buffer. + +### 2. Gaussian Elimination with Partial Pivoting + +The 4x4 augmented matrix is solved via Gaussian elimination with partial pivoting. Partial pivoting prevents division-by-zero and minimizes round-off amplification. The pivot search, row swap, and elimination are all O(1) operations on a fixed 4x4 system (64 element accesses, 48 multiply-adds). + +### 3. Back-Substitution + +After elimination produces an upper-triangular system, back-substitution extracts $a_3, a_2, a_1, a_0$ in four steps. The result $a_0$ is the fitted value at $x = 0$ (newest bar). + +### 4. Singular Matrix Guard + +If the pivot magnitude falls below $10^{-12}$, the system is treated as singular and the raw price is returned. This handles degenerate cases (e.g., all identical prices, $N < 4$ effective points). + +## Mathematical Foundation + +The cubic regression minimizes the sum of squared residuals: + +$$ +\min_{a_0, a_1, a_2, a_3} \sum_{k=0}^{N-1} \left( y_k - a_0 - a_1 x_k - a_2 x_k^2 - a_3 x_k^3 \right)^2 +$$ + +Setting partial derivatives to zero yields the 4x4 normal equation system: + +$$ +\begin{bmatrix} S_0 & S_1 & S_2 & S_3 \\ S_1 & S_2 & S_3 & S_4 \\ S_2 & S_3 & S_4 & S_5 \\ S_3 & S_4 & S_5 & S_6 \end{bmatrix} \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \end{bmatrix} = \begin{bmatrix} r_0 \\ r_1 \\ r_2 \\ r_3 \end{bmatrix} +$$ + +Where: + +$$ +S_m = \sum_{k=0}^{N-1} k^m, \quad r_m = \sum_{k=0}^{N-1} k^m \cdot y_k +$$ + +The power sums $S_m$ have closed-form expressions (Faulhaber's formulas), but accumulating them in the data loop adds negligible cost and avoids large intermediate products. + +**Default parameters:** `period = 14`, `minPeriod = 4` (minimum for degree-3 fit). + +**Pseudo-code (streaming):** + +``` +buffer ← circular_buffer(period) +buffer.push(price) +n ← min(bar_count, period) +if n < 4: return price + +// Accumulate sums in O(n) +for i = 0 to n-1: + x = i; x2 = x*x; x3 = x2*x + S0 += 1; S1 += x; S2 += x2; S3 += x3 + S4 += x2*x2; S5 += x2*x3; S6 += x3*x3 + r0 += y[i]; r1 += x*y[i]; r2 += x2*y[i]; r3 += x3*y[i] + +// Build 4×5 augmented matrix, solve via Gaussian elimination +M = [[S0,S1,S2,S3,r0], [S1,S2,S3,S4,r1], [S2,S3,S4,S5,r2], [S3,S4,S5,S6,r3]] +gaussian_eliminate_partial_pivot(M) +a = back_substitute(M) +return a[0] // fitted value at x=0 (newest bar) +``` + +## Resources + +- Legendre, A.-M. (1805). *Nouvelles méthodes pour la détermination des orbites des comètes*. Firmin Didot. +- Gauss, C.F. (1809). *Theoria motus corporum coelestium*. Perthes et Besser. +- Savitzky, A. & Golay, M.J.E. (1964). "Smoothing and Differentiation of Data by Simplified Least Squares Procedures." *Analytical Chemistry*, 36(8), 1627-1639. +- Press, W.H. et al. (2007). *Numerical Recipes*, 3rd ed. Cambridge University Press. Chapter 15: Modeling of Data. diff --git a/lib/trends_FIR/hend/Hend.md b/lib/trends_FIR/hend/Hend.md new file mode 100644 index 00000000..fbab46da --- /dev/null +++ b/lib/trends_FIR/hend/Hend.md @@ -0,0 +1,78 @@ +# HEND: Henderson Moving Average + +> "Robert Henderson designed a filter so good that the Australian Bureau of Statistics still uses it a century later. When your smoothing algorithm outlasts empires, you did something right." + +HEND is a symmetric FIR filter derived from the Henderson (1916) closed-form weight formula, designed to pass cubic polynomial trends without distortion while maximally suppressing irregular noise. Used as the core smoother in the X-11 and X-13ARIMA-SEATS seasonal adjustment frameworks by statistical agencies worldwide, HEND achieves the theoretically optimal trade-off between smoothness (measured by the sum of squared third differences of the weights) and fidelity for cubic trends. Weights can be negative at the edges, giving the filter a bandpass-like property that sharpens trend-cycle extraction. + +## Historical Context + +Robert Henderson published the weight formula in 1916 in the *Transactions of the Actuarial Society of America*, motivated by the need to graduate mortality tables without distorting underlying polynomial trends. The U.S. Census Bureau adopted Henderson filters as the trend-cycle component of the X-11 method (Shiskin, Young, and Musgrave, 1967), where 5, 9, 13, and 23-point Henderson filters became standard choices. The Australian Bureau of Statistics (ABS) uses the 13-point Henderson as its default trend estimator for quarterly national accounts. + +Henderson's filter has a unique property among polynomial-preserving smoothers: it minimizes the sum of squared third differences of the filter weights subject to the constraint that polynomials up to degree 3 pass through unchanged. This optimality criterion produces smoother weight sequences than Savitzky-Golay filters of the same polynomial order, at the cost of a fixed (non-configurable) smoothness-fidelity balance. + +The requirement for odd period length ($N \geq 5$) stems from the symmetric weight structure. Even-length Henderson filters are mathematically possible but break the centered-symmetry property that guarantees zero phase distortion. + +## Architecture & Physics + +### 1. Weight Computation (One-Time) + +Weights are computed from Henderson's closed-form formula: + +$$ +w(k) = \frac{315 \left[(n-1)^2 - k^2\right]\left[n^2 - k^2\right]\left[(n+1)^2 - k^2\right]\left[3n^2 - 16 - 11k^2\right]}{8n(n^2-1)(4n^2-1)(4n^2-9)(4n^2-25)} +$$ + +where $n = (N+3)/2$ and $k$ ranges from $-(N-1)/2$ to $(N-1)/2$. Weights are normalized to sum to 1.0 after computation. + +### 2. Symmetric Convolution + +The filter applies as a standard FIR convolution over the circular buffer. Because weights are symmetric ($w(k) = w(-k)$), the implementation can exploit symmetry to halve multiplications, though the normalization step makes this optional. + +### 3. Negative Edge Weights + +Unlike most window-based averages, Henderson weights are negative at the extremes of the window. This is not a bug; it is the mechanism by which the filter suppresses low-frequency drift that would distort cubic trends. The negative wings act as a gentle high-pass correction. + +## Mathematical Foundation + +The Henderson filter minimizes: + +$$ +\min_{\{w_k\}} \sum_{k} (\Delta^3 w_k)^2 \quad \text{subject to} \quad \sum_{k} k^j w_k = \delta_{j0}, \quad j = 0, 1, 2, 3 +$$ + +where $\Delta^3$ is the third-difference operator. The constraints ensure that constant, linear, quadratic, and cubic polynomials are reproduced exactly. + +The closed-form solution with $n = (N+3)/2$, $k \in [-(N-1)/2, (N-1)/2]$: + +$$ +w(k) = \frac{315 \cdot \left[(n-1)^2 - k^2\right]\left[n^2 - k^2\right]\left[(n+1)^2 - k^2\right]\left[3n^2 - 16 - 11k^2\right]}{8n(n^2-1)(4n^2-1)(4n^2-9)(4n^2-25)} +$$ + +**Frequency response:** The Henderson filter has zeros at specific frequencies determined by the polynomial-preservation constraints. For the 13-point filter, sidelobe attenuation exceeds $-40$ dB. + +**Default parameters:** `period = 7` (must be odd, $\geq 5$). + +**Pseudo-code (streaming):** + +``` +// One-time weight computation +half = (period - 1) / 2 +n = (period + 3) / 2 +for k = -half to half: + w[k] = 315 * ((n-1)²-k²) * (n²-k²) * ((n+1)²-k²) * (3n²-16-11k²) + / [8n(n²-1)(4n²-1)(4n²-9)(4n²-25)] +normalize(w) + +// Per-bar convolution +buffer.push(price) +if count < period: return price +result = Σ buffer[j] * w[j] for j = 0..period-1 +return result +``` + +## Resources + +- Henderson, R. (1916). "Note on Graduation by Adjusted Average." *Transactions of the Actuarial Society of America*, 17, 43-48. +- Shiskin, J., Young, A.H., & Musgrave, J.C. (1967). "The X-11 Variant of the Census Method II Seasonal Adjustment Program." Technical Paper 15, U.S. Bureau of the Census. +- Hyndman, R.J. (2011). "Moving Averages." In *International Encyclopedia of Statistical Science*. Springer. +- Kenny, P.B. & Durbin, J. (1982). "Local Trend Estimation and Seasonal Adjustment of Economic and Social Time Series." *JRSS Series A*, 145(1), 1-41. diff --git a/lib/trends_FIR/ilrs/Ilrs.md b/lib/trends_FIR/ilrs/Ilrs.md new file mode 100644 index 00000000..b10894e5 --- /dev/null +++ b/lib/trends_FIR/ilrs/Ilrs.md @@ -0,0 +1,99 @@ +# ILRS: Integral of Linear Regression Slope + +> "John Ehlers took the slope of a regression line, integrated it, and got a smoother trend follower. Differentiate to find direction, integrate to find position. Calculus: still useful after 300 years." + +ILRS computes the linear regression slope over a rolling window, then accumulates it via discrete integration (running sum) to reconstruct a smoothed price-level signal. By differentiating (slope extraction) and reintegrating, ILRS acts as a low-pass filter that preserves trend direction while suppressing high-frequency noise more aggressively than LSMA. The integration step introduces a natural momentum quality: the output continues rising even as slope magnitude diminishes, making ILRS particularly effective for trend-following systems that need early exit signals based on slope deceleration. + +## Historical Context + +John Ehlers introduced ILRS in *Rocket Science for Traders* (Wiley, 2001) as part of his signal-processing approach to technical analysis. Ehlers recognized that most moving averages are essentially low-pass filters applied directly to price, but the differentiate-then-integrate approach offers a different noise profile. The linear regression slope acts as a first-derivative estimator, and the running sum reconstructs the original signal minus the high-frequency components that the regression window cannot track. + +The concept has deep roots in control theory and signal processing. The "differentiate and integrate" technique is standard in PID controllers and phase-locked loops, where it provides better noise rejection than direct filtering when the signal's derivative is smoother than the signal itself. In financial time series, this condition holds when price changes are more persistent than price levels, a reasonable assumption during trending regimes. + +ILRS differs from LSMA (which evaluates the regression line at the endpoint) in a crucial way: LSMA's output is bounded by the regression window, while ILRS accumulates indefinitely. This makes ILRS a non-stationary filter whose output drifts with the integrated slope, requiring periodic resynchronization to avoid floating-point drift over very long series. + +## Architecture & Physics + +### 1. Rolling Linear Regression Slope + +The slope is computed via the standard least-squares formula over the circular buffer: + +$$ +\text{slope} = \frac{N \sum x_i y_i - \sum x_i \sum y_i}{N \sum x_i^2 - \left(\sum x_i\right)^2} +$$ + +The x-index sums ($\sum x$, $\sum x^2$) are computed analytically (Faulhaber's formulas), reducing the per-bar cost to a single O(N) pass for the y-dependent sums. + +### 2. Discrete Integration + +The integral is a simple running sum: + +$$ +\text{ILRS}_t = \text{ILRS}_{t-1} + \text{slope}_t +$$ + +This is O(1) per bar after the slope is computed. + +### 3. Initialization + +The integral is initialized to the first price value, ensuring the output starts at a reasonable level rather than zero. + +### 4. Drift Management + +Because ILRS accumulates slope indefinitely, floating-point precision degrades over millions of bars. A periodic resynchronization (e.g., every 1000 bars, re-anchor to slope-implied price) prevents meaningful drift. + +## Mathematical Foundation + +Given a window of $N$ prices $y_0, y_1, \ldots, y_{N-1}$ (oldest to newest), the regression slope is: + +$$ +b = \frac{N \sum_{i=0}^{N-1} i \cdot y_i - \left(\sum_{i=0}^{N-1} i\right)\left(\sum_{i=0}^{N-1} y_i\right)}{N \sum_{i=0}^{N-1} i^2 - \left(\sum_{i=0}^{N-1} i\right)^2} +$$ + +With analytical x-sums: + +$$ +\sum i = \frac{N(N-1)}{2}, \quad \sum i^2 = \frac{N(N-1)(2N-1)}{6} +$$ + +The ILRS output: + +$$ +\text{ILRS}_t = \text{ILRS}_{t-1} + b_t, \quad \text{ILRS}_0 = y_0 +$$ + +**Default parameters:** `period = 14`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +buffer ← circular_buffer(period) +buffer.push(price) +n ← min(bar_count, period) + +if n < 2: + integral ← price + return integral + +// Analytical x-sums +sumX = n*(n-1)/2 +sumX2 = n*(n-1)*(2n-1)/6 + +// Data-dependent y-sums (O(n) pass) +sumY = 0; sumXY = 0 +for i = 0 to n-1: + sumY += buffer[i] + sumXY += i * buffer[i] + +denomX = n * sumX2 - sumX * sumX +slope = (n * sumXY - sumX * sumY) / denomX + +integral += slope +return integral +``` + +## Resources + +- Ehlers, J.F. (2001). *Rocket Science for Traders: Digital Signal Processing Applications*. John Wiley & Sons. +- Ehlers, J.F. (2004). *Cybernetic Analysis for Stocks and Futures*. John Wiley & Sons. +- Kendall, M.G. & Stuart, A. (1979). *The Advanced Theory of Statistics*, Vol. 2. Griffin. Chapter 29: Regression. diff --git a/lib/trends_FIR/kaiser/Kaiser.md b/lib/trends_FIR/kaiser/Kaiser.md new file mode 100644 index 00000000..88337129 --- /dev/null +++ b/lib/trends_FIR/kaiser/Kaiser.md @@ -0,0 +1,94 @@ +# KAISER: Kaiser Window Moving Average + +> "James Kaiser gave signal processing a knob. Turn beta up, sidelobes go down, transition band widens. Turn it down, you get an SMA. One parameter to rule them all." + +KAISER applies the Kaiser-Bessel window function as FIR filter weights, providing a single parameter ($\beta$) that continuously controls the trade-off between main lobe width (transition band sharpness) and sidelobe attenuation (stopband rejection). At $\beta = 0$ it degenerates to a rectangular window (SMA); at $\beta \approx 5.65$ it approximates the Blackman window; at $\beta \approx 8.6$ it matches the Hamming window's sidelobe profile. This makes KAISER the most flexible single-parameter window-based moving average, allowing traders to tune frequency selectivity without changing the window length. + +## Historical Context + +James F. Kaiser and Ronald W. Schafer published the Kaiser window in 1980, building on Kaiser's earlier work at Bell Labs in the 1960s. The window was motivated by a practical problem: given a desired sidelobe attenuation level, what is the shortest FIR filter that achieves it? Kaiser showed that the modified Bessel function of the first kind, $I_0$, produces near-optimal windows that closely approximate the prolate spheroidal wave functions (the theoretically optimal windows derived by Slepian in 1964) while being far simpler to compute. + +The Kaiser window became the default design tool in DSP textbooks (Oppenheim & Schafer, Parks & Burrus) because of its parametric flexibility. In financial applications, this flexibility maps directly to a smoothness-responsiveness knob: low $\beta$ preserves fast price movements (less smoothing, sharper transitions), while high $\beta$ produces smoother output with greater lag (more attenuation of high-frequency price noise). + +The $I_0$ Bessel function is computed via power series: $I_0(x) = \sum_{m=0}^{M} \left[\frac{(x/2)^m}{m!}\right]^2$. Twenty-five terms provide double-precision convergence for $\beta \leq 20$. + +## Architecture & Physics + +### 1. Bessel Function Approximation + +The zeroth-order modified Bessel function $I_0(x)$ is evaluated via its power series with 25 terms. The series converges rapidly because the terms are squared factorials, guaranteeing monotonic decrease after the peak term. + +### 2. Weight Computation (One-Time) + +For each position $k \in [0, N-1]$, the normalized coordinate $t = 2k/(N-1) - 1$ maps to $[-1, 1]$. The Kaiser window value is: + +$$ +w(k) = \frac{I_0\left(\beta \sqrt{1 - t^2}\right)}{I_0(\beta)} +$$ + +Weights are normalized to sum to 1.0. The $\sqrt{1-t^2}$ argument is clamped to non-negative to handle floating-point edge cases. + +### 3. FIR Convolution + +Standard weighted sum over the circular buffer using precomputed weights. O(N) per bar. + +## Mathematical Foundation + +The Kaiser window function for a filter of length $N$: + +$$ +w[k] = \frac{I_0\left(\beta\sqrt{1 - \left(\frac{2k}{N-1} - 1\right)^2}\right)}{I_0(\beta)}, \quad k = 0, 1, \ldots, N-1 +$$ + +where $I_0(x)$ is the zeroth-order modified Bessel function of the first kind: + +$$ +I_0(x) = \sum_{m=0}^{\infty} \left[\frac{(x/2)^m}{m!}\right]^2 +$$ + +**Key $\beta$ values and their equivalences:** + +| $\beta$ | Equivalent Window | Sidelobe (dB) | Transition BW | +| :---: | :--- | :---: | :---: | +| 0 | Rectangular (SMA) | $-13$ | $0.92/N$ | +| 3.0 | General-purpose | $-33$ | $2.4/N$ | +| 5.65 | Blackman-like | $-57$ | $3.6/N$ | +| 8.6 | Hamming-like | $-90$ | $5.0/N$ | + +**Kaiser's empirical formulas** (for filter design): + +$$ +\beta = \begin{cases} 0.1102(A - 8.7) & A > 50 \\ 0.5842(A-21)^{0.4} + 0.07886(A-21) & 21 \leq A \leq 50 \\ 0 & A < 21 \end{cases} +$$ + +where $A = -20\log_{10}(\delta)$ is the desired stopband attenuation in dB. + +**Default parameters:** `period = 14`, `beta = 3.0`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +// One-time: compute I0 and weights +bessel_i0(x): + sum = 1.0; term = 1.0; hx = x/2 + for m = 1 to 25: term *= hx/m; sum += term² + return sum + +i0_beta = bessel_i0(beta) +for k = 0 to period-1: + t = 2k/(N-1) - 1 + arg = sqrt(max(0, 1 - t²)) + w[k] = bessel_i0(beta * arg) / i0_beta +normalize(w) + +// Per-bar convolution +buffer.push(price) +if count < period: return price +return Σ buffer[j] * w[j] +``` + +## Resources + +- Kaiser, J.F. & Schafer, R.W. (1980). "On the Use of the I0-Sinh Window for Spectrum Analysis." *IEEE Trans. Acoust., Speech, Signal Process.*, ASSP-28(1), 105-107. +- Oppenheim, A.V. & Schafer, R.W. (2009). *Discrete-Time Signal Processing*, 3rd ed. Prentice Hall. Section 7.4. +- Slepian, D. (1964). "Prolate Spheroidal Wave Functions, Fourier Analysis and Uncertainty." *Bell System Technical Journal*, 43(6), 3009-3057. diff --git a/lib/trends_FIR/lanczos/Lanczos.md b/lib/trends_FIR/lanczos/Lanczos.md new file mode 100644 index 00000000..5b192d85 --- /dev/null +++ b/lib/trends_FIR/lanczos/Lanczos.md @@ -0,0 +1,82 @@ +# LANCZOS: Lanczos (Sinc) Window Moving Average + +> "Cornelius Lanczos used the sinc function to reconstruct band-limited signals from discrete samples. Apply it to price data and you get a moving average that respects the Nyquist limit while your competitors are still using SMAs." + +LANCZOS applies the normalized sinc function $\text{sinc}(x) = \sin(\pi x)/(\pi x)$ as a symmetric FIR window, producing a moving average with near-ideal low-pass frequency characteristics. The sinc function is the impulse response of the perfect brick-wall low-pass filter; windowing it to finite length trades sharp cutoff for practical realizability. The result is a smoother with minimal Gibbs phenomenon ringing and excellent passband flatness, at the cost of small negative sidelobe weights that can cause minor overshooting on sharp price discontinuities. + +## Historical Context + +Cornelius Lanczos (1893-1974) was a Hungarian-American mathematician and physicist who made foundational contributions to applied mathematics, including the Lanczos algorithm for eigenvalue computation, the Lanczos tau method for differential equations, and the Lanczos sigma factor for reducing Gibbs phenomenon in Fourier series. His 1956 book *Applied Analysis* introduced the sinc-based window that bears his name. + +The Lanczos window is the simplest sinc-kernel window: a single lobe of the sinc function, truncated to the filter length. Higher-order Lanczos kernels (Lanczos-2, Lanczos-3) multiply $\text{sinc}(x) \cdot \text{sinc}(x/a)$ for sharper cutoff and are widely used in image resampling (e.g., the default resizer in FFmpeg and ImageMagick). For financial time series, the first-order Lanczos window provides a good balance between frequency selectivity and computational simplicity. + +The key property distinguishing Lanczos from other window-based MAs is the sinc function's direct relationship to the ideal low-pass filter. While Hann, Hamming, and Blackman windows are ad-hoc designs optimized for sidelobe suppression, the Lanczos window starts from the theoretically optimal impulse response and truncates it, preserving the passband flatness that other windows sacrifice for sidelobe control. + +## Architecture & Physics + +### 1. Weight Computation (One-Time) + +For each position $k \in [0, N-1]$, the normalized coordinate $x = 2k/(N-1) - 1$ maps to $[-1, 1]$. The Lanczos window value is: + +$$ +w(k) = \text{sinc}(x) = \frac{\sin(\pi x)}{\pi x}, \quad w(0) = 1 +$$ + +The sinc function produces negative values for $|x| > 1$ in the general case, but within the $[-1, 1]$ window, negative weights appear only near the edges where $|x|$ approaches 1. These negative weights are retained (not clamped) for frequency-domain fidelity. + +### 2. Normalization + +Weights are normalized to sum to 1.0, ensuring the filter preserves constant (DC) signals exactly. + +### 3. FIR Convolution + +Standard weighted convolution over the circular buffer. O(N) per bar. The symmetric weight structure enables potential paired-multiplication optimization (summing symmetric buffer pairs before multiplying by the shared weight). + +## Mathematical Foundation + +The Lanczos window for a filter of length $N$: + +$$ +w[k] = \text{sinc}\!\left(\frac{2k}{N-1} - 1\right), \quad k = 0, 1, \ldots, N-1 +$$ + +where: + +$$ +\text{sinc}(x) = \begin{cases} 1 & x = 0 \\ \frac{\sin(\pi x)}{\pi x} & x \neq 0 \end{cases} +$$ + +**Frequency response:** The continuous sinc function has an ideal rectangular frequency response (brick-wall low-pass). Truncation introduces sidelobes at approximately $-13$ dB for the first sidelobe (comparable to the rectangular window), with subsequent sidelobes decaying as $1/f$. The passband flatness is superior to most other windows of the same length. + +**Normalized output:** + +$$ +\text{LANCZOS}_t = \frac{\sum_{k=0}^{N-1} w[k] \cdot x_{t-k}}{\sum_{k=0}^{N-1} w[k]} +$$ + +**Default parameters:** `period = 14`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +// One-time weight computation +for k = 0 to period-1: + x = 2k/(N-1) - 1 + if |x| < 1e-10: + w[k] = 1.0 + else: + w[k] = sin(π·x) / (π·x) +normalize(w) + +// Per-bar convolution +buffer.push(price) +if count < period: return price +return Σ buffer[j] * w[j] +``` + +## Resources + +- Lanczos, C. (1956). *Applied Analysis*. Prentice-Hall. Reprinted by Dover, 1988. +- Duchon, C.E. (1979). "Lanczos Filtering in One and Two Dimensions." *Journal of Applied Meteorology*, 18(8), 1016-1022. +- Oppenheim, A.V. & Schafer, R.W. (2009). *Discrete-Time Signal Processing*, 3rd ed. Prentice Hall. Section 7.2: Properties of Commonly Used Windows. +- Turkowski, K. (1990). "Filters for Common Resampling Tasks." In *Graphics Gems I*, Academic Press. pp. 147-165. diff --git a/lib/trends_FIR/parzen/Parzen.md b/lib/trends_FIR/parzen/Parzen.md new file mode 100644 index 00000000..8f662fee --- /dev/null +++ b/lib/trends_FIR/parzen/Parzen.md @@ -0,0 +1,93 @@ +# PARZEN: Parzen (de la Vallée-Poussin) Window Moving Average + +> "Emanuel Parzen convolved two triangular windows and got a piecewise cubic with zero sidelobe discontinuity. When your window function is its own proof of smoothness, the spectral leakage has nowhere to hide." + +PARZEN applies the Parzen (de la Vallée-Poussin) window function as FIR filter weights, producing a moving average with exceptional sidelobe suppression ($-24$ dB/octave rolloff) and a smooth bell-shaped kernel. The Parzen window is the self-convolution of two triangular (Bartlett) windows at half-length, which guarantees continuous first and second derivatives at all points. This makes it one of the few windows whose frequency response has no discontinuities in its first three derivatives, yielding the fastest sidelobe decay rate among common windows without requiring the computational cost of Bessel functions (Kaiser) or specialized polynomials (Henderson). + +## Historical Context + +Emanuel Parzen (1929-2016) introduced the window in a 1961 paper on spectral estimation in *Technometrics*, though the underlying function was studied earlier by de la Vallée-Poussin in the context of Fourier series summability. Parzen's contribution was to recognize the window's optimality properties for spectral density estimation: among all non-negative windows with continuous derivatives up to order 2, the Parzen window minimizes the integrated squared bias of the spectral estimate. + +The Parzen window's construction as a convolution of two Bartlett windows gives it a natural interpretation: it is equivalent to computing the SMA of an SMA of half the period, twice. This "double triangular smoothing" produces the piecewise cubic shape without explicit polynomial computation. In the spectral domain, the convolution translates to multiplication: the Parzen frequency response is the square of the Bartlett frequency response, which explains the doubled sidelobe rolloff rate ($-24$ dB/octave vs. $-12$ dB/octave for Bartlett). + +Compared to competing windows, Parzen trades main-lobe width for sidelobe suppression. Its main lobe is wider than Hann or Hamming (meaning more lag in the time domain), but its sidelobes decay faster than any other polynomial-based window. For financial applications where smooth trend extraction matters more than sharp frequency cutoff, this trade-off favors Parzen. + +## Architecture & Physics + +### 1. Piecewise Cubic Weight Function + +The Parzen window is defined in two regions based on the normalized coordinate $|u| = |k - (N-1)/2| / ((N-1)/2)$: + +- **Inner region** ($|u| \leq 0.5$): Cubic spline with positive curvature tapering from the peak. +- **Outer region** ($0.5 < |u| \leq 1.0$): Cubic taper to zero at the window edge. + +The two pieces join with continuous first and second derivatives at $|u| = 0.5$, ensuring no spectral artifacts from weight discontinuities. + +### 2. Weight Normalization + +Weights are normalized to sum to 1.0. Because all Parzen weights are non-negative, the filter output is always a convex combination of input prices (no overshoot possible from negative weights). + +### 3. FIR Convolution + +Standard weighted convolution over the circular buffer. O(N) per bar. The symmetric structure allows paired-element optimization for SIMD. + +## Mathematical Foundation + +For a window of length $N$, with normalized coordinate $u = (k - (N-1)/2) / ((N-1)/2)$, $k = 0, \ldots, N-1$: + +$$ +w(k) = \begin{cases} 1 - 6u^2 + 6|u|^3 & |u| \leq 0.5 \\ 2(1 - |u|)^3 & 0.5 < |u| \leq 1.0 \\ 0 & |u| > 1.0 \end{cases} +$$ + +**Frequency response properties:** + +| Property | Value | +| :--- | :--- | +| Main lobe width ($-3$ dB) | $\approx 2.0/N$ | +| First sidelobe | $-53$ dB | +| Sidelobe rolloff | $-24$ dB/octave | +| All weights non-negative | Yes | + +**Equivalence to double convolution:** + +$$ +w_{\text{Parzen}}[n] = w_{\text{Bartlett}}[n] * w_{\text{Bartlett}}[n] +$$ + +where $*$ denotes discrete convolution and the Bartlett windows are of length $N/2$. + +**Normalized output:** + +$$ +\text{PARZEN}_t = \frac{\sum_{k=0}^{N-1} w[k] \cdot x_{t-k}}{\sum_{k=0}^{N-1} w[k]} +$$ + +**Default parameters:** `period = 14`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +// One-time weight computation +half_N = (period - 1) / 2 +for k = 0 to period-1: + u = (k - half_N) / half_N + abs_u = |u| + if abs_u <= 0.5: + w[k] = 1 - 6*abs_u² + 6*abs_u³ + else if abs_u <= 1.0: + w[k] = 2*(1 - abs_u)³ + else: + w[k] = 0 +normalize(w) + +// Per-bar convolution +buffer.push(price) +if count < period: return price +return Σ buffer[j] * w[j] +``` + +## Resources + +- Parzen, E. (1961). "Mathematical Considerations in the Estimation of Spectra." *Technometrics*, 3(2), 167-190. +- Harris, F.J. (1978). "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform." *Proceedings of the IEEE*, 66(1), 51-83. +- Nuttall, A.H. (1981). "Some Windows with Very Good Sidelobe Behavior." *IEEE Trans. Acoust., Speech, Signal Process.*, 29(1), 84-91. diff --git a/lib/trends_FIR/qrma/Qrma.md b/lib/trends_FIR/qrma/Qrma.md new file mode 100644 index 00000000..1424b2f0 --- /dev/null +++ b/lib/trends_FIR/qrma/Qrma.md @@ -0,0 +1,98 @@ +# QRMA: Quadratic Regression Moving Average + +> "Linear regression assumes the world is a straight line. Quadratic regression admits it might curve. For parabolic price moves, that admission turns out to be worth 40% less endpoint error." + +QRMA fits a second-degree polynomial $y = a + bx + cx^2$ to the most recent $N$ bars via ordinary least squares, then returns the fitted value at the endpoint (newest bar). By capturing curvature that LSMA (degree-1) misses, QRMA provides meaningfully better tracking of accelerating or decelerating price trends. The 3x3 normal-equation system is solved via Cramer's rule in O(1) after an O(N) data accumulation pass, making it computationally efficient and suitable for streaming applications. + +## Historical Context + +Quadratic regression applied to time-series smoothing is a special case of the Savitzky-Golay filter (1964) with polynomial degree 2. Savitzky and Golay showed that polynomial least-squares fitting over a sliding window produces FIR filter coefficients equivalent to convolution, and that these coefficients preserve polynomial trends of degree $\leq d$ while suppressing higher-order components. + +QRMA sits between LSMA (degree-1, captures slope only) and CRMA (degree-3, captures inflection). The degree-2 model adds one parameter (curvature $c$) relative to linear regression, which is sufficient to track parabolic moves, acceleration phases, and the initial curvature of trend reversals. For most financial time series, degree-2 captures the dominant non-linearity without the fitting instability that arises with higher degrees on noisy data. + +The x-indexing convention matters for numerical stability. QRMA uses $x = 0$ for the oldest bar and $x = N-1$ for the newest, evaluating the polynomial at $x = N-1$ (the endpoint). This avoids the large-exponent cancellation errors that arise when evaluating at $x = 0$ with the "newest=0" convention (where the polynomial coefficients must reconstruct the signal from high powers of $N-1$). + +## Architecture & Physics + +### 1. Analytical X-Sums + +The x-index power sums ($\sum x$, $\sum x^2$, $\sum x^3$, $\sum x^4$) are computed from Faulhaber's closed-form formulas, depending only on $N$. These are effectively constants for fixed period. + +### 2. Data-Dependent Y-Sums + +A single O(N) pass over the circular buffer accumulates $\sum y$, $\sum xy$, and $\sum x^2 y$. + +### 3. Cramer's Rule Solution + +The 3x3 normal-equation system is solved via Cramer's rule (determinant ratios), which is numerically stable for well-conditioned systems and avoids the overhead of Gaussian elimination. A singularity guard (determinant $< 10^{-20}$) returns the raw price for degenerate inputs. + +### 4. Endpoint Evaluation + +The fitted polynomial $a + b(N-1) + c(N-1)^2$ is evaluated at the newest bar. + +## Mathematical Foundation + +The quadratic regression minimizes: + +$$ +\min_{a, b, c} \sum_{k=0}^{N-1} \left( y_k - a - bk - ck^2 \right)^2 +$$ + +The normal equations form a 3x3 system: + +$$ +\begin{bmatrix} N & S_1 & S_2 \\ S_1 & S_2 & S_3 \\ S_2 & S_3 & S_4 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \end{bmatrix} = \begin{bmatrix} \sum y \\ \sum ky \\ \sum k^2 y \end{bmatrix} +$$ + +where $S_m = \sum_{k=0}^{N-1} k^m$ has closed forms: + +$$ +S_1 = \frac{N(N-1)}{2}, \quad S_2 = \frac{N(N-1)(2N-1)}{6} +$$ + +$$ +S_3 = \left[\frac{N(N-1)}{2}\right]^2, \quad S_4 = \frac{N(N-1)(2N-1)(3N^2-3N-1)}{30} +$$ + +**Cramer's rule:** With coefficient matrix $\mathbf{D}$ and right-hand side $\mathbf{r}$: + +$$ +a = \frac{\det(\mathbf{D}_a)}{\det(\mathbf{D})}, \quad b = \frac{\det(\mathbf{D}_b)}{\det(\mathbf{D})}, \quad c = \frac{\det(\mathbf{D}_c)}{\det(\mathbf{D})} +$$ + +**Endpoint value:** $\text{QRMA} = a + b(N-1) + c(N-1)^2$ + +**Default parameters:** `period = 14`, `minPeriod = 3` (minimum for degree-2 fit). + +**Pseudo-code (streaming):** + +``` +buffer ← circular_buffer(period) +buffer.push(price) +if count < period: return price + +// Analytical x-sums (constants for fixed N) +S1 = N*(N-1)/2; S2 = N*(N-1)*(2N-1)/6 +S3 = S1²; S4 = N*(N-1)*(2N-1)*(3N²-3N-1)/30 + +// Data sums (O(N) pass) +sy = 0; sxy = 0; sx2y = 0 +for j = 0 to N-1: + val = buffer[j] // oldest to newest + sy += val; sxy += j*val; sx2y += j²*val + +// 3×3 Cramer's rule +det = N*(S2*S4 - S3²) - S1*(S1*S4 - S3*S2) + S2*(S1*S3 - S2²) +if |det| < 1e-20: return price +a = cramer_a(det, sy, sxy, sx2y, ...) +b = cramer_b(det, ...) +c = cramer_c(det, ...) + +return a + b*(N-1) + c*(N-1)² +``` + +## Resources + +- Savitzky, A. & Golay, M.J.E. (1964). "Smoothing and Differentiation of Data by Simplified Least Squares Procedures." *Analytical Chemistry*, 36(8), 1627-1639. +- Schafer, R.W. (2011). "What Is a Savitzky-Golay Filter?" *IEEE Signal Processing Magazine*, 28(4), 111-117. +- Press, W.H. et al. (2007). *Numerical Recipes*, 3rd ed. Cambridge University Press. Section 3.5: Least-Squares Fitting. diff --git a/lib/trends_FIR/rwma/Rwma.md b/lib/trends_FIR/rwma/Rwma.md new file mode 100644 index 00000000..8b0b9fe6 --- /dev/null +++ b/lib/trends_FIR/rwma/Rwma.md @@ -0,0 +1,78 @@ +# RWMA: Range Weighted Moving Average + +> "Most averages weight by position: recent bars matter more. RWMA weights by volatility: volatile bars matter more. The market spoke loudest when the range was widest, so listen to those bars." + +RWMA weights each bar's contribution to the average by its price range (high minus low), giving greater influence to volatile bars and less to narrow-range, indecisive bars. The logic: a bar with a large range represents stronger price discovery and carries more informational content than a low-range doji. This produces a moving average that gravitates toward prices established during high-activity periods, naturally incorporating volatility as a relevance signal without requiring a separate volatility indicator. + +## Historical Context + +Range-weighted averaging is a practical adaptation of the general concept of precision-weighted means from statistics, where observations are weighted by the inverse of their variance (or, equivalently, by their "importance" or precision). In financial applications, bar range serves as a real-time proxy for intra-bar volatility, available without the computational overhead of standard deviation or ATR calculations. + +The concept appears informally in trading literature from the 1990s, often attributed to floor-trader heuristics: "wide-range bars lead price," meaning that the closing prices of high-range bars tend to be more predictive of subsequent direction than those of narrow-range bars. RWMA formalizes this heuristic into a weighted average. + +Unlike position-weighted averages (WMA, EMA) where the weighting scheme is fixed by the period, RWMA's weights are data-adaptive. The weight vector changes every bar based on the range profile of the lookback window. This makes RWMA inherently non-stationary: two windows with identical closing prices but different range profiles produce different RWMA values. The data-adaptive property also means RWMA cannot be expressed as a fixed-coefficient FIR filter, though its computation is structurally similar. + +RWMA requires high and low price data (TBar inputs), making it inapplicable to single-valued series. When all bars have zero range (constant price), the denominator collapses to zero and the filter falls back to the raw source price. + +## Architecture & Physics + +### 1. Weight Computation + +For each bar $i$ in the lookback window: + +$$ +w_i = \max(\text{High}_i - \text{Low}_i, 0) +$$ + +The $\max$ clamp ensures non-negative weights (relevant for synthetic data where high $<$ low might occur due to data errors). + +### 2. Weighted Average + +$$ +\text{RWMA} = \frac{\sum_{i=0}^{N-1} \text{Close}_i \cdot w_i}{\sum_{i=0}^{N-1} w_i} +$$ + +If $\sum w_i = 0$ (all bars have zero range), the output degenerates to the current source price. + +### 3. TBar Requirement + +RWMA consumes TBar data (OHLC), not single-valued TValue. The C# implementation should accept `TBar` inputs and route `High`, `Low`, `Close` appropriately. + +## Mathematical Foundation + +Given a window of $N$ bars with close prices $c_i$, highs $h_i$, and lows $l_i$ (where $i = 0$ is newest): + +$$ +\text{RWMA}_t = \frac{\sum_{i=0}^{N-1} c_{t-i} \cdot (h_{t-i} - l_{t-i})}{\sum_{i=0}^{N-1} (h_{t-i} - l_{t-i})} +$$ + +**Properties:** + +- **Convex combination:** All weights are non-negative, so the output is bounded by $[\min(c_i), \max(c_i)]$ within the window. No overshoot possible. +- **Adaptive lag:** Lag shifts toward the position of the highest-range bars. If the most volatile bar is recent, lag decreases; if it is old, lag increases. +- **Degeneracy:** When all ranges are zero, $\text{RWMA} = c_t$ (current close). + +**Complexity:** O(N) per bar (single pass over the window). + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +sumWV = 0; sumW = 0 +for i = 0 to period-1: + range = max(high[i] - low[i], 0) + sumWV += close[i] * range + sumW += range + +if sumW > 0: + return sumWV / sumW +else: + return close[0] +``` + +## Resources + +- Bollinger, J. (2001). *Bollinger on Bollinger Bands*. McGraw-Hill. (Discusses range-based volatility measures in the context of band-width indicators.) +- Achelis, S.B. (2000). *Technical Analysis from A to Z*, 2nd ed. McGraw-Hill. +- Garman, M.B. & Klass, M.J. (1980). "On the Estimation of Security Price Volatilities from Historical Data." *Journal of Business*, 53(1), 67-78. (Range-based volatility estimation from OHLC data.) diff --git a/lib/trends_FIR/sp15/Sp15.md b/lib/trends_FIR/sp15/Sp15.md new file mode 100644 index 00000000..7cc30f28 --- /dev/null +++ b/lib/trends_FIR/sp15/Sp15.md @@ -0,0 +1,89 @@ +# SP15: Spencer 15-Point Moving Average + +> "John Spencer designed 15 weights that zero out quarterly and quintile seasonality from economic data. Eighty years later, statisticians still reach for them when they need a quick seasonal adjustment that does not require the German engineering of X-13ARIMA." + +SP15 is a fixed-coefficient symmetric FIR filter with 15 weights: $[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]$ divided by 320. The weights were designed by John Spencer to have zero frequency response at periods 4 and 5 (frequencies $2\pi/4$ and $2\pi/5$), making the filter effective at removing quarterly and quintile seasonal components from economic time series. The negative edge weights provide bandpass-like characteristics, and the fixed design requires no parameters beyond the source series. + +## Historical Context + +John Spencer published the 15-point and 21-point weighted moving averages in 1904 for use in actuarial graduation (smoothing mortality tables). The weights were constructed to satisfy two constraints simultaneously: (1) preserve polynomial trends up to degree 3 (cubic), and (2) have zero response at specific seasonal frequencies. The 15-point variant zeros out periods 4 and 5; the 21-point variant zeros out periods 4, 5, and 7. + +Spencer's filters predated Henderson's (1916) by twelve years and were widely used in actuarial science and economic statistics before the X-11 method standardized on Henderson filters. The Spencer 15-point filter was the default seasonal adjustment tool at the U.K. Office for National Statistics until the adoption of X-11 in the 1960s. In modern practice, it remains useful as a quick-and-dirty seasonal smoother when full X-13ARIMA decomposition is overkill. + +The fixed 15-bar length creates a natural centered lag of 7 bars, which is appropriate for quarterly data (4 observations per year, so a 15-point filter spans nearly 4 quarters). For financial time series, the filter useful for removing intra-week (5-bar) and intra-month patterns from daily data. + +## Architecture & Physics + +### 1. Fixed Weight Vector + +The 15 weights are hardcoded constants, symmetric around the center: + +$$ +\mathbf{w} = \frac{1}{320}[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3] +$$ + +No weight computation is needed; the coefficients are compile-time constants. + +### 2. Symmetric Convolution + +The symmetric structure allows folded computation: pair the $i$-th and $(14-i)$-th bars (which share the same weight), sum them, then multiply by the weight once. This halves the multiplication count from 15 to 8. + +### 3. Negative Edge Weights + +Three weights at each edge are negative ($-3, -6, -5$), giving the filter its seasonal-nulling property. The output can exceed the input range when edge bars have extreme values relative to the center. + +### 4. Zero-Parameter Design + +SP15 takes no period parameter. The filter length is always 15, and the weights are always Spencer's original values. This is both a strength (no tuning required) and a limitation (no adaptation to different data characteristics). + +## Mathematical Foundation + +The Spencer 15-point filter output: + +$$ +\text{SP15}_t = \frac{1}{320}\sum_{j=0}^{14} w_j \cdot x_{t-j} +$$ + +Exploiting symmetry ($w_j = w_{14-j}$): + +$$ +\text{SP15}_t = \frac{1}{320}\left[w_7 \cdot x_{t-7} + \sum_{j=0}^{6} w_j \left(x_{t-j} + x_{t-14+j}\right)\right] +$$ + +**Frequency response zeros:** + +$$ +H\left(e^{j2\pi/4}\right) = 0, \quad H\left(e^{j2\pi/5}\right) = 0 +$$ + +These zeros ensure complete suppression of periodicities at 4 and 5 bars. + +**Weight sum:** $-3-6-5+3+21+46+67+74+67+46+21+3-5-6-3 = 320$ + +**Polynomial preservation:** The filter preserves polynomials up to degree 3: + +$$ +\sum_{j=0}^{14} w_j \cdot (j-7)^k = 320 \cdot \delta_{k0}, \quad k = 0, 1, 2, 3 +$$ + +**Default parameters:** None (fixed 15-point filter). + +**Pseudo-code (streaming):** + +``` +// Fixed symmetric weights (compile-time constants) +w = [-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3] + +// Symmetric folded computation +total = w[7] * src[7] +for j = 0 to 6: + total += w[j] * (src[j] + src[14-j]) +return total / 320 +``` + +## Resources + +- Spencer, J. (1904). "On the Graduation of the Rates of Sickness and Mortality." *Journal of the Institute of Actuaries*, 38, 334-343. +- Macaulay, F.R. (1931). *The Smoothing of Time Series.* NBER. Chapter 4: Spencer-Type Formulas. +- Kendall, M.G. & Stuart, A. (1976). *The Advanced Theory of Statistics*, Vol. 3, 3rd ed. Griffin. Section 46.13: Spencer's Formulae. +- Kenny, P.B. & Durbin, J. (1982). "Local Trend Estimation and Seasonal Adjustment of Economic and Social Time Series." *JRSS Series A*, 145(1). diff --git a/lib/trends_FIR/swma/Swma.md b/lib/trends_FIR/swma/Swma.md new file mode 100644 index 00000000..34c7a18d --- /dev/null +++ b/lib/trends_FIR/swma/Swma.md @@ -0,0 +1,88 @@ +# SWMA: Symmetric Weighted Moving Average + +> "Take the SMA of an SMA and you get a triangular filter. It is the simplest possible smoothing kernel that has zero phase distortion and no frequency-domain discontinuities. Sometimes simple is exactly what you need." + +SWMA applies triangular (symmetric) weights that peak at the center of the window and taper linearly to the edges. For period $N$, the weight at position $i$ is $w(i) = (N/2 + 1) - |i - N/2|$, producing a tent-shaped kernel. This is mathematically equivalent to convolving two rectangular windows (SMA of SMA), giving SWMA a frequency response that is the square of the SMA's sinc-like response. The result is smoother than SMA with better sidelobe suppression, at the cost of slightly more lag. + +## Historical Context + +The symmetric (triangular) weighted average is one of the oldest smoothing methods in statistics, predating modern signal processing by centuries. Its equivalence to the double-application of the simple moving average was recognized by Macaulay (1931) in his NBER monograph on time-series smoothing. The TRIMA (Triangular Moving Average) implemented elsewhere in QuanTAlib is the same mathematical operation computed via double SMA composition. + +In PineScript, `ta.swma` refers specifically to the 4-point variant with weights $[1, 2, 2, 1]/6$, which is a special case of the general symmetric weighted average. QuanTAlib's SWMA generalizes this to arbitrary periods. + +The triangular kernel has a natural Bayesian interpretation: if you believe the "true" signal is equally likely to be any value in a window of width $N/2$, and your observation window is also $N/2$, the posterior belief about the signal value is triangular. This makes SWMA the optimal Bayesian filter under uniform prior and uniform observation noise assumptions. + +## Architecture & Physics + +### 1. Weight Computation + +For a window of length $N$ with half-width $h = (N-1)/2$: + +$$ +w(i) = h + 1 - |i - h|, \quad i = 0, 1, \ldots, N-1 +$$ + +Weights form a triangle peaking at the center. For even $N$, the peak is a plateau of two equal values. + +### 2. Normalized Weighted Sum + +$$ +\text{SWMA} = \frac{\sum_{i=0}^{N-1} w(i) \cdot x_{t-i}}{\sum_{i=0}^{N-1} w(i)} +$$ + +The weight sum equals $(h+1)^2$ for odd $N$ and $h(h+2)+1$ for even $N$. + +### 3. Equivalence to Double SMA + +SWMA(N) produces the same output as SMA(M) applied to SMA(M) where $M = \lceil N/2 \rceil$. This means the streaming implementation can compose two SMA instances for O(1) updates, rather than O(N) convolution. + +## Mathematical Foundation + +The triangular window for length $N$, with $h = (N-1)/2$: + +$$ +w[i] = h + 1 - |i - h|, \quad i = 0, \ldots, N-1 +$$ + +**Frequency response:** + +$$ +H_{\text{SWMA}}(f) = H_{\text{SMA}}^2(f) = \left[\frac{\sin(\pi f M)}{\pi f M}\right]^2 +$$ + +where $M = \lceil N/2 \rceil$. The squared sinc provides: + +| Property | SMA | SWMA | +| :--- | :---: | :---: | +| First zero | $1/N$ | $2/N$ | +| First sidelobe | $-13$ dB | $-26$ dB | +| Rolloff rate | $-6$ dB/octave | $-12$ dB/octave | +| Passband ripple | Moderate | Low | + +**Weight sum (closed form):** + +For odd $N = 2m+1$: $\sum w = (m+1)^2$ + +For even $N = 2m$: $\sum w = m(m+1)$ + +**PineScript special case:** `ta.swma` uses $N = 4$, $h = 1.5$, weights $= [1, 2, 2, 1]$, $\sum w = 6$. + +**Default parameters:** `period = 4`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +half = (period - 1) / 2.0 +sumWV = 0; sumW = 0 +for i = 0 to period-1: + w = half + 1 - |i - half| + sumWV += src[i] * w + sumW += w +return sumWV / sumW +``` + +## Resources + +- Macaulay, F.R. (1931). *The Smoothing of Time Series.* National Bureau of Economic Research. Chapter 3: Moving Averages and Their Properties. +- Oppenheim, A.V. & Schafer, R.W. (2009). *Discrete-Time Signal Processing*, 3rd ed. Prentice Hall. Section 5.6: The Bartlett (Triangular) Window. +- Murphy, J.J. (1999). *Technical Analysis of the Financial Markets*. New York Institute of Finance. Chapter 9: Moving Averages. diff --git a/lib/trends_FIR/tukey_w/Tukey_w.md b/lib/trends_FIR/tukey_w/Tukey_w.md new file mode 100644 index 00000000..38a2380c --- /dev/null +++ b/lib/trends_FIR/tukey_w/Tukey_w.md @@ -0,0 +1,96 @@ +# TUKEY_W: Tukey (Tapered Cosine) Window Moving Average + +> "John Tukey designed a window with a knob that goes from 'do nothing' to 'full Hann' in one parameter. Set alpha to 0.5 and you get the pragmatist's compromise: flat where it matters, tapered where it would otherwise ring." + +TUKEY_W applies the Tukey (tapered cosine) window as FIR filter weights, offering a single parameter $\alpha$ that controls the fraction of the window that is cosine-tapered. At $\alpha = 0$, the window is rectangular (SMA). At $\alpha = 1$, it becomes the Hann window. The default $\alpha = 0.5$ tapers 25% at each edge while keeping the central 50% flat at unity, combining the passband efficiency of the rectangular window with the sidelobe suppression of cosine tapering. This makes Tukey the default "when in doubt" window in spectral analysis, and by extension, a sensible default for window-based moving averages. + +## Historical Context + +John Wilder Tukey (1915-2000) introduced the tapered cosine window as part of his extensive work on spectral analysis, culminating in the landmark *Power Spectral Analysis and Its Applications* textbook with Blackman (1958). Tukey recognized that the rectangular window's sharp edges cause spectral leakage (Gibbs phenomenon), while fully tapered windows like Hann sacrifice too much effective window length. The tapered cosine compromise preserves most of the rectangular window's frequency resolution (through the flat center) while controlling leakage through the cosine-tapered edges. + +The Tukey window is also known as the "cosine-tapered window" or "split-cosine-bell window" in the spectral analysis literature. The parameter $\alpha$ is sometimes called the "taper ratio" or "rolloff fraction." In the acoustics and seismology communities, it is standard practice to start with $\alpha = 0.5$ and adjust based on the leakage characteristics of the specific data. + +For financial applications, the Tukey window's parametric nature offers a practical advantage over fixed windows: a trader can adjust $\alpha$ to control how much edge attenuation is applied. Low $\alpha$ (near 0) prioritizes responsiveness (less lag), while high $\alpha$ (near 1) prioritizes smoothness (less noise). + +## Architecture & Physics + +### 1. Piecewise Weight Function + +The Tukey window divides the $N$-point window into three regions: + +- **Left taper** ($0 \leq n < \alpha(N-1)/2$): Raised-cosine ramp from 0 to 1. +- **Flat center** ($\alpha(N-1)/2 \leq n \leq (N-1)(1-\alpha/2)$): Constant weight of 1. +- **Right taper** ($(N-1)(1-\alpha/2) < n \leq N-1$): Raised-cosine ramp from 1 to 0. + +### 2. Normalization + +Weights are normalized by their sum, which depends on $\alpha$: + +$$ +\sum w = N - \alpha(N-1)/2 \cdot (1-2/\pi) +$$ + +(approximately, for large $N$). + +### 3. FIR Convolution + +Standard weighted convolution. O(N) per bar. The flat center section allows paired SIMD processing of constant-weight elements. + +## Mathematical Foundation + +For a window of length $N$, sample index $n \in [0, N-1]$, and taper fraction $\alpha \in [0, 1]$: + +$$ +w[n] = \begin{cases} \frac{1}{2}\left(1 - \cos\!\left(\frac{2\pi n}{\alpha(N-1)}\right)\right) & 0 \leq n < \frac{\alpha(N-1)}{2} \\ 1 & \frac{\alpha(N-1)}{2} \leq n \leq (N-1)\left(1 - \frac{\alpha}{2}\right) \\ \frac{1}{2}\left(1 - \cos\!\left(\frac{2\pi(N-1-n)}{\alpha(N-1)}\right)\right) & (N-1)\left(1 - \frac{\alpha}{2}\right) < n \leq N-1 \end{cases} +$$ + +**Special cases:** + +| $\alpha$ | Window | Properties | +| :---: | :--- | :--- | +| 0 | Rectangular (SMA) | Max resolution, worst leakage | +| 0.5 | Half-tapered (default) | Good compromise | +| 1.0 | Hann | Best leakage suppression, widest main lobe | + +**Frequency response properties (approximate for $N \gg 1$):** + +| $\alpha$ | Main lobe width | First sidelobe (dB) | +| :---: | :---: | :---: | +| 0 | $2/N$ | $-13$ | +| 0.25 | $2.2/N$ | $-19$ | +| 0.5 | $2.5/N$ | $-26$ | +| 0.75 | $2.8/N$ | $-29$ | +| 1.0 | $3.2/N$ | $-32$ | + +**Normalized output:** + +$$ +\text{TUKEY\_W}_t = \frac{\sum_{n=0}^{N-1} w[n] \cdot x_{t-n}}{\sum_{n=0}^{N-1} w[n]} +$$ + +**Default parameters:** `period = 20`, `alpha = 0.5`, `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +N = period - 1 +aN = alpha * N +sumWV = 0; sumW = 0 + +for i = 0 to N: + w = 1.0 + if aN > 0: + if i < aN/2: + w = 0.5 * (1 - cos(2π*i / aN)) + else if i > N - aN/2: + w = 0.5 * (1 - cos(2π*(N-i) / aN)) + sumWV += src[i] * w + sumW += w +return sumWV / sumW +``` + +## Resources + +- Tukey, J.W. (1967). "An Introduction to the Calculations of Numerical Spectrum Analysis." In *Spectral Analysis of Time Series*, ed. B. Harris. Wiley. pp. 25-46. +- Blackman, R.B. & Tukey, J.W. (1958). *The Measurement of Power Spectra from the Point of View of Communications Engineering*. Dover. +- Harris, F.J. (1978). "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform." *Proceedings of the IEEE*, 66(1), 51-83. diff --git a/lib/trends_IIR/adxvma/Adxvma.md b/lib/trends_IIR/adxvma/Adxvma.md new file mode 100644 index 00000000..7f72287e --- /dev/null +++ b/lib/trends_IIR/adxvma/Adxvma.md @@ -0,0 +1,100 @@ +# ADXVMA: ADX Variable Moving Average + +> "Use ADX to measure trend strength, then feed that measurement back as the smoothing constant. When the trend is strong, track fast. When it is not, stand still. The market tells you how much to listen." + +ADXVMA is an adaptive IIR filter that uses the Average Directional Index (ADX) as its smoothing constant. When ADX is high (strong trend), the smoothing factor approaches 1.0 and the filter tracks price aggressively. When ADX is low (range-bound), the smoothing factor approaches 0.0 and the filter barely moves. This creates a moving average that automatically switches between responsive trend-following and noise-immune range-holding without external regime detection. + +## Historical Context + +ADXVMA combines two well-established concepts: Welles Wilder's ADX (1978) as a trend-strength measure, and the adaptive moving average framework pioneered by Perry Kaufman's AMA (1995). While Kaufman used an efficiency ratio (net displacement / total path) to adapt smoothing, ADXVMA substitutes ADX, which measures trend directionality through the divergence of positive and negative directional movement indicators. + +The ADX-based adaptation has a practical advantage over efficiency-ratio methods: ADX responds to the consistency of directional movement, not just net displacement. A market that trends steadily but slowly produces high ADX but low efficiency ratio. Conversely, a market with a sharp one-bar spike produces high efficiency ratio but low ADX (because the spike is not sustained). For trend-following applications, the ADX criterion better matches the trading requirement of sustained directional moves. + +The implementation uses Wilder's RMA (Recursive Moving Average, equivalent to EMA with $\alpha = 1/N$) for all internal smoothing components (TR, +DM, -DM, DX), with warmup compensation to produce valid output from the first bar. The warmup compensator $c = 1/(1-\beta^n)$ corrects the exponential bias during the initial transient, eliminating the need for a multi-bar initialization period. + +## Architecture & Physics + +### 1. True Range and Directional Movement + +Per-bar computation of True Range (TR), Plus Directional Movement (+DM), and Minus Directional Movement (-DM) using Wilder's definitions. + +### 2. Wilder's RMA with Warmup Compensation + +Each of TR, +DM, -DM, and DX is smoothed using RMA ($\alpha = 1/N$). The warmup compensator tracks the decay factor $e = \beta^n$ and divides the raw exponential accumulation by $(1-e)$, providing unbiased estimates from bar 1. + +### 3. ADX Computation + +$$ +\text{ADX} = \text{RMA}\left(\frac{|+DI - -DI|}{+DI + -DI} \times 100\right) +$$ + +### 4. Adaptive Smoothing + +The ADX value is clamped to $[0, 100]$ and divided by 100 to produce a smoothing constant $sc \in [0, 1]$: + +$$ +\text{ADXVMA}_t = \text{ADXVMA}_{t-1} + sc \times (\text{source}_t - \text{ADXVMA}_{t-1}) +$$ + +## Mathematical Foundation + +**Directional indicators:** + +$$ ++DI = \frac{100 \cdot \text{RMA}(+DM, N)}{\text{RMA}(TR, N)}, \quad -DI = \frac{100 \cdot \text{RMA}(-DM, N)}{\text{RMA}(TR, N)} +$$ + +**Directional Index:** + +$$ +DX = \frac{100 \cdot |+DI - -DI|}{+DI + -DI} +$$ + +**ADX:** + +$$ +\text{ADX} = \text{RMA}(DX, N) +$$ + +**Adaptive output:** + +$$ +sc = \text{clamp}\left(\frac{\text{ADX}}{100}, 0, 1\right) +$$ + +$$ +\text{ADXVMA}_t = \text{ADXVMA}_{t-1} + sc \cdot (x_t - \text{ADXVMA}_{t-1}) +$$ + +**Effective time constant:** When ADX = 50, $sc = 0.5$, equivalent to an EMA with period 3. When ADX = 20, $sc = 0.2$, equivalent to period 9. When ADX = 80, $sc = 0.8$, equivalent to period 1.5. + +**Default parameters:** `period = 14`, `minPeriod = 1`. Requires OHLC data for TR/DM computation. + +**Pseudo-code (streaming):** + +``` +alpha = 1/period; beta = 1 - alpha + +// RMA with warmup compensation for TR, +DM, -DM, DX +raw_tr = raw_tr * beta + tr * alpha +e_tr *= beta +comp_tr = raw_tr / (1 - e_tr) // warmup-compensated + +// ... same for +DM, -DM ... + ++DI = 100 * comp_pdm / comp_tr +-DI = 100 * comp_ndm / comp_tr +DX = 100 * |+DI - -DI| / (+DI + -DI) + +raw_dx = raw_dx * beta + DX * alpha +ADX = raw_dx / (1 - e_dx) + +sc = clamp(ADX / 100, 0, 1) +result = result + sc * (source - result) +``` + +## Resources + +- Wilder, J.W. (1978). *New Concepts in Technical Trading Systems*. Trend Research. Chapter 6: Directional Movement. +- Kaufman, P.J. (1995). *Smarter Trading*. McGraw-Hill. Chapter 7: Adaptive Techniques. +- Chande, T.S. (2001). *Beyond Technical Analysis*, 2nd ed. John Wiley & Sons. diff --git a/lib/trends_IIR/ahrens/Ahrens.md b/lib/trends_IIR/ahrens/Ahrens.md new file mode 100644 index 00000000..3f565bb9 --- /dev/null +++ b/lib/trends_IIR/ahrens/Ahrens.md @@ -0,0 +1,80 @@ +# AHRENS: Ahrens Moving Average + +> "Richard Ahrens looked at the EMA and thought: what if the correction term accounted for where the average was, not just where it is? The result is a self-referencing IIR filter that uses its own history as a stabilizer." + +AHRENS is a recursive IIR filter that adjusts toward the source price minus the midpoint of its current and lagged (by one period) states. The formula $\text{AHRENS}_t = \text{AHRENS}_{t-1} + (\text{source} - \frac{\text{AHRENS}_{t-1} + \text{AHRENS}_{t-N}}{2}) / N$ creates a self-dampening feedback loop: the correction term shrinks as the current and lagged states converge, producing a smoother approach to equilibrium than a standard EMA with less tendency to overshoot on reversals. + +## Historical Context + +Richard D. Ahrens published "Build A Better Moving Average" in *Stocks & Commodities* magazine (Volume 31, Issue 11, October 2013). The article proposed a modification to the standard recursive moving average that incorporates a lagged copy of the average itself, creating a second-order feedback structure. + +The key insight is the midpoint correction: instead of pulling toward the source price directly (as EMA does), Ahrens pulls toward the source minus the midpoint of the current and lagged average. This means the correction is large when the average is changing rapidly (current and lagged states diverge) and small when it is stable (current and lagged states converge). The effect is automatic damping of oscillatory behavior without sacrificing trend-tracking ability. + +The lagged state introduces a memory requirement: a circular buffer of $N$ past AHRENS values is needed to retrieve the value from $N$ bars ago. This makes AHRENS O(1) per bar in computation but O(N) in memory, comparable to an SMA but with IIR-like smoothing characteristics. + +## Architecture & Physics + +### 1. Circular Buffer for Lagged State + +A ring buffer of size $N$ stores the most recent $N$ AHRENS output values. The lagged value $\text{AHRENS}_{t-N}$ is retrieved from the buffer before it is overwritten with the current output. + +### 2. Midpoint Correction + +The correction term is: + +$$ +\Delta = \frac{\text{source} - \frac{\text{AHRENS}_{t-1} + \text{AHRENS}_{t-N}}{2}}{N} +$$ + +This blends current and historical average states, creating a damped response. + +### 3. Recursive Update + +$$ +\text{AHRENS}_t = \text{AHRENS}_{t-1} + \Delta +$$ + +The update is O(1) per bar after buffer retrieval. + +## Mathematical Foundation + +The Ahrens recursive formula: + +$$ +\text{AHRENS}_t = \text{AHRENS}_{t-1} + \frac{x_t - \frac{1}{2}\left(\text{AHRENS}_{t-1} + \text{AHRENS}_{t-N}\right)}{N} +$$ + +Rearranging: + +$$ +\text{AHRENS}_t = \text{AHRENS}_{t-1} + \frac{x_t}{N} - \frac{\text{AHRENS}_{t-1}}{2N} - \frac{\text{AHRENS}_{t-N}}{2N} +$$ + +$$ +\text{AHRENS}_t = \left(1 - \frac{1}{2N}\right)\text{AHRENS}_{t-1} + \frac{1}{N}x_t - \frac{1}{2N}\text{AHRENS}_{t-N} +$$ + +**Transfer function analysis:** This is an ARMA(N,0) filter with two autoregressive taps: one at lag 1 with coefficient $(1 - 1/2N)$ and one at lag $N$ with coefficient $-1/2N$. The lag-$N$ tap creates a notch in the frequency response near $f = 1/N$, providing additional suppression of periodic noise at the averaging period. + +**Stability:** For $N \geq 1$, the sum of absolute autoregressive coefficients is $|1-1/2N| + |1/2N| = 1$, which is on the stability boundary. The filter is marginally stable and does not diverge, but convergence is slower than a standard EMA. + +**Default parameters:** `period = 9`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +buffer ← circular_buffer(period) // stores past AHRENS values +prev = nz(result, source) +lagged = nz(buffer[head], source) // AHRENS from N bars ago + +midpoint = (prev + lagged) / 2 +result = prev + (source - midpoint) / period + +buffer[head] = result +head = (head + 1) % period +``` + +## Resources + +- Ahrens, R.D. (2013). "Build A Better Moving Average." *Technical Analysis of Stocks & Commodities*, 31(11). +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 4: Finite and Infinite Impulse Response Filters. diff --git a/lib/trends_IIR/gdema/Gdema.md b/lib/trends_IIR/gdema/Gdema.md new file mode 100644 index 00000000..9ba7485d --- /dev/null +++ b/lib/trends_IIR/gdema/Gdema.md @@ -0,0 +1,90 @@ +# GDEMA: Generalized Double Exponential Moving Average + +> "Patrick Mulloy created DEMA to cancel first-order lag. GDEMA adds a volume knob: turn it past 1 and you cancel more lag than Mulloy thought possible. Turn it to 0 and you are back to a plain EMA. The generalization is the point." + +GDEMA extends the standard DEMA (Double Exponential Moving Average) with a tunable gain factor $v$ that controls the aggressiveness of lag compensation. The formula $\text{GDEMA} = (1+v) \cdot \text{EMA}_1 - v \cdot \text{EMA}_2$ reduces to plain EMA when $v=0$, standard DEMA when $v=1$, and progressively more aggressive lag removal for $v>1$. This parametric flexibility allows traders to dial in the exact smoothness-responsiveness trade-off for their application, rather than being locked into DEMA's fixed 2:1 ratio. + +## Historical Context + +Patrick G. Mulloy published DEMA in "Smoothing Data with Faster Moving Averages" (*Technical Analysis of Stocks & Commodities*, February 1994). The original DEMA uses the fixed formula $2 \cdot \text{EMA} - \text{EMA}(\text{EMA})$, which cancels the first-order lag of the EMA by subtracting the double-smoothed version. + +The generalization to an arbitrary volume factor $v$ is a natural extension that was explored by several authors in the late 1990s. Tim Tillson's T3 indicator (1998) uses a similar parameterized approach with six cascaded EMAs and a volume factor. GDEMA is the simplest member of this family: two cascaded EMAs combined with a single gain parameter. + +The mathematical basis is the z-transform lag cancellation technique: EMA has a group delay of approximately $(N-1)/2$ samples. EMA(EMA) has approximately double that delay. The linear combination $(1+v) \cdot \text{EMA} - v \cdot \text{EMA}(\text{EMA})$ cancels $v/(v+1)$ of the total lag. At $v=1$ (DEMA), half the lag is cancelled. At $v=2$, two-thirds is cancelled, but overshoot increases proportionally. + +## Architecture & Physics + +### 1. Dual Cascaded EMAs + +Two EMA stages share the same period $N$ and smoothing constant $\alpha = 2/(N+1)$: +- **EMA1:** Standard EMA of the source. +- **EMA2:** EMA of EMA1 (double-smoothed). + +### 2. Warmup Compensation + +Both EMAs use the exponential warmup compensator $c = 1/(1-\beta^n)$ to produce valid output from bar 1, eliminating the cold-start bias. + +### 3. Parameterized Combination + +$$ +\text{GDEMA} = (1+v) \cdot \text{EMA}_1 - v \cdot \text{EMA}_2 +$$ + +## Mathematical Foundation + +Given smoothing constant $\alpha = 2/(N+1)$, decay $\beta = 1-\alpha$: + +$$ +\text{EMA}_1[t] = \alpha \cdot x_t + \beta \cdot \text{EMA}_1[t-1] +$$ + +$$ +\text{EMA}_2[t] = \alpha \cdot \text{EMA}_1[t] + \beta \cdot \text{EMA}_2[t-1] +$$ + +$$ +\text{GDEMA}[t] = (1+v) \cdot \text{EMA}_1[t] - v \cdot \text{EMA}_2[t] +$$ + +**Z-domain transfer function:** + +$$ +H(z) = (1+v) \cdot \frac{\alpha}{1-\beta z^{-1}} - v \cdot \left(\frac{\alpha}{1-\beta z^{-1}}\right)^2 +$$ + +**Lag characteristics:** + +| $v$ | Equivalent | Lag reduction | Overshoot risk | +| :---: | :--- | :---: | :---: | +| 0 | EMA | 0% | None | +| 0.5 | Mild DEMA | 33% | Low | +| 1.0 | Standard DEMA | 50% | Moderate | +| 1.5 | Aggressive | 60% | High | +| 2.0 | Very aggressive | 67% | Very high | + +**Default parameters:** `period = 10`, `vfactor = 1.0`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +alpha = 2 / (period + 1); beta = 1 - alpha + +// EMA1 with warmup +ema1_raw = alpha * (source - ema1_raw) + ema1_raw +e *= beta +comp = 1 / (1 - e) +ema1 = ema1_raw * comp + +// EMA2 with warmup (of compensated EMA1) +ema2_raw = alpha * (ema1 - ema2_raw) + ema2_raw +ema2 = ema2_raw * comp + +// Generalized combination +return (1 + v) * ema1 - v * ema2 +``` + +## Resources + +- Mulloy, P.G. (1994). "Smoothing Data with Faster Moving Averages." *Technical Analysis of Stocks & Commodities*, 12(1), 11-19. +- Tillson, T. (1998). "Smoothing Techniques for More Accurate Signals." *Technical Analysis of Stocks & Commodities*, 16(1). +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 3: Smoothing Filters. diff --git a/lib/trends_IIR/hw/Hw.md b/lib/trends_IIR/hw/Hw.md new file mode 100644 index 00000000..41a36b32 --- /dev/null +++ b/lib/trends_IIR/hw/Hw.md @@ -0,0 +1,112 @@ +# HW: Holt-Winters Triple Exponential Smoothing + +> "Charles Holt tracked level and slope. Peter Winters added seasonality. This implementation drops seasonality and adds acceleration, the second derivative that tells you when the trend is speeding up or slowing down. Three state variables, three smoothing constants, one second-order Taylor expansion." + +HW implements Holt-Winters triple exponential smoothing with level (F), velocity (V), and acceleration (A) components. Instead of the seasonal component from classical Holt-Winters, this variant tracks the second derivative of the time series, enabling it to anticipate curvature in price trends. The output is a second-order Taylor expansion forecast: $F + V + \frac{1}{2}A$, providing smooth trend tracking that naturally leads price during acceleration phases and dampens during deceleration. + +## Historical Context + +Charles C. Holt developed double exponential smoothing in 1957 (published in 2004 after a 47-year delay), adding a slope component to simple exponential smoothing. Peter R. Winters extended this to triple smoothing in 1960, adding a seasonal component for periodic data. + +The acceleration variant used here replaces the seasonal component with a second-order derivative tracker. This approach is common in control theory and tracking filters (e.g., the alpha-beta-gamma filter used in radar tracking), where the goal is to follow a target whose acceleration changes over time. In financial applications, acceleration corresponds to the rate of change of momentum, a signal that often leads price reversals. + +The three smoothing constants ($\alpha$, $\beta$, $\gamma$) control the responsiveness of level, velocity, and acceleration respectively. When set to auto-derive from the period ($\alpha = 2/(N+1)$, $\beta = \gamma = 1/N$), the filter provides balanced tracking. Manual overrides allow fine-tuning for specific market regimes. + +## Architecture & Physics + +### 1. Three-State IIR System + +The filter maintains three state variables updated sequentially: + +- **F (Level):** Exponentially smoothed estimate of the current value. +- **V (Velocity):** Exponentially smoothed estimate of the first derivative. +- **A (Acceleration):** Exponentially smoothed estimate of the second derivative. + +### 2. Update Equations + +Each state depends on the previous values of all three states, creating a coupled IIR system: + +$$ +F_t = \alpha \cdot x_t + (1-\alpha)(F_{t-1} + V_{t-1} + \tfrac{1}{2}A_{t-1}) +$$ + +$$ +V_t = \beta(F_t - F_{t-1}) + (1-\beta)(V_{t-1} + A_{t-1}) +$$ + +$$ +A_t = \gamma(V_t - V_{t-1}) + (1-\gamma)A_{t-1} +$$ + +### 3. Taylor Forecast Output + +$$ +\text{HW}_t = F_t + V_t + \tfrac{1}{2}A_t +$$ + +## Mathematical Foundation + +**State-space formulation:** + +$$ +\mathbf{s}_t = \begin{bmatrix} F_t \\ V_t \\ A_t \end{bmatrix} +$$ + +The update equations form the state transition: + +$$ +F_t = \alpha \cdot x_t + (1-\alpha)\left(F_{t-1} + V_{t-1} + \tfrac{1}{2}A_{t-1}\right) +$$ + +$$ +V_t = \beta\left(F_t - F_{t-1}\right) + (1-\beta)\left(V_{t-1} + A_{t-1}\right) +$$ + +$$ +A_t = \gamma\left(V_t - V_{t-1}\right) + (1-\gamma) A_{t-1} +$$ + +**Output (second-order Taylor expansion):** + +$$ +\hat{x}_{t+1} = F_t + V_t + \tfrac{1}{2}A_t +$$ + +**Smoothing constant relationships:** + +| Parameter | Auto-value | Controls | +| :---: | :--- | :--- | +| $\alpha$ | $2/(N+1)$ | Level responsiveness | +| $\beta$ | $1/N$ | Velocity responsiveness | +| $\gamma$ | $1/N$ | Acceleration responsiveness | + +**Stability conditions:** All three smoothing constants must be in $(0, 1]$. The system is stable when the eigenvalues of the state transition matrix lie within the unit circle, which is guaranteed for standard parameter ranges. + +**Default parameters:** `period = 10`, `alpha = 0` (auto), `beta = 0` (auto), `gamma = 0` (auto), `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +alpha = (na > 0) ? na : 2/(period+1) +beta = (nb > 0) ? nb : 1/period +gamma = (ng > 0) ? ng : 1/period + +if first_bar: + F = source; V = 0; A = 0 + return source + +forecast = F + V + 0.5*A +F_new = alpha * source + (1-alpha) * forecast +V_new = beta * (F_new - F) + (1-beta) * (V + A) +A_new = gamma * (V_new - V) + (1-gamma) * A + +F = F_new; V = V_new; A = A_new +return F + V + 0.5*A +``` + +## Resources + +- Holt, C.C. (1957/2004). "Forecasting Seasonals and Trends by Exponentially Weighted Moving Averages." *International Journal of Forecasting*, 20(1), 5-10. +- Winters, P.R. (1960). "Forecasting Sales by Exponentially Weighted Moving Averages." *Management Science*, 6(3), 324-342. +- Brown, R.G. (1963). *Smoothing, Forecasting and Prediction of Discrete Time Series*. Prentice-Hall. Chapter 10: Higher-Order Smoothing. +- Benedict, T.R. & Bordner, G.W. (1962). "Synthesis of an Optimal Set of Radar Track-While-Scan Smoothing Equations." *IRE Trans. Automatic Control*, 7(4), 27-32. diff --git a/lib/trends_IIR/lema/Lema.md b/lib/trends_IIR/lema/Lema.md new file mode 100644 index 00000000..b960adb4 --- /dev/null +++ b/lib/trends_IIR/lema/Lema.md @@ -0,0 +1,103 @@ +# LEMA: Leader Exponential Moving Average + +> "George Siligardos asked a simple question: what if you smoothed the EMA's own error and added it back? The answer is a moving average that leads price changes instead of lagging behind them. The error becomes the signal." + +LEMA (Leader EMA) adds a smoothed error correction to the standard EMA, creating a moving average that anticipates price movement. The formula $\text{LEMA} = \text{EMA}(x, N) + \text{EMA}(x - \text{EMA}(x, N), N)$ decomposes price into a smooth component (EMA) and an error component (residual), then re-smooths the error and adds it back. The re-smoothed error represents the systematic part of the EMA's tracking deficit, and adding it back shifts the output toward where the next price is likely to be. + +## Historical Context + +George E. Siligardos published "Leader of the MACD" in *Technical Analysis of Stocks & Commodities* (Volume 26, Issue 7, July 2008). The article introduced LEMA as part of a broader MACD improvement, but the Leader EMA component proved useful as a standalone indicator. + +The mathematical basis is straightforward: the EMA error $e_t = x_t - \text{EMA}_t$ is non-random during trends. When price is rising, the error is consistently positive (EMA lags below price). By smoothing this error and adding it to the EMA, the Leader compensates for the systematic lag component while filtering out the random noise component. The result is a moving average with approximately half the group delay of a standard EMA. + +LEMA is structurally similar to DEMA ($2 \cdot \text{EMA} - \text{EMA}(\text{EMA})$), but the computational pathway differs: LEMA smooths the error signal explicitly, while DEMA derives the same correction algebraically. For identical periods, LEMA and DEMA produce similar (but not identical) outputs because the warmup compensation interacts differently with the two formulations. + +## Architecture & Physics + +### 1. Primary EMA + +Standard EMA of the source with warmup compensation: + +$$ +\text{EMA}_1 = \alpha \cdot x + (1-\alpha) \cdot \text{EMA}_1 +$$ + +### 2. Error Computation + +$$ +e_t = x_t - \text{EMA}_1[t] +$$ + +The error captures the tracking deficit: positive during uptrends, negative during downtrends, zero-mean during consolidation. + +### 3. Error EMA + +A second EMA smooths the error series, extracting the systematic (trend-related) component: + +$$ +\text{EMA}_2 = \alpha \cdot e_t + (1-\alpha) \cdot \text{EMA}_2 +$$ + +### 4. Leader Output + +$$ +\text{LEMA}_t = \text{EMA}_1[t] + \text{EMA}_2[t] +$$ + +Both EMAs use warmup compensation for valid output from bar 1. + +## Mathematical Foundation + +With $\alpha = 2/(N+1)$ and $\beta = 1-\alpha$: + +$$ +\text{EMA}_1[t] = \alpha \cdot x_t + \beta \cdot \text{EMA}_1[t-1] +$$ + +$$ +e_t = x_t - \text{EMA}_1[t] +$$ + +$$ +\text{EMA}_2[t] = \alpha \cdot e_t + \beta \cdot \text{EMA}_2[t-1] +$$ + +$$ +\text{LEMA}[t] = \text{EMA}_1[t] + \text{EMA}_2[t] +$$ + +**Expanding:** Since $e_t = x_t - \text{EMA}_1[t]$: + +$$ +\text{LEMA} = \text{EMA}_1 + \text{EMA}(x - \text{EMA}_1) = \text{EMA}_1 + \text{EMA}(x) - \text{EMA}(\text{EMA}_1) +$$ + +This shows LEMA is equivalent to $\text{EMA}_1 + \text{EMA}_1 - \text{EMA}_2 = 2\text{EMA}_1 - \text{EMA}_2$ in steady state, which is the DEMA formula. The distinction lies in the transient behavior during warmup. + +**Group delay:** Approximately $\frac{N-1}{4}$ samples (half of EMA's $\frac{N-1}{2}$). + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +alpha = 2/(period+1); beta = 1-alpha + +// EMA1 with warmup +ema1 = alpha*(price - ema1) + ema1 +e1 *= beta; comp_ema1 = ema1 / (1-e1) + +// Error +error = price - comp_ema1 + +// EMA2 (of error) with warmup +ema2 = alpha*(error - ema2) + ema2 +e2 *= beta; comp_ema2 = ema2 / (1-e2) + +return comp_ema1 + comp_ema2 +``` + +## Resources + +- Siligardos, G.E. (2008). "Leader of the MACD." *Technical Analysis of Stocks & Commodities*, 26(7), 30-37. +- Mulloy, P.G. (1994). "Smoothing Data with Faster Moving Averages." *Technical Analysis of Stocks & Commodities*, 12(1). (DEMA, the algebraic equivalent.) diff --git a/lib/trends_IIR/ltma/Ltma.md b/lib/trends_IIR/ltma/Ltma.md new file mode 100644 index 00000000..610ec80b --- /dev/null +++ b/lib/trends_IIR/ltma/Ltma.md @@ -0,0 +1,102 @@ +# LTMA: Linear Trend Moving Average + +> "Estimate the level. Estimate the slope. Project forward. It is the same trick radar operators use to track aircraft, applied to price data. LTMA extrapolates the EMA's implicit linear trend by a full period into the future." + +LTMA uses dual cascaded EMAs to estimate both the level and the instantaneous slope of the price series, then extrapolates the linear trend forward by the full period length. Unlike DEMA (which cancels first-order lag algebraically), LTMA explicitly estimates the slope from the EMA difference and projects it: $\text{LTMA} = \text{EMA}_1 + \text{slope} \times N$, where $\text{slope} = \text{EMA}_1 - \text{EMA}_2$. This produces a predictive moving average with zero steady-state error on linear trends, at the cost of significant overshoot on reversals. + +## Historical Context + +Linear trend extrapolation from dual exponential smoothing is a core technique in time-series forecasting, originating with Brown's linear exponential smoothing (1963) and Holt's two-parameter method (1957). The application to technical analysis leverages the same principle: if you can estimate both where price is and how fast it is moving, you can project where it will be. + +LTMA differs from Holt's method in that both EMAs share the same smoothing constant $\alpha = 2/(N+1)$, simplifying the parameter space to a single period. The slope estimate $\text{EMA}_1 - \text{EMA}_2$ approximates the first derivative of the exponentially smoothed series, and projecting by $N$ bars creates an aggressive lead that compensates for the EMA's inherent lag. + +The projection distance of $N$ bars (the full period) makes LTMA more aggressive than DEMA or TEMA. While DEMA effectively projects by approximately $N/2$ bars via its $2 \cdot \text{EMA}_1 - \text{EMA}_2$ formula, LTMA's full-period projection creates a stronger lead that can anticipate trend continuation but overshoots badly on sharp reversals. + +## Architecture & Physics + +### 1. Dual Cascaded EMAs + +Two EMAs with shared $\alpha = 2/(N+1)$: + +- **EMA1:** Standard EMA of source. +- **EMA2:** EMA of EMA1. + +### 2. Slope Estimation + +$$ +\text{slope} = \text{EMA}_1 - \text{EMA}_2 +$$ + +The difference between single and double-smoothed EMAs approximates the first derivative scaled by a factor related to $\alpha$. + +### 3. Linear Extrapolation + +$$ +\text{LTMA} = \text{EMA}_1 + \text{slope} \times N +$$ + +### 4. Warmup Compensation + +Both EMAs use the exponential warmup compensator for valid output from bar 1. + +## Mathematical Foundation + +With $\alpha = 2/(N+1)$, $\beta = 1 - \alpha$: + +$$ +\text{EMA}_1[t] = \alpha \cdot x_t + \beta \cdot \text{EMA}_1[t-1] +$$ + +$$ +\text{EMA}_2[t] = \alpha \cdot \text{EMA}_1[t] + \beta \cdot \text{EMA}_2[t-1] +$$ + +**Slope and output:** + +$$ +\text{slope}_t = \text{EMA}_1[t] - \text{EMA}_2[t] +$$ + +$$ +\text{LTMA}[t] = \text{EMA}_1[t] + N \cdot \text{slope}_t +$$ + +$$ += (1+N) \cdot \text{EMA}_1[t] - N \cdot \text{EMA}_2[t] +$$ + +**Comparison with DEMA/GDEMA:** LTMA is equivalent to GDEMA with $v = N$: + +| Method | Formula | Projection | +| :--- | :--- | :---: | +| EMA | $\text{EMA}_1$ | 0 bars | +| DEMA | $2\text{EMA}_1 - \text{EMA}_2$ | ~$N/2$ bars | +| LTMA | $(1+N)\text{EMA}_1 - N\text{EMA}_2$ | $N$ bars | + +**Steady-state error on linear trend:** Zero. If $x_t = a + bt$, then $\text{LTMA}_t = a + bt$ exactly (after transient). + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +alpha = 2/(period+1); beta = 1-alpha + +ema1 = alpha*(src - ema1) + ema1 +ema2 = alpha*(ema1 - ema2) + ema2 + +if warmup: + e *= beta; c = 1/(1-e) + comp1 = c*ema1; comp2 = c*ema2 + slope = comp1 - comp2 + result = comp1 + slope * period +else: + slope = ema1 - ema2 + result = ema1 + slope * period +``` + +## Resources + +- Holt, C.C. (1957/2004). "Forecasting Seasonals and Trends by Exponentially Weighted Moving Averages." *International Journal of Forecasting*, 20(1), 5-10. +- Brown, R.G. (1963). *Smoothing, Forecasting and Prediction of Discrete Time Series*. Prentice-Hall. Chapter 5: Linear Exponential Smoothing. +- Gardner, E.S. (1985). "Exponential Smoothing: The State of the Art." *Journal of Forecasting*, 4(1), 1-28. diff --git a/lib/trends_IIR/mcnma/Mcnma.md b/lib/trends_IIR/mcnma/Mcnma.md new file mode 100644 index 00000000..3928ca32 --- /dev/null +++ b/lib/trends_IIR/mcnma/Mcnma.md @@ -0,0 +1,103 @@ +# MCNMA: McNicholl EMA (Zero-Lag TEMA) + +> "Dennis McNicholl applied TEMA to itself and subtracted the result, producing six cascaded EMA stages that cancel lag through three layers of triple-smoothing. When single TEMA is not enough, double it." + +MCNMA computes $2 \times \text{TEMA}(x, N) - \text{TEMA}(\text{TEMA}(x, N), N)$, applying the DEMA lag-cancellation technique to TEMA itself. This requires six cascaded EMA stages: three for the inner TEMA and three for the outer TEMA of the inner TEMA's output. The result is an extremely responsive moving average that tracks fast trends with minimal lag, at the cost of significant overshoot on reversals. Published by Dennis McNicholl in "Better Bollinger Bands" (*Futures Magazine*, October 1998) as a component for improved volatility band construction. + +## Historical Context + +Dennis McNicholl published MCNMA as part of his "Better Bollinger Bands" article in *Futures Magazine* (October 1998), where he argued that standard Bollinger Bands use SMA as the center line, introducing unnecessary lag. His solution was to use a zero-lag moving average derived from nested triple exponential smoothing. + +MCNMA is the logical extension of Mulloy's lag-cancellation hierarchy: +- **DEMA** (1994): $2\text{EMA}_1 - \text{EMA}_2$ (2 stages, cancels first-order lag) +- **TEMA** (1994): $3\text{EMA}_1 - 3\text{EMA}_2 + \text{EMA}_3$ (3 stages, cancels first and second-order lag) +- **MCNMA** (1998): $2\text{TEMA}_1 - \text{TEMA}_2$ where $\text{TEMA}_2 = \text{TEMA}(\text{TEMA}_1)$ (6 stages, cancels through third order) + +Each additional stage of nesting removes another order of lag, but also amplifies noise and overshoot. MCNMA represents the practical limit of this approach; further nesting produces filters that oscillate around price rather than tracking it. + +## Architecture & Physics + +### 1. Inner TEMA (Stages 1-3) + +Three cascaded EMAs compute $\text{TEMA}_1 = 3 \cdot C_1 - 3 \cdot C_2 + C_3$, where $C_i$ is the warmup-compensated output of EMA stage $i$. + +### 2. Outer TEMA (Stages 4-6) + +Three more EMAs receive $\text{TEMA}_1$ as input and compute $\text{TEMA}_2 = 3 \cdot C_4 - 3 \cdot C_5 + C_6$. + +### 3. DEMA Combination + +$$ +\text{MCNMA} = 2 \cdot \text{TEMA}_1 - \text{TEMA}_2 +$$ + +### 4. Shared Warmup Compensator + +All six stages share a single decay tracker $e = \beta^n$, with compensation factor $c = 1/(1-e)$. + +## Mathematical Foundation + +With $\alpha = 2/(N+1)$, $\beta = 1 - \alpha$, and warmup compensator $c = 1/(1-\beta^n)$: + +**Inner TEMA:** + +$$ +C_1 = c \cdot E_1, \quad C_2 = c \cdot E_2, \quad C_3 = c \cdot E_3 +$$ + +$$ +\text{TEMA}_1 = 3C_1 - 3C_2 + C_3 +$$ + +where $E_1 = \alpha(x - E_1) + E_1$, $E_2 = \alpha(C_1 - E_2) + E_2$, $E_3 = \alpha(C_2 - E_3) + E_3$. + +**Outer TEMA:** + +$$ +C_4 = c \cdot E_4, \quad C_5 = c \cdot E_5, \quad C_6 = c \cdot E_6 +$$ + +$$ +\text{TEMA}_2 = 3C_4 - 3C_5 + C_6 +$$ + +where $E_4 = \alpha(\text{TEMA}_1 - E_4) + E_4$, etc. + +**Output:** + +$$ +\text{MCNMA} = 2 \cdot \text{TEMA}_1 - \text{TEMA}_2 +$$ + +**Effective lag:** Near zero for polynomial trends up to degree 3. The six-stage cascade provides approximately $5\times$ less lag than a single EMA of the same period. + +**Overshoot risk:** High. The $2\text{TEMA} - \text{TEMA}(\text{TEMA})$ formula amplifies the TEMA's already aggressive lag compensation. + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +alpha = 2/(period+1); beta = 1-alpha +e_decay *= beta; comp = 1/(1-e_decay) + +// Inner TEMA: 3 cascaded EMAs +e1 += alpha*(src - e1); c1 = e1*comp +e2 += alpha*(c1 - e2); c2 = e2*comp +e3 += alpha*(c2 - e3); c3 = e3*comp +tema1 = 3*c1 - 3*c2 + c3 + +// Outer TEMA: 3 cascaded EMAs of tema1 +e4 += alpha*(tema1 - e4); c4 = e4*comp +e5 += alpha*(c4 - e5); c5 = e5*comp +e6 += alpha*(c5 - e6); c6 = e6*comp +tema2 = 3*c4 - 3*c5 + c6 + +return 2*tema1 - tema2 +``` + +## Resources + +- McNicholl, D. (1998). "Better Bollinger Bands." *Futures Magazine*, October 1998. +- Mulloy, P.G. (1994). "Smoothing Data with Faster Moving Averages." *Technical Analysis of Stocks & Commodities*, 12(1), 11-19. (DEMA and TEMA originals.) +- Mulloy, P.G. (1994). "Smoothing Data with Less Lag." *Technical Analysis of Stocks & Commodities*, 12(2). (TEMA continuation.) diff --git a/lib/trends_IIR/nlma/Nlma.md b/lib/trends_IIR/nlma/Nlma.md new file mode 100644 index 00000000..dfc289a7 --- /dev/null +++ b/lib/trends_IIR/nlma/Nlma.md @@ -0,0 +1,92 @@ +# NLMA: Non-Lag Moving Average + +> "Igorad at TrendLaboratory borrowed a trick from digital filter design: use a damped cosine kernel with negative weights in the mid-section to cancel the lag that positive-only kernels always produce. The result looks like an FIR filter but behaves like nothing else." + +NLMA uses a damped cosine (fading sinusoid) kernel where the weight at position $i$ is $w(i) = \cos(2\pi i/N) \times (1 - i/N)$. The cosine oscillation creates negative weights in the mid-section of the kernel, which subtract lagged price components and reduce the filter's group delay. The linear decay envelope $(1 - i/N)$ ensures the kernel tapers to zero at the window edge. Normalization by the signed weight sum preserves DC gain of 1.0. The result is a moving average with substantially less lag than an SMA of the same period. + +## Historical Context + +NLMA was developed by Igorad (username on trading forums) at TrendLaboratory, inspired by the FATL/SATL digital filter coefficient sets published by Finware. The FATL (Fast Adaptive Trend Line) and SATL (Slow Adaptive Trend Line) filters use fixed FIR coefficients derived from optimal filter design, with negative weights that provide lag cancellation. Igorad's contribution was to replace the fixed coefficients with a parametric damped-cosine formula, allowing the filter to be configured for any period. + +The damped cosine kernel has a natural interpretation in signal processing: it is the impulse response of a damped resonator at frequency $f = 1/N$. The resonance frequency matches the filter period, meaning the negative portion of the cosine systematically cancels the frequency component that causes the most lag. This is analogous to how DEMA uses $2\text{EMA} - \text{EMA}(\text{EMA})$ to cancel lag, but NLMA achieves it through the kernel shape itself rather than algebraic subtraction. + +The presence of negative weights means NLMA is not a convex combination of input prices. The output can exceed the input range (overshoot), similar to DEMA and HMA. + +## Architecture & Physics + +### 1. Damped Cosine Weight Function + +For each lag position $j = 0, 1, \ldots, N-1$ (where $j=0$ is newest): + +$$ +w(j) = \cos\!\left(\frac{2\pi j}{N}\right) \times \left(1 - \frac{j}{N}\right) +$$ + +### 2. Signed-Sum Normalization + +The weight sum includes both positive and negative weights: + +$$ +\text{NLMA}_t = \frac{\sum_{j=0}^{N-1} w(j) \cdot x_{t-j}}{\sum_{j=0}^{N-1} w(j)} +$$ + +Because some weights are negative, $\sum w < \sum |w|$, which amplifies the effective contribution of recent (positive-weighted) bars. + +### 3. Adaptive Warmup + +During the warmup period ($\text{count} < N$), the kernel is recomputed with the effective period $p = \min(\text{bar\_count}, N)$, providing valid output from bar 1. + +## Mathematical Foundation + +The NLMA kernel function: + +$$ +w[j] = \cos\!\left(\frac{2\pi j}{N}\right) \cdot \left(1 - \frac{j}{N}\right), \quad j = 0, 1, \ldots, N-1 +$$ + +**Weight structure analysis:** + +| Region | Range of $j$ | $\cos$ sign | Weight sign | Effect | +| :--- | :---: | :---: | :---: | :--- | +| Recent | $0 \leq j < N/4$ | + | + | Track price | +| Mid-lag | $N/4 \leq j < 3N/4$ | - | - | Cancel lag | +| Old | $3N/4 \leq j < N$ | + | + | Mild stabilization | + +The negative mid-section weights are the lag-cancellation mechanism. They subtract the delayed price component that would otherwise pull the output backward. + +**Frequency response:** The damped cosine kernel creates a bandpass notch near $f = 1/N$, suppressing the frequency most responsible for lag while passing lower frequencies (trend) and attenuating higher frequencies (noise). + +**DC gain normalization:** + +$$ +H(0) = \frac{\sum w[j]}{\sum w[j]} = 1 +$$ + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +p = min(bar_count, period) + +// Compute weights (recompute during warmup) +for j = 0 to p-1: + angle = 2π * j / p + decay = 1 - j/p + w[j] = cos(angle) * decay + +// Weighted sum with signed normalization +sum_wv = 0; sum_w = 0 +for i = 0 to p-1: + if not NaN(source[i]): + sum_wv += source[i] * w[i] + sum_w += w[i] + +return sum_w != 0 ? sum_wv / sum_w : source +``` + +## Resources + +- Igorad / TrendLaboratory. "NonLagMA" indicator documentation. Available on TradingView and various trading forums. +- Finware Ltd. "FATL/SATL Digital Filters." Technical documentation for FinWare trading software. +- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 4: FIR Filters with Negative Weights. diff --git a/lib/trends_IIR/nma/Nma.md b/lib/trends_IIR/nma/Nma.md new file mode 100644 index 00000000..e6a59532 --- /dev/null +++ b/lib/trends_IIR/nma/Nma.md @@ -0,0 +1,108 @@ +# NMA: Natural Moving Average + +> "Jim Sloman looked at how volatility distributes across a window and asked: if the most volatile bars are recent, should the filter not respond faster? NMA derives its smoothing constant from the volatility profile itself, weighted by a square-root kernel that emphasizes recent action." + +NMA is an adaptive IIR filter whose smoothing ratio is derived from a volatility-weighted square-root kernel analysis of log-price movements over a lookback window. When volatility concentrates in recent bars, the ratio approaches 1.0 (fast tracking). When volatility is spread uniformly, the ratio approaches $1/\sqrt{N}$ (heavy smoothing). The square-root kernel $(\sqrt{i+1} - \sqrt{i})$ gives a concave-down weighting that gently emphasizes recency, while the log-price transformation normalizes for price level, making the adaptation scale-invariant. + +## Historical Context + +Jim Sloman introduced the Natural Moving Average in *Ocean Theory* (pages 63-70), a book that applied chaos and complexity theory metaphors to financial markets. The NMA was designed as a "natural" filter that lets the market's own volatility structure determine the smoothing rate, rather than imposing an arbitrary period. + +The core innovation is the square-root differencing kernel $\sqrt{i+1} - \sqrt{i}$ as the weighting function for volatility. This kernel has the property that its cumulative sum $\sqrt{N}$ grows sublinearly, meaning each additional bar in the lookback contributes less weight than the previous one. This creates a "diminishing returns" effect: extending the lookback adds context without drowning out recent information. + +The log-price transformation ($\ln(\text{price}) \times 1000$) serves two purposes: (1) it makes the volatility measure proportional to percentage moves rather than absolute dollar moves, and (2) the scaling factor of 1000 brings typical values into a numerically convenient range for the ratio computation. + +NMA belongs to the family of adaptive moving averages alongside KAMA, VIDYA, and ADXVMA, but uses a unique adaptation mechanism based on the spatial distribution of volatility rather than a single efficiency or strength metric. + +## Architecture & Physics + +### 1. Log-Price Buffer + +A circular buffer of size $N+1$ stores $\ln(\text{price}) \times 1000$ for each bar, providing the lookback data for volatility computation. + +### 2. Volatility-Weighted Square-Root Ratio + +For each bar $i$ in the lookback: + +$$ +o_i = |\ln_i - \ln_{i+1}| +$$ + +$$ +\text{num} = \sum_{i=0}^{N-1} o_i \cdot \left(\sqrt{i+1} - \sqrt{i}\right) +$$ + +$$ +\text{denom} = \sum_{i=0}^{N-1} o_i +$$ + +$$ +\text{ratio} = \frac{\text{num}}{\text{denom}} +$$ + +### 3. Adaptive EMA Step + +$$ +\text{NMA}_t = \text{NMA}_{t-1} + \text{ratio} \times (x_t - \text{NMA}_{t-1}) +$$ + +## Mathematical Foundation + +**Log-price volatility:** + +$$ +o_i = \left|\ln(x_{t-i}) - \ln(x_{t-i-1})\right| \times 1000 +$$ + +**Square-root kernel weights:** + +$$ +\phi_i = \sqrt{i+1} - \sqrt{i} = \frac{1}{\sqrt{i+1} + \sqrt{i}} +$$ + +Note: $\phi_i \approx \frac{1}{2\sqrt{i}}$ for large $i$, confirming the $1/\sqrt{i}$ decay rate. + +**Adaptive ratio:** + +$$ +r = \frac{\sum_{i=0}^{N-1} o_i \cdot \phi_i}{\sum_{i=0}^{N-1} o_i} +$$ + +**Ratio bounds:** + +- If all volatility is at $i = 0$ (most recent): $r = \phi_0 = \sqrt{1} - \sqrt{0} = 1$ +- If volatility is uniform: $r = \frac{\sum \phi_i}{N} = \frac{\sqrt{N}}{N} = \frac{1}{\sqrt{N}}$ +- For $N = 40$: uniform ratio $\approx 0.158$, equivalent to EMA period $\approx 11$ + +**IIR update:** + +$$ +\text{NMA}_t = \text{NMA}_{t-1} + r_t \cdot (x_t - \text{NMA}_{t-1}) +$$ + +**Default parameters:** `period = 40`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +// Store scaled log-price +lnBuf[head] = log(src) * 1000 + +// Compute volatility-weighted ratio +num = 0; denom = 0 +for i = 0 to bars-1: + oi = |lnBuf[t-i] - lnBuf[t-i-1]| + num += oi * (sqrt(i+1) - sqrt(i)) + denom += oi + +ratio = denom != 0 ? num/denom : 0 + +// Adaptive EMA step +result = result + ratio * (src - result) +``` + +## Resources + +- Sloman, J. *Ocean Theory*. Pages 63-70. (Original NMA description.) +- Kaufman, P.J. (2013). *Trading Systems and Methods*, 5th ed. Wiley. Chapter 7: Adaptive Moving Averages. +- Chande, T.S. & Kroll, S. (1994). *The New Technical Trader*. Wiley. (Adaptive filter framework.) diff --git a/lib/trends_IIR/nyqma/Nyqma.md b/lib/trends_IIR/nyqma/Nyqma.md new file mode 100644 index 00000000..6ab3ea7a --- /dev/null +++ b/lib/trends_IIR/nyqma/Nyqma.md @@ -0,0 +1,91 @@ +# NYQMA: Nyquist Moving Average + +> "Manfred Dürschner applied the Nyquist-Shannon sampling theorem to cascaded moving averages: the second smoothing period must not exceed half the first, or you get aliasing artifacts. Respect the theorem and the ghost signals disappear." + +NYQMA combines a primary LWMA (Linear Weighted Moving Average) with a secondary LWMA applied to the first, using lag-compensating extrapolation: $\text{NYQMA} = (1+\alpha) \cdot \text{MA}_1 - \alpha \cdot \text{MA}_2$, where $\alpha = N_2 / (N_1 - N_2)$. The Nyquist constraint $N_2 \leq \lfloor N_1/2 \rfloor$ ensures the second smoothing does not introduce aliasing artifacts ("ghost signals") into the output. This produces a lag-reduced moving average grounded in sampling theory rather than ad-hoc coefficient tuning. + +## Historical Context + +Dr. Manfred G. Dürschner published NYQMA in *Gleitende Durchschnitte 3.0* ("Moving Averages 3.0"), a German-language work that applies rigorous signal-processing theory to financial moving average design. Dürschner's key insight was that cascading two smoothing operations is mathematically equivalent to sampling a continuous signal at two rates, and the Nyquist-Shannon sampling theorem dictates that the second rate cannot exceed half the first without introducing aliasing. + +The Nyquist-Shannon theorem (1949) states that a signal must be sampled at more than twice its highest frequency to avoid aliasing. In the context of cascaded MAs, the "sampling rate" analogy maps to the smoothing period: a primary MA with period $N_1$ has an effective frequency cutoff, and the secondary MA with period $N_2$ must have a cutoff at no more than half that frequency (i.e., $N_2 \leq N_1/2$) to avoid passing through frequency components that the first MA was designed to suppress. + +The lag compensation formula $(1+\alpha) \cdot \text{MA}_1 - \alpha \cdot \text{MA}_2$ is structurally identical to DEMA and GDEMA, but with the critical distinction that both MAs are LWMAs (not EMAs) and the gain factor $\alpha$ is derived from the period ratio rather than being a free parameter. + +## Architecture & Physics + +### 1. Primary LWMA + +A standard Linear Weighted Moving Average with period $N_1$: + +$$ +\text{MA}_1 = \text{WMA}(x, N_1) +$$ + +### 2. Secondary LWMA + +A LWMA applied to $\text{MA}_1$ with Nyquist-constrained period $N_2 \leq \lfloor N_1/2 \rfloor$: + +$$ +\text{MA}_2 = \text{WMA}(\text{MA}_1, N_2) +$$ + +### 3. Lag-Compensating Extrapolation + +$$ +\alpha = \frac{N_2}{N_1 - N_2} +$$ + +$$ +\text{NYQMA} = (1 + \alpha) \cdot \text{MA}_1 - \alpha \cdot \text{MA}_2 +$$ + +### 4. Nyquist Enforcement + +The implementation clamps $N_2 = \min(N_2, \lfloor N_1/2 \rfloor)$ to enforce the sampling constraint. + +## Mathematical Foundation + +**LWMA (period $N$):** + +$$ +\text{WMA}(x, N) = \frac{\sum_{i=0}^{N-1} (N-i) \cdot x_{t-i}}{\sum_{i=0}^{N-1} (N-i)} = \frac{\sum_{i=0}^{N-1} (N-i) \cdot x_{t-i}}{N(N+1)/2} +$$ + +**Lag compensation coefficient:** + +$$ +\alpha = \frac{N_2}{N_1 - N_2} +$$ + +**Output:** + +$$ +\text{NYQMA} = (1 + \alpha) \cdot \text{WMA}(x, N_1) - \alpha \cdot \text{WMA}(\text{WMA}(x, N_1), N_2) +$$ + +**Nyquist constraint (hard rule):** + +$$ +N_2 \leq \left\lfloor \frac{N_1}{2} \right\rfloor +$$ + +**Lag analysis:** WMA has group delay $(N-1)/3$. The extrapolation compensates a fraction $\alpha/(1+\alpha) = N_2/N_1$ of the primary MA's lag. + +**Default parameters:** `period = 89` ($N_1$), `nyquist_period = 21` ($N_2$), `minPeriod = 2`. + +**Pseudo-code (streaming):** + +``` +n2 = min(nyquist_period, period / 2) // enforce Nyquist +ma1 = WMA(src, period) +ma2 = WMA(ma1, n2) +alpha = n2 / (period - n2) +return (1 + alpha) * ma1 - alpha * ma2 +``` + +## Resources + +- Dürschner, M.G. *Gleitende Durchschnitte 3.0*. (Original NYQMA publication, German language.) +- Shannon, C.E. (1949). "Communication in the Presence of Noise." *Proceedings of the IRE*, 37(1), 10-21. +- Nyquist, H. (1928). "Certain Topics in Telegraph Transmission Theory." *Transactions of the AIEE*, 47(2), 617-644. diff --git a/lib/trends_IIR/rain/Rain.md b/lib/trends_IIR/rain/Rain.md new file mode 100644 index 00000000..c22f1b50 --- /dev/null +++ b/lib/trends_IIR/rain/Rain.md @@ -0,0 +1,92 @@ +# RAIN: Rainbow Moving Average + +> "Mel Widner applied SMA ten times recursively, then weighted the layers like a rainbow: brightest at the top, fading toward the base. Ten colors of smoothing, one composite average that sees both fast and slow structure simultaneously." + +RAIN recursively applies SMA 10 times, producing 10 layers of progressively smoother price representation, then computes a weighted average across all layers. Layers 1-4 receive weights 5, 4, 3, 2 (emphasizing the more responsive layers), while layers 5-10 each receive weight 1, for a total divisor of 20. This multi-scale composition produces a moving average that responds to short-term price changes through the lightly smoothed upper layers while maintaining stability through the heavily smoothed lower layers. + +## Historical Context + +Mel Widner published "Rainbow Charts" in *Technical Analysis of Stocks & Commodities* (1998), introducing the concept of recursive SMA application as both a visualization technique and a composite smoothing method. The thinkorswim platform later standardized the weight vector as $[5, 4, 3, 2, 1, 1, 1, 1, 1, 1]$, which became the canonical RAIN MA. + +The recursive SMA application has a deep mathematical interpretation: applying SMA $k$ times is equivalent to convolving the rectangular kernel with itself $k$ times, which produces a B-spline kernel of order $k$. Thus RAIN's 10 layers correspond to B-splines of orders 1 through 10, and the weighted average blends these spline approximations. The B-spline interpretation explains why higher layers are smoother: each convolution adds a degree of polynomial reproduction and reduces the spectral sidelobe level. + +The weight vector $[5, 4, 3, 2, 1, 1, 1, 1, 1, 1]$ with sum 20 was chosen empirically rather than derived from optimization theory. The declining weights for layers 1-4 bias the output toward the more responsive layers, making RAIN track trends more closely than a uniform average of all 10 layers would. + +## Architecture & Physics + +### 1. Ten Cascaded SMA Layers + +Each layer is an SMA applied to the previous layer's output: + +$$ +\text{MA}_1 = \text{SMA}(x, N), \quad \text{MA}_k = \text{SMA}(\text{MA}_{k-1}, N), \quad k = 2, \ldots, 10 +$$ + +### 2. O(1) Running-Sum SMA + +Each of the 10 SMA layers uses a circular buffer with a running sum, giving O(1) per-bar update cost per layer. Total cost: O(10) per bar, with O($10 \times N$) memory for the 10 buffers. + +### 3. Weighted Composite + +$$ +\text{RAIN} = \frac{5 \cdot \text{MA}_1 + 4 \cdot \text{MA}_2 + 3 \cdot \text{MA}_3 + 2 \cdot \text{MA}_4 + \sum_{k=5}^{10} \text{MA}_k}{20} +$$ + +## Mathematical Foundation + +**Layer computation (recursive SMA):** + +$$ +\text{MA}_1[t] = \frac{1}{N}\sum_{i=0}^{N-1} x_{t-i} +$$ + +$$ +\text{MA}_k[t] = \frac{1}{N}\sum_{i=0}^{N-1} \text{MA}_{k-1}[t-i], \quad k = 2, \ldots, 10 +$$ + +**Equivalent kernel:** The $k$-fold SMA is the $k$-th order B-spline kernel: + +$$ +B_k(x) = \underbrace{B_0 * B_0 * \cdots * B_0}_{k \text{ times}}(x) +$$ + +where $B_0$ is the rectangular pulse. + +**Weighted output:** + +$$ +\text{RAIN} = \frac{\sum_{k=1}^{10} w_k \cdot \text{MA}_k}{20} +$$ + +with weights $\mathbf{w} = [5, 4, 3, 2, 1, 1, 1, 1, 1, 1]$. + +**Group delay:** Each SMA layer adds $(N-1)/2$ bars of lag. However, the weighted composite lag is: + +$$ +\bar{d} = \frac{\sum w_k \cdot k \cdot (N-1)/2}{\sum w_k} +$$ + +For $N = 2$: $\bar{d} \approx 1.85$ bars. The upper-layer weighting significantly reduces the effective lag below what layer 10 alone would produce. + +**Default parameters:** `period = 2`, `fixed layers = 10`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +// 10 circular buffers with running sums +for layer = 1 to 10: + sum[layer] -= buf[layer][head] + sum[layer] += input[layer] // input is price for layer 1, MA[layer-1] for others + buf[layer][head] = input[layer] + MA[layer] = sum[layer] / count + +head = (head + 1) % period + +return (5*MA[1] + 4*MA[2] + 3*MA[3] + 2*MA[4] + MA[5] + MA[6] + MA[7] + MA[8] + MA[9] + MA[10]) / 20 +``` + +## Resources + +- Widner, M. (1998). "Rainbow Charts." *Technical Analysis of Stocks & Commodities*. +- thinkorswim / TD Ameritrade. "RainbowAverage" study documentation. +- Schoenberg, I.J. (1946). "Contributions to the Problem of Approximation of Equidistant Data by Analytic Functions." *Quarterly of Applied Mathematics*, 4(1), 45-99. (B-spline theory underlying recursive SMA.) diff --git a/lib/trends_IIR/trama/Trama.md b/lib/trends_IIR/trama/Trama.md new file mode 100644 index 00000000..de8c7dda --- /dev/null +++ b/lib/trends_IIR/trama/Trama.md @@ -0,0 +1,96 @@ +# TRAMA: Trend Regularity Adaptive Moving Average + +> "LuxAlgo counted how often price makes new highs and new lows within a window, squared that fraction, and used it as an EMA smoothing constant. Trending markets produce frequent HH/LLs and the filter tracks fast. Ranging markets produce few, and the filter stops moving. Simple, effective, elegant." + +TRAMA is an adaptive EMA where the smoothing factor derives from the "trend regularity" of the lookback window, measured as the fraction of bars that produce either a new highest-high (HH) or a new lowest-low (LL). This fraction is squared to create a convex penalty: low regularity (ranging) produces near-zero smoothing (filter barely moves), while high regularity (trending) produces aggressive smoothing (filter tracks closely). Developed by LuxAlgo (TradingView, December 2020). + +## Historical Context + +TRAMA was published by LuxAlgo on TradingView in December 2020 as a novel approach to adaptive smoothing. While earlier adaptive MAs (KAMA, VIDYA, ADXVMA) derive their adaptation from efficiency ratios, standard deviations, or ADX, TRAMA uses a purely non-parametric measure: the frequency of new extremes. + +The key insight is that trending markets are characterized by a high rate of new highest-highs and lowest-lows, while ranging markets produce new extremes only occasionally (at the range boundaries). This binary event (new extreme or not) is robust to the magnitude of price changes and immune to the scale issues that affect volatility-based adaptive methods. + +The squaring of the trend coefficient $tc = [\text{SMA}(\text{HH or LL occurred}, N)]^2$ is critical to TRAMA's behavior. Without squaring, a market with 50% HH/LL bars (typical for a mild trend) would use $tc = 0.5$, producing moderate smoothing. With squaring, $tc = 0.25$, producing heavier smoothing. This convex penalty ensures that TRAMA switches between "tracking" and "holding" regimes more sharply than a linear adaptation would, reducing whipsaws in ambiguous market conditions. + +## Architecture & Physics + +### 1. Extreme Detection + +On each bar, check whether the rolling highest-high or lowest-low (over the lookback period) has changed: + +$$ +\text{HH} = \max\left(\text{sign}\left(\Delta\, \text{Highest}(N)\right), 0\right) +$$ + +$$ +\text{LL} = \max\left(\text{sign}\left(-\Delta\, \text{Lowest}(N)\right), 0\right) +$$ + +### 2. Trend Regularity Coefficient + +$$ +tc = \left[\text{SMA}\left(\text{HH or LL} \neq 0 \;\;?\;\; 1 : 0, \;\;N\right)\right]^2 +$$ + +This gives the squared fraction of bars with new extremes. + +### 3. Adaptive EMA Step + +$$ +\text{TRAMA}_t = \text{TRAMA}_{t-1} + tc \times (x_t - \text{TRAMA}_{t-1}) +$$ + +## Mathematical Foundation + +**Extreme indicators:** + +$$ +\text{HH}_t = \begin{cases} 1 & \text{if } \max(x_{t}, \ldots, x_{t-N+1}) > \max(x_{t-1}, \ldots, x_{t-N}) \\ 0 & \text{otherwise} \end{cases} +$$ + +$$ +\text{LL}_t = \begin{cases} 1 & \text{if } \min(x_{t}, \ldots, x_{t-N+1}) < \min(x_{t-1}, \ldots, x_{t-N}) \\ 0 & \text{otherwise} \end{cases} +$$ + +**Trend coefficient (squared SMA of binary events):** + +$$ +tc_t = \left[\frac{1}{N}\sum_{i=0}^{N-1} \mathbf{1}\left(\text{HH}_{t-i} \vee \text{LL}_{t-i}\right)\right]^2 +$$ + +**Adaptive update:** + +$$ +\text{TRAMA}_t = \text{TRAMA}_{t-1} + tc_t \cdot (x_t - \text{TRAMA}_{t-1}) +$$ + +**Regime behavior:** + +| Market Regime | HH/LL frequency | Raw $tc$ | Squared $tc$ | Equivalent EMA period | +| :--- | :---: | :---: | :---: | :---: | +| Strong trend | ~80% | 0.8 | 0.64 | ~2.6 | +| Moderate trend | ~50% | 0.5 | 0.25 | ~7 | +| Mild trend | ~30% | 0.3 | 0.09 | ~20 | +| Range-bound | ~10% | 0.1 | 0.01 | ~199 | + +**Default parameters:** `period = 14`, `minPeriod = 1`. + +**Pseudo-code (streaming):** + +``` +// Detect new highest-high or lowest-low +hh = max(sign(change(highest(src, length))), 0) +ll = max(sign(change(lowest(src, length)) * -1), 0) + +// Trend regularity: fraction of bars with HH or LL, squared +tc = sma((hh or ll) ? 1 : 0, length) ^ 2 + +// Adaptive EMA +trama = trama[1] + tc * (src - trama[1]) +``` + +## Resources + +- LuxAlgo (2020). "TRAMA - Trend Regularity Adaptive Moving Average." TradingView. Published December 2020. +- Kaufman, P.J. (1995). *Smarter Trading*. McGraw-Hill. Chapter 7: Adaptive Techniques. (KAMA framework, precursor to adaptive MA design.) +- Chande, T.S. (1997). *Beyond Technical Analysis*, 2nd ed. Wiley. (VIDYA and adaptive smoothing theory.)