[pshaiBot] Simple Market Maker (Leverage, NEW HEDGE MODE)
eolDescription
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.
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?
Hey Wane,
Currently HTS doesnt support any native trailing orders, only available ones are the stop orders and take-profit orders.
Dear Pshai
can you enable this bot on SPOT market
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/
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?
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
Try turning low power mode on
What can i do, if bot lost positions. Means still short open, but bot doesnt show? Thx
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?
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 :)
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.
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?
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.
Seem like the Max Open Contracts not working. I set slot size 1000, max open 10000, and it's now at 40000
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.
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.
Yeah, simulated accounts do not know the minimum amounts. Nice find though!
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. :/
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... ;)
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.
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/
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)
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 ?
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?
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
Everything is possible.
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.