// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ // © Bjorgum
// ________________________________________________________________
// |_______________/|
// || ________________ ||
// || ___ __ )_____() ____ ________ ___ ||
// || __ __ | / __ _ / __ / / / /_ __
\ ||
// || _ // / / / // / / _ // // // / / / / / / ||
// || /__/ ___ / _/// _, / _,/ // // // ||
// || // // ||
// ||||
// |/|
//@version=5
strategy( title = "Bjorgum Double Tap", shorttitle = "Bj Double Tap", overlay = true, max_lines_count = 500, max_labels_count = 500, precision = 3, default_qty_type = strategy.cash, commission_value = 0.04, commission_type = strategy.commission.percent, slippage = 1, currency = currency.USD, default_qty_value = 1000, initial_capital = 1000)
// ══════════════════════════════════ // // —————> Immutable Constants <—————— // // ══════════════════════════════════ //
string useStratTip = "Enables or disables the strategy tester allowing a change between either an indicator or a strategy." string dLongTip = "Detect long setups." string dShortTip = "Detect short setups." string FLIPTip = "Allow entry in the opposite bias while already in a position."
string startTip = "Start date & time to begin backtest period. Useful for beginning new bot. eg. Set time to now to make broker emulator in a flat position with the proper starting captial before setting alerts" string endTip = "End date & time to stop searching for setups for back testing."
string tolTip = "The difference in height allowable for the signifcant points of the pattern expressed as a percent of the pattern height. Example: The points can vary in height by 15% of the pattern height from one another." string lenTip = "The length used to calcuate significant points to form pivots for pattern detection. Example: The highest or lowest point in 50 bars." string fibTip = "The fib target extension projected from the neckline of the pattern in the direction of the pattern bias expressed as a percent. Example: 100% is a 1:1 measurment of the height from the pattern neckline." string stopPerTip = "The fib extension of the pattern height measured from the point of invalidation. Example: 0% would be the high point of a double top. 50% would be halfway between the top and the neckline." string offsetTip = "The number of bars lines are extended into the future during an ongoing pattern."
string atrStopTip = "Enables an ATR trailing stop once the target extension is breached. NOTE: This disables a take profit order in the strategy format." string atrLenTip = "The number of bars used in the ATR calculation." string atrMultTip = "The multiplier of the ATR value to subtract from the swing low or swing high point. Example: 1 is 100% of the ATR, 2 is 2x the ATR value." string lookbackTip = "The number of bars to look back to find a swing high or swing low to be used in the trailing stop calculation. Example: 1 ATR subtracted from the lowest point in 5 bars. 1 would use the lowest point within the last bar."
string tableTip = "Show the data table for trade limit levels during an active trade. (table will only show when a pattern is present on the chart, or in bar replay)." string labelTip = "Updates the double top/bottom label with 'Success' or 'Failure' and updates color based on the outcome of the pattern."
string thirdTip = "Where would you like to send your alerts?" string pPhraseTip = "A custom passphrase to authenticate your json message with a touch more security." string aTronKeyTip = "The name of your Alertatron API keys. (Add the ticker in brackets). Example: MyKeys(XBTUSD)." string tickerTip = "The ticker to be traded when using Trading Connector" string LongTip = "3Commas start long deal bot keys." string LongEndTip = "3Commas end long deal bot keys." string ShortTip = "3Commas start short deal bot keys." string ShortEndTip = "3Commas end short deal bot keys."
string dt = "Double Top"
string db = "Double Bottom"
string suc = " - Success"
string fail = " - Failure"
string winStr = "Target reached! 💰"
string loseStr = "Stopped out... 🤷♂"
string tabPerc = "{0, number, #.##}\n({1, number, #.##}%)"
string tcStop = "slmod slprice={0} tradeid={1}"
string dExit = "'{'"content": "Bjorgum {0}\\n\\n\\t'{{ticker}}' '{{interval}}'\\n\\n\\t{1}
"'}'"
string S1 = "tiny" , string P1 = "top" string S2 = "small" , string P2 = "middle" string S3 = "normal" , string P3 = "bottom" string S4 = "large" , string P4 = "left" string S5 = "huge" , string P5 = "center" string S6 = "auto" , string P6 = "right" var string tnB = "" , string A1 = "Custom Json" string altStr = "" , string A2 = "Trading Connector" string tUp = "" , string A3 = "Alertatron" string dCordWin = "" , string A4 = "3Commas" string dCordLose = "" , string A5 = "Discord"
float pos = strategy.position_size int sync = bar_index bool confirm = barstate.isconfirmed var int dir = na var float lmt = na var float stp = na string altExit = na
bool FLAT = pos == 0 bool LONG = pos > 0 bool SHORT = pos < 0 var int tradeId = 0
color col1 = color.new(#b2b5be, 15) color col2 = color.new(#b2b5be, 87) color col3 = color.new(#ffffff, 0) color col4 = color.new(#17ff00, 15) color col5 = color.new(#ff0000, 15) color col6 = color.new(#ff5252, 0)
var matrix logs = matrix.new (5, 3) var line [] zLines = array.new_line (5) var line [] tLines = array.new_line (5) var line [] bLines = array.new_line (5) var label[] bullLb = array.new_label () var label[] bearLb = array.new_label ()
int timeStart = timestamp("01 Jan 2000") int timeEnd = timestamp("01 Jan 2099")
// ══════════════════════════════════ // // —————————> User Input <——————————— // // ══════════════════════════════════ //
string GRP1 = "════ Detection and Trade Parameters ════"
bool useStrat = input.bool (true , "Use Strategy" , group= GRP1, tooltip= useStratTip)
bool dLong = input.bool (true , "Detect Bottoms" , group= GRP1, tooltip= dLongTip )
bool dShort = input.bool (true , "Detect Tops" , group= GRP1, tooltip= dShortTip )
bool FLIP = input.bool (true , "Flip Trades" , group= GRP1, tooltip= FLIPTip )
float tol = input.float (15 , "Pivot Tolerance" , group= GRP1, tooltip= tolTip , minval= 1)
int len = input.int (50 , "Pivot Length" , group= GRP1, tooltip= lenTip , minval= 1)
float fib = input.float (100 , "Target Fib" , group= GRP1, tooltip= fibTip , minval= 0)
int stopPer = input.int (0 , "Stop Loss Fib" , group= GRP1, tooltip= stopPerTip )
int offset = input.int (30 , "Line Offset" , group= GRP1, tooltip= offsetTip , minval= 0)
string GRP2 = "═══════════ Time Filter ═══════════"
int startTime = input.time(timeStart , "Start Filter" , group= GRP2, tooltip= startTip)
int endTime = input.time(timeEnd , "End Filter" , group= GRP2, tooltip= endTip )
string GRP3 = "══════════ Trailing Stop ══════════" bool atrStop = input.bool (false , "Use Trail Stop" , group= GRP3, tooltip= atrStopTip ) int atrLength = input.int (14 , "ATR Length" , group= GRP3, tooltip= atrLenTip , minval= 1) float atrMult = input.float (1 , "ATR Multiplier" , group= GRP3, tooltip= atrMultTip , minval= 0) int lookback = input.int (5 , "Swing Lookback" , group= GRP3, tooltip= lookbackTip, minval= 1)
string GRP5 = "════════════ Colors ════════════" color col = input.color (col1 , "Lines " , group= GRP5, inline= "41") color zCol = input.color (col3 , "Patterns " , group= GRP5, inline= "42") int hWidth = input.int (1 , "" , group= GRP5, inline= "41", minval= 1) int zWidth = input.int (1 , "" , group= GRP5, inline= "42", minval= 1) color colf = input.color (col2 , "Stop Fill" , group= GRP5) color tCol = input.color (col4 , "Target Color" , group= GRP5) color sCol = input.color (col5 , "Stop Color" , group= GRP5) color trailCol = input.color (col6 , "Trail Color" , group= GRP5)
string GRP6 = "═════════ Table and Label ═════════" bool showTable = input.bool (true , "Show Table" , group= GRP6, tooltip= tableTip) bool setLab = input.bool (true , "Update Label" , group= GRP6, tooltip= labelTip) string labSize = input.string("small" , "Label Text Size" , group= GRP6, options= [S1, S2, S3, S4, S5, S6]) string textSize = input.string("normal" , "Table Text Size" , group= GRP6, options= [S1, S2, S3, S4, S5, S6]) string tableYpos = input.string("bottom" , "Table Position" , group= GRP6, options= [P1, P2, P3]) string tableXpos = input.string("right" , "" , group= GRP6, options= [P4, P5, P6])
string GRP7 = "══════════ Alert Strings ══════════" string thirdParty = input.string(A1 , "3rd Party" , group= GRP7, tooltip= thirdTip, options= [A1, A2, A3, A4, A5]) string pPhrase = input.string("1234" , "Json Passphrase" , group= GRP7, tooltip= pPhraseTip ) string aTronKey = input.string("myKeys" , "Alertatron Key" , group= GRP7, tooltip= aTronKeyTip) string tcTicker = input.string("" , "TC Ticker" , group= GRP7, tooltip= tickerTip ) string c3Long = input.string("" , "3Comma Long" , group= GRP7, tooltip= LongTip ) string c3LongEnd = input.string("" , "3Comma Long End" , group= GRP7, tooltip= LongEndTip ) string c3Short = input.string("" , "3Comma Short" , group= GRP7, tooltip= ShortTip ) string c3ShortEnd = input.string("" , "3Comma Short End", group= GRP7, tooltip= ShortEndTip)
// ══════════════════════════════════ // // ————> Variable Calculations <————— // // ══════════════════════════════════ //
bool dif = stopPer != 0 int set = sync + offset
float atr = ta.atr (14) float sLow = ta.lowest (lookback) - (atr * atrMult) float sHigh = ta.highest (lookback) + (atr * atrMult)
float pivHigh = ta.highest (len) float pivLows = ta.lowest (len)
float hbar = ta.highestbars (len) float lbar = ta.lowestbars (len)
// ══════════════════════════════════ // // ———> Functional Declarations <———— // // ══════════════════════════════════ //
High(m) => float result = (m == 1 ? high : low)
Low(m) => float result = (m == 1 ? low : high)
perD(_p) => float result = (_p - close) / close * 100
_coords(_x, _i) => x = matrix.get (_x, _i, 0) y = matrix.get (_x, _i, 1) [int(x), y]
_arrayLoad(_x, _max, _val) =>
array.unshift (_x, _val)
if array.size (_x) > _max
array.pop (_x)
_matrixPush(_mx, _max, _row) => matrix.add_row(_mx, matrix.rows (_mx), _row) if matrix.rows (_mx) > _max matrix.remove_row (_mx, 0)
_mxLog(_cond, _x, _y) => float[] _row = array.from (sync, _y, 0) if _cond _matrixPush (_x, 5, _row)
_mxUpdate(_cond, _dir, _x, y) => int m = _dir ? 1 : -1 int _end = matrix.rows (_x) -1 if _cond and y * m > matrix.get (_x, _end, 1) * m matrix.set (_x, _end, 0, sync) matrix.set (_x, _end, 1, y)
_extend(_x, _len) => for l in _x line.set_x2(l, _len)
_hLine(_l, x2, y2, y3, y4, y5, _t) => line l1 = line.new (x2 , y2, set, y2, color= col, width= hWidth) line l2 = line.new (x2 , y4, set, y4, color= col, width= hWidth) array.set (_l , 3, l1) array.set (_l , 2, l2) array.set (_l , 1, line.new (x2 , y3, set, y3, color= col, width= hWidth)) array.set (_l , 0, line.new (sync-1, _t, set, _t, color= col, width= hWidth)) linefill.new (l1 , l2, colf) if stopPer != 0 array.set (_l, 4, line.new (sync-1, y5, set, y5, color= col, width= hWidth))
_zLine(x1, y1, x2, y2, x3, y3, x4, y4) => array.set (zLines, 3, line.new (x1, y1, x2 , y2 , color= zCol, width= zWidth)) array.set (zLines, 2, line.new (x2, y2, x3 , y3 , color= zCol, width= zWidth)) array.set (zLines, 1, line.new (x3, y3, x4 , y4 , color= zCol, width= zWidth)) array.set (zLines, 0, line.new (x4, y4, sync, close, color= zCol, width= zWidth))
_label(x, y, m) =>
m > 0 ?
_arrayLoad (bearLb, 1, label.new (x, y, dt, color= color(na), style= label.style_label_down, textcolor= col, size= labSize)) :
_arrayLoad (bullLb, 1, label.new (x, y, db, color= color(na), style= label.style_label_up, textcolor= col, size= labSize))
_labelUpdate(_x, _y, m) => if (_x or _y) and setLab label lab = array.get (m > 0 ? bearLb : bullLb, 0) string oStr = (m > 0 ? dt : db) string nStr = oStr + (_x ? suc : fail) label.set_text (lab, nStr) label.set_textcolor (lab, _x ? tCol : sCol)
_atrTrail(_cond, _lt, _s, m) =>
var float _stop = na
var bool _flag = na
var bool _trail = na
_flag := _cond
_stop := _s
_trail := _flag ? false : _trail
if atrStop and useStrat
if _lt
_flag := false
_trail := true
_stop := m == -1 ? _lt ? sLow : math.max(_stop, sLow) : _stop
_stop := m == 1 ? _lt ? sHigh : math.min(_stop, sHigh) : _stop
if High(m) * m > _stop * m
_flag := true
_trail := false
[_flag, _stop, _trail]
_inTrade(_cond, _x, _e, m) =>
var bool _flag = na
var float _stop = na
var float _limit = na
line l1 = array.get (_x, 0)
float lp = line.get_price(l1, sync)
line l2 = array.get (_x, dif ? 4 : 2)
float ls = line.get_price(l2, sync)
bool win = Low (m) * m <= lp * m and not _e
bool lose = High(m) * m >= ls * m and not _e
_flag := _cond
_stop := _e ? ls : _stop
_limit := _e ? lp : _limit
if win or lose
_flag := true
_extend (_x , sync)
_labelUpdate (win , lose, m)
line.set_color (win ? l1 : l2, win ? tCol : sCol)
array.fill (_x , na)
array.fill (zLines, na)
[_f, _s, _t] = _atrTrail (_flag , win , _stop, m)
_flag := atrStop ? _f : _flag
_stop := _t and _s * m < _stop * m and confirm ? _s : _stop
[_flag, _stop, _t, _limit]
_double(_cond, _l, _x, m) =>
var bool _flag = na
int _rows = matrix.rows (_x)
_flag := _cond
if _flag
[x1, y1] = _coords (_x , _rows - 5)
[x2, y2] = _coords (_x , _rows - 4)
[x3, y3] = _coords (_x , _rows - 3)
[x4, y4] = _coords (_x , _rows - 2)
bool traded = matrix.get (_x , _rows - 2, 2)
float height = math.avg (y2 , y4) - y3
float _high = y2 + height * (tol/ 100)
float _low = y2 - height * (tol/ 100)
float _t = y3 - height * (fib/ 100)
float y5 = y2 * m < y4 * m ? y2 : y4
float y6 = y2 * m > y4 * m ? y2 : y4
float y7 = y6 - height (stopPer/100)
bool result = y1m < y3m and y4m <= _highm and y4m >= _lowm and closem < y3*m and not (close[1]m < y3m) and not traded
if result and _flag and (m > 0 ? dShort : dLong)
_hLine (_l, x2, y5, y3, y6, y7, _t)
_zLine (x1, y1, x2, y2, x3, y3, x4, y4)
_label (x4, y6, m)
matrix.set (_x, _rows - 2, 2, 1)
_flag := false
_flag
_scan(_l, _x, m) => var bool _cond = true _cond := _double (_cond, _l, _x, m) bool enter = _cond[1] and not _cond [f,s,t,l] = _inTrade (_cond, _l, enter, m) _cond := f _extend(_l, set) [f,s,t,l]
_populate(_n, _x, _i, _col) => for [i, _a] in _x if not na(_a) table.cell(table_id = _n, column = _i , row = i, bgcolor = na , text = _a, text_color = _col, text_size = textSize)
// ══════════════════════════════════ // // ————————> Logical Order <————————— // // ══════════════════════════════════ //
dir := not hbar ? 1 : not lbar ? 0 : dir
bool dirUp = dir != dir[1] and dir bool dirDn = dir != dir[1] and not dir
bool setUp = not hbar and dir bool setDn = not lbar and not dir
_mxLog (dirUp or dirDn, logs, dirUp ? pivHigh : pivLows) _mxUpdate (setUp or setDn, dir, logs, setUp ? pivHigh : pivLows)
[bear,ss,ts,sl] = _scan(tLines, logs, 1) [bull,ls,tl,ll] = _scan(bLines, logs, -1)
bool st = SHORT ? ts : false bool lt = LONG ? tl : false
bool sell = bear[1] and not bear bool buy = bull[1] and not bull
color ssCol = st or st[1] ? trailCol : na color lsCol = lt or lt[1] ? trailCol : na
bool longEntry = buy and (FLAT or (SHORT and FLIP)) bool shortEntry = sell and (FLAT or (LONG and FLIP))
bool dateFilter = time >= startTime and time <= endTime
tradeId += longEntry or shortEntry ? 1 : 0
lmt := atrStop ? na : shortEntry ? sl : longEntry ? ll : lmt stp := shortEntry ? ss : longEntry ? ls : stp
plot (atrStop ? ss : na, "stop", ssCol, style= plot.style_linebr) plot (atrStop ? ls : na, "limit", lsCol, style= plot.style_linebr)
bgcolor (not dateFilter ? color.new(color.red,80) : na, title= "Filter Color")
// ══════════════════════════════════ // // —————————> Data Display <————————— // // ══════════════════════════════════ //
if showTable
string tls = bull ? na : str.format(tabPerc, ls, perD(ls))
string tss = bear ? na : str.format(tabPerc, ss, perD(ss))
string tll = bull ? na : str.format(tabPerc, ll, perD(ll))
string tsl = bear ? na : str.format(tabPerc, sl, perD(sl))
string[] titles = array.from(na, bull ? na : "Bullish", bear ? na : "Bearish")
string[] stops = array.from("Stop" , tls, tss)
string[] limtis = array.from("Target", tll, tsl)
table bjTab = table.new(tableYpos + "_" + tableXpos, 3, 3, border_color= color.new(color.gray, 60), border_width= 1)
if not bear or not bull
_populate(bjTab, titles, 0, color.white)
_populate(bjTab, stops, 1, color.red)
if not (na(ll) or lt) or not (na(sl) or st)
_populate(bjTab, limtis, 2, color.green)
// ══════════════════════════════════ // // ——————> String Variables <———————— // // ══════════════════════════════════ //
bool cSon = thirdParty == A1 bool tCon = thirdParty == A2 bool aTron = thirdParty == A3 bool c3 = thirdParty == A4 bool dCord = thirdParty == A5
if cSon and useStrat
string json =
"'{'
\n \"passphrase\": \"{0}\",
\n \"time\": '\"{{timenow}}\"',
\n \"ticker\": '\"{{ticker}}\"',
\n \"plot\": '{'
\n \"stop_price\": {1, number, #.########},
\n \"limit_price\": {2, number, #.########}
\n '}',
\n \"strategy\": '{'
\n \"position_size\": '{{strategy.position_size}}',
\n \"order_action\": '\"{{strategy.order.action}}\"',
\n \"market_position\": '\"{{strategy.market_position}}\"',
\n \"market_position_size\": '{{strategy.market_position_size}}',
\n \"prev_market_position\": '\"{{strategy.prev_market_position}}\"',
\n \"prev_market_position_size\": '{{strategy.prev_market_position_size}}'
\n '}'
\n'}'"
altStr := str.format(json, pPhrase, stp, lmt)
if tCon and useStrat
string tcTrade =
"'{{strategy.order.action}}' tradesymbol={0} tradeid={1}"
+ (atrStop ? "" : ' tpprice={2, number, #.########}')
+ ' slprice={3, number, #.########}'
altStr := str.format(tcTrade, tcTicker, tradeId, lmt, stp)
tUp := str.format(tcStop, stp, tradeId)
if aTron and useStrat
string altEnt = aTronKey + " '{'\ncancel(which=all);\nmarket(position='{{strategy.position_size}}');\n"
string altEnd = "'}'\n#bot"
string altBrkt =
(atrStop ? "stopOrder(" : "stopOrTakeProfit(")
+ (atrStop ? "" : "tp=@{0, number, #.########}, ")
+ (atrStop ? "offset=@" : "sl=@") + "{1, number, #.########}, position=0, reduceOnly=true"
+ (atrStop ? ", tag=trail" : "")
+ ");\n"
string enter = altEnt + altBrkt + altEnd
string exit = altEnt + altEnd
altStr := str.format(enter, lmt, stp)
altExit := na(altExit) ? exit : altExit
string stopUpdate = aTronKey + " '{'\ncancel(which=tagged, tag=trail);\n" + altBrkt + altEnd
tUp := atrStop ? str.format(stopUpdate, lmt, stp) : tUp
if dCord and useStrat
tnB := longEntry ? db : shortEntry ? dt : tnB
string postTrade =
"'{'\"content\": \"```🚨 Bjorgum {0} detected 🚨\\n\\n\\t'{{ticker}}' '{{interval}}'\\n\\n\\t"
+ (atrStop ? "" : "🎯 Target: {1, number, #.########}\\n\\t")
+ "🛑 Stop: {2, number, #.########}```\"'}'"
altStr := str.format(postTrade, tnB, lmt, stp)
dCordWin := str.format(dExit, tnB, winStr)
dCordLose := str.format(dExit, tnB, loseStr)
if c3 and useStrat
c3Long := SHORT and buy ? str.format("[{0}, {1}]", c3ShortEnd, c3Long) : c3Long
c3Short := LONG and sell ? str.format("[{0}, {1}]", c3LongEnd, c3Short) : c3Short
// ══════════════════════════════════ // // ——————> Strategy Execution <—————— // // ══════════════════════════════════ //
strategy.entry("Long" , strategy.long , comment= "Long", when= useStrat and longEntry and dateFilter, alert_message= c3 ? c3Long : altStr) strategy.entry("Short", strategy.short, comment= "Short", when= useStrat and shortEntry and dateFilter, alert_message= c3 ? c3Short : altStr)
strategy.exit("Long Exit",
"Long",
stop = stp,
limit = lmt,
comment = "L Exit",
alert_message = c3 ? c3LongEnd : aTron ? altExit : tCon ? "closelong" : dCord ? na : altStr,
alert_profit = dCord ? dCordWin : na,
alert_loss = dCord ? dCordLose : na)
strategy.exit("Short Exit", "Short", stop = stp, limit = lmt, comment = "S Exit", alert_message = c3 ? c3ShortEnd : aTron ? altExit : tCon ? "closeshort" : dCord ? na : altStr, alert_profit = dCord ? dCordWin : na, alert_loss = dCord ? dCordLose : na)
// ══════════════════════════════════ // // —————> Alert Functionality <—————— // // ══════════════════════════════════ //
if (lt and ls != ls[1] or st and ss != ss[1]) and (tCon or aTron) alert(tUp, alert.freq_once_per_bar_close)
if not useStrat and (buy or sell) alert((buy ? db : dt) + ' Detected', alert.freq_once_per_bar)
// ____ __ _ ____
// ( )( ( (
// ) ) / / ) D (
// (___)_))(____/