[pshaiBot] Simple Grid Bot (FUTURES)

beta
By pshai in Trading Bots Published May 2023 👁 2,101 views 💬 1 comments

Description

Hey YOU! Having trouble modifying SGB Spot to Futures? There's no need for that! Simple Grid Bot makes grid trading simple AF. All you need to do, is set your price range, amount of grids and the amount of your total investment and the bot will take care of the rest. SGB also contains start and stop conditions which you can use to limit and preserve your gains and investments. The functionality of this Futures version is exactly the same as Spot version. The only difference is that this runs on Futures markets and manages Long and Short positions only, rather than each grid with their own position. Settings Price Range Lower Limit - Lower price limit. Upper Limit - Upper price limit. Grid mode & quantity Mode - Grid mode. Arithmetic grid mode will place all grids evenly spaced based on a constant price difference between grids. Geometric grid mode will place all grids evenly spaced based on a constant percentage difference between grids. (not yet fully functioning) Quantity - The amount of grids the price range is split into. Investment amount Asset - Select the asset type in which the investment amount is based on. Example: on BTC/USDT market the Base option would point to BTC, and Quote option would point to USDT. Amount - Total amount to be invested into the grid range. This amount is split among grids. Safety Settings Max. allowed win - Disabled if zero. Set the maximum win allowed (total profits). If reached, the bot will trigger a take-profit and deactivate. Max. allowed loss - Disabled if zero. Set the maximum loss allowed (per position). If reached, the bot will trigger a stop-loss and deactivate. Advanced Settings Start condition - Instant condition will start the bot immediately. Price condition activates bot once price is above or below set start value. RSI condition will activate bot once RSI is above or below set start value. (NOTE: RSI uses the main interval setting and has 14 period length) Start type - Activation on above or below start value. Start value - The value for Price and RSI start conditions. Stop condition - Manual condition never deactivate the bot. Price condition deactivates bot once price is above or below set stop value. RSI condition will deactivate bot once RSI is above or below set stop value. (NOTE: RSI uses the main interval setting and has 14 period length) Stop type - Deactivation on above or below stop value. Stop value - The value for Price and RSI stop conditions. Stop exits - If true, bot will sell all bought assets when using [Below value] stop condition, or buy back when using [Above value] stop condition. NOTE: Bot is marked to be in beta test simply because the Geometric grid mode doesn't yet function as it should.
HaasScript
-- [HaasOnline] Simple Grid Bot (Futures) v1.4
-- Author: pshai

HideOrderSettings()
HideTradeAmountSettings()
EnableHighSpeedUpdates(true)

InputGroupHeader('Price Range')
local pr_lower = Input('Lower Limit', 0, 'Lower price limit.')
local pr_upper = Input('Upper Limit', 0, 'Upper price limit.')


InputGroupHeader('Grid mode & quantity')
local mode = InputOptions('Mode', 'Arithmetic', {'Arithmetic', 'Geometric'}, 'Grid mode. Arithmetic grid mode will place all grids evenly spaced based on a constant price difference between grids. Geometric grid mode will place all grids evenly spaced based on a constant percentage difference between grids.')
local grids = Input('Quantity', 0, 'The amount of grids the price range is split into.')


InputGroupHeader('Investment amount')
local asset = InputOptions('Asset', 'Base', {'Base', 'Quote'}, 'Select the asset type in which the investment amount is based on. Example: on BTC/USDT market the Base option would point to BTC, and Quote option would point to USDT.')
local amount = Input('Amount', 0, 'Total amount to be invested into the grid range. This amount is split among grids.')


InputGroupHeader('Safety Settings')
local max_win = Input('Max. allowed win', 0, 'Disabled if zero. Set the maximum win allowed (total profits). If reached, the bot will trigger a take-profit and deactivate.')
local max_loss = Input('Max. allowed loss', 0, 'Disabled if zero. Set the maximum loss allowed (per position). If reached, the bot will trigger a stop-loss and deactivate.')


