[ShoeBot] Shoe's Money Maker a.k.a yet another SMM version

stable
By Shoe in Trading Bots Published May 2021 👁 2,488 views 💬 8 comments

Description

Yet another SMM version. Same same but different.
HaasScript
-- Modified version of Phsai's amazing Simple Market Maker
-- combined with Smokyho's awesome dynamic budget.
--
-- ONLY FOR BINANCE FUTURES USDT/COIN HEDGE MODE ENABLED
-- If you do not have a Binance account yet feel free to use my referral
-- and save 10% on fees: https://www.binance.com/en/register?ref=XLP49I1W
--
-- CC_VPM3() by Kobalt needs installation of http://bit.ly/3sftz2G
--
-- Check this discord post for some additional infos: https://bit.ly/3tCkzod
--
-- Consider donating to support our work!
-- Phsai
    -- BTC : 1MTEdma4LgdN2hSadRppeZ6PxsyXQNuxS2
    -- USDT: 0x2f052efde92ded10e05e00277f4a5cdfd9c280ca
-- Smokyho
    -- BTC : 35KY1GPFtxKoJ6Bzri6sLYQPcmGZhHfRac
    -- USDT: 0x7720A90d0D1973eFcc258b91450c51c9967e110A
-- Kobalt
    -- BTC: 1PWedYjnVGvRmphbkM69J91PjeMM66AYbS
    -- BNB: 0x05221390ae191a880880924f15bf515480d7a12b     BEP20 (BSC)
    -- USDT: 0x05221390ae191a880880924f15bf515480d7a12b    ERC20
-- Shoe
    -- BTC: 1BCAGne4JDFMPptRkeLhQxtBm6qpLuBfd8
    -- IOTA: iota1qrzsyghryhev0g057tqspnkz7jkelfy8l46jttcv0jn7kdt59g97ghclvym
-- --------------------------------------------------------------------------

EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()

-- Check BYBIT or BINANCE
    local getMarket = PriceMarket()
    local isBybit = StringContains(getMarket, 'BYBIT')

