Simple Market Maker BFH (Binance & Bybit)
stableDescription
This is a modified version of Phsai's amazing Simple Market Maker that intended ONLY FOR BINANCE FUTURES USDT/COIN & BYBIT FUTURES USDT with HEDGE MODE ENABLED with dynamic maximum open position and slot size.
*Attention on Bybit; if you get order rejected that could be because your spread and/or slot count is too tight. In other words for now Bybit seem does not like "ultra busy" bot.
The default mode is already set profitable for normal market movement but it won't survive something like covid dump on mid March '20. For that you have to adjust either the slot spread or maximum size divider.
Features:
1. Bot switch.
Allowing you to stop the bot creating new long/short order and stop the bot when has no position. Make sure you ticked Allow Long or/and Short before you start the bot or don't ticked and start the bot if you want to check the calculated max open and slot size.
2. Budgeting & Dynamic Maximum Open Position
The only thing that will avoid you from liquidation is risk management by doing budgeting. Decide how much you want to allocate for trading (single or multiple bots) and how much from that trading budget you allocate for opening positions.
Don't get greedy, unused balance in your futures wallet actually is being used as your buffer, it is your defense that silently keeping your position away from liquidation price.
3. Dynamic Slot Size
So you have the maximum open position position activated then you should have this mode activated also to give you the total freedom from adjusting the bot. Dynamic Slot Size will calculate the slot size by dividing the maximum open position (either dynamic calculated or manually) with Max Slot Divider.
4. Size Reduction Counter & Size Exposure Chart
Giving you information about how many times your bot is oversized and how much the open position at any moment to check how far is it from liquidation or even if it's liquidated or not (combine this with liquidation calculator in Binance Futures page). The slot exposure is one of Kobalt's ultilities.
This bot and its dynamic settings will allow you to have more family time because bot will automatically calculate your budget and slot size with price movement. No more going back to bot just to adjust just because price changing.
MAY THE PROFITS BE WITH US...!
Consider donating to support our work!
Phsai
BTC : 1MTEdma4LgdN2hSadRppeZ6PxsyXQNuxS2
USDT: 0x2f052efde92ded10e05e00277f4a5cdfd9c280ca
Smokyho
BTC : 35KY1GPFtxKoJ6Bzri6sLYQPcmGZhHfRac
USDT: 0x7720A90d0D1973eFcc258b91450c51c9967e110A
Update 1
2021-01-26
Definitions;
Budget Balance: How much from wallet balance allocated for this bot. Could be all of your wallet or a portion of it.
Working Balance: Position cost (margin allocated to position) - PNL.
Example 1:
Position cost: $17.81
PNL: -$90.67
Working Balance = 17.81 - (-90.67) = $108.48
Example 2:
Position cost: $17.81
PNL: $9.23
Working Balance = 17.81 - 9.23 = $8.58
1. Balance Monitor Fix
Changing the ratio from working balance : available balance to working balance : budget balance.
This will simulate how close is the position(s) to liquidation based on budget balance.
2. Exposure Chart + Balance Monitor Chart
Additional charts in backtest result to see the movement of positions and working balance
3. Back test Settings
Deactivate on Over Budget: Deactivate bot either live or backtest when working balance > budget balance.
Deactivate on Over Size: Deactivate bot either live or in backtest when position size > max open position a.k.a. oversize.
Update 2
2021-02-06
1. Bybit Compatibility
Bot will detect if the market Binance or Bybit and will adjust the budget calculation to match their "style". Information will be shown in log.
2. Back test Profit Compounding
Adding option to compound profit and use it for trade when in back test to give you more "realistic" result on the bot performance.
Update 3
2021-03-12
Please check https://discord.com/channels/269316665483722764/617043206688997386/819886275829956609 for descriptions.
Update 4
2021-03-13
Please check https://discord.com/channels/269316665483722764/617043206688997386/820194623376195586 for descriptions
HaasScript
-- Modified version of Phsai's amazing Simple Market Maker that intended
-- ONLY FOR BINANCE FUTURES USDT/COIN HEDGE MODE ENABLED
--
-- Consider donating to support our work!
-- Phsai
-- BTC : 1MTEdma4LgdN2hSadRppeZ6PxsyXQNuxS2
-- USDT: 0x2f052efde92ded10e05e00277f4a5cdfd9c280ca
-- Smokyho
-- BTC : 35KY1GPFtxKoJ6Bzri6sLYQPcmGZhHfRac
-- USDT: 0x7720A90d0D1973eFcc258b91450c51c9967e110A
----------------------------------------------------------------------------
Log('Update 4')
EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()
-- Check BYBIT or BINANCE
local getMarket = PriceMarket()
local isBybit = StringContains(getMarket, 'BYBIT')
-- inputs
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, 'Deactivate when Bal Ratio hit 1')
local wtfRatio = Input('02. Deactivate on specific Bal Ratio', false, 'Deactivate when Bal Ratio hit trigger below')
local wtfRatioV = Input('02A. Bal Ratio Trigger', 0.5)
local wtfAmount = Input('03. Deactivate on Over Size', false, 'Deactivate when one of position size > Max Open')
local compound = Input('04. Add profit into balance', false, 'ONLY FOR BACKTEST. DO NOT USE WHEN ON RUNNING BOT')
local testWallet = Input('05. Use Custom Wallet', false, 'ONLY FOR BACKTEST. DO NOT USE WHEN ON RUNNING BOT')
local testBal = Input('05A. Custom Wallet Balance', 10000, 'ONLY FOR BACKTEST. DO NOT USE WHEN ON RUNNING BOT')
InputGroupHeader('Budget & Safety')
local maxSizeM = Input('01. Max. Open '..AmountLabel(), 99999, '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 balanca')
local leverage = Input('01B. Leverage', 50, 'MUST be filled even if using Cross Margin. Important for trading budget.')
local contVal = Input('01C. COIN-M Value', 10, 'ONLY if bot trading on INVERSE Futures then enter the Contract Value. Ignore if trade on USDT')
local maxBudget = Input('01D. Balance Budget', 0.8, 'How much from wallet balance allocated for this bot. 0.5 is 50% of wallet balance.')
local maxOpen = Input('01E. Position Budget', 0.1, '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('Grid Settings')
local slotCount = Input('01. Slot Count', 1, 'How many orders are constantly kept open on both long and short side')
local slotSizeM = Input('02. Slot Size', 0.001, 'Trade amount per slot')
local autoSlot = Input('03A. Dynamic Slot Size', true, 'DSSize - Dynamically change slot size favoring trending side.')
local slotBudget = Input('03B. Max. Open Divider', 377, '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 slotSpread = Input('04. Slot Spread %', 0.618, 'Percentage based spread value between each slot')
local slotCancel = Input('05. Cancel Distance %', 0.236, 'How much price can move to the opposite direction before orders are cancelled and replaced')
local minSpread = Input('06. Minimum Spread %', 0.236, 'Minimum spread percentage between the first long and short entries. This setting only works when bot has no position.')
InputGroupHeader('Profit Settings')
local takeProfitL = Input('01. LONG Take-Profit %', 0.236, 'Fixed take-profit value, based on price change')
local takeProfitS = Input('02. SHORT Take-Profit %', 0.236, 'Fixed take-profit value, based on price change')
local tpOrderType = InputOrderType('03. TP Order Type', MakerOrCancelOrderType, 'The order type for take-profit')
local preplacedTP = Input('04. Pre-Place TP', false, 'Place Exit Position as soon as has open position')
--
minSpread = minSpread / 2.0
local SRCounter = Load('SRCounter', 0)
-- price and data
local cp = CurrentPrice()
local c = ClosePrices()
-- 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 delta_l = pamt_l > 0 and (cp.close - aep_l) / aep_l * 100 or 0
local proi_l = delta_l * leverage
local dir_s = GetPositionDirection(hedge_shortPosId)
local aep_s = GetPositionEnterPrice(hedge_shortPosId)
local pamt_s = GetPositionAmount(hedge_shortPosId)
local delta_s = pamt_s > 0 and (aep_s - cp.close) / aep_s * 100 or 0
local proi_s = delta_s * leverage
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
-- 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(AccountGuid(), 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)
--Log('usedLong '..usedLong..' usedShort '..usedShort)
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
--Log('walletBal '..walletBal)
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
Log('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
-- dynamics
--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 > maxSizeM) and maxCheck or maxSizeM
local maxSize = autoMax and maxValue or maxSizeM
-- check max open
if autoMax then
if maxCheck > maxSizeM then
Log('Dynamic Max Open: '..Round(maxCheck, 5)..' '..AmountLabel())
else
Log('Dynamic Max Open: '..Round(maxCheck, 5)..' but using default value: '..Round(maxSizeM, 5)..' '..AmountLabel())
end
end
-- slot size
local slotCheck = autoSlot and (maxSize / slotBudget)
local slotValue = autoSlot and (slotCheck > slotSizeM) and slotCheck or slotSizeM
local slotSize = autoSlot and slotValue or slotSizeM
if autoSlot then
if slotCheck > slotSizeM then
slotSizeL = slotSize
slotSizeS = slotSize
Log('Dynamic Slot Size: '..Round(slotSize, 5))
else
slotSizeL = slotSize
slotSizeS = slotSize
Log('DSSize: '..Round(slotCheck, 5)..' But using default value: '..Round(slotSizeM, 5))
end
else
slotSizeL = slotSizeM
slotSizeS = slotSizeM
Log('Slot Size Value: '..Round(slotSize, 5))
end
-- SMM CORE LOGIC HEDGE MODE
-- 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 .. ' TP'
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 preplacedTP then
if isLong and pamt_l > 0 then
local exitL = AddPerc(aep_l, takeProfitL)
SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
oid = PlaceExitPositionOrder({price= exitL, type = tpOrderType, note = 'Pre-Placed '..name, timeout = 600, positionId = hedge_longPosId})
end
if not isLong and pamt_s > 0 then
local exitS = SubPerc(aep_s, takeProfitS)
SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
oid = PlaceExitPositionOrder({price= exitS, type = tpOrderType, note = 'Pre-Placed '..name, timeout = 600, positionId = hedge_shortPosId})
end
elseif not preplacedTP and tp_delta >= targetRoi and Time() >= timer then
SetFee(tpOrderType == MarketOrderType and TakersFee() or Abs(MakersFee())*-1)
oid = PlaceExitPositionOrder({type = tpOrderType, note = name, timeout = 600, 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 not noReduce 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
sizeRed = 1
Save('SRCounter', SRCounter + sizeRed)
end
end
Save(prefix .. 'pos_oid', oid)
Save(prefix .. 'pos_timer', timer)
end
-- da logica
-- take profit
updateTakeProfit(true, aep_l, takeProfitL, slotCancel)
updateTakeProfit(false, aep_s, takeProfitS, 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, slotSizeL, slotSpread, slotCancel, okLong) -- long slot
end
for i = 1, slotCount do
slot(false, i, slotSizeS, slotSpread, 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)
-- 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
LogWarning('Will deactivate on No Position, waiting...maybe next second, maybe never :)')
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
if wtfRatio then
if balRatio > wtfRatioV then
DeactivateBot('Bal Ratio is over '..wtfRatioV, 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')
Log('Size Reduction: '..SRCounter..' times')
if not okLong then
LogWarning('Bot is not allowed to open LONG')
end
if not okShort then
LogWarning('Bot is not allowed to open SHORT')
end
-- custom reports
Finalize(function()
CustomReport('Final Wallet Balance', Round(walletBal, 5)..' '..profitLabel)
CustomReport('Size Reduction', SRCounter..' times')
CustomReport('Highest Balance Ratio', Round(100 * BRCounter, 2)..'%')
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)
Log(' ')
15 Comments
Sign in to leave a comment.
Hello,
What parameters do you use or recommend?
Hi,
Each coin will need different parameters because they "behave" differently. The default one was tested for ETH and LTC (i think) and some alts that i don't remember exactly.
If you want a "safe for all" parameters try;
Slot count: 1
Slot Spread: 1.236
Divider: 610
Profit: 0.236
Backtest it on several coins for at least 4 weeks.
hey, what time frame was this backtest?
and why does it say losing positions 0 but further down winratio is about 28%?
Regards
1h and I don't know about how that win ratio is calculated. it is that is "there" by default is haasscript
Any chance we could talk via email?
Regards
Find me in Haasonline's discord smokyho#0662
@ smokyho, are you still using this?
Everything under 'Backtest' is only for backtest purposes. Just so you know :P
I keep getting ghost orders at the extreme of the ranges :/ Anyone else having the same issue?
Any chance to get a modified version with a trailing SL instead of a fixed TP? Thanks for the good job!!!
Sorry for the late reply, please check the newest bot https://www.haasscripts.com/t/smokybot/
Thanks for the great script! This is the first script that started working with pluses, and has been slowly increasing the balance for three hours already! When working, I get the message "WARNING: No maker template available for BinanceFutures. Falling back to normal limit order" Do I need to fix this, or let it work like that?
you could ignore it.
Does it make sense to use leverage with this script? It gives the same results but using lower funds.
What parameters do I have to change for an investment of $1000?