本页源码来自 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/
// © fluxchart
//@version=5
const bool DEBUG = false
const int maxDistanceToLastBar = 4900 // Affects Running Time
const int atrLen = 5
const bool maxTPLastHour = false
var initRun = true
indicator("ICT Turtle Soup | Flux Charts", shorttitle = "Turtle Soup | Flux Charts", overlay = true, max_boxes_count = 500, max_labels_count = 500, max_lines_count = 500, max_bars_back = maxDistanceToLastBar + 100)
//pivotLenLiq = input.int(4, "Pivot Length", group = "General Configuration", display = display.none)
mssOffset = input.int(10, "MSS Swing Length", group = "General Configuration")
higherTimeframe = input.timeframe("60", "Higher Timeframe", group = "General Configuration")
breakoutMethod = input.string("Wick", "Breakout Method", options = ["Close", "Wick"], group = "General Configuration")
entryMethod = input.string("Classic", "Entry Method", options = ["Classic", "Adaptive"], group = "General Configuration", tooltip = "The entry method for the indicator to use. Try changing this setting if you are getting poor results.")
dbgTPSLVersion = input.string("Default", "TP / SL Layout", options = ["Default", "Alternative"], group = "General Configuration")
dbgLabelSize = DEBUG ? input.string("Small", "[DBG] Label Size", ["Normal", "Small", "Tiny"], group = "General Configuration") : "Normal"
lblSize = (dbgLabelSize == "Small" ? size.small : dbgLabelSize == "Normal" ? size.normal : size.tiny)
showHL = input.bool(false, "Show Liquidity Zones", inline = "3", group = "General Configuration")
showLiqGrabs = input.bool(true, "Liq Grabs", inline = "3", group = "General Configuration")
showTPSL = input.bool(true, "TP / SL", inline = "3", group = "General Configuration")
tpslMethod = input.string("Dynamic", "TP / SL Method", options = ["Dynamic", "Fixed"], group = "TP / SL")
riskAmount = input.string("Low", "Risk", options = ["Highest", "High", "Normal", "Low", "Lowest"], group = "TP / SL", tooltip = "The risk amount when Dynamic TP / SL method is selected.\n\nDifferent assets may have different volatility so changing this setting may result in change of performance of the indicator.")
customSLATRMult = DEBUG ? input.float(6.5, "Custom Risk Mult", group = "TP / SL") : 6.5
tpPercent = input.float(0.3, "Take Profit %", group = "TP / SL")
slPercent = input.float(0.4, "Stop Loss %", group = "TP / SL")
RR = DEBUG ? input.float(0.9, "Risk:Reward Ratio", group = "Debug") : 0.9
slATRMult = riskAmount == "Highest" ? 10 : riskAmount == "High" ? 6.5 : riskAmount == "Normal" ? 5.5 : riskAmount == "Low" ? 3.5 : riskAmount == "Lowest" ? 1.15 : customSLATRMult
backtestDisplayEnabled = input.bool(true, "Enabled", group = "Backtesting Dashboard", display = display.none)
backtestingLocation = input.string("Top Center", "Position", options = ["Top Right", "Right Center", "Top Center"], group = "Backtesting Dashboard", display = display.none)
fillBackgrounds = input.bool(true, "Fill Backgrounds", group = "Backtesting Dashboard", display = display.none)
screenerColor = input.color(#1B1F2B, 'Background', inline = "1", group = 'Backtesting Dashboard', display = display.none)
highColor = input.color(color.green, "Buy", inline = "colors", group = "Visuals")
lowColor = input.color(color.red, "Sell", inline = "colors", group = "Visuals")
textColor = input.color(color.white, "Text", inline = "colors", group = "Visuals")
buyAlertEnabled = input.bool(true, "Buy Signal", inline = "BS", group = "Alerts")
sellAlertEnabled = input.bool(true, "Sell Signal", inline = "BS", group = "Alerts")
tpAlertEnabled = input.bool(true, "Take-Profit Signal", inline = "TS", group = "Alerts")
slAlertEnabled = input.bool(true, "Stop-Loss Signal ", inline = "TS", group = "Alerts")
buyAlertTick = false
sellAlertTick = false
tpAlertTick = false
slAlertTick = false
type Sweep
int startTime
int endTime
string side
float price
type TurtleSoup
string state
int startTime
int lastHour = na
float lastHourHigh = na
float lastHourLow = na
Sweep brokenSweep = na
float slTarget
float tpTarget
string entryType
int entryTime
int exitTime
float entryPrice
float exitPrice
int dayEndedBeforeExit
var lineX = array.new<line>()
var boxX = array.new<box>()
var labelX = array.new<label>()
var TurtleSoup[] tsList = array.new<TurtleSoup>(0)
var TurtleSoup lastTS = na
atr = ta.atr(atrLen)
diffPercent (float val1, float val2) =>
(math.abs(val1 - val2) / val2) * 100.0
getPosition (positionText) =>
if positionText == "Top Right"
position.top_right
else if positionText == "Top Center"
position.top_center
else if positionText == "Right Center"
position.middle_right
else if positionText == "Left Center"
position.middle_left
else if positionText == "Bottom Center"
position.bottom_center
else if positionText == "Middle Center"
position.middle_center
tfInMin = timeframe.in_seconds() / 60
//#region Liqs
if timeframe.in_seconds(higherTimeframe) <= timeframe.in_seconds()
runtime.error("Higher timeframe must be higher than current timeframe.")
int htfMins = timeframe.in_seconds(higherTimeframe) / 60
int barLength = htfMins / tfInMin
float high12 = ta.highest(barLength)
float low12 = ta.lowest(barLength)
float highMSS = ta.highest(mssOffset)
float lowMSS = ta.lowest(mssOffset)
int lastHourTime = time[barLength]
//#endregion
//#region Turtle Soup
var highBreaks = 0
var lowBreaks = 0
if bar_index > last_bar_index - maxDistanceToLastBar
if true
// Find Session Start
createNewTS = true
if not na(lastTS)
if na(lastTS.exitPrice)
createNewTS := false // Don't enter if a trade is already entered
if createNewTS
newTS = TurtleSoup.new("Waiting For Liquidity Break", time)
newTS.lastHourHigh := high12
newTS.lastHourLow := low12
newTS.lastHour := lastHourTime
tsList.unshift(newTS)
lastTS := newTS
log.info("New Turtle Soup")
if not na(lastTS)
// Find Liquidity Breaks
if lastTS.state == "Waiting For Liquidity Break"
log.info("Wait For Liq Grab")
if time > lastTS.startTime
if (breakoutMethod == "Close" ? close : low) < lastTS.lastHourLow
log.info("Sellside Liq Grab")
if entryMethod == "Classic" or highBreaks > lowBreaks
lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Sellside", lastTS.lastHourLow)
lastTS.entryType := "Long"
else if highBreaks <= lowBreaks
//lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Buyside", lastTS.lastHourHigh)
lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Sellside", lastTS.lastHourLow)
lastTS.entryType := "Short"
lastTS.state := "Waiting For Execution"
else if (breakoutMethod == "Close" ? close : high) > lastTS.lastHourHigh
log.info("Buyside Liq Grab")
if entryMethod == "Classic" or highBreaks <= lowBreaks
lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Buyside", lastTS.lastHourHigh)
lastTS.entryType := "Short"
else if highBreaks > lowBreaks
//lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Sellside", lastTS.lastHourLow)
lastTS.brokenSweep := Sweep.new(lastTS.lastHour, time, "Buyside", lastTS.lastHourHigh)
lastTS.entryType := "Long"
lastTS.state := "Waiting For Execution"
// Execute
if lastTS.state == "Waiting For Execution"
if time > lastTS.brokenSweep.endTime
log.info("MSS Execution")
if lastTS.entryType == "Short"
if (breakoutMethod == "Close" ? close : low) < lowMSS[1]
sellAlertTick := true
lastTS.state := "Entry Taken"
lastTS.entryTime := time
lastTS.entryPrice := (breakoutMethod == "Close" ? close : lowMSS[1])
if tpslMethod == "Fixed"
lastTS.slTarget := lastTS.entryPrice * (1 + slPercent / 100.0)
lastTS.tpTarget := lastTS.entryPrice * (1 - tpPercent / 100.0)
else
lastTS.slTarget := highMSS + atr * slATRMult
lastTS.tpTarget := lastTS.entryPrice - (math.abs(lastTS.entryPrice - lastTS.slTarget) * RR)
else // Long
if (breakoutMethod == "Close" ? close : high) > highMSS[1]
buyAlertTick := true
lastTS.state := "Entry Taken"
lastTS.entryTime := time
lastTS.entryPrice := (breakoutMethod == "Close" ? close : highMSS[1])
if tpslMethod == "Fixed"
lastTS.slTarget := lastTS.entryPrice * (1 - slPercent / 100.0)
lastTS.tpTarget := lastTS.entryPrice * (1 + tpPercent / 100.0)
else
lastTS.slTarget := lowMSS - atr * slATRMult
lastTS.tpTarget := lastTS.entryPrice + (math.abs(lastTS.entryPrice - lastTS.slTarget) * RR)
// Entry Taken
if not na(lastTS)
if lastTS.state == "Entry Taken"
log.info("Entry Taken")
if tpslMethod == "Fixed"
// Take Profit
if lastTS.entryType == "Long" and ((high / lastTS.entryPrice) - 1) * 100 >= tpPercent
tpAlertTick := true
lastTS.exitPrice := lastTS.entryPrice * (1 + tpPercent / 100.0)
lastTS.exitTime := time
lastTS.state := "Take Profit"
highBreaks += 1
if lastTS.entryType == "Short" and ((low / lastTS.entryPrice) - 1) * 100 <= -tpPercent
tpAlertTick := true
lastTS.exitPrice := lastTS.entryPrice * (1 - tpPercent / 100.0)
lastTS.exitTime := time
lastTS.state := "Take Profit"
lowBreaks += 1
// Stop Loss
if lastTS.entryType == "Long" and ((low / lastTS.entryPrice) - 1) * 100 <= -slPercent
slAlertTick := true
lastTS.exitPrice := lastTS.entryPrice * (1 - slPercent / 100.0)
lastTS.exitTime := time
lastTS.state := "Stop Loss"
highBreaks -= 1
if lastTS.entryType == "Short" and ((high / lastTS.entryPrice) - 1) * 100 >= slPercent
slAlertTick := true
lastTS.exitPrice := lastTS.entryPrice * (1 + slPercent / 100.0)
lastTS.exitTime := time
lastTS.state := "Stop Loss"
lowBreaks -= 1
else
// Take Profit
if lastTS.entryType == "Long" and ((maxTPLastHour and high >= lastTS.lastHourHigh) or high >= lastTS.tpTarget)
tpAlertTick := true
lastTS.exitPrice := (high >= math.max(lastTS.lastHourHigh, lastTS.tpTarget) ? math.max(lastTS.lastHourHigh, lastTS.tpTarget) : math.min(lastTS.lastHourHigh, lastTS.tpTarget))
lastTS.exitTime := time
lastTS.state := "Take Profit"
highBreaks += 1
if lastTS.entryType == "Short" and ((maxTPLastHour and low <= lastTS.lastHourLow) or low <= lastTS.tpTarget)
tpAlertTick := true
lastTS.exitPrice := (low <= math.min(lastTS.lastHourLow, lastTS.tpTarget) ? math.min(lastTS.lastHourLow, lastTS.tpTarget) : math.max(lastTS.lastHourLow, lastTS.tpTarget))
lastTS.exitTime := time
lastTS.state := "Take Profit"
lowBreaks += 1
// Stop Loss
if lastTS.entryType == "Long" and low <= lastTS.slTarget
slAlertTick := true
lastTS.exitPrice := lastTS.slTarget
lastTS.exitTime := time
lastTS.state := "Stop Loss"
highBreaks -= 1
if lastTS.entryType == "Short" and high >= lastTS.slTarget
slAlertTick := true
lastTS.exitPrice := lastTS.slTarget
lastTS.exitTime := time
lastTS.state := "Stop Loss"
lowBreaks -= 1
//#endregion
//#region Render Turtle Soups
renderTopSL = false
renderBottomSL = false
renderTopTP = false
renderBottomTP = false
if not na(lastTS)
if lastTS.state == "Stop Loss" and time >= lastTS.exitTime
if lastTS.entryType == "Long"
renderBottomSL := true
else
renderTopSL := true
lastTS.state := "Done"
if lastTS.state == "Take Profit"
if lastTS.entryType == "Long"
renderTopTP := true
else
renderBottomTP := true
lastTS.state := "Done"
plotshape(renderTopSL, "", shape.circle, location.abovebar, color.red, textcolor = textColor, text = "SL", size = size.tiny)
plotshape(renderBottomSL, "", shape.circle, location.belowbar, color.red, textcolor = textColor, text = "SL", size = size.tiny)
plotshape(renderTopTP, "", shape.xcross, location.abovebar, color.blue, textcolor = textColor, text = "TP", size = size.tiny)
plotshape(renderBottomTP, "", shape.xcross, location.belowbar, color.blue, textcolor = textColor, text = "TP", size = size.tiny)
//#endregion
//#region Alerts
if barstate.islastconfirmedhistory
initRun := false
alertcondition(buyAlertTick and not initRun, "Buy Signal", "")
alertcondition(sellAlertTick and not initRun, "Sell Signal", "")
alertcondition(tpAlertTick and not initRun, "Take-Profit Signal", "")
alertcondition(slAlertTick and not initRun, "Stop-Loss Signal", "")
if not initRun
if buyAlertTick and buyAlertEnabled
alert("Buy Signal")
if sellAlertTick and sellAlertEnabled
alert("Sell Signal")
if tpAlertTick and tpAlertEnabled
alert("Take-Profit Signal")
if slAlertTick and slAlertEnabled
alert("Stop-Loss Signal")
//#endregion
if barstate.isconfirmed
if lineX.size() > 0
for i = 0 to lineX.size() - 1
line.delete(lineX.get(i))
if boxX.size() > 0
for i = 0 to boxX.size() - 1
box.delete(boxX.get(i))
if labelX.size() > 0
for i = 0 to labelX.size() - 1
label.delete(labelX.get(i))
lineX.clear()
boxX.clear()
labelX.clear()
if tsList.size() > 0
for i = 0 to math.min(125, tsList.size() - 1)
curTS = tsList.get(i)
// Target Liquidity
if not na(curTS.brokenSweep) and showHL
offset = atr / 3.0
if curTS.brokenSweep.price == curTS.lastHourHigh
boxX.push(box.new(curTS.brokenSweep.startTime, curTS.lastHourHigh + offset, curTS.brokenSweep.endTime, curTS.lastHourHigh - offset, text = "TARGET LIQUIDITY (" + str.tostring(higherTimeframe) + ")", text_color = textColor, xloc = xloc.bar_time, border_width = 0, bgcolor = color.new(highColor, 50), text_size = size.small))
//lineX.push(line.new(curTS.brokenSweep.startTime, curTS.lastHourHigh, curTS.brokenSweep.endTime, curTS.lastHourHigh, xloc = xloc.bar_time, color = lowColor, style = line.style_dashed))
else
boxX.push(box.new(curTS.brokenSweep.startTime, curTS.lastHourLow + offset, curTS.brokenSweep.endTime, curTS.lastHourLow - offset, text = "TARGET LIQUIDITY (" + str.tostring(higherTimeframe) + ")", text_color = textColor, xloc = xloc.bar_time, border_width = 0, bgcolor = color.new(lowColor, 50), text_size = size.small))
//lineX.push(line.new(curTS.brokenSweep.startTime, curTS.lastHourLow, curTS.brokenSweep.endTime, curTS.lastHourLow, xloc = xloc.bar_time, color = highColor, style = line.style_dashed))
// Liq Grab
if not na(curTS.brokenSweep) and showLiqGrabs
if curTS.brokenSweep.price == curTS.lastHourHigh
labelX.push(label.new(curTS.brokenSweep.endTime, high, yloc = yloc.abovebar, xloc = xloc.bar_time, style = label.style_circle, size = size.tiny, color = color.new(lowColor, 50)))
else
labelX.push(label.new(curTS.brokenSweep.endTime, low, yloc = yloc.belowbar, xloc = xloc.bar_time, style = label.style_circle, size = size.tiny, color = color.new(highColor, 50)))
if not na(curTS.entryTime)
// Entry Label
if curTS.entryType == "Long"
labelX.push(label.new(curTS.entryTime, close, "Buy", xloc = xloc.bar_time, yloc = yloc.belowbar, textcolor = textColor, color = highColor, style = label.style_label_up, size = lblSize))
else
labelX.push(label.new(curTS.entryTime, close, "Sell", xloc = xloc.bar_time, yloc = yloc.abovebar, textcolor = textColor, color = lowColor, style = label.style_label_down, size = lblSize))
// TP / SL
if not na(curTS.entryTime)
if showTPSL
if dbgTPSLVersion == "Alternative"
offset = atr / 3.0
endTime = nz(curTS.exitTime, time("", -15))
boxX.push(box.new(curTS.entryTime, curTS.tpTarget + offset, endTime, curTS.tpTarget - offset, text = "TAKE PROFIT (" + str.tostring(curTS.tpTarget, format.mintick) + ")", text_color = textColor, xloc = xloc.bar_time, border_width = 0, bgcolor = color.new(highColor, 50), text_size = size.small))
boxX.push(box.new(curTS.entryTime, curTS.slTarget + offset, endTime, curTS.slTarget - offset, text = "STOP LOSS (" + str.tostring(curTS.slTarget, format.mintick) + ")", text_color = textColor, xloc = xloc.bar_time, border_width = 0, bgcolor = color.new(lowColor, 50) , text_size = size.small))
else if dbgTPSLVersion == "Default"
endTime = nz(curTS.exitTime, time("", -15))
lineX.push(line.new(curTS.entryTime, curTS.entryPrice, curTS.entryTime, curTS.tpTarget, xloc = xloc.bar_time, color = highColor, style = line.style_dashed))
lineX.push(line.new(curTS.entryTime, curTS.tpTarget, endTime, curTS.tpTarget, xloc = xloc.bar_time, color = highColor, style = line.style_dashed))
labelX.push(label.new(endTime, curTS.tpTarget, "TP", xloc = xloc.bar_time, yloc = yloc.price, textcolor = textColor, color = color.new(highColor, 50), style = label.style_label_left, size = lblSize))
//
lineX.push(line.new(curTS.entryTime, curTS.entryPrice, curTS.entryTime, curTS.slTarget, xloc = xloc.bar_time, color = lowColor, style = line.style_dashed))
lineX.push(line.new(curTS.entryTime, curTS.slTarget, endTime, curTS.slTarget, xloc = xloc.bar_time, color = lowColor, style = line.style_dashed))
labelX.push(label.new(endTime, curTS.slTarget, "SL", xloc = xloc.bar_time, yloc = yloc.price, textcolor = textColor, color = color.new(lowColor, 50), style = label.style_label_left, size = lblSize))
if not na(curTS.dayEndedBeforeExit)
labelX.push(label.new(curTS.dayEndedBeforeExit, close, "Exit", xloc = xloc.bar_time, yloc = yloc.belowbar, textcolor = textColor, color = color.yellow, style = label.style_circle, size = size.tiny))
//#region Backtesting Dashboard
if barstate.islast and backtestDisplayEnabled
var table backtestDisplay = table.new(getPosition(backtestingLocation), 2, 10, bgcolor = screenerColor, frame_width = 2, frame_color = color.black, border_width = 1, border_color = color.black)
float totalTSProfitPercent = 0
int successfulTrades = 0
int unsuccessfulTrades = 0
if tsList.size() > 0
for i = 0 to tsList.size() - 1
curTS = tsList.get(i)
if not na(curTS.entryPrice)
isSuccess = false
if not na(curTS.exitPrice)
if (curTS.entryType == "Long" and curTS.exitPrice > curTS.entryPrice) or (curTS.entryType == "Short" and curTS.exitPrice < curTS.entryPrice)
totalTSProfitPercent += math.abs(diffPercent(curTS.entryPrice, curTS.exitPrice))
isSuccess := true
else
totalTSProfitPercent -= math.abs(diffPercent(curTS.entryPrice, curTS.exitPrice))
isSuccess := false
if isSuccess
successfulTrades += 1
else
unsuccessfulTrades += 1
// Header
table.merge_cells(backtestDisplay, 0, 0, 1, 0)
table.cell(backtestDisplay, 0, 0, "TS Backtesting", text_color = color.white, bgcolor = screenerColor)
// Total ORBs
table.cell(backtestDisplay, 0, 1, "Total Entries", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 1, str.tostring(successfulTrades + unsuccessfulTrades), text_color = color.white, bgcolor = screenerColor)
// Wins
table.cell(backtestDisplay, 0, 2, "Wins", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 2, str.tostring(successfulTrades), text_color = color.white, bgcolor = screenerColor)
// Losses
table.cell(backtestDisplay, 0, 3, "Losses", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 3, str.tostring(unsuccessfulTrades), text_color = color.white, bgcolor = screenerColor)
// Winrate
table.cell(backtestDisplay, 0, 4, "Winrate", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 4, str.tostring(100.0 * (successfulTrades / (successfulTrades + unsuccessfulTrades)), "#.##") + "%", text_color = color.white, bgcolor = screenerColor)
// Average Profit %
table.cell(backtestDisplay, 0, 5, "Average Profit", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 5, str.tostring(totalTSProfitPercent / (successfulTrades + unsuccessfulTrades), "#.##") + "%", text_color = color.white, bgcolor = screenerColor)
// Total Profit %
table.cell(backtestDisplay, 0, 6, "Total Profit", text_color = color.white, bgcolor = screenerColor)
table.cell(backtestDisplay, 1, 6, str.tostring(totalTSProfitPercent, "#.##") + "%", text_color = color.white, bgcolor = screenerColor)
//#endregion