Hull MA 2nd Derivative by Mashume work, heading towards back and forward test

daemonx

New member
@mashume
I came across your original Hull MA work about 2 years ago. I was just beginning to learn Thinkscript, but I remember really liking the math that you used, especially with concavity. It was elegant and intellectually intriguing, so it always stuck with me. The Z-score applied to divergence is the indicator's most underappreciated feature. You're normalizing the magnitude of curvature, not price itself, which has cool implications. I'm still an amateur at best in Thinkscript, but getting better. I recently came back to the indicator, and wanted to see if I could apply any enhancements that would help me provide enough guardrails to properly back and forward test your work.

The look ahead bias you originally encoded in MA_Max / MA_Min is an immediate back test dis-qualifier. HMA[-1] peeks one bar into the future. In ToS charting this renders correctly because the chart has already painted future bars, but in any back testing engine this resolves on data that doesn't exist yet. Every MA_Max/MA_Min signal is a phantom. The turning_point logic doesn't have this problem; it only uses concavity[1] and concavity; so signals derived from that path are clean. The fix has to be that true local maxima/minima must be confirmed one bar late, which is a little more "honest" in terms of charting. I also think (and added) another gaurdrail of a session VWAP that disqualifies or validates long or short positions based on distance from that VWAP as an important addition to incorporate into the future testing. I've been working on it for about a week now, and I think I'm pretty close to turning this into a testable script. I mainly trade SPX (using /ES for session VWAP), I think so far that this script is best on a 5 minute interval, so I've tried to tune it to that. Here is a pic of it in action:

Screenshot 2026-03-27 142000.png


I wanted to ask you a few questions if you were game to answer. First, here is my attempt at it:

Code:
#
# Hull MA Concavity System — Commander Deviation Build
# Author: xda3monx (Commander System)
# Version: 2026-03-27 V2
# Based on: Seth Urion / Mashume V4 (2020-05-01)
#
# CHANGES FROM V1:
#  [FIX]  session_start aligned to 945 (was 1100 — inconsistent with lower study)
#  [FIX]  Label 4 unicode
#  [NEW]  Session VWAP plot — RTH-anchored, volume-weighted (upper price overlay)
#  [NEW]  VWAP neutral band plots — optional ±vwap_neutral_band visual reference
#  [NEW]  Three-zone VWAP structural filter — above / neutral / below
#  [NEW]  Dynamic Z threshold — elevated when signal is counter-structure
#  [NEW]  VWAP gate rejection state in Label 4 — diagnostic for blocked signals
#  [NEW]  VWAP zone as Label 5
#
# CHANGES FROM ORIGINAL V4:
#  [FIX]  Lookahead bias in MA_Max/MA_Min removed (backtest-safe)
#  [FIX]  Z-score color logic corrected (OB=Red, OS=Green, intuitive)
#  [FIX]  Divergence normalized to basis points (price-agnostic, SPX-safe)
#  [FIX]  stddev_len and zlength unified — one lookback for both
#  [NEW]  State persistence gate: N bars must hold before signal confirmed
#  [NEW]  Z-score momentum gate: Z must move in signal direction
#  [NEW]  Session time window filter with background highlight
#  [NEW]  Raw turning points (dots) separated from gated entry arrows
#  [NEW]  Exhaustion exit markers (boolean wedges at |Z| > 3.0)
#  [NEW]  Composite status label (State | Z-score | Divergence bp | Gate)
#  [TUNE] HMA_Length default: 55 → 34 (5-min intraday optimized)
#  [TUNE] lookback default: 2 → 3 (less noisy second derivative)
#
# Licensed under GPL v3
# ─────────────────────────────────────────────────────────────────

declare upper;

# ═══════════════════════════════════════════
# INPUTS
# ═══════════════════════════════════════════

input price              = HLC3;
input HMA_Length         = 34;       # 34 bars x 5-min = 170min lookback
input lookback           = 3;        # Second derivative smoothing
input zlength            = 21;       # Unified Z-score + StdDev lookback
input min_z              = 1.2;      # Minimum |Z-score| for valid signal
input persist_bars       = 2;        # New state must hold N bars before signal fires
input show_session       = yes;      # Highlight active trading window
input session_start      = 945;      # Active window open  (ET, 24hr) — matches lower study
input session_end        = 1430;     # Active window close (ET, 24hr)

# VWAP filter inputs
# NOTE: use_vwap_filter must be set to no on SPX cash index (no volume data)
# Valid on /ES, /MES, and any volume-bearing instrument
input use_vwap_filter    = yes;      # Disable on SPX cash — no volume available
input vwap_neutral_band  = 15;       # Points either side of VWAP = structural neutral zone
input vwap_elevated_z    = 2.0;      # Z threshold required when trading counter-structure
input show_vwap_bands    = yes;      # Plot neutral band reference lines on chart

# ═══════════════════════════════════════════
# CORE ENGINE: Hull MA
# Declared as plot — appears as underlay
# beneath 4-state colored segments
# ═══════════════════════════════════════════

plot HMA = HullMovingAvg(price = price, length = HMA_Length);
HMA.SetDefaultColor(Color.DARK_GRAY);
HMA.SetLineWeight(1);
HMA.SetStyle(Curve.SHORT_DASH);

# ═══════════════════════════════════════════
# SECOND DERIVATIVE ENGINE
# delta      = recent slope over lookback bars
# next_bar   = linear projection of that slope
# concavity  = sign of (actual - projected)
#              +1 = bending upward  (concave up)
#              -1 = bending downward (concave down)
# ═══════════════════════════════════════════

def delta         = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;
def next_bar      = HMA[1] + delta_per_bar;
def concavity     = if HMA > next_bar then 1 else -1;

# ═══════════════════════════════════════════
# DIVERGENCE — NORMALIZED (basis points)
# Scaled to price level — instrument-agnostic.
# 1 basis point = 0.01% of current HMA value
# ═══════════════════════════════════════════

def divergence = HMA - next_bar;
def norm_div   = if HMA != 0 then (divergence / HMA) * 10000 else 0;

# ═══════════════════════════════════════════
# Z-SCORE OF NORMALIZED DIVERGENCE
# Measures statistical charge of current curvature
# relative to recent history (zlength bars).
# High |Z| = statistically extreme curvature
# ═══════════════════════════════════════════

def z_mean   = Average(norm_div, zlength);
def z_std    = StDev(norm_div, zlength);
def zscore   = if z_std != 0 then (norm_div - z_mean) / z_std else 0;
def z_rising = zscore > zscore[1];

