Untitled
unknown
plain_text
a year ago
23 kB
12
Indexable
//@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)Editor is loading...
Leave a Comment