-- custom log with switch because one-liner works for variable assignements only (https://bit.ly/3nt9frB)
    local sLog = function(msg, dbg, warning)
        local dbg = Not(dbg == false or dbg == true) and true or dbg
        if dbg then
            if warning != true then
                Log(msg)
            else
                LogWarning(msg)
            end
        end
    end

-- saves values to compare if they have changed (mostly used to reinitialize orders on relevant changes)
    local hasChanged = function(key, value, tolerance)
      local tolerance = IsNull(tolerance) and 0 or tolerance
        local val2comp = Load(key, value)
        if tolerance == 0 and val2comp != value then
          sLog("Relevant change in "..key.." (previous: "..val2comp.." / new: "..value..")", true, true)
          Save(key, value)
          return true
        elseif Abs(val2comp - value) > val2comp * tolerance then
          sLog("Relevant change out of tolerance in "..key.." (previous: "..val2comp.." / new: "..value..")", true, true)
          Save(key, value)
          return true
        else
          --sLog("NO relevant change in "..key.." (previous: "..val2comp.." / new: "..value..")", true, false)
          Save(key, value)
          return false
        end
    end

-- inputs
        local relevantChange = false
   InputGroupHeader('General Settings')
        local isBacktest = Input('01. Is Backtest', false, 'Activate if you\'re backtesting and relevant settings are taken care of.')
   InputGroupHeader('Trade Settings')
        local okLong = Input('01. Allow Long', false, 'Allow bot to open Long')
        local okShort = Input('02. Allow Short', false, 'Allow bot to open Short')
        local wtfStop = Input('03. Stop at no position', false, 'Deactivate bot when there is no open position')

    InputGroupHeader('Backtest Settings')
        local wtfBal = Input('01. Deactivate on Over Budget', false)
        local wtfAmount = Input('02. Deactivate on Over Size', false)
        --local compound = Input('03. Add profit into balance', false, 'ONLY FOR BACKTEST. DO NOT USE WHEN ON RUNNING BOT') -- this is managed by isBacktest
        local compound = isBacktest
        --local testWallet = Input('04. Use Custom Wallet', false, 'ONLY FOR BACKTEST. DO NOT USE WHEN ON RUNNING BOT') -- this is managed by isBacktest
        local testWallet = isBacktest
        local testBal = Input('03. Backtest Wallet Balance', 1000, 'ONLY FOR BACKTEST. Is ignored if you did not activate backtest above.')

    InputGroupHeader('Budget & Safety')
        local maxSizeD = Input('01. Max. Open '..AmountLabel(), 23, 'Maximum open contracts at any given time also as minimum for Dynamic Max Open. After exceeding this value, the bot will dump a portion of position at a loss.')
        local autoMax = Input('01A. Dynamic Max Open', true, 'Dynamically change max open contracts based on available balance')
        local slotBudget = Input('01B. Max. Open Divider', 777, 'DSSize feature - Divide max open position with this value to get new Slot Size. Example Max Open is 1000 and devider is 200 then slot size is 5')
        local leverage = Input('01C. Leverage', 20, 'MUST be filled even if using Cross Margin. Important for trading budget.')
        local contVal = Input('01D. COIN-M Value', 10, 'ONLY if bot trading on INVERSE Futures then enter the Contract Value. Ignore if trade on USDT')
        local maxBudget = Input('01E. Balance Budget', 0.3, 'How much from wallet balance allocated for this bot. 0.5 is 50% of wallet balance.')
        local maxOpen = Input('01F. Position Budget', 0.5, 'How much from the Balance Budget allocated for opening positions. 0.1 is 10% of Balance Budget')
        local reduceSize = Input('02. Size Reduction %', 21, 'How big of a portion the bot will dump once Max. Open Contracts is exceeded')
        local reduceOrderType = InputOrderType('03. Reduction Order Type', MarketOrderType, 'The order type for size reduction dump')
        local noReduce = Input('04. Disable Size Reduction', false, 'Are you sure?')

    InputGroupHeader('Slot Size')
        local slotCountTotal = Input('01. Slot Count Total', 1, 'How many orders are constantly kept open on both long and short side')
        relevantChange = hasChanged("slotCountTotal", slotCountTotal) and true or relevantChange
        local slotCountLow = Input('02. Slot Count Low', 2, 'How many orders are constantly kept open on both long and short side')
        relevantChange = hasChanged("slotCountLow", slotCountLow) and true or relevantChange
        local slotSizeD = Input('03. Slot Size', 0.1, 'Default trade amount per slot')
        local autoSlot = Input('03A. Dynamic Slot Size', true, 'DSSize - Dynamically change slot size favoring trending side.')

    InputGroupHeader('Slot Spread')
        local slotSpreadMin = Input('05. Min Spread %', 0.07, 'Percentage based spread value between each slot')
        relevantChange = hasChanged("slotSpreadMin", slotSpreadMin) and true or relevantChange
        local slotSpreadLow = Input('06. Low Spread %', 0.40, 'Percentage based spread value between each slot')
        relevantChange = hasChanged("slotSpreadLow", slotSpreadLow) and true or relevantChange
        local slotSpreadHigh = Input('07. High Spread %', 0.56, 'Percentage based spread value between each slot')
        relevantChange = hasChanged("slotSpreadHigh", slotSpreadHigh) and true or relevantChange
        local slotSpreadHighFactor = Input('08. High Spread Increase %', 0.0, 'Percentage based spread value between each slot')
        relevantChange = hasChanged("slotSpreadHighFactor", slotSpreadHighFactor) and true or relevantChange
        local slotCancel = Input('09. Cancel Distance %', 0.21, 'How much price can move to the opposite direction before orders are cancelled and replaced')
        local hoursUntilRenewSlot = Input('10. Hrs until renewing slot', 3, 'Hours until a slot with a filled order is placed again because of missing volatility')

    InputGroupHeader('Profit')
        local takeProfit = Input('11. Take-Profit %', 0.28, 'Fixed take-profit value, based on price change')
        local tpOrderType = InputOrderType('12. TP Order Type', LimitOrderType, 'The order type for take-profit')

   InputGroupHeader('Other Settings')
        local dbg = Input('01. Debug Mode', false, 'Turn on for logs')
        sLog("Relevant change: " .. Parse(relevantChange, StringType), dbg)

-- price and data
    local cp = CurrentPrice()
    local c = ClosePrices()
    local SRCounter = Load('SRCounter', 0)

-- positions
    local hedge_longPosId = Load('hedge_longPosId', NewGuid())
    local hedge_shortPosId = Load('hedge_shortPosId', NewGuid())

    local dir_l = GetPositionDirection(hedge_longPosId)
    local aep_l = GetPositionEnterPrice(hedge_longPosId)
    local pamt_l = GetPositionAmount(hedge_longPosId)
    local proi_l = GetPositionROI(hedge_longPosId)
    local canc_l = false
    sLog('Long Position ID: ' .. aep_l .. ' (' .. pamt_l .. ' @ ' .. aep_l .. ')', dbg)
    local dir_s = GetPositionDirection(hedge_shortPosId)
    local aep_s = GetPositionEnterPrice(hedge_shortPosId)
    local pamt_s = GetPositionAmount(hedge_shortPosId)
    local proi_s = GetPositionROI(hedge_shortPosId)
    local canc_s = false
    sLog('Short Position ID: ' .. aep_s .. ' (' .. pamt_s .. ' @ ' .. aep_s .. ')', dbg)

    local lInitOrder = Load('lInitOrder', {price = 0})
    local sInitOrder = Load('sInitOrder', {price = 0})

    sLog('LONG position ROI: '..Round(proi_l, 4)..'%', dbg)
    sLog('SHORT position ROI: '..Round(proi_s, 4)..'%', dbg)

    local function sDateString(timestamp)
         return CurrentYear(timestamp).."-"..
            SubString(CurrentMonth(timestamp)+100,2,2).."-"..
            SubString(CurrentDate(timestamp)+100,2,2).." "..
            SubString(CurrentHour(timestamp)+100,2,2)..":"..
            SubString(CurrentMinute(timestamp)+100,2,2)..":"..
            SubString(CurrentSecond(timestamp)+100,2,2)
    end

-- wallet check
    local TMC = TradeMarketContainer(getMarket)
    local MTA = TMC.minimumTradeAmount
    local profitLabel = ProfitLabel()
    if profitLabel == nil then profitLabel = QuoteCurrency() end
    local availBal = testWallet and testBal or WalletAmount(getMarket, profitLabel)
    local getProfitL = GetCurrentProfit(PositionLong)
    local getProfitS = GetCurrentProfit(PositionShort)
    local getProfit = getProfitL + getProfitS
    local usedLong = UsedMargin(getMarket, aep_l, pamt_l, leverage)
    local usedShort = UsedMargin(getMarket, aep_s, pamt_s, leverage)
    sLog('usedLong '..usedLong..' usedShort '..usedShort, dbg)
    local botProfit = GetBotProfit()

        if isBybit then
            wallMount = availBal - getProfit
            xchange = 'BYBIT'
        else
            wallMount = availBal
            xchange = 'BINANCE'
        end

    -- balance warning
        local walletBal = compound and (wallMount + botProfit) or wallMount
        local workBal = usedLong + usedShort - getProfit
        local budgetBal = maxBudget * walletBal
        local balRatio = budgetBal > 0 and workBal / budgetBal or 0
        sLog('walletBal '..walletBal, dbg)
        if balRatio > 0.5 and balRatio < 0.8 then LogWarning('Working balance is > 50% of Budget!!!') end
        if balRatio > 0.8 then LogWarning('Working balance is > 80% of Budget!!!') end
        sLog('Balance Monitor '..xchange..' -> Budget Balance: '..Round(budgetBal, 5)..' '..profitLabel..' | Working Balance: '..Round(workBal, 5)..' '..profitLabel..' | Ratio: '..Round(balRatio, 3))

        local BRCounter = Load('BRCounter', 0)
        if balRatio > BRCounter then Save('BRCounter', balRatio) end

-- budgeting
    --max position
        if profitLabel == 'USD' or profitLabel == 'USDT' or profitLabel == 'BUSD' then
            maxMax = walletBal * maxBudget * maxOpen / cp.close

        else
            maxMax = walletBal * maxBudget * maxOpen * cp.close / contVal

        end

        local maxCheck = autoMax and (maxMax * leverage)
        local maxValue = autoMax and (maxCheck > maxSizeD) and maxCheck or maxSizeD
        local maxSize = autoMax and maxValue or maxSizeD

        -- check max open
            if autoMax then
                if maxCheck > maxSizeD then
                    sLog('Dynamic Max Open: '..Round(maxCheck, 5)..' '..AmountLabel())

                else
                    sLog('Dynamic Max Open: '..Round(maxCheck, 5)..' but using default value: '..Round(maxSizeD, 5)..' '..AmountLabel())

                end
            end

    -- slot size
        if autoSlot then
            local slotCheck = maxSize / slotBudget
            slotSize = slotCheck > slotSizeD and slotCheck or slotSizeD
            sLog('Dynamic Slot Size: '..Round(slotSize, 5)..' '..AmountLabel())
        else
            slotSize = slotSizeD
            sLog('Slot Size Value: '..Round(slotSize, 5))
        end
        relevantChange = hasChanged("slotSize", slotSize, 0.07) and true or relevantChange

-- CORE LOGIC
-- custom arrayAdd because native ArrayAdd does not cope with nested arrays
    local function arrayAdd(array, element)
        local len = #array
        array[len + 1] = element
        return array
    end

-- get price base
    local getPriceBase = function(isLong)
        local priceBase = isLong
                and cp.bid
                or cp.ask

        local aep = isLong and aep_l or aep_s

        -- if we have average entry price
        if aep > 0 then
            priceBase = isLong
                    and Min(aep, priceBase)
                    or Max(aep, priceBase)
        end
        return priceBase
    end

-- manage position ids
    local lDelta = Delta(lInitOrder.price, getPriceBase(true))
    sLog("lInitOrder.price: " .. lInitOrder.price .. " / lDelta: "..lDelta, dbg)
    local sDelta = Delta(getPriceBase(false), sInitOrder.price)
    sLog("sInitOrder.price: " .. sInitOrder.price .. " / sDelta: "..sDelta, dbg)

    if pamt_l == 0 and (IsPositionClosed(hedge_longPosId) or (lInitOrder.price > 0 and lDelta >= slotCancel)) then
         if IsAnyOrderOpen(hedge_longPosId) then
            CancelAllOrders(hedge_longPosId)
            canc_l = true
            sLog('lOrders cancelled', dbg)
        else
            hedge_longPosId = NewGuid()
            dir_l = GetPositionDirection(hedge_longPosId)
            aep_l = GetPositionEnterPrice(hedge_longPosId)
            pamt_l = GetPositionAmount(hedge_longPosId)
            proi_l = GetPositionROI(hedge_longPosId)
        end
        Save('lSlots', {})
        Save('lInitOrder', {price = 0})
    end

    if pamt_s == 0 and (IsPositionClosed(hedge_shortPosId) or (sInitOrder.price > 0 and sDelta >= slotCancel)) then
        if IsAnyOrderOpen(hedge_shortPosId) then
            CancelAllOrders(hedge_shortPosId)
            canc_s = true
            sLog('sOrders cancelled', dbg)
        else
            hedge_shortPosId = NewGuid()
            dir_s = GetPositionDirection(hedge_shortPosId)
            aep_s = GetPositionEnterPrice(hedge_shortPosId)
            pamt_s = GetPositionAmount(hedge_shortPosId)
            proi_s = GetPositionROI(hedge_shortPosId)
        end
        Save('sSlots', {})
        Save('sInitOrder', {price = 0})
    end

-- get pos id
    local getPositionId = function(isLong)
        return isLong and hedge_longPosId or hedge_shortPosId
    end

-- get price
    local getPrice = function(isLong, spr)
        local basePrice = isLong and lInitOrder.price or sInitOrder.price
        local price = 0

        if basePrice > 0 then
            price = isLong
                and SubPerc(lInitOrder.price, spr)
                or AddPerc(sInitOrder.price, spr)
        else
            price = isLong
                and SubPerc(getPriceBase(isLong), spr)
                or AddPerc(getPriceBase(isLong), spr)
        end
        return price
    end

-- get spread
    local getSpread = function(index)
        index = index - 1 -- we start from 0
        if index == 0 then
            spr = slotSpreadMin
        elseif index > 0 and index <= slotCountLow then
            spr = slotSpreadMin + slotSpreadLow * index
        else
            spr = slotSpreadMin + (slotSpreadLow * slotCountLow) + (AddPerc(slotSpreadHigh, ((slotSpreadHighFactor * (index - slotCountLow)))) * (index - slotCountLow))
        end

        sLog("slotSpreadMin + (slotSpreadLow * slotCountLow) + (AddPerc(slotSpreadHigh, ((slotSpreadHighFactor * (index - slotCountLow)))) * (index - slotCountLow))", dbg)
        sLog(slotSpreadMin.." + ("..slotSpreadLow.." * "..slotCountLow..") + (AddPerc("..slotSpreadHigh..", (("..slotSpreadHighFactor.." * ("..index.." - "..slotCountLow..")))) * ("..index.." - "..slotCountLow.."))", dbg)
        sLog("Slot: " .. index .. " / Spread: " .. spr .. " (" .. AddPerc(10000, spr) .. " / " .. SubPerc(10000, spr) .. ")", dbg)

        return spr
    end

-- place order
    local placeOrder = function(isLong, price, amount, name, posId)
        local cmd = isLong and PlaceGoLongOrder or PlaceGoShortOrder
        local oid = ''
        oid = cmd(price, amount, {type = MakerOrCancelOrderType, note = name, timeout = 3600, positionId = posId})
        return oid
    end

-- slot management
    local updateSlots = function(isLong, amount, slotCountTotal, cancelDist, canPlace)
        local prefix = isLong and 'l' or 's'
        local slots = Load(prefix..'Slots', {})
        local posId = getPositionId(isLong)

        local openOrders = 0
        slots['orders'] = IsNull(slots['orders']) and {} or slots['orders']

        if (Count(slots['orders']) > 0) then
            for key,order in pairs(slots['orders']) do
                local name = prefix .. key

                if order.oid != '' then
                    local orderContainer = OrderContainer(order.oid)
                    if relevantChange and orderContainer.isOpen then
                        CancelOrder(order.oid)
                        sLog("Order "..name.." cancelled due to relevant changes", dbg, true)
                    end
                    sLog(name..'order '..name..' is open  ('..Parse(orderContainer.isOpen, StringType)..') or cancelled  ('..Parse(orderContainer.isCancelled, StringType)..') or -filled ('..Parse(orderContainer.isFilled, StringType)..')-', dbg)
                    local price = getPrice(isLong, getSpread(key))
                    if orderContainer.isFilled then
                        slots['orders'][key]['fillTS'] = (order.fillTS != '' and order.fillTS > 0) and order.fillTS or AdjustTimestamp(order.placeTS, {addSeconds = orderContainer.openTime})
                        sLog("("..order.fillTS.." != '' and "..order.fillTS.." > 0) and "..order.fillTS.." or AdjustTimestamp("..order.placeTS..", {addSeconds = "..orderContainer.openTime.."})", dbg)
                        sLog(order.name .. ">fillTS: "..sDateString(order.fillTS), dbg)
                    end
                    sLog(order.name..">isFilled: "..Parse(orderContainer.isFilled,StringType).." and "..Time()..">"..AdjustTimestamp(slots['orders'][key]['fillTS'], {addHours = hoursUntilRenewSlot}).." (AdjustTimestamp("..slots['orders'][key]['fillTS']..", {addHours = "..hoursUntilRenewSlot.."})",dbg)
                    if orderContainer.isOpen then
                        sLog("canPlace: "..Parse(canPlace,StringType).." or "..openOrders.." >= "..slotCountTotal, dbg)
                        if not canPlace or openOrders >= slotCountTotal then
                            CancelOrder(order.oid)
                            sLog('Not allowed right now '..name, dbg, true)
                        else
                            openOrders = openOrders + 1
                            sLog('Open order counted: '..name, dbg)
                        end
                    elseif (
                            orderContainer.isCancelled
                            or (
                                orderContainer.isFilled
                                and hoursUntilRenewSlot > 0
                                and Time() > AdjustTimestamp(slots['orders'][key]['fillTS'], {addHours = hoursUntilRenewSlot})
                                )
                            ) and ((isLong and cp.ask > price) or (not isLong and cp.bid < price)) and canPlace and openOrders < slotCountTotal then
                        slots['orders'][key]['placeTS'] = Time()
                        slots['orders'][key]['fillTS'] = 0
                        slots['orders'][key]['name'] = (orderContainer.isFilled and 'x' or 'r') .. prefix..key..' '..sDateString(slots['orders'][key]['placeTS'])
                        slots['orders'][key]['oid'] = placeOrder(isLong, price, amount, slots['orders'][key]['name'], order.posId)
                        openOrders = openOrders + 1
                        sLog(name..'order '..name..' placed because it was cancelled  ('..Parse(orderContainer.isCancelled, StringType)..') or -filled ('..Parse(orderContainer.isFilled, StringType)..')-', dbg)
                    end
                end
            end
        end

        local slotCount = Count(slots['orders'])
        sLog('Open '..prefix..' orders: '..openOrders..' of '..slotCountTotal, dbg)

        for i = slotCount + 1, slotCount + slotCountTotal - openOrders do
            sLog('New order slot ('..i..')', dbg)
            if canPlace then
                if isBacktest then SetFee(Abs(MakersFee())*-1) end
                local name = 'n' .. prefix .. i ..' '..sDateString(Time())
                local price = getPrice(isLong, getSpread(i))
                oid = placeOrder(isLong, price, amount, name, posId)
                orderInfo = {oid = oid, placeTS = Time(), fillTS = 0, price = price, amount = amount, name = name, posId = posId}
                slots['orders'] = arrayAdd(slots['orders'], orderInfo)
                sLog(name..' order '..name..' placed because '..(i - slotCount + openOrders - 1)..' slots are less than '..slotCountTotal, dbg)
                if i == 1 then
                    Save(prefix..'InitOrder', {oid = oid, price = price})
                end
            end
        end

        Save(prefix..'Slots', slots)
    end

-- update take-profit
    local updateTakeProfit = function(isLong, entryPrice, targetRoi, cancelDist)
        local prefix = isLong and 'Long' or 'Short'
        local name = prefix .. ' Take-Profit'
        local oid = Load(prefix .. 'tp_oid', '')
        local timer = Load(prefix .. 'tp_timer', 0)
        local posId = getPositionId(isLong)
        local tp_delta = isLong and Delta(entryPrice, cp.bid) or Delta(cp.ask, entryPrice)

        if oid != '' then
            local order = OrderContainer(oid)

            if order.isOpen then
                local delta = isLong
                        and Delta(order.price, cp.close)
                        or Delta(cp.close, order.price)

                if delta >= cancelDist then
                    CancelOrder(oid)
                    sLog('Delta cancelled '..name, dbg, true)
                end
            else
                if order.isCancelled then
                    timer = 0
                end

                oid = ''
            end
        else
            if tp_delta >= targetRoi and Time() >= timer then
                if isBacktest then SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1) end
                oid = PlaceExitPositionOrder({type = tpOrderType, note = name, timeout = 3600, positionId = posId})
                timer = Time() + 60 -- 1min
            end
        end

        Save(prefix .. 'tp_oid', oid)
        Save(prefix .. 'tp_timer', timer)
    end