# ═══════════════════════════════════════════
# STATE MACHINE: 4-STATE CONCAVITY
#
# CCU_I  Concave Up,   Increasing  Green       Strong Bull
# CCU_D  Concave Up,   Decreasing  Dark Green  Bull Waning
# CCD_I  Concave Down, Increasing  Dark Orange Bear Waning
# CCD_D  Concave Down, Decreasing  Red         Strong Bear
#
# Rendering order: HMA underlay declared first,
# colored segments declared after (render on top)
# ═══════════════════════════════════════════

plot CCU_I = if concavity == 1  and HMA >  HMA[1] then HMA else Double.NaN;
CCU_I.SetDefaultColor(Color.GREEN);
CCU_I.SetLineWeight(3);

plot CCU_D = if concavity == 1  and HMA <= HMA[1] then HMA else Double.NaN;
CCU_D.SetDefaultColor(Color.DARK_GREEN);
CCU_D.SetLineWeight(3);

plot CCD_I = if concavity == -1 and HMA >= HMA[1] then HMA else Double.NaN;
CCD_I.SetDefaultColor(Color.DARK_ORANGE);
CCD_I.SetLineWeight(3);

plot CCD_D = if concavity == -1 and HMA <  HMA[1] then HMA else Double.NaN;
CCD_D.SetDefaultColor(Color.RED);
CCD_D.SetLineWeight(3);

# ═══════════════════════════════════════════
# STATE AGE COUNTER
# Counts bars since last concavity flip.
# Recursive def — valid ThinkScript pattern.
# Used for persistence gate below.
# ═══════════════════════════════════════════

def flip     = concavity != concavity[1];
def stateAge = if flip then 1 else stateAge[1] + 1;

# ═══════════════════════════════════════════
# TURNING POINTS — RAW (unfiltered, no lookahead)
# White dots = every concavity flip, no gates.
# Dot-to-arrow ratio is the primary tuning metric.
# ═══════════════════════════════════════════

plot raw_turn = if flip then HMA else Double.NaN;
raw_turn.SetPaintingStrategy(PaintingStrategy.POINTS);
raw_turn.SetDefaultColor(Color.WHITE);
raw_turn.SetLineWeight(2);

# ═══════════════════════════════════════════
# LOCAL PEAKS & TROUGHS — LOOKAHEAD-FREE
# Confirmed one bar after occurrence.
# Visual: marker appears one bar right of peak.
# Backtest-safe — no future data used.
# ═══════════════════════════════════════════

def confirmed_peak   = HMA[1] > HMA[2] and HMA[1] > HMA;
def confirmed_trough = HMA[1] < HMA[2] and HMA[1] < HMA;

plot MA_Max = if confirmed_peak   then HMA[1] else Double.NaN;
MA_Max.SetDefaultColor(Color.WHITE);
MA_Max.SetPaintingStrategy(PaintingStrategy.SQUARES);
MA_Max.SetLineWeight(3);

plot MA_Min = if confirmed_trough then HMA[1] else Double.NaN;
MA_Min.SetDefaultColor(Color.WHITE);
MA_Min.SetPaintingStrategy(PaintingStrategy.TRIANGLES);
MA_Min.SetLineWeight(3);

# ═══════════════════════════════════════════
# SESSION TIME GATE
# Background tint marks active trading window.
# HMA_34 on 5-min needs ~34 bars to stabilize
# (~2.8hrs). session_start = 945 provides
# 15min of warmup before HMA is queried.
# ═══════════════════════════════════════════

def inSession = SecondsFromTime(session_start) >= 0 and
                SecondsTillTime(session_end)   >  0;

AssignBackgroundColor(
    if show_session and inSession
    then CreateColor(15, 22, 15)
    else Color.CURRENT
);

# ═══════════════════════════════════════════
# SESSION VWAP — structural context layer
#
# Anchored to 9:30 RTH open daily.
# Resets via startRTH on first bar at/after 0930.
# IMPORTANT: requires volume data.
#   Valid on:  /ES, /MES, equities
#   Invalid on: SPX cash index (set use_vwap_filter = no)
#
# Three structural zones:
#   vwapZone =  1  Price above VWAP — sellers in control
#   vwapZone =  0  Neutral band — equilibrium, both directions valid
#   vwapZone = -1  Price below VWAP — buyers oversold
#
# Dynamic Z threshold:
#   Trading WITH structure  → min_z (standard gate)
#   Trading AGAINST structure → vwap_elevated_z (raised bar)
# ═══════════════════════════════════════════

def startRTH    = SecondsFromTime(0930) >= 0 and SecondsFromTime(0930)[1] < 0;
rec cumVol      = if startRTH then volume
                  else cumVol[1] + volume;
rec cumVolPrice = if startRTH then volume * HLC3
                  else cumVolPrice[1] + (volume * HLC3);

def sessionVWAP = if cumVol > 0 then cumVolPrice / cumVol else Double.NaN;
def vwapValid   = use_vwap_filter and !IsNaN(sessionVWAP) and
                  SecondsFromTime(0930) >= 0;

def vwapDist    = if vwapValid then close - sessionVWAP else 0;

def vwapZone    = if !vwapValid                          then  0
                  else if vwapDist >  vwap_neutral_band  then  1
                  else if vwapDist < -vwap_neutral_band  then -1
                  else                                         0;

# Dynamic Z thresholds — raised when signal is counter-structure
def longZReq    = if vwapZone ==  1 then vwap_elevated_z else min_z;
def shortZReq   = if vwapZone == -1 then vwap_elevated_z else min_z;

# VWAP line — plots at price level on upper chart
plot VWAP_Line = if vwapValid then sessionVWAP else Double.NaN;
VWAP_Line.SetDefaultColor(Color.VIOLET);
VWAP_Line.SetLineWeight(2);
VWAP_Line.SetStyle(Curve.FIRM);

# Neutral band reference lines — visual zone boundary
plot VWAP_Upper = if show_vwap_bands and vwapValid
                  then sessionVWAP + vwap_neutral_band else Double.NaN;
VWAP_Upper.SetDefaultColor(Color.DARK_GRAY);
VWAP_Upper.SetLineWeight(1);
VWAP_Upper.SetStyle(Curve.SHORT_DASH);

plot VWAP_Lower = if show_vwap_bands and vwapValid
                  then sessionVWAP - vwap_neutral_band else Double.NaN;
VWAP_Lower.SetDefaultColor(Color.DARK_GRAY);
VWAP_Lower.SetLineWeight(1);
VWAP_Lower.SetStyle(Curve.SHORT_DASH);

