Untitled

mail@pastecode.io avatar
unknown
plain_text
5 months ago
23 kB
6
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)
Leave a Comment