InputGroupHeader('Advanced Settings')
local start_cond = InputOptions('Start condition', 'Instant', {'Instant', 'Price', 'RSI'}, 'Instant condition will start the bot immediately. Price condition activates bot once price is above or below set start value. RSI condition will activate bot once RSI is above or below set start value. (NOTE: RSI uses the main interval setting and has 14 period length)')
local start_type = InputOptions('Start type', 'Above value', {'Above value', 'Below value'}, 'Activation on above or below start value.')
local start_value = Input('Start value', 0, 'The value for Price and RSI start conditions.')
local stop_cond = InputOptions('Stop condition', 'Manual', {'Manual', 'Price', 'RSI'}, 'Manual condition never deactivate the bot. Price condition deactivates bot once price is above or below set stop value. RSI condition will deactivate bot once RSI is above or below set stop value. (NOTE: RSI uses the main interval setting and has 14 period length)')
local stop_type = InputOptions('Stop type', 'Above value', {'Above value', 'Below value'}, 'Deactivation on above or below stop value.')
local stop_value = Input('Stop value', 0, 'The value for Price and RSI stop conditions.')
local stop_exit = Input('Stop exits', false, 'If true, bot will sell all bought assets when using [Below value] stop condition, or buy back when using [Above value] stop condition.')



-- check settings
    if pr_lower <= 0 then
        DeactivateBot('Lower limit not set.')
        return
    end

    if pr_upper <= 0 then
        DeactivateBot('Upper limit not set.')
        return
    end

    if pr_lower > pr_upper then
        DeactivateBot('Lower Limit cannot be higher than Upper Limit.')
        return
    end

    if grids <= 2 then
        DeactivateBot('Grid quantity not set (must be 2 or more).')
        return
    end

    if amount == 0 then
        DeactivateBot('Investment amount not set.')
        return
    end
---------------


function createGridItem(level, amt)
    return {
        p = level,
        amt = amt,
        state = 0,
        isMid = false,
        isBuy = false,
        pid = '',
        oid = ''
    }
end

function createGrid()
    local cp = CurrentPrice()
    local cur_pr = cp.ask
    local pr_interval = grids > 0 and (pr_upper - pr_lower) / grids or 0
    local min_pr_interval = MakersFee() * cur_pr * 0.02
    local slot_size = amount / grids
    local wallet = {
        base = 0, --Balance({coin = BaseCurrency()}).available,
        quote = Balance({coin = UnderlyingAsset()}).available,
    }
    local grid = {}

    -- with geometric grid mode, calculate the % based interval instead
    if mode == 'Geometric' then
        pr_interval = grids > 0 and (pr_upper / pr_lower - 1) / grids * 100 or 0
        min_pr_interval = MakersFee() * 2
    end

    Log('Grid interval: ' .. pr_interval)
    Log('Breakeven interval: ' .. min_pr_interval)

    if pr_interval <= min_pr_interval then
        DeactivateBot('The grid will not be able to produce profits; grid interval is too small (increase total range or decrease grid quantity).')
    end

    -- build grid and find the middle
    local mid_found = false
    local closest = nil
    local closest_dist = NumberMax
    for i = 1, grids+1 do
        
        local level = 0

        if mode == 'Arithmetic' then
            level = pr_lower + pr_interval * (i - 1)
        else
            if i > 1 then
                level = AddPerc(grid[i-1].p, pr_interval)
            else
                level = pr_lower
            end
        end
        
        local dist = Abs(cur_pr - level)
        local amt = (asset == 'Base' and slot_size or slot_size / level)

        grid[i] = createGridItem(level, amt) --{p = level, isMid = false}

        if dist < closest_dist
        then
            if closest then
                closest.isMid = false
            end
            closest_dist = dist
            closest = grid[i]
            closest.isMid = true
            mid_found = true
        end
    end


    -- second pass to set buy/sell types
    min_profit, max_profit = NumberMax, NumberMin
    local total_buy, total_sell = 0, 0
    local prev_level = 0
    for i = 1, grids+1 do
        if grid[i].p <= closest.p then
            grid[i].isBuy = true
            total_buy = total_buy + (grid[i].amt * grid[i].p)

            grid[i].pid = lpid
        else
            total_sell = total_sell + grid[i].amt

            grid[i].pid = spid
        end

        if prev_level > 0 then
            local p = Abs(prev_level / grid[i].p - 1)

            if p < min_profit then min_profit = p end
            if p > max_profit then max_profit = p end
        end

        prev_level = grid[i].p
    end

    Log('total sell: ' .. total_sell)
    Log('total buy: ' .. total_buy)
    Log('wallet size: ' .. wallet.quote)

    

    --DeactivateBot()

    return grid