# ═══════════════════════════════════════════
# GATED ENTRY SIGNALS
#
# Long valid when ALL conditions true:
#   1. Concavity flipped to +1 AND held persist_bars
#   2. Z-score >= longZReq  (min_z or elevated if above VWAP)
#   3. Z-score is rising    (curvature building, not decaying)
#   4. Inside session window
#
# Short valid when: mirror conditions with shortZReq
# ═══════════════════════════════════════════

def confirmedBull = concavity == 1  and stateAge == persist_bars;
def confirmedBear = concavity == -1 and stateAge == persist_bars;

def longSignal  = confirmedBull and zscore >= longZReq  and  z_rising and inSession;
def shortSignal = confirmedBear and zscore <= -shortZReq and !z_rising and inSession;

# VWAP gate diagnostic — signal almost fired but VWAP elevated the threshold
def longVWAPBlocked  = confirmedBull and z_rising   and inSession
                       and zscore >= min_z and zscore <  longZReq;
def shortVWAPBlocked = confirmedBear and !z_rising  and inSession
                       and zscore <= -min_z and zscore > -shortZReq;

# ═══════════════════════════════════════════
# EXHAUSTION EXIT WARNINGS
# |Z| > 3.0 = statistically overextended.
# Fires ONCE on threshold crossing.
# SCALE-OUT / EXIT signal — not entry.
# ═══════════════════════════════════════════

def bullExhaust     = concavity == 1  and zscore >  3;
def bearExhaust     = concavity == -1 and zscore < -3;
def bullExhaustEdge = bullExhaust  and !bullExhaust[1];
def bearExhaustEdge = bearExhaust  and !bearExhaust[1];

plot BullExitWarn = if bullExhaustEdge then 1 else Double.NaN;
BullExitWarn.SetDefaultColor(Color.YELLOW);
BullExitWarn.SetPaintingStrategy(PaintingStrategy.BOOLEAN_WEDGE_DOWN);
BullExitWarn.SetLineWeight(3);

plot BearExitWarn = if bearExhaustEdge then 1 else Double.NaN;
BearExitWarn.SetDefaultColor(Color.YELLOW);
BearExitWarn.SetPaintingStrategy(PaintingStrategy.BOOLEAN_WEDGE_UP);
BearExitWarn.SetLineWeight(3);

# ═══════════════════════════════════════════
# SIGNAL ARROWS — GATED ENTRIES ONLY
# Cyan  arrow up   = Long  (all gates passed)
# Orange arrow dn  = Short (all gates passed)
# ═══════════════════════════════════════════

plot BuyArrow = if longSignal  then low  else Double.NaN;
BuyArrow.SetDefaultColor(Color.CYAN);
BuyArrow.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
BuyArrow.SetLineWeight(3);

plot SellArrow = if shortSignal then high else Double.NaN;
SellArrow.SetDefaultColor(Color.DARK_ORANGE);
SellArrow.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
SellArrow.SetLineWeight(3);

# ═══════════════════════════════════════════
# COMPOSITE STATUS LABELS
#
# Label 1: Concavity state
# Label 2: Z-score + color zone
# Label 3: Normalized divergence (bp)
# Label 4: Gate status / active signal / VWAP block
# Label 5: VWAP structural zone
# ═══════════════════════════════════════════

# Label 1: Concavity state
AddLabel(yes,
    if   concavity == 1  and HMA >  HMA[1] then "CCU_I ++"
    else if concavity == 1  and HMA <= HMA[1] then "CCU_D +-"
    else if concavity == -1 and HMA >= HMA[1] then "CCD_I -+"
    else                                           "CCD_D --",
    if   concavity == 1  and HMA >  HMA[1] then Color.GREEN
    else if concavity == 1  and HMA <= HMA[1] then Color.DARK_GREEN
    else if concavity == -1 and HMA >= HMA[1] then Color.DARK_ORANGE
    else Color.RED
);

# Label 2: Z-score — OB warm (yellow to red), OS cool (cyan to green)
AddLabel(yes,
    "Z: " + Round(zscore, 2),
    if   zscore >  3.0 then Color.RED
    else if zscore >  2.0 then Color.ORANGE
    else if zscore >  1.2 then Color.YELLOW
    else if zscore < -3.0 then Color.GREEN
    else if zscore < -2.0 then Color.DARK_GREEN
    else if zscore < -1.2 then Color.CYAN
    else Color.GRAY
);

# Label 3: Normalized divergence in basis points
AddLabel(yes,
    "D: " + Round(norm_div, 1) + "bp",
    if norm_div > norm_div[1] and concavity == 1  then Color.GREEN
    else if norm_div < norm_div[1] and concavity == 1  then Color.DARK_GREEN
    else if norm_div < norm_div[1] and concavity == -1 then Color.RED
    else Color.DARK_ORANGE
);

# Label 4: Gate status — priority ordered, VWAP blocks now surfaced
# [FIX] Replaced  (Tier 3) and ◆ (Tier 2) with ASCII-safe characters
AddLabel(yes,
    if      longSignal          then "▲ LONG ENTRY"
    else if shortSignal         then "▼ SHORT ENTRY"
    else if bullExhaust         then "[!] BULL EXHAUST"
    else if bearExhaust         then "[!] BEAR EXHAUST"
    else if longVWAPBlocked     then "▲ VWAP GATE"
    else if shortVWAPBlocked    then "▼ VWAP GATE"
    else if !inSession          then "[.] OFF-WINDOW"
    else if confirmedBull and zscore < min_z  then "▲ Z LOW"
    else if confirmedBull and !z_rising       then "▲ Z FADING"
    else if confirmedBear and zscore > -min_z then "▼ Z LOW"
    else if confirmedBear and z_rising        then "▼ Z FADING"
    else                                           "-- STANDBY",
    if      longSignal          then Color.CYAN
    else if shortSignal         then Color.DARK_ORANGE
    else if bullExhaust or
            bearExhaust         then Color.YELLOW
    else if longVWAPBlocked     then Color.BLUE
    else if shortVWAPBlocked    then Color.DARK_ORANGE
    else if !inSession          then Color.DARK_GRAY
    else if confirmedBull       then Color.DARK_GREEN
    else if confirmedBear       then Color.DARK_ORANGE
    else Color.DARK_GRAY
);

# Label 5: VWAP structural zone
AddLabel(use_vwap_filter,
    "VWAP: " +
    (if   !vwapValid         then "INIT"
     else if vwapZone ==  1  then "ABOVE +" + Round(vwapDist, 0) + "pt"
     else if vwapZone == -1  then "BELOW "  + Round(vwapDist, 0) + "pt"
     else                         "NEAR +"  + Round(AbsValue(vwapDist), 0) + "pt"),
    if   !vwapValid         then Color.DARK_GRAY
    else if vwapZone ==  1  then Color.DARK_ORANGE
    else if vwapZone == -1  then Color.CYAN
    else Color.WHITE
);

