// "Mystery Strategy" / "Holy Grail for ETH"
//
// Preconfigured for ETH 30m on Bitget
// Works on: ETH since 2022-12, AVAX since 2023-3
//
// (c) Stefan Reich
//@version=5
strategy(shorttitle="Stefan Reich's Mystery Strategy v230", title="Stefan Reich's Mystery Strategy",
overlay=true,
commission_type=strategy.commission.percent, commission_value=0.07,
initial_capital = 100, default_qty_type=strategy.cash, default_qty_value=1000,
calc_on_every_tick = true,
max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500)
// commission_value is an approximately correct estimate for fees+spread on ETH on typical exchanges (Bitget, Bybit)
// This strategy opens few positions though, so fees+spread are not a big deal anyway.
shrouded = input.bool(false)
startingDate = input.time(timestamp("8 Dec 2022"))
doLongs=input.bool(true, "Do longs")
doShorts=input.bool(false, "Do shorts")
minMoveLong = input.float(1.6, step=0.1)
minMoveShort = input.float(1.6, step=0.1)
arrowOffset = input.float(10)
initialOffset = input.float(10)
equityOffset = input.float(10)
topOffset = input.float(1, step=0.25)
filesSlanted = input.bool(true)
ieBoxColor = input.color(color.black)
ieTextColor = input.color(color.blue)
positionSize = input.float(1000, "Position size in $", tooltip="After leverage")
// UTILITY FUNCTIONS
drift() =>
sumDrift = 0.0
for tradeNo = 0 to strategy.opentrades - 1
sumDrift += strategy.opentrades.size(tradeNo)
result = sumDrift
// macro logFloat(value, title) { plotchar(value, title, "", location.top) }
// MACD inputs
fast_length = input(title="Fast Length", defval=25)
slow_length = input(title="Slow Length", defval=100)
macd_src = input(title="Source", defval=close)
signal_length = input.int(title="Signal Smoothing", minval = 1, defval = 9)
sma_source = input.string(title="Oscillator MA Type", defval="EMA", options=["SMA", "EMA"])
sma_signal = input.string(title="Signal Line MA Type", defval="EMA", options=["SMA", "EMA"])
intraCandleOperation = input.bool(false, tooltip="Open or close trades within a candle")
switchOnLongsAbove = input.float(-100, step=0.1, tooltip="Allow longs only if MACD is above this level")
switchOnShortsBelow = input.float(100, step=0.1, tooltip="Allow shorts only if MACD is below this level")
equityXOffset = input.int(10, tooltip="Move equity box to the right using this value")
exitThresholdLong = input.float(999, step=0.1, tooltip="MACD histogram value to exit a long at. To use -MinMoveShort, enter 999")
exitThresholdShort = input.float(999, step=0.1, tooltip="MACD histogram value to exit a short at. To use MinMoveLong, enter 999")
hotSwitchOkay = input.bool(true, tooltip="Can we kill a long by opening a short and vice versa?")
equityShort = input.bool(true, tooltip="Show only the new equity, no text")
useEndDate = input.bool(false, tooltip="Activate the end date below")
endDate = input.time(timestamp("31 Dec 2023"), tooltip="When to end the backtest or execution (if checkbox above is set)")
useRelativeMACD = input.bool(false, tooltip="Use relative MACD (normalized to asset price)")
// Add more inputs down here to not mess up configurations of older versions
// MACD calculation
fast_ma = sma_source == "SMA" ? ta.sma(macd_src, fast_length) : ta.ema(macd_src, fast_length)
slow_ma = sma_source == "SMA" ? ta.sma(macd_src, slow_length) : ta.ema(macd_src, slow_length)
macd = fast_ma - slow_ma
signal = sma_signal == "SMA" ? ta.sma(macd, signal_length) : ta.ema(macd, signal_length)
hist = macd - signal
// Plot MACD
plot(fast_ma, title="MACD Fast MA", color=color.yellow)
plot(slow_ma, title="MACD Slow MA", color=color.blue)
plotchar(macd, "MACD", "", location.top)
// Relative MACD values (in preparation for Mark II strategy)
factor = 1000 / slow_ma
relMACD = macd * factor
relSignal = signal * factor
relHist = (macd - signal) * factor
plotchar(relMACD, "Relative MACD", "", location.top) // logFloat
plotchar(hist, "MACD Histogram", "", location.top) // logFloat
plotchar(relHist, "Relative MACD Histogram", "", location.top) // logFloat
actualExitLong = exitThresholdLong == 999 ? -minMoveShort : exitThresholdLong
actualExitShort = exitThresholdShort == 999 ? minMoveLong : exitThresholdShort
macdToUse = useRelativeMACD ? relMACD : macd
histToUse = useRelativeMACD ? relHist : hist
if time >= startingDate and (not useEndDate or time <= endDate) and (intraCandleOperation or barstate.isconfirmed)
qty = positionSize/close
if drift() < 0.0 and histToUse >= actualExitShort
strategy.close_all(shrouded ? "Exit" : "MACD Green")
else if drift() > 0.0 and histToUse <= actualExitLong
strategy.close_all(shrouded ? "Exit" : "MACD Red")
if hotSwitchOkay or drift() == 0.0
if doLongs and histToUse >= minMoveLong and macdToUse >= switchOnLongsAbove
strategy.entry("Long", strategy.long, qty=qty)
else if doShorts and histToUse <= -minMoveShort and macdToUse <= switchOnShortsBelow
strategy.entry("Short", strategy.short, qty=qty)
// PAINT TRADES & INITIAL EQUITY
plusMinusFix(s) =>
currency = " $"
str.startswith(s, "-") ? "-" + currency + str.substring(s, 1) : "+" + currency + s
formatProfit(profit) =>
plusMinusFix(str.tostring(profit, "#,##0.00"))
var int tradesPainted = 0
var label initialEquityLabel = na
if na(initialEquityLabel) and time >= startingDate
ieText = "⬆\nWe invest\n$" + str.tostring(strategy.initial_capital, "#,##0") + "\nand start\nthe strategy!"
ieTime = startingDate-2*24*60*60*1000 // 2 days back
//ieY = close*(1-arrowOffset/100) // for style_label_center
//style=label.style_label_center
ieY = close*(1-initialOffset/100) // for style_label_up
//boxColor = color.black
//textColor = color.white
style = label.style_label_up
//style = label.style_triangleup
initialEquityLabel := label.new(ieTime, ieY, ieText, xloc=xloc.bar_time, color=ieBoxColor, textcolor=ieTextColor, tooltip="Initial equity", size=size.huge, style=style)
type PaintedTrade
array<line> lines
array<label> labels
drawTrade(bar1, bar2, price1, price2, profit, open) =>
t = PaintedTrade.new()
t.lines := array.new<line>()
t.labels := array.new<label>()
totalProfit = strategy.equity // -strategy.initial_capital
//line.new(bar1, price1, bar2, price2, xloc=xloc.bar_index, width = 10, color=color.black, style=line.style_arrow_right)
float y1 = price1*(1-arrowOffset/100)
float y2 = price2*(1-arrowOffset/100)
float top1 = price1*(1-topOffset/100)
float top2 = (open ? math.min(price2, low) : price2)*(1-topOffset/100)
color_solid = profit > 0 ? color.lime : color.red
color = color.new(color_solid, 50)
if not filesSlanted
y1 := math.min(y1, y2)
y2 := y1
array.push(t.lines, line.new(bar1, top1, bar1, y1, color=color, width=3))
array.push(t.lines, line.new(bar2, top2, bar2, y2, color=color, width=3))
array.push(t.lines, line.new(bar1, y1, bar2, y2, xloc=xloc.bar_index, width = 10, color=color)) //, style=line.style_arrow_right)
ypos = 0.6
string profitText = formatProfit(profit)
yPrice = (price1+price2)/2
yBottom = (y1+y2)/2
yText = yPrice*(1-ypos)+yBottom*ypos
priceTooltip = "This trade " + (str.startswith(profitText, "+") ? "made" : "lost") + " " + profitText
if open
profitText := "OPEN TRADE. " + str.replace(profitText, " $", "$")
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))
// TODO: Show current equity even if there is no open trade
if not na(totalProfit)
//totalProfitText = "Total: " + formatProfit(totalProfit)
//totalProfitText = plusMinusFix(str.tostring(math.round(totalProfit)))
amount = "$" + str.tostring(totalProfit, "#,##0")
//totalProfitText = "⇒ " + amount
string format = na
if open
format := "Current\nequity:\n\n*"
else if equityShort
format := "*"
else if profit <= 0
format := "*\nleft"
else if tradesPainted > 1
format := "Now we\nhave\n*"
else
format := "Now we\nhave\n*."
totalProfitText = str.replace(format, "*", amount)
totalColor = totalProfit > 0 ? color.lime : color.orange
//yTotal = yBottom+(yBottom-yText)*4
float yTotal = y2
//xTotal = (bar1+bar2)/2
int xTotal = bar2+(open ? equityXOffset : 4)
days = math.round((time-startingDate)/1000.0/60/60/24+1)
tooltip = "New total equity (started with $" + str.tostring(strategy.initial_capital, "#,##0") + " - " + str.tostring(days) + " " + (days == 1 ? "day" : "days") + " ago)"
string style = label.style_label_left
string size = size.large
if open
style := label.style_label_center
xTotal := xTotal+15
yTotal := price2*(1-equityOffset/100)
size := size.huge
array.push(t.labels, label.new(xTotal, yTotal, totalProfitText, textcolor=color.new(totalColor, 25), style=style, color=color.black, size=size, tooltip=tooltip))
t
method delete(PaintedTrade this) =>
for line in this.lines
line.delete(line)
for lbl in this.labels
label.delete(lbl)
while tradesPainted < strategy.closedtrades
bar1 = strategy.closedtrades.entry_bar_index(tradesPainted)
bar2 = strategy.closedtrades.exit_bar_index(tradesPainted)
price1 = strategy.closedtrades.entry_price(tradesPainted)
price2 = strategy.closedtrades.exit_price(tradesPainted)
//direction = strategy.closedtrades.size(tradesPainted)
profit = strategy.closedtrades.profit(tradesPainted)
drawTrade(bar1, bar2, price1, price2, profit, false)
tradesPainted := tradesPainted+1
var PaintedTrade openTrade = na
var label macdLabel = na
if not barstate.ishistory
if not na(openTrade)
openTrade.delete()
if strategy.opentrades != 0
bar1 = strategy.opentrades.entry_bar_index(0)
bar2 = bar_index
price1 = strategy.opentrades.entry_price(0)
price2 = close
profit = strategy.opentrades.profit(0)
openTrade := drawTrade(bar1, bar2, price1, price2, profit, true)
// Show/update floating MACD value
if not na(macdLabel)
label.delete(macdLabel)
macdValue = str.tostring(histToUse, "0.0#")
lowerBound = -minMoveShort
upperBound = minMoveLong
macdPercent = (histToUse-lowerBound)/(upperBound-lowerBound)*100
percentText = str.tostring(macdPercent, "0") + "%"
tooltip = "MACD value: " + macdValue + " (" + percentText + " between close and open)"
macdColor = macdPercent >= 0 and macdPercent <= 100 ? color.blue : color.yellow
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)
// Show unrealized P&L in data window
unrealizedPnL = strategy.opentrades == 0 ? na : strategy.opentrades.profit(0)
plotchar(unrealizedPnL, "Unrealized P&L", "", location.top) // logStringTravelled to 1 computer(s): mqqgnosmbjvj
No comments. add comment
| Snippet ID: | #1036597 |
| Snippet name: | Mystery Strategy [Pine Script] |
| Eternal ID of this version: | #1036597/2 |
| Text MD5: | 987925489f1be74cd7d5a725bef3a4e5 |
| Author: | stefan |
| Category: | pine script |
| Type: | Document |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2023-04-23 16:22:16 |
| Source code size: | 11500 bytes / 262 lines |
| Pitched / IR pitched: | No / No |
| Views / Downloads: | 490 / 112 |
| Version history: | 1 change(s) |
| Referenced in: | #1036598 - MACD + Levels [Pine Script] #1036603 - Mystery Strategy Mark II [Pine Script] |