mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 09:08:04 +00:00
feat(dynamics): add PlusDI, MinusDI, PlusDM, MinusDM indicators
Complete thin Dx-composition wrapper indicators with full test coverage: - PlusDi/MinusDi: Directional Indicator wrappers (DiPlus/DiMinus from Dx) - PlusDm/MinusDm: Directional Movement wrappers (DmPlus/DmMinus from Dx) - Individual validation tests per indicator directory (TALib, Skender, bounds) - Combined unit tests (DiDm.Tests.cs) and validation tests (DiDm.Validation.Tests.cs) - Quantower wrappers + tests for all 4 indicators - PineScript v6 implementations with compensated RMA - Normalized .md documentation for all indicators and categories - 182 tests passing, 0 failures
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
# Channels
|
||||
|
||||
> "In trending markets, ride the channel. In ranging markets, fade the edges." Unknown
|
||||
|
||||
Channels define dynamic support and resistance. Upper band shows where price tends to find resistance; lower band shows support. Width measures volatility; price position within channel measures momentum and mean-reversion potential.
|
||||
|
||||
## Indicators
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# ABERR: Aberration Bands
|
||||
|
||||
> *Aberration measures the distance between price and its smoothed self — when the gap grows extreme, reversion whispers.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -73,23 +75,6 @@ $$\text{MAD} = \sigma \sqrt{\frac{2}{\pi}} \approx 0.7979\,\sigma$$
|
||||
|
||||
Therefore ABERR with $k = 2.0$ captures approximately the same range as Bollinger Bands with $k \approx 1.596$.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function ABERR(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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# ACCBANDS: Acceleration Bands
|
||||
|
||||
> *Acceleration bands widen with high-low range, framing the expected reach of each bar's ambition.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -63,26 +65,6 @@ Three independent circular buffers maintain running sums for $O(1)$ streaming up
|
||||
| `period` | Lookback period for the three SMAs ($n$) | 20 | $> 0$ |
|
||||
| `factor` | Multiplier for normalized width ($F$) | 4.0 | $> 0$ |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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]
|
||||
```
|
||||
|
||||
### Breakout Rule (Headley)
|
||||
|
||||
A trend is confirmed when:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# APCHANNEL: Adaptive Price Channel
|
||||
|
||||
> *An adaptive channel reshapes its width in real time, tracking the market's own sense of normal.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -74,30 +76,6 @@ $$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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
+2
-33
@@ -1,5 +1,7 @@
|
||||
# APZ: Adaptive Price Zone
|
||||
|
||||
> *The adaptive price zone contracts in calm and expands in chaos, mapping volatility into a living boundary.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -88,39 +90,6 @@ $$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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# ATRBANDS: Average True Range Bands
|
||||
|
||||
> *True range bands let volatility itself draw the envelope — wider when uncertain, tighter when resolved.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -72,33 +74,6 @@ The SMA uses a circular buffer for $O(1)$ running sums. The ATR uses recursive I
|
||||
| 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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# BBANDS: Bollinger Bands
|
||||
|
||||
> *Standard deviation channels adapt to the market's own volatility rhythm, expanding and contracting like breathing.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -78,36 +80,6 @@ The circular buffer maintains running sums of $x$ and $x^2$, enabling $O(1)$ com
|
||||
| 2.0 | 95.4% | 75.0% |
|
||||
| 3.0 | 99.7% | 88.9% |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function BBANDS(source, period, multiplier):
|
||||
validate: period > 0, multiplier > 0
|
||||
|
||||
// Circular buffer maintains running sums
|
||||
sum += source; sumSq += source²
|
||||
oldest = buffer[head]
|
||||
if oldest exists: sum -= oldest; sumSq -= oldest²
|
||||
|
||||
// SMA (middle band)
|
||||
middle = sum / count
|
||||
|
||||
// Population standard deviation
|
||||
variance = max(0, sumSq/count - middle²)
|
||||
sigma = √variance
|
||||
dev = multiplier * sigma
|
||||
|
||||
// Bands
|
||||
upper = middle + dev
|
||||
lower = middle - dev
|
||||
|
||||
// Derived metrics
|
||||
bandwidth = (upper - lower) / middle
|
||||
percentB = (source - lower) / (upper - lower)
|
||||
|
||||
return [middle, upper, lower, bandwidth, percentB]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Range | Meaning |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# DCHANNEL: Donchian Channels
|
||||
|
||||
> *The highest high and lowest low over a window — Donchian's simplicity captures breakout potential in two lines.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -63,29 +65,6 @@ The bands stay flat until either a new extreme occurs or the old extreme exits t
|
||||
|-----------|-------------|---------|------------|
|
||||
| `period` | Lookback window for high/low extremes ($n$) | 20 | $> 0$ |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function DCHANNEL(high, low, period):
|
||||
validate: period > 0
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
middle = (upper + lower) / 2
|
||||
|
||||
return [middle, upper, lower]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# DECAYCHANNEL: Decay Min-Max Channel
|
||||
|
||||
> *Extremes that decay over time give recent boundaries more weight, fading yesterday's peaks gradually.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -79,43 +81,6 @@ $$\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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
+2
-21
@@ -1,5 +1,7 @@
|
||||
# FCB: Fractal Chaos Bands
|
||||
|
||||
> *Fractal chaos bands connect swing pivots into a channel, letting the market's own geometry define containment.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -74,27 +76,6 @@ Fractal detection is $O(1)$ (3 comparisons). Deque maintenance is $O(1)$ amortiz
|
||||
|-----------|-------------|---------|------------|
|
||||
| `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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# JBANDS: Jurik Adaptive Envelope Bands
|
||||
|
||||
> *Jurik's adaptive envelope adjusts its width with price dynamics, hugging trends and releasing during consolidation.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -100,37 +102,6 @@ Dominated by the trimmed mean's partial sort: $O(n \log n)$ for the 128-element
|
||||
| $\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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# KCHANNEL: Keltner Channel
|
||||
|
||||
> *Keltner wraps an EMA in ATR-scaled bands — a volatility envelope that responds to both trend and range.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -81,33 +83,6 @@ $O(1)$ per bar: one EMA update, one True Range computation, one RMA update, and
|
||||
| Gap sensitivity | Yes (via TR) | Yes (via TR) | No |
|
||||
| Distribution assumption | None | None | Gaussian |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function KCHANNEL(source, high, low, close, period, multiplier):
|
||||
validate: period > 0, multiplier > 0
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// Bands
|
||||
width = multiplier * atr
|
||||
upper = ema + width
|
||||
lower = ema - width
|
||||
|
||||
return [ema, upper, lower]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# MAENV: Moving Average Envelope
|
||||
|
||||
> *A fixed percentage above and below a moving average — the simplest envelope assumes symmetry in price behavior.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -71,25 +73,6 @@ $O(1)$ for SMA and EMA modes. $O(n)$ for WMA mode due to the weighted sum.
|
||||
| `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]
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# MMCHANNEL: Min-Max Channel
|
||||
|
||||
> *The raw min-max channel captures absolute extremes — no smoothing, no forgiveness, just the bounds of recent history.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# PCHANNEL: Price Channel
|
||||
|
||||
> *Price channels frame the trading range by its own high-low extremes, defining the field of play.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -79,41 +81,6 @@ Streaming: $O(1)$ amortized per bar. Each element enters and exits each deque at
|
||||
|--------|------|------------|-------------|
|
||||
| $n$ | period | $> 0$ | Lookback window size |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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)
|
||||
|
||||
for each bar t:
|
||||
hbuf[t mod period] = high[t]
|
||||
lbuf[t mod period] = low[t]
|
||||
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
|
||||
max_deque.push_back(t)
|
||||
min_deque.push_back(t)
|
||||
|
||||
upper = hbuf[max_deque.front mod period]
|
||||
lower = lbuf[min_deque.front mod period]
|
||||
middle = (upper + lower) / 2
|
||||
|
||||
emit (upper, middle, lower)
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Interpretation |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# REGCHANNEL: Linear Regression Channel
|
||||
|
||||
> *A regression line flanked by standard error bands — the channel where statistics meets price trajectory.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -101,45 +103,6 @@ $$
|
||||
|
||||
For $n \geq 2$, $D > 0$ always, ensuring numerical stability.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
for each bar t:
|
||||
buf.add(source[t])
|
||||
n = buf.count
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Interpretation |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# SDCHANNEL: Standard Deviation Channel
|
||||
|
||||
> *Standard deviation channels center on a moving average and let dispersion define the expected range.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -97,44 +99,6 @@ $$
|
||||
|
||||
For $n \geq 2$, $D > 0$ always holds, so the slope denominator is never zero.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
| Slope | Market State |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# STARCHANNEL: Stoller Average Range Channel
|
||||
|
||||
> *Stoller channels use ATR to build a corridor around the average — a volatility-aware boundary for range traders.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -90,51 +92,6 @@ Streaming: $O(1)$ per bar. The SMA uses a running sum with circular buffer (add
|
||||
| $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) |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function starchannel(source[], high[], low[], close[], period, multiplier, atr_length):
|
||||
effective_atr = atr_length > 0 ? atr_length : period
|
||||
alpha = 1.0 / effective_atr
|
||||
|
||||
buf = circular_buffer(period)
|
||||
sum = 0.0
|
||||
count = 0
|
||||
|
||||
raw_rma = 0.0
|
||||
e = 1.0 // warmup compensator
|
||||
prevClose = close[0]
|
||||
EPSILON = 1e-10
|
||||
|
||||
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
|
||||
|
||||
// True Range
|
||||
tr = max(high[t] - low[t],
|
||||
abs(high[t] - prevClose),
|
||||
abs(low[t] - prevClose))
|
||||
prevClose = close[t]
|
||||
|
||||
// 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
|
||||
|
||||
// Bands
|
||||
width = atr * multiplier
|
||||
upper = middle + width
|
||||
lower = middle - width
|
||||
|
||||
emit (middle, upper, lower)
|
||||
```
|
||||
|
||||
### Output Interpretation
|
||||
|
||||
| Output | Interpretation |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# STBANDS: Super Trend Bands
|
||||
|
||||
> *Super Trend bands fuse trend direction with volatility width, flipping their bias at each breakout.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -111,62 +113,6 @@ Streaming: $O(1)$ per bar. The TR running sum uses a ring buffer; the ratchet lo
|
||||
| $n$ | period | 10 | $> 0$ | ATR lookback period |
|
||||
| $k$ | multiplier | 3.0 | $> 0$ | ATR multiplier for band distance from HL2 |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
| Condition | Upper Band | Lower Band |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# TTM_LRC: TTM Linear Regression Channel
|
||||
|
||||
> *Linear regression channels project the statistical trend and drape standard deviation curtains around it.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -90,50 +92,6 @@ Per bar: $O(n)$ due to two loops over the window. Memory: a ring buffer of $n$ d
|
||||
| $n$ | period | 100 | $> 1$ | Lookback window for regression |
|
||||
| $k$ | deviations | 2.0 | $> 0$ | Outer band stddev multiplier |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
### Statistical Zone Interpretation
|
||||
|
||||
| Zone | Probability | Interpretation |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# UBANDS: Ehlers Ultimate Bands
|
||||
|
||||
> *Ehlers' ultimate bands apply cycle-aware smoothing to define an envelope that resonates with dominant frequency.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -107,47 +109,6 @@ $$
|
||||
|
||||
Cutoff frequency: approximately $f_c \approx 1/(2\pi n)$ cycles per bar. Rolloff: 12 dB/octave.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
usf_prev1 = NaN, usf_prev2 = NaN
|
||||
|
||||
for each bar t:
|
||||
s0 = source[t]
|
||||
s1 = source[t-1] // or s0 if unavailable
|
||||
s2 = source[t-2] // or s1 if unavailable
|
||||
|
||||
if usf not initialized:
|
||||
usf = s0
|
||||
else:
|
||||
usf = (1 - c1)*s0 + (2*c1 - c2)*s1
|
||||
- (c1 + c3)*s2 + c2*usf_prev1 + c3*usf_prev2
|
||||
|
||||
usf_prev2 = usf_prev1
|
||||
usf_prev1 = usf
|
||||
|
||||
// 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
|
||||
|
||||
rms = count > 0 ? sqrt(sum_sq / count) : 0
|
||||
upper = usf + multiplier * rms
|
||||
lower = usf - multiplier * rms
|
||||
|
||||
emit (upper, usf, lower)
|
||||
```
|
||||
|
||||
### RMS vs Standard Deviation
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# UCHANNEL: Ehlers Ultimate Channel
|
||||
|
||||
> *The ultimate channel uses Ehlers' signal processing to carve boundaries that track the market's hidden periodicity.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -108,55 +110,6 @@ $$
|
||||
|
||||
Frequency response: cutoff at approximately $f_c \approx 1/(2\pi n)$ cycles per bar; 12 dB/octave rolloff.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
usf_str = [NaN, NaN] // two-element state
|
||||
usf_cen = [NaN, NaN]
|
||||
|
||||
for each bar t:
|
||||
// True Range
|
||||
th = max(high[t], close[t-1])
|
||||
tl = min(low[t], close[t-1])
|
||||
tr = th - tl
|
||||
|
||||
// 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)
|
||||
```
|
||||
|
||||
### UCHANNEL vs UBANDS
|
||||
|
||||
| Aspect | UBANDS | UCHANNEL |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# VWAPBANDS: VWAP with Dual Standard Deviation Bands
|
||||
|
||||
> *VWAP anchored by dual deviation bands reveals where volume-weighted fair value ends and excess begins.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -86,48 +88,6 @@ Streaming: $O(1)$ per bar. Three additions to running sums, one division, one sq
|
||||
|--------|------|---------|------------|-------------|
|
||||
| $k$ | multiplier | 1.0 | $> 0$ | Scales the standard deviation for band width |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function vwapbands(source[], volume[], reset[], multiplier):
|
||||
sum_pv = 0, sum_vol = 0, sum_pv2 = 0, count = 0
|
||||
|
||||
for each bar t:
|
||||
price = source[t]
|
||||
vol = volume[t]
|
||||
|
||||
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
|
||||
|
||||
vwap = sum_vol > 0 ? sum_pv / sum_vol : price
|
||||
|
||||
variance = 0
|
||||
if sum_vol > 0 and count > 1:
|
||||
variance = max(0, sum_pv2 / sum_vol - vwap * vwap)
|
||||
|
||||
stddev = sqrt(variance)
|
||||
|
||||
upper1 = vwap + multiplier * stddev
|
||||
lower1 = vwap - multiplier * stddev
|
||||
upper2 = vwap + 2 * multiplier * stddev
|
||||
lower2 = vwap - 2 * multiplier * stddev
|
||||
|
||||
emit (vwap, upper1, lower1, upper2, lower2, stddev)
|
||||
```
|
||||
|
||||
### Statistical Zone Interpretation
|
||||
|
||||
| Zone | Coverage | Interpretation |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# VWAPSD: VWAP with Standard Deviation Bands
|
||||
|
||||
> *Standard deviation bands around VWAP measure institutional consensus — proximity signals fair value, distance signals opportunity.*
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
@@ -84,40 +86,6 @@ Streaming: $O(1)$ per bar. Three additions to running sums, one division, one sq
|
||||
|--------|------|---------|------------|-------------|
|
||||
| $k$ | numDevs | 2.0 | $0.1$ – $5.0$ | Number of standard deviations for bands |
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function vwapsd(source[], volume[], reset[], numDevs):
|
||||
sum_pv = 0, sum_vol = 0, sum_pv2 = 0
|
||||
|
||||
for each bar t:
|
||||
price = source[t]
|
||||
vol = volume[t]
|
||||
|
||||
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
|
||||
|
||||
vwap = sum_vol > 0 ? sum_pv / sum_vol : price
|
||||
|
||||
variance = sum_vol > 0 ? sum_pv2 / sum_vol - vwap * vwap : 0
|
||||
stddev = sqrt(max(0, variance))
|
||||
|
||||
upper = vwap + numDevs * stddev
|
||||
lower = vwap - numDevs * stddev
|
||||
|
||||
emit (vwap, upper, lower)
|
||||
```
|
||||
|
||||
### VWAPSD vs VWAPBANDS
|
||||
|
||||
| Aspect | VWAPSD | VWAPBANDS |
|
||||
|
||||
Reference in New Issue
Block a user