-- update position size
    local updatePositionManagement = function(isLong, currentSize, sizeLimit, cancelDist)
        local prefix = isLong and 'Long' or 'Short'
        local name = prefix .. ' Size Reduction'
        local oid = Load(prefix .. 'pos_oid', '')
        local posId = getPositionId(isLong)
        local amount = SubPerc(currentSize, 100 - reduceSize) -- take X% of position
        local price = isLong
                and cp.ask
                or cp.bid
        local cmd = isLong
                and PlaceExitLongOrder
                or PlaceExitShortOrder
        local timer = Load(prefix .. 'pos_timer', Time())

        if oid != '' then
            local order = OrderContainer(oid)

            if order.isOpen then
                local delta = isLong
                        and Delta(order.price, cp.close)
                        or Delta(cp.close, order.price)
                if delta >= cancelDist then
                    CancelOrder(oid)
                    sLog('Delta cancelled '..name, dbg, true)
                end
            else
                oid = ''
            end
        else
            if currentSize > sizeLimit and not noReduce and Time() >= timer then
                if isBacktest then SetFee(reduceOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1) end
                oid = cmd(price, amount, {type = reduceOrderType, note = name, timeout = 6000, positionId = posId})
                timer = Time() + 60 -- 1min
            end
        end

        Save(prefix .. 'pos_oid', oid)
        Save(prefix .. 'pos_timer', timer)
    end

