Strategy_OPR_Breakout.pine
Copy
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Breizhoo
// Version: 1.0.0
// GitHub: https://github.com/jcornierfra/TradingView_Strategy_JCO_OPR_Breakout
//@version=6
strategy("Breakout NQ, NY Session with RSI extreme, EMA optionnelle, Swing SL, R:R commun/séparé, with parameters", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=10, max_lines_count=500, max_boxes_count=500)
// ============================
// 🔧 Paramètres horaires & fuseau (HH:MM) avec info-bulles
// ============================
groupSess = "Paramètres horaires & fuseau"
// Range Start
rsH = input.int(15, "Range Start HH", minval=0, maxval=23, group=groupSess, inline="rs", tooltip="Heure (HH) de la bougie de référence pour définir le range. Interprétée dans le fuseau sélectionné ci-dessous.")
rsM = input.int(30, "MM", minval=0, maxval=59, group=groupSess, inline="rs", tooltip="Minutes (MM) de la bougie de référence pour définir le range.")
// ⏱️ Durée du range (en minutes) — même valeur pour High & Low
rangeMinutes = input.int(5, "Range Duration (min)", minval=1, maxval=240, group=groupSess, tooltip="Durée du range en minutes à partir de Range Start. Le plus haut et le plus bas sont accumulés pendant cette fenêtre.")
// Trade Open / Stop / Close
tradeOH = input.int(15, "Trade Open HH", minval=0, maxval=23, group=groupSess, inline="to", tooltip="Heure (HH) d'ouverture de la fenêtre d'entrées.")
tradeOM = input.int(35, "MM", minval=0, maxval=59, group=groupSess, inline="to", tooltip="Minutes (MM) d'ouverture de la fenêtre d'entrées.")
tradeSH = input.int(16, "Trade Stop HH", minval=0, maxval=23, group=groupSess, inline="ts", tooltip="Heure (HH) de fin de la fenêtre d'entrées.")
tradeSM = input.int(30, "MM", minval=0, maxval=59, group=groupSess, inline="ts", tooltip="Minutes (MM) de fin de la fenêtre d'entrées.")
tradeCH = input.int(19, "Trade Close HH", minval=0, maxval=23, group=groupSess, inline="tc", tooltip="Heure (HH) de clôture forcée des positions.")
tradeCM = input.int(30, "MM", minval=0, maxval=59, group=groupSess, inline="tc", tooltip="Minutes (MM) de la clôture forcée.")
// Fuseau horaire (au choix)
tzInput = input.string(defval="Europe/Paris", title="Fuseau horaire", options=["Europe/Paris", "Exchange (syminfo.timezone)", "UTC", "America/New_York", "America/Chicago", "Europe/London", "Asia/Tokyo", "Asia/Hong_Kong", "Australia/Sydney"], group=groupSess, tooltip="Sélectionne le fuseau pour interpréter toutes les heures ci-dessus. Europe/Paris : 15:30 = ouverture NY (été). Exchange (syminfo.timezone) : fuseau de l'instrument (ex: CME = America/Chicago).")
tz = tzInput == "Exchange (syminfo.timezone)" ? syminfo.timezone : tzInput
// ============================
// 🎛️ Paramètres d'affichage / rétention
// ============================
groupVis = "Affichage"
showRangeBox = input.bool(true, "Afficher la box du range", group=groupVis, tooltip="Affiche une box grise semi-transparente couvrant la fenêtre du range (du début jusqu'à la fin).")
maxSessions = input.int(60, "Nombre de sessions à conserver", minval=1, maxval=1000, group=groupVis, tooltip="Garde uniquement les N dernières sessions (lignes & boxes). Les plus anciennes sont supprimées automatiquement.")
// ============================
// 🕒 Horaires de trading (pour la fenêtre d'entrées)
// ============================
tradeOpen = timestamp(tz, year, month, dayofmonth, tradeOH, tradeOM)
tradeStop = timestamp(tz, year, month, dayofmonth, tradeSH, tradeSM)
tradeClose = timestamp(tz, year, month, dayofmonth, tradeCH, tradeCM)
// ============================
// 🧭 Fenêtre de RANGE avec session (robuste fuseau)
// ============================
pad2(x) => x < 10 ? "0" + str.tostring(x) : str.tostring(x)
_endTotal = rsH * 60 + rsM + rangeMinutes
endH = math.floor(_endTotal / 60) % 24
endM = _endTotal % 60
startStr = pad2(rsH) + pad2(rsM)
endStr = pad2(endH) + pad2(endM)
rangeSess = startStr + "-" + endStr + ":1234567"
tRange = time(timeframe.period, rangeSess, tz)
inRangeWindow = not na(tRange)
isFirstBarInRange = inRangeWindow and not inRangeWindow[1]
justAfterRange = (not inRangeWindow) and inRangeWindow[1]
// ============================
// 📈 Accumulation du range pendant la fenêtre
// ============================
var float rangeHighRef = na
var float rangeLowRef = na
var bool rangeReady = false
if isFirstBarInRange
rangeHighRef := na
rangeLowRef := na
rangeReady := false
// ⚑ Conserver l'étiquette de début de range (couleur actuelle)
label.new(x=bar_index, y=high, text="📍 Début Range", style=label.style_label_down, color=color.yellow, textcolor=color.black)
if inRangeWindow
rangeHighRef := na(rangeHighRef) ? high : math.max(rangeHighRef, high)
rangeLowRef := na(rangeLowRef) ? low : math.min(rangeLowRef, low)
if justAfterRange
rangeReady := true
// ============================
// 🧱 Box visuelle du range (historique, 1 par jour)
// ============================
var array<box> rangeBoxes = array.new_box()
var box curRangeBox = na
// Masquage
if not showRangeBox
if not na(curRangeBox)
box.delete(curRangeBox)
curRangeBox := na
sz = array.size(rangeBoxes)
if sz > 0
for i = 0 to sz - 1
bx = array.get(rangeBoxes, i)
if not na(bx)
box.delete(bx)
array.clear(rangeBoxes)
// Création au premier bar de la fenêtre de range
if showRangeBox and isFirstBarInRange
if not na(curRangeBox)
box.delete(curRangeBox)
_top = na(rangeHighRef) ? high : rangeHighRef
_bot = na(rangeLowRef) ? low : rangeLowRef
curRangeBox := box.new(left = bar_index, top = _top, right = bar_index, bottom = _bot, border_color = color.new(color.gray, 20), bgcolor = color.new(color.gray, 85), xloc = xloc.bar_index)
// Pendant la fenêtre de range
if showRangeBox and not na(curRangeBox) and inRangeWindow
_top = na(rangeHighRef) ? high : rangeHighRef
_bot = na(rangeLowRef) ? low : rangeLowRef
box.set_right(curRangeBox, bar_index)
box.set_top(curRangeBox, _top)
box.set_bottom(curRangeBox, _bot)
// Fin du range : fige, archive, purge
if showRangeBox and not na(curRangeBox) and justAfterRange
box.set_right(curRangeBox, bar_index)
box.set_top(curRangeBox, rangeHighRef)
box.set_bottom(curRangeBox, rangeLowRef)
array.push(rangeBoxes, curRangeBox)
while array.size(rangeBoxes) > maxSessions
oldBx = array.shift(rangeBoxes)
if not na(oldBx)
box.delete(oldBx)
curRangeBox := na
// ============================
// 🕘 Session active (fenêtre d'entrées)
// ============================
isSession = (time >= tradeOpen and time <= tradeStop)
isSessionStart = isSession and not isSession[1]
isSessionEnd = (not isSession) and isSession[1]
// Indices & lignes pour alignement Trade Open → Trade Stop
var int tradeStartIdx = na
var int tradeStopIdx = na
var line hiLine = na
var line loLine = na
// 🗃️ Arrays pour conserver les lignes par session (pour purge)
var array<line> hiLinesArr = array.new_line()
var array<line> loLinesArr = array.new_line()
// Début de session
if isSessionStart
tradeStartIdx := bar_index
tradeStopIdx := na
hiLine := na
loLine := na
if isSessionEnd
tradeStopIdx := bar_index
if not na(hiLine) and not na(loLine)
array.push(hiLinesArr, hiLine)
array.push(loLinesArr, loLine)
while array.size(hiLinesArr) > maxSessions
oldHi = array.shift(hiLinesArr)
if not na(oldHi)
line.delete(oldHi)
while array.size(loLinesArr) > maxSessions
oldLo = array.shift(loLinesArr)
if not na(oldLo)
line.delete(oldLo)
// ============================
// 📊 RSI / EMA / Règle 1 trade par jour
// ============================
groupRSI = "Filtre RSI"
useRSIFilter = input.bool(true, "Activer le filtre RSI", group=groupRSI)
rsiOverbought = input.int(70, "Seuil surachat", minval=50, maxval=100, group=groupRSI)
rsiOversold = input.int(30, "Seuil survente", minval=0, maxval=50, group=groupRSI)
rsiMode = input.string("RSI en dehors du range", "Mode d'application", options=["Aucun filtre", "RSI en dehors du range", "RSI dans le range"], group=groupRSI)
rsi = ta.rsi(close, 14)
rsiOkBuy = switch rsiMode
"Aucun filtre" => true
"RSI en dehors du range" => rsi >= rsiOverbought
"RSI dans le range" => rsi > rsiOversold and rsi < rsiOverbought
=> true
rsiOkSell = switch rsiMode
"Aucun filtre" => true
"RSI en dehors du range" => rsi <= rsiOversold
"RSI dans le range" => rsi > rsiOversold and rsi < rsiOverbought
=> true
// ============================
// 🟠 ➕ Paramètres EMA (optionnels)
// ============================
groupEMA = "Filtre EMA"
useEMA = input.bool(false, "Activer le filtre EMA ?", group=groupEMA, tooltip="Si désactivé, le filtre EMA est ignoré dans les entrées.")
emaLen = input.int(50, "Période EMA", minval=1, group=groupEMA)
emaSeries = ta.ema(close, emaLen)
emaOkBuy = useEMA ? (open > emaSeries and close > emaSeries and low > emaSeries) : true
emaOkSell = useEMA ? (open < emaSeries and close < emaSeries and high < emaSeries) : true
plot(emaSeries, title="EMA", color=color.new(color.blue, useEMA ? 0 : 100), linewidth=1)
// ============================
// 🔁 Règle 1 trade par jour
// ============================
var bool tradedToday = false
if dayofmonth != dayofmonth[1]
// ⚠️ filet de sécurité : si des SL/TP courants n'ont pas été archivés, on fige à la dernière barre d'hier
// (implémenté plus bas via finalize si besoin)
tradedToday := false
tradeStartIdx := na
tradeStopIdx := na
// ============================
// 🔎 Swings
// ============================
findLastSwingLow() =>
var float swingLow = na
for i = 1 to 20
if low[i] < low[i-1] and low[i] < low[i+1]
swingLow := low[i]
break
swingLow
findLastSwingHigh() =>
var float swingHigh = na
for i = 1 to 20
if high[i] > high[i-1] and high[i] > high[i+1]
swingHigh := high[i]
break
swingHigh
// ============================
// ⚙️ Paramètres du trade
// ============================
groupTrade = "Paramètres du trade"
tick = syminfo.mintick
pip = tick * 10
slBuffer = 1 * pip
// —— R:R commun ou séparé
rrSeparate = input.bool(false, "Utiliser R:R séparé (Long/Short) ?", group=groupTrade, tooltip="Active des R:R distincts pour les positions longues et courtes.")
rrCommon = input.float(1.5, "R:R commun", minval=0.1, step=0.1, group=groupTrade, tooltip="Risk:Reward utilisé si l'option séparée est désactivée.")
rrLongInp = input.float(1.5, "R:R Long", minval=0.1, step=0.1, group=groupTrade, tooltip="Risk:Reward pour les positions longues (utilisé si option séparée activée).")
rrShortInp = input.float(1.5, "R:R Short", minval=0.1, step=0.1, group=groupTrade, tooltip="Risk:Reward pour les positions courtes (utilisé si option séparée activée).")
rrLong = rrSeparate ? rrLongInp : rrCommon
rrShort = rrSeparate ? rrShortInp : rrCommon
// ============================
// 🚀 Cassure du range (après rangeReady)
// ============================
breakHigh = rangeReady and isSession and (not useRSIFilter or rsiOkBuy) and emaOkBuy and not tradedToday and close > rangeHighRef
breakLow = rangeReady and isSession and (not useRSIFilter or rsiOkSell) and emaOkSell and not tradedToday and close < rangeLowRef
// =====================================================================
// 🔴 SL/TP persistants — DÉBUT (entre entrée du trade et tradeClose + historique)
// =====================================================================
var line slLine = na
var line tpLine = na
var array<line> slHistory = array.new_line()
var array<line> tpHistory = array.new_line()
var int entryIdx = na
var int tradeCloseIdx = na
// Détection du passage à l'heure de tradeClose (barre exacte)
tradeCloseCross = (time >= tradeClose) and (time[1] < tradeClose)
// Quand l'heure tradeClose est atteinte : fige x2, archive et purge
if tradeCloseCross
tradeCloseIdx := bar_index
if not na(slLine)
line.set_x2(slLine, tradeCloseIdx)
if not na(tpLine)
line.set_x2(tpLine, tradeCloseIdx)
// Archive si les deux lignes existent
if not na(slLine) and not na(tpLine)
array.push(slHistory, slLine)
array.push(tpHistory, tpLine)
// Purge selon maxSessions
while array.size(slHistory) > maxSessions
oldSL = array.shift(slHistory)
if not na(oldSL)
line.delete(oldSL)
while array.size(tpHistory) > maxSessions
oldTP = array.shift(tpHistory)
if not na(oldTP)
line.delete(oldTP)
// Réinitialisation pour la prochaine journée
slLine := na
tpLine := na
entryIdx := na
tradeCloseIdx := na
// Filet de sécurité : au premier bar d'un nouveau jour, si des lignes subsistent non archivées
newDay = dayofmonth != dayofmonth[1]
if newDay and (not na(slLine) or not na(tpLine))
// fige sur la dernière barre d'hier
tradeCloseIdx := bar_index[1]
if not na(slLine)
line.set_x2(slLine, tradeCloseIdx)
if not na(tpLine)
line.set_x2(tpLine, tradeCloseIdx)
if not na(slLine) and not na(tpLine)
array.push(slHistory, slLine)
array.push(tpHistory, tpLine)
while array.size(slHistory) > maxSessions
oldSL = array.shift(slHistory)
if not na(oldSL)
line.delete(oldSL)
while array.size(tpHistory) > maxSessions
oldTP = array.shift(tpHistory)
if not na(oldTP)
line.delete(oldTP)
slLine := na
tpLine := na
entryIdx := na
tradeCloseIdx := na
// Pendant la vie du trade (avant tradeClose), prolonge x2 au fil des barres
if not na(slLine) and na(tradeCloseIdx)
line.set_x2(slLine, bar_index)
if not na(tpLine) and na(tradeCloseIdx)
line.set_x2(tpLine, bar_index)
// =====================================================================
// 🔴 SL/TP persistants — FIN
// =====================================================================
sl = 0.0
op = 0.0
// ============================
// 🟢 Achat
// ============================
if breakHigh and strategy.position_size == 0
swingLow = findLastSwingLow()
sl := swingLow - slBuffer
sl := sl < rangeLowRef ? rangeLowRef : sl
tp = close + rrLong * (close - sl)
op := close-(10*tick)
strategy.entry(id="Buy", direction=strategy.long, limit=op)
strategy.exit("TP/SL Buy", from_entry="Buy", stop=sl, limit=tp)
tradedToday := true
// --- REMPLACE les anciennes lignes SL/TP (+50) par :
entryIdx := bar_index
// S'assure d'effacer d'éventuels restes (sécurité)
if not na(slLine)
line.delete(slLine)
if not na(tpLine)
line.delete(tpLine)
tradeCloseIdx := na
slLine := line.new(x1=entryIdx, y1=sl, x2=bar_index, y2=sl, color=color.red, width=1, xloc=xloc.bar_index, extend=extend.none)
tpLine := line.new(x1=entryIdx, y1=tp, x2=bar_index, y2=tp, color=color.green, width=1, xloc=xloc.bar_index, extend=extend.none)
// ============================
// 🔴 Vente
// ============================
if breakLow and strategy.position_size == 0
swingHigh = findLastSwingHigh()
sl := swingHigh + slBuffer
sl := sl > rangeHighRef ? rangeHighRef : sl
tp = close - rrShort * (sl - close)
op := close+(10*tick)
strategy.entry(id="Sell", direction=strategy.short, limit=op)
strategy.exit("TP/SL Sell", from_entry="Sell", stop=sl, limit=tp)
tradedToday := true
// --- REMPLACE les anciennes lignes SL/TP (+50) par :
entryIdx := bar_index
if not na(slLine)
line.delete(slLine)
if not na(tpLine)
line.delete(tpLine)
tradeCloseIdx := na
slLine := line.new(x1=entryIdx, y1=sl, x2=bar_index, y2=sl, color=color.red, width=1, xloc=xloc.bar_index, extend=extend.none)
tpLine := line.new(x1=entryIdx, y1=tp, x2=bar_index, y2=tp, color=color.green, width=1, xloc=xloc.bar_index, extend=extend.none)
// ============================
// ⏱️ Trailing SL
// ============================
//if strategy.position_size != 0
// if strategy.position_size > 0 and close > close + (close - sl)
// strategy.order(id="Buy", direction=strategy.long, stop=op + ((close - sl) * 0.5), comment=("SL update"))
// if strategy.position_size < 0 and close < close - (sl - close)
// strategy.order(id="Sell", direction=strategy.short, stop=op - ((sl - close) * 0.5), comment=("SL update"))
// ============================
// ⏱️ Clôture forcée
// ============================
if (time >= tradeClose) and strategy.position_size != 0
strategy.close_all(comment="Clôture forcée")
// ============================
// 📏 Lignes alignées sur Trade Open → Trade Stop
// ============================
canCreateLines = rangeReady and not na(tradeStartIdx)
if canCreateLines and na(hiLine)
hiLine := line.new(x1=tradeStartIdx, y1=rangeHighRef, x2=bar_index, y2=rangeHighRef, color=color.blue, width=1, xloc=xloc.bar_index, extend=extend.none)
if canCreateLines and na(loLine)
loLine := line.new(x1=tradeStartIdx, y1=rangeLowRef, x2=bar_index, y2=rangeLowRef, color=color.blue, width=1, xloc=xloc.bar_index, extend=extend.none)
if isSession
if not na(hiLine)
line.set_x1(hiLine, tradeStartIdx)
line.set_x2(hiLine, bar_index)
line.set_y1(hiLine, rangeHighRef)
line.set_y2(hiLine, rangeHighRef)
if not na(loLine)
line.set_x1(loLine, tradeStartIdx)
line.set_x2(loLine, bar_index)
line.set_y1(loLine, rangeLowRef)
line.set_y2(loLine, rangeLowRef)
if isSessionEnd and not na(tradeStopIdx)
if not na(hiLine)
line.set_x2(hiLine, tradeStopIdx)
if not na(loLine)
line.set_x2(loLine, tradeStopIdx)
Show all 425 lines