end

function drawGrid()
    local cp = CurrentPrice()
    local lpos = PositionContainer(lpid)
    local spos = PositionContainer(spid)

    if lpos.amount > 0 then
        Plot(0, 'LongAEP', lpos.enterPrice, {c = Cyan, id = ''..lpid})
        Plot(1, 'LongPnL', lpos.profit, {c = Cyan, id = ''..lpid})
    end

    if spos.amount > 0 then
        Plot(0, 'ShortAEP', spos.enterPrice, {c = Purple, id = ''..spid})
        Plot(1, 'ShortPnL', spos.profit, {c = Purple, id = ''..spid})
    end

    for i = 1, #g_grids do
        local grid = g_grids[i]

        if grid.oid != '' then
            local o = OrderContainer(grid.oid)

            if grid.isBuy then
                if grid.state == 1 then
                    Plot(0, i..'-Buy', o.price, {c = Green, id = grid.oid})
                elseif grid.state == 3 then
                    Plot(0, i..'-Sell', o.price, {c = Yellow, id = grid.oid})
                end
            else
                if grid.state == 1 then
                    Plot(0, i..'-Sell', o.price, {c = Red, id = grid.oid})
                elseif grid.state == 3 then
                    Plot(0, i..'-Buy', o.price, {c = Yellow, id = grid.oid})
                end
            end
        end
    end
end

function updateGrid()
    local sell = PlaceGoShortOrder
    local buy = PlaceGoLongOrder
    local sell2 = PlaceExitShortOrder
    local buy2 = PlaceExitLongOrder

    for i = 1, #g_grids do
        local grid = g_grids[i]
        local pos = PositionContainer(grid.pid)
        local cmd = grid.isBuy and buy or sell
        local cmd2 = grid.isBuy and buy2 or sell2

        if grid.oid != '' then
            local o = OrderContainer(grid.oid)

            if not o.isOpen then
                grid.oid = ''

                if o.isCancelled then
                    grid.state = grid.state - 1
                elseif o.isFilled then
                    grid.state = grid.state + 1

                    if grid.state == 4 then
                        grid.state = 0
                    end
                end
            end
        elseif grid.state == 1 or grid.state == 3 then
            grid.state = grid.state - 1
        end

        if not grid.isMid then
            if grid.state == 0 then
                if grid.oid == '' then
                    grid.oid = cmd(grid.p, grid.amt, {positionId = grid.pid, timeout = -1, type = LimitOrderType})
                    grid.state = 1
                end
            elseif grid.state == 2 then
                if grid.oid == '' then
                    local p = grid.isBuy and g_grids[i+1].p or g_grids[i-1].p
                    grid.oid = cmd2(p, grid.amt, {positionId = grid.pid, timeout = -1, type = LimitOrderType})
                    grid.state = 3
                end
            end
        end
    end
end

function exitGrid()
    local lpos = PositionContainer(lpid)
    local spos = PositionContainer(spid)

    Log('long amt: ' .. lpos.amount)
    Log('short amt: ' .. spos.amount)

    if lpos.amount > 0 then
        PlaceExitPositionOrder(lpid, {type = MarketOrderType})
    end

    if spos.amount > 0 then
        PlaceExitPositionOrder(spid, {type = MarketOrderType})
    end
end

function updatePositions()
    local lpos = PositionContainer(lpid)
    local spos = PositionContainer(spid)

    if lpos.amount == 0 and lpos.enterPrice > 0 then
        if IsAnyOrderOpen(lpid) then
            CancelAllOrders(lpid)
        else
            lpid = NewGuid()
        end
    end

    if spos.amount == 0 and spos.enterPrice > 0 then
        if IsAnyOrderOpen(spid) then
            CancelAllOrders(spid)
        else
            spid = NewGuid()
        end
    end