-- da logica
     -- take profit
     updateTakeProfit(true, aep_l, takeProfit, slotCancel)
     updateTakeProfit(false, aep_s, takeProfit, slotCancel)

     -- risk management
     updatePositionManagement(true, pamt_l, maxSize, slotCancel)
     updatePositionManagement(false, pamt_s, maxSize, slotCancel)

     -- update slots
     sLog("canc_l: "..Parse(canc_l, StringType))
     if not canc_l then
         sLog("Update Long Slots",dbg)
         updateSlots(true, slotSize, slotCountTotal, slotCancel, okLong) -- long slot
     end
     sLog("canc_s: "..Parse(canc_s, StringType))
     if not canc_s then
         sLog("Update Short Slots",dbg)
         updateSlots(false, slotSize, slotCountTotal, slotCancel, okShort) -- short slot
     end

     if aep_l > 0 then
         local posId = getPositionId(true)
         Plot(0, 'AvgEP Long', aep_l, {c=Teal, id=posId, w=2})
     end

     if aep_s > 0 then
         local posId = getPositionId(false)
         Plot(0, 'AvgEP Short', aep_s, {c=Purple, id=posId, w=2})
     end

     Save('hedge_longPosId', hedge_longPosId)
     Save('hedge_shortPosId', hedge_shortPosId)

