本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AlgoAlpha
//@version=6
indicator("High Probability Order Blocks [AlgoAlpha]", shorttitle = "AlgoAlpha - High Probability OB", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_polylines_count = 100)
// ============================================================================
// INPUTS
// ============================================================================
preventOverlap = input.bool(true, title = "Prevent Overlap", group = "Calculation", tooltip = "Avoid creating blocks that overlap existing ones.")
zLen = input.int(50, title = "Z-Score Window (bars)", minval = 1, group = "Calculation", tooltip = "Bars used to normalize distance into z-scores. Bigger = smoother; smaller = faster.")
maxAge = input.int(2000, title = "Max Box Age (bars)", minval = 1, group = "Calculation", tooltip = "Expire blocks after this many bars if not mitigated.")
kmLookback = input.int(70, title = "KM Training Window (zones)", minval = 1, group = "Reliability Calculation", tooltip = "Recent mitigated blocks kept per side (bullish/bearish) for KM. Higher = smoother, lower = more reactive.")
kmMinSamples = input.int(5, title = "Minimum Sample Size", minval = 5, group = "Reliability Calculation", tooltip = "Minimum mitigated blocks needed to compute KM. Otherwise shows not enough data (N.E.D.)")
// ============================================================================
// APPEARANCE
// ============================================================================
showKmPanel = input.bool(true, title = "Show KM Panel", group = "Appearance", tooltip = "Toggle KM axes/grid and curves.")
kmAxisCol = input.color(color.new(color.white, 0), title = "KM Axis/Grid Color", inline = "a", group = "Appearance")
kmGridCol = input.color(color.new(color.silver, 60), title = "", inline = "a", group = "Appearance", tooltip = "Axis and Grid line color.")
bullCol = input.color(#00ffbb, title = "Bullish/Bearish Colour", inline = "b", group = "Appearance")
bearCol = input.color(#ff1100, title = "", inline = "b", group = "Appearance", tooltip = "Color for bullish and bearish blocks and curve.")
probTxtSizeOpt = input.string("Tiny", title = "Probability Text Size", options=["Tiny","Small","Medium","Large"], group = "Appearance", tooltip = "Text size inside each orderblock label.")
probTxtSizeConst = probTxtSizeOpt == "Tiny" ? size.tiny : probTxtSizeOpt == "Small" ? size.small : probTxtSizeOpt == "Medium" ? size.normal : size.large
// ============================================================================
// KM PLOT CONSTANTS
// ============================================================================
kmGridYDivs = 4
kmGridXStep = 50
showKmLabels = true
kmXAxisTitle = "Orderblock Age (Bars)"
kmYAxisTitle = "Probability of Zone Survival %"
var updist = 0.0
var downdist = 0.0
updist := close > open ? nz(updist[1]) + (close - open) : 0.0
downdist := close < open ? nz(downdist[1]) + (open - close) : 0.0
upMean = ta.sma(updist, zLen)
upStdev = ta.stdev(updist, zLen)
dnMean = ta.sma(downdist, zLen)
dnStdev = ta.stdev(downdist, zLen)
zUp = (updist - upMean) / (upStdev == 0.0 ? na : upStdev)
zDn = (downdist - dnMean) / (dnStdev == 0.0 ? na : dnStdev)
bullish = ta.crossover(zUp, 4) and nz(zUp[1]) != 0
bearish = ta.crossunder(-zDn, -4) and nz((-zDn)[1]) != 0
var box[] bullBoxes = array.new<box>()
var box[] bearBoxes = array.new<box>()
var int[] bullStarts = array.new_int()
var int[] bearStarts = array.new_int()
var float[] kmAges = array.new_float()
var int[] kmSides = array.new_int()
var line[] bullMidlines = array.new<line>()
var line[] bearMidlines = array.new<line>()
var polyline bullKmCurve = na
var polyline bearKmCurve = na
// ============================================================================
// KM AXES/GRID STATE
// ============================================================================
var polyline kmAxisX = na
var polyline kmAxisY = na
var polyline[] kmGridLines = array.new<polyline>()
var label[] kmYTickLabels = array.new<label>()
var label[] kmXTickLabels = array.new<label>()
var label kmXAxisTitleLabel = na
var label kmYAxisTitleLabel = na
var label kmPlotTitleLabel = na
bullrej = false
bearrej = false
bullCreate = false
bearCreate = false
f_km_survival(int side, int lookbackZones, int minSamples, int ageBars) =>
int total = 0
float[] agesSide = array.new_float()
int countAges = array.size(kmAges)
int countSides = array.size(kmSides)
if countAges == 0 or countSides == 0
na
else
int useN = math.min(math.min(countAges, countSides), lookbackZones)
if useN <= 0
na
else
for i = 0 to useN - 1
if i < array.size(kmSides) and i < array.size(kmAges)
if array.get(kmSides, i) == side
array.push(agesSide, array.get(kmAges, i))
total += 1
if total < minSamples
na
else
array.sort(agesSide)
float S = 1.0
int nAtRisk = total
int idx = 0
int n = array.size(agesSide)
while idx < n
float t = array.get(agesSide, idx)
int d = 1
int j = idx + 1
while j < n and array.get(agesSide, j) == t
d += 1
j += 1
if t <= ageBars and nAtRisk > 0
S := S * (1.0 - (float(d) / float(nAtRisk)))
nAtRisk -= d
idx := j
S
f_km_points(int side, int lookbackZones, int minSamples, float topY, float botY, int xStart, int maxBars) =>
chart.point[] pts = array.new<chart.point>()
int countAges = array.size(kmAges)
int countSides = array.size(kmSides)
if countAges == 0 or countSides == 0 or maxBars <= 0
pts
else
float[] agesSide = array.new_float()
int useN = math.min(math.min(countAges, countSides), lookbackZones)
for i = 0 to useN - 1
if i < array.size(kmSides) and i < array.size(kmAges)
if array.get(kmSides, i) == side
array.push(agesSide, array.get(kmAges, i))
int total = array.size(agesSide)
if total < minSamples
pts
else
array.sort(agesSide)
float S = 1.0
int nAtRisk = total
int idx = 0
int n = array.size(agesSide)
int lastT = 0
float y0 = botY + S * (topY - botY)
array.push(pts, chart.point.from_index(xStart, y0))
while idx < n
int t = int(array.get(agesSide, idx))
if t > maxBars
break
float yS = botY + S * (topY - botY)
array.push(pts, chart.point.from_index(xStart + t, yS))
int d = 1
int j = idx + 1
while j < n and int(array.get(agesSide, j)) == t
d += 1
j += 1
if nAtRisk > 0
S := S * (1.0 - (float(d) / float(nAtRisk)))
nAtRisk -= d
float yNew = botY + S * (topY - botY)
array.push(pts, chart.point.from_index(xStart + t, yNew))
lastT := t
idx := j
float yEnd = botY + S * (topY - botY)
array.push(pts, chart.point.from_index(xStart + maxBars, yEnd))
pts
f_can_create(float tNew, float bNew) =>
bool ok = true
if bullBoxes.size() > 0
for j = 0 to array.size(bullBoxes) - 1
exB = array.get(bullBoxes, j)
float exTop = box.get_top(exB)
float exBot = box.get_bottom(exB)
if (tNew > exBot) and (bNew < exTop)
ok := false
break
if ok and bearBoxes.size() > 0
for j = 0 to array.size(bearBoxes) - 1
exS = array.get(bearBoxes, j)
float exTop2 = box.get_top(exS)
float exBot2 = box.get_bottom(exS)
if (tNew > exBot2) and (bNew < exTop2)
ok := false
break
ok
lastDownIdx = ta.valuewhen(close < open, bar_index, 0)
lastDownHigh = ta.valuewhen(close < open, high, 0)
lastDownLow = ta.valuewhen(close < open, low, 0)
if bullish and not na(lastDownIdx) and not na(lastDownHigh) and not na(lastDownLow)
if not preventOverlap or f_can_create(lastDownHigh, lastDownLow)
int bgTransB = 85
int brdTransB = math.min(100, bgTransB + 10)
bx = box.new(lastDownIdx, lastDownHigh, lastDownIdx + 1, lastDownLow, border_color = color.new(bullCol, brdTransB), bgcolor = color.new(bullCol, bgTransB))
array.unshift(bullBoxes, bx)
array.unshift(bullStarts, lastDownIdx)
float midYB = (lastDownHigh + lastDownLow) / 2.0
line midB = line.new(lastDownIdx, midYB, lastDownIdx + 1, midYB, color = color.new(bullCol, 60), width = 1, style = line.style_dashed)
array.unshift(bullMidlines, midB)
bullCreate := true
lastUpIdx = ta.valuewhen(close > open, bar_index, 0)
lastUpHigh = ta.valuewhen(close > open, high, 0)
lastUpLow = ta.valuewhen(close > open, low, 0)
if bearish and not na(lastUpIdx) and not na(lastUpHigh) and not na(lastUpLow)
if not preventOverlap or f_can_create(lastUpHigh, lastUpLow)
int bgTransS = 85
int brdTransS = math.min(100, bgTransS + 10)
bx2 = box.new(lastUpIdx, lastUpHigh, lastUpIdx + 1, lastUpLow, border_color = color.new(bearCol, brdTransS), bgcolor = color.new(bearCol, bgTransS))
array.unshift(bearBoxes, bx2)
array.unshift(bearStarts, lastUpIdx)
float midYS = (lastUpHigh + lastUpLow) / 2.0
line midS = line.new(lastUpIdx, midYS, lastUpIdx + 1, midYS, color = color.new(bearCol, 60), width = 1, style = line.style_dashed)
array.unshift(bearMidlines, midS)
bearCreate := true
if bullBoxes.size() > 0
for i = array.size(bullBoxes) - 1 to 0
b = array.get(bullBoxes, i)
box.set_right(b, bar_index)
if array.size(bullMidlines) > i
line mB = array.get(bullMidlines, i)
mB.set_x2(bar_index)
float topB = box.get_top(b)
float botB = box.get_bottom(b)
bool mitigatedB = close < botB and close[1] < botB
int startB = array.get(bullStarts, i)
bool expiredB = (bar_index - startB) > maxAge
if mitigatedB or expiredB
if mitigatedB
int durB = bar_index - startB
array.unshift(kmAges, durB)
array.unshift(kmSides, 1)
while array.size(kmAges) > kmLookback
array.pop(kmAges)
array.pop(kmSides)
array.remove(bullBoxes, i)
array.remove(bullStarts, i)
if array.size(bullMidlines) > i
array.remove(bullMidlines, i)
else
int ageB = bar_index - startB
float sB = f_km_survival(1, kmLookback, kmMinSamples, ageB)
string txtB = na(sB) ? "Age " + str.tostring(ageB) + " | N.E.D" : "Age " + str.tostring(ageB) + " | " + str.tostring(math.round(sB * 100)) + "%"
b.set_text(txtB)
b.set_text_halign(text.align_right)
b.set_text_valign(text.align_bottom)
b.set_text_color(chart.fg_color)
b.set_text_size(probTxtSizeConst)
if close > topB and close[1] < topB
bullrej := true
if bearBoxes.size() > 0
for i = array.size(bearBoxes) - 1 to 0
b = array.get(bearBoxes, i)
box.set_right(b, bar_index)
if array.size(bearMidlines) > i
line mS = array.get(bearMidlines, i)
mS.set_x2(bar_index)
float topS = box.get_top(b)
float botS = box.get_bottom(b)
bool mitigatedS = close > topS and close[1] > topS
int startS = array.get(bearStarts, i)
bool expiredS = (bar_index - startS) > maxAge
if mitigatedS or expiredS
if mitigatedS
int durS = bar_index - startS
array.unshift(kmAges, durS)
array.unshift(kmSides, -1)
while array.size(kmAges) > kmLookback
array.pop(kmAges)
array.pop(kmSides)
array.remove(bearBoxes, i)
array.remove(bearStarts, i)
if array.size(bearMidlines) > i
array.remove(bearMidlines, i)
else
int ageS = bar_index - startS
float sS = f_km_survival(-1, kmLookback, kmMinSamples, ageS)
string txtS = na(sS) ? "Age " + str.tostring(ageS) + " | N.E.D" : "Age " + str.tostring(ageS) + " | " + str.tostring(math.round(sS * 100)) + "%"
b.set_text(txtS)
b.set_text_halign(text.align_right)
b.set_text_valign(text.align_top)
b.set_text_color(chart.fg_color)
b.set_text_size(probTxtSizeConst)
if close < botS and close[1] > botS
bearrej := true
// ============================================================================
// KM RENDERING
// ============================================================================
if barstate.islast
int rightStart = bar_index + 50
int maxBarsRight = 450
// Future bound guard (objects cannot be >500 bars in future)
int rightEnd = math.min(rightStart + maxBarsRight, bar_index + 500)
int maxBarsDraw = rightEnd - rightStart
// Determine Y scaling from active orderblocks: top = highest box top, bottom = lowest box bottom
float frameTop = na
float frameBottom = na
if array.size(bullBoxes) > 0
for i = 0 to array.size(bullBoxes) - 1
bx_ = array.get(bullBoxes, i)
float t = box.get_top(bx_)
float btm = box.get_bottom(bx_)
frameTop := na(frameTop) ? t : math.max(frameTop, t)
frameBottom := na(frameBottom) ? btm : math.min(frameBottom, btm)
if array.size(bearBoxes) > 0
for i = 0 to array.size(bearBoxes) - 1
bx2_ = array.get(bearBoxes, i)
float t2 = box.get_top(bx2_)
float btm2 = box.get_bottom(bx2_)
frameTop := na(frameTop) ? t2 : math.max(frameTop, t2)
frameBottom := na(frameBottom) ? btm2 : math.min(frameBottom, btm2)
// Fallback if no boxes
float yTop = na(frameTop) ? high : frameTop
float yBottom = na(frameBottom) ? low : frameBottom
// Safety if identical
if yTop <= yBottom
yTop += 1
yBottom -= 1
// Axes and grid
if showKmPanel
// Clear previous axes
if not na(kmAxisX)
kmAxisX.delete()
if not na(kmAxisY)
kmAxisY.delete()
// Clear previous grid polylines
if array.size(kmGridLines) > 0
for gi = array.size(kmGridLines) - 1 to 0
polyline gl = array.get(kmGridLines, gi)
if not na(gl)
gl.delete()
array.remove(kmGridLines, gi)
// Clear previous tick labels
if array.size(kmYTickLabels) > 0
for li = array.size(kmYTickLabels) - 1 to 0
label lb = array.get(kmYTickLabels, li)
if not na(lb)
lb.delete()
array.remove(kmYTickLabels, li)
if array.size(kmXTickLabels) > 0
for li = array.size(kmXTickLabels) - 1 to 0
label lb = array.get(kmXTickLabels, li)
if not na(lb)
lb.delete()
array.remove(kmXTickLabels, li)
// Axes polylines
chart.point[] xAxisPts = array.new<chart.point>()
array.push(xAxisPts, chart.point.from_index(rightStart, yBottom))
array.push(xAxisPts, chart.point.from_index(rightEnd, yBottom))
kmAxisX := polyline.new(xAxisPts, line_color = kmAxisCol, line_style = line.style_solid)
chart.point[] yAxisPts = array.new<chart.point>()
array.push(yAxisPts, chart.point.from_index(rightStart, yBottom))
array.push(yAxisPts, chart.point.from_index(rightStart, yTop))
kmAxisY := polyline.new(yAxisPts, line_color = kmAxisCol, line_style = line.style_solid)
// Horizontal grid (0..100%)
for d = 0 to kmGridYDivs
float frac = float(d) / float(kmGridYDivs)
float yLvl = yBottom + frac * (yTop - yBottom)
chart.point[] hPts = array.new<chart.point>()
array.push(hPts, chart.point.from_index(rightStart, yLvl))
array.push(hPts, chart.point.from_index(rightEnd, yLvl))
polyline hLine = polyline.new(hPts, line_color = kmGridCol, line_style = line.style_dotted)
array.push(kmGridLines, hLine)
if showKmLabels
string lbl = str.tostring(math.round(frac * 100)) + "%"
label ly = label.new(rightEnd, yLvl, lbl, textcolor = chart.fg_color, color = color.new(color.black, 100), textalign = text.align_left, style = label.style_label_left, size = size.tiny)
array.push(kmYTickLabels, ly)
// Vertical grid (X step)
int vCount = int(math.floor(maxBarsDraw / kmGridXStep))
if vCount > 0
for i = 1 to vCount
int xoff = i * kmGridXStep
chart.point[] vPts = array.new<chart.point>()
array.push(vPts, chart.point.from_index(rightStart + xoff, yBottom))
array.push(vPts, chart.point.from_index(rightStart + xoff, yTop))
polyline vLine = polyline.new(vPts, line_color = kmGridCol, line_style = line.style_dotted)
array.push(kmGridLines, vLine)
if showKmLabels
string tl = str.tostring(xoff)
label lx = label.new(rightStart + xoff, yBottom, tl, textcolor = chart.fg_color, color = color.new(color.black, 100), textalign = text.align_center, style = label.style_label_down, size = size.tiny)
array.push(kmXTickLabels, lx)
// Axis titles
if not na(kmXAxisTitleLabel)
kmXAxisTitleLabel.delete()
if not na(kmYAxisTitleLabel)
kmYAxisTitleLabel.delete()
if not na(kmPlotTitleLabel)
kmPlotTitleLabel.delete()
kmXAxisTitleLabel := label.new(int((rightStart + rightEnd) / 2), yBottom, kmXAxisTitle, textcolor = chart.fg_color, color = color.new(color.black, 100), textalign = text.align_center, style = label.style_label_up, size = size.small)
// Plot title near the 50% level, placed close to the right edge but left of Y ticks
float yMid = yBottom + 0.5 * (yTop - yBottom)
float yTitle = yMid - 0.04 * (yTop - yBottom)
int titlePadBars = math.max(6, int(math.round(maxBarsDraw * 0.02)))
int titleX = math.min(bar_index + 500 - 1, rightEnd + titlePadBars)
kmPlotTitleLabel := label.new(titleX, yTitle, kmYAxisTitle, textcolor = chart.fg_color, color = color.new(color.black, 100), textalign = text.align_left, style = label.style_label_left, size = size.small)
// Determine scaling from most recent active box per side
float bullTop = na
float bullBot = na
float bearTop = na
float bearBot = na
if array.size(bullBoxes) > 0
bullTop := box.get_top(array.get(bullBoxes, 0))
bullBot := box.get_bottom(array.get(bullBoxes, 0))
if array.size(bearBoxes) > 0
bearTop := box.get_top(array.get(bearBoxes, 0))
bearBot := box.get_bottom(array.get(bearBoxes, 0))
// KM Curves
if showKmPanel
if not na(bullTop) and not na(bullBot) and bullTop != bullBot
chart.point[] ptsB = f_km_points(1, kmLookback, kmMinSamples, yTop, yBottom, rightStart, maxBarsDraw)
if array.size(ptsB) > 1
if not na(bullKmCurve)
bullKmCurve.delete()
bullKmCurve := polyline.new(ptsB, line_color = bullCol, line_style = line.style_solid)
if not na(bearTop) and not na(bearBot) and bearTop != bearBot
chart.point[] ptsS = f_km_points(-1, kmLookback, kmMinSamples, yTop, yBottom, rightStart, maxBarsDraw)
if array.size(ptsS) > 1
if not na(bearKmCurve)
bearKmCurve.delete()
bearKmCurve := polyline.new(ptsS, line_color = bearCol, line_style = line.style_solid)
else
if not na(bullKmCurve)
bullKmCurve.delete()
bullKmCurve := na
if not na(bearKmCurve)
bearKmCurve.delete()
bearKmCurve := na
volatility = ta.atr(14)
plotchar(bullrej ? low - volatility*2 : na, "Bullish Rejection", "▲", location.absolute, bullCol, size = size.tiny)
plotchar(bearrej ? high + volatility*2 : na, "Bearish Rejection", "▼", location.absolute, bearCol, size = size.tiny)
// ============================================================================
// ALERTS
// ============================================================================
alertcondition(bullCreate, "Bullish Zone Created", "Bullish OrderBlock Created")
alertcondition(bearCreate, "Bearish Zone Created", "Bearish OrderBlock Created")
alertcondition(bullrej, "Bullish Rejection", "Bullish Rejection Signal")
alertcondition(bearrej, "Bearish Rejection", "Bearish Rejection Signal")