# ═══════════════════════════════════════════
# ALERTS
# ═══════════════════════════════════════════

Alert(condition = longSignal,      text = "HMA-C: Long Entry (Gated)",      "alert type" = Alert.BAR, sound = Sound.Bell);
Alert(condition = shortSignal,     text = "HMA-C: Short Entry (Gated)",     "alert type" = Alert.BAR, sound = Sound.Chimes);
Alert(condition = bullExhaustEdge, text = "HMA-C: Bull Exhaustion — Exit",  "alert type" = Alert.BAR, sound = Sound.Ding);
Alert(condition = bearExhaustEdge, text = "HMA-C: Bear Exhaustion — Exit",  "alert type" = Alert.BAR, sound = Sound.Ding);
Alert(condition = MA_Max,          text = "HMA-C: Local Peak Confirmed",    "alert type" = Alert.BAR, sound = Sound.NoSound);
Alert(condition = MA_Min,          text = "HMA-C: Local Trough Confirmed",  "alert type" = Alert.BAR, sound = Sound.NoSound);

My questions are (I made an assumption that you have tested or observed/recorded in some fashion):

1. What instrument and timeframe did you originally design and test V4 against?
2. Was the Z-score gate a core design element or added later, and what threshold did you find produced the highest signal-to-noise ratio?
3. What was the intended interpretation of the lookback parameter; smoothing convenience or a specific physics metaphor?
4. On what market regimes did you observe it fail most consistently?
5. Did you ever test persist_bars > 1 and what effect did it have on signal quality versus latency?
6. Did the normalized basis point divergence calculation behave differently at extreme price levels?
7. Was there a specific reason you chose HullMovingAvg over DEMA or TEMA for the concavity calculation?
8. The lookahead bias in MA_Max/MA_Min; were you aware of it, and was it intentional for visual smoothness on non-backtested charts?
9. I unified stddev_len and zlength into a single parameter. Do you see a reason they should remain separate in certain configurations?
10. As mentioned earlier, I added a VWAP structural filter that elevates the Z threshold when price is significantly away from session VWAP. Does this conflict with how you envisioned the Z-score functioning as a standalone signal?
11. Probably the most meta and important question for me is: In all your use of this indicator, what was the one condition that produced the most reliable signal and the one condition that produced the most dangerous false signal?

Sorry, I know this is a lot, and I don't expect anything, thanks in advance for your original work, please let me know if you have any questions.
 
@mashume
I came across your original Hull MA work about 2 years ago. I was just beginning to learn Thinkscript, but I remember really liking the math that you used, especially with concavity. It was elegant and intellectually intriguing, so it always stuck with me. The Z-score applied to divergence is the indicator's most underappreciated feature. You're normalizing the magnitude of curvature, not price itself, which has cool implications. I'm still an amateur at best in Thinkscript, but getting better. I recently came back to the indicator, and wanted to see if I could apply any enhancements that would help me provide enough guardrails to properly back and forward test your work.

The look ahead bias you originally encoded in MA_Max / MA_Min is an immediate back test dis-qualifier. HMA[-1] peeks one bar into the future. In ToS charting this renders correctly because the chart has already painted future bars, but in any back testing engine this resolves on data that doesn't exist yet. Every MA_Max/MA_Min signal is a phantom. The turning_point logic doesn't have this problem; it only uses concavity[1] and concavity; so signals derived from that path are clean. The fix has to be that true local maxima/minima must be confirmed one bar late, which is a little more "honest" in terms of charting. I also think (and added) another gaurdrail of a session VWAP that disqualifies or validates long or short positions based on distance from that VWAP as an important addition to incorporate into the future testing. I've been working on it for about a week now, and I think I'm pretty close to turning this into a testable script. I mainly trade SPX (using /ES for session VWAP), I think so far that this script is best on a 5 minute interval, so I've tried to tune it to that. Here is a pic of it in action:

View attachment 27281

I wanted to ask you a few questions if you were game to answer. First, here is my attempt at it:

Code:
#
# Hull MA Concavity System — Commander Deviation Build
# Author: xda3monx (Commander System)
# Version: 2026-03-27 V2
# Based on: Seth Urion / Mashume V4 (2020-05-01)
#
# CHANGES FROM V1:
#  [FIX]  session_start aligned to 945 (was 1100 — inconsistent with lower study)
#  [FIX]  Label 4 unicode
#  [NEW]  Session VWAP plot — RTH-anchored, volume-weighted (upper price overlay)
#  [NEW]  VWAP neutral band plots — optional ±vwap_neutral_band visual reference
#  [NEW]  Three-zone VWAP structural filter — above / neutral / below
#  [NEW]  Dynamic Z threshold — elevated when signal is counter-structure
#  [NEW]  VWAP gate rejection state in Label 4 — diagnostic for blocked signals
#  [NEW]  VWAP zone as Label 5
#
# CHANGES FROM ORIGINAL V4:
#  [FIX]  Lookahead bias in MA_Max/MA_Min removed (backtest-safe)
#  [FIX]  Z-score color logic corrected (OB=Red, OS=Green, intuitive)
#  [FIX]  Divergence normalized to basis points (price-agnostic, SPX-safe)
#  [FIX]  stddev_len and zlength unified — one lookback for both
#  [NEW]  State persistence gate: N bars must hold before signal confirmed
#  [NEW]  Z-score momentum gate: Z must move in signal direction
#  [NEW]  Session time window filter with background highlight
#  [NEW]  Raw turning points (dots) separated from gated entry arrows
#  [NEW]  Exhaustion exit markers (boolean wedges at |Z| > 3.0)
#  [NEW]  Composite status label (State | Z-score | Divergence bp | Gate)
#  [TUNE] HMA_Length default: 55 → 34 (5-min intraday optimized)
#  [TUNE] lookback default: 2 → 3 (less noisy second derivative)
#
# Licensed under GPL v3
# ─────────────────────────────────────────────────────────────────

declare upper;

# ═══════════════════════════════════════════
# INPUTS
# ═══════════════════════════════════════════

input price              = HLC3;
input HMA_Length         = 34;       # 34 bars x 5-min = 170min lookback
input lookback           = 3;        # Second derivative smoothing
input zlength            = 21;       # Unified Z-score + StdDev lookback
input min_z              = 1.2;      # Minimum |Z-score| for valid signal
input persist_bars       = 2;        # New state must hold N bars before signal fires
input show_session       = yes;      # Highlight active trading window
input session_start      = 945;      # Active window open  (ET, 24hr) — matches lower study
input session_end        = 1430;     # Active window close (ET, 24hr)