if not isBacktest then CC_VPM3() end -- please install ... first

-- WTF
    if wtfStop then
        local longNull = pamt_l == 0
        local shortNull = pamt_s == 0
        if longNull and shortNull then
            DeactivateBot('Deactivate on No Position is active', true)

        else
            sLog('Will deactivate on No Position, waiting...maybe next second, maybe never :)', true, true)

        end
    end

    if wtfBal then
        if workBal > budgetBal then
            DeactivateBot('Over Budget', true)
        end
    end

    if wtfAmount then
        if pamt_l > maxSize or pamt_s > maxSize then
            DeactivateBot('Over Size', true)
        end
    end

-- PlotExposure
    local lineLong = IfElse(pamt_l > 0, pamt_l, 0)
    local lineShort = IfElse(pamt_s > 0, -pamt_s, 0)
    Plot(1, 'Longs', lineLong, {c=Green, s=Step})
    Plot(1, 'Max Longs', maxSize, {c=White, s=Step})
    Plot(1, 'Shorts', lineShort, {c=Red, s=Step})
    Plot(1, 'Max Shorts', -maxSize, {c=White, s=Step})
    ChartSetOptions(1, 'Exposure')

    Plot(2, 'Budget Balance', (walletBal * maxBudget), {c=White, s=Step})
    Plot(2, 'WorkBal', workBal, {c=Red, s=Step})
    ChartSetOptions(2, 'Balance Monitor')

