[pshaiBot] Simple Market Maker (leverage) (long/short setting)

stable
By Firetron in Trading Bots Published February 2021 👁 2,067 views 💬 0 comments

Description

-- Introduction: -- This simple market maker makes the market! -- It doesn't stop, it has no limits (other than max. pos. size) -- and it's amazing. Get familiar with the bot before using it! -- I strongly suggest doing backtests and especially running it -- using a simulated account until you are confident that you know that -- this bot knows what it is doing! Also remember that backtests cannot -- represent the results you would see with a live bot; the difference -- is very...different. -- -- About trading commands.... -- This bot uses exit order commands only, but dont be alarmed! -- It is merely a hack that I found while exploring, and this hack -- allowed me to not bother handling positions whatsoever! -- In other words, the bot works well with these; it just looks weird. -- -- --== !NEW! Hedge Mode ==-- -- With this new feature you are able to trade on Binance Futures, OKEx -- and other excahnges that support hedge mode. Enable this mode ONLY -- when you are certain that hedging is supported on your chosen exchange! -- -- --== !NEW! Allow Long and Allow Short ==-- -- With this new feature you are able to trade just one side of the market, -- or both, or neither if you set both to false. -- -- *THINGS TO NOTE:* -- - This bot will not work well on: -- --> Bybit; bot is too intense for their API, dont trade there... -- --> Bitmex; API Incompatibilities -- - BE VERY CAREFUL with fixed high leverage!
HaasScript
-- Simple Market Maker
-- by pshai @ 2020
--
-- Introduction:
-- This simple market maker makes the market!
-- It doesn't stop, it has no limits (other than max. pos. size)
-- and it's amazing. Get familiar with the bot before using it!
-- I strongly suggest doing backtests and especially running it
-- using a simulated account until you are confident that you know that
-- this bot knows what it is doing! Also remember that backtests cannot
-- represent the results you would see with a live bot; the difference
-- is very...different.
--
-- About trading commands....
-- This bot uses exit order commands only, but dont be alarmed!
-- It is merely a hack that I found while exploring, and this hack
-- allowed me to not bother handling positions whatsoever!
-- In other words, the bot works well with these; it just looks weird.
--
-- --== !NEW! Hedge Mode ==--
-- With this new feature you are able to trade on Binance Futures, OKEx
-- and other excahnges that support hedge mode. Enable this mode ONLY
-- when you are certain that hedging is supported on your chosen exchange!
--
-- --== !NEW! Allow Long and Allow Short ==--
-- With this new feature you are able to trade just one side of the market,
-- or both, or neither if you set both to false.
--
-- *THINGS TO NOTE:*
--  - This bot will not work well on:
--    --> Bybit; bot is too intense for their API, dont trade there...
--    --> Bitmex; API Incompatibilities
--  - BE VERY CAREFUL with fixed high leverage!
--
--
-- ~~ May the profits be with you ~~
-- ~pshai
--
--
-- Consider donating to support my work!
-- BTC: 3FRx1EkG4T4izrkaS34xeZHJFK4kQefHKf
-- --------------------------------------------------------------------------

EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()

-- inputs
local slotCount = Input('01. Slot Count', 5, 'How many orders are constantly kept open on both long and short side')
local slotSize = Input('02. Slot Size', 10, 'Trade amount per slot')
local slotSpread = Input('03. Slot Spread %', 0.1, 'Percentage based spread value between each slot')
local slotCancel = Input('04. Cancel Distance %', 0.1, 'How much price can move to the opposite direction before orders are cancelled and replaced')
local minSpread = Input('05. Minimum Spread %', 0.1, 'Minimum spread percentage between the first long and short entries. This setting only works when bot has no position.')
local maxSize = Input('06. Max. Open Contracts', 12000, 'Maximum open contracts at any given time. After exceeding this value, the bot will dump a portion of position at a loss')
local reduceSize = Input('07. Size Reduction %', 25, 'How big of a portion the bot will dump once Max. Open Contracts is exceeded')
local reduceOrderType = InputOrderType('08. Reduction Order Type', MarketOrderType, 'The order type for size reduction dump')
local takeProfit = Input('09. Take-Profit %', 0.2, 'Fixed take-profit value, based on price change')
local tpOrderType = InputOrderType('10. TP Order Type', MakerOrCancelOrderType, 'The order type for take-profit')
local hedgeMode = Input('11. Hedge Mode', false, 'Hedge Mode works on exchanges like OKEX and Binance Futures with hedge mode enabled (need to set that via Binance Futrues website!). Changing this setting while bot is running will cause unwanted behavior!!')
local hedgeRoi = Input('12. Hedge Minimum ROI %', 10, 'The ROI % the current open position needs to have before we enable the opposite side')
local allowLong = Input('13. Allow Long Positions', true, 'Set to false to disable Long Positions during a down trend.')
local allowShort = Input('13. Allow Short Positions', true, 'Set to false to disable Short Positions during an up trend.')