# VWAP filter inputs
# NOTE: use_vwap_filter must be set to no on SPX cash index (no volume data)
# Valid on /ES, /MES, and any volume-bearing instrument
input use_vwap_filter    = yes;      # Disable on SPX cash — no volume available
input vwap_neutral_band  = 15;       # Points either side of VWAP = structural neutral zone
input vwap_elevated_z    = 2.0;      # Z threshold required when trading counter-structure
input show_vwap_bands    = yes;      # Plot neutral band reference lines on chart

# ═══════════════════════════════════════════
# CORE ENGINE: Hull MA
# Declared as plot — appears as underlay
# beneath 4-state colored segments
# ═══════════════════════════════════════════

plot HMA = HullMovingAvg(price = price, length = HMA_Length);
HMA.SetDefaultColor(Color.DARK_GRAY);
HMA.SetLineWeight(1);
HMA.SetStyle(Curve.SHORT_DASH);

# ═══════════════════════════════════════════
# SECOND DERIVATIVE ENGINE
# delta      = recent slope over lookback bars
# next_bar   = linear projection of that slope
# concavity  = sign of (actual - projected)
#              +1 = bending upward  (concave up)
#              -1 = bending downward (concave down)
# ═══════════════════════════════════════════

def delta         = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;
def next_bar      = HMA[1] + delta_per_bar;
def concavity     = if HMA > next_bar then 1 else -1;

# ═══════════════════════════════════════════
# DIVERGENCE — NORMALIZED (basis points)
# Scaled to price level — instrument-agnostic.
# 1 basis point = 0.01% of current HMA value
# ═══════════════════════════════════════════

def divergence = HMA - next_bar;
def norm_div   = if HMA != 0 then (divergence / HMA) * 10000 else 0;

# ═══════════════════════════════════════════
# Z-SCORE OF NORMALIZED DIVERGENCE
# Measures statistical charge of current curvature
# relative to recent history (zlength bars).
# High |Z| = statistically extreme curvature
# ═══════════════════════════════════════════

def z_mean   = Average(norm_div, zlength);
def z_std    = StDev(norm_div, zlength);
def zscore   = if z_std != 0 then (norm_div - z_mean) / z_std else 0;
def z_rising = zscore > zscore[1];

# ═══════════════════════════════════════════
# STATE MACHINE: 4-STATE CONCAVITY
#
# CCU_I  Concave Up,   Increasing  Green       Strong Bull
# CCU_D  Concave Up,   Decreasing  Dark Green  Bull Waning
# CCD_I  Concave Down, Increasing  Dark Orange Bear Waning
# CCD_D  Concave Down, Decreasing  Red         Strong Bear
#
# Rendering order: HMA underlay declared first,
# colored segments declared after (render on top)
# ═══════════════════════════════════════════

plot CCU_I = if concavity == 1  and HMA >  HMA[1] then HMA else Double.NaN;
CCU_I.SetDefaultColor(Color.GREEN);
CCU_I.SetLineWeight(3);

plot CCU_D = if concavity == 1  and HMA <= HMA[1] then HMA else Double.NaN;
CCU_D.SetDefaultColor(Color.DARK_GREEN);
CCU_D.SetLineWeight(3);

plot CCD_I = if concavity == -1 and HMA >= HMA[1] then HMA else Double.NaN;
CCD_I.SetDefaultColor(Color.DARK_ORANGE);
CCD_I.SetLineWeight(3);

plot CCD_D = if concavity == -1 and HMA <  HMA[1] then HMA else Double.NaN;
CCD_D.SetDefaultColor(Color.RED);
CCD_D.SetLineWeight(3);

# ═══════════════════════════════════════════
# STATE AGE COUNTER
# Counts bars since last concavity flip.
# Recursive def — valid ThinkScript pattern.
# Used for persistence gate below.
# ═══════════════════════════════════════════

def flip     = concavity != concavity[1];
def stateAge = if flip then 1 else stateAge[1] + 1;

# ═══════════════════════════════════════════
# TURNING POINTS — RAW (unfiltered, no lookahead)
# White dots = every concavity flip, no gates.
# Dot-to-arrow ratio is the primary tuning metric.
# ═══════════════════════════════════════════

plot raw_turn = if flip then HMA else Double.NaN;
raw_turn.SetPaintingStrategy(PaintingStrategy.POINTS);
raw_turn.SetDefaultColor(Color.WHITE);
raw_turn.SetLineWeight(2);

# ═══════════════════════════════════════════
# LOCAL PEAKS & TROUGHS — LOOKAHEAD-FREE
# Confirmed one bar after occurrence.
# Visual: marker appears one bar right of peak.
# Backtest-safe — no future data used.
# ═══════════════════════════════════════════

def confirmed_peak   = HMA[1] > HMA[2] and HMA[1] > HMA;
def confirmed_trough = HMA[1] < HMA[2] and HMA[1] < HMA;

plot MA_Max = if confirmed_peak   then HMA[1] else Double.NaN;
MA_Max.SetDefaultColor(Color.WHITE);
MA_Max.SetPaintingStrategy(PaintingStrategy.SQUARES);
MA_Max.SetLineWeight(3);

plot MA_Min = if confirmed_trough then HMA[1] else Double.NaN;
MA_Min.SetDefaultColor(Color.WHITE);
MA_Min.SetPaintingStrategy(PaintingStrategy.TRIANGLES);
MA_Min.SetLineWeight(3);

# ═══════════════════════════════════════════
# SESSION TIME GATE
# Background tint marks active trading window.
# HMA_34 on 5-min needs ~34 bars to stabilize
# (~2.8hrs). session_start = 945 provides
# 15min of warmup before HMA is queried.
# ═══════════════════════════════════════════

def inSession = SecondsFromTime(session_start) >= 0 and
                SecondsTillTime(session_end)   >  0;

AssignBackgroundColor(
    if show_session and inSession
    then CreateColor(15, 22, 15)
    else Color.CURRENT
);

# ═══════════════════════════════════════════
# SESSION VWAP — structural context layer
#
# Anchored to 9:30 RTH open daily.
# Resets via startRTH on first bar at/after 0930.
# IMPORTANT: requires volume data.
#   Valid on:  /ES, /MES, equities
#   Invalid on: SPX cash index (set use_vwap_filter = no)
#
# Three structural zones:
#   vwapZone =  1  Price above VWAP — sellers in control
#   vwapZone =  0  Neutral band — equilibrium, both directions valid
#   vwapZone = -1  Price below VWAP — buyers oversold
#
# Dynamic Z threshold:
#   Trading WITH structure  → min_z (standard gate)
#   Trading AGAINST structure → vwap_elevated_z (raised bar)
# ═══════════════════════════════════════════

