[pshaiBot] Simple Grid Bot (SPOT)

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

Description

Hey YOU! Having trouble setting up FlashCrash bot? Don't worry! The solution is here! 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. 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. Advanced Settings Trade to fill grids? - If true, bot will automatically buy or sell assets to fill both buy and sell grids. 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 v1.2
-- 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('Advanced Settings')
local init_wallet = Input('Trade to fill grids?', false, 'If true, bot will automatically buy or sell assets to fill both buy and sell grids.')
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.')
    end

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

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

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


function createGridItem(level, amt)
    return {
        p = level,
        amt = amt,
        used = false,
        isMid = false,
        isBuy = false,
        pid = NewGuid(),
        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 = Balance({coin = BaseCurrency()}).available,
        quote = Balance({coin = QuoteCurrency()}).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)
        else
            total_sell = total_sell + grid[i].amt
        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('wallet base: ' .. wallet.base)
    Log('total buy: ' .. total_buy)
    Log('wallet quote: ' .. wallet.quote)

    -- TODO: check that wallet amounts are sufficient to fill whole grid range
    if init_wallet then
        if (total_buy > 0 and total_buy > wallet.quote)
        and (total_sell > 0 and total_sell > wallet.base) then
            -- both sides are fucked
            LogError('Not enough assets to fill buy and sell grids.')
            return
        
        elseif (total_sell > 0 and wallet.base < total_sell) then
            -- buy coins to make up for sell side
            local diff = (total_sell - wallet.base) * (1.0 + TakersFee()*0.01) + AmountStep()

            if diff > 0 then
                if (wallet.quote - total_buy) / cp.ask < diff then
                    LogWarning('Need to buy '..diff..' '..BaseCurrency()..' to fill sell grid.')
                    DeactivateBot('Not enough '..QuoteCurrency()..' to buy '..BaseCurrency()..'.')
                    return
                end

                LogWarning('Buying '..diff..' '..BaseCurrency()..' to fill sell-side grids.')

                local pid = CreatePosition(PositionSold, cp.ask, diff, {positionId = 'Initial Buy'})
                PlaceBuyOrder(1, diff, {type = MarketOrderType, positionId = 'Initial Buy'})
            end

        elseif (total_buy > 0 and wallet.quote < total_buy) then
            -- sell coins to make up for buy side
            local q = (total_buy - wallet.quote)
            local diff = q / cp.bid * (1.0 + TakersFee()*0.01) + AmountStep()
        
            if diff > 0 then
                if wallet.base - total_sell < diff then
                    LogWarning('Need to sell '..diff..' '..BaseCurrency()..' (or approx. '..q..' '..QuoteCurrency()..' in wallet) to fill buy grid.')
                    DeactivateBot('Not enough '..BaseCurrency()..' to sell for '..QuoteCurrency()..'.')
                    return
                end

                LogWarning('Selling '..diff..' '..BaseCurrency()..' to fill buy-side grids.')

                local pid = CreatePosition(PositionBought, cp.bid, diff, {positionId = 'Initial Sell'})
                PlaceSellOrder(1, diff, {type = MarketOrderType, positionId = 'Initial Sell'})
            end
            
        end
    end

    --DeactivateBot()

    return grid
end

function drawGrid()
    local cp = CurrentPrice()

    for i = 1, #g_grids do
        local grid = g_grids[i]
        local c = Green
        local pos = PositionContainer(grid.pid)

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

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

function updateGrid()
    local sell = PlaceSellOrder
    local buy = PlaceBuyOrder

    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 sell or buy

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

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

                if grid.used and IsPositionClosed(grid.pid) then
                    grid.pid = NewGuid()
                end
            end
        end

        if not grid.isMid then
            if pos.amount == 0 then
                if grid.oid == '' then
                    grid.oid = cmd(grid.p, grid.amt, {positionId = grid.pid, timeout = -1, type = LimitOrderType})
                end
            else
                if grid.oid == '' then
                    local p = grid.isBuy and g_grids[i+1].p or g_grids[i-1].p
                    grid.oid = PlaceExitPositionOrder(grid.pid, p, LimitOrderType, '', -1)
                    grid.used = true
                end
            end
        end
    end
end

function exitGrid(selling)
    local cp = CurrentPrice()
    local total_amt = 0

    for i = 1, #g_grids do
        local grid = g_grids[i]
        local pos = PositionContainer(grid.pid)

        if pos.amount > 0 then
            total_amt = total_amt + pos.amount
            CloseVPosition(cp.close, grid.pid)
        end
    end

    if total_amt == 0 then
        return
    end

    local pid
    if selling then
        pid = CreatePosition(PositionBought, cp.bid, total_amt, {positionId = 'Safety Sell'})
    else
        pid = CreatePosition(PositionSold, cp.ask, total_amt, {positionId = 'Safety Buy'})
    end

    PlaceExitPositionOrder(pid, {type = MarketOrderType, note = pid})
end

if not init then

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

    init = Load('init', true)
end

function backupGlobals()
    Save('active', active)
    Save('deactivated', deactivated)
    Save('init', init)
end

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 then
            exitGrid(stop_type == 'Below value')
        end
    end

else

    if not g_grids then
        g_grids = createGrid()
    end

    updateGrid()
    drawGrid()

    local cp = CurrentPrice()

    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', Round(min_profit*100, 2) .. ' - ' .. Round(max_profit*100, 2) .. ' %')

end

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

1 Comment

Sign in to leave a comment.

K
Katerin about 3 years ago

Nice! Thanks for sharing!