Skip to content

Commit ee33919

Browse files
committed
add accepted Regime Trend v1 Pine strategy
1 parent fd5a529 commit ee33919

1 file changed

Lines changed: 262 additions & 0 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
//@version=6
2+
strategy(
3+
"Regime Trend v1 +2 ATR BE Ratchet",
4+
overlay = true,
5+
pyramiding = 0,
6+
initial_capital = 10000,
7+
default_qty_type = strategy.percent_of_equity,
8+
default_qty_value = 100,
9+
commission_type = strategy.commission.percent,
10+
commission_value = 0.10,
11+
process_orders_on_close = false,
12+
calc_on_order_fills = true)
13+
14+
// IMPORTANT: Run this script on a 5-minute chart.
15+
// The 4-hour entry/trend logic is requested from completed 4-hour candles.
16+
// The custom model below is authoritative for percentage-slippage parity.
17+
// TradingView's built-in `slippage` setting is tick-based, so visual Strategy Tester
18+
// fills can differ slightly from the custom normalized model shown in the table.
19+
20+
const string BASE_TF = "240"
21+
const int FIVE_MINUTES_MS = 5 * 60 * 1000
22+
const float COMMISSION_RATE = 0.001
23+
const float SLIPPAGE_RATE = 0.0005
24+
const float INITIAL_STOP_ATR = 2.5
25+
const float TRAILING_STOP_ATR = 3.0
26+
const float ACTIVATION_ATR = 2.0
27+
const float ATR_FLOOR = 0.005
28+
29+
bool isFiveMinuteChart = timeframe.isminutes and timeframe.multiplier == 5
30+
if barstate.isfirst and not isFiveMinuteChart
31+
runtime.error("Regime Trend v1 Ratchet must run on a 5-minute chart.")
32+
33+
float htfClose = request.security(
34+
syminfo.tickerid,
35+
BASE_TF,
36+
close,
37+
gaps = barmerge.gaps_off,
38+
lookahead = barmerge.lookahead_off)
39+
float htfEma50 = request.security(
40+
syminfo.tickerid,
41+
BASE_TF,
42+
ta.ema(close, 50),
43+
gaps = barmerge.gaps_off,
44+
lookahead = barmerge.lookahead_off)
45+
float htfEma200 = request.security(
46+
syminfo.tickerid,
47+
BASE_TF,
48+
ta.ema(close, 200),
49+
gaps = barmerge.gaps_off,
50+
lookahead = barmerge.lookahead_off)
51+
float htfAtr14 = request.security(
52+
syminfo.tickerid,
53+
BASE_TF,
54+
ta.atr(14),
55+
gaps = barmerge.gaps_off,
56+
lookahead = barmerge.lookahead_off)
57+
float htfBreakout = request.security(
58+
syminfo.tickerid,
59+
BASE_TF,
60+
ta.highest(high[1], 20),
61+
gaps = barmerge.gaps_off,
62+
lookahead = barmerge.lookahead_off)
63+
64+
bool htfBarClosesHere = time_close == time_close(BASE_TF)
65+
bool htfReady = not na(htfClose) and not na(htfEma50) and not na(htfEma200) and not na(htfAtr14) and not na(htfBreakout)
66+
67+
f_breakEvenRawFloor(float modelEntryFill) =>
68+
float entryFeePerUnitNotional = COMMISSION_RATE
69+
float exitFillNeeded = modelEntryFill * (1.0 + entryFeePerUnitNotional) / (1.0 - COMMISSION_RATE)
70+
exitFillNeeded / (1.0 - SLIPPAGE_RATE)
71+
72+
f_exitFill(float rawReference) =>
73+
rawReference * (1.0 - SLIPPAGE_RATE)
74+
75+
var bool modelInTrade = false
76+
var bool pendingEntry = false
77+
var float pendingEntryAtr = na
78+
var bool pendingTrendExit = false
79+
80+
var float modelEntryFill = na
81+
var float modelEntryAtr = na
82+
var float modelQuantity = na
83+
var float modelEntryFee = na
84+
var float originalStop = na
85+
var float pendingOriginalStop = na
86+
var float highestHtfClose = na
87+
var float activationPrice = na
88+
var float ratchetFloor = na
89+
var bool ratchetArmed = false
90+
var bool ratchetActive = false
91+
var bool parityGap = false
92+
var int modelEntryTime = na
93+
94+
var int modelTradeCount = 0
95+
var int modelWinnerCount = 0
96+
var int modelRatchetExitCount = 0
97+
var int modelGapTradeCount = 0
98+
var float modelCumulativeNet = 0.0
99+
var float lastModelNet = na
100+
var string lastModelExitReason = "none"
101+
102+
bool dataGapThisBar = not na(time[1]) and time - time[1] != FIVE_MINUTES_MS
103+
if modelInTrade and dataGapThisBar
104+
parityGap := true
105+
106+
// State calculated on the previous 5-minute close becomes active now.
107+
if modelInTrade and not na(pendingOriginalStop)
108+
originalStop := math.max(originalStop, pendingOriginalStop)
109+
pendingOriginalStop := na
110+
111+
if modelInTrade and ratchetArmed
112+
ratchetActive := true
113+
ratchetArmed := false
114+
115+
bool enteredThisBar = false
116+
bool exitedThisBar = false
117+
118+
// A signal submitted on the final 5-minute bar of a 4-hour candle fills here,
119+
// at the next 5-minute open, which is also the next 4-hour open.
120+
if pendingEntry and not modelInTrade
121+
modelEntryFill := open * (1.0 + SLIPPAGE_RATE)
122+
modelEntryAtr := pendingEntryAtr
123+
modelQuantity := 1.0 / modelEntryFill
124+
modelEntryFee := modelEntryFill * modelQuantity * COMMISSION_RATE
125+
originalStop := modelEntryFill - INITIAL_STOP_ATR * modelEntryAtr
126+
pendingOriginalStop := na
127+
highestHtfClose := na
128+
activationPrice := modelEntryFill + ACTIVATION_ATR * modelEntryAtr
129+
ratchetFloor := f_breakEvenRawFloor(modelEntryFill)
130+
ratchetArmed := false
131+
ratchetActive := false
132+
parityGap := dataGapThisBar
133+
pendingTrendExit := false
134+
modelEntryTime := time
135+
modelInTrade := true
136+
pendingEntry := false
137+
pendingEntryAtr := na
138+
enteredThisBar := true
139+
140+
float activeRatchetFloor = modelInTrade and ratchetActive and not parityGap ? ratchetFloor : na
141+
float combinedStop = modelInTrade ? math.max(originalStop, nz(activeRatchetFloor, originalStop)) : na
142+
143+
string modelExitReason = ""
144+
float modelRawExitReference = na
145+
bool ratchetBindingExit = false
146+
147+
if modelInTrade
148+
if pendingTrendExit
149+
if open <= combinedStop
150+
modelRawExitReference := open
151+
ratchetBindingExit := ratchetActive and not parityGap and ratchetFloor > originalStop and open > originalStop
152+
modelExitReason := ratchetBindingExit ? "ratchet_gap" : "baseline_stop_gap"
153+
else
154+
modelRawExitReference := open
155+
modelExitReason := "trend_exit"
156+
else if open <= combinedStop
157+
modelRawExitReference := open
158+
ratchetBindingExit := ratchetActive and not parityGap and ratchetFloor > originalStop and open > originalStop
159+
modelExitReason := ratchetBindingExit ? "ratchet_gap" : "baseline_stop_gap"
160+
else if low <= combinedStop
161+
modelRawExitReference := combinedStop
162+
ratchetBindingExit := ratchetActive and not parityGap and ratchetFloor > originalStop
163+
modelExitReason := ratchetBindingExit ? "ratchet_stop" : "baseline_stop"
164+
165+
if modelInTrade and modelExitReason != ""
166+
float modelExitFill = f_exitFill(modelRawExitReference)
167+
float modelExitFee = modelExitFill * modelQuantity * COMMISSION_RATE
168+
float modelGrossPnl = (modelExitFill - modelEntryFill) * modelQuantity
169+
float modelNetPnl = modelGrossPnl - modelEntryFee - modelExitFee
170+
171+
modelTradeCount += 1
172+
modelWinnerCount += modelNetPnl > 0 ? 1 : 0
173+
modelRatchetExitCount += ratchetBindingExit ? 1 : 0
174+
modelGapTradeCount += parityGap ? 1 : 0
175+
modelCumulativeNet += modelNetPnl
176+
lastModelNet := modelNetPnl
177+
lastModelExitReason := modelExitReason
178+
179+
modelInTrade := false
180+
pendingTrendExit := false
181+
modelEntryFill := na
182+
modelEntryAtr := na
183+
modelQuantity := na
184+
modelEntryFee := na
185+
originalStop := na
186+
pendingOriginalStop := na
187+
highestHtfClose := na
188+
activationPrice := na
189+
ratchetFloor := na
190+
ratchetArmed := false
191+
ratchetActive := false
192+
parityGap := false
193+
modelEntryTime := na
194+
exitedThisBar := true
195+
196+
// Activation is evaluated only after the current candle's pre-existing stop.
197+
// Therefore the break-even floor can first act on the next 5-minute candle.
198+
if modelInTrade and not ratchetActive and not ratchetArmed and not parityGap
199+
bool activationTouched = open >= activationPrice or high >= activationPrice
200+
if activationTouched
201+
ratchetArmed := true
202+
203+
// Completed 4-hour close logic. The resulting stop update and trend-exit order
204+
// become active at the next 5-minute bar, i.e. the next 4-hour open.
205+
if modelInTrade and htfBarClosesHere and htfReady and not exitedThisBar
206+
highestHtfClose := na(highestHtfClose) ? htfClose : math.max(highestHtfClose, htfClose)
207+
float candidateOriginalStop = highestHtfClose - TRAILING_STOP_ATR * htfAtr14
208+
pendingOriginalStop := math.max(originalStop, candidateOriginalStop)
209+
210+
if htfClose < htfEma50
211+
pendingTrendExit := true
212+
strategy.close("Long", comment = "4H trend exit")
213+
214+
// Keep the broker-emulator stop aligned with the stop that will be active on
215+
// the next 5-minute candle. The custom model remains authoritative for the
216+
// percentage-slippage arithmetic.
217+
if modelInTrade
218+
float nextOriginalStop = not na(pendingOriginalStop) ? math.max(originalStop, pendingOriginalStop) : originalStop
219+
bool nextRatchetActive = ratchetActive or ratchetArmed
220+
float nextCombinedStop = nextRatchetActive and not parityGap ? math.max(nextOriginalStop, ratchetFloor) : nextOriginalStop
221+
strategy.exit("Long Stop", "Long", stop = nextCombinedStop, comment_loss = "Active stop")
222+
223+
bool bullishRegime = htfReady and htfEma50 > htfEma200 and htfClose > htfEma200
224+
bool breakout = htfReady and htfClose > htfBreakout
225+
bool volatilityAllowed = htfReady and htfAtr14 / htfClose >= ATR_FLOOR
226+
bool longSignal = htfBarClosesHere and bullishRegime and breakout and volatilityAllowed
227+
228+
if longSignal and not modelInTrade and not pendingEntry and not exitedThisBar
229+
pendingEntry := true
230+
pendingEntryAtr := htfAtr14
231+
strategy.entry("Long", strategy.long, comment = "4H breakout")
232+
233+
plot(htfEma50, "4H EMA 50", color = color.orange)
234+
plot(htfEma200, "4H EMA 200", color = color.blue)
235+
plot(modelInTrade ? originalStop : na, "Original 4H Stop", color = color.red, style = plot.style_linebr)
236+
plot(modelInTrade and ratchetActive and not parityGap ? ratchetFloor : na, "Ratchet Floor", color = color.aqua, style = plot.style_linebr)
237+
plot(modelInTrade ? activationPrice : na, "+2 ATR Activation", color = color.new(color.green, 40), style = plot.style_linebr)
238+
239+
plotshape(longSignal and not modelInTrade, title = "Long signal", style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny, text = "4H")
240+
plotshape(ratchetArmed, title = "Ratchet armed", style = shape.circle, location = location.abovebar, color = color.aqua, size = size.tiny, text = "BE")
241+
bgcolor(modelInTrade and parityGap ? color.new(color.red, 88) : na, title = "Parity gap trade")
242+
243+
var table status = table.new(position.top_right, 2, 9, border_width = 1)
244+
if barstate.islast
245+
table.cell(status, 0, 0, "Model")
246+
table.cell(status, 1, 0, "Regime Trend v1 Ratchet")
247+
table.cell(status, 0, 1, "Chart")
248+
table.cell(status, 1, 1, timeframe.period)
249+
table.cell(status, 0, 2, "Trades")
250+
table.cell(status, 1, 2, str.tostring(modelTradeCount))
251+
table.cell(status, 0, 3, "Winners")
252+
table.cell(status, 1, 3, str.tostring(modelWinnerCount))
253+
table.cell(status, 0, 4, "Ratchet exits")
254+
table.cell(status, 1, 4, str.tostring(modelRatchetExitCount))
255+
table.cell(status, 0, 5, "Gap-flagged")
256+
table.cell(status, 1, 5, str.tostring(modelGapTradeCount))
257+
table.cell(status, 0, 6, "Custom net")
258+
table.cell(status, 1, 6, str.tostring(modelCumulativeNet, "#.######"))
259+
table.cell(status, 0, 7, "Last net")
260+
table.cell(status, 1, 7, na(lastModelNet) ? "n/a" : str.tostring(lastModelNet, "#.######"))
261+
table.cell(status, 0, 8, "Last exit")
262+
table.cell(status, 1, 8, lastModelExitReason)

0 commit comments

Comments
 (0)