diff --git a/index.html b/index.html index b4a494f6..ba6506d3 100644 --- a/index.html +++ b/index.html @@ -99,7 +99,7 @@ padding-bottom: 0.3em; } - .markdown-section h2, + .markdown-section h2, .markdown-section h3, .markdown-section h4, .markdown-section h5, @@ -113,6 +113,10 @@ padding-bottom: 0.3em; } + .markdown-section h3 { + font-size: 1.3em; + } + .markdown-section p, .markdown-section ul, .markdown-section ol, @@ -196,8 +200,10 @@ } /* Pine Script syntax highlighting — GitHub dark palette */ + .token.comment, + .token.block-comment, .token.doctype, - .token.annotation { color: #8b949e; font-style: italic; } + .token.annotation { color: green; } .token.type-definition { color: #79c0ff; } .token.class-name { color: #d2a8ff; } .token.constant { color: #ffa657; } diff --git a/lib/channels/_index.md b/lib/channels/_index.md index 715e0b57..052652ed 100644 --- a/lib/channels/_index.md +++ b/lib/channels/_index.md @@ -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 diff --git a/lib/channels/aberr/aberr.md b/lib/channels/aberr/aberr.md index aeb492fd..bdb00620 100644 --- a/lib/channels/aberr/aberr.md +++ b/lib/channels/aberr/aberr.md @@ -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 | diff --git a/lib/channels/accbands/accbands.md b/lib/channels/accbands/accbands.md index 1aaf7217..df19a406 100644 --- a/lib/channels/accbands/accbands.md +++ b/lib/channels/accbands/accbands.md @@ -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: diff --git a/lib/channels/apchannel/apchannel.md b/lib/channels/apchannel/apchannel.md index ea9ece7a..4783321f 100644 --- a/lib/channels/apchannel/apchannel.md +++ b/lib/channels/apchannel/apchannel.md @@ -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 | diff --git a/lib/channels/apz/apz.md b/lib/channels/apz/apz.md index 59ff954b..32688c1a 100644 --- a/lib/channels/apz/apz.md +++ b/lib/channels/apz/apz.md @@ -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 | diff --git a/lib/channels/atrbands/atrbands.md b/lib/channels/atrbands/atrbands.md index 960e596e..238b07dd 100644 --- a/lib/channels/atrbands/atrbands.md +++ b/lib/channels/atrbands/atrbands.md @@ -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 | diff --git a/lib/channels/bbands/bbands.md b/lib/channels/bbands/bbands.md index dd4f2485..b1e1536c 100644 --- a/lib/channels/bbands/bbands.md +++ b/lib/channels/bbands/bbands.md @@ -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 | diff --git a/lib/channels/dchannel/dchannel.md b/lib/channels/dchannel/dchannel.md index c1609e8d..6d7a20c1 100644 --- a/lib/channels/dchannel/dchannel.md +++ b/lib/channels/dchannel/dchannel.md @@ -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 | diff --git a/lib/channels/decaychannel/decaychannel.md b/lib/channels/decaychannel/decaychannel.md index b9575b44..6002326e 100644 --- a/lib/channels/decaychannel/decaychannel.md +++ b/lib/channels/decaychannel/decaychannel.md @@ -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 | diff --git a/lib/channels/fcb/fcb.md b/lib/channels/fcb/fcb.md index 22a5baac..35f31bec 100644 --- a/lib/channels/fcb/fcb.md +++ b/lib/channels/fcb/fcb.md @@ -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 | diff --git a/lib/channels/jbands/jbands.md b/lib/channels/jbands/jbands.md index 3cdff8f7..6a2990b9 100644 --- a/lib/channels/jbands/jbands.md +++ b/lib/channels/jbands/jbands.md @@ -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 | diff --git a/lib/channels/kchannel/kchannel.md b/lib/channels/kchannel/kchannel.md index dac5f2c0..d7fd1888 100644 --- a/lib/channels/kchannel/kchannel.md +++ b/lib/channels/kchannel/kchannel.md @@ -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 | diff --git a/lib/channels/maenv/maenv.md b/lib/channels/maenv/maenv.md index 730dfc8e..bfc68bbf 100644 --- a/lib/channels/maenv/maenv.md +++ b/lib/channels/maenv/maenv.md @@ -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 | diff --git a/lib/channels/mmchannel/mmchannel.md b/lib/channels/mmchannel/mmchannel.md index f15676b3..cd5325a9 100644 --- a/lib/channels/mmchannel/mmchannel.md +++ b/lib/channels/mmchannel/mmchannel.md @@ -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 | diff --git a/lib/channels/pchannel/pchannel.md b/lib/channels/pchannel/pchannel.md index 36b17e23..cb996a9e 100644 --- a/lib/channels/pchannel/pchannel.md +++ b/lib/channels/pchannel/pchannel.md @@ -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 | diff --git a/lib/channels/regchannel/regchannel.md b/lib/channels/regchannel/regchannel.md index 150b88e3..d6855c5d 100644 --- a/lib/channels/regchannel/regchannel.md +++ b/lib/channels/regchannel/regchannel.md @@ -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 | diff --git a/lib/channels/sdchannel/sdchannel.md b/lib/channels/sdchannel/sdchannel.md index 522e5dd3..7d9243da 100644 --- a/lib/channels/sdchannel/sdchannel.md +++ b/lib/channels/sdchannel/sdchannel.md @@ -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 | diff --git a/lib/channels/starchannel/starchannel.md b/lib/channels/starchannel/starchannel.md index 64304735..435a79da 100644 --- a/lib/channels/starchannel/starchannel.md +++ b/lib/channels/starchannel/starchannel.md @@ -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 | diff --git a/lib/channels/stbands/stbands.md b/lib/channels/stbands/stbands.md index dd2ebd9d..86799bf2 100644 --- a/lib/channels/stbands/stbands.md +++ b/lib/channels/stbands/stbands.md @@ -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 | diff --git a/lib/channels/ttm_lrc/TtmLrc.md b/lib/channels/ttm_lrc/TtmLrc.md index 57914eb6..416fc542 100644 --- a/lib/channels/ttm_lrc/TtmLrc.md +++ b/lib/channels/ttm_lrc/TtmLrc.md @@ -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 | diff --git a/lib/channels/ubands/ubands.md b/lib/channels/ubands/ubands.md index e5b77ca2..56ff5c2a 100644 --- a/lib/channels/ubands/ubands.md +++ b/lib/channels/ubands/ubands.md @@ -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. diff --git a/lib/channels/uchannel/uchannel.md b/lib/channels/uchannel/uchannel.md index 54d7f413..d1b7a284 100644 --- a/lib/channels/uchannel/uchannel.md +++ b/lib/channels/uchannel/uchannel.md @@ -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 | diff --git a/lib/channels/vwapbands/vwapbands.md b/lib/channels/vwapbands/vwapbands.md index 1770e885..f25dd38b 100644 --- a/lib/channels/vwapbands/vwapbands.md +++ b/lib/channels/vwapbands/vwapbands.md @@ -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 | diff --git a/lib/channels/vwapsd/vwapsd.md b/lib/channels/vwapsd/vwapsd.md index 6e701b3e..5b011de5 100644 --- a/lib/channels/vwapsd/vwapsd.md +++ b/lib/channels/vwapsd/vwapsd.md @@ -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 | diff --git a/lib/core/_index.md b/lib/core/_index.md index 2929ab4f..f112e2df 100644 --- a/lib/core/_index.md +++ b/lib/core/_index.md @@ -34,4 +34,3 @@ All Core indicators share common traits: | TBar | AVGPRICE, MEDPRICE, MIDPRICE, MIDBODY, TYPPRICE, WCLPRICE | OHLCV bars | | TValue | MIDPOINT | Single value series | - diff --git a/lib/core/avgprice/Avgprice.md b/lib/core/avgprice/Avgprice.md index 7c17cc0a..296f687a 100644 --- a/lib/core/avgprice/Avgprice.md +++ b/lib/core/avgprice/Avgprice.md @@ -1,5 +1,7 @@ # AVGPRICE: Average Price +> *The four-point average distills an entire bar into a single representative price — open, high, low, and close in equal measure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -62,22 +64,6 @@ $O(1)$ per bar. Two additions, one FMA. No memory allocation. Always hot after t | Typical Price | $(H+L+C) \times \frac{1}{3}$ | `HLC3` | `Typprice` | | Weighted Close | $(H+L+2C) \times 0.25$ | `HLCC4` | `Wclprice` | -### Pseudo-code - -``` -function AVGPRICE(bar): - o, h, l, c ← bar.Open, bar.High, bar.Low, bar.Close - - // Substitute last-valid for non-finite inputs - if !finite(o): o ← lastValidOpen - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - if !finite(c): c ← lastValidClose - - result ← FMA(o + h, 0.25, (l + c) × 0.25) - return result -``` - ### Output Interpretation | Context | Meaning | diff --git a/lib/core/ha/Ha.md b/lib/core/ha/Ha.md index 9d0972fa..f07371d9 100644 --- a/lib/core/ha/Ha.md +++ b/lib/core/ha/Ha.md @@ -1,5 +1,7 @@ # HA: Heikin-Ashi +> *The trend is your friend — but only if the noise doesn't make you abandon it at the first bump.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -16,8 +18,6 @@ - Requires `1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend — but only if the noise doesn't make you abandon it at the first bump." — Every trader, eventually - HA transforms standard OHLC bars into smoothed Heikin-Ashi candles by averaging each component with its predecessor. The Close is the bar's four-price mean $(O+H+L+C)/4$, the Open is a recursive midpoint of the prior HA Open and HA Close, and High/Low are clamped extremes that guarantee the HA body always fits inside the HA wick. Unlike most indicators that reduce a bar to a single scalar, HA outputs a complete `TBar` — four smoothed prices per bar — making it a bar-to-bar transform rather than a bar-to-value reduction. The recursive Open gives HA an IIR character: each bar carries a decaying memory of the entire price history, which is what flattens trend noise but also why HA prices do not match any actual traded price. ## Historical Context @@ -100,31 +100,6 @@ $$\text{WarmupPeriod} = 1$$ HA is "hot" from bar 1. The seed bar uses $(O_0 + C_0)/2$ for HA_Open and produces valid output immediately. The recursive filter converges rapidly due to the $\beta = 0.5$ decay — after 7 bars, the contribution of the seed value is less than 0.4%. -### Pseudo-code - -``` -function HA(bar, prevHaOpen, prevHaClose): - o, h, l, c ← bar.Open, bar.High, bar.Low, bar.Close - - // Substitute last-valid for non-finite inputs - if !finite(o): o ← lastValidOpen - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - if !finite(c): c ← lastValidClose - - haClose ← FMA(o + h, 0.25, (l + c) × 0.25) - - if firstBar: - haOpen ← (o + c) × 0.5 - else: - haOpen ← (prevHaOpen + prevHaClose) × 0.5 - - haHigh ← max(h, haOpen, haClose) - haLow ← min(l, haOpen, haClose) - - return TBar(bar.Time, haOpen, haHigh, haLow, haClose, bar.Volume) -``` - ### Output Interpretation | Candle Pattern | Meaning | diff --git a/lib/core/medprice/Medprice.md b/lib/core/medprice/Medprice.md index 24c7ac3a..735b1813 100644 --- a/lib/core/medprice/Medprice.md +++ b/lib/core/medprice/Medprice.md @@ -1,5 +1,7 @@ # MEDPRICE: Median Price +> *The midpoint of high and low captures the bar's central tendency, ignoring where it opened or closed.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -62,20 +64,6 @@ $O(1)$ per bar. One addition, one multiply. No memory allocation. Always hot aft | AVGPRICE | O, H, L, C | Equal | Fully balanced | | WCLPRICE | H, L, C | C double-weighted | Close-biased | -### Pseudo-code - -``` -function MEDPRICE(bar): - h, l ← bar.High, bar.Low - - // Substitute last-valid for non-finite inputs - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - - result ← (h + l) × 0.5 - return result -``` - ### Output Interpretation | Context | Meaning | diff --git a/lib/core/midbody/Midbody.md b/lib/core/midbody/Midbody.md index 55f5e366..192af211 100644 --- a/lib/core/midbody/Midbody.md +++ b/lib/core/midbody/Midbody.md @@ -1,5 +1,7 @@ # MIDBODY: Open-Close Average +> *The average of open and close reveals the candle body's center — where intent met execution.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -70,13 +72,6 @@ None. OC2 is parameterless. | AVGPRICE | $(O+H+L+C) \times 0.25$ | O, H, L, C | Fully balanced | | WCLPRICE | $(H+L+2C) \times 0.25$ | H, L, C | Close double-weighted | -### Pseudo-code - -```text -function Midbody(bar): - return (bar.Open + bar.Close) * 0.5 -``` - ### Output Interpretation - OC2 > Close: bar closed below its midpoint (bearish lean) @@ -119,9 +114,3 @@ The batch loop is a trivial element-wise `(a[i] + b[i]) * 0.5`. Auto-vectorizati - **Skender.Stock.Indicators** `CandlePart.OC2` enum documentation. - **Murphy, J.J.** *Technical Analysis of the Financial Markets*. New York Institute of Finance, 1999. - - - - - - diff --git a/lib/core/midpoint/Midpoint.md b/lib/core/midpoint/Midpoint.md index ef59b11d..f8283ac0 100644 --- a/lib/core/midpoint/Midpoint.md +++ b/lib/core/midpoint/Midpoint.md @@ -1,5 +1,7 @@ # MIDPOINT: Rolling Range Midpoint +> *The center holds, but only for the window you're watching.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The center holds, but only for the window you're watching." — Statistical folk wisdom - Single-series rolling midpoint: `(Highest(V, N) + Lowest(V, N)) * 0.5`. Returns the center of the value range within a lookback window. TA-Lib compatible (`MIDPOINT` function). Unlike MIDPRICE which operates on separate High/Low bar channels, MIDPOINT operates on a single value series. ## Historical Context diff --git a/lib/core/midprice/Midprice.md b/lib/core/midprice/Midprice.md index c4456fe3..bb5b7d35 100644 --- a/lib/core/midprice/Midprice.md +++ b/lib/core/midprice/Midprice.md @@ -1,5 +1,7 @@ # MIDPRICE: Midpoint Price over Period +> *The midpoint of highest high and lowest low over a period anchors price to its range center.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -71,25 +73,6 @@ $O(N)$ per bar where $N$ is the period, due to linear scan for max/min. For typi | MEDPRICE | $(H + L) \times 0.5$ | TBar (single bar) | Stateless | | Donchian Mid | Same as MIDPRICE | TBar (H/L channels) | Rolling window | -### Pseudo-code - -``` -function MIDPRICE(bar, period): - validate: period ≥ 1 - - h, l ← bar.High, bar.Low - - // Substitute last-valid for non-finite inputs - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - - highBuffer.Add(h) - lowBuffer.Add(l) - - result ← (highBuffer.Max() + lowBuffer.Min()) × 0.5 - return result -``` - ### Output Interpretation | Context | Meaning | diff --git a/lib/core/typprice/Typprice.md b/lib/core/typprice/Typprice.md index 486c77c6..c763bd47 100644 --- a/lib/core/typprice/Typprice.md +++ b/lib/core/typprice/Typprice.md @@ -1,5 +1,7 @@ # TYPPRICE: Typical Price +> *Typical price weights high, low, and close equally — a three-point summary that drops the open and keeps the essential.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -59,23 +61,6 @@ $O(1)$ per bar. One addition, one FMA. No memory allocation. Always hot after th Division by a non-power-of-two constant is 4-5x more expensive than multiplication on modern x86 CPUs (~15 cycles vs ~3 cycles). Precomputing $\frac{1}{3}$ as a `const double` and multiplying eliminates the division entirely. The compiler constant-folds `1.0 / 3.0` to the IEEE 754 double `0x3FD5555555555555` at compile time, so the hot path sees only multiply/FMA operations. -### Pseudo-code - -```text -function TYPPRICE(bar): - const OneThird ← 1.0 / 3.0 // compile-time constant - - o, h, l ← bar.Open, bar.High, bar.Low - - // Substitute last-valid for non-finite inputs - if !finite(o): o ← lastValidOpen - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - - result ← FMA(o, OneThird, (h + l) × OneThird) - return result -``` - ### Output Interpretation | Context | Meaning | diff --git a/lib/core/wclprice/Wclprice.md b/lib/core/wclprice/Wclprice.md index a2b4bf1c..e7f76e5b 100644 --- a/lib/core/wclprice/Wclprice.md +++ b/lib/core/wclprice/Wclprice.md @@ -1,5 +1,7 @@ # WCLPRICE: Weighted Close Price +> *Weighted close doubles the closing price's vote, acknowledging that where a bar ends matters most.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Core | @@ -66,21 +68,6 @@ $O(1)$ per bar. One addition, one FMA. No memory allocation. Always hot after th | TYPPRICE | 0% | 33.3% | 33.3% | 33.3% | | **WCLPRICE** | **0%** | **25%** | **25%** | **50%** | -### Pseudo-code - -``` -function WCLPRICE(bar): - h, l, c ← bar.High, bar.Low, bar.Close - - // Substitute last-valid for non-finite inputs - if !finite(h): h ← lastValidHigh - if !finite(l): l ← lastValidLow - if !finite(c): c ← lastValidClose - - result ← FMA(c, 0.5, (h + l) × 0.25) - return result -``` - ### Output Interpretation | Context | Meaning | diff --git a/lib/cycles/_index.md b/lib/cycles/_index.md index f70f6aeb..d8b1b47b 100644 --- a/lib/cycles/_index.md +++ b/lib/cycles/_index.md @@ -1,7 +1,5 @@ # Cycles -> "The market is a discounting mechanism that anticipates cycles before they complete." Unknown - Cycle analysis identifies repeating patterns in price data. John Ehlers pioneered digital signal processing techniques for financial cycles, using Hilbert transforms and autocorrelation to detect dominant periods. Cycles exist but are non-stationary: period and amplitude shift over time. ## Indicators diff --git a/lib/cycles/ccor/Ccor.md b/lib/cycles/ccor/Ccor.md index 654efc3f..fa61e1f2 100644 --- a/lib/cycles/ccor/Ccor.md +++ b/lib/cycles/ccor/Ccor.md @@ -1,5 +1,7 @@ # CCOR: Ehlers Correlation Cycle +> *Correlation cycles reveal hidden periodicities by measuring how well price correlates with a rotating reference wave.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -94,48 +96,6 @@ 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 | diff --git a/lib/cycles/ccyc/Ccyc.md b/lib/cycles/ccyc/Ccyc.md index b4c1baee..4e64a4b2 100644 --- a/lib/cycles/ccyc/Ccyc.md +++ b/lib/cycles/ccyc/Ccyc.md @@ -1,5 +1,7 @@ # CCYC: Ehlers Cyber Cycle +> *The Cyber Cycle isolator extracts the dominant cycle component while suppressing trend — pure periodicity distilled.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -109,33 +111,6 @@ $$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 | diff --git a/lib/cycles/cg/cg.md b/lib/cycles/cg/cg.md index 0bc8298d..1f7ee02a 100644 --- a/lib/cycles/cg/cg.md +++ b/lib/cycles/cg/cg.md @@ -1,5 +1,7 @@ # CG: Ehlers Center of Gravity +> *Center of Gravity locates the balance point of price over a window, anticipating turns before they arrive.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -54,31 +56,6 @@ Streaming uses running sums for both numerator and denominator: $O(1)$ per bar w |-----------|-------------|---------|------------| | `period` | Lookback window length | 10 | $> 0$ | -### Pseudo-code - -``` -function CG(source, period): - buffer ← RingBuffer(period) - runNum ← 0 // weighted sum - runDen ← 0 // simple sum - - for each price in source: - buffer.Add(price) - if buffer.Count < period: continue - - // 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] - - cg = (den ≠ 0) ? (num / den) - (period + 1) / 2.0 : 0 - - emit cg -``` - ### Output Interpretation | Condition | Meaning | diff --git a/lib/cycles/dsp/dsp.md b/lib/cycles/dsp/dsp.md index 924711a2..8e024042 100644 --- a/lib/cycles/dsp/dsp.md +++ b/lib/cycles/dsp/dsp.md @@ -1,5 +1,7 @@ # DSP: Ehlers Detrended Synthetic Price +> *Detrended synthetic price removes the trend to expose the oscillation underneath — the signal beneath the drift.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -66,34 +68,6 @@ $O(1)$ per bar with $O(1)$ memory. Two EMA state variables plus two bias correct |-----------|-------------|---------|------------| | `period` | Dominant cycle period | 40 | $\geq 4$ | -### Pseudo-code - -``` -function DSP(source, period): - pFast ← max(2, round(period / 4)) - pSlow ← max(3, round(period / 2)) - αFast ← 2 / (pFast + 1) - αSlow ← 2 / (pSlow + 1) - - 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 -``` - ### Output Interpretation | Condition | Meaning | diff --git a/lib/cycles/eacp/eacp.md b/lib/cycles/eacp/eacp.md index 8c5acf4c..3efda665 100644 --- a/lib/cycles/eacp/eacp.md +++ b/lib/cycles/eacp/eacp.md @@ -1,5 +1,7 @@ # EACP: Ehlers Autocorrelation Periodogram +> *Autocorrelation periodogram scans every possible cycle length and ranks them by strength — a spectral fingerprint of the market.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -72,51 +74,6 @@ $O(N \times M)$ per bar where $N$ is the period range and $M$ is the averaging l | `maxPeriod` | Maximum period to evaluate | 48 | $> minPeriod$ | | `enhance` | Apply cubic emphasis to spectral peaks | true | | -### Pseudo-code - -``` -function EACP(source, minPeriod, maxPeriod, enhance): - N ← maxPeriod - minPeriod + 1 - M ← maxPeriod // averaging window - hpBuf ← HighPassFilter(source) - ssfBuf ← SuperSmoother(hpBuf) - - 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 -``` - ### Output Interpretation | Output | Meaning | diff --git a/lib/cycles/ebsw/ebsw.md b/lib/cycles/ebsw/ebsw.md index fa577903..aae7a6a3 100644 --- a/lib/cycles/ebsw/ebsw.md +++ b/lib/cycles/ebsw/ebsw.md @@ -1,5 +1,7 @@ # EBSW: Ehlers Even Better Sinewave +> *Even Better Sinewave refines cycle detection by combining bandpass filtering with adaptive gain normalization.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -78,45 +80,6 @@ b = 2·a·cos(√2·π / ssfLength) c₁ = (1 - b + a²) / 2 ``` -### Pseudo-code - -``` -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 | diff --git a/lib/cycles/homod/homod.md b/lib/cycles/homod/homod.md index 37d21ea6..91692bfe 100644 --- a/lib/cycles/homod/homod.md +++ b/lib/cycles/homod/homod.md @@ -1,5 +1,7 @@ # HOMOD: Ehlers Homodyne Discriminator +> *The homodyne discriminator locks onto a cycle's frequency by comparing successive analytic signal rotations.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -71,60 +73,6 @@ $O(1)$ per bar with $O(1)$ memory. The pipeline consists entirely of fixed-depth | `minPeriod` | Minimum detectable period | 6.0 | $> 0$ | | `maxPeriod` | Maximum detectable period | 50.0 | $> minPeriod$ | -### Pseudo-code - -``` -function HOMOD(source, minPeriod, maxPeriod): - A ← 0.0962; B ← 0.5769 - smoothBuf ← CircularBuffer(7) - detBuf ← CircularBuffer(7) - - I2_prev ← 0; Q2_prev ← 0 - Re_prev ← 0; Im_prev ← 0 - period_prev ← (minPeriod + maxPeriod) / 2 - - for each price in source: - // 4-bar WMA - smooth ← (4·price + 3·p[1] + 2·p[2] + p[3]) / 10 - smoothBuf.Add(smooth) - - // Detrender (Hilbert FIR) - det ← A·smooth[0] + B·smooth[2] - B·smooth[4] - A·smooth[6] - detBuf.Add(det) - - // 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 -``` - ### Output Interpretation | Output | Meaning | diff --git a/lib/cycles/ht_dcperiod/HtDcperiod.md b/lib/cycles/ht_dcperiod/HtDcperiod.md index 15e683fa..ceede2c9 100644 --- a/lib/cycles/ht_dcperiod/HtDcperiod.md +++ b/lib/cycles/ht_dcperiod/HtDcperiod.md @@ -1,5 +1,7 @@ # HT_DCPERIOD: Ehlers Hilbert Transform Dominant Cycle Period +> *The Hilbert Transform extracts the dominant cycle period by converting price into an analytic signal and measuring its phase rate.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -68,47 +70,6 @@ $O(1)$ per bar. Fixed Hilbert cascade with circular buffers totaling approximate The period range [6, 50] and all smoothing constants are fixed by the TA-Lib specification. -### Pseudo-code - -``` -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 -``` - ### Output Interpretation | Output | Meaning | diff --git a/lib/cycles/ht_dcphase/HtDcphase.md b/lib/cycles/ht_dcphase/HtDcphase.md index 9ce26e9a..f0dd6407 100644 --- a/lib/cycles/ht_dcphase/HtDcphase.md +++ b/lib/cycles/ht_dcphase/HtDcphase.md @@ -1,5 +1,7 @@ # HT_DCPHASE: Ehlers Hilbert Transform Dominant Cycle Phase +> *Dominant cycle phase tracks where price sits within its current cycle — the angular position of the market's heartbeat.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -64,38 +66,6 @@ $O(P)$ per bar where $P$ is the smoothed period (typically 6-50), due to the DFT All internal constants are fixed by the TA-Lib specification. -### Pseudo-code - -``` -function HT_DCPHASE(source): - // Same Hilbert cascade as HT_DCPERIOD - // ... (WMA smooth, Hilbert FIR, phasor, homodyne) - // Produces: smoothPeriod, smoothPriceBuf - - for each bar (after warmup): - P ← round(smoothPeriod) - - // 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] - - // 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 -``` - ### Phase Quadrant Interpretation | Phase Range | Cycle Position | diff --git a/lib/cycles/ht_phasor/HtPhasor.md b/lib/cycles/ht_phasor/HtPhasor.md index f80259cb..3668450a 100644 --- a/lib/cycles/ht_phasor/HtPhasor.md +++ b/lib/cycles/ht_phasor/HtPhasor.md @@ -1,5 +1,7 @@ # HT_PHASOR: Ehlers Hilbert Transform Phasor Components +> *Phasor components decompose price into in-phase and quadrature parts, mapping the cycle as a rotating vector.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -58,37 +60,6 @@ $O(1)$ per bar. Fixed Hilbert cascade with circular buffers. Warmup: 32 bars (TA |-----------|-------------|---------|------------| | (none) | No user-configurable parameters | | | -### Pseudo-code - -``` -function HT_PHASOR(source): - A ← 0.0962; B ← 0.5769 - smoothBuf ← CircularBuffer(7) - detBuf, q1Buf, i1Buf ← CircularBuffers - - I2 ← 0; Q2 ← 0 - - for each price in source: - // WMA smooth - smooth ← (4·price + 3·p[1] + 2·p[2] + p[3]) / 10 - smoothBuf.Add(smooth) - - // 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] - - // 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 -``` - ### Phasor Crossover Signals | Condition | Signal | diff --git a/lib/cycles/ht_sine/HtSine.md b/lib/cycles/ht_sine/HtSine.md index 14462834..582d17cb 100644 --- a/lib/cycles/ht_sine/HtSine.md +++ b/lib/cycles/ht_sine/HtSine.md @@ -1,5 +1,7 @@ # HT_SINE: Ehlers Hilbert Transform SineWave (also known as SINE) +> *The Hilbert sine wave renders cycle timing visible — crossovers of sine and lead-sine mark turning points.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -70,27 +72,6 @@ $O(P)$ per bar where $P$ is the smoothed period (typically 6-50), due to the DFT All constants are fixed by the TA-Lib specification. -### Pseudo-code - -``` -function HT_SINE(source): - // Full Hilbert cascade (same as HT_DCPHASE) - // Produces: smoothPeriod, smoothPriceBuf, dcPhase - - for each bar (after warmup): - // Phase from DFT accumulation (see HT_DCPHASE) - φ ← computeDCPhase(smoothPeriod, smoothPriceBuf) - - // Convert phase to radians - φ_rad ← φ · (π / 180) - - // Dual sine output - sine ← sin(φ_rad) - leadSine ← sin(φ_rad + π/4) // 45° lead - - emit sine, leadSine -``` - ### Crossover Signals | Pattern | Signal | diff --git a/lib/cycles/lunar/Lunar.md b/lib/cycles/lunar/Lunar.md index 8de420b7..76db6131 100644 --- a/lib/cycles/lunar/Lunar.md +++ b/lib/cycles/lunar/Lunar.md @@ -1,5 +1,7 @@ # LUNAR: Lunar Phase Indicator +> *The lunar cycle maps the Moon's phase onto price — an ancient rhythm tested against modern markets.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -82,41 +84,6 @@ $O(1)$ per timestamp. No state required (deterministic from time). Zero warmup. The calculation is entirely determined by the input timestamp. -### Pseudo-code - -``` -function LUNAR(timestamp): - // Julian date - JD ← timestamp_to_unix_ms / 86400000 + 2440587.5 - T ← (JD - 2451545.0) / 36525.0 - - // 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) - - // 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 -``` - ### Output Interpretation | Value | Phase | diff --git a/lib/cycles/solar/Solar.md b/lib/cycles/solar/Solar.md index 64147fbd..853e262b 100644 --- a/lib/cycles/solar/Solar.md +++ b/lib/cycles/solar/Solar.md @@ -1,5 +1,7 @@ # SOLAR: Solar Cycle Indicator +> *Solar cycles encode the Sun's rhythmic activity into a tradeable signal, bridging astrophysics and price action.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -74,36 +76,6 @@ $O(1)$ per timestamp. No state required. Zero warmup. The tropical year is appro The calculation is entirely determined by the input timestamp. -### Pseudo-code - -``` -function SOLAR(timestamp): - // Julian date - JD ← timestamp_to_unix_ms / 86400000 + 2440587.5 - T ← (JD - 2451545.0) / 36525.0 - - // Geometric mean longitude - L0 ← FMA(T, FMA(T, 0.0003032, 36000.76983), 280.46646) - L0 ← mod(L0, 360) - - // 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 -``` - ### Seasonal Correspondence (Northern Hemisphere) | Date (approx.) | $\lambda_{Sun}$ | Solar Value | Season | diff --git a/lib/cycles/ssfdsp/Ssfdsp.md b/lib/cycles/ssfdsp/Ssfdsp.md index 580d2b77..dccf2d92 100644 --- a/lib/cycles/ssfdsp/Ssfdsp.md +++ b/lib/cycles/ssfdsp/Ssfdsp.md @@ -1,5 +1,7 @@ # SSFDSP: Ehlers SSF Detrended Synthetic Price +> *SSF-based detrended synthetic price applies a super smoother before extracting cycles, achieving cleaner periodicity isolation.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Cycle | @@ -70,50 +72,6 @@ $O(1)$ per bar. Two independent 2-pole IIR filters with $O(1)$ memory. Warmup: a 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. -### Pseudo-code - -``` -function SSFDSP(source, period): - pFast ← max(2, round(period / 4)) - pSlow ← max(3, round(period / 2)) - - // Fast SSF coefficients - αf ← √2·π / pFast - c2f ← 2·exp(-αf)·cos(αf) - c3f ← -exp(-2·αf) - c1f ← 1 - c2f - c3f - - // 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 -``` - ### DSP vs SSFDSP | Aspect | DSP (EMA-based) | SSFDSP (Super-Smoother) | diff --git a/lib/dynamics/_index.md b/lib/dynamics/_index.md index 5eb2d6b7..4abab565 100644 --- a/lib/dynamics/_index.md +++ b/lib/dynamics/_index.md @@ -1,7 +1,5 @@ # Dynamics -> "The trend is your friend, but only if you know its strength." Unknown - Dynamics indicators measure trend strength, speed, and direction. Unlike momentum indicators that measure rate of change, dynamics indicators answer: "Is there a trend, and how strong is it?" Critical for filtering signals and avoiding whipsaws in ranging markets. ## Indicators diff --git a/lib/dynamics/adx/Adx.md b/lib/dynamics/adx/Adx.md index c6fc3640..a6424274 100644 --- a/lib/dynamics/adx/Adx.md +++ b/lib/dynamics/adx/Adx.md @@ -1,5 +1,7 @@ # ADX: Average Directional Index +> *ADX measures trend strength without regard to direction — a compass that tells you how hard the wind blows, not where.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -78,49 +80,6 @@ $$ADX = \text{RMA}(DX, N)$$ |--------|-----------|---------|------------| | $N$ | period | 14 | $N \geq 2$ | -### Pseudo-code - -``` -Initialize: - α = 1 / period - smoothPlusDM = smoothMinusDM = smoothTR = 0 - adx = 0 - prevHigh = prevLow = NaN - bar_count = 0 - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - TR = max(high - low, |high - prevClose|, |low - prevClose|) - - upMove = high - prevHigh - downMove = prevLow - low - - +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 - -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 - - // Wilder smoothing (RMA) - smoothPlusDM = FMA(smoothPlusDM, 1 - α, α × +DM) - smoothMinusDM = FMA(smoothMinusDM, 1 - α, α × -DM) - smoothTR = FMA(smoothTR, 1 - α, α × TR) - - // Directional Indicators - +DI = 100 × smoothPlusDM / smoothTR - -DI = 100 × smoothMinusDM / smoothTR - - // Directional Index - diSum = +DI + -DI - DX = diSum > 0 ? 100 × |+DI - -DI| / diSum : 0 - - // Final smoothing - ADX = FMA(ADX, 1 - α, α × DX) - - prevHigh = high - prevLow = low - prevClose = close - output = ADX -``` - ### The Stability Problem 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. diff --git a/lib/dynamics/adxr/Adxr.md b/lib/dynamics/adxr/Adxr.md index d4714640..c4e2f45e 100644 --- a/lib/dynamics/adxr/Adxr.md +++ b/lib/dynamics/adxr/Adxr.md @@ -1,5 +1,7 @@ # ADXR: Average Directional Movement Rating +> *ADXR smooths ADX over time, filtering out momentary strength spikes to reveal the underlying trend conviction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -56,34 +58,6 @@ The $N-1$ lag (rather than $N$) matches TA-Lib's reference implementation exactl The period controls both the internal ADX calculation and the historical lookback depth. -### Pseudo-code - -``` -Initialize: - adx = new Adx(period) - adxBuffer = RingBuffer(period) - bar_count = 0 - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - // Full ADX pipeline - adxValue = adx.Update(high, low, close, isNew) - - // Store in history - adxBuffer.Add(adxValue) - bar_count++ - - // 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 - - output = ADXR -``` - ### Lag Analysis | Component | Lag Source | diff --git a/lib/dynamics/alligator/Alligator.md b/lib/dynamics/alligator/Alligator.md index fd9046f4..0e5069bd 100644 --- a/lib/dynamics/alligator/Alligator.md +++ b/lib/dynamics/alligator/Alligator.md @@ -1,5 +1,7 @@ # ALLIGATOR: Williams Alligator +> *Three displaced moving averages form jaw, teeth, and lips — when they diverge the Alligator feeds, when they converge it sleeps.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -71,40 +73,6 @@ The offsets shift plotted values forward in time for display purposes only. The | $N_l$ | lipsPeriod | 5 | $N_l \geq 1$ | | $O_l$ | lipsOffset | 3 | $O_l \geq 0$ | -### Pseudo-code - -``` -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 - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - source = (high + low + close) / 3.0 - - // SMMA updates with bias compensation - jaw = FMA(jaw, 1 - α_jaw, α_jaw × source) - e_jaw = e_jaw × (1 - α_jaw) - jaw_compensated = jaw / (1 - e_jaw) - - teeth = FMA(teeth, 1 - α_teeth, α_teeth × source) - e_teeth = e_teeth × (1 - α_teeth) - teeth_compensated = teeth / (1 - e_teeth) - - lips = FMA(lips, 1 - α_lips, α_lips × source) - e_lips = e_lips × (1 - α_lips) - lips_compensated = lips / (1 - e_lips) - - output: - Jaw = jaw_compensated (plot at bar + jawOffset) - Teeth = teeth_compensated (plot at bar + teethOffset) - Lips = lips_compensated (plot at bar + lipsOffset) -``` - ### Market Phase Detection | Phase | Line Configuration | Action | diff --git a/lib/dynamics/amat/Amat.md b/lib/dynamics/amat/Amat.md index 91e56bc7..b7f954b1 100644 --- a/lib/dynamics/amat/Amat.md +++ b/lib/dynamics/amat/Amat.md @@ -1,5 +1,7 @@ # AMAT: Archer Moving Averages Trends +> *Archer's moving average trends compare fast and slow averages, signaling when short-term momentum confirms the longer-term direction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -63,52 +65,6 @@ $$\text{Strength}_t = \frac{|\text{Fast}_t - \text{Slow}_t|}{\text{Slow}_t} \tim | $N_f$ | fastPeriod | 10 | $N_f \geq 1$ | | $N_s$ | slowPeriod | 50 | $N_s > N_f$ | -### Pseudo-code - -``` -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 - -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 -``` - ### Period Selection Guidelines | Use Case | Fast | Slow | Ratio | diff --git a/lib/dynamics/aroon/Aroon.md b/lib/dynamics/aroon/Aroon.md index de76d23a..fb199573 100644 --- a/lib/dynamics/aroon/Aroon.md +++ b/lib/dynamics/aroon/Aroon.md @@ -1,5 +1,7 @@ # AROON: Aroon Indicator +> *Aroon measures how recently the highest high and lowest low occurred — recency as a proxy for trend vitality.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -62,51 +64,6 @@ Range: $[-100, +100]$. |--------|-----------|---------|------------| | $N$ | period | 25 | $N \geq 1$ | -### Pseudo-code - -``` -Initialize: - highBuf = RingBuffer(period + 1) - lowBuf = RingBuffer(period + 1) - bar_count = 0 - -On each bar (high, low, isNew): - if !isNew: restore previous state - - highBuf.Add(high) - lowBuf.Add(low) - bar_count++ - - // 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 - - // 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 - - len = min(bar_count, period) - barsSinceHigh = len - maxIdx - barsSinceLow = len - minIdx - - AroonUp = (len - barsSinceHigh) / len × 100 - AroonDown = (len - barsSinceLow) / len × 100 - AroonOsc = AroonUp - AroonDown - - output: - Up = AroonUp - Down = AroonDown - Oscillator = AroonOsc -``` - ### Interpretation | Condition | Signal | diff --git a/lib/dynamics/aroonosc/AroonOsc.md b/lib/dynamics/aroonosc/AroonOsc.md index 274a9e45..d1d2426a 100644 --- a/lib/dynamics/aroonosc/AroonOsc.md +++ b/lib/dynamics/aroonosc/AroonOsc.md @@ -1,5 +1,7 @@ # AROONOSC: Aroon Oscillator +> *The Aroon oscillator subtracts Aroon Down from Aroon Up, collapsing two timing signals into a single directional gauge.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -56,36 +58,6 @@ $$\text{AroonOsc} = \text{AroonUp} - \text{AroonDown}$$ |--------|-----------|---------|------------| | $N$ | period | 25 | $N \geq 1$ | -### Pseudo-code - -``` -Initialize: - highBuf = RingBuffer(period + 1) - lowBuf = RingBuffer(period + 1) - bar_count = 0 - -On each bar (high, low, isNew): - if !isNew: restore previous state - - highBuf.Add(high) - lowBuf.Add(low) - bar_count++ - - len = min(bar_count, period) - - // 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 - - barsSinceHigh = len - maxIdx - barsSinceLow = len - minIdx - - AroonUp = (len - barsSinceHigh) / len × 100 - AroonDown = (len - barsSinceLow) / len × 100 - - output = AroonUp - AroonDown -``` - ### Drift Immunity 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. diff --git a/lib/dynamics/chop/Chop.md b/lib/dynamics/chop/Chop.md index 5f620063..27896b61 100644 --- a/lib/dynamics/chop/Chop.md +++ b/lib/dynamics/chop/Chop.md @@ -1,5 +1,7 @@ # CHOP: Choppiness Index +> *Choppiness index quantifies how range-bound a market is — high values mean sideways, low values mean trending.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -56,50 +58,6 @@ The denominator $\log_{10}(N)$ normalizes the output so that the theoretical max |--------|-----------|---------|------------| | $N$ | period | 14 | $N \geq 2$ | -### Pseudo-code - -``` -Initialize: - trBuf = RingBuffer(period) - highBuf = RingBuffer(period) - lowBuf = RingBuffer(period) - trSum = 0 - prevClose = NaN - logPeriod = log10(period) - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - // True Range - if prevClose is valid: - TR = max(high - low, |high - prevClose|, |low - prevClose|) - else: - TR = high - low - - // Rolling sum update - if trBuf is full: - trSum -= trBuf.Oldest - trBuf.Add(TR) - trSum += TR - - highBuf.Add(high) - lowBuf.Add(low) - - // Channel width - maxHigh = Max(highBuf) - minLow = Min(lowBuf) - channel = maxHigh - minLow - - // Choppiness Index - if channel > 0 AND trSum > 0: - CHOP = 100 × log10(trSum / channel) / logPeriod - else: - CHOP = 50 // neutral fallback - - prevClose = close - output = Clamp(CHOP, 0, 100) -``` - ### Interpretation | CHOP Value | Market Regime | Strategy Implication | diff --git a/lib/dynamics/dmx/Dmx.md b/lib/dynamics/dmx/Dmx.md index b06fcf54..96251ad2 100644 --- a/lib/dynamics/dmx/Dmx.md +++ b/lib/dynamics/dmx/Dmx.md @@ -1,5 +1,7 @@ # DMX: Directional Movement Index (Jurik) +> *Jurik's directional movement applies adaptive smoothing to DI lines, reducing whipsaws in the classic DMI framework.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -70,47 +72,6 @@ Positive values indicate bullish directional dominance; negative values indicate |--------|-----------|---------|------------| | $N$ | period | 14 | $N \geq 2$ | -### Pseudo-code - -``` -Initialize: - jmaPlusDM = new JMA(period) - jmaMinusDM = new JMA(period) - jmaTR = new JMA(period) - prevHigh = prevLow = prevClose = NaN - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - // Wilder's directional movement decomposition - TR = max(high - low, |high - prevClose|, |low - prevClose|) - - upMove = high - prevHigh - downMove = prevLow - low - - +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 - -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 - - // Jurik smoothing (replaces Wilder's RMA) - smoothPlusDM = jmaPlusDM.Update(+DM) - smoothMinusDM = jmaMinusDM.Update(-DM) - smoothTR = jmaTR.Update(TR) - - // Directional indicators - if smoothTR > 0: - DI_plus = 100 × smoothPlusDM / smoothTR - DI_minus = 100 × smoothMinusDM / smoothTR - else: - DI_plus = DI_minus = 0 - - DMX = DI_plus - DI_minus - - prevHigh = high - prevLow = low - prevClose = close - output = DMX -``` - ### DMX vs DMI Comparison | Property | DMI (Wilder) | DMX (Jurik) | diff --git a/lib/dynamics/dx/Dx.md b/lib/dynamics/dx/Dx.md index a76fa458..140ed80f 100644 --- a/lib/dynamics/dx/Dx.md +++ b/lib/dynamics/dx/Dx.md @@ -1,5 +1,7 @@ # DX: Directional Movement Index +> *Directional movement captures the difference between positive and negative thrust, normalized by true range.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -72,49 +74,6 @@ When $+DI + (-DI) = 0$ (no directional movement), DX = 0. |--------|-----------|---------|------------| | $N$ | period | 14 | $N \geq 2$ | -### Pseudo-code - -``` -Initialize: - α = 1 / period - smoothPlusDM = smoothMinusDM = smoothTR = 0 - prevHigh = prevLow = prevClose = NaN - -On each bar (high, low, close, isNew): - if !isNew: restore previous state - - // True Range - TR = max(high - low, |high - prevClose|, |low - prevClose|) - - upMove = high - prevHigh - downMove = prevLow - low - - +DM = (upMove > downMove AND upMove > 0) ? upMove : 0 - -DM = (downMove > upMove AND downMove > 0) ? downMove : 0 - - // Wilder smoothing - smoothPlusDM = FMA(smoothPlusDM, 1 - α, α × +DM) - smoothMinusDM = FMA(smoothMinusDM, 1 - α, α × -DM) - smoothTR = FMA(smoothTR, 1 - α, α × TR) - - // Directional Indicators - +DI = smoothTR > 0 ? 100 × smoothPlusDM / smoothTR : 0 - -DI = smoothTR > 0 ? 100 × smoothMinusDM / smoothTR : 0 - - // DX (raw, no final RMA) - diSum = +DI + -DI - DX = diSum > 0 ? 100 × |+DI - -DI| / diSum : 0 - - prevHigh = high - prevLow = low - prevClose = close - - output: - DX = DX // trend strength (0-100) - DiPlus = +DI // bullish directional indicator - DiMinus = -DI // bearish directional indicator -``` - ### DX vs ADX | Property | DX | ADX | diff --git a/lib/dynamics/ghla/Ghla.md b/lib/dynamics/ghla/Ghla.md index d5e16e2c..91445d9c 100644 --- a/lib/dynamics/ghla/Ghla.md +++ b/lib/dynamics/ghla/Ghla.md @@ -1,5 +1,7 @@ # GHLA: Gann High-Low Activator +> *The simplest indicators are the hardest to argue with. Two averages, one rule, and the market tells you which side of the fence to stand on.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest indicators are the hardest to argue with. Two averages, one rule, and the market tells you which side of the fence to stand on." - The Gann High-Low Activator (GHLA) is a trend-following stop/reversal indicator that alternates between the Simple Moving Average of Highs and the Simple Moving Average of Lows based on a three-state crossover rule. Developed by Robert Krausz and published in *Technical Analysis of Stocks & Commodities* (February 1998), the indicator produces a single trailing line: SMA(Low) during uptrends (acting as dynamic support) and SMA(High) during downtrends (acting as dynamic resistance). The flip between states occurs only when price closes decisively beyond the opposing SMA, creating a hysteresis zone that filters minor whipsaws. With a default period of 3 bars, GHLA responds aggressively to trend changes while requiring just $O(N)$ additions and one comparison per bar. ## Historical Context diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.md b/lib/dynamics/ht_trendmode/HtTrendmode.md index 2b42e3e7..11656cb9 100644 --- a/lib/dynamics/ht_trendmode/HtTrendmode.md +++ b/lib/dynamics/ht_trendmode/HtTrendmode.md @@ -1,5 +1,7 @@ # HT_TRENDMODE: Hilbert Transform Trend vs Cycle Mode +> *Hilbert trend mode classifies the market as trending or cycling — a binary answer from the analytic signal's behavior.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -89,66 +91,6 @@ Criterion 4: Price deviation override No user-configurable parameters. The algorithm self-tunes based on the detected dominant cycle period (clamped to 6-50 bars). -### Pseudo-code - -``` -Initialize: - circBuffer = array for Hilbert state - smoothPrice = priceHistory = arrays - daysInTrend = 0 - smoothPeriod = 0 - prevDcPhase = 0 - bar_count = 0 - -On each bar (price, isNew): - if !isNew: restore previous state - - // 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 -``` - ### Decision Criteria Summary | Criterion | Purpose | diff --git a/lib/dynamics/ichimoku/Ichimoku.md b/lib/dynamics/ichimoku/Ichimoku.md index 31b6ce66..fa0acaef 100644 --- a/lib/dynamics/ichimoku/Ichimoku.md +++ b/lib/dynamics/ichimoku/Ichimoku.md @@ -1,5 +1,7 @@ # ICHIMOKU: Ichimoku Kinko Hyo +> *Five lines, one cloud, and a time-shifted perspective — Ichimoku maps support, resistance, and momentum in a single glance.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -81,44 +83,6 @@ Confirms trend by comparing current price to the price from 26 bars ago. | $N_s$ | senkouBPeriod | 52 | 2 trading months | | $d$ | displacement | 26 | 1 trading month forward/back | -### 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 | Condition | Meaning | diff --git a/lib/dynamics/impulse/Impulse.md b/lib/dynamics/impulse/Impulse.md index f6ab90e5..dc90b289 100644 --- a/lib/dynamics/impulse/Impulse.md +++ b/lib/dynamics/impulse/Impulse.md @@ -1,5 +1,7 @@ # IMPULSE: Elder Impulse System +> *The Impulse System identifies inflection points where a trend speeds up or slows down.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -15,8 +17,6 @@ - Requires `Math.Max(emaPeriod, macdSlow) + macdSignal - 1` bars (default 34) of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 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 @@ -80,35 +80,6 @@ Both child indicators must be warmed up and at least two comparison values must | macdSlow | int | 26 | > macdFast | MACD slow EMA period | | macdSignal | int | 9 | > 0 | MACD signal smoothing period | -### Pseudo-code - -``` -IMPULSE(close, emaPeriod=13, macdFast=12, macdSlow=26, macdSignal=9): - - // Child indicator updates - ema_val = EMA.Update(close, emaPeriod) - macd_result = MACD.Update(close, macdFast, macdSlow, macdSignal) - hist_val = macd_result.Histogram - - // Slope computation (requires previous values) - ema_slope = sign(ema_val - prev_ema) - hist_slope = sign(hist_val - prev_hist) - - // 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 - - // State update - prev_ema = ema_val - prev_hist = hist_val - - return signal -``` - ### Derivative Interpretation The system combines two derivatives: diff --git a/lib/dynamics/minusdi/MinusDi.Quantower.Tests.cs b/lib/dynamics/minusdi/MinusDi.Quantower.Tests.cs new file mode 100644 index 00000000..e7e417c8 --- /dev/null +++ b/lib/dynamics/minusdi/MinusDi.Quantower.Tests.cs @@ -0,0 +1,73 @@ +using TradingPlatform.BusinessLayer; +using QuanTAlib; + +namespace QuanTAlib.Tests; + +public class MinusDiIndicatorTests +{ + [Fact] + public void MinusDiIndicator_Constructor_SetsDefaults() + { + var indicator = new MinusDiIndicator(); + + Assert.Equal(14, indicator.Period); + Assert.True(indicator.ShowColdValues); + Assert.Equal("-DI - Minus Directional Indicator", indicator.Name); + Assert.True(indicator.SeparateWindow); + Assert.True(indicator.OnBackGround); + } + + [Fact] + public void MinusDiIndicator_MinHistoryDepths_EqualsZero() + { + var indicator = new MinusDiIndicator { Period = 20 }; + + Assert.Equal(0, MinusDiIndicator.MinHistoryDepths); + IWatchlistIndicator watchlistIndicator = indicator; + Assert.Equal(0, watchlistIndicator.MinHistoryDepths); + } + + [Fact] + public void MinusDiIndicator_Initialize_CreatesInternal() + { + var indicator = new MinusDiIndicator { Period = 14 }; + + indicator.Initialize(); + + Assert.Single(indicator.LinesSeries); + } + + [Fact] + public void MinusDiIndicator_ProcessUpdate_HistoricalBar_ComputesValue() + { + var indicator = new MinusDiIndicator { Period = 5 }; + indicator.Initialize(); + + var now = DateTime.UtcNow; + for (int i = 0; i < 20; i++) + { + indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i); + + var args = new UpdateArgs(UpdateReason.HistoricalBar); + indicator.ProcessUpdate(args); + } + + double value = indicator.LinesSeries[0].GetValue(0); + Assert.True(double.IsFinite(value)); + } + + [Fact] + public void MinusDiIndicator_ShortName_IsCorrect() + { + var indicator = new MinusDiIndicator { Period = 20 }; + Assert.Equal("-DI 20", indicator.ShortName); + } + + [Fact] + public void MinusDiIndicator_SourceCodeLink_IsValid() + { + var indicator = new MinusDiIndicator(); + Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + Assert.Contains("MinusDi.Quantower.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/lib/dynamics/minusdi/MinusDi.Quantower.cs b/lib/dynamics/minusdi/MinusDi.Quantower.cs new file mode 100644 index 00000000..13d387be --- /dev/null +++ b/lib/dynamics/minusdi/MinusDi.Quantower.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +[SkipLocalsInit] +public sealed class MinusDiIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private MinusDi _minusDi = null!; + private readonly LineSeries _minusDiSeries; + + public static int MinHistoryDepths => 0; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"-DI {Period}"; + public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/dynamics/minusdi/MinusDi.Quantower.cs"; + + public MinusDiIndicator() + { + OnBackGround = true; + SeparateWindow = true; + Name = "-DI - Minus Directional Indicator"; + Description = "Measures downward directional movement as a percentage of true range"; + + _minusDiSeries = new LineSeries(name: "-DI", color: Color.Red, width: 2, style: LineStyle.Solid); + + AddLineSeries(_minusDiSeries); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnInit() + { + _minusDi = new MinusDi(Period); + base.OnInit(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnUpdate(UpdateArgs args) + { + TValue result = _minusDi.Update(this.GetInputBar(args), args.IsNewBar()); + + _minusDiSeries.SetValue(result.Value, _minusDi.IsHot, ShowColdValues); + } +} diff --git a/lib/dynamics/minusdi/MinusDi.Validation.Tests.cs b/lib/dynamics/minusdi/MinusDi.Validation.Tests.cs new file mode 100644 index 00000000..64049405 --- /dev/null +++ b/lib/dynamics/minusdi/MinusDi.Validation.Tests.cs @@ -0,0 +1,288 @@ +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using OoplesFinance.StockIndicators.Enums; +using Skender.Stock.Indicators; +using TALib; +using QuanTAlib.Tests; + +namespace QuanTAlib; + +/// +/// Validation tests for MinusDi (-DI). Cross-validates against TA-Lib, Skender, +/// OoplesFinance, and internal Dx equivalence with multiple periods. +/// +public sealed class MinusDiValidationTests : IDisposable +{ + private readonly ValidationTestData _data; + + public MinusDiValidationTests() + { + _data = new ValidationTestData(); + } + + public void Dispose() + { + _data.Dispose(); + } + + // ═══════════════════════════════════════════════ + // TA-Lib Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesTalib() + { + var indicator = new MinusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesTalib_VariousPeriods(int period) + { + var indicator = new MinusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Skender Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesSkender() + { + var indicator = new MinusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Mdi); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesSkender_VariousPeriods(int period) + { + var indicator = new MinusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(period).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Mdi); + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence + // ═══════════════════════════════════════════════ + + [Fact] + public void ExactlyMatchesDx_DiMinus() + { + var indicator = new MinusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DiMinus.Value, indicator.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // OoplesFinance Structural Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesOoples_Structural() + { + var ooplesData = _data.SkenderQuotes + .Select(q => new TickerData + { + Date = q.Date, + Open = (double)q.Open, + High = (double)q.High, + Low = (double)q.Low, + Close = (double)q.Close, + Volume = (double)q.Volume + }) + .ToList(); + + var stockData = new StockData(ooplesData); + var adxResults = stockData.CalculateAverageDirectionalIndex(MovingAvgType.WildersSmoothingMethod, 14); + + var allValues = adxResults.OutputValues.Values.SelectMany(v => v).ToList(); + int finiteCount = allValues.Count(v => double.IsFinite(v)); + Assert.True(finiteCount > 100, $"Expected >100 finite Ooples DI values, got {finiteCount}"); + } + + // ═══════════════════════════════════════════════ + // Self-Consistency: Batch == Streaming + // ═══════════════════════════════════════════════ + + [Fact] + public void BatchEqualsStreaming() + { + var batchResults = MinusDi.Batch(_data.Bars, 14); + + var streaming = new MinusDi(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void BatchMatchesTalib() + { + var batchResults = MinusDi.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Determinism + // ═══════════════════════════════════════════════ + + [Fact] + public void ConsistentAcrossMultipleRuns() + { + var ind1 = new MinusDi(14); + var ind2 = new MinusDi(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + // ═══════════════════════════════════════════════ + // Output Range Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void OutputIsNonNegative() + { + var indicator = new MinusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"-DI output at bar {i} was {indicator.Last.Value}"); + } + } + + [Fact] + public void OutputBounded0To100() + { + var indicator = new MinusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + double val = indicator.Last.Value; + if (i >= 14) + { + Assert.True(val >= 0 && val <= 100, $"-DI at bar {i} was {val}, expected [0,100]"); + } + } + } + + // ═══════════════════════════════════════════════ + // Different Periods Produce Different Results + // ═══════════════════════════════════════════════ + + [Fact] + public void DifferentPeriods_ProduceDifferentResults() + { + var short7 = new MinusDi(7); + var long28 = new MinusDi(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short7.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short7.Last.Value, long28.Last.Value); + } +} diff --git a/lib/dynamics/minusdi/MinusDi.md b/lib/dynamics/minusdi/MinusDi.md index a67f2605..6ee59964 100644 --- a/lib/dynamics/minusdi/MinusDi.md +++ b/lib/dynamics/minusdi/MinusDi.md @@ -1,30 +1,100 @@ # MINUS_DI: Minus Directional Indicator -Measures downward directional movement strength as a percentage (0-100). +> *-DI isolates downward directional thrust as a fraction of true range — the bearish arm of Wilder's directional system.* -## Introduction -The Minus Directional Indicator (-DI) measures the strength of downward price movement relative to the true range. It is one of the components of the Directional Movement System developed by J. Welles Wilder Jr. +| Property | Value | +| ---------------- | -------------------------------- | +| **Category** | Dynamic | +| **Inputs** | OHLCV bar (TBar) | +| **Parameters** | `period` (default 14) | +| **Outputs** | Single series | +| **Output range** | 0 to 100 | +| **Warmup** | `period` bars | +| **PineScript** | [minusdi.pine](minusdi.pine) | -When -DI is rising, downward price pressure is increasing. When -DI crosses above +DI, it signals a potential bearish trend. The -DI line is commonly plotted alongside +DI to visualize directional balance. +- The Minus Directional Indicator measures the strength of downward price movement relative to true range. +- Parameterized by `period` (default 14). +- Output range: 0 to 100. +- Requires `period` bars of warmup before first valid output (IsHot = true). +- Validated against TA-Lib, Skender, and Dx equivalence. -## Calculation --DI = Smoothed(-DM) / Smoothed(TR) × 100 +The Minus Directional Indicator (-DI) is one component of J. Welles Wilder Jr.'s Directional Movement System. It quantifies the fraction of recent true range attributable to downward price extension. The computation smooths both -DM (minus directional movement) and TR (true range) with Wilder's RMA ($\alpha = 1/N$), then divides: $-DI = 100 \times \text{Smooth}(-DM) / \text{Smooth}(TR)$. When -DI rises, downward price pressure is increasing. When -DI crosses above +DI, it signals a potential bearish trend. The -DI line is commonly plotted alongside +DI to visualize directional balance. -Where: -- -DM (Minus Directional Movement) = max(PrevLow - Low, 0) when PrevLow - Low > High - PrevHigh, else 0 -- TR (True Range) = max(High - Low, |High - PrevClose|, |Low - PrevClose|) -- Smoothing uses Wilder's method: Smooth = Smooth - Smooth/N + Input +## Historical Context -## Parameters -| Parameter | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| Period | 14 | 2-∞ | Wilder smoothing period | +J. Welles Wilder Jr. introduced the Directional Movement System in *New Concepts in Technical Trading Systems* (1978). The system decomposes price range into directional components. +DI and -DI are the normalized indicators from which DX and ADX are derived. While most traders focus on ADX for trend strength, +DI and -DI remain essential for determining trend *direction* — a bearish signal occurs when -DI crosses above +DI, bullish when +DI crosses above -DI. -## Interpretation -- **Rising -DI:** Strengthening downward movement -- **-DI > +DI:** Bears dominate; potential downtrend -- **-DI crossover above +DI:** Bearish signal -- **High -DI (>40):** Strong downward momentum +## Architecture & Physics -## References -- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978) +### 1. Minus Directional Movement + +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ + +$$-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. Wilder Smoothing (RMA) + +$$-DM_{\text{smooth}} = \text{RMA}(-DM, N), \quad TR_{\text{smooth}} = \text{RMA}(TR, N)$$ + +### 4. Minus Directional Indicator + +$$-DI = 100 \times \frac{-DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +When $TR_{\text{smooth}} = 0$ (no price movement), -DI = 0. + +### 5. Complexity + +- **Time:** $O(1)$ per bar — all RMA updates are recursive +- **Space:** $O(1)$ — scalar state only (delegates to Dx) +- **Warmup:** $N$ bars + +## Mathematical Foundation + +### Parameters + +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | + +### Interpretation + +| -DI Value | Signal | +|-----------|--------| +| Rising -DI | Strengthening downward movement | +| -DI > +DI | Bears dominate; potential downtrend | +| -DI crossover above +DI | Bearish signal | +| High -DI (>40) | Strong downward momentum | + +-DI measures directional *strength*, not absolute direction. Compare +DI vs -DI for directional bias: if $-DI > +DI$, the trend is down. + +## Performance Profile + +### Operation Count (Streaming Mode) + +-DI is a thin wrapper around Dx. The per-bar cost is identical to Dx (one property extraction after Dx completes its update). + +**Post-warmup steady state (per bar):** + +| Operation | Count | Cost (cycles) | Subtotal | +| :--- | :---: | :---: | :---: | +| Dx.Update (full pipeline) | 1 | 75 | 75 | +| Property extraction | 1 | 1 | 1 | +| **Total** | **2** | — | **~76 cycles** | + +### Quality Metrics + +| Metric | Score | Notes | +| :--- | :---: | :--- | +| **Accuracy** | 9/10 | Exact Dx delegation; FMA-precise RMA smoothing | +| **Timeliness** | 7/10 | N-bar warmup; responds to bar-level changes | +| **Smoothness** | 7/10 | Single RMA layer; moderate noise suppression | +| **Noise Rejection** | 7/10 | Wilder smoothing filters transient spikes | + +## Resources + +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `minusdi.pine` in indicator directory diff --git a/lib/dynamics/minusdi/minusdi.pine b/lib/dynamics/minusdi/minusdi.pine new file mode 100644 index 00000000..3574b3dd --- /dev/null +++ b/lib/dynamics/minusdi/minusdi.pine @@ -0,0 +1,55 @@ +// Licensed under the Apache License, Version 2.0 +// © mihakralj +//@version=6 +indicator("Minus Directional Indicator (-DI)", "-DI", overlay=false) + +//@function Calculates -DI using Wilder's smoothing with compensated RMA +//@param period Number of bars used in the calculation +//@returns -DI value (0-100) +//@optimized Uses Wilder's smoothing (RMA) with warmup compensation for accurate values from bar 1 +minusdi(simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + float alpha = 1.0 / period + float beta = 1.0 - alpha + float tr = 0.0 + float minus_dm = 0.0 + if na(close[1]) + tr := high - low + else + tr := math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) + float upMove = high - high[1] + float downMove = low[1] - low + if downMove > upMove and downMove > 0 + minus_dm := downMove + var bool warmup = true + var float e = 1.0 + var float tr_ema = 0.0 + var float tr_result = tr + var float minus_dm_ema = 0.0 + var float minus_dm_result = minus_dm + tr_ema := alpha * (tr - tr_ema) + tr_ema + minus_dm_ema := alpha * (minus_dm - minus_dm_ema) + minus_dm_ema + if warmup + e *= beta + float c = 1.0 / (1.0 - e) + tr_result := c * tr_ema + minus_dm_result := c * minus_dm_ema + warmup := e > 1e-10 + else + tr_result := tr_ema + minus_dm_result := minus_dm_ema + float minus_di = tr_result != 0.0 ? 100.0 * minus_dm_result / tr_result : 0.0 + minus_di + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1, tooltip="Number of bars used in the calculation") + +// Calculation +minus_di = minusdi(i_period) + +// Plot +plot(minus_di, "-DI", color=color.red, linewidth=2) +hline(25, "Threshold", color=color.gray, linestyle=hline.style_dashed) diff --git a/lib/dynamics/minusdm/MinusDm.Quantower.Tests.cs b/lib/dynamics/minusdm/MinusDm.Quantower.Tests.cs new file mode 100644 index 00000000..154b6dcc --- /dev/null +++ b/lib/dynamics/minusdm/MinusDm.Quantower.Tests.cs @@ -0,0 +1,73 @@ +using TradingPlatform.BusinessLayer; +using QuanTAlib; + +namespace QuanTAlib.Tests; + +public class MinusDmIndicatorTests +{ + [Fact] + public void MinusDmIndicator_Constructor_SetsDefaults() + { + var indicator = new MinusDmIndicator(); + + Assert.Equal(14, indicator.Period); + Assert.True(indicator.ShowColdValues); + Assert.Equal("-DM - Minus Directional Movement", indicator.Name); + Assert.True(indicator.SeparateWindow); + Assert.True(indicator.OnBackGround); + } + + [Fact] + public void MinusDmIndicator_MinHistoryDepths_EqualsZero() + { + var indicator = new MinusDmIndicator { Period = 20 }; + + Assert.Equal(0, MinusDmIndicator.MinHistoryDepths); + IWatchlistIndicator watchlistIndicator = indicator; + Assert.Equal(0, watchlistIndicator.MinHistoryDepths); + } + + [Fact] + public void MinusDmIndicator_Initialize_CreatesInternal() + { + var indicator = new MinusDmIndicator { Period = 14 }; + + indicator.Initialize(); + + Assert.Single(indicator.LinesSeries); + } + + [Fact] + public void MinusDmIndicator_ProcessUpdate_HistoricalBar_ComputesValue() + { + var indicator = new MinusDmIndicator { Period = 5 }; + indicator.Initialize(); + + var now = DateTime.UtcNow; + for (int i = 0; i < 20; i++) + { + indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i); + + var args = new UpdateArgs(UpdateReason.HistoricalBar); + indicator.ProcessUpdate(args); + } + + double value = indicator.LinesSeries[0].GetValue(0); + Assert.True(double.IsFinite(value)); + } + + [Fact] + public void MinusDmIndicator_ShortName_IsCorrect() + { + var indicator = new MinusDmIndicator { Period = 20 }; + Assert.Equal("-DM 20", indicator.ShortName); + } + + [Fact] + public void MinusDmIndicator_SourceCodeLink_IsValid() + { + var indicator = new MinusDmIndicator(); + Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + Assert.Contains("MinusDm.Quantower.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/lib/dynamics/minusdm/MinusDm.Quantower.cs b/lib/dynamics/minusdm/MinusDm.Quantower.cs new file mode 100644 index 00000000..e2d48732 --- /dev/null +++ b/lib/dynamics/minusdm/MinusDm.Quantower.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +[SkipLocalsInit] +public sealed class MinusDmIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private MinusDm _minusDm = null!; + private readonly LineSeries _minusDmSeries; + + public static int MinHistoryDepths => 0; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"-DM {Period}"; + public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/dynamics/minusdm/MinusDm.Quantower.cs"; + + public MinusDmIndicator() + { + OnBackGround = true; + SeparateWindow = true; + Name = "-DM - Minus Directional Movement"; + Description = "Wilder-smoothed downward directional movement in price units"; + + _minusDmSeries = new LineSeries(name: "-DM", color: Color.Red, width: 2, style: LineStyle.Solid); + + AddLineSeries(_minusDmSeries); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnInit() + { + _minusDm = new MinusDm(Period); + base.OnInit(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnUpdate(UpdateArgs args) + { + TValue result = _minusDm.Update(this.GetInputBar(args), args.IsNewBar()); + + _minusDmSeries.SetValue(result.Value, _minusDm.IsHot, ShowColdValues); + } +} diff --git a/lib/dynamics/minusdm/MinusDm.Validation.Tests.cs b/lib/dynamics/minusdm/MinusDm.Validation.Tests.cs new file mode 100644 index 00000000..5835ff1d --- /dev/null +++ b/lib/dynamics/minusdm/MinusDm.Validation.Tests.cs @@ -0,0 +1,231 @@ +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using OoplesFinance.StockIndicators.Enums; +using Skender.Stock.Indicators; +using TALib; +using QuanTAlib.Tests; + +namespace QuanTAlib; + +/// +/// Validation tests for MinusDm (-DM). Cross-validates against TA-Lib, +/// OoplesFinance, and internal Dx equivalence with multiple periods. +/// Note: Skender does not expose DM values directly; only DI values via GetAdx(). +/// +public sealed class MinusDmValidationTests : IDisposable +{ + private readonly ValidationTestData _data; + + public MinusDmValidationTests() + { + _data = new ValidationTestData(); + } + + public void Dispose() + { + _data.Dispose(); + } + + // ═══════════════════════════════════════════════ + // TA-Lib Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesTalib() + { + var indicator = new MinusDm(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesTalib_VariousPeriods(int period) + { + var indicator = new MinusDm(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence + // ═══════════════════════════════════════════════ + + [Fact] + public void ExactlyMatchesDx_DmMinus() + { + var indicator = new MinusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DmMinus.Value, indicator.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // OoplesFinance Structural Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesOoples_Structural() + { + var ooplesData = _data.SkenderQuotes + .Select(q => new TickerData + { + Date = q.Date, + Open = (double)q.Open, + High = (double)q.High, + Low = (double)q.Low, + Close = (double)q.Close, + Volume = (double)q.Volume + }) + .ToList(); + + var stockData = new StockData(ooplesData); + var adxResults = stockData.CalculateAverageDirectionalIndex(MovingAvgType.WildersSmoothingMethod, 14); + + var allValues = adxResults.OutputValues.Values.SelectMany(v => v).ToList(); + int finiteCount = allValues.Count(v => double.IsFinite(v)); + Assert.True(finiteCount > 100, $"Expected >100 finite Ooples ADX/DI values, got {finiteCount}"); + } + + // ═══════════════════════════════════════════════ + // Self-Consistency: Batch == Streaming + // ═══════════════════════════════════════════════ + + [Fact] + public void BatchEqualsStreaming() + { + var batchResults = MinusDm.Batch(_data.Bars, 14); + + var streaming = new MinusDm(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void BatchMatchesTalib() + { + var batchResults = MinusDm.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Determinism + // ═══════════════════════════════════════════════ + + [Fact] + public void ConsistentAcrossMultipleRuns() + { + var ind1 = new MinusDm(14); + var ind2 = new MinusDm(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + // ═══════════════════════════════════════════════ + // Output Range Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void OutputIsNonNegative() + { + var indicator = new MinusDm(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"-DM output at bar {i} was {indicator.Last.Value}"); + } + } + + // ═══════════════════════════════════════════════ + // Different Periods Produce Different Results + // ═══════════════════════════════════════════════ + + [Fact] + public void DifferentPeriods_ProduceDifferentResults() + { + var short7 = new MinusDm(7); + var long28 = new MinusDm(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short7.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short7.Last.Value, long28.Last.Value); + } +} diff --git a/lib/dynamics/minusdm/MinusDm.md b/lib/dynamics/minusdm/MinusDm.md index 4995cf69..a3c8148d 100644 --- a/lib/dynamics/minusdm/MinusDm.md +++ b/lib/dynamics/minusdm/MinusDm.md @@ -1,27 +1,92 @@ # MINUS_DM: Minus Directional Movement -Wilder-smoothed downward directional movement in price units (≥0). +> *-DM captures the raw downward price extension, smoothed by Wilder's RMA — the building block before normalization to -DI.* -## Introduction -Minus Directional Movement (-DM) measures the magnitude of downward price movement, smoothed using Wilder's method. Unlike -DI which normalizes by true range to produce a percentage, -DM outputs raw smoothed values in price units. +| Property | Value | +| ---------------- | -------------------------------- | +| **Category** | Dynamic | +| **Inputs** | OHLCV bar (TBar) | +| **Parameters** | `period` (default 14) | +| **Outputs** | Single series | +| **Output range** | ≥ 0 (price units) | +| **Warmup** | `period` bars | +| **PineScript** | [minusdm.pine](minusdm.pine) | --DM captures when the previous bar's low exceeds the current bar's low by more than the current bar's high exceeds the previous bar's high. It is the raw building block of the Directional Movement System. +- Minus Directional Movement outputs the Wilder-smoothed downward directional movement in price units. +- Parameterized by `period` (default 14). +- Output range: ≥ 0 (price units, scales with instrument). +- Requires `period` bars of warmup before first valid output (IsHot = true). +- Validated against TA-Lib and Dx equivalence. -## Calculation --DM = max(PrevLow - Low, 0) when PrevLow - Low > High - PrevHigh, else 0 +Minus Directional Movement (-DM) measures the magnitude of downward price movement, smoothed using Wilder's method. Unlike -DI which normalizes by true range to produce a percentage, -DM outputs raw smoothed values in price units. -DM captures when the previous bar's low exceeds the current bar's low by more than the current bar's high exceeds the previous bar's high. It is the raw building block of the Directional Movement System — the unnormalized signal before division by True Range converts it to -DI. -Smoothed using Wilder's method: Smooth = Smooth - Smooth/N + Input +## Historical Context -## Parameters -| Parameter | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| Period | 14 | 2-∞ | Wilder smoothing period | +J. Welles Wilder Jr. designed the Directional Movement System as a pipeline in *New Concepts in Technical Trading Systems* (1978). -DM is the first computational stage for downward movement: a binary event detector that fires when downward range expansion dominates. Wilder's insight was that direction should be measured by range *extension*, not by close-to-close returns. A bar that pushes to a new low by more than it pushes to a new high registers as negative directional movement. The smoothed -DM series shows how much downward thrust is being sustained over the lookback period, in absolute price units. -## Interpretation -- **Rising -DM:** Increasing downward price extension -- **-DM > +DM:** Downward movement exceeds upward movement -- **Zero -DM:** No downward directional movement on the bar -- Values are in price units and scale with the instrument +## Architecture & Physics -## References -- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978) +### 1. Minus Directional Movement (Raw) + +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ + +$$-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) + +$$-DM_{\text{smooth}} = \text{RMA}(-DM, N), \quad \alpha = 1/N$$ + +### 3. Complexity + +- **Time:** $O(1)$ per bar — recursive RMA update +- **Space:** $O(1)$ — scalar state only (delegates to Dx) +- **Warmup:** $N$ bars + +## Mathematical Foundation + +### Parameters + +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | + +### Interpretation + +| -DM Behavior | Signal | +|--------------|--------| +| Rising -DM | Increasing downward price extension | +| -DM > +DM | Downward movement exceeds upward movement | +| Zero -DM | No downward directional movement on the bar | +| Values scale with instrument | Compare within same instrument only | + +-DM values are in price units and scale with the instrument. They cannot be compared across different instruments without normalization (which is what -DI provides). + +## Performance Profile + +### Operation Count (Streaming Mode) + +-DM is a thin wrapper around Dx. The per-bar cost is identical to Dx (one property extraction after Dx completes its update). + +**Post-warmup steady state (per bar):** + +| Operation | Count | Cost (cycles) | Subtotal | +| :--- | :---: | :---: | :---: | +| Dx.Update (full pipeline) | 1 | 75 | 75 | +| Property extraction | 1 | 1 | 1 | +| **Total** | **2** | — | **~76 cycles** | + +### Quality Metrics + +| Metric | Score | Notes | +| :--- | :---: | :--- | +| **Accuracy** | 9/10 | Exact Dx delegation; FMA-precise RMA smoothing | +| **Timeliness** | 7/10 | N-bar warmup; responds to bar-level changes | +| **Smoothness** | 7/10 | Single RMA layer; moderate noise suppression | +| **Noise Rejection** | 7/10 | Wilder smoothing filters transient spikes | + +## Resources + +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `minusdm.pine` in indicator directory diff --git a/lib/dynamics/minusdm/minusdm.pine b/lib/dynamics/minusdm/minusdm.pine new file mode 100644 index 00000000..1f02c48d --- /dev/null +++ b/lib/dynamics/minusdm/minusdm.pine @@ -0,0 +1,44 @@ +// Licensed under the Apache License, Version 2.0 +// © mihakralj +//@version=6 +indicator("Minus Directional Movement (-DM)", "-DM", overlay=false) + +//@function Calculates Wilder-smoothed -DM using compensated RMA +//@param period Number of bars used in the calculation +//@returns Smoothed -DM value in price units (≥0) +//@optimized Uses Wilder's smoothing (RMA) with warmup compensation for accurate values from bar 1 +minusdm(simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + float alpha = 1.0 / period + float beta = 1.0 - alpha + float minus_dm = 0.0 + if not na(low[1]) + float upMove = high - high[1] + float downMove = low[1] - low + if downMove > upMove and downMove > 0 + minus_dm := downMove + var bool warmup = true + var float e = 1.0 + var float minus_dm_ema = 0.0 + var float minus_dm_result = minus_dm + minus_dm_ema := alpha * (minus_dm - minus_dm_ema) + minus_dm_ema + if warmup + e *= beta + float c = 1.0 / (1.0 - e) + minus_dm_result := c * minus_dm_ema + warmup := e > 1e-10 + else + minus_dm_result := minus_dm_ema + minus_dm_result + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1, tooltip="Number of bars used in the calculation") + +// Calculation +minus_dm_value = minusdm(i_period) + +// Plot +plot(minus_dm_value, "-DM", color=color.red, linewidth=2) diff --git a/lib/dynamics/pfe/Pfe.md b/lib/dynamics/pfe/Pfe.md index 5364c582..3e98b5ef 100644 --- a/lib/dynamics/pfe/Pfe.md +++ b/lib/dynamics/pfe/Pfe.md @@ -1,5 +1,7 @@ # PFE: Polarized Fractal Efficiency +> *The shortest distance between two points is a straight line. The market never takes the shortest distance. PFE measures how badly it misses.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The shortest distance between two points is a straight line. The market never takes the shortest distance. PFE measures how badly it misses." - Polarized Fractal Efficiency (PFE) quantifies trend strength by comparing the Euclidean distance a price series actually travels bar-to-bar against the straight-line distance between the endpoints over the same window. The ratio, scaled to [-100, +100] and smoothed with an EMA, distinguishes efficient trending motion (values near ±100) from fractal, self-similar noise (values near 0). Created by Hans Hannula and published in *Technical Analysis of Stocks & Commodities* (January 1994), PFE applies fractal geometry to price action without requiring Hurst exponent estimation or rescaled-range analysis. With default parameters (period=10, smooth=5), the indicator needs 11 close values for the first raw reading plus 5 bars of EMA convergence, totaling ~16 bars of warmup. The core loop executes $N$ square roots per bar, making it $O(N)$ per update in streaming mode. ## Historical Context diff --git a/lib/dynamics/plusdi/DiDm.Tests.cs b/lib/dynamics/plusdi/DiDm.Tests.cs new file mode 100644 index 00000000..95c033c2 --- /dev/null +++ b/lib/dynamics/plusdi/DiDm.Tests.cs @@ -0,0 +1,917 @@ + +namespace QuanTAlib; + +/// +/// Combined unit tests for PlusDi, MinusDi, PlusDm, MinusDm. +/// All four are thin Dx-composition wrappers extracting a single property. +/// +public class DiDmTests +{ + // ═══════════════════════════════════════════════ + // A. Constructor / Parameter Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_Constructor_InvalidPeriod_Throws() + { + Assert.Throws(() => new PlusDi(0)); + Assert.Throws(() => new PlusDi(-1)); + } + + [Fact] + public void MinusDi_Constructor_InvalidPeriod_Throws() + { + Assert.Throws(() => new MinusDi(0)); + Assert.Throws(() => new MinusDi(-1)); + } + + [Fact] + public void PlusDm_Constructor_InvalidPeriod_Throws() + { + Assert.Throws(() => new PlusDm(0)); + Assert.Throws(() => new PlusDm(-1)); + } + + [Fact] + public void MinusDm_Constructor_InvalidPeriod_Throws() + { + Assert.Throws(() => new MinusDm(0)); + Assert.Throws(() => new MinusDm(-1)); + } + + [Fact] + public void PlusDi_DefaultPeriod_Is14() + { + var indicator = new PlusDi(); + Assert.Equal(14, indicator.Period); + } + + [Fact] + public void MinusDi_DefaultPeriod_Is14() + { + var indicator = new MinusDi(); + Assert.Equal(14, indicator.Period); + } + + [Fact] + public void PlusDm_DefaultPeriod_Is14() + { + var indicator = new PlusDm(); + Assert.Equal(14, indicator.Period); + } + + [Fact] + public void MinusDm_DefaultPeriod_Is14() + { + var indicator = new MinusDm(); + Assert.Equal(14, indicator.Period); + } + + [Fact] + public void PlusDi_Name_ContainsPeriod() + { + var indicator = new PlusDi(20); + Assert.Contains("20", indicator.Name, StringComparison.Ordinal); + } + + [Fact] + public void MinusDi_Name_ContainsPeriod() + { + var indicator = new MinusDi(20); + Assert.Contains("20", indicator.Name, StringComparison.Ordinal); + } + + [Fact] + public void PlusDm_Name_ContainsPeriod() + { + var indicator = new PlusDm(20); + Assert.Contains("20", indicator.Name, StringComparison.Ordinal); + } + + [Fact] + public void MinusDm_Name_ContainsPeriod() + { + var indicator = new MinusDm(20); + Assert.Contains("20", indicator.Name, StringComparison.Ordinal); + } + + // ═══════════════════════════════════════════════ + // B. Basic Calculation Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_BasicCalculation_DoesNotCrash() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDi_BasicCalculation_DoesNotCrash() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void PlusDm_BasicCalculation_DoesNotCrash() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDm_BasicCalculation_DoesNotCrash() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + // ═══════════════════════════════════════════════ + // C. IsHot / WarmupPeriod Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_IsHot_BecomesTrueAfterWarmup() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + if (indicator.IsHot) + { + break; + } + } + + Assert.True(indicator.IsHot); + } + + [Fact] + public void MinusDi_IsHot_BecomesTrueAfterWarmup() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + if (indicator.IsHot) + { + break; + } + } + + Assert.True(indicator.IsHot); + } + + [Fact] + public void PlusDm_IsHot_BecomesTrueAfterWarmup() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + if (indicator.IsHot) + { + break; + } + } + + Assert.True(indicator.IsHot); + } + + [Fact] + public void MinusDm_IsHot_BecomesTrueAfterWarmup() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + if (indicator.IsHot) + { + break; + } + } + + Assert.True(indicator.IsHot); + } + + // ═══════════════════════════════════════════════ + // D. Bar Correction (isNew=false) Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_BarCorrection_MatchesFreshInstance() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < 99; i++) + { + indicator.Update(bars[i]); + } + indicator.Update(bars[99]); + + var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 1.0, bars[99].Low - 1.0, bars[99].Close, bars[99].Volume); + var val2 = indicator.Update(modifiedBar, isNew: false); + + var fresh = new PlusDi(14); + for (int i = 0; i < 99; i++) + { + fresh.Update(bars[i]); + } + var val3 = fresh.Update(modifiedBar); + + Assert.Equal(val3.Value, val2.Value, 1e-9); + } + + [Fact] + public void MinusDi_BarCorrection_MatchesFreshInstance() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < 99; i++) + { + indicator.Update(bars[i]); + } + indicator.Update(bars[99]); + + var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 1.0, bars[99].Low - 1.0, bars[99].Close, bars[99].Volume); + var val2 = indicator.Update(modifiedBar, isNew: false); + + var fresh = new MinusDi(14); + for (int i = 0; i < 99; i++) + { + fresh.Update(bars[i]); + } + var val3 = fresh.Update(modifiedBar); + + Assert.Equal(val3.Value, val2.Value, 1e-9); + } + + [Fact] + public void PlusDm_BarCorrection_MatchesFreshInstance() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < 99; i++) + { + indicator.Update(bars[i]); + } + indicator.Update(bars[99]); + + var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 1.0, bars[99].Low - 1.0, bars[99].Close, bars[99].Volume); + var val2 = indicator.Update(modifiedBar, isNew: false); + + var fresh = new PlusDm(14); + for (int i = 0; i < 99; i++) + { + fresh.Update(bars[i]); + } + var val3 = fresh.Update(modifiedBar); + + Assert.Equal(val3.Value, val2.Value, 1e-9); + } + + [Fact] + public void MinusDm_BarCorrection_MatchesFreshInstance() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < 99; i++) + { + indicator.Update(bars[i]); + } + indicator.Update(bars[99]); + + var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 1.0, bars[99].Low - 1.0, bars[99].Close, bars[99].Volume); + var val2 = indicator.Update(modifiedBar, isNew: false); + + var fresh = new MinusDm(14); + for (int i = 0; i < 99; i++) + { + fresh.Update(bars[i]); + } + var val3 = fresh.Update(modifiedBar); + + Assert.Equal(val3.Value, val2.Value, 1e-9); + } + + // ═══════════════════════════════════════════════ + // E. Reset Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_Reset_ClearsState() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + indicator.Reset(); + Assert.Equal(0, indicator.Last.Value); + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDi_Reset_ClearsState() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + indicator.Reset(); + Assert.Equal(0, indicator.Last.Value); + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void PlusDm_Reset_ClearsState() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + indicator.Reset(); + Assert.Equal(0, indicator.Last.Value); + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDm_Reset_ClearsState() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + indicator.Reset(); + Assert.Equal(0, indicator.Last.Value); + Assert.False(indicator.IsHot); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + // ═══════════════════════════════════════════════ + // F. Batch / Streaming Consistency Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_Batch_MatchesStreaming() + { + var gbm = new GBM(seed: 123); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var batchResult = PlusDi.Batch(bars, 14); + + var streaming = new PlusDi(14); + for (int i = 0; i < bars.Count; i++) + { + streaming.Update(bars[i]); + } + + Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 9); + } + + [Fact] + public void MinusDi_Batch_MatchesStreaming() + { + var gbm = new GBM(seed: 123); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var batchResult = MinusDi.Batch(bars, 14); + + var streaming = new MinusDi(14); + for (int i = 0; i < bars.Count; i++) + { + streaming.Update(bars[i]); + } + + Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 9); + } + + [Fact] + public void PlusDm_Batch_MatchesStreaming() + { + var gbm = new GBM(seed: 123); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var batchResult = PlusDm.Batch(bars, 14); + + var streaming = new PlusDm(14); + for (int i = 0; i < bars.Count; i++) + { + streaming.Update(bars[i]); + } + + Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 9); + } + + [Fact] + public void MinusDm_Batch_MatchesStreaming() + { + var gbm = new GBM(seed: 123); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var batchResult = MinusDm.Batch(bars, 14); + + var streaming = new MinusDm(14); + for (int i = 0; i < bars.Count; i++) + { + streaming.Update(bars[i]); + } + + Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 9); + } + + // ═══════════════════════════════════════════════ + // G. Event / Pub Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_Pub_FiresOnNewBar() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + int fireCount = 0; + + indicator.Pub += (object? _, in TValueEventArgs _e) => { fireCount++; }; + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.Equal(bars.Count, fireCount); + } + + [Fact] + public void MinusDi_Pub_DoesNotFireOnCorrection() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + int fireCount = 0; + + indicator.Pub += (object? _, in TValueEventArgs _e) => { fireCount++; }; + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + int countAfterNewBars = fireCount; + + // Bar correction should not fire + var modifiedBar = new TBar(bars[^1].Time, bars[^1].Open, bars[^1].High + 1.0, bars[^1].Low - 1.0, bars[^1].Close, bars[^1].Volume); + indicator.Update(modifiedBar, isNew: false); + + Assert.Equal(countAfterNewBars, fireCount); + } + + [Fact] + public void PlusDm_Pub_FiresOnNewBar() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + int fireCount = 0; + + indicator.Pub += (object? _, in TValueEventArgs _e) => { fireCount++; }; + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + Assert.Equal(bars.Count, fireCount); + } + + [Fact] + public void MinusDm_Pub_DoesNotFireOnCorrection() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + int fireCount = 0; + + indicator.Pub += (object? _, in TValueEventArgs _e) => { fireCount++; }; + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + int countAfterNewBars = fireCount; + + var modifiedBar = new TBar(bars[^1].Time, bars[^1].Open, bars[^1].High + 1.0, bars[^1].Low - 1.0, bars[^1].Close, bars[^1].Volume); + indicator.Update(modifiedBar, isNew: false); + + Assert.Equal(countAfterNewBars, fireCount); + } + + // ═══════════════════════════════════════════════ + // H. Prime / Calculate / Chainability Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_Prime_SetsState() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + indicator.Prime(bars); + + Assert.True(indicator.IsHot); + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDi_Prime_SetsState() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + indicator.Prime(bars); + + Assert.True(indicator.IsHot); + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void PlusDm_Calculate_ReturnsTupleWithIndicator() + { + var gbm = new GBM(); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var (results, ind) = PlusDm.Calculate(bars, 14); + + Assert.Equal(bars.Count, results.Count); + Assert.True(ind.IsHot); + Assert.Equal(results[^1].Value, ind.Last.Value, 1e-9); + } + + [Fact] + public void MinusDm_Calculate_ReturnsTupleWithIndicator() + { + var gbm = new GBM(); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var (results, ind) = MinusDm.Calculate(bars, 14); + + Assert.Equal(bars.Count, results.Count); + Assert.True(ind.IsHot); + Assert.Equal(results[^1].Value, ind.Last.Value, 1e-9); + } + + [Fact] + public void PlusDi_TBarSeriesConstructor_Works() + { + var gbm = new GBM(); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var indicator = new PlusDi(bars, 14); + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void MinusDi_TBarSeriesConstructor_Works() + { + var gbm = new GBM(); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var indicator = new MinusDi(bars, 14); + + Assert.True(double.IsFinite(indicator.Last.Value)); + } + + [Fact] + public void PlusDi_ScalarUpdate_ReturnsLastUnchanged() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + var before = indicator.Last; + var result = indicator.Update(new TValue(DateTime.UtcNow, 42.0)); + + Assert.Equal(before.Value, result.Value); + } + + [Fact] + public void MinusDm_ScalarUpdate_ReturnsLastUnchanged() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + indicator.Update(bars[i]); + } + + var before = indicator.Last; + var result = indicator.Update(new TValue(DateTime.UtcNow, 42.0)); + + Assert.Equal(before.Value, result.Value); + } + + // ═══════════════════════════════════════════════ + // Range / Value Constraint Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_OutputRange_0to100() + { + var indicator = new PlusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + var result = indicator.Update(bars[i]); + if (indicator.IsHot) + { + Assert.InRange(result.Value, 0, 100); + } + } + } + + [Fact] + public void MinusDi_OutputRange_0to100() + { + var indicator = new MinusDi(14); + var gbm = new GBM(); + var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + var result = indicator.Update(bars[i]); + if (indicator.IsHot) + { + Assert.InRange(result.Value, 0, 100); + } + } + } + + [Fact] + public void PlusDm_NonNegative() + { + var indicator = new PlusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + var result = indicator.Update(bars[i]); + if (indicator.IsHot) + { + Assert.True(result.Value >= 0, $"+DM should be non-negative, got {result.Value}"); + } + } + } + + [Fact] + public void MinusDm_NonNegative() + { + var indicator = new MinusDm(14); + var gbm = new GBM(); + var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + for (int i = 0; i < bars.Count; i++) + { + var result = indicator.Update(bars[i]); + if (indicator.IsHot) + { + Assert.True(result.Value >= 0, $"-DM should be non-negative, got {result.Value}"); + } + } + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_MatchesDx_DiPlus() + { + var gbm = new GBM(seed: 42); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var plusDi = new PlusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < bars.Count; i++) + { + plusDi.Update(bars[i]); + dx.Update(bars[i]); + + Assert.Equal(dx.DiPlus.Value, plusDi.Last.Value, 1e-12); + } + } + + [Fact] + public void MinusDi_MatchesDx_DiMinus() + { + var gbm = new GBM(seed: 42); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var minusDi = new MinusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < bars.Count; i++) + { + minusDi.Update(bars[i]); + dx.Update(bars[i]); + + Assert.Equal(dx.DiMinus.Value, minusDi.Last.Value, 1e-12); + } + } + + [Fact] + public void PlusDm_MatchesDx_DmPlus() + { + var gbm = new GBM(seed: 42); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var plusDm = new PlusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < bars.Count; i++) + { + plusDm.Update(bars[i]); + dx.Update(bars[i]); + + Assert.Equal(dx.DmPlus.Value, plusDm.Last.Value, 1e-12); + } + } + + [Fact] + public void MinusDm_MatchesDx_DmMinus() + { + var gbm = new GBM(seed: 42); + var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var minusDm = new MinusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < bars.Count; i++) + { + minusDm.Update(bars[i]); + dx.Update(bars[i]); + + Assert.Equal(dx.DmMinus.Value, minusDm.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // Determinism Tests + // ═══════════════════════════════════════════════ + + [Fact] + public void AllFour_Deterministic_SameSeedSameResult() + { + var gbm1 = new GBM(seed: 99); + var bars1 = gbm1.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + var gbm2 = new GBM(seed: 99); + var bars2 = gbm2.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + + var pdi1 = new PlusDi(14); + var pdi2 = new PlusDi(14); + var mdi1 = new MinusDi(14); + var mdi2 = new MinusDi(14); + var pdm1 = new PlusDm(14); + var pdm2 = new PlusDm(14); + var mdm1 = new MinusDm(14); + var mdm2 = new MinusDm(14); + + for (int i = 0; i < bars1.Count; i++) + { + pdi1.Update(bars1[i]); + pdi2.Update(bars2[i]); + mdi1.Update(bars1[i]); + mdi2.Update(bars2[i]); + pdm1.Update(bars1[i]); + pdm2.Update(bars2[i]); + mdm1.Update(bars1[i]); + mdm2.Update(bars2[i]); + } + + Assert.Equal(pdi1.Last.Value, pdi2.Last.Value, 1e-12); + Assert.Equal(mdi1.Last.Value, mdi2.Last.Value, 1e-12); + Assert.Equal(pdm1.Last.Value, pdm2.Last.Value, 1e-12); + Assert.Equal(mdm1.Last.Value, mdm2.Last.Value, 1e-12); + } +} diff --git a/lib/dynamics/plusdi/DiDm.Validation.Tests.cs b/lib/dynamics/plusdi/DiDm.Validation.Tests.cs new file mode 100644 index 00000000..b9aee978 --- /dev/null +++ b/lib/dynamics/plusdi/DiDm.Validation.Tests.cs @@ -0,0 +1,818 @@ +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using OoplesFinance.StockIndicators.Enums; +using Skender.Stock.Indicators; +using TALib; +using QuanTAlib.Tests; + +namespace QuanTAlib; + +/// +/// Combined validation tests for PlusDi, MinusDi, PlusDm, MinusDm. +/// Cross-validates against TA-Lib, Skender, Ooples, and Dx equivalence. +/// +public sealed class DiDmValidationTests : IDisposable +{ + private readonly ValidationTestData _data; + + public DiDmValidationTests() + { + _data = new ValidationTestData(); + } + + public void Dispose() + { + _data.Dispose(); + } + + // ═══════════════════════════════════════════════ + // TA-Lib Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_MatchesTalib() + { + var indicator = new PlusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Fact] + public void MinusDi_MatchesTalib() + { + var indicator = new MinusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Fact] + public void PlusDm_MatchesTalib() + { + var indicator = new PlusDm(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Fact] + public void MinusDm_MatchesTalib() + { + var indicator = new MinusDm(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Skender Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_MatchesSkender() + { + var indicator = new PlusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList(); + + ValidationHelper.VerifyData(results, skenderResults, x => x.Pdi); + } + + [Fact] + public void MinusDi_MatchesSkender() + { + var indicator = new MinusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList(); + + ValidationHelper.VerifyData(results, skenderResults, x => x.Mdi); + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_ExactlyMatchesDx_DiPlus() + { + var indicator = new PlusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DiPlus.Value, indicator.Last.Value, 1e-12); + } + } + + [Fact] + public void MinusDi_ExactlyMatchesDx_DiMinus() + { + var indicator = new MinusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DiMinus.Value, indicator.Last.Value, 1e-12); + } + } + + [Fact] + public void PlusDm_ExactlyMatchesDx_DmPlus() + { + var indicator = new PlusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DmPlus.Value, indicator.Last.Value, 1e-12); + } + } + + [Fact] + public void MinusDm_ExactlyMatchesDx_DmMinus() + { + var indicator = new MinusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DmMinus.Value, indicator.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // Self-Consistency: Batch == Streaming + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_BatchEqualsStreaming() + { + var batchResults = PlusDi.Batch(_data.Bars, 14); + + var streaming = new PlusDi(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void MinusDi_BatchEqualsStreaming() + { + var batchResults = MinusDi.Batch(_data.Bars, 14); + + var streaming = new MinusDi(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void PlusDm_BatchEqualsStreaming() + { + var batchResults = PlusDm.Batch(_data.Bars, 14); + + var streaming = new PlusDm(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void MinusDm_BatchEqualsStreaming() + { + var batchResults = MinusDm.Batch(_data.Bars, 14); + + var streaming = new MinusDm(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + // ═══════════════════════════════════════════════ + // Multi-Period TALib Validation + // ═══════════════════════════════════════════════ + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void PlusDi_MatchesTalib_VariousPeriods(int period) + { + var indicator = new PlusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MinusDi_MatchesTalib_VariousPeriods(int period) + { + var indicator = new MinusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void PlusDm_MatchesTalib_VariousPeriods(int period) + { + var indicator = new PlusDm(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MinusDm_MatchesTalib_VariousPeriods(int period) + { + var indicator = new MinusDm(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Multi-Period Skender Validation + // ═══════════════════════════════════════════════ + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void PlusDi_MatchesSkender_VariousPeriods(int period) + { + var indicator = new PlusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(period).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Pdi); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MinusDi_MatchesSkender_VariousPeriods(int period) + { + var indicator = new MinusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(period).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Mdi); + } + + // ═══════════════════════════════════════════════ + // Determinism: Consistent Across Multiple Runs + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_ConsistentAcrossMultipleRuns() + { + var ind1 = new PlusDi(14); + var ind2 = new PlusDi(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + [Fact] + public void MinusDi_ConsistentAcrossMultipleRuns() + { + var ind1 = new MinusDi(14); + var ind2 = new MinusDi(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + [Fact] + public void PlusDm_ConsistentAcrossMultipleRuns() + { + var ind1 = new PlusDm(14); + var ind2 = new PlusDm(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + [Fact] + public void MinusDm_ConsistentAcrossMultipleRuns() + { + var ind1 = new MinusDm(14); + var ind2 = new MinusDm(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + // ═══════════════════════════════════════════════ + // Non-Negative Output Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_OutputIsNonNegative() + { + var indicator = new PlusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"PlusDi output at bar {i} was {indicator.Last.Value}"); + } + } + + [Fact] + public void MinusDi_OutputIsNonNegative() + { + var indicator = new MinusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"MinusDi output at bar {i} was {indicator.Last.Value}"); + } + } + + [Fact] + public void PlusDm_OutputIsNonNegative() + { + var indicator = new PlusDm(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"PlusDm output at bar {i} was {indicator.Last.Value}"); + } + } + + [Fact] + public void MinusDm_OutputIsNonNegative() + { + var indicator = new MinusDm(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"MinusDm output at bar {i} was {indicator.Last.Value}"); + } + } + + // ═══════════════════════════════════════════════ + // DI values bounded 0-100 + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_OutputBounded0To100() + { + var indicator = new PlusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + double val = indicator.Last.Value; + if (i >= 14) + { + Assert.True(val >= 0 && val <= 100, $"PlusDi at bar {i} was {val}, expected [0,100]"); + } + } + } + + [Fact] + public void MinusDi_OutputBounded0To100() + { + var indicator = new MinusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + double val = indicator.Last.Value; + if (i >= 14) + { + Assert.True(val >= 0 && val <= 100, $"MinusDi at bar {i} was {val}, expected [0,100]"); + } + } + } + + // ═══════════════════════════════════════════════ + // Different Periods Produce Different Results + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_DifferentPeriods_ProduceDifferentResults() + { + var short14 = new PlusDi(7); + var long28 = new PlusDi(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short14.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short14.Last.Value, long28.Last.Value); + } + + [Fact] + public void MinusDi_DifferentPeriods_ProduceDifferentResults() + { + var short14 = new MinusDi(7); + var long28 = new MinusDi(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short14.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short14.Last.Value, long28.Last.Value); + } + + [Fact] + public void PlusDm_DifferentPeriods_ProduceDifferentResults() + { + var short14 = new PlusDm(7); + var long28 = new PlusDm(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short14.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short14.Last.Value, long28.Last.Value); + } + + [Fact] + public void MinusDm_DifferentPeriods_ProduceDifferentResults() + { + var short14 = new MinusDm(7); + var long28 = new MinusDm(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short14.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short14.Last.Value, long28.Last.Value); + } + + // ═══════════════════════════════════════════════ + // OoplesFinance Structural Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void DiDm_MatchesOoples_Structural() + { + // OoplesFinance.CalculateAverageDirectionalIndex produces Di+/Di- as part of ADX + var ooplesData = _data.SkenderQuotes + .Select(q => new TickerData + { + Date = q.Date, + Open = (double)q.Open, + High = (double)q.High, + Low = (double)q.Low, + Close = (double)q.Close, + Volume = (double)q.Volume + }) + .ToList(); + + var stockData = new StockData(ooplesData); + var adxResults = stockData.CalculateAverageDirectionalIndex(MovingAvgType.WildersSmoothingMethod, 14); + + // Verify the Ooples ADX calculation produces finite DI values + var allValues = adxResults.OutputValues.Values.SelectMany(v => v).ToList(); + int finiteCount = allValues.Count(v => double.IsFinite(v)); + Assert.True(finiteCount > 100, $"Expected >100 finite Ooples DI/DM values, got {finiteCount}"); + } + + // ═══════════════════════════════════════════════ + // Batch Matches TALib + // ═══════════════════════════════════════════════ + + [Fact] + public void PlusDi_BatchMatchesTalib() + { + var batchResults = PlusDi.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + [Fact] + public void MinusDi_BatchMatchesTalib() + { + var batchResults = MinusDi.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDILookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + [Fact] + public void PlusDm_BatchMatchesTalib() + { + var batchResults = PlusDm.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + [Fact] + public void MinusDm_BatchMatchesTalib() + { + var batchResults = MinusDm.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.MinusDMLookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } +} diff --git a/lib/dynamics/plusdi/PlusDi.Quantower.Tests.cs b/lib/dynamics/plusdi/PlusDi.Quantower.Tests.cs new file mode 100644 index 00000000..e3fbae1c --- /dev/null +++ b/lib/dynamics/plusdi/PlusDi.Quantower.Tests.cs @@ -0,0 +1,73 @@ +using TradingPlatform.BusinessLayer; +using QuanTAlib; + +namespace QuanTAlib.Tests; + +public class PlusDiIndicatorTests +{ + [Fact] + public void PlusDiIndicator_Constructor_SetsDefaults() + { + var indicator = new PlusDiIndicator(); + + Assert.Equal(14, indicator.Period); + Assert.True(indicator.ShowColdValues); + Assert.Equal("+DI - Plus Directional Indicator", indicator.Name); + Assert.True(indicator.SeparateWindow); + Assert.True(indicator.OnBackGround); + } + + [Fact] + public void PlusDiIndicator_MinHistoryDepths_EqualsZero() + { + var indicator = new PlusDiIndicator { Period = 20 }; + + Assert.Equal(0, PlusDiIndicator.MinHistoryDepths); + IWatchlistIndicator watchlistIndicator = indicator; + Assert.Equal(0, watchlistIndicator.MinHistoryDepths); + } + + [Fact] + public void PlusDiIndicator_Initialize_CreatesInternal() + { + var indicator = new PlusDiIndicator { Period = 14 }; + + indicator.Initialize(); + + Assert.Single(indicator.LinesSeries); + } + + [Fact] + public void PlusDiIndicator_ProcessUpdate_HistoricalBar_ComputesValue() + { + var indicator = new PlusDiIndicator { Period = 5 }; + indicator.Initialize(); + + var now = DateTime.UtcNow; + for (int i = 0; i < 20; i++) + { + indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i); + + var args = new UpdateArgs(UpdateReason.HistoricalBar); + indicator.ProcessUpdate(args); + } + + double value = indicator.LinesSeries[0].GetValue(0); + Assert.True(double.IsFinite(value)); + } + + [Fact] + public void PlusDiIndicator_ShortName_IsCorrect() + { + var indicator = new PlusDiIndicator { Period = 20 }; + Assert.Equal("+DI 20", indicator.ShortName); + } + + [Fact] + public void PlusDiIndicator_SourceCodeLink_IsValid() + { + var indicator = new PlusDiIndicator(); + Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + Assert.Contains("PlusDi.Quantower.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/lib/dynamics/plusdi/PlusDi.Quantower.cs b/lib/dynamics/plusdi/PlusDi.Quantower.cs new file mode 100644 index 00000000..0c8f28ac --- /dev/null +++ b/lib/dynamics/plusdi/PlusDi.Quantower.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +[SkipLocalsInit] +public sealed class PlusDiIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private PlusDi _plusDi = null!; + private readonly LineSeries _plusDiSeries; + + public static int MinHistoryDepths => 0; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"+DI {Period}"; + public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/dynamics/plusdi/PlusDi.Quantower.cs"; + + public PlusDiIndicator() + { + OnBackGround = true; + SeparateWindow = true; + Name = "+DI - Plus Directional Indicator"; + Description = "Measures upward directional movement as a percentage of true range"; + + _plusDiSeries = new LineSeries(name: "+DI", color: Color.Green, width: 2, style: LineStyle.Solid); + + AddLineSeries(_plusDiSeries); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnInit() + { + _plusDi = new PlusDi(Period); + base.OnInit(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnUpdate(UpdateArgs args) + { + TValue result = _plusDi.Update(this.GetInputBar(args), args.IsNewBar()); + + _plusDiSeries.SetValue(result.Value, _plusDi.IsHot, ShowColdValues); + } +} diff --git a/lib/dynamics/plusdi/PlusDi.Validation.Tests.cs b/lib/dynamics/plusdi/PlusDi.Validation.Tests.cs new file mode 100644 index 00000000..ad93499f --- /dev/null +++ b/lib/dynamics/plusdi/PlusDi.Validation.Tests.cs @@ -0,0 +1,288 @@ +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using OoplesFinance.StockIndicators.Enums; +using Skender.Stock.Indicators; +using TALib; +using QuanTAlib.Tests; + +namespace QuanTAlib; + +/// +/// Validation tests for PlusDi (+DI). Cross-validates against TA-Lib, Skender, +/// OoplesFinance, and internal Dx equivalence with multiple periods. +/// +public sealed class PlusDiValidationTests : IDisposable +{ + private readonly ValidationTestData _data; + + public PlusDiValidationTests() + { + _data = new ValidationTestData(); + } + + public void Dispose() + { + _data.Dispose(); + } + + // ═══════════════════════════════════════════════ + // TA-Lib Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesTalib() + { + var indicator = new PlusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesTalib_VariousPeriods(int period) + { + var indicator = new PlusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Skender Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesSkender() + { + var indicator = new PlusDi(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Pdi); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesSkender_VariousPeriods(int period) + { + var indicator = new PlusDi(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + var skenderResults = _data.SkenderQuotes.GetAdx(period).ToList(); + ValidationHelper.VerifyData(results, skenderResults, x => x.Pdi); + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence + // ═══════════════════════════════════════════════ + + [Fact] + public void ExactlyMatchesDx_DiPlus() + { + var indicator = new PlusDi(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DiPlus.Value, indicator.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // OoplesFinance Structural Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesOoples_Structural() + { + var ooplesData = _data.SkenderQuotes + .Select(q => new TickerData + { + Date = q.Date, + Open = (double)q.Open, + High = (double)q.High, + Low = (double)q.Low, + Close = (double)q.Close, + Volume = (double)q.Volume + }) + .ToList(); + + var stockData = new StockData(ooplesData); + var adxResults = stockData.CalculateAverageDirectionalIndex(MovingAvgType.WildersSmoothingMethod, 14); + + var allValues = adxResults.OutputValues.Values.SelectMany(v => v).ToList(); + int finiteCount = allValues.Count(v => double.IsFinite(v)); + Assert.True(finiteCount > 100, $"Expected >100 finite Ooples DI values, got {finiteCount}"); + } + + // ═══════════════════════════════════════════════ + // Self-Consistency: Batch == Streaming + // ═══════════════════════════════════════════════ + + [Fact] + public void BatchEqualsStreaming() + { + var batchResults = PlusDi.Batch(_data.Bars, 14); + + var streaming = new PlusDi(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void BatchMatchesTalib() + { + var batchResults = PlusDi.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDILookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Determinism + // ═══════════════════════════════════════════════ + + [Fact] + public void ConsistentAcrossMultipleRuns() + { + var ind1 = new PlusDi(14); + var ind2 = new PlusDi(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + // ═══════════════════════════════════════════════ + // Output Range Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void OutputIsNonNegative() + { + var indicator = new PlusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"+DI output at bar {i} was {indicator.Last.Value}"); + } + } + + [Fact] + public void OutputBounded0To100() + { + var indicator = new PlusDi(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + double val = indicator.Last.Value; + if (i >= 14) + { + Assert.True(val >= 0 && val <= 100, $"+DI at bar {i} was {val}, expected [0,100]"); + } + } + } + + // ═══════════════════════════════════════════════ + // Different Periods Produce Different Results + // ═══════════════════════════════════════════════ + + [Fact] + public void DifferentPeriods_ProduceDifferentResults() + { + var short7 = new PlusDi(7); + var long28 = new PlusDi(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short7.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short7.Last.Value, long28.Last.Value); + } +} diff --git a/lib/dynamics/plusdi/PlusDi.md b/lib/dynamics/plusdi/PlusDi.md index b2edce7b..a398989b 100644 --- a/lib/dynamics/plusdi/PlusDi.md +++ b/lib/dynamics/plusdi/PlusDi.md @@ -1,30 +1,100 @@ # PLUS_DI: Plus Directional Indicator -Measures upward directional movement strength as a percentage (0-100). +> *+DI isolates upward directional thrust as a fraction of true range — the bullish arm of Wilder's directional system.* -## Introduction -The Plus Directional Indicator (+DI) measures the strength of upward price movement relative to the true range. It is one of the components of the Directional Movement System developed by J. Welles Wilder Jr. +| Property | Value | +| ---------------- | -------------------------------- | +| **Category** | Dynamic | +| **Inputs** | OHLCV bar (TBar) | +| **Parameters** | `period` (default 14) | +| **Outputs** | Single series | +| **Output range** | 0 to 100 | +| **Warmup** | `period` bars | +| **PineScript** | [plusdi.pine](plusdi.pine) | -When +DI is rising, upward price pressure is increasing. When +DI crosses above -DI, it signals a potential bullish trend. The +DI line is commonly plotted alongside -DI to visualize directional balance. +- The Plus Directional Indicator measures the strength of upward price movement relative to true range. +- Parameterized by `period` (default 14). +- Output range: 0 to 100. +- Requires `period` bars of warmup before first valid output (IsHot = true). +- Validated against TA-Lib, Skender, and Dx equivalence. -## Calculation -+DI = Smoothed(+DM) / Smoothed(TR) × 100 +The Plus Directional Indicator (+DI) is one component of J. Welles Wilder Jr.'s Directional Movement System. It quantifies the fraction of recent true range attributable to upward price extension. The computation smooths both +DM (plus directional movement) and TR (true range) with Wilder's RMA ($\alpha = 1/N$), then divides: $+DI = 100 \times \text{Smooth}(+DM) / \text{Smooth}(TR)$. When +DI rises, upward price pressure is increasing. When +DI crosses above -DI, it signals a potential bullish trend. The +DI line is commonly plotted alongside -DI to visualize directional balance. -Where: -- +DM (Plus Directional Movement) = max(High - PrevHigh, 0) when High - PrevHigh > PrevLow - Low, else 0 -- TR (True Range) = max(High - Low, |High - PrevClose|, |Low - PrevClose|) -- Smoothing uses Wilder's method: Smooth = Smooth - Smooth/N + Input +## Historical Context -## Parameters -| Parameter | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| Period | 14 | 2-∞ | Wilder smoothing period | +J. Welles Wilder Jr. introduced the Directional Movement System in *New Concepts in Technical Trading Systems* (1978). The system decomposes price range into directional components. +DI and -DI are the normalized indicators from which DX and ADX are derived. While most traders focus on ADX for trend strength, +DI and -DI remain essential for determining trend *direction* — a bullish signal occurs when +DI crosses above -DI, bearish when -DI crosses above +DI. -## Interpretation -- **Rising +DI:** Strengthening upward movement -- **+DI > -DI:** Bulls dominate; potential uptrend -- **+DI crossover above -DI:** Bullish signal -- **High +DI (>40):** Strong upward momentum +## Architecture & Physics -## References -- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978) +### 1. Plus Directional Movement + +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ + +$$+DM = \begin{cases} \text{UpMove} & \text{if UpMove} > \text{DownMove and UpMove} > 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. Wilder Smoothing (RMA) + +$$+DM_{\text{smooth}} = \text{RMA}(+DM, N), \quad TR_{\text{smooth}} = \text{RMA}(TR, N)$$ + +### 4. Plus Directional Indicator + +$$+DI = 100 \times \frac{+DM_{\text{smooth}}}{TR_{\text{smooth}}}$$ + +When $TR_{\text{smooth}} = 0$ (no price movement), +DI = 0. + +### 5. Complexity + +- **Time:** $O(1)$ per bar — all RMA updates are recursive +- **Space:** $O(1)$ — scalar state only (delegates to Dx) +- **Warmup:** $N$ bars + +## Mathematical Foundation + +### Parameters + +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | + +### Interpretation + +| +DI Value | Signal | +|-----------|--------| +| Rising +DI | Strengthening upward movement | +| +DI > -DI | Bulls dominate; potential uptrend | +| +DI crossover above -DI | Bullish signal | +| High +DI (>40) | Strong upward momentum | + ++DI measures directional *strength*, not absolute direction. Compare +DI vs -DI for directional bias: if $+DI > -DI$, the trend is up. + +## Performance Profile + +### Operation Count (Streaming Mode) + ++DI is a thin wrapper around Dx. The per-bar cost is identical to Dx (one property extraction after Dx completes its update). + +**Post-warmup steady state (per bar):** + +| Operation | Count | Cost (cycles) | Subtotal | +| :--- | :---: | :---: | :---: | +| Dx.Update (full pipeline) | 1 | 75 | 75 | +| Property extraction | 1 | 1 | 1 | +| **Total** | **2** | — | **~76 cycles** | + +### Quality Metrics + +| Metric | Score | Notes | +| :--- | :---: | :--- | +| **Accuracy** | 9/10 | Exact Dx delegation; FMA-precise RMA smoothing | +| **Timeliness** | 7/10 | N-bar warmup; responds to bar-level changes | +| **Smoothness** | 7/10 | Single RMA layer; moderate noise suppression | +| **Noise Rejection** | 7/10 | Wilder smoothing filters transient spikes | + +## Resources + +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `plusdi.pine` in indicator directory diff --git a/lib/dynamics/plusdi/plusdi.pine b/lib/dynamics/plusdi/plusdi.pine new file mode 100644 index 00000000..92699124 --- /dev/null +++ b/lib/dynamics/plusdi/plusdi.pine @@ -0,0 +1,55 @@ +// Licensed under the Apache License, Version 2.0 +// © mihakralj +//@version=6 +indicator("Plus Directional Indicator (+DI)", "+DI", overlay=false) + +//@function Calculates +DI using Wilder's smoothing with compensated RMA +//@param period Number of bars used in the calculation +//@returns +DI value (0-100) +//@optimized Uses Wilder's smoothing (RMA) with warmup compensation for accurate values from bar 1 +plusdi(simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + float alpha = 1.0 / period + float beta = 1.0 - alpha + float tr = 0.0 + float plus_dm = 0.0 + if na(close[1]) + tr := high - low + else + tr := math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) + float upMove = high - high[1] + float downMove = low[1] - low + if upMove > downMove and upMove > 0 + plus_dm := upMove + var bool warmup = true + var float e = 1.0 + var float tr_ema = 0.0 + var float tr_result = tr + var float plus_dm_ema = 0.0 + var float plus_dm_result = plus_dm + tr_ema := alpha * (tr - tr_ema) + tr_ema + plus_dm_ema := alpha * (plus_dm - plus_dm_ema) + plus_dm_ema + if warmup + e *= beta + float c = 1.0 / (1.0 - e) + tr_result := c * tr_ema + plus_dm_result := c * plus_dm_ema + warmup := e > 1e-10 + else + tr_result := tr_ema + plus_dm_result := plus_dm_ema + float plus_di = tr_result != 0.0 ? 100.0 * plus_dm_result / tr_result : 0.0 + plus_di + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1, tooltip="Number of bars used in the calculation") + +// Calculation +plus_di = plusdi(i_period) + +// Plot +plot(plus_di, "+DI", color=color.green, linewidth=2) +hline(25, "Threshold", color=color.gray, linestyle=hline.style_dashed) diff --git a/lib/dynamics/plusdm/PlusDm.Quantower.Tests.cs b/lib/dynamics/plusdm/PlusDm.Quantower.Tests.cs new file mode 100644 index 00000000..dc1f9319 --- /dev/null +++ b/lib/dynamics/plusdm/PlusDm.Quantower.Tests.cs @@ -0,0 +1,73 @@ +using TradingPlatform.BusinessLayer; +using QuanTAlib; + +namespace QuanTAlib.Tests; + +public class PlusDmIndicatorTests +{ + [Fact] + public void PlusDmIndicator_Constructor_SetsDefaults() + { + var indicator = new PlusDmIndicator(); + + Assert.Equal(14, indicator.Period); + Assert.True(indicator.ShowColdValues); + Assert.Equal("+DM - Plus Directional Movement", indicator.Name); + Assert.True(indicator.SeparateWindow); + Assert.True(indicator.OnBackGround); + } + + [Fact] + public void PlusDmIndicator_MinHistoryDepths_EqualsZero() + { + var indicator = new PlusDmIndicator { Period = 20 }; + + Assert.Equal(0, PlusDmIndicator.MinHistoryDepths); + IWatchlistIndicator watchlistIndicator = indicator; + Assert.Equal(0, watchlistIndicator.MinHistoryDepths); + } + + [Fact] + public void PlusDmIndicator_Initialize_CreatesInternal() + { + var indicator = new PlusDmIndicator { Period = 14 }; + + indicator.Initialize(); + + Assert.Single(indicator.LinesSeries); + } + + [Fact] + public void PlusDmIndicator_ProcessUpdate_HistoricalBar_ComputesValue() + { + var indicator = new PlusDmIndicator { Period = 5 }; + indicator.Initialize(); + + var now = DateTime.UtcNow; + for (int i = 0; i < 20; i++) + { + indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i); + + var args = new UpdateArgs(UpdateReason.HistoricalBar); + indicator.ProcessUpdate(args); + } + + double value = indicator.LinesSeries[0].GetValue(0); + Assert.True(double.IsFinite(value)); + } + + [Fact] + public void PlusDmIndicator_ShortName_IsCorrect() + { + var indicator = new PlusDmIndicator { Period = 20 }; + Assert.Equal("+DM 20", indicator.ShortName); + } + + [Fact] + public void PlusDmIndicator_SourceCodeLink_IsValid() + { + var indicator = new PlusDmIndicator(); + Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + Assert.Contains("PlusDm.Quantower.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/lib/dynamics/plusdm/PlusDm.Quantower.cs b/lib/dynamics/plusdm/PlusDm.Quantower.cs new file mode 100644 index 00000000..1c86c800 --- /dev/null +++ b/lib/dynamics/plusdm/PlusDm.Quantower.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +[SkipLocalsInit] +public sealed class PlusDmIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private PlusDm _plusDm = null!; + private readonly LineSeries _plusDmSeries; + + public static int MinHistoryDepths => 0; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"+DM {Period}"; + public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/dynamics/plusdm/PlusDm.Quantower.cs"; + + public PlusDmIndicator() + { + OnBackGround = true; + SeparateWindow = true; + Name = "+DM - Plus Directional Movement"; + Description = "Wilder-smoothed upward directional movement in price units"; + + _plusDmSeries = new LineSeries(name: "+DM", color: Color.Green, width: 2, style: LineStyle.Solid); + + AddLineSeries(_plusDmSeries); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnInit() + { + _plusDm = new PlusDm(Period); + base.OnInit(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void OnUpdate(UpdateArgs args) + { + TValue result = _plusDm.Update(this.GetInputBar(args), args.IsNewBar()); + + _plusDmSeries.SetValue(result.Value, _plusDm.IsHot, ShowColdValues); + } +} diff --git a/lib/dynamics/plusdm/PlusDm.Validation.Tests.cs b/lib/dynamics/plusdm/PlusDm.Validation.Tests.cs new file mode 100644 index 00000000..d320269f --- /dev/null +++ b/lib/dynamics/plusdm/PlusDm.Validation.Tests.cs @@ -0,0 +1,231 @@ +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using OoplesFinance.StockIndicators.Enums; +using Skender.Stock.Indicators; +using TALib; +using QuanTAlib.Tests; + +namespace QuanTAlib; + +/// +/// Validation tests for PlusDm (+DM). Cross-validates against TA-Lib, +/// OoplesFinance, and internal Dx equivalence with multiple periods. +/// Note: Skender does not expose DM values directly; only DI values via GetAdx(). +/// +public sealed class PlusDmValidationTests : IDisposable +{ + private readonly ValidationTestData _data; + + public PlusDmValidationTests() + { + _data = new ValidationTestData(); + } + + public void Dispose() + { + _data.Dispose(); + } + + // ═══════════════════════════════════════════════ + // TA-Lib Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesTalib() + { + var indicator = new PlusDm(14); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(14); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + [Theory] + [InlineData(7)] + [InlineData(21)] + [InlineData(28)] + public void MatchesTalib_VariousPeriods(int period) + { + var indicator = new PlusDm(period); + var results = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + results.Add(indicator.Last.Value); + } + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, period); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(period); + ValidationHelper.VerifyData(results, outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Dx Equivalence + // ═══════════════════════════════════════════════ + + [Fact] + public void ExactlyMatchesDx_DmPlus() + { + var indicator = new PlusDm(14); + var dx = new Dx(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + dx.Update(_data.Bars[i]); + + Assert.Equal(dx.DmPlus.Value, indicator.Last.Value, 1e-12); + } + } + + // ═══════════════════════════════════════════════ + // OoplesFinance Structural Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void MatchesOoples_Structural() + { + var ooplesData = _data.SkenderQuotes + .Select(q => new TickerData + { + Date = q.Date, + Open = (double)q.Open, + High = (double)q.High, + Low = (double)q.Low, + Close = (double)q.Close, + Volume = (double)q.Volume + }) + .ToList(); + + var stockData = new StockData(ooplesData); + var adxResults = stockData.CalculateAverageDirectionalIndex(MovingAvgType.WildersSmoothingMethod, 14); + + var allValues = adxResults.OutputValues.Values.SelectMany(v => v).ToList(); + int finiteCount = allValues.Count(v => double.IsFinite(v)); + Assert.True(finiteCount > 100, $"Expected >100 finite Ooples ADX/DI values, got {finiteCount}"); + } + + // ═══════════════════════════════════════════════ + // Self-Consistency: Batch == Streaming + // ═══════════════════════════════════════════════ + + [Fact] + public void BatchEqualsStreaming() + { + var batchResults = PlusDm.Batch(_data.Bars, 14); + + var streaming = new PlusDm(14); + var streamResults = new List(); + for (int i = 0; i < _data.Bars.Count; i++) + { + streamResults.Add(streaming.Update(_data.Bars[i]).Value); + } + + Assert.Equal(streamResults.Count, batchResults.Count); + for (int i = 0; i < batchResults.Count; i++) + { + Assert.Equal(streamResults[i], batchResults.Values[i], 1e-9); + } + } + + [Fact] + public void BatchMatchesTalib() + { + var batchResults = PlusDm.Batch(_data.Bars, 14); + + double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); + double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); + double[] outReal = new double[_data.Bars.Count]; + + var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14); + Assert.Equal(TALib.Core.RetCode.Success, retCode); + + int lookback = Functions.PlusDMLookback(14); + ValidationHelper.VerifyData(batchResults.Select(x => x.Value).ToList(), outReal, outRange, lookback); + } + + // ═══════════════════════════════════════════════ + // Determinism + // ═══════════════════════════════════════════════ + + [Fact] + public void ConsistentAcrossMultipleRuns() + { + var ind1 = new PlusDm(14); + var ind2 = new PlusDm(14); + var results1 = new List(); + var results2 = new List(); + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind1.Update(_data.Bars[i]); + results1.Add(ind1.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + ind2.Update(_data.Bars[i]); + results2.Add(ind2.Last.Value); + } + + for (int i = 0; i < _data.Bars.Count; i++) + { + Assert.Equal(results1[i], results2[i], 1e-10); + } + } + + // ═══════════════════════════════════════════════ + // Output Range Validation + // ═══════════════════════════════════════════════ + + [Fact] + public void OutputIsNonNegative() + { + var indicator = new PlusDm(14); + + for (int i = 0; i < _data.Bars.Count; i++) + { + indicator.Update(_data.Bars[i]); + Assert.True(indicator.Last.Value >= 0, $"+DM output at bar {i} was {indicator.Last.Value}"); + } + } + + // ═══════════════════════════════════════════════ + // Different Periods Produce Different Results + // ═══════════════════════════════════════════════ + + [Fact] + public void DifferentPeriods_ProduceDifferentResults() + { + var short7 = new PlusDm(7); + var long28 = new PlusDm(28); + + for (int i = 0; i < _data.Bars.Count; i++) + { + short7.Update(_data.Bars[i]); + long28.Update(_data.Bars[i]); + } + + Assert.NotEqual(short7.Last.Value, long28.Last.Value); + } +} diff --git a/lib/dynamics/plusdm/PlusDm.md b/lib/dynamics/plusdm/PlusDm.md index a5e6e8e0..a5d6c7d8 100644 --- a/lib/dynamics/plusdm/PlusDm.md +++ b/lib/dynamics/plusdm/PlusDm.md @@ -1,27 +1,92 @@ # PLUS_DM: Plus Directional Movement -Wilder-smoothed upward directional movement in price units (≥0). +> *+DM captures the raw upward price extension, smoothed by Wilder's RMA — the building block before normalization to +DI.* -## Introduction -Plus Directional Movement (+DM) measures the magnitude of upward price movement, smoothed using Wilder's method. Unlike +DI which normalizes by true range to produce a percentage, +DM outputs raw smoothed values in price units. +| Property | Value | +| ---------------- | -------------------------------- | +| **Category** | Dynamic | +| **Inputs** | OHLCV bar (TBar) | +| **Parameters** | `period` (default 14) | +| **Outputs** | Single series | +| **Output range** | ≥ 0 (price units) | +| **Warmup** | `period` bars | +| **PineScript** | [plusdm.pine](plusdm.pine) | -+DM captures when the current bar's high exceeds the previous bar's high by more than the previous bar's low exceeds the current bar's low. It is the raw building block of the Directional Movement System. +- Plus Directional Movement outputs the Wilder-smoothed upward directional movement in price units. +- Parameterized by `period` (default 14). +- Output range: ≥ 0 (price units, scales with instrument). +- Requires `period` bars of warmup before first valid output (IsHot = true). +- Validated against TA-Lib and Dx equivalence. -## Calculation -+DM = max(High - PrevHigh, 0) when High - PrevHigh > PrevLow - Low, else 0 +Plus Directional Movement (+DM) measures the magnitude of upward price movement, smoothed using Wilder's method. Unlike +DI which normalizes by true range to produce a percentage, +DM outputs raw smoothed values in price units. +DM captures when the current bar's high exceeds the previous bar's high by more than the previous bar's low exceeds the current bar's low. It is the raw building block of the Directional Movement System — the unnormalized signal before division by True Range converts it to +DI. -Smoothed using Wilder's method: Smooth = Smooth - Smooth/N + Input +## Historical Context -## Parameters -| Parameter | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| Period | 14 | 2-∞ | Wilder smoothing period | +J. Welles Wilder Jr. designed the Directional Movement System as a pipeline in *New Concepts in Technical Trading Systems* (1978). +DM is the first computational stage: a binary event detector that fires when upward range expansion dominates. Wilder's insight was that direction should be measured by range *extension*, not by close-to-close returns. A bar that pushes to a new high by more than it pushes to a new low registers as positive directional movement. The smoothed +DM series shows how much upward thrust is being sustained over the lookback period, in absolute price units. -## Interpretation -- **Rising +DM:** Increasing upward price extension -- **+DM > -DM:** Upward movement exceeds downward movement -- **Zero +DM:** No upward directional movement on the bar -- Values are in price units and scale with the instrument +## Architecture & Physics -## References -- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978) +### 1. Plus Directional Movement (Raw) + +$$\text{UpMove} = H_t - H_{t-1}, \quad \text{DownMove} = L_{t-1} - L_t$$ + +$$+DM = \begin{cases} \text{UpMove} & \text{if UpMove} > \text{DownMove and UpMove} > 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) + +$$+DM_{\text{smooth}} = \text{RMA}(+DM, N), \quad \alpha = 1/N$$ + +### 3. Complexity + +- **Time:** $O(1)$ per bar — recursive RMA update +- **Space:** $O(1)$ — scalar state only (delegates to Dx) +- **Warmup:** $N$ bars + +## Mathematical Foundation + +### Parameters + +| Symbol | Parameter | Default | Constraint | +|--------|-----------|---------|------------| +| $N$ | period | 14 | $N \geq 2$ | + +### Interpretation + +| +DM Behavior | Signal | +|--------------|--------| +| Rising +DM | Increasing upward price extension | +| +DM > -DM | Upward movement exceeds downward movement | +| Zero +DM | No upward directional movement on the bar | +| Values scale with instrument | Compare within same instrument only | + ++DM values are in price units and scale with the instrument. They cannot be compared across different instruments without normalization (which is what +DI provides). + +## Performance Profile + +### Operation Count (Streaming Mode) + ++DM is a thin wrapper around Dx. The per-bar cost is identical to Dx (one property extraction after Dx completes its update). + +**Post-warmup steady state (per bar):** + +| Operation | Count | Cost (cycles) | Subtotal | +| :--- | :---: | :---: | :---: | +| Dx.Update (full pipeline) | 1 | 75 | 75 | +| Property extraction | 1 | 1 | 1 | +| **Total** | **2** | — | **~76 cycles** | + +### Quality Metrics + +| Metric | Score | Notes | +| :--- | :---: | :--- | +| **Accuracy** | 9/10 | Exact Dx delegation; FMA-precise RMA smoothing | +| **Timeliness** | 7/10 | N-bar warmup; responds to bar-level changes | +| **Smoothness** | 7/10 | Single RMA layer; moderate noise suppression | +| **Noise Rejection** | 7/10 | Wilder smoothing filters transient spikes | + +## Resources + +- Wilder, J.W. — *New Concepts in Technical Trading Systems* (Trend Research, 1978) +- PineScript reference: `plusdm.pine` in indicator directory diff --git a/lib/dynamics/plusdm/plusdm.pine b/lib/dynamics/plusdm/plusdm.pine new file mode 100644 index 00000000..ec53468b --- /dev/null +++ b/lib/dynamics/plusdm/plusdm.pine @@ -0,0 +1,44 @@ +// Licensed under the Apache License, Version 2.0 +// © mihakralj +//@version=6 +indicator("Plus Directional Movement (+DM)", "+DM", overlay=false) + +//@function Calculates Wilder-smoothed +DM using compensated RMA +//@param period Number of bars used in the calculation +//@returns Smoothed +DM value in price units (≥0) +//@optimized Uses Wilder's smoothing (RMA) with warmup compensation for accurate values from bar 1 +plusdm(simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + float alpha = 1.0 / period + float beta = 1.0 - alpha + float plus_dm = 0.0 + if not na(high[1]) + float upMove = high - high[1] + float downMove = low[1] - low + if upMove > downMove and upMove > 0 + plus_dm := upMove + var bool warmup = true + var float e = 1.0 + var float plus_dm_ema = 0.0 + var float plus_dm_result = plus_dm + plus_dm_ema := alpha * (plus_dm - plus_dm_ema) + plus_dm_ema + if warmup + e *= beta + float c = 1.0 / (1.0 - e) + plus_dm_result := c * plus_dm_ema + warmup := e > 1e-10 + else + plus_dm_result := plus_dm_ema + plus_dm_result + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1, tooltip="Number of bars used in the calculation") + +// Calculation +plus_dm_value = plusdm(i_period) + +// Plot +plot(plus_dm_value, "+DM", color=color.green, linewidth=2) diff --git a/lib/dynamics/qstick/Qstick.md b/lib/dynamics/qstick/Qstick.md index 13acf05c..6d9b07e2 100644 --- a/lib/dynamics/qstick/Qstick.md +++ b/lib/dynamics/qstick/Qstick.md @@ -1,5 +1,7 @@ # QSTICK: Qstick Indicator +> *The average candlestick body reveals the market's true conviction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The average candlestick body reveals the market's true conviction." - 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 @@ -61,33 +61,6 @@ EMA mode uses O(1) space but weights recent bars more heavily than SMA. | period | int | 14 | > 0 | Lookback period for moving average | | useEma | bool | false | — | Use EMA (true) or SMA (false) | -### Pseudo-code - -``` -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 -``` - ### Zero-Crossing Interpretation | Condition | Meaning | diff --git a/lib/dynamics/ravi/Ravi.md b/lib/dynamics/ravi/Ravi.md index 0e7fa000..98bcbeef 100644 --- a/lib/dynamics/ravi/Ravi.md +++ b/lib/dynamics/ravi/Ravi.md @@ -1,5 +1,7 @@ # RAVI: Chande Range Action Verification Index +> *The simplest question in technical analysis is also the most important: is this market trending or not? RAVI answers it with two moving averages and a division.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `longPeriod` bars (default 65) of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest question in technical analysis is also the most important: is this market trending or not? RAVI answers it with two moving averages and a division." - RAVI (Range Action Verification Index) measures trend strength by computing the absolute percentage divergence between a short-period SMA and a long-period SMA. Created by Tushar Chande and published in *Beyond Technical Analysis* (Wiley, 2001), the indicator classifies markets into trending (RAVI > 3%) and ranging (RAVI < 3%) regimes using a single threshold. With default parameters (short=7, long=65), RAVI requires 65 bars of warmup for the first valid reading. The core computation is three operations per bar in streaming mode: two running-sum updates and one division. No square roots, no exponentials, no recursion. The entire indicator reduces to normalized SMA spread, making it one of the cheapest dynamics classifiers available. ## Historical Context diff --git a/lib/dynamics/super/Super.md b/lib/dynamics/super/Super.md index 2c71abbf..e8d305dc 100644 --- a/lib/dynamics/super/Super.md +++ b/lib/dynamics/super/Super.md @@ -1,5 +1,7 @@ # SUPER: SuperTrend +> *It's not an indicator; it's a trailing stop with a marketing budget.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `> period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "It's not an indicator; it's a trailing stop with a marketing budget." - 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 @@ -74,46 +74,6 @@ The output is the active stop level. Crossing the active band flips the state. | atrPeriod | int | 10 | > 0 | ATR lookback period | | multiplier | double | 3.0 | > 0 | ATR multiplier for band width | -### Pseudo-code - -``` -SUPERTREND(bar, atrPeriod=10, multiplier=3.0): - - // ATR update (Wilder's smoothing or SMA) - atr = ATR.Update(bar, atrPeriod) - - // Basic bands - hl2 = (bar.High + bar.Low) / 2 - upper_basic = hl2 + multiplier * atr - lower_basic = hl2 - multiplier * atr - - // 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 - - if prev_close >= prev_lower_final: - lower_final = max(lower_basic, prev_lower_final) - else: - lower_final = lower_basic - - // 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) - - // Output active stop level - if is_bullish: - supertrend = lower_final - else: - supertrend = upper_final - - return supertrend -``` - ### Band Behavior by State | State | Active Band | Ratchet Direction | Flip Condition | diff --git a/lib/dynamics/ttm_squeeze/TtmSqueeze.md b/lib/dynamics/ttm_squeeze/TtmSqueeze.md index 840d4cc7..613afd14 100644 --- a/lib/dynamics/ttm_squeeze/TtmSqueeze.md +++ b/lib/dynamics/ttm_squeeze/TtmSqueeze.md @@ -1,5 +1,7 @@ # TTM_SQUEEZE: TTM Squeeze +> *Volatility compression is the market holding its breath before screaming.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -15,8 +17,6 @@ - Requires `Math.Max(Math.Max(bbPeriod, kcPeriod), momPeriod)` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 @@ -85,40 +85,6 @@ The linear regression extracts the trend component of the deviation, filtering n | kcLength | int | 20 | > 1 | Keltner Channel period | | kcMult | double | 1.5 | > 0 | KC ATR multiplier | -### Pseudo-code - -``` -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) -``` - ### Squeeze-Fire Signal The critical trading signal occurs on the transition bar: diff --git a/lib/dynamics/ttm_trend/TtmTrend.md b/lib/dynamics/ttm_trend/TtmTrend.md index 52e2525b..87f4cf8b 100644 --- a/lib/dynamics/ttm_trend/TtmTrend.md +++ b/lib/dynamics/ttm_trend/TtmTrend.md @@ -1,5 +1,7 @@ # TTM_TREND: TTM Trend +> *The simplest trend indicator is the one you actually follow.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 @@ -75,36 +75,6 @@ This percentage rate-of-change quantifies how aggressively the trend is moving. |:----------|:-----|:--------|:-----------|:------------| | period | int | 6 | > 0 | EMA lookback period (very fast by default) | -### Pseudo-code - -``` -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) -``` - ### Period Selection 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). diff --git a/lib/dynamics/vhf/Vhf.md b/lib/dynamics/vhf/Vhf.md index 496b42b5..09f4a0b6 100644 --- a/lib/dynamics/vhf/Vhf.md +++ b/lib/dynamics/vhf/Vhf.md @@ -1,5 +1,7 @@ # VHF: Vertical Horizontal Filter +> *Before you ask which way the market is going, ask whether it is going anywhere at all. VHF answers the second question with a ratio and a ruler.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Before you ask which way the market is going, ask whether it is going anywhere at all. VHF answers the second question with a ratio and a ruler." - VHF (Vertical Horizontal Filter) measures trend strength by dividing the price range over $N$ periods by the total absolute bar-to-bar path distance over the same window. Created by Adam White and published in the August 1991 issue of *Futures* magazine, VHF produces a single positive value where higher readings indicate trending conditions and lower readings indicate choppy, range-bound markets. With the default period of 28, the indicator requires 29 close values for the first valid output. The core computation in streaming mode is O(1) per bar when implemented with deque-based min/max tracking and a running sum of absolute changes. No square roots, no exponentials, no recursion. VHF is one of the simplest and cheapest trend-strength classifiers available, requiring approximately 12 operations per bar at steady state. ## Historical Context diff --git a/lib/dynamics/vortex/Vortex.md b/lib/dynamics/vortex/Vortex.md index 338d99c1..13647115 100644 --- a/lib/dynamics/vortex/Vortex.md +++ b/lib/dynamics/vortex/Vortex.md @@ -1,5 +1,7 @@ # VORTEX: Vortex Indicator +> *When bulls and bears clash, the Vortex measures the violence.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Dynamic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When bulls and bears clash, the Vortex measures the violence." - 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 @@ -81,45 +81,6 @@ This yields O(1) per-bar updates after the initial warmup fill. |:----------|:-----|:--------|:-----------|:------------| | period | int | 14 | > 0 | Rolling window for VM and TR sums | -### Pseudo-code - -``` -VORTEX(bar, period=14): - - // Vortex movements (require previous bar) - vm_plus = abs(bar.High - prev_bar.Low) - vm_minus = abs(bar.Low - prev_bar.High) - - // True range - tr = max(bar.High - bar.Low, - abs(bar.High - prev_bar.Close), - abs(bar.Low - prev_bar.Close)) - - // 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 - - vm_plus_buffer.add(vm_plus) - vm_minus_buffer.add(vm_minus) - tr_buffer.add(tr) - - sum_vm_plus += vm_plus - sum_vm_minus += vm_minus - sum_tr += tr - - // 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 - - return (vi_plus, vi_minus) -``` - ### Reference Line at 1.0 The value 1.0 serves as a natural reference: diff --git a/lib/errors/_index.md b/lib/errors/_index.md index 367bda9a..d0ee89a8 100644 --- a/lib/errors/_index.md +++ b/lib/errors/_index.md @@ -1,7 +1,5 @@ # Errors -> "All models are wrong. Error metrics tell you how wrong." Adapted from George Box - Error metrics and loss functions for model/strategy evaluation. All error indicators accept two input series (actual and predicted values) and compute rolling error metrics over a configurable period. ## Indicators diff --git a/lib/errors/huber/Huber.md b/lib/errors/huber/Huber.md index 127886ce..2a6b0540 100644 --- a/lib/errors/huber/Huber.md +++ b/lib/errors/huber/Huber.md @@ -1,5 +1,7 @@ # Huber: Huber Loss +> *The Goldilocks of loss functions: not too sensitive, not too robust, just right.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Goldilocks of loss functions: not too sensitive, not too robust, just right." - Huber Loss is a hybrid loss function that combines the best properties of Mean Squared Error (MSE) and Mean Absolute Error (MAE). For small errors, it behaves quadratically like MSE; for large errors, it behaves linearly like MAE. ## Historical Context diff --git a/lib/errors/logcosh/LogCosh.md b/lib/errors/logcosh/LogCosh.md index 66044b39..83fcacb2 100644 --- a/lib/errors/logcosh/LogCosh.md +++ b/lib/errors/logcosh/LogCosh.md @@ -1,5 +1,7 @@ # Log-Cosh: Logarithm of Hyperbolic Cosine Loss +> *The smooth operator that acts like L2 for small errors and L1 for large ones.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The smooth operator that acts like L2 for small errors and L1 for large ones." - Log-Cosh Loss combines the best properties of L1 (absolute) and L2 (squared) error metrics through the logarithm of the hyperbolic cosine function. It provides smooth gradients everywhere while remaining robust to outliers. ## Historical Context diff --git a/lib/errors/maape/Maape.md b/lib/errors/maape/Maape.md index 686415ad..5b9bd138 100644 --- a/lib/errors/maape/Maape.md +++ b/lib/errors/maape/Maape.md @@ -1,5 +1,7 @@ # MAAPE: Mean Arctangent Absolute Percentage Error +> *When percentage errors need boundaries, arctangent provides the walls.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When percentage errors need boundaries, arctangent provides the walls." - Mean Arctangent Absolute Percentage Error (MAAPE) transforms percentage errors through the arctangent function, naturally bounding the metric between 0 and π/2. This eliminates the unbounded nature of MAPE while preserving its scale-independence. ## Historical Context diff --git a/lib/errors/mae/Mae.md b/lib/errors/mae/Mae.md index 2a601d01..3cf62387 100644 --- a/lib/errors/mae/Mae.md +++ b/lib/errors/mae/Mae.md @@ -1,5 +1,7 @@ # MAE: Mean Absolute Error +> *When you need to know how wrong you are on average, without the drama of squared errors.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When you need to know how wrong you are on average, without the drama of squared errors." - Mean Absolute Error (MAE) measures the average magnitude of errors in a set of predictions, without considering their direction. It represents the average of the absolute differences between actual and predicted values. ## Historical Context diff --git a/lib/errors/mapd/Mapd.md b/lib/errors/mapd/Mapd.md index 28ffb93c..99fa8141 100644 --- a/lib/errors/mapd/Mapd.md +++ b/lib/errors/mapd/Mapd.md @@ -1,5 +1,7 @@ # MAPD: Mean Absolute Percentage Deviation +> *Like MAPE, but divides by what you predicted instead of what actually happened.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Like MAPE, but divides by what you predicted instead of what actually happened." - Mean Absolute Percentage Deviation (MAPD) measures the average absolute percentage difference between actual and predicted values, using the predicted value as the denominator. This is the key difference from MAPE, which uses the actual value. ## Historical Context diff --git a/lib/errors/mape/Mape.md b/lib/errors/mape/Mape.md index df14c557..909ace2c 100644 --- a/lib/errors/mape/Mape.md +++ b/lib/errors/mape/Mape.md @@ -1,5 +1,7 @@ # MAPE: Mean Absolute Percentage Error +> *The metric that lets you compare apples to oranges, as long as you don't have any zeros.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The metric that lets you compare apples to oranges, as long as you don't have any zeros." - Mean Absolute Percentage Error (MAPE) measures the average absolute percentage difference between actual and predicted values. It expresses accuracy as a percentage, making it scale-independent and easy to interpret. ## Historical Context diff --git a/lib/errors/mase/Mase.md b/lib/errors/mase/Mase.md index 872c8097..a3c5bdb7 100644 --- a/lib/errors/mase/Mase.md +++ b/lib/errors/mase/Mase.md @@ -1,5 +1,7 @@ # MASE: Mean Absolute Scaled Error +> *A good forecast is one that's better than guessing. MASE tells you exactly how much better.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "A good forecast is one that's better than guessing. MASE tells you exactly how much better." - Mean Absolute Scaled Error (MASE) normalizes forecast errors by the average error of a naive "random walk" forecast (using the previous value as the prediction). This makes MASE scale-independent and interpretable across different time series. ## Architecture & Physics diff --git a/lib/errors/mdae/Mdae.md b/lib/errors/mdae/Mdae.md index 9d388a15..af239ccb 100644 --- a/lib/errors/mdae/Mdae.md +++ b/lib/errors/mdae/Mdae.md @@ -1,5 +1,7 @@ # MdAE: Median Absolute Error +> *When outliers scream but you need to hear the whisper of typical performance.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When outliers scream but you need to hear the whisper of typical performance." - Median Absolute Error (MdAE) measures the middle value of all absolute errors. Unlike MAE which averages errors, MdAE finds the median, providing exceptional robustness against outliers and extreme values. ## Historical Context diff --git a/lib/errors/mdape/Mdape.md b/lib/errors/mdape/Mdape.md index fbaf6bfb..392522e4 100644 --- a/lib/errors/mdape/Mdape.md +++ b/lib/errors/mdape/Mdape.md @@ -1,5 +1,7 @@ # MdAPE: Median Absolute Percentage Error +> *When you need relative errors but can't trust the outliers.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When you need relative errors but can't trust the outliers." - Median Absolute Percentage Error (MdAPE) combines the scale-independence of percentage errors with the robustness of median statistics. It provides a measure of typical relative prediction accuracy that remains stable even when some predictions are dramatically wrong. ## Historical Context diff --git a/lib/errors/me/Me.md b/lib/errors/me/Me.md index 2ae2fa37..1ad4009d 100644 --- a/lib/errors/me/Me.md +++ b/lib/errors/me/Me.md @@ -1,5 +1,7 @@ # ME: Mean Error (Mean Bias Error) +> *Sometimes you need to know not just how wrong you are, but which direction you're wrong in.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Sometimes you need to know not just how wrong you are, but which direction you're wrong in." - Mean Error (ME), also known as Mean Bias Error, measures the average error between actual and predicted values while preserving the sign. Unlike MAE, ME reveals systematic bias in predictions: whether a model consistently over-predicts or under-predicts. ## Historical Context diff --git a/lib/errors/mpe/Mpe.md b/lib/errors/mpe/Mpe.md index 90982513..5d6e12c9 100644 --- a/lib/errors/mpe/Mpe.md +++ b/lib/errors/mpe/Mpe.md @@ -1,5 +1,7 @@ # MPE: Mean Percentage Error +> *MAPE tells you how wrong you are; MPE tells you which direction you're wrong in.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "MAPE tells you how wrong you are; MPE tells you which direction you're wrong in." - Mean Percentage Error measures the average percentage difference between actual and predicted values while preserving the sign. Unlike MAPE, which takes absolute values, MPE reveals systematic bias in predictions—whether a model consistently over-predicts or under-predicts. ## Architecture & Physics diff --git a/lib/errors/mrae/Mrae.md b/lib/errors/mrae/Mrae.md index 83547c55..4d0de101 100644 --- a/lib/errors/mrae/Mrae.md +++ b/lib/errors/mrae/Mrae.md @@ -1,5 +1,7 @@ # MRAE: Mean Relative Absolute Error +> *When you need to understand your error in the context of what you're predicting.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When you need to understand your error in the context of what you're predicting." - Mean Relative Absolute Error (MRAE) measures the average magnitude of errors relative to the actual values. This normalization makes the metric scale-independent and easier to interpret across different datasets. ## Historical Context diff --git a/lib/errors/mse/Mse.md b/lib/errors/mse/Mse.md index 5776fdbe..9c00e278 100644 --- a/lib/errors/mse/Mse.md +++ b/lib/errors/mse/Mse.md @@ -1,5 +1,7 @@ # MSE: Mean Squared Error +> *The metric that makes outliers pay dearly for their transgressions.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The metric that makes outliers pay dearly for their transgressions." - Mean Squared Error (MSE) measures the average of the squares of the errors between actual and predicted values. By squaring errors, MSE penalizes large deviations more heavily than small ones. ## Historical Context diff --git a/lib/errors/msle/Msle.md b/lib/errors/msle/Msle.md index bf6c1dc7..30e5c427 100644 --- a/lib/errors/msle/Msle.md +++ b/lib/errors/msle/Msle.md @@ -1,5 +1,7 @@ # MSLE: Mean Squared Logarithmic Error +> *When your data spans orders of magnitude, MSLE keeps outliers from hijacking your loss function.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When your data spans orders of magnitude, MSLE keeps outliers from hijacking your loss function." - Mean Squared Logarithmic Error transforms both actual and predicted values through logarithms before computing squared error. This compression makes MSLE robust to outliers and particularly suited for data with exponential growth patterns or wide dynamic ranges. ## Architecture & Physics diff --git a/lib/errors/pseudohuber/PseudoHuber.md b/lib/errors/pseudohuber/PseudoHuber.md index 88f26728..877a68ba 100644 --- a/lib/errors/pseudohuber/PseudoHuber.md +++ b/lib/errors/pseudohuber/PseudoHuber.md @@ -1,5 +1,7 @@ # Pseudo-Huber: Smooth Huber Approximation +> *All the robustness of Huber, none of the discontinuities.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "All the robustness of Huber, none of the discontinuities." - Pseudo-Huber Loss (also called Charbonnier Loss) is a smooth approximation to the Huber loss function. Unlike Huber which has a piecewise definition with a kink at δ, Pseudo-Huber is continuously differentiable everywhere, making it ideal for gradient-based optimization. ## Historical Context diff --git a/lib/errors/quantileloss/QuantileLoss.md b/lib/errors/quantileloss/QuantileLoss.md index 3d4cce59..628eaa29 100644 --- a/lib/errors/quantileloss/QuantileLoss.md +++ b/lib/errors/quantileloss/QuantileLoss.md @@ -1,5 +1,7 @@ # Quantile Loss: Pinball Loss Function +> *When over-prediction and under-prediction carry different costs, quantiles find the balance.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When over-prediction and under-prediction carry different costs, quantiles find the balance." - Quantile Loss (also called Pinball Loss) measures prediction accuracy with asymmetric penalties for over-prediction versus under-prediction. It's essential for probabilistic forecasting where different quantiles of the distribution matter. ## Historical Context diff --git a/lib/errors/rae/Rae.md b/lib/errors/rae/Rae.md index e9951753..a47abb34 100644 --- a/lib/errors/rae/Rae.md +++ b/lib/errors/rae/Rae.md @@ -1,5 +1,7 @@ # RAE: Relative Absolute Error +> *How much better than just guessing the mean? RAE gives you the ratio.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "How much better than just guessing the mean? RAE gives you the ratio." - Relative Absolute Error (RAE) measures the total absolute error of predictions relative to the total absolute error of a simple baseline predictor that always predicts the mean of actual values. This provides a normalized performance metric. ## Architecture & Physics diff --git a/lib/errors/rmse/Rmse.md b/lib/errors/rmse/Rmse.md index 76d1ce97..921c96fc 100644 --- a/lib/errors/rmse/Rmse.md +++ b/lib/errors/rmse/Rmse.md @@ -1,5 +1,7 @@ # RMSE: Root Mean Squared Error +> *MSE's more interpretable sibling that speaks the language of your data.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "MSE's more interpretable sibling that speaks the language of your data." - Root Mean Squared Error (RMSE) is the square root of MSE, providing an error metric in the same units as the original data while retaining sensitivity to large errors. ## Mathematical Foundation diff --git a/lib/errors/rmsle/Rmsle.md b/lib/errors/rmsle/Rmsle.md index a7cd66f7..b0d2dbbe 100644 --- a/lib/errors/rmsle/Rmsle.md +++ b/lib/errors/rmsle/Rmsle.md @@ -1,5 +1,7 @@ # RMSLE: Root Mean Squared Logarithmic Error +> *RMSLE: because sometimes your errors need to be measured in decades, not dollars.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "RMSLE: because sometimes your errors need to be measured in decades, not dollars." - Root Mean Squared Logarithmic Error is the square root of MSLE, providing an error metric in log-scale units. This makes RMSLE more interpretable than MSLE while retaining all its benefits for data spanning multiple orders of magnitude. ## Architecture & Physics diff --git a/lib/errors/rse/Rse.md b/lib/errors/rse/Rse.md index 985f2c77..ef4fc194 100644 --- a/lib/errors/rse/Rse.md +++ b/lib/errors/rse/Rse.md @@ -1,5 +1,7 @@ # RSE: Relative Squared Error +> *The squared error version of RAE. RSE and R² are two sides of the same coin: R² = 1 - RSE.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The squared error version of RAE. RSE and R² are two sides of the same coin: R² = 1 - RSE." - Relative Squared Error (RSE) measures the total squared error of predictions relative to the total squared error of a simple baseline predictor that always predicts the mean. RSE is directly related to the coefficient of determination (R²). ## Architecture & Physics diff --git a/lib/errors/rsquared/Rsquared.md b/lib/errors/rsquared/Rsquared.md index 26eb0ffa..9001671e 100644 --- a/lib/errors/rsquared/Rsquared.md +++ b/lib/errors/rsquared/Rsquared.md @@ -1,5 +1,7 @@ # R²: Coefficient of Determination +> *R² tells you how much of the variance in actual values is explained by your predictions. It's the statistician's favorite metric for good reason.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "R² tells you how much of the variance in actual values is explained by your predictions. It's the statistician's favorite metric for good reason." - The Coefficient of Determination (R²) measures the proportion of variance in the actual values that is predictable from the predicted values. R² ranges from negative infinity to 1, where 1 indicates perfect predictions. ## Architecture & Physics diff --git a/lib/errors/smape/Smape.md b/lib/errors/smape/Smape.md index 022ab271..c7794968 100644 --- a/lib/errors/smape/Smape.md +++ b/lib/errors/smape/Smape.md @@ -1,5 +1,7 @@ # SMAPE: Symmetric Mean Absolute Percentage Error +> *MAPE punishes based on who's right; SMAPE punishes based on how different they are.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "MAPE punishes based on who's right; SMAPE punishes based on how different they are." - Symmetric Mean Absolute Percentage Error addresses a fundamental asymmetry in MAPE: the fact that over-predictions and under-predictions of the same magnitude receive different penalties. SMAPE uses the average of actual and predicted values in the denominator, creating a metric that treats both directions equally. ## Architecture & Physics diff --git a/lib/errors/theilu/TheilU.md b/lib/errors/theilu/TheilU.md index 48880da1..26b4541e 100644 --- a/lib/errors/theilu/TheilU.md +++ b/lib/errors/theilu/TheilU.md @@ -1,5 +1,7 @@ # Theil's U: Theil's U Statistic +> *The forecast that matters is the one that beats a naive guess.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The forecast that matters is the one that beats a naive guess." - Theil's U Statistic measures forecast accuracy relative to a naive no-change forecast. A value below 1 indicates the model outperforms simply predicting that tomorrow equals today; above 1 means you'd be better off not forecasting at all. ## Historical Context diff --git a/lib/errors/tukeybiweight/TukeyBiweight.md b/lib/errors/tukeybiweight/TukeyBiweight.md index 38834344..d076424f 100644 --- a/lib/errors/tukeybiweight/TukeyBiweight.md +++ b/lib/errors/tukeybiweight/TukeyBiweight.md @@ -1,5 +1,7 @@ # Tukey's Biweight: Robust Loss Function +> *When outliers need to be silenced, not just quieted.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When outliers need to be silenced, not just quieted." - Tukey's Biweight (also called Bisquare) is a redescending M-estimator that completely ignores errors beyond a threshold. Unlike Huber loss which still penalizes large errors linearly, Tukey's biweight treats extreme outliers as if they don't exist. ## Historical Context diff --git a/lib/errors/wmape/Wmape.md b/lib/errors/wmape/Wmape.md index d20eeef6..643a0d4b 100644 --- a/lib/errors/wmape/Wmape.md +++ b/lib/errors/wmape/Wmape.md @@ -1,5 +1,7 @@ # WMAPE: Weighted Mean Absolute Percentage Error +> *When not all errors are created equal, weight them by what matters.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When not all errors are created equal, weight them by what matters." - Weighted Mean Absolute Percentage Error (WMAPE) adjusts MAPE by weighting each error by the magnitude of the actual value. This produces a single, interpretable percentage that represents overall accuracy weighted by importance. ## Historical Context diff --git a/lib/errors/wrmse/Wrmse.md b/lib/errors/wrmse/Wrmse.md index 730900ef..4c437495 100644 --- a/lib/errors/wrmse/Wrmse.md +++ b/lib/errors/wrmse/Wrmse.md @@ -1,5 +1,7 @@ # WRMSE: Weighted Root Mean Squared Error +> *Not all errors are created equal—WRMSE lets you decide which ones matter most.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Error Metric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Not all errors are created equal—WRMSE lets you decide which ones matter most." - WRMSE extends the classic RMSE by incorporating weights for each observation, enabling analysts to emphasize critical data points such as recent observations, high-volume periods, or specific market regimes. When all weights are equal, WRMSE reduces exactly to RMSE, making it a strict generalization. This implementation uses dual RingBuffers for O(1) streaming updates with periodic resync to manage floating-point drift. ## Historical Context diff --git a/lib/filters/_index.md b/lib/filters/_index.md index e7667198..c5ae7c78 100644 --- a/lib/filters/_index.md +++ b/lib/filters/_index.md @@ -1,7 +1,5 @@ # Filters -> "All moving averages are low-pass filters. The question is which trade-offs you accept." John Ehlers - Signal processing filters adapted for financial time series. These are not indicators in the traditional sense: they are building blocks. Low-pass removes noise. High-pass isolates cycles. Band-pass extracts specific frequencies. Each filter type trades off smoothness, lag, and overshoot differently. ## Indicators diff --git a/lib/filters/agc/Agc.md b/lib/filters/agc/Agc.md index b6e05fc0..47b52b75 100644 --- a/lib/filters/agc/Agc.md +++ b/lib/filters/agc/Agc.md @@ -1,5 +1,7 @@ # AGC: Ehlers Automatic Gain Control +> *The purpose of the AGC is to normalize the amplitude of any indicator to unity.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `1` bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The purpose of the AGC is to normalize the amplitude of any indicator to unity." — John F. Ehlers, TASC January 2015 - ## Introduction The Automatic Gain Control normalizes any oscillating signal to the \[-1, +1\] range through exponential peak tracking. Unlike fixed-window normalization (min-max scaling), AGC adapts continuously: the peak decays exponentially each bar and ratchets up instantly when the signal exceeds the current peak. The result is amplitude-independent comparison of filter outputs across instruments and timeframes. Ehlers introduced AGC as the final stage of his "Universal Oscillator" — a signal-processing chain that converts any price series into a bounded, zero-mean indicator suitable for threshold-based trading signals. diff --git a/lib/filters/alaguerre/ALaguerre.md b/lib/filters/alaguerre/ALaguerre.md index fe0afb16..3d1911bc 100644 --- a/lib/filters/alaguerre/ALaguerre.md +++ b/lib/filters/alaguerre/ALaguerre.md @@ -1,5 +1,7 @@ # ALAGUERRE: Ehlers Adaptive Laguerre Filter +> *The best filter is one that knows when to listen closely and when to smooth aggressively.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `max(4, length)` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best filter is one that knows when to listen closely and when to smooth aggressively." -- John F. Ehlers (paraphrased) - ## Introduction The Adaptive Laguerre Filter extends Ehlers' four-element all-pass cascade by replacing the fixed damping factor with a per-bar adaptive alpha derived from tracking-error normalization. When price diverges from the filter output (trending conditions), alpha increases toward 1 for faster tracking. When price stays near the filter output (ranging conditions), alpha decreases toward 0 for heavier smoothing. The adaptation mechanism uses a highest/lowest normalization of the absolute tracking error over a lookback window, followed by median smoothing to prevent whipsaw in the coefficient. diff --git a/lib/filters/baxterking/BaxterKing.md b/lib/filters/baxterking/BaxterKing.md index d8a73731..71471468 100644 --- a/lib/filters/baxterking/BaxterKing.md +++ b/lib/filters/baxterking/BaxterKing.md @@ -1,5 +1,7 @@ # BK: Baxter-King Band-Pass Filter +> *The business cycle is whatever remains after you strip away the trend and the noise. Baxter and King figured out the stripping.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `2K+1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The business cycle is whatever remains after you strip away the trend and the noise. Baxter and King figured out the stripping." - The **Baxter-King Band-Pass Filter** is a symmetric finite impulse response (FIR) filter that approximates the ideal spectral band-pass by truncating the infinite sinc-like impulse response at lag $K$ and normalizing the weights to sum to zero. It extracts cyclical components with periodicities between $p_L$ (low) and $p_H$ (high) bars, rejecting both the DC trend and high-frequency noise. Output oscillates around zero with a fixed delay of $K$ bars. ## Historical Context diff --git a/lib/filters/bessel/Bessel.md b/lib/filters/bessel/Bessel.md index ca59d304..a5caf26a 100644 --- a/lib/filters/bessel/Bessel.md +++ b/lib/filters/bessel/Bessel.md @@ -1,5 +1,7 @@ # BESSEL: Bessel Filter +> *The Bessel filter preserves the shape of the input signal — maximum flatness in the time domain at the cost of a gentler rolloff.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/bilateral/Bilateral.md b/lib/filters/bilateral/Bilateral.md index 4f28e5b4..8c169817 100644 --- a/lib/filters/bilateral/Bilateral.md +++ b/lib/filters/bilateral/Bilateral.md @@ -1,5 +1,7 @@ # Bilateral Filter +> *Smoothing without blurring edges? It's not magic, it's just math.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Smoothing without blurring edges? It's not magic, it's just math." - The Bilateral Filter is a non-linear, edge-preserving, and noise-reducing smoothing filter. Unlike standard Gaussian filters that blur everything indiscriminately, the Bilateral Filter respects strong edges by weighting pixels based on both their spatial distance and their intensity difference (range). ## Historical Context diff --git a/lib/filters/bpf/Bpf.md b/lib/filters/bpf/Bpf.md index 22f6c323..ca09872a 100644 --- a/lib/filters/bpf/Bpf.md +++ b/lib/filters/bpf/Bpf.md @@ -1,5 +1,7 @@ # BPF (Bandpass Filter) +> *Most market data is noise. A sliver is signal. The rest is just detailed evidence of human panic.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `Math.Max(lowerPeriod, upperPeriod)` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Most market data is noise. A sliver is signal. The rest is just detailed evidence of human panic." - The **BPF** (BandPass Filter) is a second-order IIR architecture designed to surgically excise specific frequency components from a time series. By cascading a HighPass Filter (to reject trend) and a LowPass Filter (to reject noise), it isolates cyclic energy within a user-defined window. Unlike simple moving average crossovers which smear data, the BPF relies on Gaussian-based coefficients to achieve steeper roll-off with deterministic phase characteristics. ## Historical Context diff --git a/lib/filters/butter2/Butter2.md b/lib/filters/butter2/Butter2.md index c497ed79..4048a527 100644 --- a/lib/filters/butter2/Butter2.md +++ b/lib/filters/butter2/Butter2.md @@ -1,5 +1,7 @@ # BUTTER2: Ehlers 2-Pole Butterworth Filter +> *Maximally flat frequency response in the passband.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `4 * period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Maximally flat frequency response in the passband." - The 2-Pole Butterworth Filter (BUTTER2) is a signal processing tool designed to provide maximally flat frequency response in the passband. Developed by British engineer Stephen Butterworth in 1930, it offers traders a means to smooth price data without introducing ripples in the frequency response. This implementation provides a 2nd-order low-pass filter that effectively removes high-frequency market noise while preserving lower-frequency trend components. Compared to other filters, Butterworth offers an optimal compromise between smoothing efficiency and signal fidelity, making it a versatile choice for various market conditions. ## Core Concepts diff --git a/lib/filters/butter3/Butter3.md b/lib/filters/butter3/Butter3.md index 3c076040..f0a73914 100644 --- a/lib/filters/butter3/Butter3.md +++ b/lib/filters/butter3/Butter3.md @@ -1,5 +1,7 @@ # BUTTER3: Ehlers 3-Pole Butterworth Filter +> *Steeper rolloff demands a third pole.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `6 * period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Steeper rolloff demands a third pole." - The 3-Pole Butterworth Filter (BUTTER3) extends the classic Butterworth design to third order, providing -60 dB/decade rolloff compared to -40 dB/decade for the 2-pole variant. Developed from John Ehlers' formulation in "Cybernetic Analysis for Stocks and Futures" (2004), this implementation uses the same pole placement as the 3-pole Super Smoother (SSF3) but with binomial (1,3,3,1) feedforward weights that preserve the maximally flat passband characteristic. The steeper rolloff makes BUTTER3 more effective at rejecting high-frequency noise, at the cost of slightly more lag than BUTTER2. ## Core Concepts diff --git a/lib/filters/cfitz/Cfitz.md b/lib/filters/cfitz/Cfitz.md index ec61da5b..269c9673 100644 --- a/lib/filters/cfitz/Cfitz.md +++ b/lib/filters/cfitz/Cfitz.md @@ -1,5 +1,7 @@ # CFITZ: Christiano-Fitzgerald Band-Pass Filter +> *Christiano-Fitzgerald isolates a frequency band from the time series, extracting cycles of a chosen wavelength range.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/cheby1/Cheby1.md b/lib/filters/cheby1/Cheby1.md index 5d7ef43a..dc465e81 100644 --- a/lib/filters/cheby1/Cheby1.md +++ b/lib/filters/cheby1/Cheby1.md @@ -1,5 +1,7 @@ # CHEBY1: Chebyshev Type I Lowpass Filter +> *Chebyshev Type I trades passband ripple for a steeper rolloff — sharper frequency separation at the cost of amplitude wobble.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/cheby2/Cheby2.md b/lib/filters/cheby2/Cheby2.md index 09990280..b8f996e4 100644 --- a/lib/filters/cheby2/Cheby2.md +++ b/lib/filters/cheby2/Cheby2.md @@ -1,5 +1,7 @@ # CHEBY2 (Chebyshev Type II / Inverse Chebyshev) +> *Chebyshev Type II pushes the ripple into the stopband, keeping the passband flat while still achieving a steep transition.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/edcf/Edcf.md b/lib/filters/edcf/Edcf.md index 02c4e374..b2fc3172 100644 --- a/lib/filters/edcf/Edcf.md +++ b/lib/filters/edcf/Edcf.md @@ -1,5 +1,7 @@ # EDCF: Ehlers Distance Coefficient Filter +> *Ehlers' distance coefficient filter adjusts smoothing based on how far price has traveled, blending trend-following with noise rejection.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/elliptic/Elliptic.md b/lib/filters/elliptic/Elliptic.md index 3045d3b4..a8139397 100644 --- a/lib/filters/elliptic/Elliptic.md +++ b/lib/filters/elliptic/Elliptic.md @@ -1,5 +1,7 @@ # ELLIPTIC: 2nd Order Elliptic Lowpass Filter +> *If you want a vertical cliff, you have to accept a few bumps on the plateau.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If you want a vertical cliff, you have to accept a few bumps on the plateau." - The Elliptic filter (or Cauer filter for the history buffs) is the uncompromising extremist of linear filtering. It offers the steepest possible roll-off for a given order, but extracts a heavy price: ripple in both the passband and the stopband. While Butterworth is polite and Chebyshev is opinionated, Elliptic is aggressive. This implementation delivers a sharp 2nd-order Lowpass response with **1dB passband ripple** and a crushing **40dB stopband attenuation**. ## Historical Context diff --git a/lib/filters/gauss/Gauss.md b/lib/filters/gauss/Gauss.md index 8ccd3423..8db32d70 100644 --- a/lib/filters/gauss/Gauss.md +++ b/lib/filters/gauss/Gauss.md @@ -1,5 +1,7 @@ # Gauss: Gaussian Filter +> *SMA smears data like cheap paint. Gaussian filtering respects the signal's soul.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `2⌈3σ⌉+1` bars of warmup before first valid output (IsHot = true). Default: **7 bars** (σ=1.0). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "SMA smears data like cheap paint. Gaussian filtering respects the signal's soul." - Gauss (Gaussian Filter) is a smoothing filter that applies a Gaussian kernel to time series data. Unlike Simple Moving Average (SMA), which weights all points in the window equally (boxcar function), the Gaussian filter applies weights that follow a bell curve distribution. This minimizes lag while providing superior noise reduction and significantly better preservation of signal edges. ## Historical Context diff --git a/lib/filters/hann/Hann.md b/lib/filters/hann/Hann.md index c2bbf251..1b7e190b 100644 --- a/lib/filters/hann/Hann.md +++ b/lib/filters/hann/Hann.md @@ -1,5 +1,7 @@ # Hann: Hann FIR Filter +> *The Hanning window whispers where the Boxcar screams. Smoothness is not just an aesthetic; it's a mathematical necessity.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `length` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Hanning window whispers where the Boxcar screams. Smoothness is not just an aesthetic; it's a mathematical necessity." - Hann (Hann Filter) is a Finite Impulse Response (FIR) smoothing filter that applies a Hann window to time series data. Named after Julius von Hann, this filter uses a cosine-sum window function that tapers inputs to zero at the edges. This tapering process significantly reduces spectral leakage and provides excellent high-frequency noise attenuation compared to a Simple Moving Average (SMA). ## Historical Context diff --git a/lib/filters/hp/Hp.md b/lib/filters/hp/Hp.md index 8bc5cee3..1401a8d7 100644 --- a/lib/filters/hp/Hp.md +++ b/lib/filters/hp/Hp.md @@ -1,5 +1,7 @@ # HP - Hodrick-Prescott Filter +> *Trends are not lines; they are curves that we simplify for our sanity, often at the cost of reality.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `⌈2√λ⌉` bars of warmup before first valid output (IsHot = true). Default: **~80 bars** (λ=1600). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Trends are not lines; they are curves that we simplify for our sanity, often at the cost of reality." - The Hodrick-Prescott (HP) filter is a widely used tool in macroeconomics for separating the cyclical component of a time series from raw data. While the standard HP filter is non-causal (requiring future data), this implementation uses a causal approximation suitable for real-time streaming analysis. ## Historical Context diff --git a/lib/filters/hpf/Hpf.md b/lib/filters/hpf/Hpf.md index 4301b3c4..5b0b1b57 100644 --- a/lib/filters/hpf/Hpf.md +++ b/lib/filters/hpf/Hpf.md @@ -1,5 +1,7 @@ # HPF: Ehlers Highpass Filter +> *Noise is just signal you haven't figured out how to filter yet. Or maybe, it's the only signal that matters.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `length` bars of warmup before first valid output (IsHot = true). Default: **40 bars**. - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Noise is just signal you haven't figured out how to filter yet. Or maybe, it's the only signal that matters." - The 2-Pole Highpass Filter (HPF) is designed to separate high-frequency components (like cycles and noise) from the underlying trend. By suppressing low-frequency movements, it acts as a "detrender," making it invaluable for oscillator construction and cycle analysis. ## Historical Context diff --git a/lib/filters/kalman/Kalman.md b/lib/filters/kalman/Kalman.md index 2837d9ac..90f6de37 100644 --- a/lib/filters/kalman/Kalman.md +++ b/lib/filters/kalman/Kalman.md @@ -1,5 +1,7 @@ # Kalman Filter (KALMAN) +> *Prediction is very difficult, especially if it's about the future.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `10` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Prediction is very difficult, especially if it's about the future." — Niels Bohr. The Kalman Filter doesn't just predict; it optimally estimates the present by balancing what it thinks should happen with what actually happened. - The **Kalman Filter** is a recursive algorithm that estimates the state of a dynamic system from a series of incomplete and noisy measurements. In technical analysis, it acts as a sophisticated smoothing filter that adapts to price changes based on specified noise covariances. Unlike simple moving averages that treat all past data equally or with fixed weights, the Kalman Filter dynamically adjusts its "trust" between its own prediction and the new price data. ## Historical Context diff --git a/lib/filters/laguerre/Laguerre.md b/lib/filters/laguerre/Laguerre.md index a5132449..62ade347 100644 --- a/lib/filters/laguerre/Laguerre.md +++ b/lib/filters/laguerre/Laguerre.md @@ -1,5 +1,7 @@ # LAGUERRE: Ehlers Laguerre Filter +> *The problem with conventional filters is that they use unit delays. All-pass filters replace unit delays with frequency-dependent delays, and that changes everything.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `WarmupBars` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The problem with conventional filters is that they use unit delays. All-pass filters replace unit delays with frequency-dependent delays, and that changes everything." — John F. Ehlers - ## Introduction The Laguerre Filter is a four-element IIR (Infinite Impulse Response) filter designed by John F. Ehlers that uses cascaded all-pass sections controlled by a single damping factor γ (gamma). It produces remarkably smooth output from only four data elements. When γ = 0, the filter degenerates to a 4-tap FIR (triangular weighted average). As γ approaches 1, smoothing increases with correspondingly greater lag. The filter achieves smoothing quality comparable to much longer conventional moving averages while maintaining a fixed 4-element structure. diff --git a/lib/filters/lms/Lms.md b/lib/filters/lms/Lms.md index 7322a20d..bdd4e1e4 100644 --- a/lib/filters/lms/Lms.md +++ b/lib/filters/lms/Lms.md @@ -1,5 +1,7 @@ # LMS: Least Mean Squares Adaptive Filter +> *The filter that learns from its mistakes, one gradient step at a time.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `order + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The filter that learns from its mistakes, one gradient step at a time." - The **Least Mean Squares (LMS) Adaptive Filter** is the Widrow-Hoff adaptive FIR filter, the simplest and most widely deployed adaptive algorithm in signal processing. It maintains an `order`-tap weight vector that learns to predict the current input from its recent history, updating weights via the Normalized LMS (NLMS) gradient descent rule. The result is a price-following overlay filter that automatically adapts its frequency response to changing market conditions with O(order) per-bar complexity. ## Historical Context diff --git a/lib/filters/loess/Loess.md b/lib/filters/loess/Loess.md index 63e45c25..fca81d63 100644 --- a/lib/filters/loess/Loess.md +++ b/lib/filters/loess/Loess.md @@ -1,5 +1,7 @@ # Loess: Locally Estimated Scatterplot Smoothing +> *When global models fail, act locally. LOESS fits the data by ignoring the noise and embracing the neighborhood.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `Period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When global models fail, act locally. LOESS fits the data by ignoring the noise and embracing the neighborhood." - Locally Estimated Scatterplot Smoothing (LOESS) applies a weighted linear regression over a localized window of nearest neighbors. Unlike simple averaging or global linear regression, LOESS estimates the deterministic trend point-by-point, giving maximum influence to recent data and decaying elegantly at the edges. ## Historical Context / The Standard diff --git a/lib/filters/modf/Modf.md b/lib/filters/modf/Modf.md index 9e8f71c1..520f624e 100644 --- a/lib/filters/modf/Modf.md +++ b/lib/filters/modf/Modf.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/filters/notch/Notch.md b/lib/filters/notch/Notch.md index e248a460..01f32c96 100644 --- a/lib/filters/notch/Notch.md +++ b/lib/filters/notch/Notch.md @@ -1,5 +1,7 @@ # Notch Filter +> *A notch filter surgically removes a single frequency, silencing one resonance while leaving the rest of the spectrum untouched.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/nw/Nw.md b/lib/filters/nw/Nw.md index 5d862800..1f14f6b3 100644 --- a/lib/filters/nw/Nw.md +++ b/lib/filters/nw/Nw.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/filters/oneeuro/OneEuro.md b/lib/filters/oneeuro/OneEuro.md index ff6c110f..a6a262cf 100644 --- a/lib/filters/oneeuro/OneEuro.md +++ b/lib/filters/oneeuro/OneEuro.md @@ -1,5 +1,7 @@ # OneEuro — One Euro Filter +> *The One Euro filter adapts its cutoff frequency to signal speed — slow movements get heavy smoothing, fast ones pass through.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | diff --git a/lib/filters/rls/Rls.md b/lib/filters/rls/Rls.md index b283c156..5b236d9c 100644 --- a/lib/filters/rls/Rls.md +++ b/lib/filters/rls/Rls.md @@ -1,5 +1,7 @@ # RLS: Recursive Least Squares Adaptive Filter +> *The man who has no patience has no wisdom.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `order + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The man who has no patience has no wisdom." — but waiting is not the same as convergence. RLS converges where LMS merely approaches. - ## Introduction The Recursive Least Squares (RLS) adaptive filter is the Rolls-Royce of adaptive FIR filters. Where LMS crawls toward the Wiener solution one gradient step at a time, RLS arrives in approximately *order* iterations by maintaining an inverse correlation matrix $P$ that captures the full second-order statistics of the input signal. The trade-off is computational: $O(n^2)$ per bar versus LMS's $O(n)$, where $n$ is the filter order. For orders below 64, the convergence advantage typically outweighs the cost. diff --git a/lib/filters/rmed/Rmed.md b/lib/filters/rmed/Rmed.md index a4d958d5..c1a1d10f 100644 --- a/lib/filters/rmed/Rmed.md +++ b/lib/filters/rmed/Rmed.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires **5 bars** of warmup (MedianWindow) before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/filters/roofing/Roofing.md b/lib/filters/roofing/Roofing.md index ba1528f6..a3caa664 100644 --- a/lib/filters/roofing/Roofing.md +++ b/lib/filters/roofing/Roofing.md @@ -1,5 +1,7 @@ # ROOFING: Ehlers Roofing Filter +> *The trend is your friend until it overwhelms the signal. The noise is your enemy until you mistake it for alpha.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `hpLength` bars of warmup before first valid output (IsHot = true). Default: **48 bars**. - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend until it overwhelms the signal. The noise is your enemy until you mistake it for alpha." - The **Roofing Filter** is John Ehlers' bandpass architecture designed specifically for oscillator construction. It cascades a 2nd-order Butterworth Highpass (to strip trend) with a Super Smoother Lowpass (to strip noise), passing only the cyclic energy within a user-defined frequency band. The output oscillates around zero, with zero crossings serving as directional signals. ## Historical Context diff --git a/lib/filters/sak/Sak.md b/lib/filters/sak/Sak.md index 80c32e1d..abc0008a 100644 --- a/lib/filters/sak/Sak.md +++ b/lib/filters/sak/Sak.md @@ -1,6 +1,6 @@ # SAK: Swiss Army Knife -> "Nine filters walk into a bar. The bartender says, 'What'll it be?' They answer in unison: 'Same equation, different coefficients.'" +> *Nine filters walk into a bar. The bartender says, 'What'll it be?' They answer in unison: 'Same equation, different coefficients.'* SAK is John Ehlers' unified second-order IIR filter framework that collapses nine distinct filter types into a single difference equation. Change five coefficients and the same code path produces EMA, SMA, Gaussian, Butterworth, FIR smoother, high-pass, two-pole high-pass, band-pass, or band-stop output. One transfer function. Nine behaviors. Zero code duplication. diff --git a/lib/filters/sgf/Sgf.md b/lib/filters/sgf/Sgf.md index a7b2c89f..5119dd98 100644 --- a/lib/filters/sgf/Sgf.md +++ b/lib/filters/sgf/Sgf.md @@ -1,5 +1,7 @@ # SGF: Savitzky-Golay Filter +> *SMA smoothes. Savitzky-Golay understands.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "SMA smoothes. Savitzky-Golay understands." - SGF (Savitzky-Golay Filter) is a digital signal processing technique that smoothes data by fitting successive sub-sets of adjacent data points with a low-degree polynomial by the method of linear least squares. Unlike standard moving averages that simply average the points, SGF preserves higher moments of the data distribution, such as the area, center of gravity, and line width. This makes it exceptionally good at preserving features of the distribution such as relative maxima and minima and width, which are usually flattened by other smoothing techniques. ## Historical Context diff --git a/lib/filters/spbf/Spbf.md b/lib/filters/spbf/Spbf.md index f97b0e93..30f30932 100644 --- a/lib/filters/spbf/Spbf.md +++ b/lib/filters/spbf/Spbf.md @@ -1,5 +1,7 @@ # SPBF: Ehlers Super Passband Filter +> *Two EMAs walk into a frequency domain. The difference between them is the only thing worth trading.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `max(longPeriod, rmsPeriod)` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Two EMAs walk into a frequency domain. The difference between them is the only thing worth trading." - The **Super Passband Filter** is John Ehlers' wide-band bandpass constructed by differencing two z-transformed EMAs with Ehlers-style smoothing ($\alpha = 5/N$). It rejects both DC trend and high-frequency noise, passing only the cyclic energy between two EMA-defined cutoff frequencies. The output oscillates around zero, with an RMS trigger envelope providing signal/noise discrimination. ## Historical Context diff --git a/lib/filters/ssf2/Ssf2.md b/lib/filters/ssf2/Ssf2.md index 77c3cfd6..8e1c6d5e 100644 --- a/lib/filters/ssf2/Ssf2.md +++ b/lib/filters/ssf2/Ssf2.md @@ -1,5 +1,7 @@ # SSF2: Ehlers 2-Pole Super Smoother Filter +> *Noise is the enemy of the trend follower. The Super Smooth Filter is the silencer.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Noise is the enemy of the trend follower. The Super Smooth Filter is the silencer." - The 2-Pole Super Smooth Filter (SSF2) is a 2-pole Butterworth filter designed by John Ehlers. It offers superior noise reduction compared to standard moving averages while maintaining minimal lag. By using complex conjugate poles, it achieves a "maximally flat" response in the passband, meaning it preserves the trend signal with high fidelity while aggressively suppressing high-frequency noise. ## Historical Context diff --git a/lib/filters/ssf3/Ssf3.md b/lib/filters/ssf3/Ssf3.md index b562049e..34479bf9 100644 --- a/lib/filters/ssf3/Ssf3.md +++ b/lib/filters/ssf3/Ssf3.md @@ -1,5 +1,7 @@ # SSF3: Ehlers 3-Pole Super Smoother Filter +> *Three poles, one sample. Maximum smoothing, minimum ceremony.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `6 * period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Three poles, one sample. Maximum smoothing, minimum ceremony." - The 3-Pole Super Smoother Filter (SSF3) extends Ehlers' Super Smoother concept to third order, providing -60 dB/decade rolloff compared to -40 dB/decade for the 2-pole variant (SSF2). It shares identical pole placement with BUTTER3 but uses a single-sample feedforward (`coef1 * x`) instead of the binomial-weighted 4-sample average (`coef1 * (x + 3x1 + 3x2 + x3)`). This makes SSF3 more responsive to recent price changes while still delivering aggressive high-frequency noise suppression. ## Core Concepts diff --git a/lib/filters/usf/Usf.md b/lib/filters/usf/Usf.md index 37b065c3..869340a7 100644 --- a/lib/filters/usf/Usf.md +++ b/lib/filters/usf/Usf.md @@ -1,5 +1,7 @@ # USF: Ehlers Ultimate Smoother Filter +> *The Ultimate Smoother achieves superior smoothing by subtracting high-frequency components using a high-pass filter, resulting in zero lag in the passband.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Ultimate Smoother achieves superior smoothing by subtracting high-frequency components using a high-pass filter, resulting in zero lag in the passband." - The Ultimate Smoother Filter (USF) is a zero-lag smoothing filter introduced by John Ehlers in the April 2024 issue of *Technical Analysis of Stocks & Commodities*. It builds upon the Super Smoother Filter (SSF) by using a high-pass filter to remove high-frequency noise, leaving a smooth low-frequency component with minimal lag. ## Historical Context diff --git a/lib/filters/voss/Voss.md b/lib/filters/voss/Voss.md index d265250c..0d32ccef 100644 --- a/lib/filters/voss/Voss.md +++ b/lib/filters/voss/Voss.md @@ -1,5 +1,7 @@ # VOSS: Ehlers Voss Predictive Filter +> *The best filter is one that tells you what is about to happen, not what already did.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best filter is one that tells you what is about to happen, not what already did." — paraphrasing Ehlers - ## Introduction The Voss Predictive Filter is a two-stage signal processing pipeline that extracts a dominant cycle from noisy price data and then predicts its future trajectory using negative group delay. Stage 1 is a two-pole bandpass filter (BPF) that isolates cycles near a specified period. Stage 2 is the Voss predictor, which applies a weighted feedback summation over past output values to shift the filter response forward in time. The result is a leading oscillator that anticipates bandpass zero crossings by a configurable number of bars. Crossings between the Filt (bandpass) and Voss (predictor) lines generate early trade signals with reduced lag. diff --git a/lib/filters/wavelet/Wavelet.md b/lib/filters/wavelet/Wavelet.md index 46e71ae0..e6286825 100644 --- a/lib/filters/wavelet/Wavelet.md +++ b/lib/filters/wavelet/Wavelet.md @@ -1,5 +1,7 @@ # WAVELET: Denoising Wavelet Filter +> *The wavelet transform is to the Fourier transform what a microscope is to a telescope: same math, different scale.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -18,8 +20,6 @@ - Requires `2^levels` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The wavelet transform is to the Fourier transform what a microscope is to a telescope: same math, different scale." - ## Introduction The Wavelet Denoising Filter applies an *à trous* (with holes) Haar wavelet decomposition with soft thresholding to remove high-frequency noise from price series while preserving trend structure and edges. Unlike classical low-pass filters that blur everything uniformly, wavelet denoising estimates the noise floor at each decomposition level via Median Absolute Deviation (MAD) and surgically removes only coefficients below the threshold. The result: noise reduction without the phase lag or overshoot penalty of IIR alternatives. diff --git a/lib/filters/wiener/Wiener.md b/lib/filters/wiener/Wiener.md index 47453511..2cf57532 100644 --- a/lib/filters/wiener/Wiener.md +++ b/lib/filters/wiener/Wiener.md @@ -1,5 +1,7 @@ # Wiener Filter +> *The signal is the truth. The noise is just an opinion.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Filter | @@ -17,8 +19,6 @@ - Requires `Math.Max(period, smoothPeriod)` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The signal is the truth. The noise is just an opinion." - The Wiener Filter is an optimal linear filter that attempts to minimize the mean square error between the estimated random process and the desired process. In the context of technical analysis, it acts as an adaptive smoothing filter that adjusts its responsiveness based on the local statistical properties of the data (signal-to-noise ratio). When the signal variance is high relative to noise variance, the filter follows the input closely. When noise dominates, it smooths aggressively. ## Historical Context diff --git a/lib/forecasts/_index.md b/lib/forecasts/_index.md index ad7e0d9c..94ec71bd 100644 --- a/lib/forecasts/_index.md +++ b/lib/forecasts/_index.md @@ -1,7 +1,5 @@ # Forecasts -> "Prediction is very difficult, especially about the future." Niels Bohr - Forecasting and predictive models. Unlike reactive indicators that smooth past data, forecasts attempt to project future values. Extrapolation is inherently uncertain. Use with appropriate skepticism and position sizing. ## Indicators diff --git a/lib/forecasts/afirma/Afirma.md b/lib/forecasts/afirma/Afirma.md index 866a79b5..8ff49d16 100644 --- a/lib/forecasts/afirma/Afirma.md +++ b/lib/forecasts/afirma/Afirma.md @@ -1,5 +1,7 @@ # AFIRMA: Autoregressive FIR Moving Average +> *Standard Moving Averages assume linear or exponential weights. AFIRMA asks: what if we used signal processing window functions instead?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Forecast | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Standard Moving Averages assume linear or exponential weights. AFIRMA asks: what if we used signal processing window functions instead?" - AFIRMA is a Windowed Weighted Moving Average that replaces standard linear weighting with weights derived from signal processing window functions (Hanning, Hamming, Blackman, Blackman-Harris). This approach achieves specific frequency response characteristics tailored to noise reduction. The optional Least Squares mode fits a linear regression to recent bars and blends the fitted values with original data, producing a hybrid smoothed-predicted output. diff --git a/lib/momentum/_index.md b/lib/momentum/_index.md index c48f1c7b..5dfa8ad9 100644 --- a/lib/momentum/_index.md +++ b/lib/momentum/_index.md @@ -1,7 +1,5 @@ # Momentum -> "Momentum tells you how fast price is moving. Whether that movement has meaning: entirely different question." Unknown - Momentum indicators measure the velocity and acceleration of price changes. Unlike trend indicators (which answer "where is price going?"), momentum indicators answer "how fast?" and "is it slowing down?". This distinction matters: a strong trend can have weakening momentum (divergence), and a ranging market can show momentum spikes (false breakouts). ## Indicators diff --git a/lib/momentum/asi/Asi.md b/lib/momentum/asi/Asi.md index 630b896b..847698d3 100644 --- a/lib/momentum/asi/Asi.md +++ b/lib/momentum/asi/Asi.md @@ -1,5 +1,7 @@ # ASI: Accumulation Swing Index +> *Price tells us what is happening. The Accumulation Swing Index tells us whether to believe it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Price tells us what is happening. The Accumulation Swing Index tells us whether to believe it." — J. Welles Wilder Jr. - The Accumulation Swing Index is Wilder's method for separating genuine breakouts from whipsaw noise. Each bar computes a Swing Index value by comparing current OHLC prices to the previous bar, scaled by a user-supplied limit move parameter T. The cumulative sum of these SI values — the ASI — forms a "phantom" price line whose peaks and troughs can be compared directly to the price chart. A trendline break on ASI that accompanies a trendline break on price is confirmed; a price break without ASI confirmation is a probable false move. Introduced in Wilder's 1978 book, it predates most modern oscillators by a decade. ## Historical Context diff --git a/lib/momentum/bias/Bias.md b/lib/momentum/bias/Bias.md index c3c9a708..c039afb7 100644 --- a/lib/momentum/bias/Bias.md +++ b/lib/momentum/bias/Bias.md @@ -1,5 +1,7 @@ # BIAS: Price Deviation from Moving Average (also known as Disparity Index) +> *When traders ask 'how overbought is it?', they're really asking how far price has strayed from its anchor. Bias answers that question in percentage terms, telling you whether the current price is 5% above or 10% below its moving average. It's the market's stretch marks made visible.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When traders ask 'how overbought is it?', they're really asking how far price has strayed from its anchor. Bias answers that question in percentage terms, telling you whether the current price is 5% above or 10% below its moving average. It's the market's stretch marks made visible." - The Bias indicator measures the percentage difference between the current price and its Simple Moving Average (SMA). A positive bias indicates price is above the average (potentially overbought), while negative bias suggests price is below average (potentially oversold). This is one of the simplest yet most effective tools for identifying mean-reversion opportunities. ## Historical Context diff --git a/lib/momentum/bop/Bop.md b/lib/momentum/bop/Bop.md index 9584dcb7..fd36141a 100644 --- a/lib/momentum/bop/Bop.md +++ b/lib/momentum/bop/Bop.md @@ -1,5 +1,7 @@ # BOP: Balance of Power +> *The market is a tug of war between buyers and sellers. BOP tells you who's pulling harder.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `> 0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market is a tug of war between buyers and sellers. BOP tells you who's pulling harder." - The Balance of Power measures buying versus selling pressure by comparing the body (Close minus Open) to the range (High minus Low). Created by Igor Livshin in 2001, this ratio oscillates between -1 and +1, providing instantaneous momentum readings with zero lag. A stateless indicator: each bar evaluated independently, no memory of previous values required. ## Historical Context diff --git a/lib/momentum/cci/Cci.md b/lib/momentum/cci/Cci.md index 02aed30e..e61839c8 100644 --- a/lib/momentum/cci/Cci.md +++ b/lib/momentum/cci/Cci.md @@ -1,5 +1,7 @@ # CCI - Commodity Channel Index +> *CCI measures how far price deviates from its statistical mean, scaled by mean absolute deviation — a z-score with character.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | diff --git a/lib/momentum/cfb/Cfb.md b/lib/momentum/cfb/Cfb.md index 9ffddb0a..096ba20a 100644 --- a/lib/momentum/cfb/Cfb.md +++ b/lib/momentum/cfb/Cfb.md @@ -1,5 +1,7 @@ # CFB: Jurik Composite Fractal Behavior +> *Mark Jurik's CFB is not a momentum indicator. It is a stopwatch for chaos.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `maxLen` bars (192 default) of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Mark Jurik's CFB is not a momentum indicator. It is a stopwatch for chaos." - The Composite Fractal Behavior index measures trend duration by analyzing fractal efficiency across 96 simultaneous lookback periods (2 to 192 bars by default). Rather than asking "how strong is the trend," CFB asks "how long has the market been moving efficiently." The answer: a single integer representing the dominant trending timeframe. Use CFB to dynamically tune other indicators: instead of RSI(14), use RSI(CFB). ## Historical Context diff --git a/lib/momentum/cmo/Cmo.md b/lib/momentum/cmo/Cmo.md index 37c9327b..7fdcfb6e 100644 --- a/lib/momentum/cmo/Cmo.md +++ b/lib/momentum/cmo/Cmo.md @@ -1,5 +1,7 @@ # CMO (Chande Momentum Oscillator) +> *Chande's momentum oscillator compares the sum of gains to the sum of losses, yielding a symmetric measure of directional force.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | diff --git a/lib/momentum/macd/Macd.md b/lib/momentum/macd/Macd.md index 1987601f..6edf8618 100644 --- a/lib/momentum/macd/Macd.md +++ b/lib/momentum/macd/Macd.md @@ -1,5 +1,7 @@ # MACD: Moving Average Convergence Divergence +> *The trend is your friend, until it bends.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `Max(fast, slow) + signal - 2` bars (33 default) of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend, until it bends." — Ed Seykota - The Moving Average Convergence Divergence measures momentum through the relationship between two exponential moving averages. Created by Gerald Appel in 1979, the indicator transforms price into a bounded oscillator that reveals trend strength, direction, and potential reversals. Standard parameters (12, 26, 9) detect monthly and biweekly cycles: the 26-period represents roughly one trading month, the 12-period half that duration. ## Historical Context diff --git a/lib/momentum/mom/Mom.md b/lib/momentum/mom/Mom.md index 5f5c1b19..18610130 100644 --- a/lib/momentum/mom/Mom.md +++ b/lib/momentum/mom/Mom.md @@ -1,5 +1,7 @@ # MOM: Momentum (Absolute Price Change) +> *The market's simplest question answered: how much has price moved in N bars? No ratios, no percentages. Just the raw delta.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market's simplest question answered: how much has price moved in N bars? No ratios, no percentages. Just the raw delta." - MOM (Momentum) calculates the absolute price difference between the current value and the value N periods ago. It is the purest expression of directional price movement, returning a signed value in the same units as the input. Positive MOM indicates rising prices; negative indicates falling. This is functionally identical to ROC but with a configurable lookback period (default 10 vs ROC's convention), and maps directly to TA-Lib's `MOM` function. ## Historical Context diff --git a/lib/momentum/pmo/Pmo.md b/lib/momentum/pmo/Pmo.md index a3ff9249..ff5225dc 100644 --- a/lib/momentum/pmo/Pmo.md +++ b/lib/momentum/pmo/Pmo.md @@ -1,5 +1,7 @@ # PMO: Price Momentum Oscillator +> *Double-smooth the rate of change and you get something that actually tells you where momentum is headed, not where it was five bars ago.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `timePeriods + smoothPeriods` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Double-smooth the rate of change and you get something that actually tells you where momentum is headed, not where it was five bars ago." - PMO (Price Momentum Oscillator), developed by Carl Swenlin at DecisionPoint, is a double-smoothed 1-bar rate of change. It applies two custom EMA passes to a percentage ROC, producing a momentum oscillator that is smoother than raw ROC yet more responsive than triple-smoothed alternatives like TRIX. The custom EMA uses $\alpha = 2/N$ rather than the standard $2/(N+1)$, and seeds with the SMA of the first N values. PMO oscillates around zero: positive values indicate upward momentum, negative values indicate downward momentum. ## Historical Context diff --git a/lib/momentum/ppo/Ppo.md b/lib/momentum/ppo/Ppo.md index 8ec671d4..27e43d8b 100644 --- a/lib/momentum/ppo/Ppo.md +++ b/lib/momentum/ppo/Ppo.md @@ -1,5 +1,7 @@ # PPO: Percentage Price Oscillator +> *MACD told you the spread in dollars. PPO tells you the spread in percent. One of those actually works across instruments.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `slowPeriod + signalPeriod` bars (35 default) of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "MACD told you the spread in dollars. PPO tells you the spread in percent. One of those actually works across instruments." - PPO (Percentage Price Oscillator) measures the percentage difference between a fast EMA and a slow EMA. It is functionally equivalent to MACD normalized by the slow EMA, producing values that are comparable across instruments with different price levels. The implementation outputs three components: the PPO line, a signal line (EMA of PPO), and a histogram (PPO minus Signal). ## Historical Context diff --git a/lib/momentum/prs/Prs.md b/lib/momentum/prs/Prs.md index fa30b99e..2c23088f 100644 --- a/lib/momentum/prs/Prs.md +++ b/lib/momentum/prs/Prs.md @@ -1,5 +1,7 @@ # PRS: Price Relative Strength +> *Price relative strength ratios two instruments, revealing which one leads and which one lags in the race.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | diff --git a/lib/momentum/roc/Roc.md b/lib/momentum/roc/Roc.md index 668a63b0..746acf0e 100644 --- a/lib/momentum/roc/Roc.md +++ b/lib/momentum/roc/Roc.md @@ -1,5 +1,7 @@ # ROC: Rate of Change (Absolute) +> *The simplest momentum measure: how far has price moved? Not percentage, not ratio - just the raw difference.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest momentum measure: how far has price moved? Not percentage, not ratio - just the raw difference." - ROC (Rate of Change) calculates the absolute price difference between the current value and the value N periods ago. This is the most basic form of momentum measurement, returning the raw price change in the same units as the input data. Unlike ROCP (percentage) or ROCR (ratio), ROC preserves the original scale, making it directly interpretable in dollar/point terms. ## Historical Context diff --git a/lib/momentum/rocp/Rocp.md b/lib/momentum/rocp/Rocp.md index 2d06af45..5e98712b 100644 --- a/lib/momentum/rocp/Rocp.md +++ b/lib/momentum/rocp/Rocp.md @@ -1,5 +1,7 @@ # ROCP: Rate of Change Percentage +> *The percentage form of momentum: by what percent has price changed? The most intuitive momentum measure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The percentage form of momentum: by what percent has price changed? The most intuitive momentum measure." - ROCP (Rate of Change Percentage) calculates the percentage change between the current value and the value N periods ago. This is the most commonly used form of rate of change, expressing change in percentage terms that are directly interpretable (e.g., 5.0 = 5% increase). ## Historical Context diff --git a/lib/momentum/rocr/Rocr.md b/lib/momentum/rocr/Rocr.md index 96a9664c..de2384fa 100644 --- a/lib/momentum/rocr/Rocr.md +++ b/lib/momentum/rocr/Rocr.md @@ -1,5 +1,7 @@ # ROCR: Rate of Change Ratio +> *The ratio form of momentum: how many times larger is the current price compared to the past? A multiplier view of market movement.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The ratio form of momentum: how many times larger is the current price compared to the past? A multiplier view of market movement." - ROCR (Rate of Change Ratio) calculates the ratio between the current value and the value N periods ago. Values hover around 1.0, with values above 1.0 indicating price increase and values below 1.0 indicating price decrease. Unlike ROC (absolute) or ROCP (percentage), ROCR provides a dimensionless multiplier that directly shows the price ratio. ## Historical Context diff --git a/lib/momentum/rsi/Rsi.md b/lib/momentum/rsi/Rsi.md index 19042c53..626c623a 100644 --- a/lib/momentum/rsi/Rsi.md +++ b/lib/momentum/rsi/Rsi.md @@ -1,5 +1,7 @@ # RSI: Relative Strength Index +> *Momentum is the premier anomaly.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Momentum is the premier anomaly." — Clifford Asness, AQR Capital (who actually said it, and meant it) - The Relative Strength Index measures the speed and magnitude of price changes. Introduced by J. Welles Wilder Jr. in 1978, it oscillates between 0 and 100, identifying overbought and oversold conditions. The "Relative Strength" name is misleading: RSI measures internal strength (price versus itself) not relative strength (asset versus benchmark). Wilder knew this. He kept the name anyway. Marketing, perhaps. ## Historical Context diff --git a/lib/momentum/rsx/Rsx.md b/lib/momentum/rsx/Rsx.md index 341a9058..8307ae94 100644 --- a/lib/momentum/rsx/Rsx.md +++ b/lib/momentum/rsx/Rsx.md @@ -1,5 +1,7 @@ # RSX: Relative Strength Xtra (Jurik RSX) +> *RSX is to RSI what a Tesla is to a horse-drawn carriage: same basic concept, vastly superior engineering.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "RSX is to RSI what a Tesla is to a horse-drawn carriage: same basic concept, vastly superior engineering." - Mark Jurik's RSX represents the pinnacle of bounded momentum oscillator design. Standard RSI suffers from a fundamental paradox: raw RSI produces jagged noise triggering false signals at overbought/oversold boundaries, but smoothing introduces unacceptable lag that delays turning points. RSX solves this through cascaded IIR filter topology that eliminates high-frequency noise while preserving linear phase response. The result: output so smooth it resembles a sine wave, yet turns precisely at market extrema with zero effective lag. ## Historical Context diff --git a/lib/momentum/sam/Sam.md b/lib/momentum/sam/Sam.md index b7f62b5d..5d103846 100644 --- a/lib/momentum/sam/Sam.md +++ b/lib/momentum/sam/Sam.md @@ -1,5 +1,7 @@ # SAM: Smoothed Adaptive Momentum +> *Smoothed adaptive momentum adjusts its sensitivity to volatility, amplifying signals in trending regimes and dampening them in noise.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | diff --git a/lib/momentum/tsi/Tsi.md b/lib/momentum/tsi/Tsi.md index e67c60f0..27cd775f 100644 --- a/lib/momentum/tsi/Tsi.md +++ b/lib/momentum/tsi/Tsi.md @@ -1,5 +1,7 @@ # TSI: True Strength Index +> *True Strength Index double-smooths momentum, filtering out noise while preserving the directional signal in price change.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | diff --git a/lib/momentum/vel/Vel.md b/lib/momentum/vel/Vel.md index c5b4412d..f719c8a1 100644 --- a/lib/momentum/vel/Vel.md +++ b/lib/momentum/vel/Vel.md @@ -1,5 +1,7 @@ # VEL: Jurik Velocity +> *Momentum is easy. Smooth momentum without lag is hard. Jurik Velocity is the answer.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Momentum | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Momentum is easy. Smooth momentum without lag is hard. Jurik Velocity is the answer." - Jurik Velocity (VEL) measures price rate-of-change through the differential between two weighted moving averages with distinct inertia profiles. Standard momentum ($P_t - P_{t-n}$) amplifies noise: single outlier bars create false signals. VEL exploits the different convergence speeds of Parabolic Weighted Moving Average (PWMA) and linear Weighted Moving Average (WMA) to isolate clean velocity information. The quadratic weighting of PWMA responds faster than linear WMA; their difference captures acceleration without bar-to-bar noise. ## Historical Context diff --git a/lib/numerics/_index.md b/lib/numerics/_index.md index a347913b..37ce1c97 100644 --- a/lib/numerics/_index.md +++ b/lib/numerics/_index.md @@ -1,7 +1,5 @@ # Numerics -> "Price is raw signal. Transform exposes hidden structure. Derivative reveals momentum. Normalization enables comparison." - Basic mathematical transforms and utility functions for time series. These building blocks convert raw price data into forms suitable for analysis, comparison, and downstream indicator consumption. | Indicator | Full Name | Description | diff --git a/lib/numerics/accel/Accel.md b/lib/numerics/accel/Accel.md index e4fc7715..c6df3aae 100644 --- a/lib/numerics/accel/Accel.md +++ b/lib/numerics/accel/Accel.md @@ -1,5 +1,7 @@ # ACCEL: Second Derivative (Acceleration) +> *Velocity tells you where you're going. Acceleration tells you if you're getting there faster or slower.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `3` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Velocity tells you where you're going. Acceleration tells you if you're getting there faster or slower." - ACCEL measures the rate of change of velocity—the acceleration of a time series. As the second derivative, it reveals momentum shifts before they manifest in price direction. Positive acceleration means velocity is increasing (trend strengthening); negative means velocity is decreasing (trend weakening). This O(1) streaming implementation uses FMA optimization and SIMD batch processing. ## Historical Context diff --git a/lib/numerics/betadist/Betadist.md b/lib/numerics/betadist/Betadist.md index cca811b6..fe858680 100644 --- a/lib/numerics/betadist/Betadist.md +++ b/lib/numerics/betadist/Betadist.md @@ -1,5 +1,7 @@ # BETADIST: Beta Distribution CDF +> *The Beta distribution CDF maps values onto a flexible probability curve defined by two shape parameters — versatile enough to model any bounded outcome.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/binomdist/Binomdist.md b/lib/numerics/binomdist/Binomdist.md index a8de98c6..3aa93430 100644 --- a/lib/numerics/binomdist/Binomdist.md +++ b/lib/numerics/binomdist/Binomdist.md @@ -1,5 +1,7 @@ # BINOMDIST: Binomial Distribution CDF +> *Binomial distribution CDF counts the probability of success in fixed trials — discrete probability at its most fundamental.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/change/Change.md b/lib/numerics/change/Change.md index d2ba952a..f0eb0277 100644 --- a/lib/numerics/change/Change.md +++ b/lib/numerics/change/Change.md @@ -1,5 +1,7 @@ # CHANGE: Relative Price Change +> *The simplest measure of movement is often the most powerful.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest measure of movement is often the most powerful." - CHANGE calculates the percentage change between the current value and a value N periods ago. This fundamental indicator forms the basis for momentum analysis, rate of change calculations, and relative performance comparisons. ## Mathematical Foundation diff --git a/lib/numerics/cwt/Cwt.md b/lib/numerics/cwt/Cwt.md index ace4ec89..c8542d53 100644 --- a/lib/numerics/cwt/Cwt.md +++ b/lib/numerics/cwt/Cwt.md @@ -1,5 +1,7 @@ # CWT: Continuous Wavelet Transform +> *The continuous wavelet transform decomposes a signal across scale and time simultaneously — frequency analysis with temporal precision.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/decay/Decay.md b/lib/numerics/decay/Decay.md index 7846e86a..774f21d0 100644 --- a/lib/numerics/decay/Decay.md +++ b/lib/numerics/decay/Decay.md @@ -1,5 +1,7 @@ # DECAY: Linear Decay +> *A ratchet that only moves down slowly: price can push it up instantly, but gravity pulls it back at a steady, linear pace.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numerics | @@ -16,8 +18,6 @@ - Requires `1` bar of warmup before first valid output (IsHot = true). - Validated against Tulip Indicators `ti_decay` reference algorithm. -> "A ratchet that only moves down slowly: price can push it up instantly, but gravity pulls it back at a steady, linear pace." - DECAY implements the Tulip Indicators `ti_decay` function. When price is above the decayed level, output snaps to price. When price falls below, the output decays linearly at a rate of `1/period` per bar, creating a ceiling that gradually descends. This produces a one-sided envelope that hugs price from above. ## Historical Context diff --git a/lib/numerics/dwt/Dwt.md b/lib/numerics/dwt/Dwt.md index f9185548..5408274e 100644 --- a/lib/numerics/dwt/Dwt.md +++ b/lib/numerics/dwt/Dwt.md @@ -1,5 +1,7 @@ # DWT: Discrete Wavelet Transform +> *The discrete wavelet transform splits a signal into approximation and detail at each scale — multiresolution analysis in action.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/edecay/Edecay.md b/lib/numerics/edecay/Edecay.md index 3ea87989..85355592 100644 --- a/lib/numerics/edecay/Edecay.md +++ b/lib/numerics/edecay/Edecay.md @@ -1,5 +1,7 @@ # EDECAY: Exponential Decay +> *A ratchet that only moves down gradually: price can push it up instantly, but gravity pulls it back at an exponential pace — faster when far from zero, slower as it approaches.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numerics | @@ -16,8 +18,6 @@ - Requires `1` bar of warmup before first valid output (IsHot = true). - Validated against Tulip Indicators `ti_edecay` reference algorithm. -> "A ratchet that only moves down gradually: price can push it up instantly, but gravity pulls it back at an exponential pace — faster when far from zero, slower as it approaches." - EDECAY implements the exponential decaying function. When price is above the decayed level, output snaps to price. When price falls below, the output decays exponentially by multiplying by `(period-1)/period` per bar, creating a ceiling that gradually descends. Unlike linear DECAY which subtracts a fixed amount, EDECAY's multiplicative factor produces a proportional decay rate. ## Historical Context diff --git a/lib/numerics/expdist/Expdist.md b/lib/numerics/expdist/Expdist.md index 929884f6..b44bccac 100644 --- a/lib/numerics/expdist/Expdist.md +++ b/lib/numerics/expdist/Expdist.md @@ -1,5 +1,7 @@ # EXPDIST: Exponential Distribution CDF +> *The exponential distribution models the time between events — memoryless waiting distilled into a single rate parameter.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/exptrans/Exptrans.md b/lib/numerics/exptrans/Exptrans.md index 755718b5..67bd3791 100644 --- a/lib/numerics/exptrans/Exptrans.md +++ b/lib/numerics/exptrans/Exptrans.md @@ -1,5 +1,7 @@ # EXPTRANS: Exponential Function +> *The exponential function is the only function that is its own derivative—a mathematical curiosity that makes it indispensable for modeling growth, decay, and everything compounding.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The exponential function is the only function that is its own derivative—a mathematical curiosity that makes it indispensable for modeling growth, decay, and everything compounding." - The Exponential (EXP) transformer applies the natural exponential function $e^x$ to each value in a time series. As the inverse of the natural logarithm, it converts additive relationships back to multiplicative ones, making it essential for reconstructing price levels from log-returns and implementing models that assume log-normal distributions. ## Mathematical Foundation diff --git a/lib/numerics/fdist/Fdist.md b/lib/numerics/fdist/Fdist.md index 603ca72d..172474e2 100644 --- a/lib/numerics/fdist/Fdist.md +++ b/lib/numerics/fdist/Fdist.md @@ -1,5 +1,7 @@ # FDIST: F-Distribution CDF +> *The F-distribution CDF tests variance ratios — a cornerstone of hypothesis testing built from two chi-squared variables.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/fft/Fft.md b/lib/numerics/fft/Fft.md index c1467d95..ba0a10ca 100644 --- a/lib/numerics/fft/Fft.md +++ b/lib/numerics/fft/Fft.md @@ -1,5 +1,7 @@ # FFT: Fast Fourier Transform (Dominant Cycle Detector) +> *The FFT decomposes price into its constituent frequencies, revealing how much of each cycle lives inside the data.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/gammadist/Gammadist.md b/lib/numerics/gammadist/Gammadist.md index 4562db2c..b20bebf0 100644 --- a/lib/numerics/gammadist/Gammadist.md +++ b/lib/numerics/gammadist/Gammadist.md @@ -1,5 +1,7 @@ # GAMMADIST: Gamma Distribution CDF +> *The Gamma distribution generalizes the exponential, modeling the sum of waiting times with a shape that bends to fit.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/highest/Highest.md b/lib/numerics/highest/Highest.md index 757d196b..26c9d18a 100644 --- a/lib/numerics/highest/Highest.md +++ b/lib/numerics/highest/Highest.md @@ -1,5 +1,7 @@ # HIGHEST: Rolling Maximum +> *What's the peak? The answer to that question defines support, resistance, and breakout levels.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "What's the peak? The answer to that question defines support, resistance, and breakout levels." - HIGHEST calculates the maximum value over a rolling lookback window. This O(1) amortized streaming implementation uses a monotonic deque algorithm, enabling real-time updates without re-scanning the entire window. Validated against TA-Lib MAX and Tulip max functions. ## Historical Context diff --git a/lib/numerics/ifft/Ifft.md b/lib/numerics/ifft/Ifft.md index acb94ca7..21d9dada 100644 --- a/lib/numerics/ifft/Ifft.md +++ b/lib/numerics/ifft/Ifft.md @@ -1,5 +1,7 @@ # IFFT: Inverse Fast Fourier Transform (Spectral Low-Pass Filter) +> *Inverse FFT reconstructs a time series from selected frequency components — a spectral scalpel for noise removal.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/jerk/Jerk.md b/lib/numerics/jerk/Jerk.md index 55331af8..71e5ebdb 100644 --- a/lib/numerics/jerk/Jerk.md +++ b/lib/numerics/jerk/Jerk.md @@ -1,5 +1,7 @@ # JERK: Third Derivative +> *Acceleration tells you the trend is changing. Jerk tells you that change is itself changing—the earliest possible warning.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `4` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Acceleration tells you the trend is changing. Jerk tells you that change is itself changing—the earliest possible warning." - JERK measures the rate of change of acceleration—called "jerk" in physics. As the third derivative, it detects changes in momentum dynamics before they appear in acceleration, velocity, or price. A positive jerk means acceleration is increasing; negative means acceleration is decreasing. This O(1) streaming implementation uses dual FMA optimization and SIMD batch processing for four-point calculations. ## Historical Context diff --git a/lib/numerics/lineartrans/Lineartrans.md b/lib/numerics/lineartrans/Lineartrans.md index 8e2b3d81..b2aeaf38 100644 --- a/lib/numerics/lineartrans/Lineartrans.md +++ b/lib/numerics/lineartrans/Lineartrans.md @@ -1,5 +1,7 @@ # LINEARTRANS: Linear Scaling Transformer +> *The simplest transformations are often the most powerful—linear scaling is the mathematical equivalent of adjusting the volume and tuning the dial.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest transformations are often the most powerful—linear scaling is the mathematical equivalent of adjusting the volume and tuning the dial." - The Linear transformer applies an affine transformation $y = \text{slope} \cdot x + \text{intercept}$ to each value in a time series. This fundamental operation enables scaling, offsetting, unit conversion, and normalization—the building blocks for preparing data for analysis or combining signals from different sources. ## Mathematical Foundation diff --git a/lib/numerics/lognormdist/Lognormdist.md b/lib/numerics/lognormdist/Lognormdist.md index 296506ae..9e96fc87 100644 --- a/lib/numerics/lognormdist/Lognormdist.md +++ b/lib/numerics/lognormdist/Lognormdist.md @@ -1,5 +1,7 @@ # LOGNORMDIST: Log-Normal Distribution CDF +> *The log-normal CDF models variables whose logarithm is normal — the natural distribution of prices and multiplicative processes.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/logtrans/Logtrans.md b/lib/numerics/logtrans/Logtrans.md index 98283bd8..62d0de82 100644 --- a/lib/numerics/logtrans/Logtrans.md +++ b/lib/numerics/logtrans/Logtrans.md @@ -1,5 +1,7 @@ # LOGTRANS: Natural Logarithm Transformer +> *The logarithm is one of the most useful mathematical functions, turning multiplicative relationships into additive ones—a property that makes many financial calculations tractable.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The logarithm is one of the most useful mathematical functions, turning multiplicative relationships into additive ones—a property that makes many financial calculations tractable." - The LOG transformer applies the natural logarithm function $\ln(x)$ to input values. This point-wise transformation compresses large values and expands small ones, making it essential for analyzing multiplicative processes like compounded returns. ## Mathematical Foundation diff --git a/lib/numerics/lowest/Lowest.md b/lib/numerics/lowest/Lowest.md index b883b7e4..92573df1 100644 --- a/lib/numerics/lowest/Lowest.md +++ b/lib/numerics/lowest/Lowest.md @@ -1,5 +1,7 @@ # LOWEST: Rolling Minimum +> *Know your floor. Support levels are just historical minimums waiting to be tested.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Know your floor. Support levels are just historical minimums waiting to be tested." - LOWEST calculates the minimum value over a rolling lookback window. This O(1) amortized streaming implementation uses a monotonic deque algorithm, enabling real-time updates without re-scanning the entire window. Validated against TA-Lib MIN and Tulip min functions. ## Historical Context diff --git a/lib/numerics/maxindex/Maxindex.md b/lib/numerics/maxindex/Maxindex.md index fdcb9b05..dbb25982 100644 --- a/lib/numerics/maxindex/Maxindex.md +++ b/lib/numerics/maxindex/Maxindex.md @@ -1,5 +1,7 @@ # MAXINDEX: Rolling Maximum Index +> *It's not just about the peak — it's about *when* the peak occurred.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Cross-validation: `source[Maxindex.Batch[i]] == Highest.Batch[i]` for all bars after warmup. -> "It's not just about the peak — it's about *when* the peak occurred." - MAXINDEX identifies the position of the maximum value within a rolling window. While HIGHEST tells you the peak *value*, MAXINDEX tells you *where* that peak is relative to the current bar. This is essential for pattern recognition, timing analysis, and detecting how "stale" a high is. ## Historical Context diff --git a/lib/numerics/minindex/Minindex.md b/lib/numerics/minindex/Minindex.md index 5c2da793..6ac561f2 100644 --- a/lib/numerics/minindex/Minindex.md +++ b/lib/numerics/minindex/Minindex.md @@ -1,5 +1,7 @@ # MININDEX: Rolling Minimum Index +> *Finding support isn't just about the price — it's about *when* the floor was set.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Cross-validation: `source[Minindex.Batch[i]] == Lowest.Batch[i]` for all bars after warmup. -> "Finding support isn't just about the price — it's about *when* the floor was set." - MININDEX identifies the position of the minimum value within a rolling window. While LOWEST tells you the trough *value*, MININDEX tells you *where* that trough is relative to the current bar. This is essential for support analysis, timing studies, and detecting how "stale" a low is. ## Historical Context diff --git a/lib/numerics/normalize/Normalize.md b/lib/numerics/normalize/Normalize.md index 08310b23..d3e6f77f 100644 --- a/lib/numerics/normalize/Normalize.md +++ b/lib/numerics/normalize/Normalize.md @@ -1,5 +1,7 @@ # NORMALIZE: Min-Max Normalization +> *Normalization is the art of making apples and oranges comparable—by insisting that everything lives on the same scale from 0 to 1.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Normalization is the art of making apples and oranges comparable—by insisting that everything lives on the same scale from 0 to 1." - The Normalize transformer applies min-max scaling to map any value series into the bounded range [0, 1] based on the observed minimum and maximum within a rolling lookback window. This technique is fundamental for feature scaling, creating bounded oscillators, and comparing series with different magnitudes. ## Mathematical Foundation diff --git a/lib/numerics/normdist/Normdist.md b/lib/numerics/normdist/Normdist.md index 8e60f8f2..d29d97b5 100644 --- a/lib/numerics/normdist/Normdist.md +++ b/lib/numerics/normdist/Normdist.md @@ -1,5 +1,7 @@ # NORMDIST: Normal Distribution CDF +> *The normal distribution CDF is the bell curve's integral — the universal reference for probabilistic reasoning.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/poissondist/Poissondist.md b/lib/numerics/poissondist/Poissondist.md index 156cdf59..0f5eea67 100644 --- a/lib/numerics/poissondist/Poissondist.md +++ b/lib/numerics/poissondist/Poissondist.md @@ -1,5 +1,7 @@ # POISSONDIST: Poisson Distribution CDF +> *Poisson distribution CDF counts rare events in fixed intervals — the mathematics of arrivals, defaults, and surprises.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/relu/Relu.md b/lib/numerics/relu/Relu.md index 1bb55668..fdec1475 100644 --- a/lib/numerics/relu/Relu.md +++ b/lib/numerics/relu/Relu.md @@ -1,5 +1,7 @@ # RELU: Rectified Linear Unit +> *The simplest non-linearity that works—ReLU's computational efficiency and gradient-friendly properties made deep learning practical.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest non-linearity that works—ReLU's computational efficiency and gradient-friendly properties made deep learning practical." - The Rectified Linear Unit (ReLU) activation function applies `max(0, x)` to each value, passing positive inputs unchanged while zeroing negative ones. Its simplicity belies its importance: ReLU enabled the training of deep neural networks by mitigating vanishing gradients, and its computational efficiency makes it the default activation for most architectures. ## Mathematical Foundation diff --git a/lib/numerics/sigmoid/Sigmoid.md b/lib/numerics/sigmoid/Sigmoid.md index 5392d8f6..c067c995 100644 --- a/lib/numerics/sigmoid/Sigmoid.md +++ b/lib/numerics/sigmoid/Sigmoid.md @@ -1,5 +1,7 @@ # SIGMOID: Logistic Function +> *The sigmoid function is the S-curve that turns messy reality into neat probabilities—a mathematical diplomat that insists every answer must be between 0 and 1.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The sigmoid function is the S-curve that turns messy reality into neat probabilities—a mathematical diplomat that insists every answer must be between 0 and 1." - The Sigmoid (Logistic) transformer maps any real-valued input to the bounded range (0, 1) using the standard logistic function. Its characteristic S-shaped curve makes it indispensable for probability estimation, neural network activations, and any scenario requiring bounded outputs from unbounded inputs. ## Mathematical Foundation diff --git a/lib/numerics/slope/Slope.md b/lib/numerics/slope/Slope.md index 547e6848..7f90c287 100644 --- a/lib/numerics/slope/Slope.md +++ b/lib/numerics/slope/Slope.md @@ -1,5 +1,7 @@ # SLOPE: First Derivative (Velocity) +> *The simplest measure of change reveals the most: is it going up, or going down?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest measure of change reveals the most: is it going up, or going down?" - SLOPE measures the instantaneous rate of change—the velocity of a time series. As the first derivative, it answers the fundamental question: how fast is the value changing right now? A positive slope means ascending; negative means descending; zero means flat. This O(1) streaming implementation uses SIMD optimization for batch calculations and handles bar corrections via state rollback. ## Historical Context diff --git a/lib/numerics/sqrttrans/Sqrttrans.md b/lib/numerics/sqrttrans/Sqrttrans.md index 221e6c70..4819a7eb 100644 --- a/lib/numerics/sqrttrans/Sqrttrans.md +++ b/lib/numerics/sqrttrans/Sqrttrans.md @@ -1,5 +1,7 @@ # SQRTTRANS: Square Root Transform +> *The square root is nature's variance-stabilizing trick—halving the exponent space while preserving monotonicity. When price volatility scales with level, sqrt compresses the noise.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | @@ -16,8 +18,6 @@ - Requires `0` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The square root is nature's variance-stabilizing trick—halving the exponent space while preserving monotonicity. When price volatility scales with level, sqrt compresses the noise." - The Square Root (SQRT) transformer applies $\sqrt{x}$ to each value in a time series. This variance-stabilizing transformation compresses ranges where volatility scales with magnitude, making it useful for heteroscedastic data where standard deviation increases with price level. ## Mathematical Foundation diff --git a/lib/numerics/tdist/Tdist.md b/lib/numerics/tdist/Tdist.md index 227b756d..204aaa83 100644 --- a/lib/numerics/tdist/Tdist.md +++ b/lib/numerics/tdist/Tdist.md @@ -1,5 +1,7 @@ # TDIST: Student's t-Distribution CDF +> *Student's t-distribution CDF handles small samples with heavier tails than the normal — uncertainty acknowledged in the shape itself.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/numerics/weibulldist/Weibulldist.md b/lib/numerics/weibulldist/Weibulldist.md index c72ee669..4e8ec5d7 100644 --- a/lib/numerics/weibulldist/Weibulldist.md +++ b/lib/numerics/weibulldist/Weibulldist.md @@ -1,5 +1,7 @@ # WEIBULLDIST: Weibull Distribution CDF +> *The Weibull distribution models failure rates that change over time — a flexible tool for reliability and survival analysis.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Numeric | diff --git a/lib/oscillators/_index.md b/lib/oscillators/_index.md index bf1adc80..566a468a 100644 --- a/lib/oscillators/_index.md +++ b/lib/oscillators/_index.md @@ -1,7 +1,5 @@ # Oscillators -> "Oscillators tell you when to act, not which direction to trade." Unknown - Oscillators fluctuate above and below a centerline or within bounded ranges. Useful for identifying overbought/oversold conditions, momentum shifts, and divergences. Best in ranging markets; trend-following indicators work better in trending markets. | Indicator | Full Name | Description | diff --git a/lib/oscillators/ac/Ac.md b/lib/oscillators/ac/Ac.md index 34b5033b..d33b8461 100644 --- a/lib/oscillators/ac/Ac.md +++ b/lib/oscillators/ac/Ac.md @@ -1,6 +1,6 @@ # AC: Accelerator Oscillator -> "Momentum tells you which way the wind is blowing. Acceleration tells you whether the wind is picking up." -- Bill Williams, paraphrased +> *Momentum tells you which way the wind is blowing. Acceleration tells you whether the wind is picking up.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/ao/Ao.md b/lib/oscillators/ao/Ao.md index ea5ef753..f4e6d34e 100644 --- a/lib/oscillators/ao/Ao.md +++ b/lib/oscillators/ao/Ao.md @@ -1,6 +1,6 @@ # AO: Awesome Oscillator -> "Awesome is a marketing term. The math is just a moving average crossover. But sometimes, simple is all you need." -- Bill Williams, paraphrased +> *Awesome is a marketing term. The math is just a moving average crossover. But sometimes, simple is all you need.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/apo/Apo.md b/lib/oscillators/apo/Apo.md index b05fea30..b7579dec 100644 --- a/lib/oscillators/apo/Apo.md +++ b/lib/oscillators/apo/Apo.md @@ -1,6 +1,6 @@ # APO: Absolute Price Oscillator -> "Percentages are for analysts. Traders pay bills in cash. APO tells you the cash value of the trend." +> *Percentages are for analysts. Traders pay bills in cash. APO tells you the cash value of the trend.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/bbb/Bbb.md b/lib/oscillators/bbb/Bbb.md index 53646197..c3d3e706 100644 --- a/lib/oscillators/bbb/Bbb.md +++ b/lib/oscillators/bbb/Bbb.md @@ -1,6 +1,6 @@ # BBB: Bollinger %B -> "Price oscillates, but %B tells you where it lives inside the band." -- John Bollinger, paraphrased +> *Price oscillates, but %B tells you where it lives inside the band.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/bbi/Bbi.md b/lib/oscillators/bbi/Bbi.md index ebe78b9a..8b1b111e 100644 --- a/lib/oscillators/bbi/Bbi.md +++ b/lib/oscillators/bbi/Bbi.md @@ -1,5 +1,7 @@ # BBI: Bulls Bears Index +> *Average four moving averages of doubling periods and you get a single line that votes on whether bulls or bears own the tape. It is a committee of trends, each watching a different time horizon, forced to agree on one number.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `Math.Max(Math.Max(p1, p2), Math.Max(p3, p4))` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Average four moving averages of doubling periods and you get a single line that votes on whether bulls or bears own the tape. It is a committee of trends, each watching a different time horizon, forced to agree on one number." - BBI (Bulls Bears Index) computes the arithmetic mean of four Simple Moving Averages with geometrically spaced periods (3, 6, 12, 24 by default). The result is a price-overlay line that captures trend consensus across ultra-short, short, medium, and long timeframes simultaneously. Price above BBI signals bullish dominance; price below BBI signals bearish control. The crossover point marks the regime boundary between long and short markets. ## Historical Context diff --git a/lib/oscillators/bbs/Bbs.md b/lib/oscillators/bbs/Bbs.md index f403d0f6..3f4c83cd 100644 --- a/lib/oscillators/bbs/Bbs.md +++ b/lib/oscillators/bbs/Bbs.md @@ -1,6 +1,6 @@ # BBS: Bollinger Band Squeeze -> "Volatility contraction precedes expansion. The squeeze tells you when to watch." -- John Carter, paraphrased +> *Volatility contraction precedes expansion. The squeeze tells you when to watch.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/brar/Brar.md b/lib/oscillators/brar/Brar.md index e16f3b1f..305ecc29 100644 --- a/lib/oscillators/brar/Brar.md +++ b/lib/oscillators/brar/Brar.md @@ -1,5 +1,7 @@ # BRAR: Bull-Bear Power Ratio +> *The open is the amateur's price. The close is the professional's price. The distance between them is where the money hides.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The open is the amateur's price. The close is the professional's price. The distance between them is where the money hides." - BRAR is a dual-output sentiment oscillator from the Japanese technical analysis tradition that decomposes market pressure into two independent ratios: BR (Buying Ratio), which measures upside thrust relative to the previous close, and AR (Atmosphere Ratio), which measures intraday range asymmetry relative to the open. Both outputs oscillate around an equilibrium of 100, where values above 100 signal dominance of the measured pressure and values below 100 signal weakness. The default lookback of 26 bars (one Japanese trading month) produces stable readings with 4 additions per bar in streaming mode. ## Historical Context diff --git a/lib/oscillators/cfo/Cfo.md b/lib/oscillators/cfo/Cfo.md index 423f0bdb..9657db7a 100644 --- a/lib/oscillators/cfo/Cfo.md +++ b/lib/oscillators/cfo/Cfo.md @@ -1,6 +1,6 @@ # CFO: Chande Forecast Oscillator (also known as FOSC) -> "The distance between where you are and where regression says you should be tells you everything about momentum." -- Tushar Chande, paraphrased +> *The distance between where you are and where regression says you should be tells you everything about momentum.* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/coppock/Coppock.md b/lib/oscillators/coppock/Coppock.md index cb643846..d2b28c3d 100644 --- a/lib/oscillators/coppock/Coppock.md +++ b/lib/oscillators/coppock/Coppock.md @@ -1,5 +1,7 @@ # COPPOCK: Coppock Curve +> *The Coppock Curve sums two rates of change through a weighted average, designed to spot the start of long-term bull markets.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/crsi/Crsi.md b/lib/oscillators/crsi/Crsi.md index 4ae50cdc..98739f76 100644 --- a/lib/oscillators/crsi/Crsi.md +++ b/lib/oscillators/crsi/Crsi.md @@ -1,5 +1,7 @@ # CRSI: Connors RSI +> *Connors RSI blends classic RSI with streak length and percentile rank, creating a multi-dimensional momentum snapshot.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/cti/Cti.md b/lib/oscillators/cti/Cti.md index 519a0460..783f52da 100644 --- a/lib/oscillators/cti/Cti.md +++ b/lib/oscillators/cti/Cti.md @@ -1,5 +1,7 @@ # CTI: Correlation Trend Indicator +> *Correlation trend indicator measures the linear correlation between price and a perfect trend line — how orderly is the move.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/deco/Deco.md b/lib/oscillators/deco/Deco.md index c9b6bf31..9acbd067 100644 --- a/lib/oscillators/deco/Deco.md +++ b/lib/oscillators/deco/Deco.md @@ -1,5 +1,7 @@ # DECO: Ehlers Decycler Oscillator +> *Ehlers' decycler oscillator removes the trend and isolates residual oscillation — what remains when the drift is subtracted.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/dem/Dem.md b/lib/oscillators/dem/Dem.md index da07870f..80a953b1 100644 --- a/lib/oscillators/dem/Dem.md +++ b/lib/oscillators/dem/Dem.md @@ -1,5 +1,7 @@ # DEM: DeMarker Oscillator +> *The trend is your friend — right up until DeMark starts counting against it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend — right up until DeMark starts counting against it." - DEM (DeMarker Oscillator) is a bounded [0, 1] momentum oscillator that measures sequential demand pressure by comparing each bar's high and low against the previous bar's high and low. It isolates bullish demand momentum in the numerator and bearish supply pressure in the denominator, then normalizes their ratio with SMA smoothing over a configurable period. Values near 0.7 signal overbought exhaustion; values near 0.3 signal oversold exhaustion. Neither external library in common use (TA-Lib, Skender, Tulip, Ooples) implements DeMarker, so self-consistency tests against batch/streaming/span modes serve as the primary validation. ## Historical Context diff --git a/lib/oscillators/dosc/Dosc.md b/lib/oscillators/dosc/Dosc.md index 363f4983..6edfc156 100644 --- a/lib/oscillators/dosc/Dosc.md +++ b/lib/oscillators/dosc/Dosc.md @@ -1,5 +1,7 @@ # DOSC: Derivative Oscillator +> *The derivative oscillator takes the derivative of a smoothed RSI, catching momentum shifts at their earliest inflection.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/dpo/Dpo.md b/lib/oscillators/dpo/Dpo.md index 74578dbf..d7d038fa 100644 --- a/lib/oscillators/dpo/Dpo.md +++ b/lib/oscillators/dpo/Dpo.md @@ -1,6 +1,6 @@ # DPO: Detrended Price Oscillator -> "Strip the trend and what remains is the cycle." — William Blau +> *Strip the trend and what remains is the cycle.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/dymoi/Dymoi.md b/lib/oscillators/dymoi/Dymoi.md index 4e09be3f..a2202324 100644 --- a/lib/oscillators/dymoi/Dymoi.md +++ b/lib/oscillators/dymoi/Dymoi.md @@ -1,5 +1,7 @@ # DYMOI: Dynamic Momentum Index +> *The market is not a fixed-frequency oscillator. Why would you analyze it with one?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market is not a fixed-frequency oscillator. Why would you analyze it with one?" — Tushar Chande & Stanley Kroll, *The New Technical Trader*, 1994 - DYMOI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator becomes more responsive; when price action tightens, the period extends and the output smooths. The result is an oscillator that self-adjusts its sensitivity to the market's current state, avoiding both the lag of long fixed-period RSIs in trending regimes and the noise of short-period RSIs in ranging ones. ## Historical Context diff --git a/lib/oscillators/er/Er.md b/lib/oscillators/er/Er.md index 044d093a..20b69667 100644 --- a/lib/oscillators/er/Er.md +++ b/lib/oscillators/er/Er.md @@ -1,5 +1,7 @@ # ER: Efficiency Ratio +> *The best trades move in a straight line. The worst ones wander. ER tells you which kind you're looking at.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - It is core component of KAMA (Kaufman's Adaptive Moving Average), where ER dynamically adjusts the smoothing constant. - Not available and therefore not validated against any other TA library -> "The best trades move in a straight line. The worst ones wander. ER tells you which kind you're looking at." -- Perry Kaufman - ## Historical Context Perry Kaufman introduced the Efficiency Ratio in *Trading Systems and Methods* (1995) as part of his Adaptive Moving Average (KAMA) framework. The idea was straightforward: an ideal trend indicator should react quickly in trending markets and slowly in choppy ones. ER provides the adaptive signal that tells KAMA how to behave. diff --git a/lib/oscillators/eri/Eri.md b/lib/oscillators/eri/Eri.md index c0864b16..e9bf4c24 100644 --- a/lib/oscillators/eri/Eri.md +++ b/lib/oscillators/eri/Eri.md @@ -1,6 +1,6 @@ # ERI: Elder Ray Index -> "The job of the indicator is to separate the bulls from the bears. If you can measure their power independently, you can see who is winning before the trend changes." -- Alexander Elder +> *The job of the indicator is to separate the bulls from the bears. If you can measure their power independently, you can see who is winning before the trend changes.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/fi/Fi.md b/lib/oscillators/fi/Fi.md index 79c0bf7c..6b3e5982 100644 --- a/lib/oscillators/fi/Fi.md +++ b/lib/oscillators/fi/Fi.md @@ -1,6 +1,6 @@ # FI: Force Index -> "Volume is the steam that makes the locomotive run. Price shows direction; volume shows conviction." -- Alexander Elder +> *Volume is the steam that makes the locomotive run. Price shows direction; volume shows conviction.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/fisher/Fisher.md b/lib/oscillators/fisher/Fisher.md index 9cf18d56..18617d7c 100644 --- a/lib/oscillators/fisher/Fisher.md +++ b/lib/oscillators/fisher/Fisher.md @@ -1,6 +1,6 @@ # FISHER: Ehlers Fisher Transform -> "The Fisher Transform turns price into a well-behaved Gaussian — because sometimes, the best way to see a reversal is to force the data to confess." +> *The Fisher Transform turns price into a well-behaved Gaussian — because sometimes, the best way to see a reversal is to force the data to confess.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/fisher04/Fisher04.md b/lib/oscillators/fisher04/Fisher04.md index 59ac487e..0e7bec3d 100644 --- a/lib/oscillators/fisher04/Fisher04.md +++ b/lib/oscillators/fisher04/Fisher04.md @@ -1,6 +1,6 @@ # FISHER04: Ehlers Fisher Transform (2004 Cybernetic Analysis) -> "The Fisher Transform provides clear, unambiguous turning points that make it possible to identify trend reversals." — John Ehlers, *Cybernetic Analysis for Stocks and Futures* (2004) +> *The Fisher Transform provides clear, unambiguous turning points that make it possible to identify trend reversals.* ## Introduction diff --git a/lib/oscillators/gator/Gator.md b/lib/oscillators/gator/Gator.md index 9cba1c5c..7cd4f09d 100644 --- a/lib/oscillators/gator/Gator.md +++ b/lib/oscillators/gator/Gator.md @@ -1,5 +1,7 @@ # GATOR: Williams Gator Oscillator +> *The alligator tells you the trend exists. The gator tells you whether the alligator is hungry or full.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `Math.Max(jawPeriod + jawShift, Math.Max(teethPeriod + teethShift, lipsPeriod + lipsShift))` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The alligator tells you the trend exists. The gator tells you whether the alligator is hungry or full." - The Williams Gator Oscillator is a dual-histogram visualization of the Alligator indicator's convergence and divergence. It strips the Alligator's three SMMA lines down to two absolute differences: upper (Jaw minus Teeth) and lower (negative of Teeth minus Lips). The result is a zero-centered oscillator where expanding bars signal trend acceleration and contracting bars signal trend exhaustion. Because it operates on pre-computed SMMA values, the Gator adds zero computational overhead beyond two subtractions, two absolute values, and one sign flip per bar. ## Historical Context diff --git a/lib/oscillators/imi/Imi.md b/lib/oscillators/imi/Imi.md index a29b5027..84a0b864 100644 --- a/lib/oscillators/imi/Imi.md +++ b/lib/oscillators/imi/Imi.md @@ -1,5 +1,7 @@ # IMI: Intraday Momentum Index +> *Intraday momentum index applies RSI logic to candle bodies — bullish closes accumulate strength, bearish closes accumulate weakness.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -64,39 +66,6 @@ When both sums are zero (all doji bars in window), IMI defaults to 50.0 (neutral |--------|-----------|---------|------------| | $N$ | period | 14 | $N \geq 1$ | -### Pseudo-code - -``` -Initialize: - gainBuf = RingBuffer(period) - lossBuf = RingBuffer(period) - gainSum = lossSum = 0 - bar_count = 0 - -On each bar (open, close, isNew): - if !isNew: restore previous state - - // Classify bar - diff = close - open - gain = diff > 0 ? diff : 0 - loss = diff < 0 ? -diff : 0 - - // 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 | diff --git a/lib/oscillators/inertia/Inertia.md b/lib/oscillators/inertia/Inertia.md index 7161c76e..7e49c701 100644 --- a/lib/oscillators/inertia/Inertia.md +++ b/lib/oscillators/inertia/Inertia.md @@ -1,6 +1,6 @@ # INERTIA: Inertia Oscillator -> "Price tends to keep doing what it's been doing — until it doesn't. Inertia measures the gap between reality and the regression's expectations." +> *Price tends to keep doing what it's been doing — until it doesn't. Inertia measures the gap between reality and the regression's expectations.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/kdj/Kdj.md b/lib/oscillators/kdj/Kdj.md index 45f58f0a..a72501c6 100644 --- a/lib/oscillators/kdj/Kdj.md +++ b/lib/oscillators/kdj/Kdj.md @@ -1,6 +1,6 @@ # KDJ: Enhanced Stochastic Oscillator -> "K leads, D confirms, J exaggerates — three perspectives on momentum condensed into one indicator." +> *K leads, D confirms, J exaggerates — three perspectives on momentum condensed into one indicator.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/kri/Kri.md b/lib/oscillators/kri/Kri.md index 43387f9a..c8e528bb 100644 --- a/lib/oscillators/kri/Kri.md +++ b/lib/oscillators/kri/Kri.md @@ -1,6 +1,6 @@ # KRI: Kairi Relative Index -> "The simplest measure of overextension is the oldest: how far has price strayed from its average? The Japanese knew this before anyone had a computer." -- Anonymous +> *The simplest measure of overextension is the oldest: how far has price strayed from its average? The Japanese knew this before anyone had a computer.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/kst/Kst.md b/lib/oscillators/kst/Kst.md index 903f8f66..d4f58c80 100644 --- a/lib/oscillators/kst/Kst.md +++ b/lib/oscillators/kst/Kst.md @@ -1,5 +1,7 @@ # KST: Know Sure Thing Oscillator +> *Know Sure Thing layers four smoothed rates of change at different periods, weighting longer cycles more heavily.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/lrsi/Lrsi.md b/lib/oscillators/lrsi/Lrsi.md index cf677cba..a67406f4 100644 --- a/lib/oscillators/lrsi/Lrsi.md +++ b/lib/oscillators/lrsi/Lrsi.md @@ -1,5 +1,7 @@ # LRSI: Laguerre RSI +> *The Laguerre transform lets you trade off between lag and smoothness using a single parameter.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `4` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Laguerre transform lets you trade off between lag and smoothness using a single parameter." — John Ehlers - Laguerre RSI is an adaptive oscillator invented by John Ehlers that replaces standard RSI's Wilder-smoothed gain/loss averages with a 4-stage cascaded Laguerre filter. A single γ (gamma) parameter controls the entire responsiveness-smoothness trade-off. Output is dimensionless, always in [0, 1]. No period selection required. ## Historical Context diff --git a/lib/oscillators/marketfi/Marketfi.md b/lib/oscillators/marketfi/Marketfi.md index 8692f384..a0fa4f50 100644 --- a/lib/oscillators/marketfi/Marketfi.md +++ b/lib/oscillators/marketfi/Marketfi.md @@ -1,5 +1,7 @@ # MARKETFI: Market Facilitation Index +> *Price moves in an empty room; volume tells you how many people showed up.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `> 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Price moves in an empty room; volume tells you how many people showed up." - The Market Facilitation Index answers a single question with arithmetic directness: how much price moved per unit of volume traded? One division. No lookback period. No smoothing. No parameter to debate. What you get is raw market efficiency — the price range a market delivers for each unit of liquidity consumed. Bill Williams introduced BW MFI in *Trading Chaos* (1995) as part of his Profitunity trading system, alongside the Awesome Oscillator and Accelerator Oscillator. His central insight was that price and volume carry independent signals, and only their *combination* reveals whether a trend has genuine participation. A wide range bar on thin volume suggests ease of movement but not conviction. A narrow range bar on heavy volume suggests absorption — large players defending a level. diff --git a/lib/oscillators/mstoch/Mstoch.md b/lib/oscillators/mstoch/Mstoch.md index 919bdfa6..81c187ee 100644 --- a/lib/oscillators/mstoch/Mstoch.md +++ b/lib/oscillators/mstoch/Mstoch.md @@ -1,5 +1,7 @@ # MSTOCH: Ehlers MESA Stochastic +> *MESA Stochastic applies Ehlers' cycle measurement to stochastic normalization, binding the oscillator to the dominant market rhythm.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/pgo/Pgo.md b/lib/oscillators/pgo/Pgo.md index a2ddac59..a85e494a 100644 --- a/lib/oscillators/pgo/Pgo.md +++ b/lib/oscillators/pgo/Pgo.md @@ -1,6 +1,6 @@ # PGO: Pretty Good Oscillator -> "Good enough to trade, honest enough not to pretend otherwise." +> *Good enough to trade, honest enough not to pretend otherwise.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/psl/Psl.md b/lib/oscillators/psl/Psl.md index 385f43ce..f25fbbb2 100644 --- a/lib/oscillators/psl/Psl.md +++ b/lib/oscillators/psl/Psl.md @@ -1,6 +1,6 @@ # PSL: Psychological Line -> "Markets are crowds, and crowds have moods. Count the up days; you will know the mood." -- Japanese proverb (adapted) +> *Markets are crowds, and crowds have moods. Count the up days; you will know the mood.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/qqe/Qqe.md b/lib/oscillators/qqe/Qqe.md index eebfae7f..9975cde7 100644 --- a/lib/oscillators/qqe/Qqe.md +++ b/lib/oscillators/qqe/Qqe.md @@ -1,5 +1,7 @@ # QQE: Quantitative Qualitative Estimation +> *QQE smooths RSI and wraps it in dynamic trailing bands, quantifying both the quality and magnitude of momentum.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/reflex/Reflex.md b/lib/oscillators/reflex/Reflex.md index 4b73b53b..c0276fbd 100644 --- a/lib/oscillators/reflex/Reflex.md +++ b/lib/oscillators/reflex/Reflex.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/oscillators/reverseema/ReverseEma.md b/lib/oscillators/reverseema/ReverseEma.md index 7f1a4610..2a783b59 100644 --- a/lib/oscillators/reverseema/ReverseEma.md +++ b/lib/oscillators/reverseema/ReverseEma.md @@ -1,5 +1,7 @@ # REVERSEEMA: Ehlers Reverse EMA +> *The best way to remove lag is to understand where it comes from.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best way to remove lag is to understand where it comes from." — John F. Ehlers - ## Introduction The Reverse EMA applies an 8-stage cascaded Z-transform inversion to a compensated EMA, progressively extracting and subtracting the accumulated lag component. Where standard EMA smoothing introduces phase delay proportional to the filter order, the reverse cascade reconstructs the lag error through successively doubled power coefficients of the decay factor, producing a signal with dramatically reduced latency. O(1) per bar, zero allocation, 8 FMA operations in the critical path. diff --git a/lib/oscillators/rvgi/Rvgi.md b/lib/oscillators/rvgi/Rvgi.md index 216771b5..c08781c1 100644 --- a/lib/oscillators/rvgi/Rvgi.md +++ b/lib/oscillators/rvgi/Rvgi.md @@ -1,5 +1,7 @@ # RVGI: Relative Vigor Index +> *Relative Vigor Index compares the close-open range to the high-low range, measuring conviction in each bar's direction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/smi/Smi.md b/lib/oscillators/smi/Smi.md index 611ba4eb..5e082f33 100644 --- a/lib/oscillators/smi/Smi.md +++ b/lib/oscillators/smi/Smi.md @@ -1,6 +1,6 @@ # SMI: Stochastic Momentum Index -> "The stochastic tells you where price is in the range. The SMI tells you how enthusiastically it got there." — William Blau +> *The stochastic tells you where price is in the range. The SMI tells you how enthusiastically it got there.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/squeeze/Squeeze.md b/lib/oscillators/squeeze/Squeeze.md index fbf3b588..2ae682df 100644 --- a/lib/oscillators/squeeze/Squeeze.md +++ b/lib/oscillators/squeeze/Squeeze.md @@ -1,5 +1,7 @@ # SQUEEZE: Squeeze Momentum +> *Squeeze momentum detects compression inside Bollinger-Keltner overlap and then measures the explosive release when bands expand.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/stc/stc.md b/lib/oscillators/stc/stc.md index 3a682930..acfdcf57 100644 --- a/lib/oscillators/stc/stc.md +++ b/lib/oscillators/stc/stc.md @@ -1,5 +1,7 @@ # STC: Schaff Trend Cycle +> *Schaff Trend Cycle applies double stochastic smoothing to MACD, compressing a trend indicator into an oscillator's bounded range.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -91,54 +93,6 @@ Smoothing options: | $s$ | slowLength | 50 | $s > f$ | | — | smoothing | EMA | None / EMA / Sigmoid / Digital | -### Pseudo-code - -``` -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 - -On each bar (price, isNew): - if !isNew: restore previous state - - // 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 - - // 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) -``` - ### Signal Characteristics | Condition | Output Behavior | diff --git a/lib/oscillators/stoch/Stoch.md b/lib/oscillators/stoch/Stoch.md index 4cd53c85..6ffa89fa 100644 --- a/lib/oscillators/stoch/Stoch.md +++ b/lib/oscillators/stoch/Stoch.md @@ -1,6 +1,6 @@ # STOCH: Stochastic Oscillator -> "The Stochastic Oscillator doesn't follow price. It follows the speed, or momentum, of price. Momentum changes direction before price." -- George C. Lane +> *The Stochastic Oscillator doesn't follow price. It follows the speed, or momentum, of price. Momentum changes direction before price.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/stochf/Stochf.md b/lib/oscillators/stochf/Stochf.md index ababff9f..d157ced7 100644 --- a/lib/oscillators/stochf/Stochf.md +++ b/lib/oscillators/stochf/Stochf.md @@ -1,6 +1,6 @@ # STOCHF: Stochastic Fast Oscillator -> "Speed kills in traffic. In markets, it merely whipsaws." -- Anonymous +> *Speed kills in traffic. In markets, it merely whipsaws.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/stochrsi/Stochrsi.md b/lib/oscillators/stochrsi/Stochrsi.md index 7b7efd11..568c373c 100644 --- a/lib/oscillators/stochrsi/Stochrsi.md +++ b/lib/oscillators/stochrsi/Stochrsi.md @@ -1,6 +1,6 @@ # STOCHRSI: Stochastic RSI Oscillator -> "RSI tells you whether momentum is overbought. Stochastic RSI tells you whether RSI itself is overbought. It's turtles all the way down." -- Anonymous quant +> *RSI tells you whether momentum is overbought. Stochastic RSI tells you whether RSI itself is overbought. It's turtles all the way down.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/td_seq/Td_seq.md b/lib/oscillators/td_seq/Td_seq.md index be09c243..e29248ed 100644 --- a/lib/oscillators/td_seq/Td_seq.md +++ b/lib/oscillators/td_seq/Td_seq.md @@ -1,5 +1,7 @@ # TD_SEQ: TD Sequential +> *TD Sequential counts consecutive closes relative to a prior bar, mapping exhaustion through the simple act of counting.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | diff --git a/lib/oscillators/trendflex/Trendflex.md b/lib/oscillators/trendflex/Trendflex.md index 4255d5f2..fefc11a1 100644 --- a/lib/oscillators/trendflex/Trendflex.md +++ b/lib/oscillators/trendflex/Trendflex.md @@ -1,5 +1,7 @@ # TRENDFLEX: Ehlers Trendflex Indicator +> *The trend is your friend until it bends.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Oscillator | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend until it bends." — Ed Seykota, but Ehlers actually measures the bending. - ## Introduction The Trendflex indicator combines a 2-pole Butterworth low-pass pre-filter (Super Smoother) with an O(1) cumulative slope measurement and exponential RMS normalization to produce a zero-centered oscillator that quantifies trend strength. Unlike conventional slope or momentum indicators that suffer from noise amplification or lag, Trendflex pre-smooths via the Super Smoother, computes the least-squares slope of the filtered signal over a lookback window in constant time, then normalizes by a running RMS estimate. The result: a bounded oscillator where values above zero indicate uptrend, below zero indicate downtrend, and magnitude reflects trend conviction. diff --git a/lib/oscillators/trix/Trix.md b/lib/oscillators/trix/Trix.md index b42b4eaf..7cb8a6aa 100644 --- a/lib/oscillators/trix/Trix.md +++ b/lib/oscillators/trix/Trix.md @@ -1,6 +1,6 @@ # TRIX: Triple Exponential Average Oscillator -> "Smooth it once, smooth it twice, smooth it thrice, then ask: is it still moving?" -- Jack Hutson, probably +> *Smooth it once, smooth it twice, smooth it thrice, then ask: is it still moving?* | Property | Value | |--------------|-------| diff --git a/lib/oscillators/ttm_wave/TtmWave.md b/lib/oscillators/ttm_wave/TtmWave.md index aa0e1a95..51d0a272 100644 --- a/lib/oscillators/ttm_wave/TtmWave.md +++ b/lib/oscillators/ttm_wave/TtmWave.md @@ -1,6 +1,6 @@ # TTM_WAVE: TTM Wave Indicator -> "The market speaks in waves. Most traders only hear the ripples." -- John Carter +> *The market speaks in waves. Most traders only hear the ripples.* | Property | Value | |----------|-------| diff --git a/lib/oscillators/ultosc/Ultosc.md b/lib/oscillators/ultosc/Ultosc.md index 8d334b9a..c6126ca3 100644 --- a/lib/oscillators/ultosc/Ultosc.md +++ b/lib/oscillators/ultosc/Ultosc.md @@ -1,6 +1,6 @@ # ULTOSC: Ultimate Oscillator -> "Why use one timeframe when three can save you from yourself?" +> *Why use one timeframe when three can save you from yourself?* | Property | Value | |----------|-------| diff --git a/lib/oscillators/willr/Willr.md b/lib/oscillators/willr/Willr.md index 03ab191f..fa3d3c85 100644 --- a/lib/oscillators/willr/Willr.md +++ b/lib/oscillators/willr/Willr.md @@ -1,6 +1,6 @@ # WILLR: Williams %R -> "The market tells you where it closed relative to where it traded. That single fact contains more information than most traders realize." -- George Lane +> *The market tells you where it closed relative to where it traded. That single fact contains more information than most traders realize.* | Property | Value | |----------|-------| diff --git a/lib/reversals/_index.md b/lib/reversals/_index.md index 02f1c8fe..cd4056c3 100644 --- a/lib/reversals/_index.md +++ b/lib/reversals/_index.md @@ -1,7 +1,5 @@ # Reversals -> "The market speaks in reversals. The art is hearing it above the noise." - Reversal indicators identify potential turning points where price may change direction. Pivot points calculate support/resistance from prior period data. Pattern-based tools detect structural shifts in price action. | Indicator | Full Name | Description | diff --git a/lib/reversals/chandelier/Chandelier.md b/lib/reversals/chandelier/Chandelier.md index bd21d0e8..1e59a0a5 100644 --- a/lib/reversals/chandelier/Chandelier.md +++ b/lib/reversals/chandelier/Chandelier.md @@ -1,5 +1,7 @@ # CHANDELIER: Chandelier Exit +> *The exit is more important than the entry. Everyone knows where to get in; getting out alive is the real trick.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The exit is more important than the entry. Everyone knows where to get in; getting out alive is the real trick." - The Chandelier Exit computes ATR-based trailing stop levels that hang from the highest high (for longs) or rise from the lowest low (for shorts) over a lookback period. It produces two overlay lines: ExitLong (trailing stop for long positions) and ExitShort (trailing stop for short positions). Developed by Charles Le Beau and popularized by Alexander Elder. Default parameters: period 22, multiplier 3.0. ## Historical Context diff --git a/lib/reversals/ckstop/Ckstop.md b/lib/reversals/ckstop/Ckstop.md index 4ee07539..cd1acf9c 100644 --- a/lib/reversals/ckstop/Ckstop.md +++ b/lib/reversals/ckstop/Ckstop.md @@ -1,5 +1,7 @@ # CKSTOP: Chande Kroll Stop +> *The best stop-loss is the one that knows where volatility ends and trend begins.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `atrPeriod + stopPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best stop-loss is the one that knows where volatility ends and trend begins." - The Chande Kroll Stop computes adaptive trailing stop levels using ATR-smoothed volatility envelopes around rolling extremes. It produces two lines: StopLong (support) and StopShort (resistance). When price trades above both stops, the trend is bullish. When below both, bearish. Crossovers between the two stops signal potential reversals. ## Historical Context diff --git a/lib/reversals/fractals/Fractals.md b/lib/reversals/fractals/Fractals.md index 2b186fba..605a9d4e 100644 --- a/lib/reversals/fractals/Fractals.md +++ b/lib/reversals/fractals/Fractals.md @@ -1,5 +1,7 @@ # FRACTALS: Williams Fractals +> *Markets leave fingerprints at their turning points. Five bars is all it takes to read them.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Markets leave fingerprints at their turning points. Five bars is all it takes to read them." - Williams Fractals detect local price extremes using a strict five-bar pattern: an Up Fractal marks a bar whose high exceeds the highs of the two bars before and after it; a Down Fractal marks a bar whose low undercuts the lows of the two bars before and after it. No parameters, no smoothing, no lag compensation. The pattern either exists or it does not. Developed by Bill Williams and published in *Trading Chaos* (1995). ## Historical Context diff --git a/lib/reversals/pivot/Pivot.md b/lib/reversals/pivot/Pivot.md index 14459218..c4800afa 100644 --- a/lib/reversals/pivot/Pivot.md +++ b/lib/reversals/pivot/Pivot.md @@ -1,5 +1,7 @@ # PIVOT: Classic Pivot Points (Floor Trader Pivots) +> *The floor traders had it figured out before the quants arrived. Three numbers from yesterday's bar, seven levels for today. No optimization, no curve fitting, no excuses.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The floor traders had it figured out before the quants arrived. Three numbers from yesterday's bar, seven levels for today. No optimization, no curve fitting, no excuses." - Classic Pivot Points calculate seven horizontal support and resistance levels from the previous bar's high, low, and close. The central pivot point (PP) is the arithmetic mean of HLC; three resistance levels (R1-R3) and three support levels (S1-S3) are derived from PP and the prior bar's range. The formula has been in continuous use since the 1930s among floor traders at commodity exchanges. Zero parameters, zero lag, zero ambiguity. ## Historical Context diff --git a/lib/reversals/pivotcam/Pivotcam.md b/lib/reversals/pivotcam/Pivotcam.md index 52a6678f..2072528c 100644 --- a/lib/reversals/pivotcam/Pivotcam.md +++ b/lib/reversals/pivotcam/Pivotcam.md @@ -1,5 +1,7 @@ # PIVOTCAM: Camarilla Pivot Points +> *The Camarilla trader does not care where the market opens. The trader cares how far price strays from yesterday's close, and whether it returns.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Camarilla trader does not care where the market opens. The trader cares how far price strays from yesterday's close, and whether it returns." - Camarilla Pivot Points calculate nine horizontal support and resistance levels from the previous bar's high, low, and close. Unlike classic floor trader pivots that radiate from the PP midpoint, Camarilla levels radiate symmetrically from the previous close using fixed fractions of the prior range. The R3/S3 levels serve as the primary mean-reversion zone; breakouts beyond R4/S4 signal trend continuation. Developed by Nick Scott in 1989 using bond market data, the equation was originally distributed as a shareware Excel plugin. ## Historical Context diff --git a/lib/reversals/pivotdem/Pivotdem.md b/lib/reversals/pivotdem/Pivotdem.md index 14f18178..c8cc1226 100644 --- a/lib/reversals/pivotdem/Pivotdem.md +++ b/lib/reversals/pivotdem/Pivotdem.md @@ -1,5 +1,7 @@ # PIVOTDEM: DeMark Pivot Points +> *Most pivot formulas treat every bar the same. DeMark looked at the open-close relationship and asked: why would a bearish bar predict the same levels as a bullish one?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Most pivot formulas treat every bar the same. DeMark looked at the open-close relationship and asked: why would a bearish bar predict the same levels as a bullish one?" - DeMark Pivot Points calculate three horizontal support and resistance levels from the previous bar's open, high, low, and close. The defining characteristic is a conditional intermediate value X that changes its weighting depending on whether the prior bar closed below, above, or equal to its open. Bearish bars weight the low; bullish bars weight the high; doji bars weight the close. Three levels (PP, R1, S1) emerge from this single conditional calculation. The only pivot variant that uses the open price. ## Historical Context diff --git a/lib/reversals/pivotext/Pivotext.md b/lib/reversals/pivotext/Pivotext.md index 16cf1f62..fc20e577 100644 --- a/lib/reversals/pivotext/Pivotext.md +++ b/lib/reversals/pivotext/Pivotext.md @@ -1,5 +1,7 @@ # PIVOTEXT: Extended Traditional Pivot Points +> *Classic pivots tell you where the crowd expects the market to pause. Extended pivots tell you where the crowd starts to panic.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Classic pivots tell you where the crowd expects the market to pause. Extended pivots tell you where the crowd starts to panic." - Extended Traditional Pivot Points calculate eleven horizontal support and resistance levels from the previous bar's high, low, and close. The core levels (PP, R1-R3, S1-S3) are identical to classic floor trader pivots. The extension adds R4/R5 and S4/S5 levels that project further beyond the prior bar's range, covering extreme move scenarios such as gap opens, news-driven spikes, and trend continuation through multiple prior-range increments. The formula is pure arithmetic with zero parameters. ## Historical Context diff --git a/lib/reversals/pivotfib/Pivotfib.md b/lib/reversals/pivotfib/Pivotfib.md index da7b8479..3f992a90 100644 --- a/lib/reversals/pivotfib/Pivotfib.md +++ b/lib/reversals/pivotfib/Pivotfib.md @@ -1,5 +1,7 @@ # PIVOTFIB: Fibonacci Pivot Points +> *Fibonacci pivots project support and resistance from the golden ratio, blending numerology with price structure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | diff --git a/lib/reversals/pivotwood/Pivotwood.md b/lib/reversals/pivotwood/Pivotwood.md index 7988e385..540b84f9 100644 --- a/lib/reversals/pivotwood/Pivotwood.md +++ b/lib/reversals/pivotwood/Pivotwood.md @@ -1,5 +1,7 @@ # PIVOTWOOD: Woodie's Pivot Points +> *Woodie's pivots weight the close twice, tilting the pivot toward where the session actually settled.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | diff --git a/lib/reversals/psar/Psar.md b/lib/reversals/psar/Psar.md index 915dbc61..6ae90cc3 100644 --- a/lib/reversals/psar/Psar.md +++ b/lib/reversals/psar/Psar.md @@ -1,5 +1,7 @@ # PSAR: Parabolic Stop And Reverse +> *The trend is your friend until the end when it bends.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires `1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend until the end when it bends." — Ed Seykota - ## Introduction The Parabolic Stop And Reverse (PSAR) is a trend-following overlay indicator created by J. Welles Wilder Jr. in 1978. It produces a trailing stop level that accelerates toward price as the trend extends, then flips to the opposite side when price crosses the stop. The acceleration mechanism is the key differentiator: SAR starts slow and tightens progressively, creating the characteristic parabolic curve that gives the indicator its name. Default parameters (0.02 start, 0.02 increment, 0.20 maximum) produce approximately 10–30 reversals per 500 bars on typical equity data. diff --git a/lib/reversals/sarext/Sarext.md b/lib/reversals/sarext/Sarext.md index 7eb0e325..854e57ee 100644 --- a/lib/reversals/sarext/Sarext.md +++ b/lib/reversals/sarext/Sarext.md @@ -1,5 +1,7 @@ # SAREXT: Parabolic SAR Extended +> *The trend is your friend — but which way it accelerates depends on whether you're long or short.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -17,8 +19,6 @@ - Requires `2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib reference implementation. -> "The trend is your friend — but which way it accelerates depends on whether you're long or short." — QuanTAlib - ## Introduction The Parabolic SAR Extended (SAREXT) is an enhanced version of Wilder's Parabolic Stop And Reverse that allows **separate acceleration factor configurations for long and short positions**. While standard PSAR uses the same AF start, increment, and maximum for both trend directions, SAREXT provides six independent AF parameters (three for long, three for short), plus a `startValue` to force initial direction and `offsetOnReverse` to add a gap buffer when the indicator reverses. diff --git a/lib/reversals/swings/Swings.md b/lib/reversals/swings/Swings.md index 4295811a..78f8e441 100644 --- a/lib/reversals/swings/Swings.md +++ b/lib/reversals/swings/Swings.md @@ -1,5 +1,7 @@ # SWINGS: Swing High/Low Detection +> *The market tells you where it turned. You just have to listen long enough to be sure it actually meant it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market tells you where it turned. You just have to listen long enough to be sure it actually meant it." - Swing High/Low detection identifies local price extremes using a configurable lookback window. A Swing High marks a bar whose high strictly exceeds the highs of all bars within the lookback window on each side. A Swing Low marks a bar whose low is strictly less than all corresponding lows. The lookback parameter controls sensitivity: larger lookback windows require more confirmation and produce fewer, more significant signals. This generalizes Williams' fixed five-bar Fractals into a flexible structural analysis tool. ## Historical Context diff --git a/lib/reversals/ttm_scalper/TtmScalper.md b/lib/reversals/ttm_scalper/TtmScalper.md index fae15150..3832dac1 100644 --- a/lib/reversals/ttm_scalper/TtmScalper.md +++ b/lib/reversals/ttm_scalper/TtmScalper.md @@ -1,5 +1,7 @@ # TTM_SCALPER: TTM Scalper Alert +> *TTM Scalper spots pivot reversals in real time, marking the bars where short-term direction flips.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Reversal | diff --git a/lib/statistics/_index.md b/lib/statistics/_index.md index 5c7cae65..52a0391d 100644 --- a/lib/statistics/_index.md +++ b/lib/statistics/_index.md @@ -1,7 +1,5 @@ # Statistics -> "All models are wrong, but some are useful." — George Box - Statistical tools applied to price and returns. These indicators quantify relationships, measure dispersion, test hypotheses. Unlike momentum or trend indicators, statistics describe the data itself. | Indicator | Full Name | Description | diff --git a/lib/statistics/acf/Acf.md b/lib/statistics/acf/Acf.md index 01d9aef4..e2f9ccc0 100644 --- a/lib/statistics/acf/Acf.md +++ b/lib/statistics/acf/Acf.md @@ -1,5 +1,7 @@ # ACF: Autocorrelation Function +> *The past doesn't predict the future, but it whispers patterns to those who listen.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against mathematical properties and theoretical AR-process expectations. -> "The past doesn't predict the future, but it whispers patterns to those who listen." - The Autocorrelation Function (ACF) measures the correlation of a time series with a lagged copy of itself. It is fundamental for identifying repeating patterns, seasonal effects, and determining the order of time series models like ARMA/ARIMA. ## Historical Context diff --git a/lib/statistics/beta/Beta.md b/lib/statistics/beta/Beta.md index 8c37bae4..37ba9449 100644 --- a/lib/statistics/beta/Beta.md +++ b/lib/statistics/beta/Beta.md @@ -1,5 +1,7 @@ # Beta: Beta Coefficient +> *Volatility is not risk. It's the price of admission.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility is not risk. It's the price of admission." - Beta measures the volatility of an asset in relation to the overall market. It's the slope of the regression line between the asset's returns and the market's returns. A beta of 1.0 means the asset moves in lockstep with the market. A beta of 2.0 means the asset is twice as volatile as the market. ## Historical Context diff --git a/lib/statistics/cma/Cma.md b/lib/statistics/cma/Cma.md index dbbb7a95..3b684566 100644 --- a/lib/statistics/cma/Cma.md +++ b/lib/statistics/cma/Cma.md @@ -1,5 +1,7 @@ # CMA: Cumulative Moving Average +> *The running average that never forgets. Every single tick you've ever fed it? Still in there, affecting the result. It's like the elephant of technical indicators.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The running average that never forgets. Every single tick you've ever fed it? Still in there, affecting the result. It's like the elephant of technical indicators." - The Cumulative Moving Average (CMA) calculates the arithmetic mean of ALL data points seen so far, not just a fixed window. Unlike SMA or EMA which use a sliding window, CMA treats every historical value with equal weight. As the sample size grows, each new value has diminishing impact on the average. ## Historical Context diff --git a/lib/statistics/cointegration/Cointegration.md b/lib/statistics/cointegration/Cointegration.md index 7bbad98c..1a869c4f 100644 --- a/lib/statistics/cointegration/Cointegration.md +++ b/lib/statistics/cointegration/Cointegration.md @@ -1,5 +1,7 @@ # Cointegration: Engle-Granger Two-Step Cointegration Test +> *Correlation tells you they move together. Cointegration tells you they're bound together. Two stocks can be uncorrelated yet cointegrated, or perfectly correlated yet destined to drift apart forever. The difference between 'similar direction' and 'shared destiny' is the difference between a tourist attraction and a gravitational orbit.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TradingView PineScript reference and statistical property tests. -> "Correlation tells you they move together. Cointegration tells you they're bound together. Two stocks can be uncorrelated yet cointegrated, or perfectly correlated yet destined to drift apart forever. The difference between 'similar direction' and 'shared destiny' is the difference between a tourist attraction and a gravitational orbit." - The Cointegration indicator measures the long-run equilibrium relationship between two price series using the Engle-Granger two-step method with an Augmented Dickey-Fuller (ADF) test. Unlike correlation, which measures short-term co-movement, cointegration tests whether two non-stationary series share a common stochastic trend—meaning they may diverge temporarily but are statistically bound to revert to their equilibrium relationship. ## Historical Context diff --git a/lib/statistics/correlation/Correlation.md b/lib/statistics/correlation/Correlation.md index a50b56a7..45a40828 100644 --- a/lib/statistics/correlation/Correlation.md +++ b/lib/statistics/correlation/Correlation.md @@ -1,5 +1,7 @@ # CORR: Pearson Correlation Coefficient +> *Correlation is not causation, but it sure is a hint. The market doesn't care why two instruments move together—only that they do, and whether that relationship will persist long enough for you to profit from it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TradingView reference behavior and mathematical invariants. -> "Correlation is not causation, but it sure is a hint. The market doesn't care why two instruments move together—only that they do, and whether that relationship will persist long enough for you to profit from it." - The Pearson Correlation Coefficient measures the linear relationship between two variables, returning a value from -1 (perfect negative correlation) to +1 (perfect positive correlation). Zero indicates no linear relationship. This implementation uses running sums for O(1) streaming updates, making it suitable for real-time analysis of price relationships. ## Historical Context diff --git a/lib/statistics/covariance/Covariance.md b/lib/statistics/covariance/Covariance.md index cdfc6309..18f26d78 100644 --- a/lib/statistics/covariance/Covariance.md +++ b/lib/statistics/covariance/Covariance.md @@ -1,5 +1,7 @@ # Covariance: Covariance +> *Correlation is just covariance normalized by standard deviation. But sometimes you want the raw, unadulterated relationship.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Correlation is just covariance normalized by standard deviation. But sometimes you want the raw, unadulterated relationship." - Covariance measures the joint variability of two random variables. It indicates the direction of the linear relationship between variables. ## Architecture & Physics diff --git a/lib/statistics/entropy/Entropy.md b/lib/statistics/entropy/Entropy.md index 081369fc..9acf1807 100644 --- a/lib/statistics/entropy/Entropy.md +++ b/lib/statistics/entropy/Entropy.md @@ -1,5 +1,7 @@ # ENTROPY: Shannon Entropy +> *Information is the resolution of uncertainty.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Information is the resolution of uncertainty." — Claude Shannon - Shannon Entropy measures the unpredictability or randomness of a time series over a sliding window. A low entropy value indicates the series is highly predictable (clustered values), while a high entropy value indicates the data is spread uniformly across its range — maximum randomness. ## Historical Context diff --git a/lib/statistics/geomean/Geomean.md b/lib/statistics/geomean/Geomean.md index f9f2fcea..dbff5508 100644 --- a/lib/statistics/geomean/Geomean.md +++ b/lib/statistics/geomean/Geomean.md @@ -1,5 +1,7 @@ # GEOMEAN: Geometric Mean +> *The geometric mean is never greater than the arithmetic mean.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The geometric mean is never greater than the arithmetic mean." - Mathematical inequality since antiquity - The Geometric Mean computes the nth root of the product of n positive values over a sliding window. Unlike the arithmetic mean, it captures multiplicative relationships and is the correct average for growth rates, ratios, and log-normally distributed data. For financial time series, this means it properly accounts for compounding. ## Historical Context diff --git a/lib/statistics/granger/Granger.md b/lib/statistics/granger/Granger.md index 847f965c..1b145535 100644 --- a/lib/statistics/granger/Granger.md +++ b/lib/statistics/granger/Granger.md @@ -1,5 +1,7 @@ # GRANGER: Granger Causality F-Statistic +> *Correlation is not causation, but Granger causality is not causation either. It is prediction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Correlation is not causation, but Granger causality is not causation either. It is prediction." -- Clive Granger - ## Introduction The Granger Causality test asks a precise, falsifiable question: does knowing the history of series X improve your ability to predict series Y, beyond what Y's own history already provides? The answer arrives as an F-statistic from comparing two OLS regression models. Higher F means X contains predictive information about Y that Y itself does not. This implementation uses lag-1, runs in O(1) streaming mode via running sums, and handles bar corrections for live trading. diff --git a/lib/statistics/harmean/Harmean.md b/lib/statistics/harmean/Harmean.md index 5101e8c2..d556be4b 100644 --- a/lib/statistics/harmean/Harmean.md +++ b/lib/statistics/harmean/Harmean.md @@ -1,5 +1,7 @@ # HARMEAN: Harmonic Mean +> *The harmonic mean is never greater than the geometric mean, which is never greater than the arithmetic mean.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The harmonic mean is never greater than the geometric mean, which is never greater than the arithmetic mean." - The Mean Inequality, a mathematical fact older than calculus - The Harmonic Mean computes the reciprocal of the arithmetic mean of reciprocals over a sliding window. It is the correct average for quantities defined in terms of rates or ratios (speed, P/E ratios, yield). For financial time series, the harmonic mean gives the largest discount to outliers, making it the most conservative of the three Pythagorean means. ## Historical Context diff --git a/lib/statistics/hurst/Hurst.md b/lib/statistics/hurst/Hurst.md index 18c61339..bbcb11f5 100644 --- a/lib/statistics/hurst/Hurst.md +++ b/lib/statistics/hurst/Hurst.md @@ -1,5 +1,7 @@ # HURST: Hurst Exponent +> *The past is not dead. In fact, it's not even past.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The past is not dead. In fact, it's not even past." — William Faulkner, and also every mean-reverting time series that refuses to forget. - ## Introduction The Hurst Exponent ($H$) quantifies long-range dependence in a time series through Rescaled Range (R/S) analysis. Where autocorrelation decays and dies, the Hurst exponent measures the memory that persists across scales. $H > 0.5$ signals persistence (trending behavior), $H < 0.5$ signals anti-persistence (mean-reversion), and $H = 0.5$ represents the memoryless random walk that efficient market theorists insist you should believe in. diff --git a/lib/statistics/iqr/Iqr.md b/lib/statistics/iqr/Iqr.md index 111f62f3..fcf5543b 100644 --- a/lib/statistics/iqr/Iqr.md +++ b/lib/statistics/iqr/Iqr.md @@ -1,5 +1,7 @@ # IQR: Interquartile Range +> *The median is the most important statistic, and the interquartile range is the second most important.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The median is the most important statistic, and the interquartile range is the second most important." — John Tukey - ## Introduction The Interquartile Range measures the spread of the middle 50% of a sorted dataset within a rolling window. By subtracting the 25th percentile (Q1) from the 75th percentile (Q3), IQR provides a robust dispersion metric that ignores outliers in both tails. Unlike standard deviation, which squares deviations and amplifies extremes, IQR tells you how wide the "typical" price band actually is. diff --git a/lib/statistics/jb/Jb.md b/lib/statistics/jb/Jb.md index 60c5c6a1..87973c6c 100644 --- a/lib/statistics/jb/Jb.md +++ b/lib/statistics/jb/Jb.md @@ -1,5 +1,7 @@ # JB: Jarque-Bera Test +> *The assumption of normality is the most dangerous assumption in all of statistics.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The assumption of normality is the most dangerous assumption in all of statistics." — George Box (paraphrased) - The Jarque-Bera test quantifies departure from normality by combining skewness and excess kurtosis into a single chi-squared statistic. A rolling JB value near zero means the window looks Gaussian. Values exceeding 5.991 (5% significance) reject normality. Financial returns almost always fail this test, which is precisely why the test matters. ## Historical Context diff --git a/lib/statistics/kendall/Kendall.md b/lib/statistics/kendall/Kendall.md index ae516009..c145af05 100644 --- a/lib/statistics/kendall/Kendall.md +++ b/lib/statistics/kendall/Kendall.md @@ -1,6 +1,6 @@ # KENDALL: Kendall Tau-a Rank Correlation Coefficient -> "The rank is the message." -- adapted from Marshall McLuhan +> *The rank is the message.* diff --git a/lib/statistics/kurtosis/Kurtosis.md b/lib/statistics/kurtosis/Kurtosis.md index 5ece3e55..017edba0 100644 --- a/lib/statistics/kurtosis/Kurtosis.md +++ b/lib/statistics/kurtosis/Kurtosis.md @@ -1,5 +1,7 @@ # KURTOSIS: Excess Kurtosis +> *Normal is getting dressed in clothes that you buy for work and driving through traffic in a car that you are still paying for, in order to get to the job you need to pay for the clothes and the car.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Normal is getting dressed in clothes that you buy for work and driving through traffic in a car that you are still paying for, in order to get to the job you need to pay for the clothes and the car." The fourth moment measures how far your returns deviate from that comforting fiction. - ## Introduction Kurtosis measures the **tailedness** of a probability distribution. Specifically, this implementation calculates *excess kurtosis*, which subtracts 3 from the raw kurtosis so that a normal distribution has excess kurtosis of zero. A positive value (leptokurtic) indicates fatter tails than normal, meaning more frequent extreme events. A negative value (platykurtic) indicates thinner tails, fewer surprises. Financial returns consistently exhibit positive excess kurtosis, which is why "once in a century" events happen every decade. diff --git a/lib/statistics/linreg/LinReg.md b/lib/statistics/linreg/LinReg.md index 95cd08fd..16b3be15 100644 --- a/lib/statistics/linreg/LinReg.md +++ b/lib/statistics/linreg/LinReg.md @@ -1,5 +1,7 @@ # LinReg: Linear Regression Curve +> *The trend is your friend, until it bends.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is your friend, until it bends." - The Linear Regression Curve plots the end point of the linear regression line for each bar. It fits a straight line $y = mx + b$ to the data points using the least squares method, providing a smoothed representation of the price trend that is more responsive than a Simple Moving Average (SMA). ## Historical Context diff --git a/lib/statistics/meandev/MeanDev.md b/lib/statistics/meandev/MeanDev.md index d76e8ddd..319cf0a2 100644 --- a/lib/statistics/meandev/MeanDev.md +++ b/lib/statistics/meandev/MeanDev.md @@ -1,5 +1,7 @@ # MeanDev: Mean Deviation (Average Absolute Deviation) +> *Not all dispersion is created equal — some prefer robustness over elegance.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Not all dispersion is created equal — some prefer robustness over elegance." - Mean Deviation (also known as Mean Absolute Deviation or Average Absolute Deviation) measures the average of the absolute deviations from the mean. Unlike Standard Deviation, it does not square the deviations, making it more robust to outliers and more intuitive to interpret. ## Historical Context diff --git a/lib/statistics/median/Median.md b/lib/statistics/median/Median.md index 015e0969..c33fea08 100644 --- a/lib/statistics/median/Median.md +++ b/lib/statistics/median/Median.md @@ -1,5 +1,7 @@ # MEDIAN: Rolling Median +> *The average is easily influenced by outliers; the median stands its ground.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The average is easily influenced by outliers; the median stands its ground." - The Rolling Median is a robust statistic that represents the middle value of a dataset within a moving window. Unlike the Simple Moving Average (SMA), which can be skewed by extreme values, the Median provides a more stable measure of central tendency, making it particularly useful for filtering noise in volatile markets. ## Historical Context diff --git a/lib/statistics/mode/Mode.md b/lib/statistics/mode/Mode.md index 98044e7f..99e85241 100644 --- a/lib/statistics/mode/Mode.md +++ b/lib/statistics/mode/Mode.md @@ -1,5 +1,7 @@ # MODE: Statistical Mode (Most Frequent Value) +> *The mode is the value that appears most frequently in a data set — the only measure of central tendency that tells you what's actually popular, not what's average.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The mode is the value that appears most frequently in a data set — the only measure of central tendency that tells you what's actually popular, not what's average." - ## Introduction The **Mode** is a rolling statistical indicator that identifies the most frequently occurring value within diff --git a/lib/statistics/pacf/Pacf.md b/lib/statistics/pacf/Pacf.md index d4078393..29c36cc9 100644 --- a/lib/statistics/pacf/Pacf.md +++ b/lib/statistics/pacf/Pacf.md @@ -1,5 +1,7 @@ # PACF: Partial Autocorrelation Function +> *Strip away the intermediaries, and you'll see the true direct relationship.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against mathematical properties and Durbin-Levinson recursion expectations. -> "Strip away the intermediaries, and you'll see the true direct relationship." - The Partial Autocorrelation Function (PACF) measures the correlation between a time series and its lagged values, after removing the effects of all intermediate lags. While ACF shows total correlation at each lag, PACF isolates the direct correlation, making it essential for AR model identification. ## Historical Context diff --git a/lib/statistics/percentile/Percentile.md b/lib/statistics/percentile/Percentile.md index 921fe367..6899e495 100644 --- a/lib/statistics/percentile/Percentile.md +++ b/lib/statistics/percentile/Percentile.md @@ -1,5 +1,7 @@ # PERCENTILE: Rolling Percentile +> *There are three kinds of lies: lies, damned lies, and statistics.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "There are three kinds of lies: lies, damned lies, and statistics." — Mark Twain. - > But percentiles, at least, tell you exactly where you stand. ## Introduction diff --git a/lib/statistics/polyfit/Polyfit.md b/lib/statistics/polyfit/Polyfit.md index e9bd78ed..29b83c06 100644 --- a/lib/statistics/polyfit/Polyfit.md +++ b/lib/statistics/polyfit/Polyfit.md @@ -1,5 +1,7 @@ # POLYFIT: Polynomial Fitting +> *Polynomial fitting bends a curve through price data, capturing nonlinear trends that a straight line cannot.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | diff --git a/lib/statistics/quantile/Quantile.md b/lib/statistics/quantile/Quantile.md index 89037071..2b1ac97d 100644 --- a/lib/statistics/quantile/Quantile.md +++ b/lib/statistics/quantile/Quantile.md @@ -1,5 +1,7 @@ # QUANTILE: Rolling Quantile +> *The quantile function is the inverse of the distribution function.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The quantile function is the inverse of the distribution function." — Every probability textbook ever written, and yet somehow it still surprises people. - ## Introduction The Rolling Quantile computes the value below which a given fraction of observations fall within a sliding window. It is mathematically identical to Percentile but uses the statistician's convention of q ∈ [0, 1] instead of the analyst's p ∈ [0, 100]. When q=0.5, it returns the median; q=0 gives the minimum; q=1 gives the maximum. The linear interpolation method matches Excel's PERCENTILE.INC and PineScript's `ta.percentile_linear_interpolation` conventions (Hyndman-Fan Method 7). diff --git a/lib/statistics/skew/Skew.md b/lib/statistics/skew/Skew.md index ae2acc1d..79e0e257 100644 --- a/lib/statistics/skew/Skew.md +++ b/lib/statistics/skew/Skew.md @@ -1,5 +1,7 @@ # SKEW: Skewness +> *In the land of the blind, the one-eyed man is king. In the land of the normal distribution, the skewed man is profitable.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "In the land of the blind, the one-eyed man is king. In the land of the normal distribution, the skewed man is profitable." - Skewness measures the asymmetry of the probability distribution of a real-valued random variable about its mean. It tells you where the "tail" of the distribution is. ## Historical Context diff --git a/lib/statistics/spearman/Spearman.md b/lib/statistics/spearman/Spearman.md index cd0904ef..ebe54958 100644 --- a/lib/statistics/spearman/Spearman.md +++ b/lib/statistics/spearman/Spearman.md @@ -1,5 +1,7 @@ # SPEARMAN: Spearman Rank Correlation Coefficient +> *The person who asks whether rank correlation exists is not asking a wholly foolish question.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The person who asks whether rank correlation exists is not asking a wholly foolish question." — Maurice Kendall (1970) - Spearman's ρ (rho) measures the strength and direction of monotonic association between two variables. Unlike Pearson's correlation, which measures linear relationship, Spearman captures any monotonic relationship. A portfolio of stocks whose returns move monotonically together has different risk than one whose components merely share a linear trend. Spearman detects both. ## Historical Context diff --git a/lib/statistics/stddev/StdDev.md b/lib/statistics/stddev/StdDev.md index 5a5993de..2d5bdaaf 100644 --- a/lib/statistics/stddev/StdDev.md +++ b/lib/statistics/stddev/StdDev.md @@ -1,5 +1,7 @@ # STDDEV: Standard Deviation +> *Volatility is not risk, but it's the only thing we can measure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility is not risk, but it's the only thing we can measure." - Standard Deviation measures the amount of variation or dispersion of a set of values. A low standard deviation indicates that the values tend to be close to the mean (also called the expected value) of the set, while a high standard deviation indicates that the values are spread out over a wider range. ## Historical Context diff --git a/lib/statistics/stderr/Stderr.md b/lib/statistics/stderr/Stderr.md index 349c3050..9f6ac285 100644 --- a/lib/statistics/stderr/Stderr.md +++ b/lib/statistics/stderr/Stderr.md @@ -1,5 +1,7 @@ # Stderr: Standard Error of Regression +> *How confident are you in your line of best fit?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first stable output (`IsHot = true`). - Validated against an internal brute-force OLS reference implementation. -> "How confident are you in your line of best fit?" - Standard Error of Regression (also called the Standard Error of the Estimate) measures the average distance that the observed values fall from the regression line. It quantifies the typical size of the residuals, providing a direct measure of how well a linear regression model fits the data. ## Historical Context diff --git a/lib/statistics/sum/Sum.md b/lib/statistics/sum/Sum.md index a8a3face..257e1a2f 100644 --- a/lib/statistics/sum/Sum.md +++ b/lib/statistics/sum/Sum.md @@ -1,5 +1,7 @@ # Sum: Summation with Kahan-Babuška Algorithm +> *The naive approach to summation assumes all digits matter equally. They don't. When you add 1e-10 to 1e10, that small value vanishes into the rounding noise. Kahan-Babuška tracks what got lost and adds it back later. It's bookkeeping for bits that would otherwise slip through the cracks.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The naive approach to summation assumes all digits matter equally. They don't. When you add 1e-10 to 1e10, that small value vanishes into the rounding noise. Kahan-Babuška tracks what got lost and adds it back later. It's bookkeeping for bits that would otherwise slip through the cracks." - The Sum indicator calculates a rolling window summation using the Kahan-Babuška algorithm (also known as "improved Kahan" or "second-order compensated summation") for maximum numerical precision. This approach captures rounding errors that even classic Kahan summation misses, making it suitable for numerical libraries, statistics, and trading applications where precision matters. ## Historical Context diff --git a/lib/statistics/theil/Theil.md b/lib/statistics/theil/Theil.md index b0820d9e..eb369f55 100644 --- a/lib/statistics/theil/Theil.md +++ b/lib/statistics/theil/Theil.md @@ -1,5 +1,7 @@ # THEIL: Theil's T Index +> *The only useful measure of inequality is one that tells you how much redistribution would make everyone equally well off.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The only useful measure of inequality is one that tells you how much redistribution would make everyone equally well off." — Henri Theil - ## Introduction The Theil T Index is an information-theoretic measure of inequality (or concentration) within a distribution of positive values. Originally developed for income inequality analysis, it quantifies how far a set of values deviates from perfect equality. In financial contexts, it measures the concentration of returns or price magnitudes within a sliding window, producing values ranging from 0 (perfect equality, all values identical) upward with no fixed upper bound. The Theil T Index belongs to the family of generalized entropy indices and is notable for its decomposability property: total inequality can be additively decomposed into between-group and within-group components. diff --git a/lib/statistics/trim/Trim.md b/lib/statistics/trim/Trim.md index cfc6a3c3..f4fcbb2a 100644 --- a/lib/statistics/trim/Trim.md +++ b/lib/statistics/trim/Trim.md @@ -1,5 +1,7 @@ # TRIM: Trimmed Mean Moving Average +> *Trimmed mean drops the extreme tails before averaging — robust estimation that refuses to let outliers hijack the center.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | diff --git a/lib/statistics/variance/Variance.md b/lib/statistics/variance/Variance.md index 212a969a..dd030729 100644 --- a/lib/statistics/variance/Variance.md +++ b/lib/statistics/variance/Variance.md @@ -1,5 +1,7 @@ # Variance (VAR) +> *Volatility is the price of admission for high returns.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility is the price of admission for high returns." - Variance measures how far a set of numbers is spread out from their average value. In finance, it is a key measure of volatility and risk. ## Historical Context diff --git a/lib/statistics/wavg/Wavg.md b/lib/statistics/wavg/Wavg.md index e6db6469..b04c3133 100644 --- a/lib/statistics/wavg/Wavg.md +++ b/lib/statistics/wavg/Wavg.md @@ -1,5 +1,7 @@ # WAVG: Weighted Average +> *Weighted average assigns importance by position, giving recent or central observations a louder voice in the mean.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | diff --git a/lib/statistics/wins/Wins.md b/lib/statistics/wins/Wins.md index 04726316..3ecec826 100644 --- a/lib/statistics/wins/Wins.md +++ b/lib/statistics/wins/Wins.md @@ -1,5 +1,7 @@ # WINS: Winsorized Mean Moving Average +> *Winsorization clamps outliers to the nearest percentile fence, preserving sample size while taming extreme values.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | diff --git a/lib/statistics/zscore/Zscore.md b/lib/statistics/zscore/Zscore.md index e2e1ea7d..e32ca4f7 100644 --- a/lib/statistics/zscore/Zscore.md +++ b/lib/statistics/zscore/Zscore.md @@ -1,5 +1,7 @@ # ZSCORE: Z-Score (Population Standard Score, also known as STANDARDIZE) +> *How far from normal is this?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against manual computation, PineScript parity, and statistical invariants. -> "How far from normal is this?" — Every risk manager, every day. - ## Introduction The Z-Score measures how many population standard deviations a value lies from the rolling mean over a lookback window. ZSCORE is the canonical implementation for z-score standardization in QuanTAlib (the former Standardize indicator, which used sample standard deviation with N-1, has been consolidated into this indicator). ZSCORE uses population standard deviation, matching the PineScript `ta.zscore` convention. Output is unbounded, typically ranging from -3 to +3 for normally distributed data. A z-score of 0 means the value equals the window mean; ±2 flags statistical outliers at the 95% level. diff --git a/lib/statistics/ztest/Ztest.md b/lib/statistics/ztest/Ztest.md index c6c76679..4823db55 100644 --- a/lib/statistics/ztest/Ztest.md +++ b/lib/statistics/ztest/Ztest.md @@ -1,5 +1,7 @@ # ZTEST: One-Sample t-Test Statistic +> *The purpose of hypothesis testing is not to prove what we believe, but to measure what we observe.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against manual computation, PineScript parity, and testable statistical properties. -> "The purpose of hypothesis testing is not to prove what we believe, but to measure what we observe." — Adapted from R.A. Fisher - ## Introduction ZTEST computes the **one-sample t-statistic**, measuring how many standard errors the rolling sample mean deviates from a hypothesized population mean $\mu_0$. Despite the PineScript naming convention ("ZTEST"), this indicator computes a proper t-statistic using Bessel-corrected sample standard deviation with $N-1$ degrees of freedom. Values beyond $\pm 2.04$ (for $n=30$) indicate the sample mean differs from $\mu_0$ at the 95% confidence level; values beyond $\pm 2.75$ indicate 99% significance. diff --git a/lib/trends_FIR/_index.md b/lib/trends_FIR/_index.md index 21e894b4..45cc5e42 100644 --- a/lib/trends_FIR/_index.md +++ b/lib/trends_FIR/_index.md @@ -1,7 +1,5 @@ # Trends (FIR) -> "FIR filters are always stable. The question is how many coefficients you need." Digital Signal Processing folklore - Finite Impulse Response (FIR) trend indicators. These use fixed-length windows with explicit coefficients. No feedback loops, no recursion. Output depends only on current and past inputs. Always stable. Linear phase possible. SIMD-friendly batch computation. ## Indicators diff --git a/lib/trends_FIR/alma/Alma.md b/lib/trends_FIR/alma/Alma.md index 299328b6..2f6fdb12 100644 --- a/lib/trends_FIR/alma/Alma.md +++ b/lib/trends_FIR/alma/Alma.md @@ -1,5 +1,7 @@ # ALMA: Arnaud Legoux Moving Average +> *Gaussian distributions govern everything from particle diffusion to the distribution of shoe sizes. Applying them to price action isn't 'technical analysis'; it's just physics with a profit motive.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Gaussian distributions govern everything from particle diffusion to the distribution of shoe sizes. Applying them to price action isn't 'technical analysis'; it's just physics with a profit motive." - ALMA is a Finite Impulse Response (FIR) filter that applies a Gaussian window to price data. Unlike the Simple Moving Average (which treats 10-minute-old data with the same reverence as 1-minute-old data) or the Exponential Moving Average (which holds onto history like a hoarder), ALMA allows you to shape the weight distribution precisely. It lets you define the trade-off between smoothness and lag using standard deviation ($\sigma$) and offset, rather than arbitrary periods. ## Historical Context / The Standard diff --git a/lib/trends_FIR/blma/Blma.md b/lib/trends_FIR/blma/Blma.md index f14b0f0c..3b4be815 100644 --- a/lib/trends_FIR/blma/Blma.md +++ b/lib/trends_FIR/blma/Blma.md @@ -1,5 +1,7 @@ # BLMA: Blackman Window Moving Average +> *If you want to filter noise, don't just average it - window it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If you want to filter noise, don't just average it - window it." - The Blackman Window Moving Average (BLMA) applies a triple-cosine window function from digital signal processing to financial time series. Originally developed by **Ralph Beebe Blackman** at Bell Labs in the 1950s for spectral analysis, this filter provides superior noise suppression compared to standard moving averages by minimizing spectral leakage. ## Historical Context diff --git a/lib/trends_FIR/bwma/Bwma.md b/lib/trends_FIR/bwma/Bwma.md index e09041f8..252b720d 100644 --- a/lib/trends_FIR/bwma/Bwma.md +++ b/lib/trends_FIR/bwma/Bwma.md @@ -1,5 +1,7 @@ # BWMA: Bessel-Weighted Moving Average +> *The Bessel function appears in problems involving cylindrical symmetry—heat flow in pipes, vibration of drumheads, and apparently, the smoothing of financial time series. Mathematics doesn't care about your asset class.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Bessel function appears in problems involving cylindrical symmetry—heat flow in pipes, vibration of drumheads, and apparently, the smoothing of financial time series. Mathematics doesn't care about your asset class." - BWMA is a Finite Impulse Response (FIR) filter that applies a Bessel-derived window function to weight price data. The weighting follows a parabolic (or higher-order polynomial) profile that emphasizes the center of the lookback window while smoothly tapering to zero at the edges. Unlike rectangular (SMA) or exponential (EMA) weighting, BWMA provides a mathematically smooth transition that reduces spectral leakage and Gibbs phenomenon artifacts. ## Historical Context diff --git a/lib/trends_FIR/conv/Conv.md b/lib/trends_FIR/conv/Conv.md index c9b75d37..9bd46751 100644 --- a/lib/trends_FIR/conv/Conv.md +++ b/lib/trends_FIR/conv/Conv.md @@ -1,5 +1,7 @@ # CONV: Convolution Moving Average +> *If you want a moving average that behaves exactly how you want it to, build it yourself. CONV is the 'Bring Your Own Kernel' of indicators.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If you want a moving average that behaves exactly how you want it to, build it yourself. CONV is the 'Bring Your Own Kernel' of indicators." - CONV (Convolution Moving Average) is the ultimate tool for the signal processing purist. It doesn't presume to know what kind of smoothing you need; it simply asks for a kernel (a set of weights) and applies it to the data. Want a Gaussian filter? A Sinc filter? A custom edge-detection filter? CONV runs them all. ## Historical Context diff --git a/lib/trends_FIR/crma/Crma.md b/lib/trends_FIR/crma/Crma.md index 33fed673..79b381b6 100644 --- a/lib/trends_FIR/crma/Crma.md +++ b/lib/trends_FIR/crma/Crma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/dwma/Dwma.md b/lib/trends_FIR/dwma/Dwma.md index 339dc950..0e21e442 100644 --- a/lib/trends_FIR/dwma/Dwma.md +++ b/lib/trends_FIR/dwma/Dwma.md @@ -1,5 +1,7 @@ # DWMA: Double Weighted Moving Average +> *If one WMA is good, two must be better. DWMA is for when you want your signal so smooth it looks like it's been sanded, polished, and waxed.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `(period * 2) - 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If one WMA is good, two must be better. DWMA is for when you want your signal so smooth it looks like it's been sanded, polished, and waxed." - DWMA (Double Weighted Moving Average) is exactly what it says on the tin: a Weighted Moving Average of a Weighted Moving Average. Unlike DEMA, which tries to *remove* lag, DWMA accepts lag as the price of admission for superior noise reduction. It produces a curve that is incredibly smooth, ideal for identifying long-term trends without getting faked out by market chop. ## Historical Context diff --git a/lib/trends_FIR/fwma/Fwma.md b/lib/trends_FIR/fwma/Fwma.md index d2c02952..ffc1b758 100644 --- a/lib/trends_FIR/fwma/Fwma.md +++ b/lib/trends_FIR/fwma/Fwma.md @@ -1,5 +1,7 @@ # FWMA: Fibonacci Weighted Moving Average +> *Nature uses Fibonacci for sunflower seeds and nautilus shells. Using it for price weighting is either profound biological insight or the most expensive numerology in finance. The math doesn't care which.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Nature uses Fibonacci for sunflower seeds and nautilus shells. Using it for price weighting is either profound biological insight or the most expensive numerology in finance. The math doesn't care which." - The Fibonacci Weighted Moving Average applies the Fibonacci sequence as FIR filter weights, assigning exponentially growing importance to recent bars. Where WMA uses linear weights (1, 2, 3, ..., N) and PWMA uses parabolic weights ($1^2, 2^2, ..., N^2$), FWMA uses F(1), F(2), ..., F(N). The Fibonacci growth rate ($\phi \approx 1.618$) produces a weighting profile between exponential and parabolic, giving FWMA a distinctive "golden ratio decay" that concentrates roughly 61.8% of total weight in the most recent third of the window. ## Historical Context diff --git a/lib/trends_FIR/gwma/Gwma.md b/lib/trends_FIR/gwma/Gwma.md index 90afc022..8a32a70a 100644 --- a/lib/trends_FIR/gwma/Gwma.md +++ b/lib/trends_FIR/gwma/Gwma.md @@ -1,5 +1,7 @@ # GWMA: Gaussian-Weighted Moving Average +> *The Gaussian distribution shows up everywhere from thermal noise to the central limit theorem. Using it to weight price data isn't magic; it's just applied statistics with a trading account.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Gaussian distribution shows up everywhere from thermal noise to the central limit theorem. Using it to weight price data isn't magic; it's just applied statistics with a trading account." - GWMA is a Finite Impulse Response (FIR) filter that applies a centered Gaussian window to price data. Unlike ALMA (which allows shifting the Gaussian peak via an offset parameter), GWMA centers the bell curve at the middle of the lookback window. The sigma parameter controls the width of the Gaussian, determining how sharply the weights decay from the center. ## Historical Context diff --git a/lib/trends_FIR/hamma/Hamma.md b/lib/trends_FIR/hamma/Hamma.md index a00a8c64..a87136d6 100644 --- a/lib/trends_FIR/hamma/Hamma.md +++ b/lib/trends_FIR/hamma/Hamma.md @@ -1,5 +1,7 @@ # HAMMA: Hamming-Weighted Moving Average +> *Julius von Hann picked his window function to suppress spectral leakage; we're just using it to smooth price data. Same math, different trading floor.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Julius von Hann picked his window function to suppress spectral leakage; we're just using it to smooth price data. Same math, different trading floor." - HAMMA is a Finite Impulse Response (FIR) filter that applies a Hamming window to price data. The Hamming window is a raised cosine with specific coefficients (0.54 and 0.46) chosen to minimize the amplitude of the first side lobe in the frequency domain. This makes it particularly effective at separating the signal (trend) from nearby noise frequencies. ## Historical Context diff --git a/lib/trends_FIR/hanma/Hanma.md b/lib/trends_FIR/hanma/Hanma.md index 9bad1d3a..fd647237 100644 --- a/lib/trends_FIR/hanma/Hanma.md +++ b/lib/trends_FIR/hanma/Hanma.md @@ -1,5 +1,7 @@ # HANMA: Hanning-Weighted Moving Average +> *Julius von Hann deserves credit for the window that bears his name—even if autocomplete keeps trying to change it to 'Hamming.' The zero-edge weights aren't a bug; they're the whole point.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Julius von Hann deserves credit for the window that bears his name—even if autocomplete keeps trying to change it to 'Hamming.' The zero-edge weights aren't a bug; they're the whole point." - HANMA is a Finite Impulse Response (FIR) filter that applies a Hanning (Hann) window to price data. The Hanning window is a pure raised cosine with edge weights of exactly zero, which provides excellent side lobe suppression while maintaining a narrower main lobe than Hamming. It's particularly effective when you want to eliminate boundary discontinuities entirely. ## Historical Context diff --git a/lib/trends_FIR/hend/Hend.md b/lib/trends_FIR/hend/Hend.md index e331784f..acfc41b9 100644 --- a/lib/trends_FIR/hend/Hend.md +++ b/lib/trends_FIR/hend/Hend.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/hma/Hma.md b/lib/trends_FIR/hma/Hma.md index f7fa6783..5518ee34 100644 --- a/lib/trends_FIR/hma/Hma.md +++ b/lib/trends_FIR/hma/Hma.md @@ -1,5 +1,7 @@ # HMA: Hull Moving Average +> *Alan Hull looked at the lag in moving averages and said, 'I can fix that.' And he did, by making the math do gymnastics.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period + sqrtPeriod - 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Alan Hull looked at the lag in moving averages and said, 'I can fix that.' And he did, by making the math do gymnastics." - HMA (Hull Moving Average) is a solution to the eternal struggle between smoothness and lag. Most indicators force you to choose one; HMA gives you both. It achieves this by using weighted moving averages (WMAs) in a clever configuration that cancels out lag while maintaining the smoothing properties of the WMA. ## Historical Context diff --git a/lib/trends_FIR/ilrs/Ilrs.md b/lib/trends_FIR/ilrs/Ilrs.md index 46145058..a5db28a1 100644 --- a/lib/trends_FIR/ilrs/Ilrs.md +++ b/lib/trends_FIR/ilrs/Ilrs.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/kaiser/Kaiser.md b/lib/trends_FIR/kaiser/Kaiser.md index ecc5b504..1a393ec5 100644 --- a/lib/trends_FIR/kaiser/Kaiser.md +++ b/lib/trends_FIR/kaiser/Kaiser.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/lanczos/Lanczos.md b/lib/trends_FIR/lanczos/Lanczos.md index 673cbe72..51e63eed 100644 --- a/lib/trends_FIR/lanczos/Lanczos.md +++ b/lib/trends_FIR/lanczos/Lanczos.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/lsma/Lsma.md b/lib/trends_FIR/lsma/Lsma.md index 24584f1e..bb0fa531 100644 --- a/lib/trends_FIR/lsma/Lsma.md +++ b/lib/trends_FIR/lsma/Lsma.md @@ -1,5 +1,7 @@ # LSMA: Least Squares Moving Average +> *If you want to know where the price is going, draw a line through where it's been. LSMA does this for every single bar, tirelessly fitting linear regressions while you sleep.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If you want to know where the price is going, draw a line through where it's been. LSMA does this for every single bar, tirelessly fitting linear regressions while you sleep." - LSMA (Least Squares Moving Average), also known as the Moving Linear Regression or Endpoint Moving Average, calculates the least squares regression line for the preceding time periods. In plain English: it finds the "best fit" line for the data window and tells you where that line ends. ## Historical Context diff --git a/lib/trends_FIR/nlma/Nlma.md b/lib/trends_FIR/nlma/Nlma.md index ff7ccbaf..fafd3fe4 100644 --- a/lib/trends_FIR/nlma/Nlma.md +++ b/lib/trends_FIR/nlma/Nlma.md @@ -1,5 +1,7 @@ # NLMA: Non-Lag Moving Average +> *Igorad at TrendLaboratory built a two-phase FIR kernel that uses five times more taps than the period parameter suggests. The extra taps carry negative weights that actively cancel group delay. Most 'non-lag' indicators are marketing. This one is signal processing.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Igorad at TrendLaboratory built a two-phase FIR kernel that uses five times more taps than the period parameter suggests. The extra taps carry negative weights that actively cancel group delay. Most 'non-lag' indicators are marketing. This one is signal processing." - NLMA uses a two-phase damped cosine kernel with $5P - 1$ taps (where $P$ is the user period). Phase 1 builds the initial sweep; Phase 2 extends it through multiple cosine cycles. The kernel's negative weights in the mid-section subtract lagged price components, reducing group delay well below what a positive-only SMA of the same length achieves. Normalization by the signed weight sum preserves DC gain of 1.0. The result is a trend-following filter with moderate overshoot but substantially less lag than conventional moving averages. ## Historical Context @@ -134,46 +134,6 @@ The signed-sum normalization guarantees unit DC gain regardless of the weight di | Phase | $P - 1$ | derived | Boundary between Phase 1 and Phase 2 | | Coeff | $3\pi$ | fixed | Gain decay rate in Phase 2 | -### Pseudo-code (streaming) - -```text -// Constants -Cycle = 4 -Phase = period - 1 -Coeff = 3 * PI -flen = 5 * period - 1 - -// Precompute weights once -wsum = 0 -for i = 0 to flen-1: - if i <= Phase - 1: - t = i / (Phase - 1) - else: - t = 1.0 + (i - Phase + 1) * (2*Cycle - 1) / (Cycle * period - 1) - - if t <= 0.5: - g = 1.0 - else: - g = 1.0 / (Coeff * t + 1) - - w[i] = g * cos(PI * t) - wsum += w[i] - -// Per bar: insert into circular buffer of size flen -buffer[head] = price -head = (head + 1) % flen - -// Warmup: return price when count < flen -if count < flen: return price - -// Full convolution -sum = 0 -for k = 0 to flen-1: - sum += buffer[(head+k) % flen] * w[flen-1-k] - -return sum / wsum -``` - ## Performance Profile ### Operation Count (Streaming Mode) diff --git a/lib/trends_FIR/nyqma/Nyqma.md b/lib/trends_FIR/nyqma/Nyqma.md index bd6f7795..0280a63e 100644 --- a/lib/trends_FIR/nyqma/Nyqma.md +++ b/lib/trends_FIR/nyqma/Nyqma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 into the output. This produces a lag-reduced moving average grounded in sampling theory rather than ad-hoc coefficient tuning. Streaming update is O(1) per bar via composed Wma instances; batch mode uses stackalloc/ArrayPool with FMA in the extrapolation loop. ## Historical Context diff --git a/lib/trends_FIR/parzen/Parzen.md b/lib/trends_FIR/parzen/Parzen.md index fcf0ba94..d0ac4d8d 100644 --- a/lib/trends_FIR/parzen/Parzen.md +++ b/lib/trends_FIR/parzen/Parzen.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/pma/Pma.md b/lib/trends_FIR/pma/Pma.md index 7aaf4a8b..172ed402 100644 --- a/lib/trends_FIR/pma/Pma.md +++ b/lib/trends_FIR/pma/Pma.md @@ -1,5 +1,7 @@ # PMA: Predictive Moving Average +> *John Ehlers looked at WMA's lag and said: 'What if we just extrapolated it away?' The result is a moving average that actually tries to predict where price is going, not where it has been.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `(period * 2) - 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "John Ehlers looked at WMA's lag and said: 'What if we just extrapolated it away?' The result is a moving average that actually tries to predict where price is going, not where it has been." - PMA (Predictive Moving Average) is a lag-cancellation filter that uses linear extrapolation of dual WMA (Weighted Moving Average) cascades to predict price direction. It produces two outputs: the PMA line (extrapolated trend) and a Trigger line for crossover signals. Default period is 7 per Ehlers' original specification. ## Historical Context diff --git a/lib/trends_FIR/pwma/Pwma.md b/lib/trends_FIR/pwma/Pwma.md index f6b17e01..75ab069c 100644 --- a/lib/trends_FIR/pwma/Pwma.md +++ b/lib/trends_FIR/pwma/Pwma.md @@ -1,5 +1,7 @@ # PWMA: Parabolic Weighted Moving Average +> *Linear weighting is for people who think the world is flat. PWMA squares the weights, because recent data isn't just more important—it's exponentially more important.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Linear weighting is for people who think the world is flat. PWMA squares the weights, because recent data isn't just more important—it's exponentially more important." - PWMA (Parabolic Weighted Moving Average) applies a parabolic ($i^2$) weighting scheme to the data window. This assigns massive importance to the most recent data points while still technically including the older data. It's like a WMA on steroids. ## Historical Context diff --git a/lib/trends_FIR/qrma/Qrma.md b/lib/trends_FIR/qrma/Qrma.md index 175f2ef2..0f1cb9f4 100644 --- a/lib/trends_FIR/qrma/Qrma.md +++ b/lib/trends_FIR/qrma/Qrma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/rain/Rain.md b/lib/trends_FIR/rain/Rain.md index c8850938..8b08eef7 100644 --- a/lib/trends_FIR/rain/Rain.md +++ b/lib/trends_FIR/rain/Rain.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/rwma/Rwma.md b/lib/trends_FIR/rwma/Rwma.md index 3179b704..f8379c1c 100644 --- a/lib/trends_FIR/rwma/Rwma.md +++ b/lib/trends_FIR/rwma/Rwma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/sgma/Sgma.md b/lib/trends_FIR/sgma/Sgma.md index f9a67aeb..4a86b7cb 100644 --- a/lib/trends_FIR/sgma/Sgma.md +++ b/lib/trends_FIR/sgma/Sgma.md @@ -1,5 +1,7 @@ # SGMA: Savitzky-Golay Moving Average +> *Least-squares polynomial fitting has been solving signal processing problems since 1964. That most traders still use medieval averaging techniques says more about the industry than the math.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Least-squares polynomial fitting has been solving signal processing problems since 1964. That most traders still use medieval averaging techniques says more about the industry than the math." - SGMA is a Finite Impulse Response (FIR) filter that uses polynomial fitting to smooth data while preserving higher moments (peaks, valleys, and inflection points). Unlike the Simple Moving Average (which flattens everything) or the Exponential Moving Average (which introduces phase lag), SGMA uses polynomial weighting to maintain the original signal's shape characteristics. ## Historical Context / The Standard diff --git a/lib/trends_FIR/sinema/Sinema.md b/lib/trends_FIR/sinema/Sinema.md index 7137feb9..8dd54e2e 100644 --- a/lib/trends_FIR/sinema/Sinema.md +++ b/lib/trends_FIR/sinema/Sinema.md @@ -1,5 +1,7 @@ # SINEMA: Sine-Weighted Moving Average +> *Nature doesn't do straight lines, and neither should your weights.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Nature doesn't do straight lines, and neither should your weights." - The Sine-Weighted Moving Average (SINEMA) applies sine-wave weighting to data points within the lookback window. Weights follow the formula $w_i = \sin(\pi \cdot (i+1) / N)$, creating a smooth bell-shaped distribution that emphasizes middle values while gracefully tapering at the edges. Unlike SMA's uniform weighting or WMA's linear ramp, sine weighting provides a natural transition that reduces high-frequency noise while preserving mid-frequency trends. ## Historical Context diff --git a/lib/trends_FIR/sma/Sma.md b/lib/trends_FIR/sma/Sma.md index 2776afcc..b507b2b7 100644 --- a/lib/trends_FIR/sma/Sma.md +++ b/lib/trends_FIR/sma/Sma.md @@ -1,5 +1,7 @@ # SMA: Simple Moving Average +> *The vanilla ice cream of technical analysis. Boring, ubiquitous, and the only thing your grandfather and your high-frequency trading bot agree on.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The vanilla ice cream of technical analysis. Boring, ubiquitous, and the only thing your grandfather and your high-frequency trading bot agree on." - The Simple Moving Average (SMA) is the unweighted arithmetic mean of the last $N$ data points. It acts as a low-pass filter, smoothing out high-frequency noise to reveal the underlying trend. While conceptually simple, efficient implementation on modern hardware requires careful attention to memory access patterns and vectorization. ## Historical Context diff --git a/lib/trends_FIR/sp15/Sp15.md b/lib/trends_FIR/sp15/Sp15.md index c0a3067b..1efc86e4 100644 --- a/lib/trends_FIR/sp15/Sp15.md +++ b/lib/trends_FIR/sp15/Sp15.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `Period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/swma/Swma.md b/lib/trends_FIR/swma/Swma.md index 12bc482b..b8592238 100644 --- a/lib/trends_FIR/swma/Swma.md +++ b/lib/trends_FIR/swma/Swma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/trima/Trima.md b/lib/trends_FIR/trima/Trima.md index bacc1731..af3c6a1f 100644 --- a/lib/trends_FIR/trima/Trima.md +++ b/lib/trends_FIR/trima/Trima.md @@ -1,5 +1,7 @@ # TRIMA: Triangular Moving Average +> *The weighted blanket of moving averages. It doesn't care where the price is going right now; it cares where the price feels most comfortable.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `p1 + p2 - 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The weighted blanket of moving averages. It doesn't care where the price is going right now; it cares where the price feels most comfortable." - The Triangular Moving Average (TRIMA) places the majority of its weight on the middle of the data window, tapering off linearly towards the ends. This creates a triangular weight distribution (hence the name). It is mathematically equivalent to a double-smoothed SMA. ## Historical Context diff --git a/lib/trends_FIR/tsf/Tsf.md b/lib/trends_FIR/tsf/Tsf.md index 7dfbf85c..c9d32b31 100644 --- a/lib/trends_FIR/tsf/Tsf.md +++ b/lib/trends_FIR/tsf/Tsf.md @@ -1,5 +1,7 @@ # TSF: Time Series Forecast +> *The best prediction of the future is the trend that's already in motion — extended by exactly one step.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best prediction of the future is the trend that's already in motion — extended by exactly one step." - TSF projects the least-squares regression line one bar forward, providing a statistically grounded forecast of the next bar's value. Unlike simple moving averages that smooth past data, TSF answers the question: "If the current trend continues, where will price be next?" This makes it inherently leading rather than lagging, though the forecast degrades quickly beyond one step. ## Historical Context diff --git a/lib/trends_FIR/tukey_w/Tukey_w.md b/lib/trends_FIR/tukey_w/Tukey_w.md index 3df13e29..6e7e8a08 100644 --- a/lib/trends_FIR/tukey_w/Tukey_w.md +++ b/lib/trends_FIR/tukey_w/Tukey_w.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_FIR/wma/Wma.md b/lib/trends_FIR/wma/Wma.md index 44086c0e..b6f5652e 100644 --- a/lib/trends_FIR/wma/Wma.md +++ b/lib/trends_FIR/wma/Wma.md @@ -1,5 +1,7 @@ # WMA: Weighted Moving Average +> *Because yesterday matters more than last Tuesday. WMA is the linear answer to the question: 'What have you done for me lately?'* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (FIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Because yesterday matters more than last Tuesday. WMA is the linear answer to the question: 'What have you done for me lately?'" - The Weighted Moving Average (WMA) assigns a linearly decreasing weight to data points. The most recent price gets weight $N$, the one before it $N-1$, down to 1. This makes it more responsive to recent price changes than an SMA, but without the infinite tail of an EMA. ## Historical Context diff --git a/lib/trends_IIR/_index.md b/lib/trends_IIR/_index.md index a85fedab..7f507a89 100644 --- a/lib/trends_IIR/_index.md +++ b/lib/trends_IIR/_index.md @@ -1,7 +1,5 @@ # Trends (IIR) -> "Recursion trades memory for computation. Single coefficient replaces entire window. But feedback loop carries risk: instability lurks in coefficient choices that FIR designers never face." - Trend indicators based on Infinite Impulse Response (IIR) filters. Recursive architecture uses previous outputs to compute current values, enabling lower lag with fewer coefficients than equivalent FIR filters. | Indicator | Full Name | Description | diff --git a/lib/trends_IIR/adxvma/Adxvma.md b/lib/trends_IIR/adxvma/Adxvma.md index a9c64751..a899ac08 100644 --- a/lib/trends_IIR/adxvma/Adxvma.md +++ b/lib/trends_IIR/adxvma/Adxvma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -16,8 +18,6 @@ - Requires `period * 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/ahrens/Ahrens.md b/lib/trends_IIR/ahrens/Ahrens.md index e8082d7d..85476201 100644 --- a/lib/trends_IIR/ahrens/Ahrens.md +++ b/lib/trends_IIR/ahrens/Ahrens.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/coral/Coral.md b/lib/trends_IIR/coral/Coral.md index d6eeb59a..94e26b24 100644 --- a/lib/trends_IIR/coral/Coral.md +++ b/lib/trends_IIR/coral/Coral.md @@ -1,5 +1,7 @@ # CORAL — Coral Trend Filter +> *Coral blends multiple EMA stages with tunable smoothing, producing a trend line that bends without breaking.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | diff --git a/lib/trends_IIR/decycler/Decycler.md b/lib/trends_IIR/decycler/Decycler.md index 2740de24..cc800b92 100644 --- a/lib/trends_IIR/decycler/Decycler.md +++ b/lib/trends_IIR/decycler/Decycler.md @@ -1,5 +1,7 @@ # DECYCLER: Ehlers Decycler +> *The trend is what remains when you stop looking for cycles.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The trend is what remains when you stop looking for cycles." - The Ehlers Decycler extracts the trend component from a price series by subtracting a 2-pole Butterworth high-pass filter from the source signal. Where most moving averages blur the boundary between trend and cycle, the Decycler defines it with a frequency-domain cutoff: cycles shorter than the specified period are removed, everything longer stays. The result is an overlay that hugs price with near-zero lag during trends and rejects short-term oscillations without the smoothing artifacts of convolution-based averages. ## Historical Context diff --git a/lib/trends_IIR/dema/Dema.md b/lib/trends_IIR/dema/Dema.md index c9238421..7874fe36 100644 --- a/lib/trends_IIR/dema/Dema.md +++ b/lib/trends_IIR/dema/Dema.md @@ -1,6 +1,6 @@ # DEMA: Double Exponential Moving Average -> "EMA is good. DEMA is better. It's like an EMA that drank a double espresso and stopped lagging behind the conversation." +> *EMA is good. DEMA is better. It's like an EMA that drank a double espresso and stopped lagging behind the conversation.* ## Quick Reference diff --git a/lib/trends_IIR/dsma/Dsma.md b/lib/trends_IIR/dsma/Dsma.md index be909f3b..50d8ab12 100644 --- a/lib/trends_IIR/dsma/Dsma.md +++ b/lib/trends_IIR/dsma/Dsma.md @@ -1,5 +1,7 @@ # DSMA: Deviation-Scaled Moving Average +> *When the market screams, DSMA sprints. When it whispers, DSMA crawls. An adaptive moving average that lets volatility dictate the pace.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When the market screams, DSMA sprints. When it whispers, DSMA crawls. An adaptive moving average that lets volatility dictate the pace." - DSMA (Deviation-Scaled Moving Average) is a volatility-adaptive trend filter that combines a Super Smoother (2-pole Butterworth IIR filter) with RMS-based deviation scaling. Unlike fixed-period moving averages that treat all market conditions identically, DSMA adjusts its responsiveness based on measured volatility—accelerating when trends are strong and decelerating when prices consolidate. ## Historical Context diff --git a/lib/trends_IIR/ema/Ema.md b/lib/trends_IIR/ema/Ema.md index 6e9cad4a..e0df3a9e 100644 --- a/lib/trends_IIR/ema/Ema.md +++ b/lib/trends_IIR/ema/Ema.md @@ -1,6 +1,6 @@ # EMA: Exponential Moving Average -> "The SMA drops an old price, the average jumps, the signal fires, the market does something unhelpful. The EMA exists because someone finally asked: what if old data just... mattered less?" +> *The SMA drops an old price, the average jumps, the signal fires, the market does something unhelpful. The EMA exists because someone finally asked: what if old data just... mattered less?* ## Quick Reference diff --git a/lib/trends_IIR/frama/Frama.md b/lib/trends_IIR/frama/Frama.md index 49ea2761..afc311cf 100644 --- a/lib/trends_IIR/frama/Frama.md +++ b/lib/trends_IIR/frama/Frama.md @@ -1,5 +1,7 @@ # FRAMA: Ehlers Fractal Adaptive Moving Average +> *Markets do not move at one speed. FRAMA listens to the roughness and adjusts the filter.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `pe` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Markets do not move at one speed. FRAMA listens to the roughness and adjusts the filter." - FRAMA is John Ehlers' fractal adaptive moving average. It estimates a fractal dimension from high and low ranges, then converts that dimension into a dynamic EMA alpha. The result is a moving average that tightens in trends and relaxes in noise. ## Historical Context diff --git a/lib/trends_IIR/gdema/Gdema.md b/lib/trends_IIR/gdema/Gdema.md index 3f25850a..21c6b68e 100644 --- a/lib/trends_IIR/gdema/Gdema.md +++ b/lib/trends_IIR/gdema/Gdema.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/hema/Hema.md b/lib/trends_IIR/hema/Hema.md index 183296dc..b00679cb 100644 --- a/lib/trends_IIR/hema/Hema.md +++ b/lib/trends_IIR/hema/Hema.md @@ -1,5 +1,7 @@ # HEMA: Hull Exponential Moving Average +> *HMA is a topology. HEMA keeps the topology and swaps the physics: windows to decay, with identical lag.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `EstimateWarmupPeriod()` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "HMA is a topology. HEMA keeps the topology and swaps the physics: windows to decay, with identical lag." - ## An EMA-domain analog of HMA with WMA-lag-matched alphas diff --git a/lib/trends_IIR/holt/Holt.md b/lib/trends_IIR/holt/Holt.md index d76956ef..cfde0fbb 100644 --- a/lib/trends_IIR/holt/Holt.md +++ b/lib/trends_IIR/holt/Holt.md @@ -1,5 +1,7 @@ # HOLT: Holt Exponential Moving Average +> *Single smoothing tracks level. Double smoothing tracks trend. The elegance is not in complexity but in the admission that yesterday's direction matters.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Single smoothing tracks level. Double smoothing tracks trend. The elegance is not in complexity but in the admission that yesterday's direction matters." — Charles C. Holt (1957) - ## Overview Holt's exponential smoothing extends simple exponential smoothing (EMA) by adding a second equation that explicitly tracks the local trend. The result is a 1-step-ahead forecast that adapts to both the level and direction of the time series. When applied to financial data, HOLT produces a trend-following line that anticipates price continuation rather than merely reacting to it. diff --git a/lib/trends_IIR/htit/Htit.md b/lib/trends_IIR/htit/Htit.md index bbb4c181..403a3b4e 100644 --- a/lib/trends_IIR/htit/Htit.md +++ b/lib/trends_IIR/htit/Htit.md @@ -1,5 +1,7 @@ # HTIT: Ehlers Hilbert Transform Instantaneous Trend (also known as HT_TRENDLINE) +> *John Ehlers brought rocket science to trading. Literally. HTIT uses signal processing to find the trend by removing the cycle. It's not smoothing; it's extraction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -18,8 +20,6 @@ - Requires `12` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "John Ehlers brought rocket science to trading. Literally. HTIT uses signal processing to find the trend by removing the cycle. It's not smoothing; it's extraction." - HTIT (Hilbert Transform Instantaneous Trend) is a trend-following indicator that doesn't rely on simple averaging. Instead, it uses the Hilbert Transform to measure the dominant cycle period of the market and then computes a trendline that filters out that specific cycle. It adapts to the market's rhythm rather than imposing a fixed period. ## Historical Context diff --git a/lib/trends_IIR/hwma/Hwma.md b/lib/trends_IIR/hwma/Hwma.md index e77b986f..1efad223 100644 --- a/lib/trends_IIR/hwma/Hwma.md +++ b/lib/trends_IIR/hwma/Hwma.md @@ -1,5 +1,7 @@ # HWMA: Holt-Winters Moving Average +> *Triple exponential smoothing: because sometimes tracking level, velocity, and acceleration is exactly what a price series needs—and sometimes it's overkill. Holt and Winters figured this out for inventory forecasting in the 1950s. Traders rediscovered it decades later.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Triple exponential smoothing: because sometimes tracking level, velocity, and acceleration is exactly what a price series needs—and sometimes it's overkill. Holt and Winters figured this out for inventory forecasting in the 1950s. Traders rediscovered it decades later." - HWMA is an Infinite Impulse Response (IIR) filter that applies triple exponential smoothing with level (F), velocity (V), and acceleration (A) components. Unlike simple exponential smoothing which only tracks the current level, HWMA anticipates future values by extrapolating trend and trend changes. ## Historical Context diff --git a/lib/trends_IIR/jma/Jma.md b/lib/trends_IIR/jma/Jma.md index dca2a3bc..8880e000 100644 --- a/lib/trends_IIR/jma/Jma.md +++ b/lib/trends_IIR/jma/Jma.md @@ -1,5 +1,7 @@ # JMA: Jurik Moving Average +> *The spectral approach isn't marketing. It's the difference between guessing at volatility and measuring it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The spectral approach isn't marketing. It's the difference between guessing at volatility and measuring it." - JMA (Jurik Moving Average) is Mark Jurik's flagship adaptive smoother, recovered through decompilation of his proprietary AmiBroker/MetaTrader binaries. Unlike forum-sourced approximations that use exponential volatility smoothing, this implementation maintains a 128-bar volatility distribution and applies percentile trimming to derive a robust reference. The result: identical behavior to Jurik's commercial software within floating-point tolerance, including spike rejection during 3-sigma events where approximations diverge by 3-4%. ## Historical Context diff --git a/lib/trends_IIR/kama/Kama.md b/lib/trends_IIR/kama/Kama.md index 69c2dd1e..7c7f123d 100644 --- a/lib/trends_IIR/kama/Kama.md +++ b/lib/trends_IIR/kama/Kama.md @@ -1,5 +1,7 @@ # KAMA: Kaufman's Adaptive Moving Average +> *Perry Kaufman asked a simple question: 'Why should I use the same smoothing in a trending market as in a chopping market?' KAMA is the answer.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Perry Kaufman asked a simple question: 'Why should I use the same smoothing in a trending market as in a chopping market?' KAMA is the answer." - KAMA (Kaufman's Adaptive Moving Average) is an intelligent moving average that adjusts its smoothing speed based on market noise. When the price is moving steadily (high signal-to-noise ratio), KAMA speeds up to capture the trend. When the price is chopping sideways (low signal-to-noise ratio), KAMA slows down to filter out the noise. ## Historical Context diff --git a/lib/trends_IIR/lema/Lema.md b/lib/trends_IIR/lema/Lema.md index 66cfb494..9b076bc9 100644 --- a/lib/trends_IIR/lema/Lema.md +++ b/lib/trends_IIR/lema/Lema.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/ltma/Ltma.md b/lib/trends_IIR/ltma/Ltma.md index 38de2120..72d2a7df 100644 --- a/lib/trends_IIR/ltma/Ltma.md +++ b/lib/trends_IIR/ltma/Ltma.md @@ -1,6 +1,6 @@ # 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." +> *Estimate the level. Estimate the slope. Project forward. It is the same trick radar operators use to track aircraft, applied to price data.* diff --git a/lib/trends_IIR/mama/Mama.md b/lib/trends_IIR/mama/Mama.md index 5d1f5973..34b19410 100644 --- a/lib/trends_IIR/mama/Mama.md +++ b/lib/trends_IIR/mama/Mama.md @@ -1,5 +1,7 @@ # MAMA: Ehlers MESA Adaptive Moving Average +> *John Ehlers again. This time, he built a moving average that doesn't just adapt to volatility—it adapts to the phase of the market cycle. It's like having a GPS for your trend.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -18,8 +20,6 @@ - Requires `50` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "John Ehlers again. This time, he built a moving average that doesn't just adapt to volatility—it adapts to the phase of the market cycle. It's like having a GPS for your trend." - MAMA (MESA Adaptive Moving Average) is a unique adaptive moving average that uses the Hilbert Transform to determine the phase rate of change of the market cycle. It produces two outputs: MAMA (the adaptive average) and FAMA (Following Adaptive Moving Average), which acts as a slower, confirming signal. ## Historical Context diff --git a/lib/trends_IIR/mavp/Mavp.md b/lib/trends_IIR/mavp/Mavp.md index dd40c308..56552f85 100644 --- a/lib/trends_IIR/mavp/Mavp.md +++ b/lib/trends_IIR/mavp/Mavp.md @@ -1,5 +1,7 @@ # MAVP: Moving Average Variable Period +> *You can't fix your moving average period because the market doesn't run at a fixed frequency. MAVP stops pretending it does.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -16,8 +18,6 @@ - Requires `maxPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "You can't fix your moving average period because the market doesn't run at a fixed frequency. MAVP stops pretending it does." - ## Introduction MAVP applies an EMA-style exponential smoothing where the period -- and therefore the smoothing constant alpha -- changes on every bar. Each bar receives an externally supplied period value, clamped to [minPeriod, maxPeriod], producing `alpha = 2 / (period + 1)`. The result is a single-pass O(1) IIR filter with an adaptive warmup compensator that tracks the cumulative product of all per-bar `(1 - alpha)` values. With a fixed period MAVP reduces exactly to standard EMA (validated to 1e-9 tolerance against Skender and TA-Lib EMA). With a time-varying period series, it becomes a general-purpose adaptive smoother controlled entirely by external logic. diff --git a/lib/trends_IIR/mcnma/Mcnma.md b/lib/trends_IIR/mcnma/Mcnma.md index e3c7800d..200557ed 100644 --- a/lib/trends_IIR/mcnma/Mcnma.md +++ b/lib/trends_IIR/mcnma/Mcnma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/mgdi/Mgdi.md b/lib/trends_IIR/mgdi/Mgdi.md index 820871f4..a29d30f9 100644 --- a/lib/trends_IIR/mgdi/Mgdi.md +++ b/lib/trends_IIR/mgdi/Mgdi.md @@ -1,5 +1,7 @@ # MGDI: McGinley Dynamic Indicator +> *John McGinley saw moving averages failing in fast markets and said, 'It's not the market's fault, it's the math's fault.' MGDI is the apology.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "John McGinley saw moving averages failing in fast markets and said, 'It's not the market's fault, it's the math's fault.' MGDI is the apology." - MGDI (McGinley Dynamic Indicator) looks like a moving average but operates on a fundamentally different principle. Rather than using a fixed smoothing factor, it dynamically adjusts based on the ratio between price and the indicator's current value. The result is a filter that accelerates to catch breakouts while decelerating to avoid overshooting reversals—a behavior that fixed-alpha filters cannot achieve. ## Historical Context diff --git a/lib/trends_IIR/mma/Mma.md b/lib/trends_IIR/mma/Mma.md index e599fab6..eb932b0b 100644 --- a/lib/trends_IIR/mma/Mma.md +++ b/lib/trends_IIR/mma/Mma.md @@ -1,5 +1,7 @@ # MMA: Modified Moving Average +> *MMA is a compromise: less lag than SMA, less overshoot than fully weighted filters. It's what you get when an SMA and a WMA have a carefully engineered offspring.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "MMA is a compromise: less lag than SMA, less overshoot than fully weighted filters. It's what you get when an SMA and a WMA have a carefully engineered offspring." - MMA (Modified Moving Average) uses a **simple mean** as a baseline, then adds a **weighted correction** based on the position of values within the buffer. The weighting tilts toward newer bars without fully discarding older ones, creating a filter that sits between SMA (equal weights) and WMA (linear weights) in both lag and smoothness characteristics. ## Historical Context diff --git a/lib/trends_IIR/nma/Nma.md b/lib/trends_IIR/nma/Nma.md index ee5e2f2c..190d1691 100644 --- a/lib/trends_IIR/nma/Nma.md +++ b/lib/trends_IIR/nma/Nma.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/qema/Qema.md b/lib/trends_IIR/qema/Qema.md index 6e2b62bf..12f96d74 100644 --- a/lib/trends_IIR/qema/Qema.md +++ b/lib/trends_IIR/qema/Qema.md @@ -1,5 +1,7 @@ # QEMA: Quad Exponential Moving Average +> *Four EMAs walk into a bar. The first one's slow and thoughtful. The fourth one's practically twitching. Together, they somehow produce a signal that's both smooth and responsive. The bartender asks, 'How did you achieve zero lag?' They reply, 'Constrained quadratic optimization.' The bartender pours them a free drink.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Four EMAs walk into a bar. The first one's slow and thoughtful. The fourth one's practically twitching. Together, they somehow produce a signal that's both smooth and responsive. The bartender asks, 'How did you achieve zero lag?' They reply, 'Constrained quadratic optimization.' The bartender pours them a free drink." - QEMA (Quad Exponential Moving Average) is a zero-lag smoothing filter that cascades four EMAs with geometrically ramped alphas and combines them using minimum-energy weights. Unlike traditional multi-stage EMAs (DEMA, TEMA) that use fixed coefficients, QEMA solves for weights that explicitly eliminate DC lag while minimizing output variance. The result is a filter that tracks linear trends with zero group delay while suppressing high-frequency noise more effectively than standard EMA cascades. ## Historical Context diff --git a/lib/trends_IIR/rema/Rema.md b/lib/trends_IIR/rema/Rema.md index 5d3f8f57..e38bcbf1 100644 --- a/lib/trends_IIR/rema/Rema.md +++ b/lib/trends_IIR/rema/Rema.md @@ -1,5 +1,7 @@ # REMA: Regularized Exponential Moving Average +> *Someone looked at the EMA and thought: 'What if we punished it for changing its mind?' The result is REMA—an EMA with a conscience that remembers where it was going and resists the temptation to chase every price wiggle.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Someone looked at the EMA and thought: 'What if we punished it for changing its mind?' The result is REMA—an EMA with a conscience that remembers where it was going and resists the temptation to chase every price wiggle." - REMA (Regularized Exponential Moving Average) combines exponential smoothing with a regularization term that penalizes deviations from the previous trend direction. The result is a filter that responds to genuine price movements while suppressing noise-induced oscillations. Think of it as an EMA with momentum awareness: it knows where it was heading and applies a penalty for sudden course corrections. ## Historical Context diff --git a/lib/trends_IIR/rgma/Rgma.md b/lib/trends_IIR/rgma/Rgma.md index 19b8d44e..3befbf53 100644 --- a/lib/trends_IIR/rgma/Rgma.md +++ b/lib/trends_IIR/rgma/Rgma.md @@ -1,5 +1,7 @@ # RGMA: Recursive Gaussian Moving Average +> *The statisticians wanted Gaussian smoothing. The HFT folks wanted O(1) updates. RGMA splits the difference: chain enough cheap EMAs together and the impulse response starts looking suspiciously bell-shaped. It's not real Gaussian—but the market doesn't know that.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The statisticians wanted Gaussian smoothing. The HFT folks wanted O(1) updates. RGMA splits the difference: chain enough cheap EMAs together and the impulse response starts looking suspiciously bell-shaped. It's not real Gaussian—but the market doesn't know that." - RGMA (Recursive Gaussian Moving Average) approximates Gaussian smoothing by cascading multiple identical exponential moving averages. Each pass through an EMA filter smooths the signal further, and the mathematical magic is that cascaded low-pass filters push the impulse response toward a Gaussian-like shape. You get the desirable properties of Gaussian smoothing—smooth frequency roll-off, minimal ringing, symmetric lag—without the computational cost of a true FIR convolution. ## Historical Context diff --git a/lib/trends_IIR/rma/Rma.md b/lib/trends_IIR/rma/Rma.md index ce5ed612..80feef73 100644 --- a/lib/trends_IIR/rma/Rma.md +++ b/lib/trends_IIR/rma/Rma.md @@ -1,5 +1,7 @@ # RMA: Running Moving Average +> *Wilder didn't like standard EMA weighting. He wanted history to decay slower. So he invented RMA, which is just EMA with a different alpha, confusing traders for 40 years.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `ema.WarmupPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Wilder didn't like standard EMA weighting. He wanted history to decay slower. So he invented RMA, which is just EMA with a different alpha, confusing traders for 40 years." - The Running Moving Average (RMA), also known as the Smoothed Moving Average (SMMA) or Wilder's Moving Average, is the backbone of J. Welles Wilder's most famous indicators: RSI, ATR, and ADX. It is functionally identical to an Exponential Moving Average (EMA), but with a smoothing factor ($\alpha$) of $1/N$ instead of $2/(N+1)$. This results in a longer "memory" and slower decay than a standard EMA of the same period. ## Historical Context diff --git a/lib/trends_IIR/t3/T3.md b/lib/trends_IIR/t3/T3.md index e4f04a21..5d2e0a9c 100644 --- a/lib/trends_IIR/t3/T3.md +++ b/lib/trends_IIR/t3/T3.md @@ -1,5 +1,7 @@ # T3: Tillson T3 Moving Average +> *If one EMA is good, six must be better. Tim Tillson's logic is impeccable, provided you hate noise more than you love latency.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period * 6` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "If one EMA is good, six must be better. Tim Tillson's logic is impeccable, provided you hate noise more than you love latency." - The T3 Moving Average is a hyper-smooth, low-lag filter that cascades six Exponential Moving Averages (EMAs). Unlike standard cascading (which increases lag), T3 uses a "Volume Factor" ($v$) to weight the EMAs in a way that partially cancels out the lag, resulting in a curve that is smoother than an EMA but more responsive than an SMA. ## Historical Context diff --git a/lib/trends_IIR/tema/Tema.md b/lib/trends_IIR/tema/Tema.md index 061adc6b..536b28ba 100644 --- a/lib/trends_IIR/tema/Tema.md +++ b/lib/trends_IIR/tema/Tema.md @@ -1,6 +1,6 @@ # TEMA: Triple Exponential Moving Average -> "Patrick Mulloy looked at the lag of an EMA and took it personally. TEMA is what happens when you apply algebra to impatience." +> *Patrick Mulloy looked at the lag of an EMA and took it personally. TEMA is what happens when you apply algebra to impatience.* diff --git a/lib/trends_IIR/trama/Trama.md b/lib/trends_IIR/trama/Trama.md index 345f5138..0fd2bc36 100644 --- a/lib/trends_IIR/trama/Trama.md +++ b/lib/trends_IIR/trama/Trama.md @@ -1,5 +1,7 @@ # 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.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "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 diff --git a/lib/trends_IIR/vama/Vama.md b/lib/trends_IIR/vama/Vama.md index 2e767fff..7755f3a4 100644 --- a/lib/trends_IIR/vama/Vama.md +++ b/lib/trends_IIR/vama/Vama.md @@ -1,5 +1,7 @@ # VAMA: Volatility Adjusted Moving Average +> *The market doesn't care about your moving average period. VAMA returns the favor by not caring about a fixed period either.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market doesn't care about your moving average period. VAMA returns the favor by not caring about a fixed period either." - ## The Core Insight Most moving averages use a fixed lookback period. VAMA takes a different approach: it dynamically adjusts its effective period based on current market volatility relative to historical norms. When short-term volatility exceeds long-term volatility (high activity), VAMA shortens its period for faster response. When volatility contracts (quiet markets), it lengthens the period for smoother output. diff --git a/lib/trends_IIR/vidya/Vidya.md b/lib/trends_IIR/vidya/Vidya.md index 44573a29..f19f4ba5 100644 --- a/lib/trends_IIR/vidya/Vidya.md +++ b/lib/trends_IIR/vidya/Vidya.md @@ -1,5 +1,7 @@ # VIDYA: Variable Index Dynamic Average +> *Tushar Chande asked: 'Why should I trust a moving average that treats a market crash the same as a lunch break?' VIDYA is the answer.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Tushar Chande asked: 'Why should I trust a moving average that treats a market crash the same as a lunch break?' VIDYA is the answer." - The Variable Index Dynamic Average (VIDYA) is an adaptive moving average that automatically adjusts its smoothing speed based on market volatility. When the market is trending (high volatility), VIDYA speeds up to capture the move. When the market is ranging (low volatility), it slows down to filter out the noise. ## Historical Context diff --git a/lib/trends_IIR/yzvama/Yzvama.md b/lib/trends_IIR/yzvama/Yzvama.md index 1d85248b..547a89b9 100644 --- a/lib/trends_IIR/yzvama/Yzvama.md +++ b/lib/trends_IIR/yzvama/Yzvama.md @@ -1,5 +1,7 @@ # YZVAMA: Yang-Zhang Volatility Adjusted Moving Average +> *ATR tells you how much the market moved. Yang-Zhang tells you how much it *should* have moved given the gaps and intrabar action. YZVAMA uses that distinction to know when the market is lying about its volatility.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "ATR tells you how much the market moved. Yang-Zhang tells you how much it *should* have moved given the gaps and intrabar action. YZVAMA uses that distinction to know when the market is lying about its volatility." - ## The Core Insight Most adaptive moving averages measure volatility using close-to-close changes (standard deviation) or high-low ranges (ATR). Both approaches miss a critical market dynamic: overnight gaps. A stock that gaps up 5% at the open but closes unchanged shows zero close-to-close volatility, yet anyone trading that day felt every point of that 5% move. diff --git a/lib/trends_IIR/zldema/Zldema.md b/lib/trends_IIR/zldema/Zldema.md index d7bb60ad..9e5994b3 100644 --- a/lib/trends_IIR/zldema/Zldema.md +++ b/lib/trends_IIR/zldema/Zldema.md @@ -1,5 +1,7 @@ # ZLDEMA: Zero-Lag Double Exponential Moving Average +> *ZLDEMA combines the speed of zero-lag prediction with the smoothness of double exponential averaging. You get faster response than ZLEMA, with better trend-following than DEMA.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "ZLDEMA combines the speed of zero-lag prediction with the smoothness of double exponential averaging. You get faster response than ZLEMA, with better trend-following than DEMA." - ## DEMA with lag compensation via a zero-lag signal diff --git a/lib/trends_IIR/zlema/Zlema.md b/lib/trends_IIR/zlema/Zlema.md index 05cb1c4b..83c60a29 100644 --- a/lib/trends_IIR/zlema/Zlema.md +++ b/lib/trends_IIR/zlema/Zlema.md @@ -1,5 +1,7 @@ # ZLEMA: Zero-Lag Exponential Moving Average +> *ZLEMA does not erase lag. It predicts just enough to act early, then pays the price in overshoot.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "ZLEMA does not erase lag. It predicts just enough to act early, then pays the price in overshoot." - ## EMA with lag compensation via a zero-lag signal diff --git a/lib/trends_IIR/zltema/Zltema.md b/lib/trends_IIR/zltema/Zltema.md index b84655f6..109e7a5c 100644 --- a/lib/trends_IIR/zltema/Zltema.md +++ b/lib/trends_IIR/zltema/Zltema.md @@ -1,5 +1,7 @@ # ZLTEMA: Zero-Lag Triple Exponential Moving Average +> *ZLTEMA combines the speed of zero-lag prediction with the smoothness of triple exponential averaging. You get the fastest response in the zero-lag family, with the best noise rejection from the TEMA cascade.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | @@ -17,8 +19,6 @@ - Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "ZLTEMA combines the speed of zero-lag prediction with the smoothness of triple exponential averaging. You get the fastest response in the zero-lag family, with the best noise rejection from the TEMA cascade." - ## TEMA with lag compensation via a zero-lag signal diff --git a/lib/volatility/_index.md b/lib/volatility/_index.md index 9f3abecf..c5a6d1b4 100644 --- a/lib/volatility/_index.md +++ b/lib/volatility/_index.md @@ -1,7 +1,5 @@ # Volatility -> "Volatility is the price of admission. The question is whether the ride is worth it." - Volatility measures the magnitude of price changes, independent of direction. Low volatility indicates consolidation and coiling energy; high volatility indicates explosive movement and trend development. These indicators answer "how much?" and "how fast?", not "which way?". | Indicator | Full Name | Description | diff --git a/lib/volatility/adr/Adr.md b/lib/volatility/adr/Adr.md index 9d01764b..9f444211 100644 --- a/lib/volatility/adr/Adr.md +++ b/lib/volatility/adr/Adr.md @@ -1,5 +1,7 @@ # ADR: Average Daily Range +> *The simplest measure is often the most useful. Why complicate what doesn't need complicating?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `ma.WarmupPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest measure is often the most useful. Why complicate what doesn't need complicating?" - The Average Daily Range (ADR) measures the average distance between High and Low prices over a specified period. Unlike its cousin ATR, ADR ignores gaps entirely. It answers a straightforward question: "How much does this asset typically move within a single bar?" This simplicity is ADR's strength. When you don't care about overnight gaps—perhaps you're day trading or analyzing intraday bars—ADR gives you exactly what you need without the complexity of True Range calculations. diff --git a/lib/volatility/atr/Atr.md b/lib/volatility/atr/Atr.md index 5008fe67..7537849f 100644 --- a/lib/volatility/atr/Atr.md +++ b/lib/volatility/atr/Atr.md @@ -1,5 +1,7 @@ # ATR: Average True Range +> *Volatility is the price of admission. The question is whether the ride is worth it.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `rma.WarmupPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility is the price of admission. The question is whether the ride is worth it." - The Average True Range measures market "heat" with complete disregard for direction. It ignores whether the market is screaming upward or crashing downward. ATR cares only about magnitude. When ATR is high, expect wide swings. When ATR is low, expect narrow consolidation. Most traders mistakenly use ATR to find entries. Its true power lies in exits and position sizing. ATR answers the critical question: "How far can this asset move against me in a single day?" ## Historical Context diff --git a/lib/volatility/atrn/Atrn.md b/lib/volatility/atrn/Atrn.md index 3d1bedc2..fff17d57 100644 --- a/lib/volatility/atrn/Atrn.md +++ b/lib/volatility/atrn/Atrn.md @@ -1,5 +1,7 @@ # ATRN: Average True Range Normalized +> *Context is everything. A \$5 ATR means nothing until you know the \$5 ATR from last month was \$2.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Context is everything. A \$5 ATR means nothing until you know the \$5 ATR from last month was \$2." - ATRN transforms the absolute ATR into a relative measure by normalizing it to a [0,1] scale using min-max scaling over a lookback window. This answers the question: "Is current volatility high or low *compared to recent history*?" While ATR tells you *how much* an asset moves, ATRN tells you *how unusual* that movement is relative to the asset's own recent behavior. A value near 1 means volatility is at its recent high; a value near 0 means volatility is at its recent low; 0.5 means volatility is average. diff --git a/lib/volatility/bbw/Bbw.md b/lib/volatility/bbw/Bbw.md index 2b3238a4..b931bdaa 100644 --- a/lib/volatility/bbw/Bbw.md +++ b/lib/volatility/bbw/Bbw.md @@ -1,5 +1,7 @@ # BBW: Bollinger Band Width +> *Volatility breeds opportunity. The squeeze precedes the explosion.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility breeds opportunity. The squeeze precedes the explosion." - Bollinger Band Width measures the distance between upper and lower Bollinger Bands, normalized by the middle band. When BBW is low, the bands are squeezing together, signaling compressed volatility and impending breakout. When BBW is high, the market is in an expanded volatility state. BBW transforms Bollinger Bands from a visual channel indicator into a quantifiable volatility oscillator, enabling algorithmic detection of "squeeze" conditions that often precede significant price moves. ## Historical Context diff --git a/lib/volatility/bbwn/Bbwn.md b/lib/volatility/bbwn/Bbwn.md index b1c58e37..74fc91df 100644 --- a/lib/volatility/bbwn/Bbwn.md +++ b/lib/volatility/bbwn/Bbwn.md @@ -1,5 +1,7 @@ # BBWN: Bollinger Band Width Normalized +> *Normalization transforms volatility chaos into comparable signals.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period + lookback` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Normalization transforms volatility chaos into comparable signals." - Bollinger Band Width Normalized (BBWN) extends the standard BBW by normalizing it to a [0,1] range based on historical minimum and maximum values over a lookback period. This normalization enables better comparison across different timeframes, instruments, and market conditions, making it easier to identify relative volatility levels consistently. ## Historical Context diff --git a/lib/volatility/bbwp/Bbwp.md b/lib/volatility/bbwp/Bbwp.md index a0cefec5..94615e56 100644 --- a/lib/volatility/bbwp/Bbwp.md +++ b/lib/volatility/bbwp/Bbwp.md @@ -1,5 +1,7 @@ # BBWP: Bollinger Band Width Percentile +> *Where does current volatility rank in the historical distribution? BBWP answers with a percentile.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period + lookback` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Where does current volatility rank in the historical distribution? BBWP answers with a percentile." - BBWP (Bollinger Band Width Percentile) measures where the current Bollinger Band Width falls within its historical distribution, expressing the result as a percentile rank between 0 and 1. Unlike BBWN which normalizes using min/max values, BBWP uses percentile ranking which is more robust to outliers. ## Historical Context diff --git a/lib/volatility/ccv/Ccv.md b/lib/volatility/ccv/Ccv.md index 4bc4be8b..0d619da5 100644 --- a/lib/volatility/ccv/Ccv.md +++ b/lib/volatility/ccv/Ccv.md @@ -1,5 +1,7 @@ # CCV: Close-to-Close Volatility +> *The simplest volatility measure is often the most robust—when all you have is closing prices, make the most of them.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest volatility measure is often the most robust—when all you have is closing prices, make the most of them." - Close-to-Close Volatility (CCV) calculates the annualized standard deviation of logarithmic returns using only closing prices. This is the foundational volatility measure in quantitative finance, serving as a benchmark against which more sophisticated estimators are compared. The implementation supports three smoothing methods (SMA, EMA, WMA) and annualizes using the standard √252 factor for daily data. ## Historical Context diff --git a/lib/volatility/cv/Cv.md b/lib/volatility/cv/Cv.md index 79e5a40f..e950cce4 100644 --- a/lib/volatility/cv/Cv.md +++ b/lib/volatility/cv/Cv.md @@ -1,5 +1,7 @@ # CV: Conditional Volatility (GARCH(1,1)) +> *Volatility begets volatility—the GARCH model captures what traders have always known: calm markets stay calm, turbulent markets stay turbulent.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility begets volatility—the GARCH model captures what traders have always known: calm markets stay calm, turbulent markets stay turbulent." - Conditional Volatility (CV) implements the GARCH(1,1) model for volatility forecasting, the most widely used time-varying volatility model in financial econometrics. Unlike simple historical volatility measures, GARCH captures two key empirical features of financial returns: volatility clustering (large moves tend to follow large moves) and mean reversion (volatility eventually returns to a long-run average). The output is annualized volatility expressed as a percentage. ## Historical Context diff --git a/lib/volatility/cvi/Cvi.md b/lib/volatility/cvi/Cvi.md index dd75d1bd..61560417 100644 --- a/lib/volatility/cvi/Cvi.md +++ b/lib/volatility/cvi/Cvi.md @@ -1,5 +1,7 @@ # CVI: Chaikin's Volatility +> *Volatility expansion precedes major moves—when the trading range starts widening, pay attention.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volatility expansion precedes major moves—when the trading range starts widening, pay attention." - Chaikin's Volatility (CVI) measures the rate of change of the EMA-smoothed high-low trading range. Unlike traditional volatility measures that focus on returns, CVI directly tracks the expansion and contraction of price ranges over time. A positive CVI indicates expanding volatility (wider trading ranges), while a negative CVI signals contracting volatility (narrower ranges). This makes CVI particularly useful for identifying breakout conditions and market transitions. ## Historical Context diff --git a/lib/volatility/etherm/Etherm.md b/lib/volatility/etherm/Etherm.md index 17ef39f1..6425f1e6 100644 --- a/lib/volatility/etherm/Etherm.md +++ b/lib/volatility/etherm/Etherm.md @@ -1,5 +1,7 @@ # ETHERM: Elder's Thermometer +> *Markets run a fever before they crash. The thermometer tells you when to reach for the aspirin.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Markets run a fever before they crash. The thermometer tells you when to reach for the aspirin." - Elder's Thermometer (ETHERM) measures bar-to-bar range extension — the maximum outward protrusion of the current bar beyond the previous bar's high or low. Developed by Dr. Alexander Elder, it captures only outward expansions; inward contractions clamp to zero. An EMA signal line with bias compensation provides a smoothed reference for detecting explosive moves (temperature significantly exceeding the signal). ## Historical Context diff --git a/lib/volatility/ewma/Ewma.md b/lib/volatility/ewma/Ewma.md index 08968494..9dfc0e1f 100644 --- a/lib/volatility/ewma/Ewma.md +++ b/lib/volatility/ewma/Ewma.md @@ -1,5 +1,7 @@ # EWMA: Exponentially Weighted Moving Average Volatility +> *The past doesn't repeat itself, but it does rhyme—and EWMA captures the rhythm of volatility with exponential memory.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The past doesn't repeat itself, but it does rhyme—and EWMA captures the rhythm of volatility with exponential memory." - EWMA Volatility calculates market volatility using an exponentially weighted moving average of squared log returns with bias correction. Unlike simple historical volatility that weights all observations equally, EWMA gives more weight to recent observations while still considering historical data, making it more responsive to current market conditions. ## Historical Context diff --git a/lib/volatility/gkv/Gkv.md b/lib/volatility/gkv/Gkv.md index 282c4888..6f85d96c 100644 --- a/lib/volatility/gkv/Gkv.md +++ b/lib/volatility/gkv/Gkv.md @@ -1,5 +1,7 @@ # GKV: Garman-Klass Volatility +> *Why settle for closing prices when you have the full trading range? It's like judging a book by its last page.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Why settle for closing prices when you have the full trading range? It's like judging a book by its last page." - Garman-Klass Volatility (GKV) is a range-based volatility estimator that uses all four OHLC prices to provide more efficient volatility estimates than traditional close-to-close methods. Developed by Mark Garman and Michael Klass in 1980, this estimator achieves theoretical efficiency gains of 7-8x over simple close-to-close variance by incorporating intraday price information. The implementation includes RMA (Wilder's) smoothing with bias correction and optional annualization. ## Historical Context diff --git a/lib/volatility/hlv/Hlv.md b/lib/volatility/hlv/Hlv.md index dc7435af..723d8ec1 100644 --- a/lib/volatility/hlv/Hlv.md +++ b/lib/volatility/hlv/Hlv.md @@ -1,5 +1,7 @@ # HLV: High-Low Volatility (Parkinson) +> *The simplest solution is often the most elegant. When you only need the peaks and valleys, why ask for the whole journey?* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The simplest solution is often the most elegant. When you only need the peaks and valleys, why ask for the whole journey?" - *Also known as: PV (Parkinson Volatility)* diff --git a/lib/volatility/hv/Hv.md b/lib/volatility/hv/Hv.md index fefc4f59..ccf6cbc6 100644 --- a/lib/volatility/hv/Hv.md +++ b/lib/volatility/hv/Hv.md @@ -1,5 +1,7 @@ # HV: Historical Volatility (Close-to-Close) +> *The foundation of all volatility measures—simple, intuitive, and yet surprisingly informative when you understand what it's actually measuring.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The foundation of all volatility measures—simple, intuitive, and yet surprisingly informative when you understand what it's actually measuring." - Historical Volatility (HV), also known as close-to-close volatility or realized volatility, is the classical measure of price volatility using the standard deviation of logarithmic returns. First formalized in the early 20th century and central to the Black-Scholes option pricing model, HV remains the benchmark against which all other volatility estimators are compared. This implementation uses population standard deviation with a rolling window and optional annualization. ## Historical Context diff --git a/lib/volatility/jvolty/Jvolty.md b/lib/volatility/jvolty/Jvolty.md index 7209b295..4b52a56d 100644 --- a/lib/volatility/jvolty/Jvolty.md +++ b/lib/volatility/jvolty/Jvolty.md @@ -1,5 +1,7 @@ # JVOLTY: Jurik Volatility +> *The volatility measure that ignores the noise—because sometimes, the best signal comes from knowing what to throw away.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The volatility measure that ignores the noise—because sometimes, the best signal comes from knowing what to throw away." - Jurik Volatility (JVOLTY) is the adaptive volatility component extracted from Mark Jurik's JMA algorithm. Unlike traditional volatility measures that treat all price movements equally, JVOLTY uses a 128-bar trimmed mean distribution to compute a robust volatility reference that rejects outliers by design. The result: a volatility measure that remains stable during flash crashes, earnings surprises, and 5-sigma events while still tracking genuine regime changes. ## Historical Context diff --git a/lib/volatility/jvoltyn/Jvoltyn.md b/lib/volatility/jvoltyn/Jvoltyn.md index 6ad78aa8..54a6e188 100644 --- a/lib/volatility/jvoltyn/Jvoltyn.md +++ b/lib/volatility/jvoltyn/Jvoltyn.md @@ -1,5 +1,7 @@ # JVOLTYN: Normalized Jurik Volatility +> *When you need to compare apples to apples, normalize your volatility—0 is calm, 100 is chaos.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When you need to compare apples to apples, normalize your volatility—0 is calm, 100 is chaos." - Normalized Jurik Volatility (JVOLTYN) maps the raw JVOLTY dynamic exponent to a 0-100 scale. While JVOLTY outputs values in the range [1, logParam] (where logParam is period-dependent), JVOLTYN transforms this to a universal scale where 0 represents minimum volatility and 100 represents maximum volatility. This normalization enables direct comparison across different periods and instruments. ## Historical Context diff --git a/lib/volatility/massi/Massi.md b/lib/volatility/massi/Massi.md index 3d343a08..19ac6a61 100644 --- a/lib/volatility/massi/Massi.md +++ b/lib/volatility/massi/Massi.md @@ -1,5 +1,7 @@ # MASSI: Mass Index +> *The Mass Index doesn't predict direction—it predicts the moment of maximum uncertainty before clarity emerges.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The Mass Index doesn't predict direction—it predicts the moment of maximum uncertainty before clarity emerges." - The Mass Index, developed by Donald Dorsey and introduced in the June 1992 issue of *Technical Analysis of Stocks & Commodities*, identifies potential trend reversals by measuring the narrowing and widening of the range between high and low prices. Unlike directional indicators, MASSI focuses on the *pattern* of range expansion and contraction, particularly the characteristic "reversal bulge" that often precedes significant market turns. ## Historical Context diff --git a/lib/volatility/natr/Natr.md b/lib/volatility/natr/Natr.md index e2c769ef..05f2c9a1 100644 --- a/lib/volatility/natr/Natr.md +++ b/lib/volatility/natr/Natr.md @@ -1,5 +1,7 @@ # NATR: Normalized Average True Range +> *The same volatility reads different on different price scales. NATR speaks the universal language of percentages.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The same volatility reads different on different price scales. NATR speaks the universal language of percentages." - NATR normalizes the Average True Range (ATR) as a percentage of the closing price. This is mathematically identical to ATRP (Average True Range Percent)—both compute `(ATR / Close) × 100`. The difference is purely nomenclature: NATR is the term used in TA-Lib and many charting platforms. ## Historical Context diff --git a/lib/volatility/rsv/Rsv.md b/lib/volatility/rsv/Rsv.md index ae05db95..ae01a4c7 100644 --- a/lib/volatility/rsv/Rsv.md +++ b/lib/volatility/rsv/Rsv.md @@ -1,5 +1,7 @@ # RSV: Rogers-Satchell Volatility +> *The best estimator is one that extracts maximum information from all available data while remaining robust to the noise of market microstructure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best estimator is one that extracts maximum information from all available data while remaining robust to the noise of market microstructure." - Rogers-Satchell Volatility (RSV) is a drift-adjusted OHLC-based volatility estimator that uses all four price points (Open, High, Low, Close) to provide more accurate volatility estimates than simpler range-based methods. Developed by L.C.G. Rogers and S.E. Satchell in 1991, this estimator is unique in its ability to account for price drift, making it particularly suitable for trending markets. The implementation uses SMA smoothing and optional annualization. ## Historical Context diff --git a/lib/volatility/rv/Rv.md b/lib/volatility/rv/Rv.md index 9a590d35..b0cbf69f 100644 --- a/lib/volatility/rv/Rv.md +++ b/lib/volatility/rv/Rv.md @@ -1,5 +1,7 @@ # RV: Realized Volatility +> *The sum of squared returns—a direct measure of how much the market actually moved, free from the assumptions embedded in standard deviation.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The sum of squared returns—a direct measure of how much the market actually moved, free from the assumptions embedded in standard deviation." - Realized Volatility (RV) measures price volatility using the sum of squared logarithmic returns over a rolling window, then applying SMA smoothing for stability. Unlike traditional Historical Volatility (HV) which calculates standard deviation of returns, RV directly accumulates squared returns—the raw building blocks of variance—providing a more direct measure of realized price variation. ## Historical Context diff --git a/lib/volatility/rvi/Rvi.md b/lib/volatility/rvi/Rvi.md index 4da3dd82..8b529ea4 100644 --- a/lib/volatility/rvi/Rvi.md +++ b/lib/volatility/rvi/Rvi.md @@ -1,5 +1,7 @@ # RVI: Relative Volatility Index +> *Not all volatility is created equal—upward volatility feels like profit, downward volatility feels like loss. RVI separates these psychological experiences into a quantifiable measure.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -17,8 +19,6 @@ - Requires `stdevLength` bars of warmup before first valid output (IsHot = true). - Validated against FM Labs revised RVI specification. -> "Not all volatility is created equal—upward volatility feels like profit, downward volatility feels like loss. RVI separates these psychological experiences into a quantifiable measure." - The Relative Volatility Index (RVI) is a directional volatility oscillator that distinguishes between upward and downward price volatility. Originally developed by Donald Dorsey in 1993 using close prices only, RVI was **revised in 1995** to compute separate RVI values on the High and Low price series and average them. This implementation follows the revised version: when fed OHLCV bars (TBar), it runs independent RVI channels on High and Low; when fed single prices (TValue), both channels receive the same value, reducing to the original formula. ## Historical Context diff --git a/lib/volatility/tr/Tr.md b/lib/volatility/tr/Tr.md index 63422b85..0bbe5a15 100644 --- a/lib/volatility/tr/Tr.md +++ b/lib/volatility/tr/Tr.md @@ -1,5 +1,7 @@ # TR: True Range +> *The true measure of volatility isn't just where price traveled within the bar, but whether it leaped from where it was.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The true measure of volatility isn't just where price traveled within the bar, but whether it leaped from where it was." - True Range (TR) is a volatility measure that captures the maximum price movement for each bar, including any gap from the previous close. Developed by J. Welles Wilder Jr. in 1978, TR forms the foundation for Average True Range (ATR) and numerous other volatility-based indicators. Unlike simple High-Low range, TR accounts for overnight gaps and opening jumps, providing a complete picture of price movement. ## Historical Context diff --git a/lib/volatility/ui/Ui.md b/lib/volatility/ui/Ui.md index 8e29b396..d735c502 100644 --- a/lib/volatility/ui/Ui.md +++ b/lib/volatility/ui/Ui.md @@ -1,5 +1,7 @@ # UI: Ulcer Index +> *The ulcer-inducing anxiety of watching your portfolio decline—now quantified.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The ulcer-inducing anxiety of watching your portfolio decline—now quantified." - Ulcer Index (UI) is a downside volatility measure that quantifies the depth and duration of drawdowns from recent highs. Developed by Peter G. Martin in 1987, UI captures what most volatility measures miss: the pain of being underwater. Unlike standard deviation or ATR that treat upside and downside moves equally, UI measures only the decline from peaks—the psychological stress that keeps investors awake at night. ## Historical Context diff --git a/lib/volatility/vov/Vov.md b/lib/volatility/vov/Vov.md index 97f56850..df0ea9c8 100644 --- a/lib/volatility/vov/Vov.md +++ b/lib/volatility/vov/Vov.md @@ -1,5 +1,7 @@ # VOV: Volatility of Volatility +> *When markets become uncertain about their own uncertainty, that's when things get interesting.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `volatilityPeriod + vovPeriod - 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When markets become uncertain about their own uncertainty, that's when things get interesting." - Volatility of Volatility (VOV) measures the standard deviation of volatility itself, quantifying how much volatility fluctuates over time. While standard volatility tells you how much prices move, VOV tells you how stable or unstable that movement pattern is. High VOV indicates volatility is erratic and unpredictable; low VOV suggests volatility is relatively stable and consistent. ## Historical Context diff --git a/lib/volatility/vr/Vr.md b/lib/volatility/vr/Vr.md index 1a7ecad2..c36c231c 100644 --- a/lib/volatility/vr/Vr.md +++ b/lib/volatility/vr/Vr.md @@ -1,5 +1,7 @@ # VR: Volatility Ratio +> *When today's range dwarfs the average, pay attention—the market is telling you something unusual is happening.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When today's range dwarfs the average, pay attention—the market is telling you something unusual is happening." - Volatility Ratio (VR) measures the current bar's True Range relative to its Average True Range (ATR), providing a normalized indicator of short-term volatility expansion or contraction. Values above 1.0 indicate above-average volatility (potential breakouts), while values below 1.0 suggest below-average volatility (consolidation). This simple yet powerful ratio helps traders identify when markets are moving unusually, often preceding significant price moves. ## Historical Context diff --git a/lib/volatility/yzv/Yzv.md b/lib/volatility/yzv/Yzv.md index 82800bda..ae2034b1 100644 --- a/lib/volatility/yzv/Yzv.md +++ b/lib/volatility/yzv/Yzv.md @@ -1,5 +1,7 @@ # YZV: Yang-Zhang Volatility +> *The best volatility estimator uses all the information the market gives you—overnight gaps, intraday swings, and everything in between.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volatility | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The best volatility estimator uses all the information the market gives you—overnight gaps, intraday swings, and everything in between." - Yang-Zhang Volatility is a sophisticated volatility estimator that combines overnight (close-to-open) returns with Rogers-Satchell intraday volatility to capture the full spectrum of price dynamics. Unlike simple close-to-close volatility that misses overnight gaps, or purely intraday measures that ignore opening moves, Yang-Zhang provides a theoretically unbiased estimate that remains consistent whether markets gap or drift. ## Historical Context diff --git a/lib/volume/_index.md b/lib/volume/_index.md index 57e5dfd0..b253078a 100644 --- a/lib/volume/_index.md +++ b/lib/volume/_index.md @@ -1,7 +1,5 @@ # Volume -> "It takes volume to make prices move." — Charles Dow - Volume is market fuel. Price tells what happened; volume tells how hard the market worked to make it happen. In a world of algorithmic trading and dark pools, volume analysis reveals where money actually flows. | Indicator | Full Name | Description | diff --git a/lib/volume/adl/Adl.md b/lib/volume/adl/Adl.md index aff18461..15747318 100644 --- a/lib/volume/adl/Adl.md +++ b/lib/volume/adl/Adl.md @@ -1,5 +1,7 @@ # ADL: Accumulation/Distribution Line +> *Volume precedes price.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume precedes price." — Old Wall Street Adage - The Accumulation/Distribution Line (ADL) is the bedrock of volume analysis. It attempts to answer a single, vital question: "Are the big players buying or selling?" Unlike On-Balance Volume (OBV), which treats every up-day as 100% buying, ADL is nuanced. It looks at *where* the price closed within the day's range. A close near the high on massive volume screams "Accumulation." A close near the low on massive volume screams "Distribution." diff --git a/lib/volume/adosc/Adosc.md b/lib/volume/adosc/Adosc.md index d9acd491..13b7e4cc 100644 --- a/lib/volume/adosc/Adosc.md +++ b/lib/volume/adosc/Adosc.md @@ -1,5 +1,7 @@ # ADOSC: Chaikin A/D Oscillator +> *Momentum precedes price. Volume momentum precedes price momentum.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `slowPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Momentum precedes price. Volume momentum precedes price momentum." - The Chaikin Oscillator (ADOSC) is an indicator of an indicator. It applies the MACD formula to the Accumulation/Distribution Line (ADL) instead of the price. While the ADL is great for spotting long-term flow, it can be sluggish. ADOSC acts as a turbocharger, measuring the *momentum* of that flow. It anticipates changes in the ADL, often signaling a reversal before the ADL itself turns. diff --git a/lib/volume/aobv/Aobv.md b/lib/volume/aobv/Aobv.md index c5bb0df9..36e42193 100644 --- a/lib/volume/aobv/Aobv.md +++ b/lib/volume/aobv/Aobv.md @@ -1,5 +1,7 @@ # AOBV: Archer On-Balance Volume +> *OBV told me what was happening. AOBV told me when to act.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> SlowPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "OBV told me what was happening. AOBV told me when to act." — Adapted trader wisdom - Archer On-Balance Volume (AOBV) applies dual exponential smoothing to the classic On-Balance Volume indicator, creating a responsive yet noise-filtered momentum signal. The intersection of fast and slow EMAs provides actionable crossover signals while preserving OBV's core insight: volume precedes price. Developed by EverGet (known as "Archer" in the TradingView community), AOBV addresses OBV's fundamental weakness—its sensitivity to single high-volume bars that can distort the cumulative reading. By smoothing with EMAs of period 4 (fast) and 14 (slow), AOBV filters noise while maintaining responsiveness to genuine accumulation/distribution shifts. diff --git a/lib/volume/cmf/Cmf.md b/lib/volume/cmf/Cmf.md index d027d177..e616a646 100644 --- a/lib/volume/cmf/Cmf.md +++ b/lib/volume/cmf/Cmf.md @@ -1,5 +1,7 @@ # CMF: Chaikin Money Flow +> *Money flow tells you what the big players are doing. CMF tells you if they're winning.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Money flow tells you what the big players are doing. CMF tells you if they're winning." — Marc Chaikin - Chaikin Money Flow (CMF) is the normalized cousin of the Accumulation/Distribution Line. While ADL is cumulative and unbounded, CMF oscillates between -1 and +1, measuring the persistence of buying or selling pressure over a rolling window. The genius of CMF is that it answers not just "Are they buying?" but "Have they been buying *consistently*?" A CMF reading of +0.25 means 25% more money flow went into accumulation than distribution over the lookback period. diff --git a/lib/volume/efi/Efi.md b/lib/volume/efi/Efi.md index c82e147a..8270ff5e 100644 --- a/lib/volume/efi/Efi.md +++ b/lib/volume/efi/Efi.md @@ -1,5 +1,7 @@ # EFI: Elder's Force Index +> *Force Index combines price movement with volume to measure the power behind every move. It's the market's polygraph test.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Force Index combines price movement with volume to measure the power behind every move. It's the market's polygraph test." — Dr. Alexander Elder - Elder's Force Index (EFI) quantifies the buying and selling pressure behind price movements by multiplying price change by volume. Large positive values indicate strong buying pressure (bulls in control), while large negative values reveal strong selling pressure (bears dominant). The genius of EFI lies in its integration of three essential market elements: direction (price change sign), extent (price change magnitude), and conviction (volume). A $1 move on 1 million shares tells a very different story than the same move on 10,000 shares. diff --git a/lib/volume/eom/Eom.md b/lib/volume/eom/Eom.md index 0eaba6ae..eccf27fa 100644 --- a/lib/volume/eom/Eom.md +++ b/lib/volume/eom/Eom.md @@ -1,5 +1,7 @@ # EOM: Ease of Movement +> *Ease of Movement reveals when price advances effortlessly versus when it struggles against resistance. It's the market's accelerometer.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Ease of Movement reveals when price advances effortlessly versus when it struggles against resistance. It's the market's accelerometer." — Richard W. Arms Jr. - Ease of Movement (EOM) quantifies how easily price moves relative to volume. High positive values indicate price is advancing with little resistance (low volume relative to price range), while high negative values reveal price declining easily. Values near zero suggest price is meeting resistance, requiring substantial volume to produce movement. The elegance of EOM lies in its normalization: it divides price change by a "box ratio" that accounts for both volume and price range. This makes the indicator comparable across securities with different price and volume characteristics. diff --git a/lib/volume/evwma/Evwma.md b/lib/volume/evwma/Evwma.md index e9d46818..88ee2169 100644 --- a/lib/volume/evwma/Evwma.md +++ b/lib/volume/evwma/Evwma.md @@ -1,5 +1,7 @@ # EVWMA: Elastic Volume Weighted Moving Average +> *Volume is the one technical indicator that never lies.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume is the one technical indicator that never lies." — Joe Granville - ## Introduction EVWMA (Elastic Volume Weighted Moving Average) is a volume-adaptive moving average that weights each bar's contribution to the average by its volume relative to a rolling volume sum. High-volume bars shift the average more aggressively toward the current price; low-volume bars barely nudge it. The "elastic" behavior emerges from volume-proportional blending: the smoothing factor is not fixed (like EMA's alpha) but varies dynamically with each bar's volume share. diff --git a/lib/volume/iii/Iii.md b/lib/volume/iii/Iii.md index fbd1bdc5..2ecab182 100644 --- a/lib/volume/iii/Iii.md +++ b/lib/volume/iii/Iii.md @@ -1,5 +1,7 @@ # III: Intraday Intensity Index +> *Where the close lands within the day's range tells you who won the battle—bulls or bears. Volume tells you how hard they fought.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Where the close lands within the day's range tells you who won the battle—bulls or bears. Volume tells you how hard they fought." - The Intraday Intensity Index (III) measures buying and selling pressure by analyzing where the close price falls within the high-low range, weighted by volume. Originally developed by David Bostian, this indicator quantifies whether money is flowing into or out of a security on an intraday basis. Values range from -1 (close at low, maximum selling pressure) to +1 (close at high, maximum buying pressure), multiplied by volume for magnitude. ## Historical Context diff --git a/lib/volume/kvo/Kvo.md b/lib/volume/kvo/Kvo.md index 73afaab5..8a3405b8 100644 --- a/lib/volume/kvo/Kvo.md +++ b/lib/volume/kvo/Kvo.md @@ -1,5 +1,7 @@ # KVO: Klinger Volume Oscillator +> *Volume is the fuel that drives the market train.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `slowPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume is the fuel that drives the market train." - The Klinger Volume Oscillator (KVO), developed by Stephen Klinger in the 1970s, measures the long-term trend of money flow while remaining sensitive to short-term fluctuations. Unlike simple volume indicators, KVO incorporates price direction and range into its volume analysis, creating a comprehensive measure of buying and selling pressure that can identify divergences before they appear in price action. ## Historical Context diff --git a/lib/volume/mfi/Mfi.md b/lib/volume/mfi/Mfi.md index 727a3d12..1560564f 100644 --- a/lib/volume/mfi/Mfi.md +++ b/lib/volume/mfi/Mfi.md @@ -1,5 +1,7 @@ # MFI: Money Flow Index +> *Volume confirms price, but money flow confirms intent.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume confirms price, but money flow confirms intent." — Gene Quong & Avrum Soudack - Money Flow Index is the volume-weighted cousin of RSI. While RSI measures the momentum of price changes alone, MFI incorporates volume to determine whether the price movement has conviction behind it. The result is an oscillator that can identify when strong hands are accumulating or distributing. The innovation of MFI is answering not just "Is price going up?" but "Is significant money pushing price up?" A stock rising on thin volume produces a different MFI reading than one rising on heavy institutional participation. diff --git a/lib/volume/nvi/Nvi.md b/lib/volume/nvi/Nvi.md index bede9152..1d6fcaf9 100644 --- a/lib/volume/nvi/Nvi.md +++ b/lib/volume/nvi/Nvi.md @@ -1,5 +1,7 @@ # NVI: Negative Volume Index +> *Low volume suggests smart money is at work; high volume days are for the crowd.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Low volume suggests smart money is at work; high volume days are for the crowd." — Norman Fosback - The Negative Volume Index tracks price changes exclusively on days when trading volume decreases compared to the previous day. The underlying theory: institutional investors—the "smart money"—prefer to accumulate or distribute positions during quiet, low-volume periods, while retail traders drive high-volume days with more emotional, less informed decisions. NVI essentially asks: "What are prices doing when the crowd isn't participating?" If NVI rises while volume falls, smart money may be quietly buying. If NVI falls on low volume, institutions might be exiting positions without attracting attention. diff --git a/lib/volume/obv/Obv.md b/lib/volume/obv/Obv.md index 4740c344..82765d6c 100644 --- a/lib/volume/obv/Obv.md +++ b/lib/volume/obv/Obv.md @@ -1,5 +1,7 @@ # OBV: On Balance Volume +> *Volume is the fuel that drives price.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume is the fuel that drives price." — Joseph Granville - On Balance Volume distills the relationship between price and volume into a single cumulative indicator. The premise is elegantly simple: volume flows into a security when it closes higher, and flows out when it closes lower. OBV tracks this flow as a running total, creating a momentum indicator that often leads price movements. Granville's insight was that volume precedes price. Institutional buying or selling shows up in volume before it manifests in price trends. When OBV rises while price remains flat, accumulation is occurring—a potential bullish signal. When OBV falls despite stable prices, distribution may be underway. diff --git a/lib/volume/pvd/Pvd.md b/lib/volume/pvd/Pvd.md index 9c703aab..600f5b7f 100644 --- a/lib/volume/pvd/Pvd.md +++ b/lib/volume/pvd/Pvd.md @@ -1,5 +1,7 @@ # PVD: Price Volume Divergence +> *When price and volume disagree, one of them is lying.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "When price and volume disagree, one of them is lying." - Price Volume Divergence (PVD) quantifies the disagreement between price momentum and volume momentum. The indicator identifies situations where price movement lacks volume confirmation—a classic warning signal that the current trend may be weakening or about to reverse. ## Historical Context diff --git a/lib/volume/pvi/Pvi.md b/lib/volume/pvi/Pvi.md index f67dd915..a2d33f64 100644 --- a/lib/volume/pvi/Pvi.md +++ b/lib/volume/pvi/Pvi.md @@ -1,5 +1,7 @@ # PVI: Positive Volume Index +> *High volume days reveal where retail traders swarm; smart money prefers the quiet.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "High volume days reveal where retail traders swarm; smart money prefers the quiet." — Norman Fosback - The Positive Volume Index tracks price changes exclusively on days when trading volume increases compared to the previous day. The underlying theory: retail investors—the "uninformed crowd"—drive high-volume trading days, often reacting emotionally to news and price movements. Institutional investors prefer to operate during quieter periods to avoid moving markets. PVI essentially asks: "What are prices doing when the crowd is most active?" If PVI rises on high volume, retail enthusiasm is driving prices up. If PVI falls on high volume, retail panic may be pushing prices down. Either way, this represents the emotional, less-informed segment of the market. diff --git a/lib/volume/pvo/Pvo.md b/lib/volume/pvo/Pvo.md index b98f9f22..0227f8dc 100644 --- a/lib/volume/pvo/Pvo.md +++ b/lib/volume/pvo/Pvo.md @@ -1,5 +1,7 @@ # PVO: Percentage Volume Oscillator +> *Volume precedes price—PVO measures whether the market is inhaling or exhaling.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `slowPeriod` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume precedes price—PVO measures whether the market is inhaling or exhaling." - The Percentage Volume Oscillator (PVO) measures the difference between two exponential moving averages of volume, expressed as a percentage of the slower EMA. Essentially the MACD of volume, PVO identifies whether volume is expanding (accumulation) or contracting (distribution) relative to its recent history. This percentage normalization makes it comparable across instruments with vastly different volume profiles. ## Historical Context diff --git a/lib/volume/pvr/Pvr.md b/lib/volume/pvr/Pvr.md index e5a0f703..00091826 100644 --- a/lib/volume/pvr/Pvr.md +++ b/lib/volume/pvr/Pvr.md @@ -1,5 +1,7 @@ # PVR: Price Volume Rank +> *The relationship between price and volume reveals the conviction behind market moves.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The relationship between price and volume reveals the conviction behind market moves." — Technical Analysis Axiom - Price Volume Rank distills the price-volume relationship into a simple categorical indicator. Rather than producing a continuous value, PVR returns one of five discrete states (0-4) that classify the current bar's price and volume behavior relative to the previous bar. This creates an instant "market condition" snapshot. The elegance of PVR lies in its simplicity: it answers two questions simultaneously—is price rising or falling, and is volume supporting that move? The four non-zero categories represent the classic volume confirmation matrix, while zero indicates price equilibrium. diff --git a/lib/volume/pvt/Pvt.md b/lib/volume/pvt/Pvt.md index 50add2cd..508d58d6 100644 --- a/lib/volume/pvt/Pvt.md +++ b/lib/volume/pvt/Pvt.md @@ -1,5 +1,7 @@ # PVT: Price Volume Trend +> *Volume tells you about the intensity of price moves, but PVT tells you what volume is actually accomplishing.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume tells you about the intensity of price moves, but PVT tells you what volume is actually accomplishing." — Unknown - Price Volume Trend refines the OBV concept by weighting volume according to the percentage price change rather than using an all-or-nothing approach. Where OBV assigns the entire bar's volume to either buyers or sellers, PVT scales the volume contribution by the relative price movement—a 1% move adds only 1% of volume to the running total. This proportional weighting makes PVT more sensitive to the magnitude of price changes, not just their direction. A large price move with moderate volume registers more strongly than a tiny price move with massive volume—aligning the indicator more closely with price momentum. diff --git a/lib/volume/tvi/Tvi.md b/lib/volume/tvi/Tvi.md index 84e43eff..dfae95d4 100644 --- a/lib/volume/tvi/Tvi.md +++ b/lib/volume/tvi/Tvi.md @@ -1,5 +1,7 @@ # TVI: Trade Volume Index +> *The direction of money flow matters more than the magnitude of price change.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 2` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The direction of money flow matters more than the magnitude of price change." — William Blau - Trade Volume Index refines the relationship between price and volume by introducing a threshold filter. Unlike OBV which responds to any price change, TVI only changes direction when price movement exceeds a minimum tick threshold. This "sticky direction" behavior filters out noise from insignificant price fluctuations, allowing the indicator to better capture genuine accumulation and distribution. The insight behind TVI is that small price movements within the bid-ask spread or normal market noise shouldn't flip the volume attribution. Only when buyers or sellers demonstrate enough conviction to move price beyond a meaningful threshold should the volume be credited to that side. diff --git a/lib/volume/twap/Twap.md b/lib/volume/twap/Twap.md index 25dc8f28..cbf3386a 100644 --- a/lib/volume/twap/Twap.md +++ b/lib/volume/twap/Twap.md @@ -1,5 +1,7 @@ # TWAP: Time Weighted Average Price +> *Equal time, equal weight—the simplest benchmark refuses to let any single moment dominate the conversation.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Equal time, equal weight—the simplest benchmark refuses to let any single moment dominate the conversation." — Anonymous Quant - Time Weighted Average Price (TWAP) calculates the average price over a period by giving equal weight to each price point, regardless of volume. Unlike VWAP which emphasizes high-volume periods, TWAP treats every moment as equally important. This makes it a pure temporal benchmark—ideal for evaluating execution quality when volume patterns could bias the analysis. The elegance of TWAP lies in its simplicity: accumulate prices, count observations, divide. No volume weighting, no complex adjustments. Just a running average that answers the question: "What was the typical price during this period?" diff --git a/lib/volume/va/Va.md b/lib/volume/va/Va.md index c461fadf..77b7ff75 100644 --- a/lib/volume/va/Va.md +++ b/lib/volume/va/Va.md @@ -1,5 +1,7 @@ # VA: Volume Accumulation +> *Volume tells you who's winning the argument between bulls and bears—VA keeps a running tally of the score.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume tells you who's winning the argument between bulls and bears—VA keeps a running tally of the score." — Anonymous Trader - Volume Accumulation (VA) measures the cumulative flow of volume weighted by where price closes relative to the bar's midpoint. When price closes above the midpoint, volume is considered buying pressure; when below, selling pressure. The cumulative sum reveals the net directional conviction of market participants over time. Unlike the Accumulation/Distribution Line (ADL) which uses the full bar range, VA simplifies to the midpoint—a cleaner measure that's less sensitive to extreme wicks. This makes VA particularly useful in markets prone to liquidity spikes that create artificial range extensions. diff --git a/lib/volume/vf/Vf.md b/lib/volume/vf/Vf.md index 8ebd7d8f..0b657e04 100644 --- a/lib/volume/vf/Vf.md +++ b/lib/volume/vf/Vf.md @@ -1,5 +1,7 @@ # VF: Volume Force +> *Price without volume is like a punch without body weight behind it—VF measures the momentum of conviction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Price without volume is like a punch without body weight behind it—VF measures the momentum of conviction." — Anonymous Quant - Volume Force (VF) quantifies the strength of volume behind price movements by multiplying price change by volume and applying EMA smoothing with warmup compensation. The result is a momentum-style oscillator that distinguishes between genuine volume-backed moves and hollow price action. Unlike simple volume indicators that ignore direction, VF combines directional price change with volume intensity. Large volumes during significant price moves produce high VF readings; large volumes during flat price action contribute nothing. This selectivity makes VF particularly effective at filtering noise from signal. diff --git a/lib/volume/vo/Vo.md b/lib/volume/vo/Vo.md index 1d460f92..0d1b5c9f 100644 --- a/lib/volume/vo/Vo.md +++ b/lib/volume/vo/Vo.md @@ -1,5 +1,7 @@ # VO: Volume Oscillator +> *Volume tells us the conviction behind price moves—the oscillator reveals when that conviction is accelerating or fading.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume tells us the conviction behind price moves—the oscillator reveals when that conviction is accelerating or fading." - The Volume Oscillator (VO) measures the difference between two moving averages of volume, expressed as a percentage. It helps identify changes in volume trends and potential momentum shifts by comparing short-term volume activity against longer-term volume norms. ## Historical Context diff --git a/lib/volume/vroc/Vroc.md b/lib/volume/vroc/Vroc.md index 139d84dc..e25a7bcc 100644 --- a/lib/volume/vroc/Vroc.md +++ b/lib/volume/vroc/Vroc.md @@ -1,5 +1,7 @@ # VROC: Volume Rate of Change +> *Yesterday's volume is ancient history; what matters is how fast it's changing.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period + 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Yesterday's volume is ancient history; what matters is how fast it's changing." - VROC (Volume Rate of Change) measures the percentage or absolute change in volume over a specified lookback period. Unlike moving average-based volume indicators that smooth data, VROC provides a direct comparison between current volume and historical volume, making it particularly useful for detecting sudden volume surges or contractions that may signal significant market events. ## Historical Context diff --git a/lib/volume/vwad/Vwad.md b/lib/volume/vwad/Vwad.md index 94a43da5..ecbb354a 100644 --- a/lib/volume/vwad/Vwad.md +++ b/lib/volume/vwad/Vwad.md @@ -1,5 +1,7 @@ # VWAD: Volume Weighted Accumulation/Distribution +> *The market's memory isn't just about price—it's about who showed up with conviction.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "The market's memory isn't just about price—it's about who showed up with conviction." - Volume Weighted Accumulation/Distribution (VWAD) takes the classic ADL concept and asks a sharper question: not just "where did the close fall in the range?" but "how significant was this bar's volume compared to recent activity?" Traditional ADL treats all bars equally—a 100-share bar and a 10-million-share bar contribute the same mathematical weight if their MFM is identical. VWAD recognizes that volume concentration matters. A high-volume bar during a period of thin trading represents institutional commitment; the same MFM reading during heavy volume is just noise in the crowd. diff --git a/lib/volume/vwap/Vwap.md b/lib/volume/vwap/Vwap.md index b0019cf7..db60d55a 100644 --- a/lib/volume/vwap/Vwap.md +++ b/lib/volume/vwap/Vwap.md @@ -1,5 +1,7 @@ # VWAP: Volume Weighted Average Price +> *VWAP doesn't predict where price will go—it reveals where institutional money has already committed.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> 1` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "VWAP doesn't predict where price will go—it reveals where institutional money has already committed." - VWAP (Volume Weighted Average Price) calculates the cumulative average price weighted by trading volume, typically reset at session boundaries. It represents the true average price at which a security has traded throughout the period, giving more weight to prices where higher volume occurred. This implementation supports flexible period-based resets rather than traditional session-based anchoring. ## Historical Context diff --git a/lib/volume/vwma/Vwma.md b/lib/volume/vwma/Vwma.md index 1196b59e..02cd1d67 100644 --- a/lib/volume/vwma/Vwma.md +++ b/lib/volume/vwma/Vwma.md @@ -1,5 +1,7 @@ # VWMA: Volume Weighted Moving Average +> *VWMA reveals where the smart money traded—not just where price went, but where conviction backed the moves.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires `> period` bars of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "VWMA reveals where the smart money traded—not just where price went, but where conviction backed the moves." - VWMA (Volume Weighted Moving Average) calculates a moving average where each price is weighted by its corresponding volume over a specified lookback period. Unlike VWAP which accumulates from a reset point, VWMA uses a sliding window that continuously drops old values, making it a true moving average. Bars with higher volume contribute more to the average, surfacing price levels where institutional activity concentrated. ## Historical Context diff --git a/lib/volume/wad/Wad.md b/lib/volume/wad/Wad.md index bfa6e42d..45944e7f 100644 --- a/lib/volume/wad/Wad.md +++ b/lib/volume/wad/Wad.md @@ -1,5 +1,7 @@ # WAD: Williams Accumulation/Distribution +> *Volume is the fuel that drives price.* + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Volume | @@ -16,8 +18,6 @@ - Requires 1 bar of warmup before first valid output (IsHot = true). - Validated against TA-Lib, Skender, and Tulip reference implementations where available. -> "Volume is the fuel that drives price." — Larry Williams - Williams Accumulation/Distribution (WAD) is Larry Williams' contribution to the volume analysis toolkit. Unlike the standard Accumulation/Distribution Line that uses the close's position within the day's range, WAD incorporates **True Range** concepts. This gives it a different perspective on buying and selling pressure. ## Historical Context