sLog('Size Reduction: '..SRCounter..' times')
if not okLong then
    sLog('Bot is not allowed to open LONG', true, true)
end

if not okShort then
    sLog('Bot is not allowed to open SHORT', true, true)
end

Finalize(function()
    CustomReport('Current Balance Ratio', Round(100 * balRatio, 2)..'%')
    CustomReport('Highest Balance Ratio', Round(100 * BRCounter, 2)..'%')
    CustomReport('Size Reduction', SRCounter..' times')
    CustomReport('Final Wallet Balance', Round(walletBal, 5)..' '..profitLabel)
    if testWallet then
        local profitPercent = Round(PercentageChange(testBal, walletBal), 2)
        CustomReport('Profit %: ', profitPercent..'%')
        CustomReport('Profit to Highest Bal Ratio', Round(profitPercent / (100 * BRCounter), 2))
    end
end)
sLog(' ')

8 Comments

Sign in to leave a comment.

D
daniel_p about 5 years ago

Hi, thanks for the post,

Think that calls a custom command:

ERROR: Unknown references: CC_VPM3

Daniel.

D
daniel_p about 5 years ago

Sorry, saw it now in the comments...

M
mardedude about 5 years ago

Hi,

I have a question. Could you make a function that increase the slot size? So for example first order slot is 0.15 second order slot 0.20 etc

