feat: add new indicators (Decay, Edecay, MinusDi, MinusDm, PlusDi, PlusDm, Maxindex, Minindex, Sarext) and update pine scripts, core libs, validation tests, and python bindings
2026-03-09 13:45:46 -07:00
|
|
|
|
// Licensed under the Apache License, Version 2.0
|
2026-02-20 18:44:56 -08:00
|
|
|
|
// © mihakralj
|
|
|
|
|
|
//@version=6
|
|
|
|
|
|
indicator("TD Sequential (TD_SEQ)", "TD_SEQ", overlay=false)
|
|
|
|
|
|
|
|
|
|
|
|
//@function Calculates TD Sequential — Tom DeMark's exhaustion counting system
|
|
|
|
|
|
//@param comparePeriod Lookback for setup comparison (close vs close[comparePeriod])
|
|
|
|
|
|
//@returns [setupCount, countdownCount] signed setup (1-9) and countdown (1-13) values
|
|
|
|
|
|
//@description TD Sequential identifies trend exhaustion via two phases:
|
|
|
|
|
|
// Phase 1 — Setup (1 to 9):
|
|
|
|
|
|
// Buy Setup: consecutive bars where close < close[comparePeriod] (count negative)
|
|
|
|
|
|
// Sell Setup: consecutive bars where close > close[comparePeriod] (count positive)
|
|
|
|
|
|
// A completed setup reaches ±9. The count resets when the condition breaks.
|
|
|
|
|
|
// Phase 2 — Countdown (1 to 13):
|
|
|
|
|
|
// After a completed 9-count setup, countdown begins:
|
|
|
|
|
|
// Buy Countdown: close < low[2] (counts negative, toward −13)
|
|
|
|
|
|
// Sell Countdown: close > high[2] (counts positive, toward +13)
|
|
|
|
|
|
// Countdown is non-consecutive — only qualifying bars increment.
|
|
|
|
|
|
// Countdown resets if an opposite setup completes (±9 in other direction).
|
|
|
|
|
|
// A completed ±13 signals potential exhaustion reversal.
|
|
|
|
|
|
// Setup values: −9 to −1 (buy) or +1 to +9 (sell), 0 = no active setup bar
|
|
|
|
|
|
// Countdown values: −13 to −1 (buy) or +1 to +13 (sell), 0 = no active countdown
|
|
|
|
|
|
// The oscillator output combines both: setup during phase 1, countdown during phase 2.
|
|
|
|
|
|
// All state is O(1) via var scalars — no buffers needed.
|
|
|
|
|
|
td_seq(simple int comparePeriod) =>
|
|
|
|
|
|
if comparePeriod <= 0
|
|
|
|
|
|
runtime.error("Compare period must be greater than 0")
|
|
|
|
|
|
|
|
|
|
|
|
var int setupCount = 0
|
|
|
|
|
|
var int countdownCount = 0
|
|
|
|
|
|
var int countdownDir = 0
|
|
|
|
|
|
var bool setupComplete = false
|
|
|
|
|
|
|
|
|
|
|
|
float curClose = nz(close)
|
|
|
|
|
|
float prevClose = nz(close[comparePeriod])
|
|
|
|
|
|
|
|
|
|
|
|
// --- Phase 1: Setup counting ---
|
|
|
|
|
|
int newSetup = 0
|
|
|
|
|
|
if curClose < prevClose
|
|
|
|
|
|
newSetup := setupCount < 0 ? setupCount - 1 : -1
|
|
|
|
|
|
else if curClose > prevClose
|
|
|
|
|
|
newSetup := setupCount > 0 ? setupCount + 1 : 1
|
|
|
|
|
|
else
|
|
|
|
|
|
newSetup := 0
|
|
|
|
|
|
|
|
|
|
|
|
if newSetup > 9
|
|
|
|
|
|
newSetup := 9
|
|
|
|
|
|
if newSetup < -9
|
|
|
|
|
|
newSetup := -9
|
|
|
|
|
|
|
|
|
|
|
|
// --- Check for completed setup (reached ±9) ---
|
|
|
|
|
|
bool justCompleted = false
|
|
|
|
|
|
if math.abs(newSetup) == 9 and not setupComplete
|
|
|
|
|
|
setupComplete := true
|
|
|
|
|
|
justCompleted := true
|
|
|
|
|
|
countdownCount := 0
|
|
|
|
|
|
countdownDir := newSetup > 0 ? 1 : -1
|
|
|
|
|
|
|
|
|
|
|
|
if math.abs(newSetup) < math.abs(setupCount) or (newSetup > 0 and setupCount < 0) or (newSetup < 0 and setupCount > 0)
|
|
|
|
|
|
setupComplete := false
|
|
|
|
|
|
|
|
|
|
|
|
setupCount := newSetup
|
|
|
|
|
|
|
|
|
|
|
|
// --- Phase 2: Countdown (non-consecutive) ---
|
|
|
|
|
|
if countdownDir != 0
|
|
|
|
|
|
float low2 = nz(low[2])
|
|
|
|
|
|
float high2 = nz(high[2])
|
|
|
|
|
|
|
|
|
|
|
|
if countdownDir == -1 and curClose < low2
|
|
|
|
|
|
countdownCount -= 1
|
|
|
|
|
|
else if countdownDir == 1 and curClose > high2
|
|
|
|
|
|
countdownCount += 1
|
|
|
|
|
|
|
|
|
|
|
|
if math.abs(countdownCount) >= 13
|
|
|
|
|
|
countdownCount := countdownDir == 1 ? 13 : -13
|
|
|
|
|
|
countdownDir := 0
|
|
|
|
|
|
|
|
|
|
|
|
if (countdownDir == 1 and newSetup == -9) or (countdownDir == -1 and newSetup == 9)
|
|
|
|
|
|
countdownCount := 0
|
|
|
|
|
|
countdownDir := newSetup > 0 ? 1 : -1
|
|
|
|
|
|
|
|
|
|
|
|
// --- Output: show countdown if active, else setup ---
|
|
|
|
|
|
int result = countdownDir != 0 ? countdownCount : setupCount
|
|
|
|
|
|
[setupCount, countdownCount]
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- Main loop ----------
|
|
|
|
|
|
|
|
|
|
|
|
// Inputs
|
|
|
|
|
|
i_comparePeriod = input.int(4, "Compare Period", minval=1)
|
|
|
|
|
|
|
|
|
|
|
|
// Calculation
|
|
|
|
|
|
[setup, countdown] = td_seq(i_comparePeriod)
|
|
|
|
|
|
|
|
|
|
|
|
// Plot
|
|
|
|
|
|
plot(setup, "Setup", color=color.yellow, linewidth=2, style=plot.style_histogram)
|
|
|
|
|
|
plot(countdown, "Countdown", color=color.aqua, linewidth=1)
|
|
|
|
|
|
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
|
|
|
|
|
|
hline(9, "Sell Setup Complete", color=color.red, linestyle=hline.style_dashed)
|
|
|
|
|
|
hline(-9, "Buy Setup Complete", color=color.green, linestyle=hline.style_dashed)
|
|
|
|
|
|
hline(13, "Sell Countdown", color=color.red, linestyle=hline.style_dotted)
|
|
|
|
|
|
hline(-13, "Buy Countdown", color=color.green, linestyle=hline.style_dotted)
|