def startRTH    = SecondsFromTime(0930) >= 0 and SecondsFromTime(0930)[1] < 0;
rec cumVol      = if startRTH then volume
                  else cumVol[1] + volume;
rec cumVolPrice = if startRTH then volume * HLC3
                  else cumVolPrice[1] + (volume * HLC3);

def sessionVWAP = if cumVol > 0 then cumVolPrice / cumVol else Double.NaN;
def vwapValid   = use_vwap_filter and !IsNaN(sessionVWAP) and
                  SecondsFromTime(0930) >= 0;

def vwapDist    = if vwapValid then close - sessionVWAP else 0;

def vwapZone    = if !vwapValid                          then  0
                  else if vwapDist >  vwap_neutral_band  then  1
                  else if vwapDist < -vwap_neutral_band  then -1
                  else                                         0;

# Dynamic Z thresholds — raised when signal is counter-structure
def longZReq    = if vwapZone ==  1 then vwap_elevated_z else min_z;
def shortZReq   = if vwapZone == -1 then vwap_elevated_z else min_z;

# VWAP line — plots at price level on upper chart
plot VWAP_Line = if vwapValid then sessionVWAP else Double.NaN;
VWAP_Line.SetDefaultColor(Color.VIOLET);
VWAP_Line.SetLineWeight(2);
VWAP_Line.SetStyle(Curve.FIRM);

# Neutral band reference lines — visual zone boundary
plot VWAP_Upper = if show_vwap_bands and vwapValid
                  then sessionVWAP + vwap_neutral_band else Double.NaN;
VWAP_Upper.SetDefaultColor(Color.DARK_GRAY);
VWAP_Upper.SetLineWeight(1);
VWAP_Upper.SetStyle(Curve.SHORT_DASH);

plot VWAP_Lower = if show_vwap_bands and vwapValid
                  then sessionVWAP - vwap_neutral_band else Double.NaN;
VWAP_Lower.SetDefaultColor(Color.DARK_GRAY);
VWAP_Lower.SetLineWeight(1);
VWAP_Lower.SetStyle(Curve.SHORT_DASH);

# ═══════════════════════════════════════════
# GATED ENTRY SIGNALS
#
# Long valid when ALL conditions true:
#   1. Concavity flipped to +1 AND held persist_bars
#   2. Z-score >= longZReq  (min_z or elevated if above VWAP)
#   3. Z-score is rising    (curvature building, not decaying)
#   4. Inside session window
#
# Short valid when: mirror conditions with shortZReq
# ═══════════════════════════════════════════

def confirmedBull = concavity == 1  and stateAge == persist_bars;
def confirmedBear = concavity == -1 and stateAge == persist_bars;

def longSignal  = confirmedBull and zscore >= longZReq  and  z_rising and inSession;
def shortSignal = confirmedBear and zscore <= -shortZReq and !z_rising and inSession;

# VWAP gate diagnostic — signal almost fired but VWAP elevated the threshold
def longVWAPBlocked  = confirmedBull and z_rising   and inSession
                       and zscore >= min_z and zscore <  longZReq;
def shortVWAPBlocked = confirmedBear and !z_rising  and inSession
                       and zscore <= -min_z and zscore > -shortZReq;

# ═══════════════════════════════════════════
# EXHAUSTION EXIT WARNINGS
# |Z| > 3.0 = statistically overextended.
# Fires ONCE on threshold crossing.
# SCALE-OUT / EXIT signal — not entry.
# ═══════════════════════════════════════════

def bullExhaust     = concavity == 1  and zscore >  3;
def bearExhaust     = concavity == -1 and zscore < -3;
def bullExhaustEdge = bullExhaust  and !bullExhaust[1];
def bearExhaustEdge = bearExhaust  and !bearExhaust[1];

plot BullExitWarn = if bullExhaustEdge then 1 else Double.NaN;
BullExitWarn.SetDefaultColor(Color.YELLOW);
BullExitWarn.SetPaintingStrategy(PaintingStrategy.BOOLEAN_WEDGE_DOWN);
BullExitWarn.SetLineWeight(3);

plot BearExitWarn = if bearExhaustEdge then 1 else Double.NaN;
BearExitWarn.SetDefaultColor(Color.YELLOW);
BearExitWarn.SetPaintingStrategy(PaintingStrategy.BOOLEAN_WEDGE_UP);
BearExitWarn.SetLineWeight(3);

# ═══════════════════════════════════════════
# SIGNAL ARROWS — GATED ENTRIES ONLY
# Cyan  arrow up   = Long  (all gates passed)
# Orange arrow dn  = Short (all gates passed)
# ═══════════════════════════════════════════

plot BuyArrow = if longSignal  then low  else Double.NaN;
BuyArrow.SetDefaultColor(Color.CYAN);
BuyArrow.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
BuyArrow.SetLineWeight(3);

plot SellArrow = if shortSignal then high else Double.NaN;
SellArrow.SetDefaultColor(Color.DARK_ORANGE);
SellArrow.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
SellArrow.SetLineWeight(3);

# ═══════════════════════════════════════════
# COMPOSITE STATUS LABELS
#
# Label 1: Concavity state
# Label 2: Z-score + color zone
# Label 3: Normalized divergence (bp)
# Label 4: Gate status / active signal / VWAP block
# Label 5: VWAP structural zone
# ═══════════════════════════════════════════

# Label 1: Concavity state
AddLabel(yes,
    if   concavity == 1  and HMA >  HMA[1] then "CCU_I ++"
    else if concavity == 1  and HMA <= HMA[1] then "CCU_D +-"
    else if concavity == -1 and HMA >= HMA[1] then "CCD_I -+"
    else                                           "CCD_D --",
    if   concavity == 1  and HMA >  HMA[1] then Color.GREEN
    else if concavity == 1  and HMA <= HMA[1] then Color.DARK_GREEN
    else if concavity == -1 and HMA >= HMA[1] then Color.DARK_ORANGE
    else Color.RED
);

# Label 2: Z-score — OB warm (yellow to red), OS cool (cyan to green)
AddLabel(yes,
    "Z: " + Round(zscore, 2),
    if   zscore >  3.0 then Color.RED
    else if zscore >  2.0 then Color.ORANGE
    else if zscore >  1.2 then Color.YELLOW
    else if zscore < -3.0 then Color.GREEN
    else if zscore < -2.0 then Color.DARK_GREEN
    else if zscore < -1.2 then Color.CYAN
    else Color.GRAY
);

