mtf_wavetrend_opportunity_hunter.pine
Copy
//@version=6
// ============================================================================
// MTF WaveTrend Opportunity Hunter [WavesUnchained]
// Version: 2.7.0
// Author: WavesUnchained
// Build: 2026-10-02 00:00:00
// ============================================================================
// MTF confluence pane around WaveTrend — shows WHY a signal fires (or
// doesn't) over time via a net confluence histogram plus per-layer heat
// ribbons, and fires on a slim three-condition gate instead of stacked
// score thresholds.
//
// Features:
// ✓ Confluence pane: net score histogram (EMA/WMA/HMA) + raw column
// layer + heat ribbons for Regime / Opportunity / Timing / Quality /
// Noise / Context — the "why (no) signal" view over time
// ✓ Context TF ribbon and dashboard row — weekly (or custom) directional
// bias shown as sixth ribbon and dashboard row; purely informational,
// never a gate condition
// ✓ Confluence Rotation Map — RRG-style quadrant inset (Leading /
// Improving / Weakening / Lagging) with polyline trail of the net score
// ✓ Ultimate Smoother core (Ehlers, TASC 04/2024) — optional zero-lag
// replacement for the EMA core of WaveTrend
// ✓ Entropy noise floor — Shannon sign-entropy percentile rank as fifth
// ribbon and Quality component (self-calibrating per instrument)
// ✓ Entry/exit-decoupled stop-and-reverse: ENTRIES fire on a WT zone-cross
// only (the entry trigger); an open position is closed EARLY to flat by
// an opposite turn/exhaustion HOOK (profit-take at the extreme), then flat
// until the next cross re-enters. A cross reverses outright. Hooks act as
// exits only, crosses as entries
// ✓ Non-blocking tradeable flag: opportunity ≥ threshold marks the
// high-opportunity subset (★, full colour) vs watch (·, dimmed)
// — the gate no longer hides signals, it only grades them
// ✓ Live layer panel measures whether the tradeable subset differs from
// watch signals (Trade✓ vs Watch avg R) on numbers, not by eye
// ✓ Situation panel: avg R per situational bucket (HTF slope, drift,
// stretch from the 4H mean, volatility) split Long / Short
// ✓ Compact on-chart signal labels (dir · ★/· · opportunity, full breakdown
// in the tooltip) — no fixed TP/SL boxes; the exit is dynamic (the
// opposite signal), so static targets would misrepresent it
// ✓ Three-tier MTF framework: Regime (D) → Setup (4H) → Trigger (1H)
// ✓ Signals fire on any chart TF; sampled-trigger warning when chart ≠
// Trigger TF
// ✓ Exit warnings gated to active trades — no noise outside of signals
// ✓ Per-side dashboard with ARMED / SETUP / NEAR / BIAS / REGIME states
// ✓ Pivot reference control overlay (ta.pivothigh/low) — markers, dotted
// pivot→signal connector and bar/ATR distance/proximity/knife flag in
// the label to review signal accuracy; never gates or triggers a signal
// ✓ Rich WT oscillator visualization: gradient line color (OS → OB),
// shadow + momentum fills, OB/OS zone fills, and cross dots for cadence
//
// ============================================================================
indicator("MTF WaveTrend Opportunity Hunter [WavesUnchained]", format=format.price, precision=2, overlay=false, max_labels_count=500, max_lines_count=100, max_boxes_count=100, max_polylines_count=10)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Inputs
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
g_tf = "Timeframes"
tfRegime = input.timeframe("D", "Regime TF", group=g_tf)
tfSetup = input.timeframe("240", "Setup TF", group=g_tf)
tfTrigger = input.timeframe("60", "Trigger TF", group=g_tf)
g_ctx = "Context TF (Directional Bias — Info Only)"
tfContext = input.timeframe("W", "Context TF", group=g_ctx)
showContextLayer = input.bool(true, "Show Context Ribbon + Dashboard Row", group=g_ctx,
tooltip="Weekly (or custom) directional bias. Never a gate — signals fire regardless of context. Use it to stay aware of the higher-order trend.")
g_wt = "WaveTrend"
wtChLen = input.int(10, "WT Channel Length", minval=1, group=g_wt)
wtAvgLen = input.int(21, "WT Average Length", minval=1, group=g_wt)
wtSigLen = input.int(4, "WT Signal Length", minval=1, group=g_wt)
wtExtreme = input.float(55.0, "WT Extreme Level", minval=10, group=g_wt)
useJmaCore = input.bool(true, "EMA + JMA Hybrid Core", group=g_wt,
tooltip="Keeps EMA as the WaveTrend base and blends in JMA smoothing. More stable than replacing the core with a zero-lag filter.")
jmaCoreBlend = input.float(0.35, "JMA Blend", minval=0.0, maxval=1.0, step=0.05, group=g_wt,
tooltip="0 = pure EMA, 1 = JMA-smoothed EMA. 0.25-0.45 is usually the useful range for volatile markets.")
useUltimate = input.bool(false, "Ultimate Smoother Core (Ehlers 2024)", group=g_wt,
tooltip="Replaces the EMA core of WaveTrend with John Ehlers' Ultimate Smoother (TASC 04/2024) — zero lag in the passband, earlier crosses at the same length.")
g_trend = "Trend / Regime"
emaFastLen = input.int(50, "EMA Fast", minval=1, group=g_trend)
emaSlowLen = input.int(200, "EMA Slow", minval=1, group=g_trend)
adxLen = input.int(14, "ADX Length", minval=1, group=g_trend)
adxMin = input.float(18.0, "Min ADX", minval=1, group=g_trend)
g_jma = "JMA Pullback Smoothing"
jmaLen = input.int(34, "JMA Length", minval=1, group=g_jma)
jmaPhase = input.int(50, "JMA Phase", minval=-100, maxval=100, group=g_jma)
jmaPower = input.float(2.0, "JMA Power", minval=1.0, maxval=4.0, step=0.25, group=g_jma)
g_quality = "Quality"
erLen = input.int(20, "Efficiency Ratio Length", minval=2, group=g_quality)
atrLen = input.int(14, "ATR Length", minval=1, group=g_quality)
entropyLen = input.int(40, "Entropy Window (Trigger TF)", minval=10, group=g_quality,
tooltip="Shannon sign-entropy over this many Trigger-TF bars. Low entropy = directional tape, high entropy = noise.")
entropyRankLen = input.int(200, "Entropy Rank Lookback", minval=50, group=g_quality,
tooltip="Percentile-rank window — makes the noise floor self-calibrating per instrument.")
useRS = input.bool(false, "Use Relative Strength Filter", group=g_quality)
benchmark = input.symbol("SPY", "Benchmark Symbol", group=g_quality)
g_signal = "Signal Engine"
minRegime = input.float(65, "Min Regime Score", minval=0, maxval=100, group=g_signal)
minOpportunity = input.float(35, "Min Opportunity Score", minval=0, maxval=100, group=g_signal)
minOppSignal = input.float(40, "Tradeable Opportunity ≥", minval=0, maxval=100, group=g_signal,
tooltip="Marks a signal 'tradeable' (★) when its 4H opportunity is at least this. It does NOT block: every WT reversal is drawn so nothing is hidden — this only flags the high-opportunity subset. The Trade✓ / Watch rows of the layer panel measure whether that subset actually differs.")
gateBuffer = input.float(10, "Gate Buffer", minval=0, maxval=30, group=g_signal,
tooltip="Softens Regime/Opportunity gates. A value of 10 means a 65 threshold arms from 55 upward; the dashboard still shows the raw score.")
nearSetupBuffer = input.float(10, "Near Setup Buffer", minval=0, maxval=30, group=g_signal,
tooltip="Marks a near setup when Regime passes and Opportunity is within this many points of the effective Opportunity gate.")
cooldownBars = input.int(8, "Minimum Hold (bars)", minval=1, maxval=30, group=g_signal,
tooltip="Minimum bars a position is held before the opposite WT trigger may reverse it. Prevents whipsaw flip-flopping when the WT oscillates fast. Higher = holds through minor counter-crosses; lower = reverses sooner.")
wtCrossZone = input.float(0.0, "WT Cross Zone Limit", minval=-40, maxval=40, step=5, group=g_signal,
tooltip="ENTRY trigger. Long entry only fires when the WT signal line was BELOW this value before the cross (0 = must come from negative territory, 20 = also accept mid-range continuation crosses up to +20). Short mirrors at -value. Higher = more entries incl. momentum continuation in the range; lower = only deep zero-line reversals. Above +~30 starts buying into overbought. Entries are always crosses — the validated entry trigger.")
useHookExit = input.bool(false, "Early Exit on Turn/Exhaustion Hook", group=g_signal,
tooltip="Decouples exit from entry. Entries fire on a WT CROSS; an open position is then closed EARLY to flat by an opposite TURN or EXHAUSTION hook out of the extreme (a profit-take at the top/bottom), and stays flat until the next cross re-enters. The data showed cross entries carry the edge while turn/exhaustion are good EXITS but bad entries — this pairs the two. Off = pure stop-and-reverse (a position is held until the opposite cross reverses it).")
triggerTurnLevel = input.float(35.0, "WT Turn Level (hook exit)", minval=5, maxval=100, group=g_signal,
tooltip="Hook-exit sensitivity. A bear hook (closes a long) needs WT above +level and hooking down; a bull hook (closes a short) needs WT below -level and hooking up. Lower = exits earlier on shallower extremes (more, earlier exits).")
turnFallBars = input.int(2, "WT Turn Min Falling Bars", minval=1, maxval=10, group=g_signal,
tooltip="A hook only counts if WT had been rising (bear hook) / falling (bull hook) for at least this many bars into the extreme before it hooks back. Higher = stricter, fewer false hooks; lower = earlier but noisier. The hook fires on the turn itself, decoupled from the lagging histogram.")
useExhaustion = input.bool(true, "Exhaustion Hook (deep-extreme exit)", group=g_signal,
tooltip="Adds a second hook-exit path: closes an open position the moment WT1 hooks back from a deep extreme (|WT1| ≥ Exhaustion Level) — the sharp top/bottom. Narrow by design (only at the extremes). Combined with the Turn hook above as the early-exit trigger.")
exhaustLevel = input.float(50.0, "Exhaustion WT Level", minval=20, maxval=80, step=5, group=g_signal,
tooltip="WT1 must have reached at least this far into overbought / oversold on the prior bar before the hook counts as an exhaustion exit. Higher = only the most extreme tops/bottoms; lower = also milder extremes (more, earlier exits).")
g_mode = "Mode"
allowLong = input.bool(true, "Allow Long Signals", group=g_mode)
allowShort = input.bool(true, "Allow Short Signals", group=g_mode)
g_rot = "Confluence Rotation Map"
showRotation = input.bool(false, "Show Rotation Map", group=g_rot,
tooltip="RRG-style quadrant inset right of the last bar: x = net confluence, y = confluence momentum. Leading / Improving / Weakening / Lagging.")
rotMomLen = input.int(8, "Momentum Lookback", minval=1, maxval=30, group=g_rot)
rotSmooth = input.int(5, "Rotation Smoothing", minval=1, maxval=20, group=g_rot,
tooltip="EMA smoothing of both rotation axes — like real RRGs, the path is meaningless without it.")
rotStep = input.int(2, "Trail Sampling (bars)", minval=1, maxval=10, group=g_rot)
rotTrail = input.int(24, "Trail Points", minval=5, maxval=100, group=g_rot)
g_vis = "Visuals"
histSmooth = input.int(3, "Histogram Smoothing", minval=1, maxval=20, group=g_vis,
tooltip="Smoothing length applied to the net confluence line. 1 = raw. The smoothed line overlays raw columns.")
histSmoothType = input.string("EMA", "Histogram Smooth Type", options=["EMA", "WMA", "HMA"], group=g_vis,
tooltip="EMA = classic exponential. WMA = linearly-weighted (faster response). HMA (Hull) = low-lag, nearly no overshoot.")
showRibbons = input.bool(true, "Show Layer Heat Ribbons", group=g_vis)
showBg = input.bool(true, "Show Regime Background", group=g_vis)
showDashboard = input.bool(true, "Show Dashboard", group=g_vis)
showStats = input.bool(true, "Show Signal Stats", group=g_vis,
tooltip="Live performance panel over the visible chart history — signal count, win rate, average R per closed position, and failed %. Lets you tune the signal knobs against numbers right here on the 1H chart. R is measured vs the fresh entry (re-entries averaged in are ignored).")
showTfWarning = input.bool(false, "Show Lower-TF Sampling Warning", group=g_vis,
tooltip="Warns when Setup or Trigger TF is below the chart TF. request.security then samples lower-TF values only once per chart bar, so intrabar crosses can be missed.")
labelMode = input.string("Compact", "Signal Label Detail", options=["Compact", "Full", "None"], group=g_vis,
tooltip="Compact = a small tag (dir · ★/· tradeable · opportunity); Full = all scores + quality breakdown on the chart; None = no on-chart label (pane triangle only). The full breakdown is always in the label's tooltip on hover.")
showWtShadow = input.bool(true, "Show WT Shadow Fill", group=g_vis,
tooltip="Gradient fill between WT1 and the zero line — bull tint below, bear tint above. Very transparent so it does not obscure the confluence histogram.")
showWtFill = input.bool(true, "Show WT / Signal Fill", group=g_vis,
tooltip="Tint between WT1 and WT2 — bull when WT1 > WT2, bear when below. Shows momentum split at a glance.")
showWtZones = input.bool(true, "Show WT OB/OS Zone Fill", group=g_vis,
tooltip="Light zone fills between WT Extreme Level (±55 default) and the hard clamp (±80). Marks the overbought / oversold territory on the oscillator.")
showWtDots = input.bool(true, "Show WT Cross Dots", group=g_vis,
tooltip="Small dots on the WT1 line at every WT cross. Entry signals show as the larger triangle; all other crosses show as dots. Lets you see oscillator cadence and which crosses did not produce a signal.")
g_fail = "Failed Signals (study)"
markFailed = input.bool(true, "Mark Failed Signals", group=g_fail,
tooltip="Recolours a signal's tag once price trades the 'adverse ATR' distance against its entry (where the old stop sat) — so you can see at a glance which entries went wrong.")
failAtrMult = input.float(1.2, "Failed = adverse ATR×", minval=0.3, maxval=5.0, step=0.1, group=g_fail,
tooltip="A signal counts as failed once price moves this many ATR against its entry. 1.2 matches the previous fixed stop distance.")
failColor = input.color(#29b6f6, "Failed Marker Color", group=g_fail,
tooltip="Colour for failed signal tags and debug labels (light blue by default; orange #ff6f00 is the obvious alternative).")
showFailDebug = input.bool(true, "Failed Debug Label", group=g_fail,
tooltip="Prints a larger label at each failed signal with debug values — most importantly the count of WT crosses in the window before entry. A high count means the entry sat in a choppy multi-cross zone (the 'too early' pattern), which helps decide how to push the entry later.")
failCrossLookback = input.int(10, "Failed Debug: Cross Lookback (bars)", minval=3, maxval=40, group=g_fail,
tooltip="Window before the entry over which the debug label counts WT crosses. High count = choppy zone = likely too-early entry.")
g_piv = "Pivot Reference (Control Overlay — Never a Gate)"
showPivots = input.bool(true, "Show Pivot Markers", group=g_piv,
tooltip="Confirmed swing highs/lows on the price chart (ta.pivothigh/low). Pure control overlay — never influences which signals fire. Use it to judge how close a signal landed to the actual turn.")
pivLeftBars = input.int(5, "Pivot Left Bars", minval=1, group=g_piv)
pivRightBars = input.int(5, "Pivot Right Bars", minval=1, group=g_piv,
tooltip="Bars of confirmation. The marker prints this many bars after the pivot — the same lag the eye needs to call a swing.")
showPivDist = input.bool(true, "Show Pivot → Signal Distance", group=g_piv,
tooltip="Dotted connector from the current swing extreme to the entry. Long is measured against the swing low, short against the swing high. Bar/ATR distance and proximity also printed in the signal label — diagnostic only, small distance = signal fired close to the swing.")
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Helpers
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
clamp(x, lo, hi) =>
math.max(lo, math.min(hi, x))
scoreBool(cond, pts) =>
cond ? pts : 0.0
f_jma(src, length, phase, power) =>
var float jma = na
var float e0 = na
var float e1 = na
var float e2 = na
_pr = phase < -100 ? 0.5 : phase > 100 ? 2.5 : phase / 100.0 + 1.5
_beta = 0.45 * (length - 1) / (0.45 * (length - 1) + 2)
_alpha = math.pow(_beta, power)
e0 := (1 - _alpha) * src + _alpha * nz(e0[1], src)
e1 := (src - e0) * (1 - _beta) + _beta * nz(e1[1], 0)
e2 := (e0 + _pr * e1 - nz(jma[1], src)) * math.pow(1 - _alpha, 2) + math.pow(_alpha, 2) * nz(e2[1], 0)
jma := nz(jma[1], src) + e2
jma
// Ehlers Ultimate Smoother (TASC 04/2024) — zero lag in the passband
f_ultimateSmoother(src, period) =>
f = 1.414 * math.pi / period
a1 = math.exp(-f)
c2 = 2 * a1 * math.cos(f)
c3 = -a1 * a1
c1 = (1 - c2 - c3) / 4
var float us = na
us := (1 - c1) * src + (2 * c1 - c2) * nz(src[1], src) - (c1 + c3) * nz(src[2], src) + c2 * nz(us[1], src) + c3 * nz(us[2], src)
us
// EMA remains the stable base. JMA is blended on top; Ultimate can still
// replace the core when explicitly enabled.
f_smoothCore(src, len, useUS, useJma, blend, phase, power) =>
e = ta.ema(src, len)
j = f_jma(e, len, phase, power)
u = f_ultimateSmoother(src, len)
h = e * (1.0 - blend) + j * blend
useUS ? u : useJma ? h : e
waveTrend(src, chLen, avgLen, sigLen, useUS, useJma, blend, phase, power) =>
esa = f_smoothCore(src, chLen, useUS, useJma, blend, phase, power)
deRaw = f_smoothCore(math.abs(src - esa), chLen, useUS, useJma, blend, phase, power)
de = math.max(deRaw, 0.0)
ci = de > 0 ? (src - esa) / (0.015 * de) : 0.0
wt1 = f_smoothCore(ci, avgLen, useUS, useJma, blend, phase, power)
wt2 = ta.sma(wt1, sigLen)
[wt1, wt2, wt1 - wt2]
efficiencyRatio(src, len) =>
change = math.abs(src - src[len])
volatility = ta.sma(math.abs(src - src[1]), len) * len
volatility != 0 ? change / volatility : 0.0
adxValue(len) =>
[_, _, adx] = ta.dmi(len, len)
adx
// Shannon sign-entropy: 1.0 = coin flip (noise), → 0 = one-sided tape
f_signEntropy(len) =>
int ups = 0
for i = 0 to len - 1
if close[i] > close[i + 1]
ups += 1
p = ups / float(len)
float h = 1.0
if p <= 0.0 or p >= 1.0
h := 0.0
else
h := -(p * math.log(p) + (1 - p) * math.log(1 - p)) / math.log(2)
h
// Hull MA (low-lag, minimal overshoot)
f_hma(src, len) =>
half = math.max(len / 2, 1)
sqLen = math.max(int(math.sqrt(float(len))), 1)
ta.wma(2.0 * ta.wma(src, half) - ta.wma(src, len), sqLen)
// Selectable histogram smoother
f_smoothHist(src, len, typ) =>
if typ == "HMA"
f_hma(src, len)
else if typ == "WMA"
ta.wma(src, len)
else
ta.ema(src, len)
// Heat color for a net layer score (-100..+100): green bull, red bear, gray neutral
f_heatColor(net) =>
color c = color.new(color.gray, 72)
if not na(net)
if net >= 15
c := color.from_gradient(net, 15, 70, color.new(#00c853, 65), color.new(#00c853, 0))
else if net <= -15
c := color.from_gradient(net, -70, -15, color.new(#d50000, 0), color.new(#d50000, 65))
c
// Noise color: green = unusually clean tape, orange = unusually noisy
f_noiseColor(rank) =>
color c = color.new(color.gray, 72)
if not na(rank)
if rank <= 30
c := color.from_gradient(rank, 0, 30, color.new(#00c853, 0), color.new(#00c853, 65))
else if rank >= 70
c := color.from_gradient(rank, 70, 100, color.new(#ff6f00, 65), color.new(#ff6f00, 0))
c
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// MTF Data
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[dWT1, dWT2, dHist] = request.security(syminfo.tickerid, tfRegime, waveTrend(hlc3, wtChLen, wtAvgLen, wtSigLen, useUltimate, useJmaCore, jmaCoreBlend, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
dClose = request.security(syminfo.tickerid, tfRegime, close, barmerge.gaps_off, barmerge.lookahead_off)
dEMA50 = request.security(syminfo.tickerid, tfRegime, ta.ema(close, emaFastLen), barmerge.gaps_off, barmerge.lookahead_off)
dEMA200 = request.security(syminfo.tickerid, tfRegime, ta.ema(close, emaSlowLen), barmerge.gaps_off, barmerge.lookahead_off)
dADX = request.security(syminfo.tickerid, tfRegime, adxValue(adxLen), barmerge.gaps_off, barmerge.lookahead_off)
[h4WT1, h4WT2, h4Hist] = request.security(syminfo.tickerid, tfSetup, waveTrend(hlc3, wtChLen, wtAvgLen, wtSigLen, useUltimate, useJmaCore, jmaCoreBlend, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
h4Close = request.security(syminfo.tickerid, tfSetup, close, barmerge.gaps_off, barmerge.lookahead_off)
h4ATR = request.security(syminfo.tickerid, tfSetup, ta.atr(atrLen), barmerge.gaps_off, barmerge.lookahead_off)
h4JMA = request.security(syminfo.tickerid, tfSetup, f_jma(close, jmaLen, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
[h1WT1, h1WT2, h1Hist] = request.security(syminfo.tickerid, tfTrigger, waveTrend(hlc3, wtChLen, wtAvgLen, wtSigLen, useUltimate, useJmaCore, jmaCoreBlend, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
h1Close = request.security(syminfo.tickerid, tfTrigger, close, barmerge.gaps_off, barmerge.lookahead_off)
h1JMA = request.security(syminfo.tickerid, tfTrigger, f_jma(close, jmaLen, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
h1ER = request.security(syminfo.tickerid, tfTrigger, efficiencyRatio(close, erLen), barmerge.gaps_off, barmerge.lookahead_off)
h1ATR = request.security(syminfo.tickerid, tfTrigger, ta.atr(atrLen), barmerge.gaps_off, barmerge.lookahead_off)
h1Entropy = request.security(syminfo.tickerid, tfTrigger, f_signEntropy(entropyLen), barmerge.gaps_off, barmerge.lookahead_off)
// Context TF — directional bias only, never a gate
[ctxWT1, ctxWT2, _ctxHist] = request.security(syminfo.tickerid, tfContext, waveTrend(hlc3, wtChLen, wtAvgLen, wtSigLen, useUltimate, useJmaCore, jmaCoreBlend, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
ctxClose = request.security(syminfo.tickerid, tfContext, close, barmerge.gaps_off, barmerge.lookahead_off)
ctxEMA50 = request.security(syminfo.tickerid, tfContext, ta.ema(close, emaFastLen), barmerge.gaps_off, barmerge.lookahead_off)
ctxEMA200 = request.security(syminfo.tickerid, tfContext, ta.ema(close, emaSlowLen), barmerge.gaps_off, barmerge.lookahead_off)
benchClose = request.security(benchmark, tfRegime, close, barmerge.gaps_off, barmerge.lookahead_off)
rsLine = useRS and benchClose != 0 ? dClose / benchClose : na
rsSlope = useRS ? ta.linreg(rsLine, 10, 0) - ta.linreg(rsLine, 10, 1) : 0.0
chartTfSec = timeframe.in_seconds(timeframe.period)
setupTfSec = timeframe.in_seconds(tfSetup)
triggerTfSec = timeframe.in_seconds(tfTrigger)
setupTfBelowChart = not na(chartTfSec) and not na(setupTfSec) and setupTfSec < chartTfSec
triggerTfBelowChart = not na(chartTfSec) and not na(triggerTfSec) and triggerTfSec < chartTfSec
tfSamplingWarning = setupTfBelowChart or triggerTfBelowChart
tfSamplingScope = setupTfBelowChart and triggerTfBelowChart ? "Setup + Trigger TF" : setupTfBelowChart ? "Setup TF" : "Trigger TF"
tfSamplingText = "Chart " + timeframe.period + " > " + tfSamplingScope + ": lower-TF data is sampled once per chart bar; intrabar WT crosses/signals can be missed."
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Regime Score
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
trendBull = dClose > dEMA50 and dEMA50 > dEMA200
trendBear = dClose < dEMA50 and dEMA50 < dEMA200
adxOk = dADX >= adxMin
bullRegime =
scoreBool(dWT1 > dWT2, 25) +
scoreBool(dWT1 > dWT1[1], 20) +
scoreBool(trendBull, 30) +
scoreBool(adxOk, 15) +
scoreBool(dHist > dHist[1], 10)
bearRegime =
scoreBool(dWT1 < dWT2, 25) +
scoreBool(dWT1 < dWT1[1], 20) +
scoreBool(trendBear, 30) +
scoreBool(adxOk, 15) +
scoreBool(dHist < dHist[1], 10)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Opportunity Score
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
h4DistAtr = h4ATR != 0 ? (h4Close - h4JMA) / h4ATR : 0.0
// ── Situation context for the measurement panel (diagnostics only, never gates a signal) ──
// All four are recorded at entry and read RELATIVE to the trade direction, so the buckets stay
// symmetric: a long and a short in the mirrored situation land in the same bucket.
f_jmaSlope(src, length, phase, power) =>
float j = f_jma(src, length, phase, power)
j - nz(j[1], j)
h4JmaSlope = request.security(syminfo.tickerid, tfSetup, f_jmaSlope(close, jmaLen, jmaPhase, jmaPower), barmerge.gaps_off, barmerge.lookahead_off)
sitDriftLen = input.int(100, "Situation: Drift Lookback (bars)", minval=20, maxval=500, group=g_vis,
tooltip="Measurement only. Net price change over this many chart bars at entry. The Situation panel splits trades into 'with drift' and 'against drift' to show whether the long/short asymmetry just mirrors the market's net direction.")
sitStretchAtr = input.float(1.0, "Situation: Stretch Threshold (4H ATR)", minval=0.2, maxval=5.0, step=0.1, group=g_vis,
tooltip="Measurement only. Distance of the 4H close from the 4H JMA, in 4H ATR, read relative to the trade: 'Room' = entering from the far side of the mean (long below / short above by at least this), 'Extended' = already this far on the trade's side, 'Near' = in between.")
sitDrift = close - nz(close[sitDriftLen], close)
// 25 at JMA, 100 at 1.5-ATR pullback, 0 from +0.5 ATR adverse extension onward
longPullbackScore = clamp((-h4DistAtr + 0.5) / 2.0 * 100.0, 0, 100)
shortPullbackScore = clamp((h4DistAtr + 0.5) / 2.0 * 100.0, 0, 100)
longWtExtremeScore =
clamp((-h4WT1 / wtExtreme) * 100.0, 0, 100)
shortWtExtremeScore =
clamp((h4WT1 / wtExtreme) * 100.0, 0, 100)
longOpportunity =
longPullbackScore * 0.45 +
longWtExtremeScore * 0.35 +
scoreBool(h4WT1 > h4WT1[1], 20)
shortOpportunity =
shortPullbackScore * 0.45 +
shortWtExtremeScore * 0.35 +
scoreBool(h4WT1 < h4WT1[1], 20)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Timing Score (display context — the gate itself is the WT trigger)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Clamp before cross detection so that detected crosses match the plotted lines.
// Without this, crosses in the clamped "flat zone" (|WT| > 80) fire signals
// with no visible cross in the pane.
plotWT1 = clamp(h1WT1, -80, 80)
plotWT2 = clamp(h1WT2, -80, 80)
h1BullCross = ta.crossover(plotWT1, plotWT2)
h1BearCross = ta.crossunder(plotWT1, plotWT2)
// WT turn = WT1 hooks out of the extreme on the bar it ticks back, after a confirmed
// falling/rising run into the zone. Decoupled from the histogram (hist lags the WT1 low
// by 1-2 bars), so the turn fires at the hook — closer to the actual swing.
h1BullTurnRaw = h1WT1 < -triggerTurnLevel and h1WT1 > h1WT1[1] and h1WT1[1] < h1WT1[1 + turnFallBars]
h1BearTurnRaw = h1WT1 > triggerTurnLevel and h1WT1 < h1WT1[1] and h1WT1[1] > h1WT1[1 + turnFallBars]
h1BullTurn = h1BullTurnRaw and not h1BullTurnRaw[1]
h1BearTurn = h1BearTurnRaw and not h1BearTurnRaw[1]
// Exhaustion reversal: WT1 was at a deep extreme on the prior bar and hooks back now — the
// sharp top/bottom turn. Narrow (only at |WT1| ≥ exhaustLevel) so it catches the big
// exhaustion reversals a lagging core detects too late, without flooding the mid-range.
h1BearExhaustRaw = useExhaustion and h1WT1[1] >= exhaustLevel and h1WT1 < h1WT1[1]
h1BullExhaustRaw = useExhaustion and h1WT1[1] <= -exhaustLevel and h1WT1 > h1WT1[1]
h1BearExhaust = h1BearExhaustRaw and not h1BearExhaustRaw[1]
h1BullExhaust = h1BullExhaustRaw and not h1BullExhaustRaw[1]
// Zone filter: cross must emerge from the correct side (WT signal line was below zone for long, above for short)
rawBullCross = h1BullCross and h1WT2[1] < wtCrossZone
rawBearCross = h1BearCross and h1WT2[1] > -wtCrossZone
// ── Entry vs exit triggers (decoupled) ──────────────────────────────────────────────────
// ENTRY = the zone-filtered WT cross only — the entry trigger (cross entries carry the
// edge; turn/exhaustion entries were knife-catches). HOOK = a turn or exhaustion hook out of
// the extreme — repurposed as an early profit-take EXIT (good at the top/bottom, bad as entry).
bullEntryTrig = rawBullCross
bearEntryTrig = rawBearCross
bullHook = useHookExit and (h1BullTurn or h1BullExhaust) // hook up from a low → closes a short
bearHook = useHookExit and (h1BearTurn or h1BearExhaust) // hook down from a high → closes a long
// Any bull/bear WT event — used only for the Timing score + dashboard "WT Trigger" display
h1BullTrigger = rawBullCross or h1BullTurn or h1BullExhaust
h1BearTrigger = rawBearCross or h1BearTurn or h1BearExhaust
// Directional WT extreme over the last few bars — how stretched the zone the cross emerged
// from was. A long entry from a deep oversold (wtLow5 ≪ 0) is a real reversal cross; one from
// the mid-range (wtLow5 near 0) is a shallow continuation cross — the prime fail candidate.
// This is the entry-quality criterion captured at the cross and shown on every signal label.
wtLow5 = ta.lowest(h1WT1, 5) // most oversold recently (long entries read this)
wtHigh5 = ta.highest(h1WT1, 5) // most overbought recently (short entries read this)
h1BullMomentum = h1WT1 > h1WT1[1] and h1Hist > h1Hist[1]
h1BearMomentum = h1WT1 < h1WT1[1] and h1Hist < h1Hist[1]
h1BullStructure = h1Close > h1JMA and h1JMA > h1JMA[1]
h1BearStructure = h1Close < h1JMA and h1JMA < h1JMA[1]
// Setup-TF WT direction (h4WT1 rising/falling) is scored in Opportunity only — not
// repeated here. The three Trigger-TF components are rescaled 35/30/25 → 39/33/28 so
// Timing still tops out at 100.
longTiming =
scoreBool(h1BullTrigger, 39) +
scoreBool(h1BullMomentum, 33) +
scoreBool(h1BullStructure, 28)
shortTiming =
scoreBool(h1BearTrigger, 39) +
scoreBool(h1BearMomentum, 33) +
scoreBool(h1BearStructure, 28)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Quality Score (label diagnostic only — does not block or grade signals)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// JMA slope computed first — used for acceleration and direction-sensitive ER below
jmaSlope5 = ta.linreg(h1JMA, 5, 0) - ta.linreg(h1JMA, 5, 1)
jmaSlope10 = ta.linreg(h1JMA, 10, 0) - ta.linreg(h1JMA, 10, 1)
// Continuous acceleration: how strong is slope5 pulling away from slope10, as a self-
// calibrating z-score (÷ its own 50-bar stdev). Replaces the old binary accel > / < step,
// so a strong momentum thrust at the reversal scores higher than a marginal one.
accelMag = jmaSlope5 - jmaSlope10
accelStdev = ta.stdev(accelMag, 50)
accelNorm = accelStdev > 0 ? accelMag / accelStdev : 0.0
longAccelScore = clamp(accelNorm, 0, 2) / 2.0 * 15.0
shortAccelScore = clamp(-accelNorm, 0, 2) / 2.0 * 15.0
// Direction-sensitive ER: high efficiency ratio during a counter-trend move is NOT
// evidence of quality for the signal direction. Penalize ER when the 1H JMA trend
// opposes the signal (e.g. strong downtrend → ER penalized for longs, not for shorts).
erScoreRaw = clamp(h1ER * 100.0, 0, 100)
longERScore = jmaSlope5 > 0 ? erScoreRaw : erScoreRaw * 0.35
shortERScore = jmaSlope5 < 0 ? erScoreRaw : erScoreRaw * 0.35
// Continuous vol: a gradient that peaks in the healthy ATR band and falls off toward dead
// (too quiet) and wild (too volatile) tape — replaces the old binary 75/35 step.
volSma = ta.sma(h1ATR, 20)
volRatio = volSma > 0 ? h1ATR / volSma : 1.0
float volScore = 50.0
if volRatio < 0.6
volScore := 25.0
else if volRatio < 1.0
volScore := 25.0 + (volRatio - 0.6) / 0.4 * 55.0
else if volRatio <= 1.4
volScore := 80.0
else if volRatio <= 2.2
volScore := 80.0 - (volRatio - 1.4) / 0.8 * 45.0
else
volScore := 30.0
// RS only contributes when enabled. When off (default), its 15% is NOT spent on a flat
// neutral 50 (dead weight that compressed every score toward the middle) — it is handed to
// entropy, which is direction-NEUTRAL. (Not to ER: ER is direction-penalised ×0.35 against
// the trend, so weighting it more would amplify the counter-trend penalty and starve longs
// in a downtrend / shorts in an uptrend. Entropy keeps the reclaim symmetric.)
rsBullScore = scoreBool(rsSlope > 0, 100)
rsBearScore = scoreBool(rsSlope < 0, 100)
erW = 0.30
entW = useRS ? 0.20 : 0.35
rsW = useRS ? 0.15 : 0.0
// Entropy noise floor — percentile rank makes it self-calibrating
entropyRank = ta.percentrank(h1Entropy, entropyRankLen)
entropyScore = 100.0 - entropyRank
longQuality =
longERScore * erW +
volScore * 0.20 +
rsBullScore * rsW +
longAccelScore +
entropyScore * entW
shortQuality =
shortERScore * erW +
volScore * 0.20 +
rsBearScore * rsW +
shortAccelScore +
entropyScore * entW
// Weighted quality contributions (sum to Quality) — exposed in the entry label so the
// reworked score is legible on a screenshot: which component carries it, ER vs Vol vs
// Accel vs Entropy (vs RS when enabled).
longQualBreak = "ER" + str.tostring(longERScore * erW, "#") + " V" + str.tostring(volScore * 0.20, "#") + " A" + str.tostring(longAccelScore, "#") + " E" + str.tostring(entropyScore * entW, "#") + (useRS ? " R" + str.tostring(rsBullScore * rsW, "#") : "")
shortQualBreak = "ER" + str.tostring(shortERScore * erW, "#") + " V" + str.tostring(volScore * 0.20, "#") + " A" + str.tostring(shortAccelScore, "#") + " E" + str.tostring(entropyScore * entW, "#") + (useRS ? " R" + str.tostring(rsBearScore * rsW, "#") : "")
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Context TF Bias (info only — not a gate)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ctxTrendBull = ctxClose > ctxEMA50 and ctxEMA50 > ctxEMA200
ctxTrendBear = ctxClose < ctxEMA50 and ctxEMA50 < ctxEMA200
ctxBullNet =
scoreBool(ctxWT1 > ctxWT2, 40) +
scoreBool(ctxWT1 > 0, 20) +
scoreBool(ctxTrendBull, 40)
ctxBearNet =
scoreBool(ctxWT1 < ctxWT2, 40) +
scoreBool(ctxWT1 < 0, 20) +
scoreBool(ctxTrendBear, 40)
netContext = float(ctxBullNet) - float(ctxBearNet)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Final / Net Scores
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
longFinal =
bullRegime * 0.35 +
longOpportunity * 0.30 +
longTiming * 0.25 +
longQuality * 0.10
shortFinal =
bearRegime * 0.35 +
shortOpportunity * 0.30 +
shortTiming * 0.25 +
shortQuality * 0.10
netRegime = bullRegime - bearRegime
netOpp = longOpportunity - shortOpportunity
netTiming = longTiming - shortTiming
netQuality = longQuality - shortQuality
netFinal = longFinal - shortFinal
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Pivot Reference — swing extremes, proximity, knife-catch (control overlay only — never a gate)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
riskAtr = ta.atr(atrLen)
sitVolRank = ta.percentrank(riskAtr, 100) // situation panel: volatility regime at entry
pivHi = ta.pivothigh(high, pivLeftBars, pivRightBars)
pivLo = ta.pivotlow(low, pivLeftBars, pivRightBars)
// Last *confirmed* pivot — used for the knife-catch test against established structure
var float lastPivLoPrice = na
var float lastPivHiPrice = na
if not na(pivLo)
lastPivLoPrice := pivLo
if not na(pivHi)
lastPivHiPrice := pivHi
// Current-leg extremes — the relevant reference for an entry, tracked live so a
// fresh (still-unconfirmed) low/high is used instead of a stale confirmed pivot.
// Down-leg trough resets at a confirmed pivot high; up-leg peak at a confirmed pivot low.
var float legLow = na
var int legLowBar = na
var float legHigh = na
var int legHighBar = na
if na(legLow) or not na(pivHi)
legLow := low
legLowBar := bar_index
else if low <= legLow
legLow := low
legLowBar := bar_index
if na(legHigh) or not na(pivLo)
legHigh := high
legHighBar := bar_index
else if high >= legHigh
legHigh := high
legHighBar := bar_index
// Markers at the actual pivot bar (offset back by the right-bar confirmation lag)
plotshape(showPivots ? pivLo : na, title="Pivot Low", style=shape.triangleup, location=location.belowbar, offset=-pivRightBars, color=color.new(#26a69a, 0), size=size.tiny, force_overlay=true)
plotshape(showPivots ? pivHi : na, title="Pivot High", style=shape.triangledown, location=location.abovebar, offset=-pivRightBars, color=color.new(#ef5350, 0), size=size.tiny, force_overlay=true)
// Distances: long vs swing low, short vs swing high (R is always ≥ 0 by construction)
longPivBars = bar_index - legLowBar
shortPivBars = bar_index - legHighBar
longPivR = riskAtr > 0 ? (close - legLow) / riskAtr : 0.0
shortPivR = riskAtr > 0 ? (legHigh - close) / riskAtr : 0.0
// Knife-catch: entering below the last confirmed swing low (long) / above the high (short)
longKnife = not na(lastPivLoPrice) and close < lastPivLoPrice
shortKnife = not na(lastPivHiPrice) and close > lastPivHiPrice
// Proximity 0..100 (display only): 100 = right at the swing extreme, decays with bars and ATR distance
longProx = clamp(100.0 - longPivBars * 4.0 - longPivR * 30.0, 0, 100)
shortProx = clamp(100.0 - shortPivBars * 4.0 - shortPivR * 30.0, 0, 100)
f_pivDistStr(barsAway, rAway) =>
str.tostring(barsAway) + "b / " + str.tostring(rAway, "#.0") + "R"
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Signals — stop-and-reverse on every WT trigger; opportunity flags 'tradeable' (no gate)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
minRegimeEff = clamp(minRegime - gateBuffer, 0, 100)
minOpportunityEff = clamp(minOpportunity - gateBuffer, 0, 100)
minOpportunityNear = clamp(minOpportunityEff - nearSetupBuffer, 0, 100)
// Regime dominance — grading only, not a gate
regimeLongDominant = bullRegime >= minRegimeEff and bullRegime > bearRegime
regimeShortDominant = bearRegime >= minRegimeEff and bearRegime > bullRegime
// Bias: regime-aligned direction (informational — dashboard only)
longBias = allowLong and regimeLongDominant
shortBias = allowShort and regimeShortDominant
// Setup: opportunity score alone — no binary structural gate
// The score already captures WT extreme, pullback depth, and 4H momentum direction.
// A higher minOpportunity threshold (default 50) requires a meaningful 4H setup
// without hard-blocking valid signals at turning points.
longSetup = allowLong and longOpportunity >= minOpportunityEff
shortSetup = allowShort and shortOpportunity >= minOpportunityEff
longNearSetup = allowLong and not longSetup and longOpportunity >= minOpportunityNear
shortNearSetup = allowShort and not shortSetup and shortOpportunity >= minOpportunityNear
chartIsTriggerTf = chartTfSec == triggerTfSec
// Setup/Near start transitions — must be defined before catchup uses them
longSetupStart = longSetup and not longSetup[1]
shortSetupStart = shortSetup and not shortSetup[1]
longNearSetupStart = longNearSetup and not longNearSetup[1]
shortNearSetupStart = shortNearSetup and not shortNearSetup[1]
// ── Stop-and-reverse alternation ────────────────────────────────────────────
// The exit of a position IS the opposite signal: a long closes any open short and a short
// closes any open long. Signals therefore alternate — every short is eventually faced by a
// long — and the exit is indicator-native (the opposite WT trigger, not an external stop).
// Direction is enforced by the open position, not by any gate: the counter-trigger always
// fires, so the alternation can never get stuck spamming one side. Opportunity (tradeable)
// stays on the labels as conviction info; it no longer blocks a signal.
var bool inLong = false
var bool inShort = false
var int posBar = -999
var float longEntryPx = na
var float shortEntryPx = na
// Per-position state for failed-signal marking + stats (captured at the fresh entry)
var float longOpenPx = na
var float shortOpenPx = na
var float longOpenAtr = na
var float shortOpenAtr = na
var float longSLlvl = na
var float shortSLlvl = na
var int longOpenBar = na
var int shortOpenBar = na
var float longCrossCtx = na
var float shortCrossCtx = na
var bool longWasWith = false
var bool shortWasWith = false
var bool longOppStrong = false
var bool shortOppStrong = false
var bool longWtDeep = false // entry came off a deep WT extreme (oversold ≤ −60 / overbought ≥ 60)
var bool shortWtDeep = false
var float longEntryDepth = na // the directional WT extreme the entry cross emerged from
var float shortEntryDepth = na
var bool longExitHookWas = false // last long closed via a hook-exit (true) vs a cross-reverse (false)
var bool longSitHtfWith = false // 4H JMA slope pointed in the trade direction at entry
var bool shortSitHtfWith = false
var bool longSitDriftWith = false // net drift over sitDriftLen pointed in the trade direction
var bool shortSitDriftWith = false
var int longSitStretch = 0 // +1 room (from the far side of the 4H mean), 0 near, -1 extended
var int shortSitStretch = 0
var bool longSitVolHigh = false // ATR percentile ≥ 50 at entry
var bool shortSitVolHigh = false
var bool shortExitHookWas = false
var bool longFailed = false
var bool shortFailed = false
var label longPosLbl = na
var label shortPosLbl = na
barsHeld = bar_index - posBar
held = barsHeld >= cooldownBars // minimum bars held before a reverse
// Non-blocking tradeable flag: opportunity ≥ minOppSignal marks the high-opportunity subset.
// It does NOT block — every WT reversal still fires. Whether the subset separates is measured
// in the layer panel (calibration record: it did not on the full NatGas sample).
longTradeable = longOpportunity >= minOppSignal
shortTradeable = shortOpportunity >= minOppSignal
// ── Entry/exit decoupled stop-and-reverse ────────────────────────────────────────────────
// ENTRIES fire on a WT CROSS only (the entry trigger). An open position is closed
// EARLY to flat by an opposite TURN/EXHAUSTION HOOK (profit-take at the extreme), then stays
// flat until the next cross re-enters. A cross in the opposite direction reverses outright.
// The minimum hold gates exits/reverses only — a flat entry fires on any cross immediately,
// so an exit-at-the-top can re-enter short on the very next confirmed cross.
flat = not inLong and not inShort
// A WT cross ARMS a pending entry (raw cross, gated by direction + minimum hold)
longEntrySig = allowLong and bullEntryTrig and not inLong and (flat or held)
shortEntrySig = allowShort and bearEntryTrig and not inShort and (flat or held)
// Ambiguous double-cross bar → take neither
if longEntrySig and shortEntrySig
longEntrySig := false
shortEntrySig := false
// Hook exit to flat — only when not simultaneously reversed by an opposite confirmed entry
longHookExit = inLong and held and bearHook and not shortEntrySig
shortHookExit = inShort and held and bullHook and not longEntrySig
// Every entry opens a fresh position (no same-direction re-entry)
longFresh = longEntrySig
shortFresh = shortEntrySig
// WT cross density in the window before the bar — high = choppy multi-cross zone (too-early risk)
crossCtxNow = math.sum((h1BullCross or h1BearCross) ? 1.0 : 0.0, failCrossLookback)
if longEntrySig
longOpenPx := close
longOpenAtr := riskAtr
longSLlvl := close - failAtrMult * riskAtr
longOpenBar := bar_index
longCrossCtx := crossCtxNow
longWasWith := regimeLongDominant
longOppStrong := longTradeable
longEntryDepth := wtLow5
longWtDeep := wtLow5 <= -60
longSitHtfWith := h4JmaSlope > 0
longSitDriftWith := sitDrift > 0
longSitStretch := h4DistAtr <= -sitStretchAtr ? 1 : h4DistAtr >= sitStretchAtr ? -1 : 0
longSitVolHigh := sitVolRank >= 50
longFailed := false
inLong := true
inShort := false
posBar := bar_index
longEntryPx := close
if shortEntrySig
shortOpenPx := close
shortOpenAtr := riskAtr
shortSLlvl := close + failAtrMult * riskAtr
shortOpenBar := bar_index
shortCrossCtx := crossCtxNow
shortWasWith := regimeShortDominant
shortOppStrong := shortTradeable
shortEntryDepth := wtHigh5
shortWtDeep := wtHigh5 >= 60
shortSitHtfWith := h4JmaSlope < 0
shortSitDriftWith := sitDrift < 0
shortSitStretch := h4DistAtr >= sitStretchAtr ? 1 : h4DistAtr <= -sitStretchAtr ? -1 : 0
shortSitVolHigh := sitVolRank >= 50
shortFailed := false
inShort := true
inLong := false
posBar := bar_index
shortEntryPx := close
// Hook exits flip the position to flat (after the entry/reverse logic above has run)
if longHookExit
inLong := false
if shortHookExit
inShort := false
// Entry signals fire on any chart TF; sampled flag is informational only
longSignal = longEntrySig
shortSignal = shortEntrySig
sampledLongSignal = longEntrySig and not chartIsTriggerTf
sampledShortSignal = shortEntrySig and not chartIsTriggerTf
longNearTip ="Long Near Setup\nOpportunity: " + str.tostring(longOpportunity, "#.0") + " (threshold: " + str.tostring(minOpportunityNear, "#.0") + ")\nRegime: " + str.tostring(bullRegime, "#.0") + " vs " + str.tostring(bearRegime, "#.0")
shortNearTip = "Short Near Setup\nOpportunity: " + str.tostring(shortOpportunity, "#.0") + " (threshold: " + str.tostring(minOpportunityNear, "#.0") + ")\nRegime: " + str.tostring(bearRegime, "#.0") + " vs " + str.tostring(bullRegime, "#.0")
longSetupTip = "Long Setup — waiting for WT bull cross\nOpportunity: " + str.tostring(longOpportunity, "#.0") + " Regime: " + str.tostring(bullRegime, "#.0") + " vs " + str.tostring(bearRegime, "#.0")
shortSetupTip = "Short Setup — waiting for WT bear cross\nOpportunity: " + str.tostring(shortOpportunity, "#.0") + " Regime: " + str.tostring(bearRegime, "#.0") + " vs " + str.tostring(bullRegime, "#.0")
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Confluence Rotation (RRG-inspired): x = net confluence, y = its momentum
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Both axes EMA-smoothed — like real RRGs, the raw path is unreadable noise
rotXSm = ta.ema(netFinal, rotSmooth)
rotMomRaw = rotXSm - nz(rotXSm[rotMomLen], rotXSm)
rotYSm = ta.ema(rotMomRaw, rotSmooth)
rotX = clamp(rotXSm, -100, 100)
rotY = clamp(rotYSm * 2.5, -100, 100)
string rotQuad = "LAGGING"
color rotQuadColor = color.new(#d50000, 0)
if rotX >= 0 and rotY >= 0
rotQuad := "LEADING"
rotQuadColor := color.new(#00c853, 0)
else if rotX >= 0
rotQuad := "WEAKENING"
rotQuadColor := color.new(#ff6f00, 0)
else if rotY >= 0
rotQuad := "IMPROVING"
rotQuadColor := color.new(#ffd600, 0)
var array<float> rotXs = array.new_float()
var array<float> rotYs = array.new_float()
if barstate.isconfirmed and bar_index % rotStep == 0
array.push(rotXs, rotX)
array.push(rotYs, rotY)
if array.size(rotXs) > rotTrail
array.shift(rotXs)
array.shift(rotYs)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Position exits — the opposite signal closes a trade (stop-and-reverse)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// A position closes when it was open at bar start and is no longer open — covers BOTH a
// cross-reverse and an early hook-exit to flat. The flip is resolved in the signal section
// above; here we derive the close events for stats, markers and alerts.
longExit = inLong[1] and not inLong
shortExit = inShort[1] and not inShort
// Record HOW it closed so the layer test can compare hook-exit R vs cross-reverse R
if longExit
longExitHookWas := longHookExit
if shortExit
shortExitHookWas := shortHookExit
// Live stats over the loaded history, split Long / Short — measure the config (win rate /
// avg R / failed %) on the chart, so the knobs can be tuned against numbers and the long vs
// short asymmetry is visible. R = (exit−entry)/ATR-at-entry vs the fresh open (re-entry
// averaging ignored — a slight approximation).
var int statNL = 0
var int statWinsL = 0
var float statSumRL = 0.0
var int statFailsL = 0
var int statNS = 0
var int statWinsS = 0
var float statSumRS = 0.0
var int statFailsS = 0
// Layer-test buckets (both directions): does Regime alignment (with-trend vs counter-trend)
// or Setup strength (opportunity ≥ 40 vs below) discriminate avg R? If a bucket separates,
// that layer is a real filter; if not, it is dead weight.
var int statN_w = 0 // with-trend
var float statR_w = 0.0
var int statN_c = 0 // counter-trend
var float statR_c = 0.0
var int statN_s = 0 // strong setup (tradeable)
var float statR_s = 0.0
var int statN_k = 0 // weak setup (watch)
var float statR_k = 0.0
// Exit-type buckets — validates the decoupling: does an early HOOK exit (profit-take at the
// extreme) actually beat a CROSS reverse exit? And does the depth of the extreme the ENTRY
// came off (≥60 vs shallow) matter?
var int statN_hk = 0 // closed via hook exit (to flat)
var float statR_hk = 0.0
var int statN_rv = 0 // closed via opposite-cross reverse
var float statR_rv = 0.0
var int statN_dp = 0 // deep WT extreme (≥60)
var float statR_dp = 0.0
var int statN_sh = 0 // shallow extreme
var float statR_sh = 0.0
// Situation buckets, kept PER DIRECTION (index = bucket * 2 + dir, dir 0 = long, 1 = short).
// Pooling long and short would hide exactly the asymmetry these buckets are meant to explain.
// Buckets: 0 HTF with, 1 HTF against, 2 drift with, 3 drift against, 4 room, 5 near,
// 6 extended, 7 vola high, 8 vola low
var array<int> sitN = array.new_int(18, 0)
var array<float> sitR = array.new_float(18, 0.0)
f_sitAdd(int bucket, int dir, float rr) =>
int i = bucket * 2 + dir
sitN.set(i, sitN.get(i) + 1)
sitR.set(i, sitR.get(i) + rr)
f_sitRecord(int dir, bool htfWith, bool driftWith, int stretch, bool volHigh, float rr) =>
f_sitAdd(htfWith ? 0 : 1, dir, rr)
f_sitAdd(driftWith ? 2 : 3, dir, rr)
f_sitAdd(stretch == 1 ? 4 : stretch == 0 ? 5 : 6, dir, rr)
f_sitAdd(volHigh ? 7 : 8, dir, rr)
if longExit and not na(longOpenPx) and longOpenAtr > 0
float rr = (close - longOpenPx) / longOpenAtr
statNL += 1
statSumRL += rr
statWinsL += rr > 0 ? 1 : 0
statFailsL += longFailed ? 1 : 0
statN_w := statN_w + (longWasWith ? 1 : 0)
statR_w := statR_w + (longWasWith ? rr : 0.0)
statN_c := statN_c + (longWasWith ? 0 : 1)
statR_c := statR_c + (longWasWith ? 0.0 : rr)
statN_s := statN_s + (longOppStrong ? 1 : 0)
statR_s := statR_s + (longOppStrong ? rr : 0.0)
statN_k := statN_k + (longOppStrong ? 0 : 1)
statR_k := statR_k + (longOppStrong ? 0.0 : rr)
statN_hk := statN_hk + (longExitHookWas ? 1 : 0)
statR_hk := statR_hk + (longExitHookWas ? rr : 0.0)
statN_rv := statN_rv + (longExitHookWas ? 0 : 1)
statR_rv := statR_rv + (longExitHookWas ? 0.0 : rr)
statN_dp := statN_dp + (longWtDeep ? 1 : 0)
statR_dp := statR_dp + (longWtDeep ? rr : 0.0)
statN_sh := statN_sh + (longWtDeep ? 0 : 1)
statR_sh := statR_sh + (longWtDeep ? 0.0 : rr)
f_sitRecord(0, longSitHtfWith, longSitDriftWith, longSitStretch, longSitVolHigh, rr)
if shortExit and not na(shortOpenPx) and shortOpenAtr > 0
float rr = (shortOpenPx - close) / shortOpenAtr
statNS += 1
statSumRS += rr
statWinsS += rr > 0 ? 1 : 0
statFailsS += shortFailed ? 1 : 0
statN_w := statN_w + (shortWasWith ? 1 : 0)
statR_w := statR_w + (shortWasWith ? rr : 0.0)
statN_c := statN_c + (shortWasWith ? 0 : 1)
statR_c := statR_c + (shortWasWith ? 0.0 : rr)
statN_s := statN_s + (shortOppStrong ? 1 : 0)
statR_s := statR_s + (shortOppStrong ? rr : 0.0)
statN_k := statN_k + (shortOppStrong ? 0 : 1)
statR_k := statR_k + (shortOppStrong ? 0.0 : rr)
statN_hk := statN_hk + (shortExitHookWas ? 1 : 0)
statR_hk := statR_hk + (shortExitHookWas ? rr : 0.0)
statN_rv := statN_rv + (shortExitHookWas ? 0 : 1)
statR_rv := statR_rv + (shortExitHookWas ? 0.0 : rr)
statN_dp := statN_dp + (shortWtDeep ? 1 : 0)
statR_dp := statR_dp + (shortWtDeep ? rr : 0.0)
statN_sh := statN_sh + (shortWtDeep ? 0 : 1)
statR_sh := statR_sh + (shortWtDeep ? 0.0 : rr)
f_sitRecord(1, shortSitHtfWith, shortSitDriftWith, shortSitStretch, shortSitVolHigh, rr)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Signal labels on the price chart (compact / full / none; full detail in the tooltip)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
if longSignal and labelMode != "None"
string longTag = longTradeable ? "★" : "·" // ★ = tradeable (opp ≥ threshold), · = watch
string longFull = "LONG " + longTag + (longKnife ? " ⚠K" : "") + "\ncross from WT " + str.tostring(longEntryDepth, "#") + (longWtDeep ? " (deep)" : " (shallow)") + "\nΔLow " + f_pivDistStr(longPivBars, longPivR) + " · P" + str.tostring(longProx, "#") + "\nO" + str.tostring(longOpportunity, "#") + " Rg" + str.tostring(bullRegime, "#") + " T" + str.tostring(longTiming, "#") + " Q" + str.tostring(longQuality, "#") + "\n" + longQualBreak
string longTxt = labelMode == "Full" ? longFull : "L" + longTag + (longKnife ? "⚠" : "") + " O" + str.tostring(longOpportunity, "#") + " z" + str.tostring(longEntryDepth, "#")
label ll = label.new(bar_index, low, longTxt, style=label.style_label_up, color=color.new(#00c853, longTradeable ? 0 : 45), textcolor=color.white, size=labelMode == "Full" ? size.small : size.tiny, yloc=yloc.belowbar, force_overlay=true, tooltip=longFull)
if longFresh
longPosLbl := ll
if shortSignal and labelMode != "None"
string shortTag = shortTradeable ? "★" : "·"
string shortFull = "SHORT " + shortTag + (shortKnife ? " ⚠K" : "") + "\ncross from WT +" + str.tostring(shortEntryDepth, "#") + (shortWtDeep ? " (deep)" : " (shallow)") + "\nΔHigh " + f_pivDistStr(shortPivBars, shortPivR) + " · P" + str.tostring(shortProx, "#") + "\nO" + str.tostring(shortOpportunity, "#") + " Rg" + str.tostring(bearRegime, "#") + " T" + str.tostring(shortTiming, "#") + " Q" + str.tostring(shortQuality, "#") + "\n" + shortQualBreak
string shortTxt = labelMode == "Full" ? shortFull : "S" + shortTag + (shortKnife ? "⚠" : "") + " O" + str.tostring(shortOpportunity, "#") + " z" + str.tostring(shortEntryDepth, "#")
label sl = label.new(bar_index, high, shortTxt, style=label.style_label_down, color=color.new(#d50000, shortTradeable ? 0 : 45), textcolor=color.white, size=labelMode == "Full" ? size.small : size.tiny, yloc=yloc.abovebar, force_overlay=true, tooltip=shortFull)
if shortFresh
shortPosLbl := sl
// Hook-exit-to-flat markers — a small neutral tag where a position was closed early on a hook
// (profit-take at the extreme) rather than reversed by a cross. Shows the flat periods.
if longHookExit and labelMode != "None"
label.new(bar_index, high, "✕", style=label.style_label_down, color=color.new(color.gray, 25), textcolor=color.white, size=size.tiny, yloc=yloc.abovebar, force_overlay=true, tooltip="Long closed early on a bear hook (profit-take at the top) → flat until next cross")
if shortHookExit and labelMode != "None"
label.new(bar_index, low, "✕", style=label.style_label_up, color=color.new(color.gray, 25), textcolor=color.white, size=size.tiny, yloc=yloc.belowbar, force_overlay=true, tooltip="Short closed early on a bull hook (profit-take at the bottom) → flat until next cross")
// ── Failed-signal marking: recolour the entry tag + debug label once price trades the
// adverse-ATR distance against the open (where the old stop sat). Studies early entries.
// The entry bar is skipped: its low/high formed before the close entry. ──
if markFailed and inLong and not longFailed and not na(longSLlvl) and bar_index > longOpenBar and low <= longSLlvl
longFailed := true
if not na(longPosLbl)
label.set_color(longPosLbl, failColor)
if showFailDebug
int heldBars = bar_index - longOpenBar
string dbg = "FAILED LONG " + (longOppStrong ? "★" : "·") + "\ncross from WT " + str.tostring(longEntryDepth, "#") + (longWtDeep ? " (deep)" : " (SHALLOW)") + "\nx" + str.tostring(longCrossCtx, "#") + " crosses in " + str.tostring(failCrossLookback) + "b before entry\nheld " + str.tostring(heldBars) + "b → -" + str.tostring(failAtrMult, "#.0") + "R\nentry " + str.tostring(longEntryPx, format.mintick)
label.new(bar_index, low, dbg, style=label.style_label_up, color=failColor, textcolor=color.white, size=size.small, yloc=yloc.belowbar, force_overlay=true)
if markFailed and inShort and not shortFailed and not na(shortSLlvl) and bar_index > shortOpenBar and high >= shortSLlvl
shortFailed := true
if not na(shortPosLbl)
label.set_color(shortPosLbl, failColor)
if showFailDebug
int heldBars = bar_index - shortOpenBar
string dbg = "FAILED SHORT " + (shortOppStrong ? "★" : "·") + "\ncross from WT +" + str.tostring(shortEntryDepth, "#") + (shortWtDeep ? " (deep)" : " (SHALLOW)") + "\nx" + str.tostring(shortCrossCtx, "#") + " crosses in " + str.tostring(failCrossLookback) + "b before entry\nheld " + str.tostring(heldBars) + "b → -" + str.tostring(failAtrMult, "#.0") + "R\nentry " + str.tostring(shortEntryPx, format.mintick)
label.new(bar_index, high, dbg, style=label.style_label_down, color=failColor, textcolor=color.white, size=size.small, yloc=yloc.abovebar, force_overlay=true)
// Pivot → signal connectors: visualise how far the entry sits from the swing extreme
if showPivDist and longSignal and not na(legLowBar)
line.new(legLowBar, legLow, bar_index, close, color=color.new(#26a69a, 30), style=line.style_dotted, force_overlay=true)
if showPivDist and shortSignal and not na(legHighBar)
line.new(legHighBar, legHigh, bar_index, close, color=color.new(#ef5350, 30), style=line.style_dotted, force_overlay=true)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Pane Plots — net confluence + layer heat ribbons
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Require 3 consecutive bars — a regime hovering around the threshold
// otherwise paints flickering single-bar stripes
bullBgOk = bullRegime >= minRegimeEff and bullRegime >= bearRegime
bearBgOk = bearRegime >= minRegimeEff and bearRegime > bullRegime
color bgColor = na
if showBg and bullBgOk and bullBgOk[1] and bullBgOk[2]
bgColor := color.new(color.green, 92)
else if showBg and bearBgOk and bearBgOk[1] and bearBgOk[2]
bgColor := color.new(color.red, 92)
bgcolor(bgColor)
// Raw columns as the base layer — shows unsmoothed bar-by-bar signal
plot(netFinal, "Net Confluence Raw", color=netFinal >= 0 ? color.new(#00c853, 78) : color.new(#d50000, 78), style=plot.style_columns)
// Smoothed area + line on top — trend direction and momentum at a glance
netFinalSm = f_smoothHist(netFinal, histSmooth, histSmoothType)
plot(netFinalSm, "Net Confluence", color=netFinalSm >= 0 ? color.new(#00c853, 60) : color.new(#d50000, 60), style=plot.style_area)
plot(netFinalSm, "Net Confluence Line", color=netFinalSm >= 0 ? color.new(#00c853, 0) : color.new(#d50000, 0), linewidth=2)
hline(0, "Zero", color=color.new(color.gray, 50))
hline(60, "Strong Long Confluence", color=color.new(#00c853, 70), linestyle=hline.style_dotted)
hline(-60, "Strong Short Confluence", color=color.new(#d50000, 70), linestyle=hline.style_dotted)
// WT OB/OS zone hlines + fills — at ±wtExtreme (scoring threshold) and ±80 (hard clamp)
hOB2 = hline( 80, "WT OB Hard", color=color.new(#ef5350, 45), linestyle=hline.style_solid, linewidth=1)
hOB1 = hline(wtExtreme, "WT OB", color=color.new(#ef5350, 65), linestyle=hline.style_dashed, linewidth=1)
hOS1 = hline(-wtExtreme, "WT OS", color=color.new(#26a69a, 65), linestyle=hline.style_dashed, linewidth=1)
hOS2 = hline(-80, "WT OS Hard", color=color.new(#26a69a, 45), linestyle=hline.style_solid, linewidth=1)
fill(hOB1, hOB2, color=showWtZones ? color.new(#ef5350, 92) : na, title="WT OB Zone")
fill(hOS2, hOS1, color=showWtZones ? color.new(#26a69a, 92) : na, title="WT OS Zone")
// Gradient WT lines: teal at OS → red at OB
color wtCol1 = color.from_gradient(plotWT1, -wtExtreme, wtExtreme, #26a69a, #ef5350)
color wtCol2 = color.from_gradient(plotWT2, -wtExtreme, wtExtreme, color.new(#26a69a, 40), color.new(#ef5350, 40))
pZeroWt = plot(0.0, display=display.none)
pWT2 = plot(plotWT2, "Trigger WT2", color=wtCol2, linewidth=1)
pWT1 = plot(plotWT1, "Trigger WT1", color=wtCol1, linewidth=2)
// Shadow fill: fades from near-opaque at the extreme level toward transparent at zero
fill(pWT1, pZeroWt, wtExtreme, 0.0,
color.new(#ef5350, 78), color.new(#ef5350, 100),
title="WT Bear Shadow", display=showWtShadow ? display.all : display.none)
fill(pWT1, pZeroWt, 0.0, -wtExtreme,
color.new(#26a69a, 100), color.new(#26a69a, 78),
title="WT Bull Shadow", display=showWtShadow ? display.all : display.none)
// Momentum fill between WT1 and WT2
fill(pWT1, pWT2,
color=showWtFill ? (plotWT1 >= plotWT2 ? color.new(#26a69a, 88) : color.new(#ef5350, 88)) : na,
title="WT Momentum Fill")
plot(triggerTurnLevel, "WT Turn Level +", color=color.new(#ff6f00, 75), linewidth=1, style=plot.style_linebr)
plot(-triggerTurnLevel, "WT Turn Level -", color=color.new(#00c853, 75), linewidth=1, style=plot.style_linebr)
plot(showRibbons ? 92.0 : na, "Regime Ribbon", color=f_heatColor(netRegime), linewidth=6, style=plot.style_line)
plot(showRibbons ? 102.0 : na, "Opportunity Ribbon", color=f_heatColor(netOpp), linewidth=6, style=plot.style_line)
plot(showRibbons ? 112.0 : na, "Timing Ribbon", color=f_heatColor(netTiming), linewidth=6, style=plot.style_line)
plot(showRibbons ? 122.0 : na, "Quality Ribbon", color=f_heatColor(netQuality), linewidth=6, style=plot.style_line)
plot(showRibbons ? 132.0 : na, "Noise Ribbon", color=f_noiseColor(entropyRank), linewidth=6, style=plot.style_line)
plot(showRibbons and showContextLayer ? 142.0 : na, "Context Ribbon", color=f_heatColor(netContext), linewidth=6, style=plot.style_line)
var label ribR = na
var label ribO = na
var label ribT = na
var label ribQ = na
var label ribN = na
var label ribC = na
if showRibbons and barstate.islast
if na(ribR)
ribR := label.new(bar_index, 92, "Regime", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
ribO := label.new(bar_index, 102, "Opportunity", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
ribT := label.new(bar_index, 112, "Timing", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
ribQ := label.new(bar_index, 122, "Quality", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
ribN := label.new(bar_index, 132, "Noise", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
ribC := label.new(bar_index, 142, "Context", style=label.style_label_left, color=color.new(color.white, 100), textcolor=color.new(color.gray, 25), size=size.small)
label.set_x(ribR, bar_index + 2)
label.set_x(ribO, bar_index + 2)
label.set_x(ribT, bar_index + 2)
label.set_x(ribQ, bar_index + 2)
label.set_x(ribN, bar_index + 2)
label.set_x(ribC, bar_index + 2)
// Setup markers — only shown when trigger-TF WT is already in the correct zone.
// This ensures "LS" only appears when WT is oversold and "SS" when overbought,
// matching what the user sees in the pane rather than firing on 4H-driven state changes.
longSetupWtOk = plotWT1 < wtCrossZone
shortSetupWtOk = plotWT1 > -wtCrossZone
if longNearSetupStart and longSetupWtOk
label.new(bar_index, low, "LN", style=label.style_label_up, color=color.new(#00c853, 35), textcolor=color.white, size=size.tiny, yloc=yloc.belowbar, force_overlay=true, tooltip=longNearTip)
if shortNearSetupStart and shortSetupWtOk
label.new(bar_index, high, "SN", style=label.style_label_down, color=color.new(#d50000, 35), textcolor=color.white, size=size.tiny, yloc=yloc.abovebar, force_overlay=true, tooltip=shortNearTip)
if longSetupStart and longSetupWtOk
label.new(bar_index, low, "LS", style=label.style_label_up, color=color.new(#00c853, 0), textcolor=color.white, size=size.tiny, yloc=yloc.belowbar, force_overlay=true, tooltip=longSetupTip)
if shortSetupStart and shortSetupWtOk
label.new(bar_index, high, "SS", style=label.style_label_down, color=color.new(#d50000, 0), textcolor=color.white, size=size.tiny, yloc=yloc.abovebar, force_overlay=true, tooltip=shortSetupTip)
// Entry marker placed AT the WT cross (on the WT1 line) — so you can see at a glance whether
// the entry actually sits on a real WT1/WT2 cross and where on the oscillator it fired.
// Full colour = tradeable (opp ≥ threshold), dimmed = watch; orange overrides when sampled.
plotshape(longSignal ? plotWT1 : na, title="Long Signal (Pane)", style=shape.triangleup, location=location.absolute, color=sampledLongSignal ? color.new(#ff6f00, 0) : color.new(#00c853, longTradeable ? 0 : 60), size=size.small)
plotshape(shortSignal ? plotWT1 : na, title="Short Signal (Pane)", style=shape.triangledown, location=location.absolute, color=sampledShortSignal ? color.new(#ff6f00, 0) : color.new(#d50000, shortTradeable ? 0 : 60), size=size.small)
// Cross dots — all WT crosses; entry crosses are already covered by the larger triangles above
plotshape(showWtDots and h1BullCross ? plotWT1 : na, "WT Bull Cross Dot",
style=shape.circle, location=location.absolute, color=color.new(#26a69a, 30), size=size.tiny)
plotshape(showWtDots and h1BearCross ? plotWT1 : na, "WT Bear Cross Dot",
style=shape.circle, location=location.absolute, color=color.new(#ef5350, 30), size=size.tiny)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Rotation Map inset (right of the last bar, lower pane area)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var polyline rotPl = na
var box rotFrame = na
var box rotQLead = na
var box rotQImp = na
var box rotQWeak = na
var box rotQLag = na
var line rotVLn = na
var line rotHLn = na
var label rotDot = na
var label rotTitle = na
var label rotLbLead = na
var label rotLbImp = na
var label rotLbWeak = na
var label rotLbLag = na
if showRotation and barstate.islast and array.size(rotXs) >= 2
polyline.delete(rotPl)
box.delete(rotFrame)
box.delete(rotQLead)
box.delete(rotQImp)
box.delete(rotQWeak)
box.delete(rotQLag)
line.delete(rotVLn)
line.delete(rotHLn)
label.delete(rotDot)
label.delete(rotTitle)
label.delete(rotLbLead)
label.delete(rotLbImp)
label.delete(rotLbWeak)
label.delete(rotLbLag)
int xL = bar_index + 6
int rotW = 34
float yB = -95.0
float rotHt = 78.0
int cx = xL + rotW / 2
float cy = yB + rotHt / 2
float yT = yB + rotHt
// Quadrant tints make the map self-explanatory
rotQImp := box.new(xL, yT, cx, cy, border_color=color.new(color.white, 100), bgcolor=color.new(#ffd600, 94))
rotQLead := box.new(cx, yT, xL + rotW, cy, border_color=color.new(color.white, 100), bgcolor=color.new(#00c853, 94))
rotQLag := box.new(xL, cy, cx, yB, border_color=color.new(color.white, 100), bgcolor=color.new(#d50000, 95))
rotQWeak := box.new(cx, cy, xL + rotW, yB, border_color=color.new(color.white, 100), bgcolor=color.new(#ff6f00, 95))
rotFrame := box.new(xL, yT, xL + rotW, yB, border_color=color.new(color.gray, 50), bgcolor=color.new(color.white, 100))
rotVLn := line.new(cx, yB, cx, yT, color=color.new(color.gray, 60), style=line.style_dotted)
rotHLn := line.new(xL, cy, xL + rotW, cy, color=color.new(color.gray, 60), style=line.style_dotted)
rotTitle := label.new(cx, yT, "Confluence Rotation", style=label.style_label_down, color=color.new(color.white, 100), textcolor=color.new(color.gray, 35), size=size.tiny)
rotLbLead := label.new(xL + rotW, yT, "LEAD", style=label.style_label_lower_left, color=color.new(color.white, 100), textcolor=color.new(#00c853, 30), size=size.tiny)
rotLbImp := label.new(xL, yT, "IMPR", style=label.style_label_lower_right, color=color.new(color.white, 100), textcolor=color.new(#c8a800, 20), size=size.tiny)
rotLbWeak := label.new(xL + rotW, yB, "WEAK", style=label.style_label_upper_left, color=color.new(color.white, 100), textcolor=color.new(#ff6f00, 30), size=size.tiny)
rotLbLag := label.new(xL, yB, "LAG", style=label.style_label_upper_right, color=color.new(color.white, 100), textcolor=color.new(#d50000, 30), size=size.tiny)
pts = array.new<chart.point>()
int n = array.size(rotXs)
for i = 0 to n - 1
float vx = array.get(rotXs, i)
float vy = array.get(rotYs, i)
int px = xL + int(math.round((vx + 100.0) / 200.0 * rotW))
float py = yB + (vy + 100.0) / 200.0 * rotHt
array.push(pts, chart.point.from_index(px, py))
rotPl := polyline.new(pts, curved=true, closed=false, line_color=color.new(color.gray, 45), line_width=1)
int pxN = xL + int(math.round((rotX + 100.0) / 200.0 * rotW))
float pyN = yB + (rotY + 100.0) / 200.0 * rotHt
rotDot := label.new(pxN, pyN, "", style=label.style_circle, color=rotQuadColor, size=size.small)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Dashboard
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table dash = table.new(position.top_right, 3, 13,
bgcolor=color.new(color.white, 5),
border_color=color.new(color.gray, 60),
border_width=1,
frame_color=color.new(color.gray, 40),
frame_width=1,
force_overlay=true)
color tc = color.new(color.gray, 20)
color hd = color.new(color.gray, 90)
color cGood = #00c853
color cBad = #d50000
var table tfWarn = table.new(position.top_center, 1, 2,
bgcolor=color.new(#fff3e0, 0),
border_color=color.new(#ff6f00, 15),
border_width=1,
frame_color=color.new(#ff6f00, 0),
frame_width=1,
force_overlay=true)
if barstate.islast
table.clear(tfWarn, 0, 0, 0, 1)
if showTfWarning and tfSamplingWarning
table.cell(tfWarn, 0, 0, "⚠ Lower-TF sampling", text_color=color.new(#e65100, 0), bgcolor=color.new(#ffe0b2, 0), text_size=size.small,
tooltip=tfSamplingText)
table.cell(tfWarn, 0, 1, tfSamplingText, text_color=color.new(#5d4037, 0), bgcolor=color.new(#fff8e1, 0), text_size=size.small,
tooltip="Use the Trigger TF chart for exact trigger crosses, or set Trigger/Setup TF at or above the chart TF.")
if showDashboard and barstate.islast
table.cell(dash, 0, 0, tfSamplingWarning ? "MTF WT Hunter ⚠" : "MTF WT Hunter", text_color=tfSamplingWarning ? color.new(#ff6f00, 0) : tc, bgcolor=hd, text_size=size.small,
tooltip=tfSamplingWarning ? tfSamplingText : "")
table.cell(dash, 1, 0, "Long", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(dash, 2, 0, "Short", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(dash, 0, 1, "Regime", text_color=tc, text_size=size.small)
table.cell(dash, 1, 1, str.tostring(bullRegime, "#.0"), text_color=bullRegime >= minRegimeEff ? cGood : tc, text_size=size.small)
table.cell(dash, 2, 1, str.tostring(bearRegime, "#.0"), text_color=bearRegime >= minRegimeEff ? cBad : tc, text_size=size.small)
table.cell(dash, 0, 2, "Opportunity", text_color=tc, text_size=size.small)
table.cell(dash, 1, 2, str.tostring(longOpportunity, "#.0"), text_color=longOpportunity >= minOpportunityEff ? cGood : tc, text_size=size.small)
table.cell(dash, 2, 2, str.tostring(shortOpportunity, "#.0"), text_color=shortOpportunity >= minOpportunityEff ? cBad : tc, text_size=size.small)
table.cell(dash, 0, 3, "Timing", text_color=tc, text_size=size.small)
table.cell(dash, 1, 3, str.tostring(longTiming, "#.0"), text_color=tc, text_size=size.small)
table.cell(dash, 2, 3, str.tostring(shortTiming, "#.0"), text_color=tc, text_size=size.small)
table.cell(dash, 0, 4, "Quality", text_color=tc, text_size=size.small)
table.cell(dash, 1, 4, str.tostring(longQuality, "#.0") + (longTradeable ? " ★" : ""), text_color=longTradeable ? cGood : tc, text_size=size.small)
table.cell(dash, 2, 4, str.tostring(shortQuality, "#.0") + (shortTradeable ? " ★" : ""), text_color=shortTradeable ? cBad : tc, text_size=size.small)
table.cell(dash, 0, 5, "Final", text_color=tc, text_size=size.small)
table.cell(dash, 1, 5, str.tostring(longFinal, "#.0"), text_color=tc, text_size=size.small)
table.cell(dash, 2, 5, str.tostring(shortFinal, "#.0"), text_color=tc, text_size=size.small)
color noiseCol = tc
if entropyRank >= 70
noiseCol := #ff6f00
else if entropyRank <= 30
noiseCol := cGood
table.cell(dash, 0, 6, "Noise", text_color=tc, text_size=size.small)
table.cell(dash, 1, 6, str.tostring(entropyRank, "#") + "%", text_color=noiseCol, text_size=size.small)
table.cell(dash, 2, 6, "", text_size=size.small)
// Context TF row — informational, green/red if strongly aligned, gray if mixed
color ctxLCol = netContext >= 40 ? cGood : netContext <= -40 ? color.new(cGood, 60) : tc
color ctxSCol = netContext <= -40 ? cBad : netContext >= 40 ? color.new(cBad, 60) : tc
table.cell(dash, 0, 7, "Ctx " + tfContext, text_color=tc, text_size=size.small,
tooltip="Context TF directional bias — informational only, not a gate. Green/red = strong alignment.")
table.cell(dash, 1, 7, str.tostring(ctxBullNet, "#"), text_color=ctxLCol, text_size=size.small)
table.cell(dash, 2, 7, str.tostring(ctxBearNet, "#"), text_color=ctxSCol, text_size=size.small)
table.cell(dash, 0, 8, "Rotation", text_color=tc, text_size=size.small)
table.cell(dash, 1, 8, rotQuad, text_color=rotQuadColor, text_size=size.small)
table.cell(dash, 2, 8, "", text_size=size.small)
string setupL = longSetup ? "SETUP" : longNearSetup ? "NEAR" : longBias ? "BIAS" : "–"
string setupS = shortSetup ? "SETUP" : shortNearSetup ? "NEAR" : shortBias ? "BIAS" : "–"
table.cell(dash, 0, 9, "Setup", text_color=tc, bgcolor=color.new(color.gray, 80), text_size=size.small)
table.cell(dash, 1, 9, setupL, text_color=longSetup ? cGood : longNearSetup ? color.new(#ff6f00, 0) : longBias ? color.new(#00c853, 40) : tc, bgcolor=color.new(color.gray, 80), text_size=size.small)
table.cell(dash, 2, 9, setupS, text_color=shortSetup ? cBad : shortNearSetup ? color.new(#ff6f00, 0) : shortBias ? color.new(#d50000, 40) : tc, bgcolor=color.new(color.gray, 80), text_size=size.small)
table.cell(dash, 0, 10, "WT Trigger", text_color=tc, text_size=size.small)
table.cell(dash, 1, 10, h1BullTrigger ? triggerTfBelowChart ? "⚠" : "✓" : "–", text_color=h1BullTrigger ? triggerTfBelowChart ? color.new(#ff6f00, 0) : cGood : tc, text_size=size.small,
tooltip=h1BullTrigger and triggerTfBelowChart ? "Sampled lower-TF WT trigger. Switch chart to Trigger TF for actionable entry." : h1BullCross ? "WT bull cross" : h1BullTurn ? "WT bull turn from extended zone" : h1BullExhaust ? "WT bull exhaustion reversal (hook from deep oversold)" : "")
table.cell(dash, 2, 10, h1BearTrigger ? triggerTfBelowChart ? "⚠" : "✓" : "–", text_color=h1BearTrigger ? triggerTfBelowChart ? color.new(#ff6f00, 0) : cBad : tc, text_size=size.small,
tooltip=h1BearTrigger and triggerTfBelowChart ? "Sampled lower-TF WT trigger. Switch chart to Trigger TF for actionable entry." : h1BearCross ? "WT bear cross" : h1BearTurn ? "WT bear turn from extended zone" : h1BearExhaust ? "WT bear exhaustion reversal (hook from deep overbought)" : "")
string stL = longBias ? "BIAS" : "–"
color stLBg = color.new(color.gray, 80)
color stLTx = longBias ? color.new(#00c853, 40) : tc
if not allowLong
stL := "OFF"
stLBg := color.new(color.gray, 80)
stLTx := tc
else if longSignal
stL := "SIGNAL"
stLBg := color.new(#00c853, 0)
stLTx := color.white
else if sampledLongSignal
stL := "TRIG TF"
stLBg := color.new(#ff6f00, 0)
stLTx := color.white
else if longSetup
stL := "SETUP"
stLBg := color.new(#00c853, 65)
stLTx := color.white
else if longNearSetup
stL := "NEAR"
stLBg := color.new(#ff6f00, 35)
stLTx := color.white
else if longBias
stL := "BIAS"
stLBg := color.new(color.gray, 80)
stLTx := tc
string stS = shortBias ? "BIAS" : "–"
color stSBg = color.new(color.gray, 80)
color stSTx = shortBias ? color.new(#d50000, 40) : tc
if not allowShort
stS := "OFF"
stSBg := color.new(color.gray, 80)
stSTx := tc
else if shortSignal
stS := "SIGNAL"
stSBg := color.new(#d50000, 0)
stSTx := color.white
else if sampledShortSignal
stS := "TRIG TF"
stSBg := color.new(#ff6f00, 0)
stSTx := color.white
else if shortSetup
stS := "SETUP"
stSBg := color.new(#d50000, 65)
stSTx := color.white
else if shortNearSetup
stS := "NEAR"
stSBg := color.new(#ff6f00, 35)
stSTx := color.white
else if shortBias
stS := "BIAS"
stSBg := color.new(color.gray, 80)
stSTx := tc
table.cell(dash, 0, 11, "Status", text_color=tc, text_size=size.small)
table.cell(dash, 1, 11, stL, text_color=stLTx, bgcolor=stLBg, text_size=size.small)
table.cell(dash, 2, 11, stS, text_color=stSTx, bgcolor=stSBg, text_size=size.small)
// Separator row
table.cell(dash, 0, 12, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
table.cell(dash, 1, 12, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
table.cell(dash, 2, 12, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Signal Stats panel — live performance over the visible history (tune knobs by numbers)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table statsTbl = table.new(position.bottom_right, 3, 6,
bgcolor=color.new(color.white, 5),
border_color=color.new(color.gray, 60),
border_width=1,
frame_color=color.new(color.gray, 40),
frame_width=1,
force_overlay=true)
f_avgRcol(v) => v > 0 ? cGood : v < 0 ? cBad : tc
if showStats and barstate.islast
float winL = statNL > 0 ? statWinsL / float(statNL) * 100.0 : 0.0
float winS = statNS > 0 ? statWinsS / float(statNS) * 100.0 : 0.0
float avgRL = statNL > 0 ? statSumRL / float(statNL) : 0.0
float avgRS = statNS > 0 ? statSumRS / float(statNS) : 0.0
float failL = statNL > 0 ? statFailsL / float(statNL) * 100.0 : 0.0
float failS = statNS > 0 ? statFailsS / float(statNS) * 100.0 : 0.0
table.cell(statsTbl, 0, 0, "Stats", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(statsTbl, 1, 0, "Long", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(statsTbl, 2, 0, "Short", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(statsTbl, 0, 1, "N", text_color=tc, text_size=size.small)
table.cell(statsTbl, 1, 1, str.tostring(statNL), text_color=tc, text_size=size.small)
table.cell(statsTbl, 2, 1, str.tostring(statNS), text_color=tc, text_size=size.small)
table.cell(statsTbl, 0, 2, "Win %", text_color=tc, text_size=size.small)
table.cell(statsTbl, 1, 2, str.tostring(winL, "#") + "%", text_color=winL >= 50 ? cGood : tc, text_size=size.small)
table.cell(statsTbl, 2, 2, str.tostring(winS, "#") + "%", text_color=winS >= 50 ? cGood : tc, text_size=size.small)
table.cell(statsTbl, 0, 3, "Avg R", text_color=tc, text_size=size.small)
table.cell(statsTbl, 1, 3, str.tostring(avgRL, "+0.00;-0.00"), text_color=f_avgRcol(avgRL), text_size=size.small)
table.cell(statsTbl, 2, 3, str.tostring(avgRS, "+0.00;-0.00"), text_color=f_avgRcol(avgRS), text_size=size.small)
table.cell(statsTbl, 0, 4, "Net R", text_color=tc, text_size=size.small)
table.cell(statsTbl, 1, 4, str.tostring(statSumRL, "+0.0;-0.0"), text_color=f_avgRcol(statSumRL), text_size=size.small)
table.cell(statsTbl, 2, 4, str.tostring(statSumRS, "+0.0;-0.0"), text_color=f_avgRcol(statSumRS), text_size=size.small)
table.cell(statsTbl, 0, 5, "Fail %", text_color=tc, text_size=size.small)
table.cell(statsTbl, 1, 5, str.tostring(failL, "#") + "%", text_color=failL >= 50 ? cBad : tc, text_size=size.small)
table.cell(statsTbl, 2, 5, str.tostring(failS, "#") + "%", text_color=failS >= 50 ? cBad : tc, text_size=size.small)
// Layer test: avg R per situational bucket. If a pair/group separates clearly, that layer is a
// real, situation-driven filter; if not, it is dead weight. Regime (with/counter) and Setup
// (tradeable/watch) proved flat on the full sample; trigger-type and WT-extreme-depth are the
// new hypotheses for the long/short asymmetry.
var table layerTbl = table.new(position.middle_right, 3, 9,
bgcolor=color.new(color.white, 5),
border_color=color.new(color.gray, 60),
border_width=1,
frame_color=color.new(color.gray, 40),
frame_width=1,
force_overlay=true)
if showStats and barstate.islast
float arW = statN_w > 0 ? statR_w / float(statN_w) : 0.0
float arC = statN_c > 0 ? statR_c / float(statN_c) : 0.0
float arS = statN_s > 0 ? statR_s / float(statN_s) : 0.0
float arK = statN_k > 0 ? statR_k / float(statN_k) : 0.0
float arHk = statN_hk > 0 ? statR_hk / float(statN_hk) : 0.0
float arRv = statN_rv > 0 ? statR_rv / float(statN_rv) : 0.0
float arDp = statN_dp > 0 ? statR_dp / float(statN_dp) : 0.0
float arSh = statN_sh > 0 ? statR_sh / float(statN_sh) : 0.0
table.cell(layerTbl, 0, 0, "Layer", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(layerTbl, 1, 0, "AvgR", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(layerTbl, 2, 0, "N", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(layerTbl, 0, 1, "Regime+", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 1, str.tostring(arW, "+0.00;-0.00"), text_color=f_avgRcol(arW), text_size=size.small)
table.cell(layerTbl, 2, 1, str.tostring(statN_w), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 2, "Regime−", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 2, str.tostring(arC, "+0.00;-0.00"), text_color=f_avgRcol(arC), text_size=size.small)
table.cell(layerTbl, 2, 2, str.tostring(statN_c), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 3, "Trade✓", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 3, str.tostring(arS, "+0.00;-0.00"), text_color=f_avgRcol(arS), text_size=size.small)
table.cell(layerTbl, 2, 3, str.tostring(statN_s), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 4, "Watch", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 4, str.tostring(arK, "+0.00;-0.00"), text_color=f_avgRcol(arK), text_size=size.small)
table.cell(layerTbl, 2, 4, str.tostring(statN_k), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 5, "HookExit", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 5, str.tostring(arHk, "+0.00;-0.00"), text_color=f_avgRcol(arHk), text_size=size.small)
table.cell(layerTbl, 2, 5, str.tostring(statN_hk), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 6, "CrossRev", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 6, str.tostring(arRv, "+0.00;-0.00"), text_color=f_avgRcol(arRv), text_size=size.small)
table.cell(layerTbl, 2, 6, str.tostring(statN_rv), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 7, "Deep≥60", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 7, str.tostring(arDp, "+0.00;-0.00"), text_color=f_avgRcol(arDp), text_size=size.small)
table.cell(layerTbl, 2, 7, str.tostring(statN_dp), text_color=tc, text_size=size.small)
table.cell(layerTbl, 0, 8, "Shallow", text_color=tc, text_size=size.small)
table.cell(layerTbl, 1, 8, str.tostring(arSh, "+0.00;-0.00"), text_color=f_avgRcol(arSh), text_size=size.small)
table.cell(layerTbl, 2, 8, str.tostring(statN_sh), text_color=tc, text_size=size.small)
// Situation panel: avg R and N per situational bucket, split Long / Short. A bucket pair is a
// candidate driver of the long/short asymmetry only if it separates on BOTH sides in the same
// direction (e.g. "with HTF" better than "against HTF" for longs AND shorts) — a split that only
// shows on one side is the asymmetry itself, not its cause.
var table sitTbl = table.new(position.bottom_left, 3, 10,
bgcolor=color.new(color.white, 5),
border_color=color.new(color.gray, 60),
border_width=1,
frame_color=color.new(color.gray, 40),
frame_width=1,
force_overlay=true)
f_sitCell(int bucket, int dir) =>
int i = bucket * 2 + dir
int n = sitN.get(i)
float ar = n > 0 ? sitR.get(i) / float(n) : 0.0
[n > 0 ? str.tostring(ar, "+0.00;-0.00") + " ·" + str.tostring(n) : "—", n > 0 ? f_avgRcol(ar) : tc]
if showStats and barstate.islast
table.cell(sitTbl, 0, 0, "Situation", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(sitTbl, 1, 0, "Long R ·N", text_color=tc, bgcolor=hd, text_size=size.small)
table.cell(sitTbl, 2, 0, "Short R ·N", text_color=tc, bgcolor=hd, text_size=size.small)
array<string> sitNames = array.from("HTF with", "HTF against", "Drift with", "Drift against", "Room", "Near", "Extended", "Vola high", "Vola low")
for b = 0 to 8
[txtL, colL] = f_sitCell(b, 0)
[txtS, colS] = f_sitCell(b, 1)
table.cell(sitTbl, 0, b + 1, sitNames.get(b), text_color=tc, text_size=size.small)
table.cell(sitTbl, 1, b + 1, txtL, text_color=colL, text_size=size.small)
table.cell(sitTbl, 2, b + 1, txtS, text_color=colS, text_size=size.small)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Alerts
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed
alertcondition(alertOk and (longSignal), title="MTF WT Hunter Long", message="WTOH · LONG · {{ticker}} {{interval}}")
alertcondition(alertOk and (shortSignal), title="MTF WT Hunter Short", message="WTOH · SHORT · {{ticker}} {{interval}}")
alertcondition(alertOk and (longSignal and longTradeable), title="MTF WT Hunter Long (Tradeable)", message="WTOH · LONG TRADEABLE · {{ticker}} {{interval}}")
alertcondition(alertOk and (shortSignal and shortTradeable), title="MTF WT Hunter Short (Tradeable)", message="WTOH · SHORT TRADEABLE · {{ticker}} {{interval}}")
alertcondition(alertOk and (sampledLongSignal), title="MTF WT Hunter Long (Sampled)", message="WTOH · LONG SAMPLED · {{ticker}} {{interval}}")
alertcondition(alertOk and (sampledShortSignal), title="MTF WT Hunter Short (Sampled)", message="WTOH · SHORT SAMPLED · {{ticker}} {{interval}}")
alertcondition(alertOk and (longExit), title="MTF WT Hunter Long Closed", message="WTOH · EXIT LONG · {{ticker}} {{interval}}")
alertcondition(alertOk and (shortExit), title="MTF WT Hunter Short Closed", message="WTOH · EXIT SHORT · {{ticker}} {{interval}}")
Show all 1,480 lines