S
Shoe about 5 years ago

Hi,

I started implementing it a while ago and I remember there was some hurdle (I don't know exactly what it was) why I stopped working on it. It's still in my backlog but not with high priority. I am not sure how benificiary it really is. I also remember that I did some calculations and found the dynamic spreads renewing the slots after a while more helpful. If you think it's a very helpful strategy contact me via discord and I am happy to hear your thoughts.

S
SupMap about 5 years ago

Hi, first of all thanks a lot for releasing your code! Secondly after quickly looking over it i was not able to explain myself what the part is for:

if isBacktest then SetFee(Abs(MakersFee())*-1) end

I mean i get that you are actually making negative fees positive and vice versa, but what for? I really would love to understand that one, maybe you could explain? 🙂
Thanks!

P.S: And this one as well 🙂

if not isBacktest then CC_VPM3() end

Dan

S
Shoe about 5 years ago

Hi Dan, thanks for taking a look at the details. :) It feels good seeing people digging deeper and challenging the code.

Honestly, I don't understand the first line either. This is coming from the original by Pshai. The only thing I added was checking for isBacktest because SetFee() is applied in backtests only anyways and I thought it might speed up the backtest by a nano second if I check this first. If you would like to understand why setFee() is used like this please approach Pshai, most preferrably on the Discord server in the #haasscript-bots channel. I am sure he has an answer to this...I just trusted him with this line of code but now that you're asking I am interested as well.

Regarding if not isBacktest then CC_VPM3() end I have a proper answer. :D It's loading the Custom Command for Virtual Position Management by Kobalt but this is definitely not used in backtests why I also tried to save some time in backtests not loading it at all. I did some performance tests and it doesn't save as much as I was hoping for but if you're testing a large timeframe and some variations (HaasLabs) it definitely counts.

I hope this helps.

Cheers,
Shoe

S
SupMap about 5 years ago

Hi Shoe,

thanks a lot for the quick reply, now i understand the approach of speeding up the backtest by disabling CC_VPM3, a smart idea! Regarding the other question i will get in contact with Pshai directly, thank you!

Best

Dan

H
Hiromatsu over 3 years ago

Hey Shoe,

thank you for this awesome version of the SMM. I wrote you a DM on discord concerning a problem I have with a mod I did to your script. Kind regards Hiromatsu