# Label 3: Normalized divergence in basis points
AddLabel(yes,
    "D: " + Round(norm_div, 1) + "bp",
    if norm_div > norm_div[1] and concavity == 1  then Color.GREEN
    else if norm_div < norm_div[1] and concavity == 1  then Color.DARK_GREEN
    else if norm_div < norm_div[1] and concavity == -1 then Color.RED
    else Color.DARK_ORANGE
);

# Label 4: Gate status — priority ordered, VWAP blocks now surfaced
# [FIX] Replaced  (Tier 3) and ◆ (Tier 2) with ASCII-safe characters
AddLabel(yes,
    if      longSignal          then "▲ LONG ENTRY"
    else if shortSignal         then "▼ SHORT ENTRY"
    else if bullExhaust         then "[!] BULL EXHAUST"
    else if bearExhaust         then "[!] BEAR EXHAUST"
    else if longVWAPBlocked     then "▲ VWAP GATE"
    else if shortVWAPBlocked    then "▼ VWAP GATE"
    else if !inSession          then "[.] OFF-WINDOW"
    else if confirmedBull and zscore < min_z  then "▲ Z LOW"
    else if confirmedBull and !z_rising       then "▲ Z FADING"
    else if confirmedBear and zscore > -min_z then "▼ Z LOW"
    else if confirmedBear and z_rising        then "▼ Z FADING"
    else                                           "-- STANDBY",
    if      longSignal          then Color.CYAN
    else if shortSignal         then Color.DARK_ORANGE
    else if bullExhaust or
            bearExhaust         then Color.YELLOW
    else if longVWAPBlocked     then Color.BLUE
    else if shortVWAPBlocked    then Color.DARK_ORANGE
    else if !inSession          then Color.DARK_GRAY
    else if confirmedBull       then Color.DARK_GREEN
    else if confirmedBear       then Color.DARK_ORANGE
    else Color.DARK_GRAY
);

# Label 5: VWAP structural zone
AddLabel(use_vwap_filter,
    "VWAP: " +
    (if   !vwapValid         then "INIT"
     else if vwapZone ==  1  then "ABOVE +" + Round(vwapDist, 0) + "pt"
     else if vwapZone == -1  then "BELOW "  + Round(vwapDist, 0) + "pt"
     else                         "NEAR +"  + Round(AbsValue(vwapDist), 0) + "pt"),
    if   !vwapValid         then Color.DARK_GRAY
    else if vwapZone ==  1  then Color.DARK_ORANGE
    else if vwapZone == -1  then Color.CYAN
    else Color.WHITE
);

# ═══════════════════════════════════════════
# ALERTS
# ═══════════════════════════════════════════

Alert(condition = longSignal,      text = "HMA-C: Long Entry (Gated)",      "alert type" = Alert.BAR, sound = Sound.Bell);
Alert(condition = shortSignal,     text = "HMA-C: Short Entry (Gated)",     "alert type" = Alert.BAR, sound = Sound.Chimes);
Alert(condition = bullExhaustEdge, text = "HMA-C: Bull Exhaustion — Exit",  "alert type" = Alert.BAR, sound = Sound.Ding);
Alert(condition = bearExhaustEdge, text = "HMA-C: Bear Exhaustion — Exit",  "alert type" = Alert.BAR, sound = Sound.Ding);
Alert(condition = MA_Max,          text = "HMA-C: Local Peak Confirmed",    "alert type" = Alert.BAR, sound = Sound.NoSound);
Alert(condition = MA_Min,          text = "HMA-C: Local Trough Confirmed",  "alert type" = Alert.BAR, sound = Sound.NoSound);

My questions are (I made an assumption that you have tested or observed/recorded in some fashion):

1. What instrument and timeframe did you originally design and test V4 against?
2. Was the Z-score gate a core design element or added later, and what threshold did you find produced the highest signal-to-noise ratio?
3. What was the intended interpretation of the lookback parameter; smoothing convenience or a specific physics metaphor?
4. On what market regimes did you observe it fail most consistently?
5. Did you ever test persist_bars > 1 and what effect did it have on signal quality versus latency?
6. Did the normalized basis point divergence calculation behave differently at extreme price levels?
7. Was there a specific reason you chose HullMovingAvg over DEMA or TEMA for the concavity calculation?
8. The lookahead bias in MA_Max/MA_Min; were you aware of it, and was it intentional for visual smoothness on non-backtested charts?
9. I unified stddev_len and zlength into a single parameter. Do you see a reason they should remain separate in certain configurations?
10. As mentioned earlier, I added a VWAP structural filter that elevates the Z threshold when price is significantly away from session VWAP. Does this conflict with how you envisioned the Z-score functioning as a standalone signal?
11. Probably the most meta and important question for me is: In all your use of this indicator, what was the one condition that produced the most reliable signal and the one condition that produced the most dangerous false signal?

Sorry, I know this is a lot, and I don't expect anything, thanks in advance for your original work, please let me know if you have any questions.
@daemonx
Wow. I'm flattered that anyone is still looking at work I did 6 years ago (has it really been that long?!?).
To try to answer some of your questions:
1. I think I was workin on this mostly on 5min and 30 min timeframes. It has applicaiton on day, but it was intended for intraday. I traded mainly futures contracts at the time, so it was likely tested against /es and /cl and maybe /6e
2. The z-score came about shortly after the initial idea. Certainly before the indicator came to the form I released. I don't think it was there from the very beginning, though it made sense to me, when looking at how quickly things were changing (rate of change) to look at how that rate compared to other rates. I thiknn that's what I had going on at any rate.
3. lookback was initially my attempt at limiting or adjusting the scope of the local minima maxima calculation.. .the longer it was, the les slikely you were to be at a min/max (local of course)... as a filter agianst jitter.
4. It hink it did far less well on ranging markets as /6e often is when compared to things like the linear regression channel position indicators I worked on later.
5. noper
6. ? hmmm
7. Yes there was. I appreciated the smootheness of the transitions. it's a very sinusoidal wave at most times and worked out well for this one. I think I read an article on it at some point, but that was ages and ages ago.
...
11. I think the biggest flaw was trying to use it alone. it is a good indicator, and I stand by the mathematics (including many of the problems of repainting -- i know I'll be hung for that one, but as a leading indicator "hey! something may be happening you should look" I think it's a marvelous tool). It is more of an alert when combined with other things in my book now. How I felt about it 6+ years ago is as relevant as the close of a futures contract that expired in '16. :)


