[pshaiBot] Futures Band-It

4 2270 Views 7 Comments 8 months ago
  • Hello everyone!

    I’m excited to release this new awesome grid-bot script with you! It is a very powerful and dynamic grid-bot that utilizes ATR (Average True Range) for order placement, ADX (Average Directional Index) to limit when and where to trade and it offers a good bunch of settings for you to tweak it the way you like!

    I hope this massive beast can generate you the income you deserve!

    Parameters:
    – BASIC
    – – 1. Max. consecutive orders: Maximum amount of consecutive orders. Once last order is filled, stop-loss is activated
    – – 2. First order size: The size of the first order. Also works as the base for next order sizes (see [Order size multiplier])
    – – 3. Order size multiplier: Multiplier for how much order sizes grow, example with first order 100 and multiplier of 3; 100, 300, 900, 2700 and so on
    – – 4. Take-profit %: Take-profit is calculated based on the current average entry price of open position. If your exchange doesn’t offer rebate fees, you need to take those paid fees into account!!

    – GRID
    – – 1. Grid type: The type of grid. [ATR] type uses Average True Range, [StdDev] type uses Standard Deviation and [Fixed] type uses fixed stepping for order placement.
    – – 2. Grid period length: Period length of grid. Doesn’t affect FIXED type.
    – – 3. Grid base multiplier: Base multiplier, controls the distance of the orders.
    – – 4. Grid fixed step: Grid fixed step defines the step size for the [FIXED] grid type.
    – – 5. Grid timeframe: Interval used for grid data. Doesn’t affect FIXED type.

    – SAFETY
    – – 1. ADX period length: Period length of ADX
    – – 2. ADX shutdown limit: If ADX rises above this value, the bot will shutdown and exit any position it has open, using market order. Use this setting to control what volatility levels this bot should trade in. Value ignored if set to zero.
    – – 3. ADX timeframe: Interval used for ADX data
    – – 4. Stop-loss offset %: Stop-loss is offsetted based on the last filled entry price, NOT the current average entry price of open position
    – – 5. Cooldown length (min): The length of cooldown (in minutes) after ADX-shutdown or stop-loss

    “May the profits be with you…”
    ~pshai

    • This topic was modified 7 months, 2 weeks ago by pshai. Reason: Limited order count
    • This topic was modified 7 months, 2 weeks ago by pshai.
    • This topic was modified 7 months, 1 week ago by pshai.
    • This topic was modified 6 months, 1 week ago by pshai.
    • This topic was modified 1 month, 1 week ago by Derrick.
    • This topic was modified 1 month, 1 week ago by Team HaasScripts.
    HaasScript Code
    EnableHighSpeedUpdates(true)
    HideOrderSettings()
    HideTradeAmountSettings()
    SetFee(-0.025) -- for backtesting
     
     
    -- grid types
        local gridTypes = {
            atr = 'ATR',
            stddev = 'StdDev',
            fixed = 'Fixed Step'
            }
     
    -- inputs
        local orderCount = Input('1. Max. consecutive orders', 10, 'Maximum amount of consecutive orders. Once last order is filled, stop-loss is activated', 'BASIC')
        local initialAmount = Input('2. First order size', 100, 'The size of the first order. Also works as the base for next order sizes (see [Order size multiplier])', 'BASIC')
        local sizeMult = Input('3. Order size multiplier', 1, 'Multiplier for how much order sizes grow, example with first order 100 and multiplier of 3; 100, 300, 900, 2700 and so on', 'BASIC')
        local takeProfit = Input('4. Take-profit %', 0.25, 'Take-profit is calculated based on the current average entry price of open position. If your exchange doesn\'t offer rebate fees, you need to take those paid fees into account!!', 'BASIC')
        local gridType = InputOptions('1. Grid type', gridTypes.atr, {gridTypes.atr, gridTypes.stddev, gridTypes.fixed}, 'The type of grid. [ATR] type uses Average True Range, [StdDev] type uses Standard Deviation and [Fixed] type uses fixed stepping for order placement.', 'GRID')
        local atrLen = Input('2. Grid period length', 20, 'Period length of grid. Doesn\'t affect FIXED type.', 'GRID')
        local baseMult = Input('3. Grid multiplier', 1, 'Grid multiplier controls the distance of the orders (ATR & StdDev types). For FIXED type, this value is the price gap between orders.', 'GRID')
        local fixedStep = Input('4. Grid fixed step', 10, 'Grid fixed step defines the step size for the [FIXED] grid type.', 'GRID')
        local atrInt = InputInterval('5. Grid timeframe', 15, 'Interval used for grid data. Doesn\'t affect FIXED type.', 'GRID')
        local adxLen = Input('1. ADX period length', 20, 'Period length of ADX', 'SAFETY')
        local adxLimit = Input('2. ADX shutdown limit', 0, 'If ADX rises above this value, the bot will shutdown and exit any position it has open, using market order. Use this setting to control what volatility levels this bot should trade in. Value ignored if set to zero.', 'SAFETY')
        local adxInt = InputInterval('3. ADX timeframe', 15, 'Interval used for ADX data', 'SAFETY')
        local stopLossOffset = Input('4. Stop-loss offset %', 0.25, 'Stop-loss is offsetted based on the last filled entry price, NOT the current average entry price of open position', 'SAFETY')
        local timeoutLen = Input('5. Cooldown length (min)', 60, 'The length of cooldown (in minutes) after ADX-shutdown or stop-loss', 'SAFETY')
     
    -- usefulness
        local adxShutdownCount = Load('adxsc', 0)
        local slHitCount = Load('slhc', 0)
        local tpHitCount = Load('tphc', 0)
        local orderHitCount = Load('ohc', 0)
        local maxOrderHitCount = Load('mohc', 0)
        local totalOrderHitCount = Load('tohc', 0)
        local positionCount = Load('pc', 0)
     
        local shutdownTypes = {
            stoploss = 1,
            adx = 2
            }
        
        if Load('init', true) then
            Save('startBtc', WalletAmount(AccountGuid(), BaseCurrency()))
            Save('init', false)
        end
     
    -- data
        local ob = GetOrderbook()
        local h0 = HighPrices(1)
        local l0 = LowPrices(1)
        local c0 = ClosePrices(1)
        local h1 = HighPrices(atrInt)
        local l1 = LowPrices(atrInt)
        local c1 = ClosePrices(atrInt)
        local h2 = HighPrices(adxInt)
        local l2 = LowPrices(adxInt)
        local c2 = ClosePrices(adxInt)
        local cp = CurrentPrice()
        local baseMa = TYPPRICE(h0, l0, c0)
        local baseStd
        local adx = ADX(h2, l2, c2, adxLen)
     
        if gridType == gridTypes.atr then
            baseStd = ATR(h1, l1, c1, atrLen)
        elseif gridType == gridTypes.stddev then
            baseStd = STDDEV(c1, atrLen, 1)
        elseif gridType == gridTypes.fixed then
            baseStd = fixedStep
        end
     
    -- plot
        Plot(1, 'ADX', adx)
        PlotHorizontalLine(1, 'ADX Limit', Red, adxLimit, Dashed)
        Plot(2, 'Displacement', baseStd)
     
    -- positions
        local posId = Load('posId', NewGuid())
        local position = PositionContainer(posId)
        local posDD = Load('posDD', 0)
        local posDDs = Load('posDDs', {})
        local maxDD = Load('maxDD', 0)
     
        posDD = ArrayGet(Min(posDD, position.roi), 1)
     
    -- memory
        local index = Load('index', 0)
        local leid = Load('leid', '')
        local seid = Load('seid', '')
        local xeid = Load('xeid', '')
        local timeout = Load('timeout', 0)
     
    -- price adjustment helpers
        function adjustBuyPrice(price)
            if ob.bidPrices[1] <= price then
                price = SubPerc(ob.bidPrices[1], 0.05)
            end
     
            return price
        end
     
        function adjustSellPrice(price)
            if ob.askPrices[1] >= price then
                price = AddPerc(ob.askPrices[1], 0.05)
            end
     
            return price
        end
     
    -- price helper
        function getPrice(index, isLong)
            return isLong 
                and adjustBuyPrice( baseMa - baseStd * Pow(baseMult, index) )
                or adjustSellPrice( baseMa + baseStd * Pow(baseMult, index) )
        end
     
        function getExitPrice(isLong)
            return isLong
                and adjustSellPrice( AddPerc(position.enterPrice, takeProfit) )
                or adjustBuyPrice( SubPerc(position.enterPrice, takeProfit) )
        end
     
    -- order amount helper
        function getAmount(index, price)
            return ParseTradeAmount(PriceMarket(), price, initialAmount * Pow(sizeMult, index))
        end
     
    -- position helper
        function newPosition()
            index = 0
            xeid = ''
            leid = ''
            seid = ''
            posId = NewGuid()
            orderHitCount = 0
            positionCount = positionCount + 1
        end
     
    -- stop-loss helper
    -- loaded variables above fall into this function,
    -- so there's no need for input parameters
        function execStopLossExit(type)
            if IsAnyOrderOpen() then
                CancelAllOrders(posId)
     
            elseif position.isLong or position.isShort then
                LogWarning('Executing a hard exit!')
                PlaceExitPositionOrder(posId, {type = MarketOrderType, note='SL'})
                newPosition()
     
                if type == shutdownTypes.adx then
                    adxShutdownCount = adxShutdownCount + 1
                    LogWarning('--- ADX cutoff')
                elseif type == shutdownTypes.stoploss then
                    slHitCount = slHitCount + 1
                    LogWarning('--- SL cutoff')
                end
            end
     
            if timeout < Time() then
                timeout = Time() + timeoutLen * 60
            end
        end
     
    -- order update helper
        function updateOrders(isLong)
            local mainId = isLong and leid or seid
            local secId = isLong and seid or leid
     
            if mainId != '' and secId != '' and IsOrderFilled(mainId) and IsOrderFilled(secId) then
                newPosition()
                LogWarning('Both orders got filled?')
            end
     
            if secId != '' and IsOrderOpen(secId) then
                CancelOrder(secId)
                secId = ''
            end
     
            local postfix = isLong and 'L' or 'S'
            local entryCmd = isLong and PlaceGoLongOrder or PlaceGoShortOrder
     
     
            if xeid != '' and IsOrderFilled(xeid) then
                if mainId != '' and IsOrderOpen(mainId) then CancelOrder(mainId) end
     
                newPosition()
                tpHitCount = tpHitCount + 1
                return
     
            elseif mainId != '' and IsOrderOpen(mainId) == false then
                if xeid != '' and IsOrderOpen(xeid) then
                    CancelOrder(xeid)
                
                else
                    if IsOrderFilled(mainId) == true then
                        
                        Save('lastEntryPrice', OrderContainer(mainId).price)
     
                        index = index + 1
     
                        orderHitCount = orderHitCount + 1
                        totalOrderHitCount = totalOrderHitCount + 1
                        if orderHitCount > maxOrderHitCount then
                            maxOrderHitCount = orderHitCount
                        end
                    end
     
                    local entryPrice = getPrice(index, isLong)
                    local entrySize = getAmount(index, entryPrice)
                    
                    -- only add new orders if allowed
                    if index < orderCount then
                        mainId = entryCmd(
                            entryPrice,
                            entrySize,
                            {
                                type=MakerOrCancelOrderType,
                                note=postfix..(index+1),
                                timeout=604800,
                                positionId=posId
                            })
                    else
                        mainId = ''
                    end
                end
            end
     
            -- update id's
            leid = isLong and mainId or secId
            seid = isLong and secId or mainId
     
            -- update draw-down
            posDD = ArrayGet(Min(posDD, position.roi), 1)
     
            -- if all orders are in use, then we want to check for stop-loss
            if index >= orderCount then
                local lastEntryPrice = Load('lastEntryPrice', 0)
                local slPrice = isLong
                    and SubPerc(lastEntryPrice, stopLossOffset)
                    or AddPerc(lastEntryPrice, stopLossOffset)
     
                Plot(0, 'SL ('..stopLossOffset..'%)', slPrice, {c=Red, id=posId})
     
                if (isLong and cp.bid < slPrice)
                or (not isLong and cp.ask > slPrice)
                then
                    execStopLossExit(shutdownTypes.stoploss)
                end
            end
     
            -- placing exit order here to ensure it is where it has to be.
            if (xeid == '' or (xeid != '' and IsOrderOpen(xeid) == false and IsOrderFilled(xeid) == false)) then
                local exitPrice = getExitPrice(isLong)
     
                xeid = PlaceExitPositionOrder({
                    positionId = posId,
                    price = exitPrice,
                    type=MakerOrCancelOrderType,
                    note=postfix..'X'..(index+1),
                    timeout=604800
                    })
            end
        end
     
     
    -- da trading logic
        local adxAllows = (adxLimit > 0 and adx <= adxLimit) or (adxLimit <= 0)
        if adxAllows then
            -- No position detected, place initial orders
            if not (position.isLong or position.isShort) and timeout < Time() then
                local longPrice = getPrice(index, true)
                local shortPrice = getPrice(index, false)
                local longSize = getAmount(index, longPrice)
                local shortSize = getAmount(index, shortPrice)
                
                if leid == '' or (leid != '' and IsOrderOpen(leid) == false) then
                    leid = PlaceGoLongOrder(longPrice, longSize, {type=MakerOrCancelOrderType, note='L'..(index+1), timeout=604800, positionId=posId})
                end
     
                if seid == '' or (seid != '' and IsOrderOpen(seid) == false) then
                    seid = PlaceGoShortOrder(shortPrice, shortSize, {type=MakerOrCancelOrderType, note='S'..(index+1), timeout=604800, positionId=posId})
                end
            elseif position.isLong then
                updateOrders(true)
     
            elseif position.isShort then
                updateOrders(false)
            end
        else
            -- execute forced exit
            execStopLossExit(shutdownTypes.adx)
        end
     
    -- plot open orders for backtesting
        if leid != '' and IsOrderOpen(leid) then
            Plot(0, 'leid', OrderContainer(leid).price, {c=Green, id=posId})
        end
     
        if seid != '' and IsOrderOpen(seid) then
            Plot(0, 'seid', OrderContainer(seid).price, {c=Red, id=posId})
        end
     
        if xeid != '' and IsOrderOpen(xeid) then
            Plot(0, 'xeid', OrderContainer(xeid).price, {c=Purple, id=posId})
        end
     
        if position.amount > 0 or position.amount < 0 then
            Plot(0, 'position', position.enterPrice, {c=Blue, id=posId})
        end
     
     
    -- update draw-downs
        local lastPosId = Load('posId', '')
        if #lastPosId > 0 and lastPosId != posId then
            posDDs[#posDDs+1] = posDD
            maxDD = ArrayGet(Min(maxDD, posDD), 1)
            posDD = 0
        end
     
    -- save memory
        Save('posId', posId)
        Save('index', index)
        Save('leid', leid)
        Save('seid', seid)
        Save('xeid', xeid)
        Save('timeout', timeout)
        Save('posDD', posDD)
        Save('posDDs', posDDs)
        Save('maxDD', maxDD)
        Save('adxsc', adxShutdownCount)
        Save('slhc', slHitCount)
        Save('tphc', tpHitCount)
        Save('ohc', orderHitCount)
        Save('mohc', maxOrderHitCount)
        Save('tohc', totalOrderHitCount)
        Save('pc', positionCount)
     
     
    -- finalize
        Finalize(function()
            local adxSlRatio = slHitCount > 0 and adxShutdownCount/slHitCount or 1
            local tpSlRatio = slHitCount > 0 and tpHitCount/slHitCount or 1
            local t = Round((timeout-Time())/60, 0)
            local startBtc = Load('startBtc', 0)
            local endBtc = WalletAmount(AccountGuid(), BaseCurrency())
            CustomReport('Max. DrawDown', maxDD..' %', 'Performance')
            CustomReport('Avg. DrawDown', Round(Average(posDDs), 4)..' %', 'Performance')
            CustomReport('Last DrawDown', posDD..' %', 'Performance')
            CustomReport('ADX Shutdowns', adxShutdownCount, 'Performance')
            CustomReport('StopLoss hits', slHitCount, 'Performance')
            CustomReport('TakeProfit hits', tpHitCount, 'Performance')
            CustomReport('ADX-StopLoss ratio', slHitCount > 0 and adxSlRatio or 1, 'Performance')
            CustomReport('TakeProfit-StopLoss ratio', slHitCount > 0 and tpSlRatio or 1, 'Performance')
            CustomReport('Total positions', positionCount, 'Performance')
            CustomReport('Total orders hit', totalOrderHitCount, 'Performance')
            CustomReport('Max. orders hit/pos', maxOrderHitCount, 'Performance')
            CustomReport('Avg. orders hit/pos', totalOrderHitCount/positionCount, 'Performance')
            CustomReport('ADX', Round(adx,2)..'/'..Round(adxLimit,2), 'Safeties')
            CustomReport('Timeout', t > 0 and (t..' minutes') or ('No active timeout'), 'Safeties')
            CustomReport('Start '..BaseCurrency(), Round(startBtc,8), 'Wallet')
            CustomReport('End '..BaseCurrency(), Round(endBtc,8), 'Wallet')
            CustomReport('Delta', Round(Delta(startBtc, endBtc),4)..'%', 'Wallet')
            CustomReport('Difference', Round(endBtc-startBtc,8)..' '..BaseCurrency(), 'Wallet')
        end)
    • #806
      Busera
      Basic
      Up
      17
      Down

      Hi, I started to test your script (simulated Kraken Future Accounts with 1 BTC) and I have encounter two questions:
      – I am getting the following log messages: “WARNING: No maker template available for KrakenFutures. Falling back to normal limit order.”
      — Q: Do I need to create the respective maker template?

      – Although the bot is showing +26% , the overall wallet amount dropped to “0.99758157” (from 1.0).

      Cheers
      Andre

      • This reply was modified 7 months, 2 weeks ago by Busera.
    • #808
      pshai
      Admin
      Up
      18
      Down

      Hey @Busera.

      1) With simulated accounts there are no Maker templates, so the warning is nothing to be worried about.

      2) The bot reserves some assets for its orders (when it has open orders). You can add the “In Wallet” value with “In Order”/”In Use” value to get the real total (or simply stop the bot and cancel open orders).

    • #809
      Busera
      Basic
      Up
      1
      Down

      @pshai Thx a lot for the prompt reply.

    • #822
      Busera
      Basic
      Up
      23
      Down

      Hi @pshai,

      I am running your script now exclusively for the Kraken Future Challenge, and it generates excellent results. I adjusted the settings based on 8W backtesting, which I update every week. I also use a more conservative setting to minimize the drawdown and not to maximize the return.

      However, once in a while, the script gets stuck (Rather quite often), e.g., it sells only 99 of 100 contracts. In this case, I have to sell the remaining contracts manually, need to clear the statistics and restart the bot.

      Are you facing the same problem?

      Update: Today, during the BTC pump, all my gains were killed because the stop loss was not triggered. I am not sure why the script stops working.

      • This reply was modified 7 months ago by Busera.
      • This reply was modified 7 months ago by Busera.
      • This reply was modified 7 months ago by Busera.
    • #838
      pshai
      Admin
      Up
      1
      Down

      Hey Busera,

      Are you able to send me more information @ Haasonline Discord server? Anything you can provide regarding this will be very helpful!

      Join the discord server by following this link: https://discord.gg/VxKxgQ

    • #855
      BlockHash
      Basic
      Up
      1
      Down

      Hi Pshai,

      I’ve got these errors while trying the script

      5. 05 May 2020 23:54:14 ERROR: Unknown references: adjustSellPrice
      4. 05 May 2020 23:54:14 ERROR: Unknown references: adjustSellPrice

      can you provide your support please ?

    • #864
      pshai
      Admin
      Up
      1
      Down

      Hey @BlockHash the script is now fixed. There was a bug on the site which broke the script… It is all now fixed! 🙂

Login or Register to Comment

Unlock your crypto trading potential

Create a free account and enjoy everything we have to offer.

Join for Free