// 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=5 indicator( 'SwingFailurePattern [LuxAlgo]', 'LuxAlgo - Swing Failure Pattern', max_labels_count = 500, max_lines_count = 500, max_boxes_count = 500, overlay = true) //---------------------------------------------------------------------------------------------------------------------} //Settings //---------------------------------------------------------------------------------------------------------------------{ sp = '            ' len = input.int ( 5 , 'Swings' , minval= 1 ) bull = input.bool ( true , 'Bullish SFP' ) bear = input.bool ( true , 'Bearish SFP' ) iVal = input.string ('None', 'Validation'+sp+ '   ', group='Volume Validation' , options = ['Volume outside swing < Threshold' ,'Volume outside swing > Threshold','None']) percent = input.float (25, 'Volume Threshold %' , inline='valy', group='Volume Validation' , tooltip = '% of Total Volume' , minval= 0 , maxval=100 ) auto = input.bool (true, '' , inline='auto', group='Volume Validation' ) mlt = input.int (50 , '      Auto' + sp , inline='auto', group='Volume Validation' ) res = input.timeframe('1' , sp +'   LTF' + sp +'  ', inline='ltf' , group='Volume Validation' ) prem = input.bool (false ,'    Premium' , group='Volume Validation' , tooltip = 'Premium Plan or higher' ) showDash = input.bool (true , 'Show Dashboard' , group= 'Dashboard' ) dashLoc = input.string ( 'Top Right' , 'Location' , options = ['Top Right', 'Bottom Right', 'Bottom Left'] , group= 'Dashboard' ) textSize = input.string ( 'Normal' , 'Size' , options = ['Tiny', 'Small', 'Normal'] , group= 'Dashboard' ) dSwingLine = input.bool (true , 'Swing Lines' , group= 'Style' ) dOpposLine = input.bool (true , 'Confirmation Lines' , group= 'Style' ) dSFP_Line = input.bool (true , 'Swing Failure Wick' , group= 'Style' ) dSFP_Label = input.bool (true , 'Swing Failure Label' , group= 'Style' ) showAlerts = input.bool (true , 'Show Alert Markers' , group= 'Style' ) alertSize = input.string ('Normal', 'Alert Marker Size' , group= 'Style' , options = ['Small', 'Normal', 'Large'] ) colBl = input.color (#089981 , 'Lines / Labels' , inline='lin' , group= 'Style' ) colBr = input.color (#f23645 , '' , inline='lin' , group= 'Style' , tooltip = 'Bullish/Bearish' ) colBl2 = input.color (#08998180, 'SFP Wicks      ', inline='wic' , group= 'Style' ) colBr2 = input.color (#f2364580, '' , inline='wic' , group= 'Style' , tooltip = 'Wick outside Swing, Bullish/Bearish' ) //-----------------------------------------------------------------------------} //UDT //-----------------------------------------------------------------------------{ type piv float swing_prc // price int swing_bix // bar_index float oppos_prc // price int oppos_bix // bar_index bool active bool confirmed line swing_line line oppos_line line wicky_line label wicky_label type swing int bix float prc //-----------------------------------------------------------------------------} //Variables //-----------------------------------------------------------------------------{ n = bar_index INV = color(na) FGc = chart.fg_color table_position = dashLoc == 'Bottom Left' ? position.bottom_left : dashLoc == 'Top Right' ? position.top_right : position.bottom_right table_size = textSize == 'Tiny' ? size.tiny : textSize == 'Small' ? size.small : size.normal alert_marker_size = alertSize == 'Small' ? size.small : alertSize == 'Large' ? size.large : size.normal var tb = table.new(table_position, 2, 3 , bgcolor = #1e222d , border_color = #373a46 , border_width = 1 , frame_color = #373a46 , frame_width = 1) var swing swingH = swing.new() var swing swingL = swing.new() var piv pivH = piv.new() var piv pivL = piv.new() validate = iVal != 'None' valHigher= iVal == 'Volume outside swing > Threshold' valLower = iVal == 'Volume outside swing < Threshold' //---------------------------------------------------------------------------------------------------------------------} //Method //---------------------------------------------------------------------------------------------------------------------{ method n(float piv) => bool out = not na(piv) //---------------------------------------------------------------------------------------------------------------------} //Execution //---------------------------------------------------------------------------------------------------------------------{ tfS = timeframe.in_seconds( res ) tfC = timeframe.in_seconds(timeframe.period) rs = auto ? tfC / mlt : tfS if not validate res := timeframe.period else rs := prem ? rs : math.max(60, rs) res := timeframe.from_seconds(math.min(tfC, rs)) ph = ta.pivothigh(len, 1) pl = ta.pivotlow (len, 1) [ltf_close, ltf_volume] = request.security_lower_tf(syminfo.tickerid, res, [close, volume]) ltf_size = ltf_close.size() if validate if ltf_size > 0 and ltf_size[1] == 0 line.new(n, close, n, close + syminfo.mintick, color=color.silver, style=line.style_dotted, extend=extend.both) //---------------------------------------------------------------------------------------------------------------------} //Bearish Pattern //---------------------------------------------------------------------------------------------------------------------{ if bear if ph.n() swingH.bix := n-1 swingH.prc := ph sw = swingH.prc bx = swingH.bix if high > sw and open < sw and close < sw valid = true if validate if ltf_close.size() > 0 outsideVolume = 0. totalVolume = ltf_volume.sum() for j = 0 to ltf_close.size() -1 if ltf_close.get(j) > sw outsideVolume += ltf_volume.get(j) if (valHigher ? 100 / totalVolume * outsideVolume < percent : 100 / totalVolume * outsideVolume > percent ) valid := false //if valid // label.new(n, high, text=str.format("Total Volume: {0}\nWick Volume: {1}", totalVolume, outsideVolume)) if valid opposL = sw opposB = n for i = 1 to n - bx -1 if low [i] < opposL opposL := low [i] opposB := n - i if not pivH.confirmed pivH.swing_line .delete() pivH.oppos_line .delete() pivH.wicky_line .delete() pivH.wicky_label.delete() pivH := piv.new(sw, bx, opposL, opposB, true, false) if dSwingLine pivH.swing_line := line.new (bx , sw , n, sw , color=colBr) if dOpposLine pivH.oppos_line := line.new (opposB, opposL, n, opposL, color=colBr, style=line.style_dotted) if dSFP_Line pivH.wicky_line := line.new (n , high , n, sw , color=colBr2, width=3) if dSFP_Label pivH.wicky_label := label.new(n , high , style=label.style_label_down , text='SFP', textcolor=colBr , color=INV, size=size.normal ) if pivH.active and not pivH.confirmed pivH.swing_line.set_x2(n) pivH.oppos_line.set_x2(n) if close < pivH.oppos_prc pivH.confirmed := true if pivH.wicky_label.get_x() == n pivH.wicky_label.set_text('SFP\n▼') else label.new(n, high, style=label.style_label_down, text='▼', textcolor=colBr, color=INV, size=size.normal) if n - pivH.swing_bix > 500 or close > pivH.swing_prc pivH.active := false if not pivH.confirmed pivH.swing_line .delete() pivH.oppos_line .delete() pivH.wicky_line .delete() pivH.wicky_label.delete() //---------------------------------------------------------------------------------------------------------------------} //Bullish Pattern //---------------------------------------------------------------------------------------------------------------------{ if bull if pl.n() swingL.bix := n-1 swingL.prc := pl sw = swingL.prc bx = swingL.bix if low < sw and open > sw and close > sw valid = true if validate if ltf_close.size() > 0 outsideVolume = 0. totalVolume = ltf_volume.sum() for j = 0 to ltf_close.size() -1 if ltf_close.get(j) < sw outsideVolume += ltf_volume.get(j) if (valHigher ? 100 / totalVolume * outsideVolume < percent : 100 / totalVolume * outsideVolume > percent ) valid := false if valid opposH = sw opposB = n for i = 1 to n - bx -1 if high[i] > opposH opposH := high[i] opposB := n - i if not pivL.confirmed pivL.swing_line .delete() pivL.oppos_line .delete() pivL.wicky_line .delete() pivL.wicky_label.delete() pivL := piv.new(sw, bx, opposH, opposB, true, false) if dSwingLine pivL.swing_line := line.new (bx , sw , n, sw , color=colBl ) if dOpposLine pivL.oppos_line := line.new (opposB, opposH, n, opposH, color=colBl , style=line.style_dotted) if dSFP_Line pivL.wicky_line := line.new (n , low , n, sw , color=colBl2, width=3) if dSFP_Label pivL.wicky_label := label.new(n , low , style=label.style_label_up , text='SFP', textcolor=colBl , color=INV, size=size.normal ) if pivL.active and not pivL.confirmed pivL.swing_line.set_x2(n) pivL.oppos_line.set_x2(n) if close > pivL.oppos_prc pivL.confirmed := true if pivL.wicky_label.get_x() == n pivL.wicky_label.set_text('▲\nSFP') else label.new(n, low, style=label.style_label_up, text='▲', textcolor=colBl, color=INV, size=size.normal) if n - pivL.swing_bix > 500 or close < pivL.swing_prc pivL.active := false if not pivL.confirmed pivL.swing_line .delete() pivL.oppos_line .delete() pivL.wicky_line .delete() pivL.wicky_label.delete() //---------------------------------------------------------------------------------------------------------------------} //Dashboard //---------------------------------------------------------------------------------------------------------------------{ if barstate.islast and validate and showDash tb.cell(0, 0, str.format("LTF: {0}", res), text_color=color.white, text_size=table_size) //---------------------------------------------------------------------------------------------------------------------} //Alert System //---------------------------------------------------------------------------------------------------------------------{ // ===== 统一警报系统 ===== // 统一警报系统 - 只需添加一次警报即可捕获所有信号 // 警报频率限制 - 每分钟只触发一次 var int last_alert_bearish_sfp = 0 var int last_alert_bullish_sfp = 0 var int last_alert_bearish_sfp_confirmed = 0 var int last_alert_bullish_sfp_confirmed = 0 // 获取当前时间(分钟级别) current_minute = math.floor(timenow / 60000) // 检测所有警报条件并生成对应的JSON消息 alert_message = "" // 看跌SFP信号检测 - 每分钟限制 bearish_sfp_signal = bear and high > swingH.prc and open < swingH.prc and close < swingH.prc and not pivH.confirmed if bearish_sfp_signal and current_minute > last_alert_bearish_sfp alert_message := '{"指标名称":"SwingFailurePattern","交易对":"' + syminfo.ticker + '","触发时间":"' + str.tostring(timenow) + '","时间":"' + str.tostring(time) + '","开盘价":"' + str.tostring(open, '#.####') + '","收盘价":"' + str.tostring(close, '#.####') + '","最高价":"' + str.tostring(high, '#.####') + '","最低价":"' + str.tostring(low, '#.####') + '","摆动高点":"' + str.tostring(swingH.prc, '#.####') + '","事件":"看跌摆动失败模式触发","信号":"bearish_sfp","备注":"突破高点后看跌回落"}' alert(alert_message, alert.freq_once_per_bar_close) last_alert_bearish_sfp := current_minute // 在K线上绘制看跌SFP信号标记 if showAlerts label.new(bar_index, high + (high - low) * 0.1, text='🔻SFP', style=label.style_label_down, color=color.new(color.red, 20), textcolor=color.white, size=alert_marker_size, tooltip='突破高点后看跌回落\n摆动高点: ' + str.tostring(swingH.prc, '#.####')) // 看涨SFP信号检测 - 每分钟限制 bullish_sfp_signal = bull and low < swingL.prc and open > swingL.prc and close > swingL.prc and not pivL.confirmed if bullish_sfp_signal and current_minute > last_alert_bullish_sfp alert_message := '{"指标名称":"SwingFailurePattern","交易对":"' + syminfo.ticker + '","触发时间":"' + str.tostring(timenow) + '","时间":"' + str.tostring(time) + '","开盘价":"' + str.tostring(open, '#.####') + '","收盘价":"' + str.tostring(close, '#.####') + '","最高价":"' + str.tostring(high, '#.####') + '","最低价":"' + str.tostring(low, '#.####') + '","摆动低点":"' + str.tostring(swingL.prc, '#.####') + '","事件":"看涨摆动失败模式触发","信号":"bullish_sfp","备注":"跌破低点后看涨反弹"}' alert(alert_message, alert.freq_once_per_bar_close) last_alert_bullish_sfp := current_minute // 在K线上绘制看涨SFP信号标记 if showAlerts label.new(bar_index, low - (high - low) * 0.1, text='🔺SFP', style=label.style_label_up, color=color.new(color.green, 20), textcolor=color.white, size=alert_marker_size, tooltip='跌破低点后看涨反弹\n摆动低点: ' + str.tostring(swingL.prc, '#.####')) // 看跌SFP确认信号 - 每分钟限制 bearish_sfp_confirmed = pivH.active and not pivH.confirmed[1] and pivH.confirmed and close < pivH.oppos_prc if bearish_sfp_confirmed and current_minute > last_alert_bearish_sfp_confirmed alert_message := '{"指标名称":"SwingFailurePattern","交易对":"' + syminfo.ticker + '","触发时间":"' + str.tostring(timenow) + '","时间":"' + str.tostring(time) + '","开盘价":"' + str.tostring(open, '#.####') + '","收盘价":"' + str.tostring(close, '#.####') + '","最高价":"' + str.tostring(high, '#.####') + '","最低价":"' + str.tostring(low, '#.####') + '","摆动高点":"' + str.tostring(pivH.swing_prc, '#.####') + '","对立价格":"' + str.tostring(pivH.oppos_prc, '#.####') + '","事件":"看跌摆动失败模式确认","信号":"bearish_sfp_confirmed","备注":"跌破对立低点确认看跌"}' alert(alert_message, alert.freq_once_per_bar_close) last_alert_bearish_sfp_confirmed := current_minute // 在K线上绘制看跌SFP确认信号标记 if showAlerts label.new(bar_index, high + (high - low) * 0.15, text='✅🔻', style=label.style_label_down, color=color.new(color.maroon, 10), textcolor=color.white, size=alert_marker_size, tooltip='跌破对立低点确认看跌\n摆动高点: ' + str.tostring(pivH.swing_prc, '#.####') + '\n对立价格: ' + str.tostring(pivH.oppos_prc, '#.####')) // 看涨SFP确认信号 - 每分钟限制 bullish_sfp_confirmed = pivL.active and not pivL.confirmed[1] and pivL.confirmed and close > pivL.oppos_prc if bullish_sfp_confirmed and current_minute > last_alert_bullish_sfp_confirmed alert_message := '{"指标名称":"SwingFailurePattern","交易对":"' + syminfo.ticker + '","触发时间":"' + str.tostring(timenow) + '","时间":"' + str.tostring(time) + '","开盘价":"' + str.tostring(open, '#.####') + '","收盘价":"' + str.tostring(close, '#.####') + '","最高价":"' + str.tostring(high, '#.####') + '","最低价":"' + str.tostring(low, '#.####') + '","摆动低点":"' + str.tostring(pivL.swing_prc, '#.####') + '","对立价格":"' + str.tostring(pivL.oppos_prc, '#.####') + '","事件":"看涨摆动失败模式确认","信号":"bullish_sfp_confirmed","备注":"突破对立高点确认看涨"}' alert(alert_message, alert.freq_once_per_bar_close) last_alert_bullish_sfp_confirmed := current_minute // 在K线上绘制看涨SFP确认信号标记 if showAlerts label.new(bar_index, low - (high - low) * 0.15, text='✅🔺', style=label.style_label_up, color=color.new(color.teal, 10), textcolor=color.white, size=alert_marker_size, tooltip='突破对立高点确认看涨\n摆动低点: ' + str.tostring(pivL.swing_prc, '#.####') + '\n对立价格: ' + str.tostring(pivL.oppos_prc, '#.####')) // ===== 传统警报条件(保留兼容性)===== // 注意:使用统一警报系统时,建议只使用上面的alert()函数 // 以下alertcondition保留用于需要单独设置警报的情况 // 创建用于警报的plot变量 plot(swingH.prc, title = '摆动高点', display = display.none) plot(swingL.prc, title = '摆动低点', display = display.none) plot(pivH.oppos_prc, title = '看跌对立价格', display = display.none) plot(pivL.oppos_prc, title = '看涨对立价格', display = display.none) // SFP信号警报 alertcondition(bearish_sfp_signal, title = '看跌摆动失败模式', message = '{"指标名称":"SwingFailurePattern","交易对":"{{ticker}}","触发时间":"{{timenow}}","时间":"{{time}}","开盘价":"{{open}}","收盘价":"{{close}}","最高价":"{{high}}","最低价":"{{low}}","摆动高点":"{{plot("摆动高点")}}","事件":"看跌摆动失败模式触发","信号":"bearish_sfp","备注":"突破高点后看跌回落"}') alertcondition(bullish_sfp_signal, title = '看涨摆动失败模式', message = '{"指标名称":"SwingFailurePattern","交易对":"{{ticker}}","触发时间":"{{timenow}}","时间":"{{time}}","开盘价":"{{open}}","收盘价":"{{close}}","最高价":"{{high}}","最低价":"{{low}}","摆动低点":"{{plot("摆动低点")}}","事件":"看涨摆动失败模式触发","信号":"bullish_sfp","备注":"跌破低点后看涨反弹"}') // SFP确认信号警报 alertcondition(bearish_sfp_confirmed, title = '看跌摆动失败模式确认', message = '{"指标名称":"SwingFailurePattern","交易对":"{{ticker}}","触发时间":"{{timenow}}","时间":"{{time}}","开盘价":"{{open}}","收盘价":"{{close}}","最高价":"{{high}}","最低价":"{{low}}","摆动高点":"{{plot("摆动高点")}}","对立价格":"{{plot("看跌对立价格")}}","事件":"看跌摆动失败模式确认","信号":"bearish_sfp_confirmed","备注":"跌破对立低点确认看跌"}') alertcondition(bullish_sfp_confirmed, title = '看涨摆动失败模式确认', message = '{"指标名称":"SwingFailurePattern","交易对":"{{ticker}}","触发时间":"{{timenow}}","时间":"{{time}}","开盘价":"{{open}}","收盘价":"{{close}}","最高价":"{{high}}","最低价":"{{low}}","摆动低点":"{{plot("摆动低点")}}","对立价格":"{{plot("看涨对立价格")}}","事件":"看涨摆动失败模式确认","信号":"bullish_sfp_confirmed","备注":"突破对立高点确认看涨"}') //---------------------------------------------------------------------------------------------------------------------}