I'm around if you have other questions. I put all this up around here in the hopes that it would make others think, enjoy trading, and appreciate some alternative views on technical analysis. You've certainly picked up on this one... run with it!

-mashume
 
@daemonx
Wow. I'm flattered that anyone is still looking at work I did 6 years ago (has it really been that long?!?).
To try to answer some of your questions:
1. I think I was workin on this mostly on 5min and 30 min timeframes. It has applicaiton on day, but it was intended for intraday. I traded mainly futures contracts at the time, so it was likely tested against /es and /cl and maybe /6e
2. The z-score came about shortly after the initial idea. Certainly before the indicator came to the form I released. I don't think it was there from the very beginning, though it made sense to me, when looking at how quickly things were changing (rate of change) to look at how that rate compared to other rates. I thiknn that's what I had going on at any rate.
3. lookback was initially my attempt at limiting or adjusting the scope of the local minima maxima calculation.. .the longer it was, the les slikely you were to be at a min/max (local of course)... as a filter agianst jitter.
4. It hink it did far less well on ranging markets as /6e often is when compared to things like the linear regression channel position indicators I worked on later.
5. noper
6. ? hmmm
7. Yes there was. I appreciated the smootheness of the transitions. it's a very sinusoidal wave at most times and worked out well for this one. I think I read an article on it at some point, but that was ages and ages ago.
...
11. I think the biggest flaw was trying to use it alone. it is a good indicator, and I stand by the mathematics (including many of the problems of repainting -- i know I'll be hung for that one, but as a leading indicator "hey! something may be happening you should look" I think it's a marvelous tool). It is more of an alert when combined with other things in my book now. How I felt about it 6+ years ago is as relevant as the close of a futures contract that expired in '16. :)


I'm around if you have other questions. I put all this up around here in the hopes that it would make others think, enjoy trading, and appreciate some alternative views on technical analysis. You've certainly picked up on this one... run with it!

-mashume
Trying to contain my thoughts here because I'm excited you replied and I've had the time to run a proper back test and have recently started a forward test. Going to try and keep this as relevant as possible, but my mind is racing with what to share with you, lol.

First, Thank you, genuinely; for taking the time to respond. Six years ago is longer than most indicators last before being abandoned, the mathematics you developed hold up under rigorous testing and is a testament to the soundness of the original thinking.

Your answers are REALLY helpful.

On timeframes and instruments: 5-min on /ES is what I settled on after experimenting with 3-min. The confirmation that this was your primary testing ground removes a calibration uncertainty I had been carrying. /6E makes sense too; and your observation that it under performed.

On the Z-score: Your description: "how quickly things are changing compared to other rates"; is a clean description of normalized second derivative, which is the mathematical framing I've been using. Because you arrived at it organically from watching the signal behavior rather than from a statistical framework actually increases my confidence in it.

On lookback as jitter filter: This was a genuine correction for me. I had framed it as a "second derivative smoothing window" in my implementation comments, which is mathematically consistent but misrepresents your original intent. I've noted this to preserve both interpretations: your original intent (local min/max scope control, anti-jitter) and the emergent physics reading (curvature smoothing). They don't contradict, but your framing is the primary one and it's now in the code.

On ranging markets failing: This was the answer I needed most, it validated something I built independently: I added the session VWAP structural filter that elevates the Z-score entry threshold when price is significantly above or below the session VWAP. What I didn't realize until you answered is that this filter is essentially a ranging market detector. When price is oscillating around VWAP without structural direction, Zone 0 triggers; and the backtest confirms Zone 0 entries average -3.73 pts at 44% win rate. Your /6E ranging observation and my Zone 0 data are describing the same failure mode from opposite ends. The filter is working for the right reason.

On lookahead in MA_Max/MA_Min: The comment in my implementation now explicitly preserves your intent. You designed a leading alert tool, I adjusted it for backtest integrity but documented that the leading behavior is preserved in the Z-score and concavity signals, which are the operationally meaningful components anyway.

On HullMovingAvg: The sinusoidal quality is precisely why the concavity calculation works as well as it does. I tested this empirically; on a noisier MA the second derivative produces spurious flips that the persist_bars gate barely contains. The HMA's smooth transitions are load-bearing for this approach.

I ran a canonical backtest: 1,093 trading sessions, January 2022 through March 2026, 83,877 five-minute bars on /ES. The system I tested is your core mathematics (HMA concavity, normalized basis-point divergence, Z-score gate) plus the VWAP structural filter I mentioned, plus a state persistence gate. No machine learning, no curve fitting; deterministic physics throughout.

899 trades. 55.4% win rate. +1,812 net points over four years.

More importantly: when the signal produces an active exit; meaning the Hull concavity flipped before end of day: win rate is 81.1% across 301 trades. Your mathematics, working exactly as you designed them, produce a statistically significant edge in the regime where they have theoretical validity.

The drag on the system is entirely positional: 598 of 899 trades (66.5%) didn't develop before the session close and were exited flat, averaging -3.62 pts. That is not a signal problem. That is a position lifecycle problem — entries that fired correctly but didn't have enough session time to develop. The optimization work ahead is about entry timing cutoffs and intraday trailing stops, not about touching your mathematics.

The VWAP filter I added is, as best I can tell, a regime selector rather than a signal corrector. It identifies the conditions under which your core math has structural validity. Zone -1 (below VWAP, both directions) produces 62.5% win rate on longs and 59.0% on shorts at ~4.7 average points. Those numbers belong to your indicator. The filter just identifies when to listen to it.

Your answer to Question 11: "the biggest flaw was trying to use it alone", I've now built the architectural answer to that: the indicator runs as a plugin inside a host framework that applies regime and volatility context before acting on any signal. It has a companion regime/volatility system that handles the "other things" you may be referencing. The combination is what I'm currently forward-testing.

Live forward test status: approximately 130 sessions of observation, 4 paper trades taken, 3 winners and 1 loser (75% WR, net +$1,380 gross). Small sample, but the signal mechanics are behaving exactly as the backtest predicted.

One follow-up question, the one I most want your perspective on:

In your later work, what did "combined with other things" actually mean in practice? Not in the abstract; specifically, what types of signals or contextual reads did you find complemented this indicator's outputs? Were you looking at breadth, volume structure, volatility state, something else entirely? The back test tells me when the signal fires correctly; what I'm still calibrating is which external conditions you found most predictive of whether a signal that fired would develop. Your linear regression channel work came up in passing; did that provide the context layer that made this more actionable?

The mathematics hold. You built something real. Thank you for putting it out there.
 

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
1009 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top