本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo
//@version=6
indicator("HTF Reversal Divergences [LuxAlgo]", "LuxAlgo - HTF Reversal Divergences", overlay = false, max_labels_count = 500, max_boxes_count = 500, max_lines_count = 500)
//---------------------------------------------------------------------------------------------------------------------}
// Constants
//---------------------------------------------------------------------------------------------------------------------{
const color BULL_COLOR = #089981
const color BEAR_COLOR = #f23645
const color NEUTRAL_COLOR = #787b86
//---------------------------------------------------------------------------------------------------------------------}
// Inputs
//---------------------------------------------------------------------------------------------------------------------{
// HTF Pattern Inputs
htfInput = input.timeframe("15", "High Timeframe", group = "HTF Reversal Patterns", tooltip = "The timeframe to check for reversal patterns.")
showEngulfingInput = input.bool(true, "Show Engulfing Patterns", group = "HTF Reversal Patterns")
showPinBarsInput = input.bool(true, "Show Pin Bars", group = "HTF Reversal Patterns", tooltip = "Includes Hammer and Shooting Star patterns.")
bullPatternColInput = input.color(BULL_COLOR, "Bullish Pattern Color", group = "HTF Reversal Patterns", inline = "Colors")
bearPatternColInput = input.color(BEAR_COLOR, "Bearish Pattern Color", group = "HTF Reversal Patterns", inline = "Colors")
// RSI Divergence Inputs
showDivInput = input.bool(true, "Show RSI Divergences", group = "RSI Divergence")
rsiLenInput = input.int(14, "RSI Length", group = "RSI Divergence", minval = 1)
lbRInput = input.int(5, "Pivot Right Lookback", group = "RSI Divergence")
lbLInput = input.int(5, "Pivot Left Lookback", group = "RSI Divergence")
bullDivColInput = input.color(BULL_COLOR, "Bullish Div Color", group = "RSI Divergence", inline = "Div Colors")
bearDivColInput = input.color(BEAR_COLOR, "Bearish Div Color", group = "RSI Divergence", inline = "Div Colors")
// Advanced: HTF PO3 Inputs
showPo3Input = input.bool(false, "Show HTF PO3", group = "Advanced: HTF PO3", tooltip = "Displays projected HTF candles to the right of price on the main chart.")
candleCountInput = input.int(1, "Candles to Show", group = "Advanced: HTF PO3", minval = 1, maxval = 10)
po3OffsetInput = input.int(15, "Right Offset (Bars)", group = "Advanced: HTF PO3", minval = 5)
showPo3LabelsInput = input.bool(true, "Show PO3 Price Labels", group = "Advanced: HTF PO3")
showPo3DeltaInput = input.bool(true, "Show PO3 Running Delta", group = "Advanced: HTF PO3")
// Alert Inputs
alertBullDivInput = input.bool(true, "Bullish Divergence", group = "Alerts")
alertBearDivInput = input.bool(true, "Bearish Divergence", group = "Alerts")
alertBullEngInput = input.bool(true, "Bullish Engulfing", group = "Alerts")
alertBearEngInput = input.bool(true, "Bearish Engulfing", group = "Alerts")
alertHammerInput = input.bool(true, "Hammer", group = "Alerts")
alertStarInput = input.bool(true, "Shooting Star", group = "Alerts")
//---------------------------------------------------------------------------------------------------------------------}
// Types & Methods
//---------------------------------------------------------------------------------------------------------------------{
type HTFData
float o
float h
float l
float c
int oIdx
int hIdx
int lIdx
float delta
int startTime
type HTFCandleUI
box body
line wick
line oM
line hM
line lM
line cM
label oL
label hL
label lL
label cL
label dL
label tL
//---------------------------------------------------------------------------------------------------------------------}
// Functions
//---------------------------------------------------------------------------------------------------------------------{
formatDelta(float val) =>
string sign = val > 0 ? "+" : ""
float absVal = math.abs(val)
absVal >= 1000000 ? sign + str.format("{0,number,#.#}M", val / 1000000) : absVal >= 1000 ? sign + str.format("{0,number,#.#}K", val / 1000) : sign + str.tostring(val)
drawHtfPattern(string name, float h, float l, float o, float c, int tStart, int tEnd, color col, bool isBullish) =>
string timeStr = str.format_time(tStart, "HH:mm", syminfo.timezone)
string labelTxt = name + "\n" + timeStr
int midTime = math.round((tStart + tEnd) / 2)
int timeInset = math.round((tEnd - tStart) * 0.1)
box.new(tStart, h, tEnd, l, xloc = xloc.bar_time, bgcolor = color.new(col, 90), border_color = color.new(col, 50), border_width = 1, force_overlay = true)
box.new(tStart + timeInset, math.max(o, c), tEnd - timeInset, math.min(o, c), xloc = xloc.bar_time, bgcolor = color.new(col, 60), border_color = color.new(col, 20), border_width = 1, force_overlay = true)
line.new(midTime, h, midTime, l, xloc = xloc.bar_time, color = color.new(col, 20), width = 1, force_overlay = true)
label.new(midTime, isBullish ? l : h, labelTxt, xloc = xloc.bar_time, yloc = yloc.price, color = #00000000, textcolor = col, style = isBullish ? label.style_label_up : label.style_label_down, size = size.small, force_overlay = true)
//---------------------------------------------------------------------------------------------------------------------}
// Core Calculations
//---------------------------------------------------------------------------------------------------------------------{
bool htfNewBar = nz(ta.change(time(htfInput))) != 0
// Fetch HTF Data: [1] is the bar that just finished
[hOpen, hHigh, hLow, hClose, hPrevOpen, hPrevClose, hTimeStart, hTimeEnd] = request.security(syminfo.tickerid, htfInput, [open[1], high[1], low[1], close[1], open[2], close[2], time[1], time_close[1]])
// PO3 Tracking logic
var htfHistory = array.new<HTFData>()
var float curO = na, var float curH = na, var float curL = na
var int curOIdx = na, var int curHIdx = na, var int curLIdx = na
var float curDelta = 0.0
var int curStartTime = na
if showPo3Input
if htfNewBar
if not na(curO)
htfHistory.unshift(HTFData.new(curO, curH, curL, close[1], curOIdx, curHIdx, curLIdx, curDelta, curStartTime))
if htfHistory.size() > 10
htfHistory.pop()
curO := open, curH := high, curL := low, curOIdx := bar_index, curHIdx := bar_index, curLIdx := bar_index, curDelta := (close > open ? volume : close < open ? -volume : 0), curStartTime := time
else
if high > curH or na(curH)
curH := high, curHIdx := bar_index
if low < curL or na(curL)
curL := low, curLIdx := bar_index
curDelta += (close > open ? volume : close < open ? -volume : 0)
// HTF Pattern Logic
bool isBullEngulfing = showEngulfingInput and hClose > hOpen and hPrevClose < hPrevOpen and hClose >= hPrevOpen and hOpen <= hPrevClose
bool isBearEngulfing = showEngulfingInput and hClose < hOpen and hPrevClose > hPrevOpen and hClose <= hPrevOpen and hOpen >= hPrevClose
bool isHammer = showPinBarsInput and (math.min(hOpen, hClose) - hLow) > (hHigh - hLow) * 0.6 and math.abs(hClose - hOpen) < (hHigh - hLow) * 0.3
bool isShootingStar = showPinBarsInput and (hHigh - math.max(hOpen, hClose)) > (hHigh - hLow) * 0.6 and math.abs(hClose - hOpen) < (hHigh - hLow) * 0.3
bool isBullPattern = isBullEngulfing or isHammer
bool isBearPattern = isBearEngulfing or isShootingStar
// RSI Calculations
float rsiValue = ta.rsi(close, rsiLenInput)
color rsiColor = rsiValue > 50 ? bullDivColInput : bearDivColInput
// RSI Divergence Logic
float phRsi = ta.pivothigh(rsiValue, lbLInput, lbRInput), float plRsi = ta.pivotlow(rsiValue, lbLInput, lbRInput)
bool bullDivConfirmed = false, bearDivConfirmed = false
var float plPriceMem = na, var float plRsiMem = na, var int plIndexMem = na
if not na(plRsi)
if not na(plPriceMem) and rsiValue[lbRInput] > plRsiMem and low[lbRInput] < plPriceMem
bullDivConfirmed := true
if showDivInput
line.new(plIndexMem, plPriceMem, bar_index - lbRInput, low[lbRInput], color = bullDivColInput, width = 2, force_overlay = true)
label.new(bar_index - lbRInput, low[lbRInput], "Bull Div", yloc = yloc.belowbar, textcolor = bullDivColInput, color = #00000000, size = size.small, style = label.style_label_up, force_overlay = true)
line.new(plIndexMem, plRsiMem, bar_index - lbRInput, rsiValue[lbRInput], color = bullDivColInput, width = 2)
plPriceMem := low[lbRInput], plRsiMem := rsiValue[lbRInput], plIndexMem := bar_index - lbRInput
var float phPriceMem = na, var float phRsiMem = na, var int phIndexMem = na
if not na(phRsi)
if not na(phPriceMem) and rsiValue[lbRInput] < phRsiMem and high[lbRInput] > phPriceMem
bearDivConfirmed := true
if showDivInput
line.new(phIndexMem, phPriceMem, bar_index - lbRInput, high[lbRInput], color = bearDivColInput, width = 2, force_overlay = true)
label.new(bar_index - lbRInput, high[lbRInput], "Bear Div", yloc = yloc.abovebar, textcolor = bearDivColInput, color = #00000000, size = size.small, style = label.style_label_down, force_overlay = true)
line.new(phIndexMem, phRsiMem, bar_index - lbRInput, rsiValue[lbRInput], color = bearDivColInput, width = 2)
phPriceMem := high[lbRInput], phRsiMem := rsiValue[lbRInput], phIndexMem := bar_index - lbRInput
//---------------------------------------------------------------------------------------------------------------------}
// Visuals
//---------------------------------------------------------------------------------------------------------------------{
// RSI Plots
rsiPlot = plot(rsiValue, "RSI", color = rsiColor, linewidth = 2)
midPlot = plot(50, "Mid Line", color = color.new(chart.fg_color, 80), style = plot.style_linebr)
fill(midPlot, rsiPlot, 50, rsiValue, rsiValue > 50 ? color.new(bullDivColInput, 100) : color.new(bearDivColInput, 50), rsiValue > 50 ? color.new(bullDivColInput, 50) : color.new(bearDivColInput, 100))
hline(70, "OB", color = color.new(bearDivColInput, 50), linestyle = hline.style_dashed)
hline(30, "OS", color = color.new(bullDivColInput, 50), linestyle = hline.style_dashed)
// HTF Reversal Pattern Drawings
if htfNewBar and not na(hTimeStart)
if isBullPattern
drawHtfPattern(isBullEngulfing ? "Bull Engulfing" : "Hammer", hHigh, hLow, hOpen, hClose, hTimeStart, hTimeEnd, bullPatternColInput, true)
if isBearPattern
drawHtfPattern(isBearEngulfing ? "Bear Engulfing" : "Shooting Star", hHigh, hLow, hOpen, hClose, hTimeStart, hTimeEnd, bearPatternColInput, false)
// PO3 UI Logic
var uiElements = array.new<HTFCandleUI>()
if barstate.islast and showPo3Input
if uiElements.size() > 0
for i = 0 to uiElements.size() - 1
HTFCandleUI ui = uiElements.get(i)
ui.body.delete()
ui.wick.delete()
if not na(ui.oM)
ui.oM.delete()
if not na(ui.hM)
ui.hM.delete()
if not na(ui.lM)
ui.lM.delete()
if not na(ui.cM)
ui.cM.delete()
if not na(ui.oL)
ui.oL.delete()
if not na(ui.hL)
ui.hL.delete()
if not na(ui.lL)
ui.lL.delete()
if not na(ui.cL)
ui.cL.delete()
if not na(ui.dL)
ui.dL.delete()
if not na(ui.tL)
ui.tL.delete()
uiElements.clear()
int candleWidth = 6, int candleGap = 10
for p = 0 to candleCountInput - 1
bool isLive = (p == candleCountInput - 1)
HTFData data = isLive ? HTFData.new(curO, curH, curL, close, curOIdx, curHIdx, curLIdx, curDelta, curStartTime) : (htfHistory.size() > (candleCountInput - 2) - p ? htfHistory.get((candleCountInput - 2) - p) : na)
if not na(data)
int startIdx = last_bar_index + po3OffsetInput + (p * (candleWidth + candleGap)), int endIdx = startIdx + candleWidth, int midIdx = (startIdx + endIdx) / 2
color baseColor = data.c >= data.o ? bullPatternColInput : bearPatternColInput
color wickColor = isLive ? baseColor : color.new(baseColor, 70)
line wLine = line.new(midIdx, data.h, midIdx, data.l, color = wickColor, width = 2, force_overlay = true)
box bBox = box.new(startIdx, math.max(data.o, data.c), endIdx, math.min(data.o, data.c), border_color = wickColor, bgcolor = color.new(baseColor, isLive ? 20 : 85), force_overlay = true)
line oM = na, line hM = na, line lM = na, line cM = na, label oL = na, label hL = na, label lL = na, label cL = na, label dL = na
if isLive
oM := line.new(data.oIdx, data.o, startIdx, data.o, color = NEUTRAL_COLOR, style = line.style_dashed, force_overlay = true)
hM := line.new(data.hIdx, data.h, midIdx, data.h, color = bullPatternColInput, style = line.style_dashed, force_overlay = true)
lM := line.new(data.lIdx, data.l, midIdx, data.l, color = bearPatternColInput, style = line.style_dashed, force_overlay = true)
cM := line.new(bar_index, data.c, endIdx, data.c, color = baseColor, style = line.style_dashed, force_overlay = true)
if showPo3LabelsInput
oL := label.new(endIdx + 1, data.o, "Open: " + str.tostring(data.o, format.mintick), color = #00000000, textcolor = NEUTRAL_COLOR, style = label.style_label_left, size = size.small, force_overlay = true)
hL := label.new(endIdx + 1, data.h, "High: " + str.tostring(data.h, format.mintick), color = #00000000, textcolor = bullPatternColInput, style = label.style_label_left, size = size.small, force_overlay = true)
lL := label.new(endIdx + 1, data.l, "Low: " + str.tostring(data.l, format.mintick), color = #00000000, textcolor = bearPatternColInput, style = label.style_label_left, size = size.small, force_overlay = true)
cL := label.new(endIdx + 1, data.c, "Close: " + str.tostring(data.c, format.mintick), color = #00000000, textcolor = baseColor, style = label.style_label_left, size = size.small, force_overlay = true)
int totalMins = timeframe.in_seconds(htfInput) / 60
string tfStr = totalMins >= 1440 ? str.tostring(totalMins/1440) + "D" : totalMins >= 60 ? str.tostring(totalMins/60) + "H" : str.tostring(totalMins) + "m"
label tL = label.new(midIdx, data.h, tfStr + (not isLive ? "\n" + str.format_time(data.startTime, "HH:mm", syminfo.timezone) : ""), color = #00000000, textcolor = isLive ? NEUTRAL_COLOR : color.new(NEUTRAL_COLOR, 60), style = label.style_label_down, size = size.normal, force_overlay = true)
if showPo3DeltaInput
dL := label.new(midIdx, data.l, "Delta: " + formatDelta(data.delta), color = #00000000, textcolor = isLive ? (data.delta >= 0 ? bullPatternColInput : bearPatternColInput) : color.new(NEUTRAL_COLOR, 60), style = label.style_label_up, size = size.normal, force_overlay = true)
uiElements.push(HTFCandleUI.new(bBox, wLine, oM, hM, lM, cM, oL, hL, lL, cL, dL, tL))
//---------------------------------------------------------------------------------------------------------------------}
// Alerts
//---------------------------------------------------------------------------------------------------------------------{
if (alertBullDivInput and bullDivConfirmed)
alert("Bullish RSI Divergence on " + syminfo.ticker, alert.freq_once_per_bar_close)
if (alertBearDivInput and bearDivConfirmed)
alert("Bearish RSI Divergence on " + syminfo.ticker, alert.freq_once_per_bar_close)
if (alertBullEngInput and htfNewBar and isBullEngulfing)
alert("HTF Bullish Engulfing on " + syminfo.ticker, alert.freq_once_per_bar_close)
if (alertBearEngInput and htfNewBar and isBearEngulfing)
alert("HTF Bearish Engulfing on " + syminfo.ticker, alert.freq_once_per_bar_close)
if (alertHammerInput and htfNewBar and isHammer)
alert("HTF Hammer on " + syminfo.ticker, alert.freq_once_per_bar_close)
if (alertStarInput and htfNewBar and isShootingStar)
alert("HTF Shooting Star on " + syminfo.ticker, alert.freq_once_per_bar_close)
//---------------------------------------------------------------------------------------------------------------------}