1 | // "Mystery Strategy Mark II" / "Holy Grail for ETH" |
2 | // |
3 | // Preconfigured for ETH 30m on Bitget |
4 | // Works on: ETH since 2022-12, AVAX since 2023-3 |
5 | // |
6 | // (c) Stefan Reich |
7 | //@version=5 |
8 | |
9 | strategy(shorttitle="Stefan Reich's Mystery Strategy Mark II v15", title="Stefan Reich's Mystery Strategy Mark II", |
10 | overlay=true, |
11 | commission_type=strategy.commission.percent, commission_value=0.07, |
12 | initial_capital = 100, default_qty_type=strategy.cash, default_qty_value=1000, |
13 | calc_on_every_tick = true, |
14 | max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500) |
15 | |
16 | // commission_value is an approximately correct estimate for fees+spread on ETH on typical exchanges (Bitget, Bybit) |
17 | // This strategy opens few positions though, so fees+spread are not a big deal anyway. |
18 | |
19 | startingDate = input.time(timestamp("8 Dec 2022")) |
20 | useEndDate = input.bool(false, tooltip="Activate the end date below") |
21 | endDate = input.time(timestamp("31 Dec 2023"), tooltip="When to end the backtest or execution (if checkbox above is set)") |
22 | |
23 | enabledHoldSides = input.string(title="Hold side", defval="Longs + Shorts", options=["Longs", "Shorts", "Longs + Shorts"]) |
24 | doLongs=str.contains(enabledHoldSides, "Longs") |
25 | doShorts=str.contains(enabledHoldSides, "Shorts") |
26 | |
27 | positionSize = input.float(1000, "Position size in $", tooltip="After leverage") |
28 | intraCandleOperation = input.bool(false, tooltip="Open or close trades within a candle") |
29 | |
30 | hotSwitchOkay = input.bool(true, tooltip="Can we kill a long by opening a short and vice versa?") |
31 | macdType = input.string(title="MACD type", options=["Relative", "Absolute"], defval="Relative", tooltip="Relative=MACD normalized to asset price, Absolute=Normal MACD") |
32 | useRelativeMACD = macdType == "Relative" |
33 | |
34 | switchOnLongsAbove = input.float(5, step=1, tooltip="Allow longs only if MACD is above this level") |
35 | switchOnShortsBelow = input.float(-5, step=1, tooltip="Allow shorts only if MACD is below this level") |
36 | |
37 | enterThresholdLong = input.float(1, step=0.1, tooltip="MACD histogram value to enter a long at") |
38 | exitThresholdLong = input.float(-1, step=0.1, tooltip="MACD histogram value to exit a long at") |
39 | enterThresholdShort = input.float(-1, step=0.1, tooltip="MACD histogram value to enter a short at") |
40 | exitThresholdShort = input.float(1, step=0.1, tooltip="MACD histogram value to exit a short at") |
41 | |
42 | // Layout options |
43 | |
44 | arrowOffset = input.float(10, group="Layout") |
45 | initialOffset = input.float(10, group="Layout") |
46 | equityOffset = input.float(10, group="Layout") |
47 | topOffset = input.float(1, step=0.25, group="Layout") |
48 | filesSlanted = input.bool(true, group="Layout") |
49 | equityXOffset = input.int(10, tooltip="Move equity box to the right using this value", group="Layout") |
50 | equityShort = input.bool(true, tooltip="Show only the new equity, no text", group="Layout") |
51 | ieBoxColor = input.color(color.black, group="Layout") |
52 | ieTextColor = input.color(color.blue, group="Layout") |
53 | |
54 | // UTILITY FUNCTIONS |
55 | |
56 | drift() => |
57 | sumDrift = 0.0 |
58 | for tradeNo = 0 to strategy.opentrades - 1 |
59 | sumDrift += strategy.opentrades.size(tradeNo) |
60 | result = sumDrift |
61 | |
62 | // macro logFloat(value, title) { plotchar(value, title, "", location.top) } |
63 | |
64 | // MACD inputs |
65 | fast_length = input(title="Fast Length", defval=25) |
66 | slow_length = input(title="Slow Length", defval=100) |
67 | macd_src = input(title="Source", defval=close) |
68 | signal_length = input.int(title="Signal Smoothing", minval = 1, defval = 9) |
69 | sma_source = input.string(title="Oscillator MA Type", defval="EMA", options=["SMA", "EMA"]) |
70 | sma_signal = input.string(title="Signal Line MA Type", defval="EMA", options=["SMA", "EMA"]) |
71 | |
72 | // Add more inputs down here to not mess up configurations of older versions |
73 | |
74 | // MACD calculation |
75 | |
76 | fast_ma = sma_source == "SMA" ? ta.sma(macd_src, fast_length) : ta.ema(macd_src, fast_length) |
77 | slow_ma = sma_source == "SMA" ? ta.sma(macd_src, slow_length) : ta.ema(macd_src, slow_length) |
78 | macd = fast_ma - slow_ma |
79 | signal = sma_signal == "SMA" ? ta.sma(macd, signal_length) : ta.ema(macd, signal_length) |
80 | hist = macd - signal |
81 | |
82 | // Plot MACD |
83 | plot(fast_ma, title="MACD Fast MA", color=color.yellow) |
84 | plot(slow_ma, title="MACD Slow MA", color=color.blue) |
85 | plotchar(macd, "MACD", "", location.top) |
86 | |
87 | // Relative MACD values (in preparation for Mark II strategy) |
88 | factor = 1000 / slow_ma |
89 | relMACD = macd * factor |
90 | relSignal = signal * factor |
91 | relHist = (macd - signal) * factor |
92 | |
93 | plotchar(relMACD, "Relative MACD", "", location.top) // logFloat |
94 | plotchar(hist, "MACD Histogram", "", location.top) // logFloat |
95 | plotchar(relHist, "Relative MACD Histogram", "", location.top) // logFloat |
96 | |
97 | macdToUse = useRelativeMACD ? relMACD : macd |
98 | histToUse = useRelativeMACD ? relHist : hist |
99 | |
100 | if time >= startingDate and (not useEndDate or time <= endDate) and (intraCandleOperation or barstate.isconfirmed) |
101 | qty = positionSize/close |
102 | |
103 | if drift() < 0.0 and histToUse >= exitThresholdShort |
104 | strategy.close_all("MACD Green") |
105 | else if drift() > 0.0 and histToUse <= exitThresholdLong |
106 | strategy.close_all("MACD Red") |
107 | |
108 | if hotSwitchOkay or drift() == 0.0 |
109 | if doLongs and histToUse >= enterThresholdLong and macdToUse >= switchOnLongsAbove |
110 | strategy.entry("Long", strategy.long, qty=qty) |
111 | else if doShorts and histToUse <= enterThresholdShort and macdToUse <= switchOnShortsBelow |
112 | strategy.entry("Short", strategy.short, qty=qty) |
113 | |
114 | // PAINT TRADES & INITIAL EQUITY |
115 | |
116 | plusMinusFix(s) => |
117 | currency = " $" |
118 | str.startswith(s, "-") ? "-" + currency + str.substring(s, 1) : "+" + currency + s |
119 | |
120 | formatProfit(profit) => |
121 | plusMinusFix(str.tostring(profit, "#,##0.00")) |
122 | |
123 | var int tradesPainted = 0 |
124 | var label initialEquityLabel = na |
125 | |
126 | if na(initialEquityLabel) and time >= startingDate |
127 | ieText = "⬆\nWe invest\n$" + str.tostring(strategy.initial_capital, "#,##0") + "\nand start\nthe strategy!" |
128 | ieTime = startingDate-2*24*60*60*1000 // 2 days back |
129 | //ieY = close*(1-arrowOffset/100) // for style_label_center |
130 | //style=label.style_label_center |
131 | ieY = close*(1-initialOffset/100) // for style_label_up |
132 | //boxColor = color.black |
133 | //textColor = color.white |
134 | style = label.style_label_up |
135 | //style = label.style_triangleup |
136 | initialEquityLabel := label.new(ieTime, ieY, ieText, xloc=xloc.bar_time, color=ieBoxColor, textcolor=ieTextColor, tooltip="Initial equity", size=size.huge, style=style) |
137 | |
138 | type PaintedTrade |
139 | array<line> lines |
140 | array<label> labels |
141 | |
142 | drawTrade(bar1, bar2, price1, price2, profit, open, long) => |
143 | t = PaintedTrade.new() |
144 | t.lines := array.new<line>() |
145 | t.labels := array.new<label>() |
146 | totalProfit = strategy.equity // -strategy.initial_capital |
147 | //line.new(bar1, price1, bar2, price2, xloc=xloc.bar_index, width = 10, color=color.black, style=line.style_arrow_right) |
148 | float y1 = price1*(1-arrowOffset/100) |
149 | float y2 = price2*(1-arrowOffset/100) |
150 | float top1 = price1*(1-topOffset/100) |
151 | float top2 = (open ? math.min(price2, low) : price2)*(1-topOffset/100) |
152 | color_solid = profit > 0 ? color.lime : color.red |
153 | color = color.new(color_solid, 50) |
154 | |
155 | if not filesSlanted |
156 | y1 := math.min(y1, y2) |
157 | y2 := y1 |
158 | |
159 | array.push(t.lines, line.new(bar1, top1, bar1, y1, color=color, width=3)) |
160 | array.push(t.lines, line.new(bar2, top2, bar2, y2, color=color, width=3)) |
161 | array.push(t.lines, line.new(bar1, y1, bar2, y2, xloc=xloc.bar_index, width = 10, color=color)) //, style=line.style_arrow_right) |
162 | |
163 | ypos = 0.6 |
164 | string profitText = formatProfit(profit) |
165 | yPrice = (price1+price2)/2 |
166 | yBottom = (y1+y2)/2 |
167 | yText = yPrice*(1-ypos)+yBottom*ypos |
168 | priceTooltip = "This " + (long ? "long" : "short") + " trade " + (str.startswith(profitText, "+") ? "made" : "lost") + " " + profitText |
169 | if open |
170 | profitText := "OPEN TRADE. " + str.replace(profitText, " $", "$") |
171 | array.push(t.labels, label.new((bar1+bar2)/2, yText, profitText, textcolor=color.new(color_solid, 25), color=color.black, style=label.style_text_outline, size=size.huge, tooltip=priceTooltip)) |
172 | |
173 | // TODO: Show current equity even if there is no open trade |
174 | if not na(totalProfit) |
175 | //totalProfitText = "Total: " + formatProfit(totalProfit) |
176 | //totalProfitText = plusMinusFix(str.tostring(math.round(totalProfit))) |
177 | amount = "$" + str.tostring(totalProfit, "#,##0") |
178 | //totalProfitText = "⇒ " + amount |
179 | string format = na |
180 | if open |
181 | format := "Current\nequity:\n\n*" |
182 | else if equityShort |
183 | format := "*" |
184 | else if profit <= 0 |
185 | format := "*\nleft" |
186 | else if tradesPainted > 1 |
187 | format := "Now we\nhave\n*" |
188 | else |
189 | format := "Now we\nhave\n*." |
190 | totalProfitText = str.replace(format, "*", amount) |
191 | totalColor = totalProfit > 0 ? color.lime : color.orange |
192 | //yTotal = yBottom+(yBottom-yText)*4 |
193 | float yTotal = y2 |
194 | //xTotal = (bar1+bar2)/2 |
195 | int xTotal = bar2+(open ? equityXOffset : 4) |
196 | days = math.round((time-startingDate)/1000.0/60/60/24+1) |
197 | tooltip = "New total equity (started with $" + str.tostring(strategy.initial_capital, "#,##0") + " - " + str.tostring(days) + " " + (days == 1 ? "day" : "days") + " ago)" |
198 | string style = label.style_label_left |
199 | string size = size.large |
200 | if open |
201 | style := label.style_label_center |
202 | xTotal := xTotal+15 |
203 | yTotal := price2*(1-equityOffset/100) |
204 | size := size.huge |
205 | array.push(t.labels, label.new(xTotal, yTotal, totalProfitText, textcolor=color.new(totalColor, 25), style=style, color=color.black, size=size, tooltip=tooltip)) |
206 | |
207 | t |
208 | |
209 | method delete(PaintedTrade this) => |
210 | for line in this.lines |
211 | line.delete(line) |
212 | for lbl in this.labels |
213 | label.delete(lbl) |
214 | |
215 | while tradesPainted < strategy.closedtrades |
216 | bar1 = strategy.closedtrades.entry_bar_index(tradesPainted) |
217 | bar2 = strategy.closedtrades.exit_bar_index(tradesPainted) |
218 | price1 = strategy.closedtrades.entry_price(tradesPainted) |
219 | price2 = strategy.closedtrades.exit_price(tradesPainted) |
220 | //direction = strategy.closedtrades.size(tradesPainted) |
221 | profit = strategy.closedtrades.profit(tradesPainted) |
222 | long = strategy.closedtrades.size(tradesPainted) > 0 |
223 | drawTrade(bar1, bar2, price1, price2, profit, false, long) |
224 | tradesPainted := tradesPainted+1 |
225 | |
226 | var PaintedTrade openTrade = na |
227 | var label macdLabel = na |
228 | |
229 | if not barstate.ishistory |
230 | if not na(openTrade) |
231 | openTrade.delete() |
232 | |
233 | if strategy.opentrades != 0 |
234 | bar1 = strategy.opentrades.entry_bar_index(0) |
235 | bar2 = bar_index |
236 | price1 = strategy.opentrades.entry_price(0) |
237 | price2 = close |
238 | profit = strategy.opentrades.profit(0) |
239 | long = strategy.opentrades.size(0) > 0 |
240 | openTrade := drawTrade(bar1, bar2, price1, price2, profit, true, long) |
241 | |
242 | // Show/update floating MACD value |
243 | |
244 | if not na(macdLabel) |
245 | label.delete(macdLabel) |
246 | macdValue = str.tostring(histToUse, "0.0#") |
247 | lowerBound = enterThresholdShort |
248 | upperBound = enterThresholdLong |
249 | macdPercent = (histToUse-lowerBound)/(upperBound-lowerBound)*100 |
250 | percentText = str.tostring(macdPercent, "0") + "%" |
251 | tooltip = "MACD value: " + macdValue + " (" + percentText + " between close and open)" |
252 | macdColor = macdPercent >= 0 and macdPercent <= 100 ? color.blue : color.yellow |
253 | macdLabel := label.new(bar_index, close, percentText, tooltip=tooltip, yloc=yloc.abovebar, textcolor=macdColor, color=color.black, style=label.style_text_outline, size=size.large) |
254 | |
255 | // Show unrealized P&L in data window |
256 | |
257 | unrealizedPnL = strategy.opentrades == 0 ? na : strategy.opentrades.profit(0) |
258 | plotchar(unrealizedPnL, "Unrealized P&L", "") |
259 | |
260 | // Record max drawdown of winning trades |
261 | |
262 | var float drawdownInTrade = 0 |
263 | var int tradesClosed = 0 |
264 | var float maxDrawdown = 0 |
265 | if not na(unrealizedPnL) |
266 | drawdownInTrade := math.max(0, -unrealizedPnL, drawdownInTrade) |
267 | |
268 | if tradesClosed < strategy.closedtrades |
269 | profit = strategy.closedtrades.profit(tradesClosed) |
270 | if profit > 0 |
271 | maxDrawdown := math.max(maxDrawdown, drawdownInTrade) |
272 | drawdownInTrade := 0 |
273 | tradesClosed := strategy.closedtrades |
274 | |
275 | plotchar(maxDrawdown, "Max drawdown in a winning trade", "") |
Began life as a copy of #1036597
Travelled to 1 computer(s): mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1036603 |
Snippet name: | Mystery Strategy Mark II [Pine Script] |
Eternal ID of this version: | #1036603/1 |
Text MD5: | c933355dec24a3dc9d7085991d22f6b0 |
Author: | stefan |
Category: | pine script |
Type: | Document |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-04-24 19:35:04 |
Source code size: | 12451 bytes / 275 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 184 / 46 |
Referenced in: | [show references] |