[pshaiBot] Simple Market Maker (Leverage, NEW HEDGE MODE)

eol
By pshai in Trading Bots Published July 2020 👁 13,566 views 💬 27 comments ★ Staff Pick

Description

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! *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 - Community mods - Smokyho: https://www.haasscripts.com/t/simple-market-maker-bfh/ Firetron: https://www.haasscripts.com/t/pshaibot-simple-market-maker-leverage-long-short-setting/ https://www.haasscripts.com/t/pshaibot-simple-market-maker-spot-mode/ ORtchi: https://www.haasscripts.com/t/experimentalpshaibot-simple-market-maker-leverage-binance-bybit-ortchimod-option-alternativ-sales-mod-optimised/ strooth: https://www.haasscripts.com/t/ortchimod-table-mod-working-backtesting/
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!
-- 
-- *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
-- 
-- 
-- --------------------------------------------------------------------------

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')

-- 
    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)


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

            for i = 1, slotCount do
                slot(false, i, slotSize, slotSpread, slotCancel) -- short slot
            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
            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
            for i = 1, slotCount do
                slot(true, i, slotSize, slotSpread, slotCancel, (pamt_s == 0 and signal == 1) or proi_s >= hedgeRoi) -- long slot
            end

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

27 Comments

Sign in to leave a comment.

B
BNDT almost 6 years ago

This is truly a beautiful beast!

I do have a question though, maybe you can help. I'd like the to use the "Trailing stop market order" for Take Profit, but it doesn't work. Is there a setting in the script that I haven't paid attention to, or is the Binance API currently not up to it?

P
pshai over 5 years ago

Hey Wane,

Currently HTS doesnt support any native trailing orders, only available ones are the stop orders and take-profit orders.

V
VxLCLi over 5 years ago

Dear Pshai
can you enable this bot on SPOT market

F
Firetron over 5 years ago

Test this with SIM for a while to make sure I don't have any bugs: https://www.haasscripts.com/t/pshaibot-simple-market-maker-spot-mode/

S
scsmcripto over 5 years ago

Pshai this script is really crazy!

I have a question, I hope you can help me. I want to add "Stop Loss" to restrict loss, how can I add it?

I
iddqd over 5 years ago

52. 15 Feb 2021 18:30:00 WARNING: Order has been cancelled. Reason unknown. (6ce13588878a40da9b835c09ad8f637d)
51. 15 Feb 2021 18:30:00 WARNING: No maker template available for BinanceFutures. Falling back to normal limit order.

on simulated account all ok but on real not working

S
Strooth over 5 years ago

Try turning low power mode on

C
CryptoHarvey over 5 years ago

What can i do, if bot lost positions. Means still short open, but bot doesnt show? Thx

F
firestone777 over 5 years ago

I have the same issue with the bot saying "No maker template available for BinanceFutures. Falling back to normal limit order." what does it mean?

P
pshai about 5 years ago

It is just a warning telling you that it is trying to use unsupported order type which makes it fall back to a limit order. Nothing worth worrying :)

P
pshai about 5 years ago

Adding a simple Stop-Loss might bring in some problems, as the bot relies on averaged (DCA) positions. Contact me on Discord misthema#5446 and we can work something out.

A
antharas about 5 years ago

Hi pshai,

It gives great results in test mode, but when I try to run with Binance USDT Futures, no orders were created. From the log:

408. 28 Apr 2021 05:34:25 WARNING: Order has been cancelled. Reason unknown. (82fafa7104e44cceb94d5f4552913060)

Please can you let me know if that's error from my side or how can we fix it?

X
ximalo about 5 years ago

I had the same issue. Look at the HaasOnline Trade Server Log/Shell/Command Window. Here I found the following message:
"Order's notional must be no smaller than 5.0 (unless you choose reduce only)"
I had the slot size reduced to 0.9 in simulation mode. There it seems to work, but for the real account/live mode I had to increase the value.

J
Justin about 5 years ago

Seem like the Max Open Contracts not working. I set slot size 1000, max open 10000, and it's now at 40000

D
dewors over 4 years ago

This is genius!
The market is not predictable, so let the bot adjust itself)
I thought for a long time how to make an unkillable bot and here it is! As soon as I earn something on it, I will definitely send it to you.
Many thanks.

P
pshai over 4 years ago

Unfortunately you need to stop and clear bot, handle open positions manually and then restart the bot. :/ This bot has no logic for getting back into lost positions.

P
pshai over 4 years ago

Yeah, simulated accounts do not know the minimum amounts. Nice find though!

P
pshai over 4 years ago

If you have reduction size set to 0, it wont reduce the excessive position size... The bot in that part is kinda dumb; it allows you to go above the limit, and then starts dumping excessive contracts. :/

P
pshai over 4 years ago

Thank you so much!

Yes, indeed the markets work in ways that they simply can't be predicted.. Buying when they sell, and selling when they buy, that's how I roll... ;)

J
juliennonnon almost 4 years ago

I live in France, and from August 2, 2022, I could no longer use Binance Futures (French legislation). I have a Bybit futures account, but my open orders are always rejected. Could you advise me which platform to use for SMM. Thanks for your help. All the best.

P
pshai almost 4 years ago

I think you are entirely cut out from leverage trading because of your legislations... The only way for you to trade is to either go DEX which is not yet supported by HaasOnline software, or trade in spot markets. There is a spot version of this bot: https://www.haasscripts.com/t/pshaibot-simple-market-maker-spot-mode/

H
Hiromatsu almost 4 years ago

Did you went through KYC with your Bybit account? I am using a modified version of SMM (https://www.haasscripts.com/t/pshaibot-simple-market-maker-pshais-ss-mod/) and another Mod by Smokyho (https://www.haasscripts.com/t/simple-market-maker-bfh/) both are working well on Bybit (with no KNC)

G
Gk3yHi over 3 years ago

i have some problems with the bot if it get orders Partially filled .. someone can help me please . if it Partially filled orders then it register postions not right . example if it is in a long position of 200 contracts and my tp order get only 150 fills first then the bot thinks it has a short of 50 if the rest gets filled later and it is with filling normal orders too .
what to do here ?

D
Digitalnomad1526 about 3 years ago

Hello, this is an awesome bot.. just getting used to it.. I do have a question, would it be possible to add dynamic slot spread based on a volatility indicator?

E
emadiano_7613 over 2 years ago

Hello, dear Pshai
Thank you for this space giant that you made
I only get this error when the volume increases by an amount
Warning : Currently L1 is not allowed
After this error, the position is not closed and only adds volume

P
pshai over 2 years ago

Everything is possible.

P
pshai over 2 years ago

It's not an error, but a message notifying you that currently the bot is not allowed to place the order(s) and it only affects entry orders. This happens when the ROI % spread is not enough between the positions.