Untitled
//@version=5 strategy("CBA Backtester", overlay=true, margin_long=100, margin_short=100, calc_on_every_tick=true, max_labels_count=250) version = 1.0 // Bitsgap Inputs UPPER_PRICE = input.price(75000.0, 'High Price') LOWER_PRICE = input.price(60000.0, 'Low Price') NUM_GRIDS = input.int(defval = 10, title = "Number of Grid Lines", minval = 1, maxval = 180) + 1 NO_TRADING_SPAN = input.float(defval = 5.0, title = "No Trading Span (%)") / 2.0 ENABLE_LONG_STOP_LOSS = input.bool(false, "", inline = "long_sl", tooltip = "Configure Stop Loss options to close the bot when the price moves against the chosen direction. If the price reaches a specified Stop Loss level, the bot sells all the Base currency used and stops further operations.") LONG_STOP_LOSS_PRICE = input.price(500, "Long Stop Loss Price", inline = "long_sl") ENABLE_SHORT_STOP_LOSS = input.bool(false, "", inline = "short_sl", tooltip = "Configure Stop Loss options to close the bot when the price moves against the chosen direction. If the price reaches a specified Stop Loss level, the bot sells all the Base currency used and stops further operations.") SHORT_STOP_LOSS_PRICE = input.price(500, "Short Stop Loss Price", inline = "short_sl") TRAILING_UP = input.bool(false, "Trailing Up", tooltip = "The Trailing function automatically moves the grid up when the market price increases.") TRAILING_DOWN = input.bool(false, "Trailing Down", tooltip = "The Trailing function automatically moves the grid down when the market price decreases.") ENABLE_LONG_TRAILING_STOP = input.bool(false, "Long Trailing Stop", tooltip = "The Trailing function automatically moves the grid up when the market price increases. If SL Trailing is enabled, Stop Loss will also dynamically follow the grid.") LONG_TRAILING_STOP_PERC = input.float(1.0, " Trailing Distance %") ENABLE_SHORT_TRAILING_STOP = input.bool(false, "Short Trailing Stop", tooltip = "The Trailing function automatically moves the grid up when the market price increases. If SL Trailing is enabled, Stop Loss will also dynamically follow the grid.") SHORT_TRAILING_STOP_PERC = input.float(1.0, " Trailing Distance %") //Date START_DATE = input.time(timestamp("2019-01-01"), title="Start Date", group="Date Range", tooltip="Configure this date to the present day when enabling automation to receive the most recent alerts.") END_DATE = input.time(timestamp("2030-01-01"), title="End Date", group="Date Range", tooltip="Configure this to a previous date to conduct a backtest within a specified range.") NUM_GRIDS_DIR = NUM_GRIDS/2 /////////// // GRIDS /////////// type tradeEntry string id int grid_num float ep varip float tp varip float sl varip bool is_active type gridEntry float price line ln array<tradeEntry> trades var array<gridEntry> up_grids = array.new<gridEntry>() var array<gridEntry> dn_grids = array.new<gridEntry>() init_grid(array<gridEntry> up_grids, array<gridEntry> dn_grids, int num_grids_up, int num_grids_dn) => int i = 0 while i < num_grids_up e = gridEntry.new() e.trades := array.new<tradeEntry>() up_grids.push(e) i += 1 i := 0 while i < num_grids_dn e = gridEntry.new() e.trades := array.new<tradeEntry>() dn_grids.push(e) i += 1 var array<tradeEntry> long_trades = array.new<tradeEntry>() // var array<tradeEntry> active_long_trades = array.new<tradeEntry>() // var map<int, array<tradeEntry>> active_long_trades_map = map.new<tradeEntry>() var array<tradeEntry> short_trades = array.new<tradeEntry>() // var array<tradeEntry> active_short_trades = array.new<tradeEntry>() // var map<int, array<tradeEntry>> active_short_trades_map = map.new<tradeEntry>() var int start_bar_idx = 0 var int long_id_num = 0 var int short_id_num = 0 var float INIT_QTY = na var float QTY = na var float grid_up_start_price = na var float grid_dn_start_price = na var float UP_STEP_PRICE = na var float DN_STEP_PRICE = na var int NUM_GRIDS_UP = NUM_GRIDS_DIR var int NUM_GRIDS_DN = NUM_GRIDS_DIR calc_short_steps(array<gridEntry> up_grids, bool use_short_sl, float short_sl, bool use_short_trailing_stop, float grid_up_start_price, float UP_STEP_PRICE, int num_grids_up) => int my_short_id_num = 0 float grid_up_price = grid_up_start_price int i = 0 while i < num_grids_up up_entry = up_grids.get(i) if na(up_entry.ln) up_entry.ln := line.new(x1 = start_bar_idx, y1 = grid_up_price, x2 = bar_index+1, y2 = grid_up_price, color=color.red, extend=extend.right) else up_entry.ln.set_y1(grid_up_price) up_entry.ln.set_y2(grid_up_price) up_entry.price := grid_up_price // entries short_tp = grid_up_price - UP_STEP_PRICE short_id = "short_" + str.tostring(my_short_id_num) strategy.order(short_id, strategy.short, qty = QTY, limit = grid_up_price) if not use_short_trailing_stop strategy.exit(id = 'tp_' + short_id, from_entry = short_id, limit = short_tp) if use_short_sl strategy.exit('sl_' + short_id, short_id, stop = short_sl) // set up the trade entry tradeEntry trade_short = tradeEntry.new(short_id, i, grid_up_price, short_tp) short_trades.push(trade_short) up_entry.trades.push(trade_short) grid_up_price += UP_STEP_PRICE i += 1 my_short_id_num += 1 my_short_id_num calc_long_steps(array<gridEntry> dn_grids, bool use_long_sl, float long_sl, bool use_long_trailing_stop, float grid_dn_start_price, float DN_STEP_PRICE, int num_grids_dn) => int my_long_id_num = 0 float grid_dn_price = grid_dn_start_price int i = 0 while i < num_grids_dn dn_entry = dn_grids.get(i) if na(dn_entry.ln) dn_entry.ln := line.new(x1 = start_bar_idx, y1 = grid_dn_price, x2 = bar_index+1, y2 = grid_dn_price, color=color.green, extend=extend.right) else dn_entry.ln.set_y1(grid_dn_price) dn_entry.ln.set_y2(grid_dn_price) dn_entry.price := grid_dn_price // entries long_tp = grid_dn_price + DN_STEP_PRICE long_id = "long_" + str.tostring(my_long_id_num) strategy.order(long_id, strategy.long, qty = QTY, limit = grid_dn_price) if not use_long_trailing_stop strategy.exit('tp_' + long_id, long_id, limit = long_tp) if use_long_sl strategy.exit('sl_' + long_id, long_id, stop = long_sl) // set up the trade entry tradeEntry trade_long = tradeEntry.new(long_id, i, grid_dn_price, long_tp) long_trades.push(trade_long) dn_entry.trades.push(trade_long) grid_dn_price -= DN_STEP_PRICE i += 1 my_long_id_num += 1 my_long_id_num debug_grid(array<gridEntry> grid) => debug_txt = "" int i = 0 while i < grid.size() entry = grid.get(i) debug_txt += "Entry: " + str.tostring(entry.price, "#.####") + "\n" i += 1 debug_txt debug_trade(array<tradeEntry> trades) => debug_txt = "" int i = 0 while i < trades.size() entry = trades.get(i) debug_txt += "id: " + entry.id + " tp: " + str.tostring(entry.tp, "#.####") + "\n" i += 1 debug_txt var is_init = false var line entry_line = na if time > START_DATE and not is_init // init the vars entry_price = low + (high-low)/2 grid_up_start_price := high + entry_price * NO_TRADING_SPAN / 100.0 grid_dn_start_price := low - entry_price * NO_TRADING_SPAN / 100.0 UP_STEP_PRICE := math.abs(UPPER_PRICE - grid_up_start_price) / NUM_GRIDS_UP DN_STEP_PRICE := math.abs(grid_dn_start_price - LOWER_PRICE) / NUM_GRIDS_DN INIT_QTY := (strategy.initial_capital / 2) / close QTY := ((strategy.initial_capital / 2) / NUM_GRIDS) / close start_bar_idx := bar_index // then we need to buy (because this is a spot bot) strategy.order("init", strategy.long, qty = INIT_QTY) init_grid(up_grids, dn_grids, NUM_GRIDS_UP, NUM_GRIDS_DN) entry_line := line.new(x1 = bar_index-1, y1 = entry_price, x2 = bar_index, y2 = entry_price, color=color.yellow, extend=extend.right) short_id_num := calc_short_steps(up_grids, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE, ENABLE_SHORT_TRAILING_STOP, grid_up_start_price, UP_STEP_PRICE, NUM_GRIDS_UP) long_id_num := calc_long_steps(dn_grids, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE, ENABLE_LONG_TRAILING_STOP, grid_dn_start_price, DN_STEP_PRICE, NUM_GRIDS_DN) //label.new(bar_index, high, debug_grid(up_grids)) is_init := true check_highs_trailing_up(array<gridEntry> up_grid, array<gridEntry> dn_grid, array<tradeEntry> my_short_trades) => int my_short_id_num = short_id_num int i = 0 float ret = na while i < up_grid.size() gridEntry e = up_grid.get(i) if high >= e.price // mark all trades as active for trade in e.trades trade.is_active := true // make this line the new entry line entry_line.set_y1(e.price) entry_line.set_y2(e.price) ret := e.price i := i + 1 else break if not na(ret) // delete all orders above // go through the grids above and remove the trades with the ids while i < up_grid.size() gridEntry e = up_grid.get(i) for trade in e.trades // remove that trade by id from my_short_trades int j = 0 while j < my_short_trades.size() tradeEntry t = my_short_trades.get(j) strategy.cancel(t.id) if t.id == trade.id my_short_trades.remove(j) break j += 1 i += 1 // delete all lines int j = 0 while j < dn_grid.size() dn_grid.get(j).ln.delete() j += 1 j := 0 while j < up_grid.size() up_grid.get(j).ln.delete() j += 1 ret check_highs(array<gridEntry> up_grid, array<gridEntry> dn_grid, bool use_long_sl, float long_sl, bool trailingUp, bool use_short_sl, float short_sl, bool use_long_trailing_stop, bool use_short_trailing_stop) => int my_short_id_num = short_id_num int i = 0 while i < up_grid.size() gridEntry e = up_grid.get(i) if high >= e.price // mark all trades as active for trade in e.trades trade.is_active := true // add a new sell order for the previous one if i > 0 prev_entry = up_grid.get(i-1) id = "short_" + str.tostring(i-1) tp = prev_entry.price - UP_STEP_PRICE tradeEntry trade = tradeEntry.new(id, i-1, prev_entry.price, tp) strategy.order(id, strategy.short, qty = QTY, stop = prev_entry.price) if not use_short_trailing_stop strategy.exit('tp_' + id, id, limit = tp) if use_short_sl strategy.exit('sl_' + id, id, stop = short_sl) short_trades.push(trade) prev_entry.trades.push(trade) my_short_id_num += 1 i += 1 else break my_short_id_num check_lows(array<gridEntry> dn_grid, array<gridEntry> up_grid, bool use_short_sl, float short_sl, bool trailingDown, bool use_long_sl, float long_sl, bool use_short_trailing_stop, bool use_long_trailing_stop) => int my_short_id_num = short_id_num int my_long_id_num = long_id_num int i = 0 txt = "" while i < dn_grid.size() gridEntry e = dn_grid.get(i) if low <= e.price if trailingDown e.ln.delete() dn_grid.remove(i) // move the strategy down -> add a new sell order first_el = up_grid.get(0) // calc the new sell price gridEntry newEntry = gridEntry.new() newEntry.price := e.price + DN_STEP_PRICE newEntry.ln := line.new(x1 = start_bar_idx, y1 = newEntry.price, x2 = bar_index, y2 = newEntry.price, color=color.red, extend=extend.right) up_grid.push(newEntry) // add a new trade id = "short_" + str.tostring(my_short_id_num) tp = newEntry.price - DN_STEP_PRICE strategy.order(id, strategy.short, qty = QTY, limit = newEntry.price) if not use_short_trailing_stop strategy.exit('tp_' + id, id, limit = tp) if use_short_sl strategy.exit('sl_' + id, id, stop = short_sl) tradeEntry trade = tradeEntry.new(id, i, newEntry.price, tp) short_trades.push(trade) my_short_id_num += 1 else // add a new buy order for the previous one if i > 0 prev_entry = dn_grid.get(i-1) id = "long_" + str.tostring(i-1) tp = prev_entry.price + DN_STEP_PRICE tradeEntry trade = tradeEntry.new(id, i, prev_entry.price, tp) txt += "new_buy_order: " + id + "\n" strategy.order(id, strategy.long, qty = QTY, stop = prev_entry.price) if not use_long_trailing_stop strategy.exit('tp_' + id, id, limit = tp) if use_long_sl strategy.exit('sl_' + id, id, stop = long_sl) long_trades.push(trade) my_long_id_num += 1 i += 1 continue else break //label.new(bar_index, low, txt) [my_short_id_num, my_long_id_num] remove_inactive_trades(array<tradeEntry> trades) => int i = 0 while i < trades.size() tradeEntry trade = trades.get(i) if not trade.is_active trades.remove(i) 0 else i += 1 if time > START_DATE and is_init if TRAILING_UP new_entry = check_highs_trailing_up(up_grids, dn_grids, short_trades) if not na(new_entry) grid_up_start_price := new_entry + new_entry * NO_TRADING_SPAN / 100.0 grid_dn_start_price := new_entry - new_entry * NO_TRADING_SPAN / 100.0 NUM_GRIDS_UP := NUM_GRIDS_UP - 1 NUM_GRIDS_DN := NUM_GRIDS_DN + 1 // remove inactive trades remove_inactive_trades(long_trades) remove_inactive_trades(short_trades) // and the grid up_grids.clear() dn_grids.clear() // recalc init_grid(up_grids, dn_grids, NUM_GRIDS_UP, NUM_GRIDS_DN) short_id_num := calc_short_steps(up_grids, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE, ENABLE_SHORT_TRAILING_STOP, grid_up_start_price, UP_STEP_PRICE, NUM_GRIDS_UP) long_id_num := calc_long_steps(dn_grids, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE, ENABLE_LONG_TRAILING_STOP, grid_dn_start_price, DN_STEP_PRICE, NUM_GRIDS_DN) else short_id_num := check_highs(up_grids, dn_grids, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE, TRAILING_UP, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE, ENABLE_LONG_TRAILING_STOP, ENABLE_SHORT_TRAILING_STOP) [cur_long_id_num2, cur_short_id_num2] = check_lows(dn_grids, up_grids, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE, TRAILING_DOWN, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE, ENABLE_SHORT_TRAILING_STOP, ENABLE_LONG_TRAILING_STOP) long_id_num := cur_long_id_num2 short_id_num := cur_short_id_num2 check_short_stops(bool trailing_up, bool trailing_down, bool use_trailing_stop, bool use_sl, float sl, array<tradeEntry> activeTrades, array<gridEntry> up_grids, float ts_dist_perc, bool use_short_sl, float short_sl) => // first stop loss if use_sl and high > sl // close all long trades int i = 0 int cnt = 0 while i < activeTrades.size() tradeEntry entry = activeTrades.get(i) strategy.cancel(entry.id) strategy.close(entry.id, 'Stop Loss') activeTrades.shift() // trailing stop int i = 0 // thing here is, we added our trades while i < activeTrades.size() tradeEntry entry = activeTrades.get(i) // get the current tp if low <= entry.tp float old_tp = entry.tp entry.tp := low entry.sl := low + (0.01 * ts_dist_perc * low) if not use_trailing_stop // the trade has run into take profit and should be removed // we have to remove the trade from both structures if up_grids.size() > 0 trades = up_grids.get(entry.grid_num).trades int j = 0 while j < trades.size() if trades.get(j).id == entry.id trades.remove(j) break j += 1 activeTrades.remove(i) if not (trailing_up or trailing_down) // here we create a new entry (and keep the trade active) strategy.order(entry.id, strategy.short, qty = QTY, limit = entry.ep) strategy.exit('tp_' + entry.id, entry.id, limit = old_tp) if use_short_sl strategy.exit('sl_' + entry.id, entry.id, stop = short_sl) else // add a new or modify the existing trailing stop strategy.exit('ts_' + entry.id, entry.id, stop = entry.sl) if use_trailing_stop and not na(entry.sl) and high >= entry.sl // strategy.cancel(entry.id) // strategy.close(entry.id, 'ts_' + entry.id) //label.new(bar_index, high, "stopped out") // we have to remove the trade from the map and from the activeTrades array trades = up_grids.get(entry.grid_num).trades int j = 0 while j < trades.size() if trades.get(j).id == entry.id trades.remove(j) break j += 1 activeTrades.remove(i) // create a new entry 0 else i += 1 check_long_stops(bool trailing_down, bool use_trailing_stop, bool use_sl, float sl, array<tradeEntry> activeTrades, float ts_dist_perc, bool use_long_sl, float long_sl) => // first stop loss if use_sl and low < sl // close all long trades int i = 0 int cnt = 0 while i < activeTrades.size() tradeEntry entry = activeTrades.get(i) strategy.cancel(entry.id) strategy.close(entry.id, 'Stop Loss') activeTrades.shift() // trailing stop int i = 0 // thing here is, we added our trades while i < activeTrades.size() tradeEntry entry = activeTrades.get(i) // get the current tp if high >= entry.tp float old_tp = entry.tp entry.tp := high entry.sl := high - (0.01 * ts_dist_perc * high) if not use_trailing_stop and not trailing_down // here we create a new entry (and keep the trade active) strategy.order(entry.id, strategy.long, qty = QTY, limit = entry.ep) strategy.exit('tp_' + entry.id, entry.id, limit = old_tp) if use_long_sl strategy.exit('sl_' + entry.id, entry.id, stop = long_sl) if use_trailing_stop and not na(entry.sl) and low <= entry.sl strategy.cancel(entry.id) strategy.close(entry.id, 'ts_' + entry.id) //label.new(bar_index, low, "stopped out") activeTrades.remove(i) // create a new entry 0 else i += 1 // check for stop loss if time > START_DATE and is_init check_short_stops(TRAILING_UP, TRAILING_DOWN, ENABLE_SHORT_TRAILING_STOP, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE, short_trades, up_grids, SHORT_TRAILING_STOP_PERC, ENABLE_SHORT_STOP_LOSS, SHORT_STOP_LOSS_PRICE) check_long_stops(TRAILING_DOWN, ENABLE_LONG_TRAILING_STOP, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE, long_trades, LONG_TRAILING_STOP_PERC, ENABLE_LONG_STOP_LOSS, LONG_STOP_LOSS_PRICE) //Statistics / Info Box // if barstate.islastconfirmedhistory // transparent = color.new(#131722, transp = 0) // color1 = color.white // quote = syminfo.currency // var table tab1 = table.new("bottom_right", 15, 15, transparent, transparent,0,transparent,0) // table.cell(tab1, 0, 1, 'Bitsgab GridBot Tester' , text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 1, str.tostring(version, '#.0') , text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 2, "Grid Steps", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 2, str.tostring(0.2) + " " + "%", text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 3, "Grid Range", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 3, str.tostring(70) + " " + "%", text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 4, "Current Value", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 4, str.tostring(9700,"#,###") + " " + quote, text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 5, "Total PNL", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 5, str.tostring(10000,"#,###") + " " + quote, text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 6, "Bot Profit", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 6, str.tostring(12890,"#,###") + " " + quote, text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 7, "Portfolio Profit", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 7, str.tostring(145,"#,###") + " " + "%", text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 8, "Avg. Daily", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 8, str.tostring(389,"#,###") + " " + quote, text_halign = text.align_right, text_size= size.normal, text_color=color1) // table.cell(tab1, 0, 9, "Avg. Daily Percent", text_halign = text.align_left, text_size= size.normal, text_color=color1) // table.cell(tab1, 1, 9, str.tostring(12,"#,###") + " " + "%", text_halign = text.align_right, text_size= size.normal, text_color=color1)
Leave a Comment