--
minSpread = minSpread / 2.0


-- regular single-position logic
if not hedgeMode then
  -- price and data
    local cp = CurrentPrice()
    local aep = GetPositionEnterPrice()
    local pamt = GetPositionAmount()
    local proi = GetPositionROI()

    Log('position ROI: '..Round(proi, 4)..'%')

  -- not using spread if we have a position
    if pamt > 0 then
      minSpread = 0
    end

  -- slot function
    local slot = function(isLong, index, amount, spread, cancelDist)
      local prefix = isLong and 'L' or 'S'
      local name = prefix .. index
      local cmd = isLong and PlaceExitShortOrder or PlaceExitLongOrder -- a little hack...
      local priceBase = isLong
          and cp.bid
          or cp.ask
      local spr = minSpread + spread * index

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

      -- get price
      local price = isLong
          and SubPerc(priceBase, spr)
          or AddPerc(priceBase, spr)

      local oid = Load(name..'oid', '') -- order id

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

        if order.isOpen then
          local delta = isLong
              and Delta(AddPerc(order.price, spr), priceBase)
              or Delta(priceBase, SubPerc(order.price, spr))

          if delta >= cancelDist then
            CancelOrder(oid)
            oid = '' -- reset id immediately, otherwise need 2 updates to get new order
            LogWarning('Delta cancelled '..name)
          end
        else
          oid = ''
        end
      else
        SetFee(Abs(MakersFee())*-1)
        oid = cmd(price, amount, {type = MakerOrCancelOrderType, note = name, timeout = 3600})
      end

      Save(name..'oid', oid)
    end

  -- update take-profit
    local updateTakeProfit = function(entryPrice, targetRoi, cancelDist)
      local name = 'Take-Profit'
      local oid = Load('tp_oid', '')
      local isLong = GetPositionDirection() == PositionLong
      local timer = Load('tp_timer', Time())
      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)
            LogWarning('Delta cancelled '..name)
          end
        else
          oid = ''
        end
      else
        if tp_delta >= targetRoi and Time() >= timer then
          SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
          oid = PlaceExitPositionOrder({type = tpOrderType, note = name, timeout = 3600})
          timer = Time() + 60 -- 1min
        end
      end

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


  -- update position size
    local updatePositionManagement = function(currentSize, sizeLimit, cancelDist)
      local name = 'Size Reduction'
      local oid = Load('pos_oid', '')
      local isLong = GetPositionDirection() == PositionLong
      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('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)
            LogWarning('Delta cancelled '..name)
          end
        else
          oid = ''
        end
      else
        if currentSize > sizeLimit and Time() >= timer then
          SetFee(reduceOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
          oid = cmd(price, amount, {type = reduceOrderType, note = name, timeout = 6000})
          timer = Time() + 60 -- 1min
        end
      end

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


  -- da logica

    -- take profit
    updateTakeProfit(aep, takeProfit, slotCancel)

    -- risk management
    updatePositionManagement(pamt, maxSize, slotCancel)


    if allowLong then
      -- update slots
      for i = 1, slotCount do
        slot(true, i, slotSize, slotSpread, slotCancel) -- long slot
      end
    end

    if allowShort then
      for i = 1, slotCount do
        slot(false, i, slotSize, slotSpread, slotCancel) -- short slot
      end
    end

    if aep > 0 then
      local posId = PositionContainer().positionId
      Plot(0, 'AvgEP', aep, {c=Purple, id=posId, w=2})
    end



--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
-- hedge it!
else
  -- price and data
    local cp = CurrentPrice()
    local c = ClosePrices()
    local rsi = RSI(c, 9) -- RSI is used to determine which side to start off with. this is to eliminate ghost positions.
    local signal = rsi > 52 and -1 or rsi < 48 and 1 or 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 dir_s = GetPositionDirection(hedge_shortPosId)
    local aep_s = GetPositionEnterPrice(hedge_shortPosId)
    local pamt_s = GetPositionAmount(hedge_shortPosId)
    local proi_s = GetPositionROI(hedge_shortPosId)

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

  -- not using spread if we have a position
    if pamt_l > 0 or pamt_s > 0 then
      minSpread = 0
    end

  -- manage position ids
    if pamt_l == 0 and IsPositionClosed(hedge_longPosId) then
      if IsAnyOrderOpen(hedge_longPosId) then
        CancelAllOrders(hedge_longPosId)
      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
    end

    if pamt_s == 0 and IsPositionClosed(hedge_shortPosId) then
      if IsAnyOrderOpen(hedge_shortPosId) then
        CancelAllOrders(hedge_shortPosId)
      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
    end

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

  -- slot function
    local slot = function(isLong, index, amount, spread, cancelDist, canPlace)
      local prefix = isLong and 'L' or 'S'
      local name = prefix .. index
      local cmd = isLong and PlaceGoLongOrder or PlaceGoShortOrder
      local priceBase = isLong
          and cp.bid
          or cp.ask
      local spr = minSpread + spread * index
      local posId = getPositionId(isLong)
      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

      -- get price
      local price = isLong
          and SubPerc(priceBase, spr)
          or AddPerc(priceBase, spr)

      local oid = Load(name..'oid', '') -- order id

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

        if order.isOpen then
          local delta = isLong
              and Delta(AddPerc(order.price, spr), priceBase)
              or Delta(priceBase, SubPerc(order.price, spr))

          if delta >= cancelDist then
            CancelOrder(oid)
            LogWarning('Delta cancelled '..name)
          elseif not canPlace then
            CancelOrder(oid)
            LogWarning('Not allowed right now '..name)
          end
        else
          oid = ''
        end
      else
        if canPlace then
          SetFee(Abs(MakersFee())*-1)
          oid = cmd(price, amount, {type = MakerOrCancelOrderType, note = name, timeout = 3600, positionId = posId})
        end
      end

      Save(name..'oid', oid)
    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)
            LogWarning('Delta cancelled '..name)
          end
        else
          if order.isCancelled then
            timer = 0
          end

          oid = ''
        end
      else
        if tp_delta >= targetRoi and Time() >= timer then
          SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
          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)
            LogWarning('Delta cancelled '..name)
          end
        else
          oid = ''
        end
      else
        if currentSize > sizeLimit and Time() >= timer then
          SetFee(reduceOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
          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
    if allowLong then
      updateTakeProfit(true, aep_l, takeProfit, slotCancel)
    end
    if allowShort then
      updateTakeProfit(false, aep_s, takeProfit, slotCancel)
    end

    -- risk management
    if allowLong then
      updatePositionManagement(true, pamt_l, maxSize, slotCancel)
    end
    if allowShort then
      updatePositionManagement(false, pamt_s, maxSize, slotCancel)
    end


    -- update slots
    if allowLong then
      for i = 1, slotCount do
        slot(true, i, slotSize, slotSpread, slotCancel, (pamt_s == 0 and signal == 1) or proi_s >= hedgeRoi) -- long slot
      end
    end

    if allowShort then
      for i = 1, slotCount do
        slot(false, i, slotSize, slotSpread, slotCancel, (pamt_l == 0 and signal == -1) or proi_l >= hedgeRoi) -- short slot
      end
    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)
end

0 Comments

Sign in to leave a comment.

No comments yet. Be the first!