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:
Miha Kralj
2026-03-11 20:21:52 -07:00
parent 56b86bebfb
commit 33d20f2a18
437 changed files with 4589 additions and 2792 deletions
-2
View File
@@ -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
+2 -17
View File
@@ -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 |
+2 -20
View File
@@ -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:
+2 -24
View File
@@ -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
View File
@@ -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 |
+2 -27
View File
@@ -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 |
+2 -30
View File
@@ -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 |
+2 -23
View File
@@ -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 |
+2 -37
View File
@@ -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
View File
@@ -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 |
+2 -31
View File
@@ -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 |
+2 -27
View File
@@ -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 |
+2 -19
View File
@@ -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 |
+2
View File
@@ -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 |
+2 -35
View File
@@ -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 |
+2 -39
View File
@@ -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 |
+2 -38
View File
@@ -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 |
+2 -45
View File
@@ -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 |
+2 -56
View File
@@ -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 |
+2 -44
View File
@@ -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 |
+2 -41
View File
@@ -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.
+2 -49
View File
@@ -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 |
+2 -42
View File
@@ -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 |
+2 -34
View File
@@ -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 |