A strategy meant for a 5-minute chart that detects a break of a delta-based level on a 15-minute context, waits for a retest from the correct side, then enters on a reclaim candle.
//@version=6
strategy(
title = "Delta Break & Retest - Stage 8.0.2 Profile + Entry Fix",
shorttitle = "DBR S8.0.2",
overlay = true,
max_bars_back = 5000,
max_boxes_count = 500,
max_lines_count = 500,
max_labels_count = 300,
default_qty_type = strategy.fixed,
default_qty_value = 1,
calc_on_order_fills = false,
calc_on_every_tick = true,
process_orders_on_close = true,
initial_capital = 100000,
margin_long = 3,
margin_short = 3
)
// ============================================================================
// DELTA BREAK & RETEST - STAGE 8
// ----------------------------------------------------------------------------
// RUN ON MNQ 5m.
//
// 15m = break-context / POI event.
// 4H = directional bias.
// 1H = active trend quality.
// 5m = retest + reclaim + execution.
//
// ENTRY SEQUENCE:
// BREAK
// -> RETEST FROM CORRECT SIDE
// -> PRESSURE ROTATION
// -> RECLAIM
// -> 4H + 1H AGREEMENT
// -> ENTRY
//
// IMPORTANT:
// Pine does not expose exchange-native bid/ask aggressor delta here.
// All delta/pressure values below are estimated from OHLCV candle pressure.
// ============================================================================
// ============================================================================
// HELPERS
// ============================================================================
f_clamp(float v, float lo, float hi) =>
math.max(lo, math.min(hi, v))
f_delta(float o, float h, float l, float c, float v) =>
float safeRange = math.max(h - l, syminfo.mintick)
float pressure = f_clamp((c - o) / safeRange, -1.0, 1.0)
v * pressure
f_typeCodeName(int code) =>
code == 1 ? "Delta POC" :
code == 2 ? "Delta Extreme +" :
code == 3 ? "Delta Extreme -" :
code == 4 ? "Delta Flip" :
code == 5 ? "Absorption Node" :
"?"
float barDelta = f_delta(open, high, low, close, nz(volume, 0.0))
float atr5m = math.max(nz(ta.atr(14), ta.tr(true)), syminfo.mintick)
// ============================================================================
// 01 - 15m BREAK CONTEXT INPUTS
// ============================================================================
string G_BREAK = "01 - 15m Break Context"
string breakContextTimeframeInput = input.timeframe(
"15",
"Break-Context Timeframe",
group = G_BREAK
)
int profileRowsInput = input.int(
40,
"POI Extraction Rows",
minval = 10,
maxval = 120,
group = G_BREAK
)
float poiAtrMultInput = input.float(
0.25,
"POI Zone Half-Width (x ATR)",
minval = 0.05,
maxval = 2.0,
step = 0.05,
group = G_BREAK
)
int minEndedSessionBarsInput = input.int(
10,
"Minimum Bars To Finalize Session",
minval = 3,
maxval = 100,
group = G_BREAK
)
bool showAbsorptionInput = input.bool(
true,
"Track Absorption Node",
group = G_BREAK
)
float minBreakAtrMultInput = input.float(
0.10,
"Minimum Break Buffer (x ATR)",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = G_BREAK
)
bool requireBreakDeltaConfirmationInput = input.bool(
true,
"Require Estimated Delta On 15m Break",
group = G_BREAK
)
// ============================================================================
// 02 - RETEST / TRIGGER INPUTS
// ============================================================================
string G_EXEC = "02 - Retest / Trigger"
float retestWindowHoursInput = input.float(
6.0,
"Maximum Break-To-Retest Window (Hours)",
minval = 0.5,
maxval = 48.0,
step = 0.5,
group = G_EXEC
)
int minBarsAfterRetestInput = input.int(
0,
"Minimum Bars After Retest Before Trigger",
minval = 0,
maxval = 5,
tooltip = "0 lets the trigger check run on the same bar as the retest touch itself, so a diamond can print on the same bar as its circle whenever reclaim/candle-quality already line up there -- the trigger always fires on the EARLIEST qualifying bar in its window, never a later one, so lowering this is what pulls diamonds as close as possible to circles.",
group = G_EXEC
)
int maxBarsAfterRetestInput = input.int(
4,
"Maximum Bars After Retest To Trigger",
minval = 1,
maxval = 12,
group = G_EXEC
)
float maxRetestOvershootAtrInput = input.float(
0.10,
"Maximum Retest Overshoot Beyond Far Edge (ATR)",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = G_EXEC
)
float reclaimBufferAtrInput = input.float(
0.05,
"Minimum Reclaim Beyond Zone Edge (ATR)",
minval = 0.0,
maxval = 0.50,
step = 0.01,
group = G_EXEC
)
float maxEntryExtensionAtrInput = input.float(
0.50,
"Maximum Entry Extension From Zone (ATR)",
minval = 0.10,
maxval = 1.50,
step = 0.05,
group = G_EXEC
)
float minimumTriggerBodyFractionInput = input.float(
0.30,
"Minimum Trigger Candle Body / Range",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = G_EXEC
)
float minimumTriggerCloseLocationInput = input.float(
0.58,
"Minimum Trigger Close Location",
minval = 0.50,
maxval = 0.95,
step = 0.05,
group = G_EXEC
)
bool requireTriggerDeltaConfirmationInput = input.bool(
false,
"Require Pressure Rotation On Trigger",
tooltip = "OFF by default: on-device funnel diagnostics showed this gate (an OHLCV-estimated pressure proxy, not real order-flow delta) blocking ~76% of every retest attempt on MNQ 5m, far more than any other single gate. Turn back on only after re-tuning its thresholds/window against fresh funnel data -- do not just flip it on blind.",
group = G_EXEC
)
int executionDeltaWindowInput = input.int(
3,
"Execution Pressure Window",
minval = 1,
maxval = 8,
group = G_EXEC
)
float minimumDeltaPressureInput = input.float(
8.0,
"Minimum Trigger Pressure %",
minval = 0.0,
maxval = 60.0,
step = 1.0,
group = G_EXEC
)
float minimumDeltaRotationInput = input.float(
10.0,
"Minimum Pressure Rotation From Retest %",
minval = 0.0,
maxval = 80.0,
step = 1.0,
group = G_EXEC
)
bool requireRvolOnTriggerInput = input.bool(
false,
"Require RVOL On Trigger",
group = G_EXEC
)
int triggerRvolLengthInput = input.int(
20,
"Trigger RVOL Length",
minval = 5,
maxval = 100,
group = G_EXEC
)
float minimumTriggerRvolInput = input.float(
1.05,
"Minimum Trigger RVOL",
minval = 0.50,
maxval = 3.0,
step = 0.05,
group = G_EXEC
)
// ============================================================================
// 03 - MTF CONTEXT INPUTS
// ============================================================================
string G_MTF = "03 - MTF Context"
string biasTimeframeInput = input.timeframe(
"240",
"4H Bias Timeframe",
group = G_MTF
)
int pivotLeftInput = input.int(
3,
"4H Pivot Left",
minval = 1,
maxval = 20,
group = G_MTF
)
int pivotRightInput = input.int(
3,
"4H Pivot Right",
minval = 1,
maxval = 20,
group = G_MTF
)
int biasEmaLengthInput = input.int(
50,
"4H EMA Length",
minval = 2,
group = G_MTF
)
int biasSlopeLookbackInput = input.int(
5,
"4H EMA Slope Lookback",
minval = 1,
maxval = 50,
group = G_MTF
)
string trendTimeframeInput = input.timeframe(
"60",
"1H Trend Timeframe",
group = G_MTF
)
int adxLengthInput = input.int(
14,
"1H ADX / DI Length",
minval = 1,
group = G_MTF
)
float adxMinimumInput = input.float(
20.0,
"1H Minimum ADX",
minval = 0.0,
step = 0.5,
group = G_MTF
)
int fastEmaInput = input.int(
21,
"1H Fast EMA",
minval = 1,
group = G_MTF
)
int slowEmaInput = input.int(
50,
"1H Slow EMA",
minval = 2,
group = G_MTF
)
int erLengthInput = input.int(
10,
"1H Efficiency Ratio Length",
minval = 2,
group = G_MTF
)
float erMinimumInput = input.float(
0.30,
"1H Minimum Efficiency Ratio",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = G_MTF
)
bool require1hTrendAgreementInput = input.bool(
true,
"Require 1H Trend Agreement",
group = G_MTF
)
// ============================================================================
// 04 - COOLDOWN / ORDERS / RISK
// ============================================================================
string G_RISK = "04 - Cooldown / Orders / Risk"
int globalCooldownWinBarsInput = input.int(
6,
"Global Cooldown After Win",
minval = 0,
group = G_RISK
)
int globalCooldownLossBarsInput = input.int(
12,
"Global Cooldown After Loss",
minval = 0,
group = G_RISK
)
int directionCooldownBarsInput = input.int(
12,
"Direction Cooldown After Loss",
minval = 0,
group = G_RISK
)
int consecutiveLossLimitInput = input.int(
2,
"Consecutive Loss Session Limit",
minval = 1,
group = G_RISK
)
bool requireAlignedEntryInput = input.bool(
true,
"Require Full Trend Alignment",
group = G_RISK
)
bool enableDeltaFlipEntriesInput = input.bool(
false,
"Enable Delta Flip POI Entries",
group = G_RISK
)
float stopBufferAtrMultInput = input.float(
0.10,
"Stop Buffer Beyond Zone (x ATR)",
minval = 0.0,
maxval = 1.0,
step = 0.05,
group = G_RISK
)
float maximumEntryRiskAtrInput = input.float(
1.25,
"Maximum Entry-To-Stop Risk (ATR)",
minval = 0.25,
maxval = 4.0,
step = 0.05,
group = G_RISK
)
float rMultipleInput = input.float(
2.0,
"Target R-Multiple",
minval = 0.5,
maxval = 10.0,
step = 0.5,
group = G_RISK
)
int contractsInput = input.int(
1,
"Contracts Per Entry",
minval = 1,
maxval = 100,
group = G_RISK
)
// ============================================================================
// 05 - SIGNAL VISUALS
// ============================================================================
string G_SIG = "05 - Signal Visuals"
bool showRetestTouchMarkerInput = input.bool(
true,
"Show Retest Touch Marker",
group = G_SIG
)
bool showEntrySignalInput = input.bool(
true,
"Show Actual Entry Signal",
group = G_SIG
)
bool showQualifiedTriggerInput = input.bool(
true,
"Show Qualified Retest Trigger",
tooltip = "Diamond = price/pressure retest trigger. Triangle = actual strategy entry after trend, cooldown, POI-type and risk gates.",
group = G_SIG
)
string entrySignalSizeInput = input.string(
"Normal",
"Entry Signal Size",
options = ["Tiny", "Small", "Normal", "Large", "Huge"],
group = G_SIG
)
color longEntryColorInput = input.color(
color.aqua,
"Long Entry Color",
group = G_SIG
)
color shortEntryColorInput = input.color(
color.orange,
"Short Entry Color",
group = G_SIG
)
color longTriggerColorInput = input.color(
color.rgb(70, 160, 255),
"Long Qualified Trigger Color",
group = G_SIG
)
color shortTriggerColorInput = input.color(
color.rgb(255, 120, 70),
"Short Qualified Trigger Color",
group = G_SIG
)
color retestLongColorInput = input.color(
color.new(color.aqua, 20),
"Long Retest Touch Color",
group = G_SIG
)
color retestShortColorInput = input.color(
color.new(color.orange, 20),
"Short Retest Touch Color",
group = G_SIG
)
bool highlightEntryBarInput = input.bool(
true,
"Highlight Entry Candle",
group = G_SIG
)
// ============================================================================
// 06 - VOLUME DELTA PROFILE INPUTS
// ============================================================================
string G_PROFILE = "06 - Volume Delta Profile"
bool showVolumeDeltaProfileInput = input.bool(
true,
"Show Volume Delta Profile",
group = G_PROFILE
)
int deltaProfileLookbackInput = input.int(
360,
"Profile Lookback Bars",
minval = 50,
maxval = 1500,
step = 10,
group = G_PROFILE
)
int deltaProfileRowsInput = input.int(
40,
"Profile Rows",
minval = 10,
maxval = 100,
step = 5,
group = G_PROFILE
)
int deltaProfileWidthInput = input.int(
24,
"Profile Width (Bars)",
minval = 8,
maxval = 80,
group = G_PROFILE
)
string deltaProfilePlacementInput = input.string(
"Right Side",
"Profile Placement",
options = ["Overlay Left", "Right Side"],
tooltip = "Right Side draws the profile into the chart's blank right margin, past the last candle -- set the offset below to at least the Profile Width so it fully clears price instead of sitting on top of it. Overlay Left instead draws it directly over the last N historical candles (kept for anyone who prefers that compact look on a narrow phone screen), which is why it was the old default.",
group = G_PROFILE
)
int deltaProfileOffsetInput = input.int(
36,
"Profile Horizontal Offset",
minval = 0,
maxval = 60,
tooltip = "In Right Side mode this is measured from the current bar back to the LEFT edge of the profile -- keep it >= Profile Width so the box lands entirely in blank space past the last candle instead of overlapping price history.",
group = G_PROFILE
)
string deltaProfileMethodInput = input.string(
"Candle Pressure",
"Delta Estimation Method",
options = ["Candle Pressure", "Bar Polarity"],
group = G_PROFILE
)
bool showTotalVolumeFrameInput = input.bool(
true,
"Show Total Volume Frame",
group = G_PROFILE
)
bool showDeltaBarsInput = input.bool(
true,
"Show Delta Bars",
group = G_PROFILE
)
bool showProfilePocInput = input.bool(
true,
"Show Profile POC",
group = G_PROFILE
)
color profileBullColorInput = input.color(
color.rgb(55, 155, 255),
"Positive Delta Color",
group = G_PROFILE
)
color profileBearColorInput = input.color(
color.rgb(245, 82, 95),
"Negative Delta Color",
group = G_PROFILE
)
color profileTotalColorInput = input.color(
color.new(color.gray, 75),
"Total Volume Frame Color",
group = G_PROFILE
)
color profilePocColorInput = input.color(
color.yellow,
"Profile POC Color",
group = G_PROFILE
)
int profileTransparencyInput = input.int(
35,
"Delta Bar Transparency",
minval = 0,
maxval = 90,
group = G_PROFILE
)
// ============================================================================
// 07 - DASHBOARD
// ============================================================================
string G_DASH = "07 - Dashboard"
bool showDashboardInput = input.bool(
true,
"Show Dashboard",
group = G_DASH
)
int deltaStrengthWindowInput = input.int(
20,
"Buyer / Seller Strength Window",
minval = 5,
maxval = 200,
group = G_DASH
)
bool showFunnelDiagnosticsInput = input.bool(
true,
"Show Retest Funnel Diagnostics",
tooltip = "Second table: counts how many break events reach each stage of the retest/trigger chain, and which gate blocks a RETESTED bar most often. Use this to find where signals are dying without Strategy Tester access.",
group = G_DASH
)
string dashboardSizeInput = input.string(
"Small",
"Dashboard Text Size",
options = ["Tiny", "Small", "Normal", "Large"],
tooltip = "Shrinks both tables' text (and therefore their footprint) without hiding any row -- use Tiny on a small phone screen.",
group = G_DASH
)
string dashboardPositionInput = input.string(
"Bottom Right",
"Main Dashboard Position",
options = ["Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right"],
group = G_DASH
)
string funnelPositionInput = input.string(
"Bottom Left",
"Funnel Diagnostics Position",
options = ["Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right"],
tooltip = "Keep this different from Main Dashboard Position or the two tables will stack on top of each other.",
group = G_DASH
)
f_tablePosition(string label) =>
switch label
"Top Left" => position.top_left
"Top Center" => position.top_center
"Top Right" => position.top_right
"Middle Left" => position.middle_left
"Middle Center" => position.middle_center
"Middle Right" => position.middle_right
"Bottom Left" => position.bottom_left
"Bottom Center" => position.bottom_center
"Bottom Right" => position.bottom_right
=> position.bottom_right
f_tableTextSize(string label) =>
switch label
"Tiny" => size.tiny
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.small
tableTextSize = f_tableTextSize(dashboardSizeInput)
// ============================================================================
// EXECUTION PRESSURE / CANDLE QUALITY
// ============================================================================
float executionDeltaSum = 0.0
float executionVolumeSum = 0.0
for i = 0 to executionDeltaWindowInput - 1
float execVolume = nz(volume[i], 0.0)
executionDeltaSum += f_delta(open[i], high[i], low[i], close[i], execVolume)
executionVolumeSum += execVolume
float executionDeltaPressure = (
executionVolumeSum > 0 ?
executionDeltaSum / executionVolumeSum * 100.0 :
0.0
)
float triggerVolumeAverage = ta.sma(volume, triggerRvolLengthInput)
float triggerRvol = triggerVolumeAverage > 0 ? volume / triggerVolumeAverage : 0.0
float triggerRange = math.max(high - low, syminfo.mintick)
float triggerBodyFraction = math.abs(close - open) / triggerRange
float triggerCloseLocation = (close - low) / triggerRange
// ============================================================================
// SESSION POI EXTRACTION
// ============================================================================
f_extractSessionPois(int barsBack, int rows) =>
float scanHigh = -1.0e20
float scanLow = 1.0e20
for i = 1 to barsBack
scanHigh := math.max(scanHigh, high[i])
scanLow := math.min(scanLow, low[i])
float rowStep = math.max((scanHigh - scanLow) / rows, syminfo.mintick)
array<float> rowDelta = array.new_float(rows, 0.0)
array<float> rowRange = array.new_float(rows, 0.0)
array<float> rowVolume = array.new_float(rows, 0.0)
for i = 1 to barsBack
float d = f_delta(open[i], high[i], low[i], close[i], nz(volume[i], 0.0))
int row = int(math.floor((close[i] - scanLow) / rowStep))
row := math.max(0, math.min(rows - 1, row))
array.set(rowDelta, row, array.get(rowDelta, row) + d)
array.set(rowRange, row, array.get(rowRange, row) + (high[i] - low[i]))
array.set(rowVolume, row, array.get(rowVolume, row) + nz(volume[i], 0.0))
float maxAbs = -1.0
int pocRow = -1
for row = 0 to rows - 1
float d = array.get(rowDelta, row)
if math.abs(d) > maxAbs
maxAbs := math.abs(d)
pocRow := row
float pocPrice = pocRow >= 0 ? scanLow + (pocRow + 0.5) * rowStep : na
float maxPos = 0.0
int maxPosRow = -1
float maxNeg = 0.0
int maxNegRow = -1
for row = 0 to rows - 1
float d = array.get(rowDelta, row)
if d > maxPos
maxPos := d
maxPosRow := row
if d < maxNeg
maxNeg := d
maxNegRow := row
float extremeHighPrice = maxPosRow >= 0 ? scanLow + (maxPosRow + 0.5) * rowStep : na
float extremeLowPrice = maxNegRow >= 0 ? scanLow + (maxNegRow + 0.5) * rowStep : na
float cum = 0.0
float prevCum = 0.0
float flipPrice = na
for row = 0 to rows - 1
prevCum := cum
cum := cum + array.get(rowDelta, row)
if row > 0 and ((prevCum <= 0 and cum > 0) or (prevCum >= 0 and cum < 0))
flipPrice := scanLow + row * rowStep
float totalVol = 0.0
for row = 0 to rows - 1
totalVol += array.get(rowVolume, row)
float avgVol = totalVol / rows
float bestRatio = -1.0
int absorptionRow = -1
for row = 0 to rows - 1
if row != pocRow and array.get(rowVolume, row) > avgVol * 0.5
float rng = array.get(rowRange, row)
float ratio = rng > 0 ? math.abs(array.get(rowDelta, row)) / rng : 0.0
if ratio > bestRatio
bestRatio := ratio
absorptionRow := row
float absorptionPrice = absorptionRow >= 0 ? scanLow + (absorptionRow + 0.5) * rowStep : na
[pocPrice, extremeHighPrice, extremeLowPrice, flipPrice, absorptionPrice]
// ============================================================================
// 15m BREAK CONTEXT
// ============================================================================
f_get15mBreakContextRaw() =>
float atr15Local = ta.atr(14)
bool newSessionLocal = ta.change(time("D")) != 0
var int sessionStartBarLocal = bar_index
int barsIntoSessionLocal = bar_index - sessionStartBarLocal + 1
var float pocLevel = na
var bool pocBroken = false
var float exHighLevel = na
var bool exHighBroken = false
var float exLowLevel = na
var bool exLowBroken = false
var float flipLevel = na
var bool flipBroken = false
var float absLevel = na
var bool absBroken = false
float eventDirection = na
float eventTypeCode = na
float eventZoneLow = na
float eventZoneHigh = na
if newSessionLocal
if bar_index > 0 and barsIntoSessionLocal > 1
int endedBarsLocal = bar_index - sessionStartBarLocal
if endedBarsLocal >= minEndedSessionBarsInput and not na(atr15Local)
[pocPx, exHighPx, exLowPx, flipPx, absPx] = f_extractSessionPois(
endedBarsLocal,
profileRowsInput
)
if not na(pocPx)
pocLevel := pocPx
pocBroken := false
if not na(exHighPx)
exHighLevel := exHighPx
exHighBroken := false
if not na(exLowPx)
exLowLevel := exLowPx
exLowBroken := false
if not na(flipPx)
flipLevel := flipPx
flipBroken := false
if showAbsorptionInput and not na(absPx)
absLevel := absPx
absBroken := false
sessionStartBarLocal := bar_index
float localBarDelta = f_delta(open, high, low, close, nz(volume, 0.0))
float buffer = not na(atr15Local) ? atr15Local * minBreakAtrMultInput : 0.0
float halfWidth = not na(atr15Local) ? atr15Local * poiAtrMultInput : na
bool deltaOkUp = not requireBreakDeltaConfirmationInput or localBarDelta > 0
bool deltaOkDown = not requireBreakDeltaConfirmationInput or localBarDelta < 0
if not na(pocLevel) and not pocBroken and not na(halfWidth)
float zLow = pocLevel - halfWidth
float zHigh = pocLevel + halfWidth
bool brokeUp = close > zHigh + buffer and close[1] <= zHigh + buffer
bool brokeDown = close < zLow - buffer and close[1] >= zLow - buffer
if brokeUp and deltaOkUp
pocBroken := true
eventDirection := 1.0
eventTypeCode := 1.0
eventZoneLow := zLow
eventZoneHigh := zHigh
else if brokeDown and deltaOkDown
pocBroken := true
eventDirection := -1.0
eventTypeCode := 1.0
eventZoneLow := zLow
eventZoneHigh := zHigh
if not na(exHighLevel) and not exHighBroken and not na(halfWidth)
float zLow = exHighLevel - halfWidth
float zHigh = exHighLevel + halfWidth
bool brokeUp = close > zHigh + buffer and close[1] <= zHigh + buffer
bool brokeDown = close < zLow - buffer and close[1] >= zLow - buffer
if brokeUp and deltaOkUp
exHighBroken := true
eventDirection := 1.0
eventTypeCode := 2.0
eventZoneLow := zLow
eventZoneHigh := zHigh
else if brokeDown and deltaOkDown
exHighBroken := true
eventDirection := -1.0
eventTypeCode := 2.0
eventZoneLow := zLow
eventZoneHigh := zHigh
if not na(exLowLevel) and not exLowBroken and not na(halfWidth)
float zLow = exLowLevel - halfWidth
float zHigh = exLowLevel + halfWidth
bool brokeUp = close > zHigh + buffer and close[1] <= zHigh + buffer
bool brokeDown = close < zLow - buffer and close[1] >= zLow - buffer
if brokeUp and deltaOkUp
exLowBroken := true
eventDirection := 1.0
eventTypeCode := 3.0
eventZoneLow := zLow
eventZoneHigh := zHigh
else if brokeDown and deltaOkDown
exLowBroken := true
eventDirection := -1.0
eventTypeCode := 3.0
eventZoneLow := zLow
eventZoneHigh := zHigh
if enableDeltaFlipEntriesInput and not na(flipLevel) and not flipBroken and not na(halfWidth)
float zLow = flipLevel - halfWidth
float zHigh = flipLevel + halfWidth
bool brokeUp = close > zHigh + buffer and close[1] <= zHigh + buffer
bool brokeDown = close < zLow - buffer and close[1] >= zLow - buffer
if brokeUp and deltaOkUp
flipBroken := true
eventDirection := 1.0
eventTypeCode := 4.0
eventZoneLow := zLow
eventZoneHigh := zHigh
else if brokeDown and deltaOkDown
flipBroken := true
eventDirection := -1.0
eventTypeCode := 4.0
eventZoneLow := zLow
eventZoneHigh := zHigh
if showAbsorptionInput and not na(absLevel) and not absBroken and not na(halfWidth)
float zLow = absLevel - halfWidth
float zHigh = absLevel + halfWidth
bool brokeUp = close > zHigh + buffer and close[1] <= zHigh + buffer
bool brokeDown = close < zLow - buffer and close[1] >= zLow - buffer
if brokeUp and deltaOkUp
absBroken := true
eventDirection := 1.0
eventTypeCode := 5.0
eventZoneLow := zLow
eventZoneHigh := zHigh
else if brokeDown and deltaOkDown
absBroken := true
eventDirection := -1.0
eventTypeCode := 5.0
eventZoneLow := zLow
eventZoneHigh := zHigh
[eventZoneLow, eventZoneHigh, eventDirection, eventTypeCode, float(time)]
f_get15mBreakContext() =>
[zoneLow, zoneHigh, direction, typeCode, eventTime] = f_get15mBreakContextRaw()
[zoneLow[1], zoneHigh[1], direction[1], typeCode[1], eventTime[1]]
[breakZoneLow, breakZoneHigh, breakDirectionRaw, breakTypeCodeRaw, breakEventTimeRaw] = request.security(
syminfo.tickerid,
breakContextTimeframeInput,
f_get15mBreakContext(),
gaps = barmerge.gaps_off,
lookahead = barmerge.lookahead_on
)
bool haveBrokenAny = not na(breakDirectionRaw)
int breakDirection = haveBrokenAny ? int(breakDirectionRaw) : 0
int breakTypeCode = haveBrokenAny ? int(breakTypeCodeRaw) : 0
int breakEventTime = haveBrokenAny ? int(breakEventTimeRaw) : na
// ============================================================================
// 4H BIAS
// ============================================================================
f_get4hContext() =>
float ph = ta.pivothigh(high, pivotLeftInput, pivotRightInput)
float pl = ta.pivotlow(low, pivotLeftInput, pivotRightInput)
float latestHigh = ta.valuewhen(not na(ph), ph, 0)
float previousHigh = ta.valuewhen(not na(ph), ph, 1)
float latestLow = ta.valuewhen(not na(pl), pl, 0)
float previousLow = ta.valuewhen(not na(pl), pl, 1)
float ema = ta.ema(close, biasEmaLengthInput)
bool aboveEma = close > ema
bool slopeUp = ema > ema[biasSlopeLookbackInput]
bool slopeDown = ema < ema[biasSlopeLookbackInput]
[latestHigh[1], previousHigh[1], latestLow[1], previousLow[1], aboveEma[1], slopeUp[1], slopeDown[1]]
[lastPivotHigh, prevPivotHigh, lastPivotLow, prevPivotLow, closeAboveEma4h, emaSlopeUp4h, emaSlopeDown4h] = request.security(
syminfo.tickerid,
biasTimeframeInput,
f_get4hContext(),
gaps = barmerge.gaps_off,
lookahead = barmerge.lookahead_on
)
bool haveEnoughPivots = (
not na(lastPivotHigh) and
not na(prevPivotHigh) and
not na(lastPivotLow) and
not na(prevPivotLow)
)
bool higherHigh = haveEnoughPivots and lastPivotHigh > prevPivotHigh
bool lowerHigh = haveEnoughPivots and lastPivotHigh < prevPivotHigh
bool higherLow = haveEnoughPivots and lastPivotLow > prevPivotLow
bool lowerLow = haveEnoughPivots and lastPivotLow < prevPivotLow
bool structureUp = higherHigh and higherLow
bool structureDown = lowerHigh and lowerLow
bool slowFilterUp = closeAboveEma4h and emaSlopeUp4h
bool slowFilterDown = not closeAboveEma4h and emaSlopeDown4h
string bias = (
structureUp and slowFilterUp ?
"LONG_ONLY" :
structureDown and slowFilterDown ?
"SHORT_ONLY" :
"NO_TRADE"
)
// ============================================================================
// 1H TREND
// ============================================================================
f_efficiencyRatio(int length) =>
float netMove = math.abs(close - close[length])
float sumMoves = 0.0
for i = 0 to length - 1
sumMoves += math.abs(close[i] - close[i + 1])
sumMoves > 0 ? netMove / sumMoves : 0.0
f_get1hContext() =>
[plusDi, minusDi, adx] = ta.dmi(adxLengthInput, adxLengthInput)
float emaFast = ta.ema(close, fastEmaInput)
float emaSlow = ta.ema(close, slowEmaInput)
float er = f_efficiencyRatio(erLengthInput)
[adx[1], emaFast[1], emaSlow[1], er[1]]
[adx1h, emaFast1h, emaSlow1h, er1h] = request.security(
syminfo.tickerid,
trendTimeframeInput,
f_get1hContext(),
gaps = barmerge.gaps_off,
lookahead = barmerge.lookahead_on
)
bool adxOk = adx1h >= adxMinimumInput
bool erOk = er1h >= erMinimumInput
bool emaStackBullish1h = emaFast1h > emaSlow1h
bool emaStackBearish1h = emaFast1h < emaSlow1h
bool long1hTrendOk = (
emaStackBullish1h and
adxOk and
erOk
)
bool short1hTrendOk = (
emaStackBearish1h and
adxOk and
erOk
)
bool fullLongContext = (
bias == "LONG_ONLY" and
(
not require1hTrendAgreementInput or
long1hTrendOk
)
)
bool fullShortContext = (
bias == "SHORT_ONLY" and
(
not require1hTrendAgreementInput or
short1hTrendOk
)
)
// ============================================================================
// COOLDOWN MODULE
// ============================================================================
type CooldownState
int lastExitBarIndex = na
bool lastExitWasWin = false
int lastLongLossBar = na
int lastShortLossBar = na
int lossesThisSession = 0
array<string> usedPoiIds
f_poiUsed(array<string> usedIds, string poiId) =>
bool found = false
if array.size(usedIds) > 0
for i = 0 to array.size(usedIds) - 1
if array.get(usedIds, i) == poiId
found := true
found
f_canTrade(
CooldownState state,
string direction,
string poiId,
int barIdx,
int winBars,
int lossBars,
int dirBars,
int lossLimit
) =>
string reason = "OK"
bool tradeable = true
if state.lossesThisSession >= lossLimit
tradeable := false
reason := "CONSECUTIVE_LOSS_LOCKOUT"
if tradeable and not na(state.lastExitBarIndex)
int requiredBars = state.lastExitWasWin ? winBars : lossBars
if barIdx - state.lastExitBarIndex <= requiredBars
tradeable := false
reason := "GLOBAL_POST_TRADE"
if tradeable and f_poiUsed(state.usedPoiIds, poiId)
tradeable := false
reason := "POI_LOCKOUT"
if tradeable
int lastDirLoss = (
direction == "long" ?
state.lastLongLossBar :
state.lastShortLossBar
)
if not na(lastDirLoss) and barIdx - lastDirLoss <= dirBars
tradeable := false
reason := "DIRECTION_LOCKOUT"
[tradeable, reason]
f_recordExit(
CooldownState state,
int barIdx,
bool isWin,
string direction,
string poiId
) =>
state.lastExitBarIndex := barIdx
state.lastExitWasWin := isWin
if not isWin
if direction == "long"
state.lastLongLossBar := barIdx
else
state.lastShortLossBar := barIdx
state.lossesThisSession += 1
if not f_poiUsed(state.usedPoiIds, poiId)
array.push(state.usedPoiIds, poiId)
f_resetSession(CooldownState state) =>
state.lossesThisSession := 0
array.clear(state.usedPoiIds)
var CooldownState cd = CooldownState.new(
usedPoiIds = array.new_string()
)
bool newDay5m = ta.change(time("D")) != 0
if barstate.isconfirmed and newDay5m
f_resetSession(cd)
// ============================================================================
// RETEST / TRIGGER STATE MACHINE
// ============================================================================
var float activeZoneLow = na
var float activeZoneHigh = na
var int activeDirection = 0
var int activeTypeCode = 0
var int activeEventTime = na
var string retestState = "NONE"
var bool activeTriggerAligned = false
var int retestTouchBar = na
var float retestTouchPressure = na
// -- retest funnel diagnostics: count where events die in the chain --
var int cEvents = 0
var int cRetested = 0
var int cTriggered = 0
var int cExpired = 0
var int cInvalidated = 0
var int cEvalBars = 0
var int cReclaimFail = 0
var int cExtensionFail = 0
var int cBodyFail = 0
var int cLocationFail = 0
var int cDeltaFail = 0
var int cRvolFail = 0
bool newBreakEvent = (
breakDirection != 0 and
(
na(activeEventTime) or
breakEventTime != activeEventTime
)
)
if newBreakEvent
cEvents += 1
activeZoneLow := breakZoneLow
activeZoneHigh := breakZoneHigh
activeDirection := breakDirection
activeTypeCode := breakTypeCode
activeEventTime := breakEventTime
retestState := "WATCHING"
activeTriggerAligned := false
retestTouchBar := na
retestTouchPressure := na
int hoursSinceEvent = (
na(activeEventTime) ?
na :
int((time - activeEventTime) / (60 * 60 * 1000))
)
bool windowExpired = (
not na(activeEventTime) and
hoursSinceEvent > retestWindowHoursInput
)
bool retestLongTouchNow = false
bool retestShortTouchNow = false
if retestState == "WATCHING"
if windowExpired
retestState := "EXPIRED"
cExpired += 1
else
bool failedBeforeRetestLong = (
activeDirection == 1 and
close < activeZoneLow
)
bool failedBeforeRetestShort = (
activeDirection == -1 and
close > activeZoneHigh
)
if failedBeforeRetestLong or failedBeforeRetestShort
retestState := "INVALIDATED"
cInvalidated += 1
else
bool validLongTouch = (
activeDirection == 1 and
close[1] > activeZoneHigh and
low <= activeZoneHigh and
high >= activeZoneLow and
low >= activeZoneLow -
atr5m * maxRetestOvershootAtrInput
)
bool validShortTouch = (
activeDirection == -1 and
close[1] < activeZoneLow and
high >= activeZoneLow and
low <= activeZoneHigh and
high <= activeZoneHigh +
atr5m * maxRetestOvershootAtrInput
)
if validLongTouch or validShortTouch
retestState := "RETESTED"
cRetested += 1
retestTouchBar := bar_index
retestTouchPressure := executionDeltaPressure
retestLongTouchNow := validLongTouch
retestShortTouchNow := validShortTouch
bool triggerUpNow = false
bool triggerDownNow = false
bool chartIsRecommended5m = timeframe.in_seconds(timeframe.period) == 300
if retestState == "RETESTED"
int barsAfterRetest = (
na(retestTouchBar) ?
100000 :
bar_index - retestTouchBar
)
bool retestTooOld = (
barsAfterRetest > maxBarsAfterRetestInput
)
bool invalidateLong = (
activeDirection == 1 and
close <
activeZoneLow -
atr5m * maxRetestOvershootAtrInput
)
bool invalidateShort = (
activeDirection == -1 and
close >
activeZoneHigh +
atr5m * maxRetestOvershootAtrInput
)
if windowExpired or retestTooOld
retestState := "EXPIRED"
cExpired += 1
else if invalidateLong or invalidateShort
retestState := "INVALIDATED"
cInvalidated += 1
else if barsAfterRetest >= minBarsAfterRetestInput
float reclaimBuffer = atr5m * reclaimBufferAtrInput
float maxExtension = atr5m * maxEntryExtensionAtrInput
bool priceReclaimLong = (
activeDirection == 1 and
close > activeZoneHigh + reclaimBuffer
)
bool priceReclaimShort = (
activeDirection == -1 and
close < activeZoneLow - reclaimBuffer
)
bool extensionLongOk = (
close - activeZoneHigh <= maxExtension
)
bool extensionShortOk = (
activeZoneLow - close <= maxExtension
)
bool candleBodyOk = (
triggerBodyFraction >= minimumTriggerBodyFractionInput
)
bool candleLocationLongOk = (
triggerCloseLocation >= minimumTriggerCloseLocationInput
)
bool candleLocationShortOk = (
triggerCloseLocation <= 1.0 - minimumTriggerCloseLocationInput
)
float touchPressure = nz(
retestTouchPressure,
0.0
)
bool deltaLongOk = (
not requireTriggerDeltaConfirmationInput or
(
executionDeltaPressure >= minimumDeltaPressureInput and
executionDeltaPressure - touchPressure >= minimumDeltaRotationInput
)
)
bool deltaShortOk = (
not requireTriggerDeltaConfirmationInput or
(
executionDeltaPressure <= -minimumDeltaPressureInput and
touchPressure - executionDeltaPressure >= minimumDeltaRotationInput
)
)
bool rvolOk = (
not requireRvolOnTriggerInput or
triggerRvol >= minimumTriggerRvolInput
)
triggerUpNow := (
priceReclaimLong and
extensionLongOk and
candleBodyOk and
candleLocationLongOk and
deltaLongOk and
rvolOk
)
triggerDownNow := (
priceReclaimShort and
extensionShortOk and
candleBodyOk and
candleLocationShortOk and
deltaShortOk and
rvolOk
)
// -- funnel diagnostics: tally which gate(s) blocked this bar's attempt,
// resolved to the active event's own direction so a long event isn't
// penalized by the (always-false) short-side conditions and vice versa --
cEvalBars += 1
bool reclaimOkDir = activeDirection == 1 ? priceReclaimLong : priceReclaimShort
bool extensionOkDir = activeDirection == 1 ? extensionLongOk : extensionShortOk
bool locationOkDir = activeDirection == 1 ? candleLocationLongOk : candleLocationShortOk
bool deltaOkDir = activeDirection == 1 ? deltaLongOk : deltaShortOk
if not reclaimOkDir
cReclaimFail += 1
if not extensionOkDir
cExtensionFail += 1
if not candleBodyOk
cBodyFail += 1
if not locationOkDir
cLocationFail += 1
if not deltaOkDir
cDeltaFail += 1
if not rvolOk
cRvolFail += 1
if triggerUpNow or triggerDownNow
retestState := "TRIGGERED"
cTriggered += 1
activeTriggerAligned := (
(
triggerUpNow and
fullLongContext
) or
(
triggerDownNow and
fullShortContext
)
)
// ============================================================================
// ORDERS
// ============================================================================
var int lastActedEventTime = na
var string openTradeDirection = ""
var string openTradePoiId = ""
var int openTradeTypeCode = 0
var int entryCallCount = 0
var int processedClosedTrades = 0
bool actualLongEntrySignal = false
bool actualShortEntrySignal = false
bool freshTrigger = (
retestState == "TRIGGERED" and
(
na(lastActedEventTime) or
activeEventTime != lastActedEventTime
)
)
if barstate.isconfirmed and freshTrigger and strategy.position_size == 0
string directionStr = (
activeDirection == 1 ?
"long" :
"short"
)
string poiId = (
f_typeCodeName(activeTypeCode) +
"_" +
str.tostring(activeEventTime)
)
// -- checked LIVE against current bias, not the frozen activeTriggerAligned
// snapshot from the (possibly much earlier) trigger bar -- retestState stays
// "TRIGGERED" until a new break event arrives, so if a prior position was
// still open at trigger time, entry can fire many bars later once flat; using
// a stale alignment reading from the original trigger bar could let a
// counter-trend entry through if bias flipped in that gap --
bool alignedOk = (
not requireAlignedEntryInput or
(
directionStr == "long" ?
fullLongContext :
fullShortContext
)
)
bool typeEnabled = (
activeTypeCode == 4 ?
enableDeltaFlipEntriesInput :
true
)
[tradeable, reason] = f_canTrade(
cd,
directionStr,
poiId,
bar_index,
globalCooldownWinBarsInput,
globalCooldownLossBarsInput,
directionCooldownBarsInput,
consecutiveLossLimitInput
)
lastActedEventTime := activeEventTime
float stopPrice = (
directionStr == "long" ?
activeZoneLow -
stopBufferAtrMultInput * atr5m :
activeZoneHigh +
stopBufferAtrMultInput * atr5m
)
float riskPerUnit = math.abs(
close - stopPrice
)
float riskAtr = (
atr5m > 0 ?
riskPerUnit / atr5m :
999.0
)
bool riskOk = (
riskAtr <= maximumEntryRiskAtrInput
)
if alignedOk and typeEnabled and tradeable and riskOk
float targetPrice = (
directionStr == "long" ?
close +
riskPerUnit * rMultipleInput :
close -
riskPerUnit * rMultipleInput
)
string entryId = (
directionStr == "long" ?
"Long" :
"Short"
)
openTradeDirection := directionStr
openTradePoiId := poiId
openTradeTypeCode := activeTypeCode
if directionStr == "long"
strategy.entry(
entryId,
strategy.long,
qty = contractsInput
)
actualLongEntrySignal := true
else
strategy.entry(
entryId,
strategy.short,
qty = contractsInput
)
actualShortEntrySignal := true
strategy.exit(
entryId + " Exit",
entryId,
stop = stopPrice,
limit = targetPrice
)
entryCallCount += 1
// ============================================================================
// BROKER-VERIFIED EXIT DETECTION
// ============================================================================
if strategy.closedtrades > processedClosedTrades
int closedIdx = strategy.closedtrades - 1
string closedEntryId = strategy.closedtrades.entry_id(closedIdx)
float closedProfit = strategy.closedtrades.profit(closedIdx)
bool isWin = closedProfit > 0
string closedDirection = closedEntryId == "Long" ? "long" : "short"
int closedExitBar = strategy.closedtrades.exit_bar_index(closedIdx)
f_recordExit(
cd,
closedExitBar,
isWin,
closedDirection,
openTradePoiId
)
processedClosedTrades := strategy.closedtrades
// ============================================================================
// ACTIVE RETEST ZONE
// ============================================================================
var box activeZoneBox = na
if barstate.islast
if not na(activeZoneBox)
box.delete(activeZoneBox)
if retestState == "WATCHING" or retestState == "RETESTED"
color zoneColor = (
activeDirection == 1 ?
retestLongColorInput :
retestShortColorInput
)
activeZoneBox := box.new(
left = bar_index - 40,
top = activeZoneHigh,
right = bar_index + 5,
bottom = activeZoneLow,
xloc = xloc.bar_index,
bgcolor = color.new(zoneColor, 87),
border_color = zoneColor
)
// ============================================================================
// QUALIFIED RETEST TRIGGER MARKERS
// ----------------------------------------------------------------------------
// Diamond = retest/reclaim/pressure trigger occurred.
// This is intentionally separate from the actual strategy entry triangle.
// ============================================================================
plotshape(
showQualifiedTriggerInput and triggerUpNow,
title = "Qualified Long Retest Trigger",
style = shape.diamond,
location = location.belowbar,
size = size.small,
color = longTriggerColorInput,
text = "T",
textcolor = color.white
)
plotshape(
showQualifiedTriggerInput and triggerDownNow,
title = "Qualified Short Retest Trigger",
style = shape.diamond,
location = location.abovebar,
size = size.small,
color = shortTriggerColorInput,
text = "T",
textcolor = color.white
)
// ============================================================================
// RETEST TOUCH MARKERS
// ============================================================================
plotshape(
showRetestTouchMarkerInput and retestLongTouchNow,
title = "Long Retest Touch",
style = shape.circle,
location = location.belowbar,
size = size.tiny,
color = retestLongColorInput
)
plotshape(
showRetestTouchMarkerInput and retestShortTouchNow,
title = "Short Retest Touch",
style = shape.circle,
location = location.abovebar,
size = size.tiny,
color = retestShortColorInput
)
// ============================================================================
// ENTRY MARKERS - INPUT-CONTROLLED COLOR + SIZE
// ============================================================================
bool showLongEntry = showEntrySignalInput and actualLongEntrySignal
bool showShortEntry = showEntrySignalInput and actualShortEntrySignal
plotshape(
showLongEntry and entrySignalSizeInput == "Tiny",
title = "Long Entry Tiny",
style = shape.triangleup,
location = location.belowbar,
size = size.tiny,
color = longEntryColorInput,
text = "LONG",
textcolor = color.white
)
plotshape(
showLongEntry and entrySignalSizeInput == "Small",
title = "Long Entry Small",
style = shape.triangleup,
location = location.belowbar,
size = size.small,
color = longEntryColorInput,
text = "LONG",
textcolor = color.white
)
plotshape(
showLongEntry and entrySignalSizeInput == "Normal",
title = "Long Entry Normal",
style = shape.triangleup,
location = location.belowbar,
size = size.normal,
color = longEntryColorInput,
text = "LONG",
textcolor = color.white
)
plotshape(
showLongEntry and entrySignalSizeInput == "Large",
title = "Long Entry Large",
style = shape.triangleup,
location = location.belowbar,
size = size.large,
color = longEntryColorInput,
text = "LONG",
textcolor = color.white
)
plotshape(
showLongEntry and entrySignalSizeInput == "Huge",
title = "Long Entry Huge",
style = shape.triangleup,
location = location.belowbar,
size = size.huge,
color = longEntryColorInput,
text = "LONG",
textcolor = color.white
)
plotshape(
showShortEntry and entrySignalSizeInput == "Tiny",
title = "Short Entry Tiny",
style = shape.triangledown,
location = location.abovebar,
size = size.tiny,
color = shortEntryColorInput,
text = "SHORT",
textcolor = color.white
)
plotshape(
showShortEntry and entrySignalSizeInput == "Small",
title = "Short Entry Small",
style = shape.triangledown,
location = location.abovebar,
size = size.small,
color = shortEntryColorInput,
text = "SHORT",
textcolor = color.white
)
plotshape(
showShortEntry and entrySignalSizeInput == "Normal",
title = "Short Entry Normal",
style = shape.triangledown,
location = location.abovebar,
size = size.normal,
color = shortEntryColorInput,
text = "SHORT",
textcolor = color.white
)
plotshape(
showShortEntry and entrySignalSizeInput == "Large",
title = "Short Entry Large",
style = shape.triangledown,
location = location.abovebar,
size = size.large,
color = shortEntryColorInput,
text = "SHORT",
textcolor = color.white
)
plotshape(
showShortEntry and entrySignalSizeInput == "Huge",
title = "Short Entry Huge",
style = shape.triangledown,
location = location.abovebar,
size = size.huge,
color = shortEntryColorInput,
text = "SHORT",
textcolor = color.white
)
barcolor(
highlightEntryBarInput and actualLongEntrySignal ?
color.new(longEntryColorInput, 35) :
highlightEntryBarInput and actualShortEntrySignal ?
color.new(shortEntryColorInput, 35) :
na
)
// ============================================================================
// FIXED-RANGE VOLUME DELTA PROFILE
// ----------------------------------------------------------------------------
// Compact mobile-safe profile.
//
// Layout:
// - Entire profile occupies ONE configured width instead of extending 2x width.
// - Overlay Left keeps all boxes on visible historical bars.
// - Right Side only extends by the requested offset.
// - POC is a short segment across the profile itself, not a 360-bar ray.
// - High/low are scanned explicitly so the profile is stable inside
// barstate.islast.
// ============================================================================
var array<box> profileBoxes = array.new_box()
var array<line> profileLines = array.new_line()
if barstate.islast
if array.size(profileBoxes) > 0
for i = 0 to array.size(profileBoxes) - 1
box.delete(array.get(profileBoxes, i))
array.clear(profileBoxes)
if array.size(profileLines) > 0
for i = 0 to array.size(profileLines) - 1
line.delete(array.get(profileLines, i))
array.clear(profileLines)
if showVolumeDeltaProfileInput
int profileBars = math.min(deltaProfileLookbackInput, bar_index + 1)
if profileBars >= 10
float pHigh = -1.0e20
float pLow = 1.0e20
for i = 0 to profileBars - 1
pHigh := math.max(pHigh, high[i])
pLow := math.min(pLow, low[i])
float pStep = math.max(
(pHigh - pLow) / deltaProfileRowsInput,
syminfo.mintick
)
array<float> rowTotal = array.new_float(
deltaProfileRowsInput,
0.0
)
array<float> rowDelta = array.new_float(
deltaProfileRowsInput,
0.0
)
for i = 0 to profileBars - 1
float bHigh = high[i]
float bLow = low[i]
float bOpen = open[i]
float bClose = close[i]
float bVol = nz(volume[i], 0.0)
float bRange = bHigh - bLow
float signedDelta = (
deltaProfileMethodInput == "Bar Polarity" ?
(bClose > bOpen ? bVol : -bVol) :
f_delta(
bOpen,
bHigh,
bLow,
bClose,
bVol
)
)
if bRange <= syminfo.mintick
int row = int(
math.floor(
(bClose - pLow) /
pStep
)
)
row := math.max(
0,
math.min(
deltaProfileRowsInput - 1,
row
)
)
array.set(
rowTotal,
row,
array.get(rowTotal, row) +
bVol
)
array.set(
rowDelta,
row,
array.get(rowDelta, row) +
signedDelta
)
else
int firstRow = int(
math.floor(
(bLow - pLow) /
pStep
)
)
int lastRow = int(
math.floor(
(bHigh - pLow) /
pStep
)
)
firstRow := math.max(
0,
math.min(
deltaProfileRowsInput - 1,
firstRow
)
)
lastRow := math.max(
0,
math.min(
deltaProfileRowsInput - 1,
lastRow
)
)
for row = firstRow to lastRow
float rowLow = (
pLow +
row * pStep
)
float rowHigh = (
rowLow +
pStep
)
float overlap = math.max(
0.0,
math.min(
bHigh,
rowHigh
) -
math.max(
bLow,
rowLow
)
)
float fraction = (
overlap /
bRange
)
if fraction > 0
array.set(
rowTotal,
row,
array.get(
rowTotal,
row
) +
bVol * fraction
)
array.set(
rowDelta,
row,
array.get(
rowDelta,
row
) +
signedDelta * fraction
)
float maxTotal = 0.0
float maxAbsDelta = 0.0
int pocRow = 0
for row = 0 to deltaProfileRowsInput - 1
float totalV = array.get(
rowTotal,
row
)
float absDeltaV = math.abs(
array.get(
rowDelta,
row
)
)
if totalV > maxTotal
maxTotal := totalV
pocRow := row
maxAbsDelta := math.max(
maxAbsDelta,
absDeltaV
)
int profileRightX = (
deltaProfilePlacementInput == "Right Side" ?
bar_index + deltaProfileOffsetInput :
bar_index - deltaProfileOffsetInput
)
int profileLeftX = (
profileRightX -
deltaProfileWidthInput
)
int profileCenterX = (
profileLeftX +
int(
math.floor(
deltaProfileWidthInput *
0.5
)
)
)
int deltaHalfWidth = math.max(
1,
int(
math.floor(
deltaProfileWidthInput *
0.5
)
)
)
for row = 0 to deltaProfileRowsInput - 1
float rowLow = (
pLow +
row * pStep
)
float rowHigh = (
rowLow +
pStep
)
float rowMid = (
rowLow +
rowHigh
) * 0.5
float totalV = array.get(
rowTotal,
row
)
float deltaV = array.get(
rowDelta,
row
)
int totalWidth = (
maxTotal > 0 ?
int(
math.round(
deltaProfileWidthInput *
totalV /
maxTotal
)
) :
0
)
int deltaWidth = (
maxAbsDelta > 0 ?
int(
math.round(
deltaHalfWidth *
math.abs(deltaV) /
maxAbsDelta
)
) :
0
)
float halfHeight = (
pStep *
0.36
)
if showTotalVolumeFrameInput and totalWidth > 0
int totalLeftX = (
profileRightX -
totalWidth
)
box totalBox = box.new(
left = totalLeftX,
top = rowMid + halfHeight,
right = profileRightX,
bottom = rowMid - halfHeight,
xloc = xloc.bar_index,
bgcolor = color.new(
profileTotalColorInput,
88
),
border_color = color.new(
profileTotalColorInput,
65
)
)
array.push(
profileBoxes,
totalBox
)
if showDeltaBarsInput and deltaWidth > 0
color deltaColor = (
deltaV >= 0 ?
profileBullColorInput :
profileBearColorInput
)
int deltaLeftX = (
deltaV >= 0 ?
profileCenterX :
profileCenterX -
deltaWidth
)
int deltaRightX = (
deltaV >= 0 ?
profileCenterX +
deltaWidth :
profileCenterX
)
box deltaBox = box.new(
left = deltaLeftX,
top = rowMid + halfHeight,
right = deltaRightX,
bottom = rowMid - halfHeight,
xloc = xloc.bar_index,
bgcolor = color.new(
deltaColor,
profileTransparencyInput
),
border_color = color.new(
deltaColor,
20
)
)
array.push(
profileBoxes,
deltaBox
)
if showProfilePocInput
float profilePocPrice = (
pLow +
(pocRow + 0.5) *
pStep
)
line pocLine = line.new(
x1 = profileLeftX,
y1 = profilePocPrice,
x2 = profileRightX,
y2 = profilePocPrice,
xloc = xloc.bar_index,
color = profilePocColorInput,
width = 2
)
array.push(
profileLines,
pocLine
)
// ============================================================================
// DASHBOARD
// ============================================================================
var table statusTable = table.new(
f_tablePosition(dashboardPositionInput),
2,
13,
bgcolor = color.new(color.rgb(15, 20, 27), 10),
border_width = 1
)
if barstate.islast and showDashboardInput
string regime = (
not adxOk or not erOk ?
"CHOPPY" :
fullLongContext ?
"TRENDING UP" :
fullShortContext ?
"TRENDING DOWN" :
"TRANSITIONAL"
)
float rollingBuyDelta = 0.0
float rollingSellDelta = 0.0
for i = 0 to deltaStrengthWindowInput - 1
float d = f_delta(
open[i],
high[i],
low[i],
close[i],
nz(volume[i], 0.0)
)
if d > 0
rollingBuyDelta += d
else
rollingSellDelta += -d
float totalPressure = rollingBuyDelta + rollingSellDelta
float buyRatio = (
totalPressure > 0 ?
rollingBuyDelta / totalPressure :
0.5
)
float sellRatio = 1.0 - buyRatio
string queryDirection = (
activeDirection == 1 ?
"long" :
activeDirection == -1 ?
"short" :
"long"
)
string queryPoiId = (
na(activeEventTime) ?
"none" :
f_typeCodeName(activeTypeCode) +
"_" +
str.tostring(activeEventTime)
)
[cooldownOk, cooldownReason] = f_canTrade(
cd,
queryDirection,
queryPoiId,
bar_index,
globalCooldownWinBarsInput,
globalCooldownLossBarsInput,
directionCooldownBarsInput,
consecutiveLossLimitInput
)
string positionDisplay = (
strategy.position_size == 0 ?
"FLAT" :
strategy.position_size > 0 ?
"LONG" :
"SHORT"
)
table.cell(statusTable, 0, 0, "DBR STAGE 8.0.2 [r8]", text_color = color.white, bgcolor = color.rgb(22, 52, 86), text_size = tableTextSize)
table.cell(statusTable, 1, 0, syminfo.ticker + " " + timeframe.period, text_color = color.white, bgcolor = color.rgb(22, 52, 86), text_size = tableTextSize)
table.cell(statusTable, 0, 1, "4H Bias", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 1, bias, text_color = bias == "LONG_ONLY" ? color.aqua : bias == "SHORT_ONLY" ? color.orange : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 2, "1H Trend", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 2, long1hTrendOk ? "BULL" : short1hTrendOk ? "BEAR" : "MIXED", text_color = long1hTrendOk ? color.aqua : short1hTrendOk ? color.orange : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 3, "Regime", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 3, regime, text_color = regime == "TRENDING UP" ? color.aqua : regime == "TRENDING DOWN" ? color.orange : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 4, "Retest", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 4, retestState + " " + f_typeCodeName(activeTypeCode), text_color = retestState == "TRIGGERED" ? color.white : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 5, "Pressure", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 5, str.tostring(executionDeltaPressure, "#.0") + "%", text_color = executionDeltaPressure >= 0 ? color.aqua : color.orange, text_size = tableTextSize)
table.cell(statusTable, 0, 6, "RVOL", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 6, str.tostring(triggerRvol, "#.##") + "x", text_color = triggerRvol >= minimumTriggerRvolInput ? color.white : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 7, "Buyers", text_color = color.aqua, text_size = tableTextSize)
table.cell(statusTable, 1, 7, str.tostring(int(math.round(buyRatio * 100.0))) + "%", text_color = color.aqua, text_size = tableTextSize)
table.cell(statusTable, 0, 8, "Sellers", text_color = color.orange, text_size = tableTextSize)
table.cell(statusTable, 1, 8, str.tostring(int(math.round(sellRatio * 100.0))) + "%", text_color = color.orange, text_size = tableTextSize)
table.cell(statusTable, 0, 9, "Position", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 9, positionDisplay, text_color = strategy.position_size > 0 ? color.aqua : strategy.position_size < 0 ? color.orange : color.gray, text_size = tableTextSize)
table.cell(statusTable, 0, 10, "Cooldown", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 10, cooldownOk ? "READY" : cooldownReason, text_color = cooldownOk ? color.aqua : color.orange, text_size = tableTextSize)
table.cell(statusTable, 0, 11, "Broker Trades", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 11, str.tostring(strategy.opentrades) + " / " + str.tostring(strategy.closedtrades), text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 0, 12, "Execution Chart", text_color = color.white, text_size = tableTextSize)
table.cell(statusTable, 1, 12, chartIsRecommended5m ? "5m RECOMMENDED" : timeframe.period + "m - USE 5m FOR EXEC", text_color = chartIsRecommended5m ? color.aqua : color.orange, text_size = tableTextSize)
// ============================================================================
// RETEST FUNNEL DIAGNOSTICS
// ----------------------------------------------------------------------------
// Separate table so it can be toggled off independently of the main
// dashboard. Shows how many break events reached each stage of the
// WATCHING -> RETESTED -> TRIGGERED chain, and -- for bars where the
// RETESTED trigger logic actually ran -- which single gate blocked it most.
// ============================================================================
var table funnelTable = table.new(
f_tablePosition(funnelPositionInput),
2,
9,
bgcolor = color.new(color.rgb(15, 20, 27), 10),
border_width = 1
)
if barstate.islast and showDashboardInput and showFunnelDiagnosticsInput
table.cell(funnelTable, 0, 0, "RETEST FUNNEL", text_color = color.white, bgcolor = color.rgb(22, 52, 86), text_size = tableTextSize)
table.cell(funnelTable, 1, 0, "", text_color = color.white, bgcolor = color.rgb(22, 52, 86), text_size = tableTextSize)
table.cell(funnelTable, 0, 1, "Break Events", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 1, str.tostring(cEvents), text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 0, 2, "-> Retested", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 2, str.tostring(cRetested), text_color = cRetested > 0 ? color.aqua : color.gray, text_size = tableTextSize)
table.cell(funnelTable, 0, 3, "-> Triggered", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 3, str.tostring(cTriggered), text_color = cTriggered > 0 ? color.aqua : color.gray, text_size = tableTextSize)
table.cell(funnelTable, 0, 4, "Expired / Invalid", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 4, str.tostring(cExpired) + " / " + str.tostring(cInvalidated), text_color = color.orange, text_size = tableTextSize)
table.cell(funnelTable, 0, 5, "Eval Bars (RETESTED)", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 5, str.tostring(cEvalBars), text_color = color.gray, text_size = tableTextSize)
table.cell(funnelTable, 0, 6, "Fail: Reclaim / Ext", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 6, str.tostring(cReclaimFail) + " / " + str.tostring(cExtensionFail), text_color = color.orange, text_size = tableTextSize)
table.cell(funnelTable, 0, 7, "Fail: Body / Loc", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 7, str.tostring(cBodyFail) + " / " + str.tostring(cLocationFail), text_color = color.orange, text_size = tableTextSize)
table.cell(funnelTable, 0, 8, "Fail: Delta / RVOL", text_color = color.white, text_size = tableTextSize)
table.cell(funnelTable, 1, 8, str.tostring(cDeltaFail) + " / " + str.tostring(cRvolFail), text_color = color.orange, text_size = tableTextSize)
// ============================================================================
// ALERTS
// ============================================================================
alertcondition(
actualLongEntrySignal,
title = "BULL LONG RETEST ENTRY",
message = "BULL | LONG ENTRY | BREAK RETEST | {{ticker}} {{interval}} @ {{close}}"
)
alertcondition(
actualShortEntrySignal,
title = "BEAR SHORT RETEST ENTRY",
message = "BEAR | SHORT ENTRY | BREAK RETEST | {{ticker}} {{interval}} @ {{close}}"
)
// ============================================================================
// DATA WINDOW
// ============================================================================
plot(barDelta, title = "Per-Bar Estimated Delta", display = display.data_window)
plot(executionDeltaPressure, title = "Execution Pressure %", display = display.data_window)
plot(triggerRvol, title = "Trigger RVOL", display = display.data_window)
plot(activeDirection, title = "Active Retest Direction", display = display.data_window)
plot(strategy.position_size, title = "Position Size", display = display.data_window)
plot(strategy.opentrades, title = "Open Trades", display = display.data_window)
plot(strategy.closedtrades, title = "Closed Trades", display = display.data_window)
plot(entryCallCount, title = "Entry Calls", display = display.data_window)
plot(triggerUpNow ? 1 : 0, title = "Qualified Long Trigger", display = display.data_window)
plot(triggerDownNow ? 1 : 0, title = "Qualified Short Trigger", display = display.data_window)
plot(actualLongEntrySignal ? 1 : 0, title = "Actual Long Entry", display = display.data_window)
plot(actualShortEntrySignal ? 1 : 0, title = "Actual Short Entry", display = display.data_window)
// ============================================================================
// RECOMMENDED STARTING SETTINGS - MNQ 5m [r3]
// ----------------------------------------------------------------------------
// [r3] update: on-device retest-funnel diagnostics (bottom-left table, see
// "Show Retest Funnel Diagnostics") on real MNQ1! 5m history showed the
// ORIGINAL defaults below let only 1 trigger through 188 RETESTED eval bars
// (0.5%) -- the pressure-rotation gate alone was blocking ~76% of every
// attempt, with close-location and entry-extension as the next two largest
// blockers. Defaults changed below reflect that data. This is a first-pass,
// data-informed loosening on a ~200-event sample across a couple weeks of
// history -- NOT a validated edge. Re-run the funnel after any further
// change, and treat these as a starting point for a real quantor
// walk-forward validation before sizing real risk off them.
//
// 4H Bias TF: 240
// 1H Trend TF: 60
// Require 1H Trend Agreement: ON
//
// Break-to-retest window: 4-6 hours
// Min bars after retest: 1
// Max bars after retest: 4 (was 3)
// Max retest overshoot: 0.10 ATR
// Reclaim buffer: 0.05 ATR
// Max entry extension: 0.50 ATR (was 0.35 -- 2nd/3rd largest funnel blocker)
// Min trigger body/range: 0.30
// Min trigger close location: 0.58 (was 0.65 -- 2nd largest funnel blocker, 63% fail)
//
// Pressure window: 3
// Minimum pressure: 8%
// Minimum pressure rotation: 10%
// Require Pressure Rotation: OFF (was ON -- confirmed dominant blocker, 76% fail)
// RVOL requirement: OFF initially
//
// Max entry risk: 1.25 ATR
// Target: 2.0R
//
// Volume Delta Profile:
// Lookback: 360 bars
// Rows: 40
// Placement: Right Side (was Overlay Left -- sat on top of candles)
// Width: 24 bars
// Offset: 36 bars (was 2 -- must be >= Width to clear price)
// ============================================================================
Overview
Gates include estimated pressure rotation, candle body and close location, entry extension limits, optional relative volume, and 4-hour and 1-hour agreement. Delta is estimated from candle data, not true order flow. It calculates on every tick and pulls higher timeframe data, so live signals may differ.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Break-Context Timeframe
timeframe
15
breakContextTimeframeInput
POI Extraction Rows
int
40
profileRowsInput
POI Zone Half-Width (x ATR)
float
0.25
poiAtrMultInput
Minimum Bars To Finalize Session
int
10
minEndedSessionBarsInput
Track Absorption Node
bool
true
showAbsorptionInput
Minimum Break Buffer (x ATR)
float
0.10
minBreakAtrMultInput
Require Estimated Delta On 15m Break
bool
true
requireBreakDeltaConfirmationInput
Maximum Break-To-Retest Window (Hours)
float
6.0
retestWindowHoursInput
Minimum Bars After Retest Before Trigger
int
0
minBarsAfterRetestInput
Maximum Bars After Retest To Trigger
int
4
maxBarsAfterRetestInput
Maximum Retest Overshoot Beyond Far Edge (ATR)
float
0.10
maxRetestOvershootAtrInput
Minimum Reclaim Beyond Zone Edge (ATR)
float
0.05
reclaimBufferAtrInput
Show all 40 settings
Setting
Type
Default
Variable
Maximum Entry Extension From Zone (ATR)
float
0.50
maxEntryExtensionAtrInput
Minimum Trigger Candle Body / Range
float
0.30
minimumTriggerBodyFractionInput
Minimum Trigger Close Location
float
0.58
minimumTriggerCloseLocationInput
Require Pressure Rotation On Trigger
bool
false
requireTriggerDeltaConfirmationInput
Execution Pressure Window
int
3
executionDeltaWindowInput
Minimum Trigger Pressure %
float
8.0
minimumDeltaPressureInput
Minimum Pressure Rotation From Retest %
float
10.0
minimumDeltaRotationInput
Require RVOL On Trigger
bool
false
requireRvolOnTriggerInput
Trigger RVOL Length
int
20
triggerRvolLengthInput
Minimum Trigger RVOL
float
1.05
minimumTriggerRvolInput
4H Bias Timeframe
timeframe
240
biasTimeframeInput
4H Pivot Left
int
3
pivotLeftInput
4H Pivot Right
int
3
pivotRightInput
4H EMA Length
int
50
biasEmaLengthInput
4H EMA Slope Lookback
int
5
biasSlopeLookbackInput
1H Trend Timeframe
timeframe
60
trendTimeframeInput
1H ADX / DI Length
int
14
adxLengthInput
1H Minimum ADX
float
20.0
adxMinimumInput
1H Fast EMA
int
21
fastEmaInput
1H Slow EMA
int
50
slowEmaInput
1H Efficiency Ratio Length
int
10
erLengthInput
1H Minimum Efficiency Ratio
float
0.30
erMinimumInput
Require 1H Trend Agreement
bool
true
require1hTrendAgreementInput
Global Cooldown After Win
int
6
globalCooldownWinBarsInput
Global Cooldown After Loss
int
12
globalCooldownLossBarsInput
Direction Cooldown After Loss
int
12
directionCooldownBarsInput
Consecutive Loss Session Limit
int
2
consecutiveLossLimitInput
Require Full Trend Alignment
bool
true
requireAlignedEntryInput
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
A strategy-type script that draws the high, low and midpoint of an opening window each day, plus pivot-based support and resistance lines from left and right bar settings. It prints buy or sell labels when the close breaks a pivot level while an EMA-based volu
TN Footprint Imbalance Sovereign v6 is a strategy that uses request.footprint() to detect imbalances in buy and sell volume at the point of control. It pulls POC price, buy volume, sell volume and bar delta from the footprint data object. Long entries trigger
VABOAlgo is a Pine Script v5 strategy that detects breakouts from a value area. It uses VWAP as a POC proxy and ATR to calculate VAH and VAL, requiring volume above average for confirmation and prior bar consolidation within the VA. Long entries trigger on bre
This trend-following strategy executes a classic Donchian channel breakout system designed to cross-validate backtest results against external framework benchmarks. It triggers a long entry when the closing price breaks above the highest high of the prior entr
This strategy plots running pivot highs as red R circles and pivot lows as blue S circles directly on the price chart. It computes an EMA 3 and a Cto trend line from close prices, then generates long signals on upward EMA-Cto crossovers combined with price pos
STRG-HOLP is a trading strategy that identifies High of Low Period (HOLP) and Low of High Period (LOHP) breakout setups. It tracks the lowest low or highest high over a user-defined lookback window. A long trade opens when the close breaks above the high of th