end

if not init then

    active = Load('active', false)
    deactivated = Load('deactivated', false)
    tpsl = Load('tpsl', false)
    stop_count = Load('sc', 0)
    min_profit = 0
    max_profit = 0

    lpid = Load('lpid', NewGuid())
    spid = Load('spid', NewGuid())

    init = Load('init', true)
end

function backupGlobals()
    Save('active', active)
    Save('deactivated', deactivated)
    Save('tpsl', tpsl)
    Save('sc', stop_count)
    Save('lpid', lpid)
    Save('spid', spid)
    Save('init', init)
end

-- update positions
--updatePositions()
------

if not active then
    if not deactivated then
        local cp = CurrentPrice()

        if start_cond == 'Price' then
            -- check price and activate
            if (start_type == 'Above value' and cp.high > start_value)
            or (start_type == 'Below value' and cp.low < start_value)
            then
                active = true
            end

        elseif start_cond == 'RSI' then
            -- check RSI and activate
            local rsi = RSI(ClosePrices(), 14)

            if (start_type == 'Above value' and rsi > start_value)
            or (start_type == 'Below value' and rsi < start_value)
            then
                active = true
            end
        else
            active = true -- activate instantly
        end
    else
        -- check if any orders are open and cancel
        if IsAnyOrderOpen() then
            CancelAllOrders()
        elseif stop_exit or tpsl then
            exitGrid()
        end

        stop_count = stop_count + 1

        if stop_count > 5 then
            DeactivateBot()
        end
    end

else

    if not g_grids then
        g_grids = createGrid()
    end

    updateGrid()
    drawGrid()

    local cp = CurrentPrice()
    local lpos = PositionContainer(lpid)
    local spos = PositionContainer(spid)

    if max_win > 0 then
        if GetBotProfit({includeUnrealized = true}) >= max_win then
            active = false
            deactivated = true
            tpsl = true
            LogWarning('------------------------------------------------------------------------------')
            LogWarning('-- Bot deactivated by TAKE-PROFIT: please stop and clean bot before a new run.')
            LogWarning('------------------------------------------------------------------------------')
        end
    end

    if max_loss > 0 then
        if lpos.profit <= -max_loss or spos.profit <= -max_loss then
            active = false
            deactivated = true
            tpsl = true
            LogWarning('----------------------------------------------------------------------------')
            LogWarning('-- Bot deactivated by STOP-LOSS: please stop and clean bot before a new run.')
            LogWarning('----------------------------------------------------------------------------')
        end
    end

    if stop_cond == 'Price' then
        -- check price and deactivate
        if (stop_type == 'Above value' and cp.high > stop_value)
        or (stop_type == 'Below value' and cp.low < stop_value)
        then
            active = false
            deactivated = true
            LogWarning('------------------------------------------------------------------------')
            LogWarning('-- Bot deactivated by PRICE: please stop and clean bot before a new run.')
            LogWarning('------------------------------------------------------------------------')
        end

    elseif stop_cond == 'RSI' then
        -- check RSI and deactivate
        local rsi = RSI(ClosePrices(), 14)

        if (stop_type == 'Above value' and rsi > stop_value)
        or (stop_type == 'Below value' and rsi < stop_value)
        then
            active = false
            deactivated = true
            LogWarning('------------------------------------------------------------------------')
            LogWarning('-- Bot deactivated by RSI: please stop and clean bot before a new run.')
            LogWarning('------------------------------------------------------------------------')
        end
    end

    CustomReport('Potential profit/grid (ROI)', Round(min_profit*100*Leverage(), 2) .. ' - ' .. Round(max_profit*100*Leverage(), 2) .. ' %')

end

------------------------------------------
backupGlobals()


PlotDoubleColor(
    Plot(99, 'Bot PnL', GetBotProfit({includeUnrealized = true}), Gold),
    0,
    Red,
    Gold(10)
)

1 Comment

Sign in to leave a comment.

K
Katerin about 3 years ago

Nice! Thanks for posting! KING! :)