[pshaiBot] Futures Band-It
stableDescription
Hello everyone!
I'm excited to release this new awesome grid-bot script with you! It is a very powerful and dynamic grid-bot that utilizes ATR (Average True Range) for order placement, ADX (Average Directional Index) to limit when and where to trade and it offers a good bunch of settings for you to tweak it the way you like!
I hope this massive beast can generate you the income you deserve!
Parameters:
- BASIC
- - 1. Max. consecutive orders: Maximum amount of consecutive orders. Once last order is filled, stop-loss is activated
- - 2. First order size: The size of the first order. Also works as the base for next order sizes (see [Order size multiplier])
- - 3. Order size multiplier: Multiplier for how much order sizes grow, example with first order 100 and multiplier of 3; 100, 300, 900, 2700 and so on
- - 4. Take-profit %: Take-profit is calculated based on the current average entry price of open position. If your exchange doesn't offer rebate fees, you need to take those paid fees into account!!
- GRID
- - 1. Grid type: The type of grid. [ATR] type uses Average True Range, [StdDev] type uses Standard Deviation and [Fixed] type uses fixed stepping for order placement.
- - 2. Grid period length: Period length of grid. Doesn't affect FIXED type.
- - 3. Grid base multiplier: Base multiplier, controls the distance of the orders.
- - 4. Grid fixed step: Grid fixed step defines the step size for the [FIXED] grid type.
- - 5. Grid timeframe: Interval used for grid data. Doesn't affect FIXED type.
- SAFETY
- - 1. ADX period length: Period length of ADX
- - 2. ADX shutdown limit: If ADX rises above this value, the bot will shutdown and exit any position it has open, using market order. Use this setting to control what volatility levels this bot should trade in. Value ignored if set to zero.
- - 3. ADX timeframe: Interval used for ADX data
- - 4. Stop-loss offset %: Stop-loss is offsetted based on the last filled entry price, NOT the current average entry price of open position
- - 5. Cooldown length (min): The length of cooldown (in minutes) after ADX-shutdown or stop-loss
"May the profits be with you..."
~pshai
HaasScript
EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()
SetFee(-0.025) -- for backtesting
-- grid types
local gridTypes = {
atr = 'ATR',
stddev = 'StdDev',
fixed = 'Fixed Step'
}
-- inputs
local orderCount = Input('1. Max. consecutive orders', 10, 'Maximum amount of consecutive orders. Once last order is filled, stop-loss is activated', 'BASIC')
local initialAmount = Input('2. First order size', 100, 'The size of the first order. Also works as the base for next order sizes (see [Order size multiplier])', 'BASIC')
local sizeMult = Input('3. Order size multiplier', 1, 'Multiplier for how much order sizes grow, example with first order 100 and multiplier of 3; 100, 300, 900, 2700 and so on', 'BASIC')
local takeProfit = Input('4. Take-profit %', 0.25, 'Take-profit is calculated based on the current average entry price of open position. If your exchange doesn\'t offer rebate fees, you need to take those paid fees into account!!', 'BASIC')
local gridType = InputOptions('1. Grid type', gridTypes.atr, {gridTypes.atr, gridTypes.stddev, gridTypes.fixed}, 'The type of grid. [ATR] type uses Average True Range, [StdDev] type uses Standard Deviation and [Fixed] type uses fixed stepping for order placement.', 'GRID')
local atrLen = Input('2. Grid period length', 20, 'Period length of grid. Doesn\'t affect FIXED type.', 'GRID')
local baseMult = Input('3. Grid multiplier', 1, 'Grid multiplier controls the distance of the orders (ATR & StdDev types). For FIXED type, this value is the price gap between orders.', 'GRID')
local fixedStep = Input('4. Grid fixed step', 10, 'Grid fixed step defines the step size for the [FIXED] grid type.', 'GRID')
local atrInt = InputInterval('5. Grid timeframe', 15, 'Interval used for grid data. Doesn\'t affect FIXED type.', 'GRID')
local adxLen = Input('1. ADX period length', 20, 'Period length of ADX', 'SAFETY')
local adxLimit = Input('2. ADX shutdown limit', 0, 'If ADX rises above this value, the bot will shutdown and exit any position it has open, using market order. Use this setting to control what volatility levels this bot should trade in. Value ignored if set to zero.', 'SAFETY')
local adxInt = InputInterval('3. ADX timeframe', 15, 'Interval used for ADX data', 'SAFETY')
local stopLossOffset = Input('4. Stop-loss offset %', 0.25, 'Stop-loss is offsetted based on the last filled entry price, NOT the current average entry price of open position', 'SAFETY')
local timeoutLen = Input('5. Cooldown length (min)', 60, 'The length of cooldown (in minutes) after ADX-shutdown or stop-loss', 'SAFETY')
-- usefulness
local adxShutdownCount = Load('adxsc', 0)
local slHitCount = Load('slhc', 0)
local tpHitCount = Load('tphc', 0)
local orderHitCount = Load('ohc', 0)
local maxOrderHitCount = Load('mohc', 0)
local totalOrderHitCount = Load('tohc', 0)
local positionCount = Load('pc', 0)
local shutdownTypes = {
stoploss = 1,
adx = 2
}
if Load('init', true) then
Save('startBtc', WalletAmount(AccountGuid(), BaseCurrency()))
Save('init', false)
end
-- data
local ob = GetOrderbook()
local h0 = HighPrices(1)
local l0 = LowPrices(1)
local c0 = ClosePrices(1)
local h1 = HighPrices(atrInt)
local l1 = LowPrices(atrInt)
local c1 = ClosePrices(atrInt)
local h2 = HighPrices(adxInt)
local l2 = LowPrices(adxInt)
local c2 = ClosePrices(adxInt)
local cp = CurrentPrice()
local baseMa = TYPPRICE(h0, l0, c0)
local baseStd
local adx = ADX(h2, l2, c2, adxLen)
if gridType == gridTypes.atr then
baseStd = ATR(h1, l1, c1, atrLen)
elseif gridType == gridTypes.stddev then
baseStd = STDDEV(c1, atrLen, 1)
elseif gridType == gridTypes.fixed then
baseStd = fixedStep
end
-- plot
Plot(1, 'ADX', adx)
PlotHorizontalLine(1, 'ADX Limit', Red, adxLimit, Dashed)
Plot(2, 'Displacement', baseStd)
-- positions
local posId = Load('posId', NewGuid())
local position = PositionContainer(posId)
local posDD = Load('posDD', 0)
local posDDs = Load('posDDs', {})
local maxDD = Load('maxDD', 0)
posDD = ArrayGet(Min(posDD, position.roi), 1)
-- memory
local index = Load('index', 0)
local leid = Load('leid', '')
local seid = Load('seid', '')
local xeid = Load('xeid', '')
local timeout = Load('timeout', 0)
-- price adjustment helpers
function adjustBuyPrice(price)
if ob.bidPrices[1] <= price then
price = SubPerc(ob.bidPrices[1], 0.05)
end
return price
end
function adjustSellPrice(price)
if ob.askPrices[1] >= price then
price = AddPerc(ob.askPrices[1], 0.05)
end
return price
end
-- price helper
function getPrice(index, isLong)
return isLong
and adjustBuyPrice( baseMa - baseStd * Pow(baseMult, index) )
or adjustSellPrice( baseMa + baseStd * Pow(baseMult, index) )
end
function getExitPrice(isLong)
return isLong
and adjustSellPrice( AddPerc(position.enterPrice, takeProfit) )
or adjustBuyPrice( SubPerc(position.enterPrice, takeProfit) )
end
-- order amount helper
function getAmount(index, price)
return ParseTradeAmount(PriceMarket(), price, initialAmount * Pow(sizeMult, index))
end
-- position helper
function newPosition()
index = 0
xeid = ''
leid = ''
seid = ''
posId = NewGuid()
orderHitCount = 0
positionCount = positionCount + 1
end
-- stop-loss helper
-- loaded variables above fall into this function,
-- so there's no need for input parameters
function execStopLossExit(type)
if IsAnyOrderOpen() then
CancelAllOrders(posId)
elseif position.isLong or position.isShort then
LogWarning('Executing a hard exit!')
PlaceExitPositionOrder(posId, {type = MarketOrderType, note='SL'})
newPosition()
if type == shutdownTypes.adx then
adxShutdownCount = adxShutdownCount + 1
LogWarning('--- ADX cutoff')
elseif type == shutdownTypes.stoploss then
slHitCount = slHitCount + 1
LogWarning('--- SL cutoff')
end
end
if timeout < Time() then
timeout = Time() + timeoutLen * 60
end
end
-- order update helper
function updateOrders(isLong)
local mainId = isLong and leid or seid
local secId = isLong and seid or leid
if mainId != '' and secId != '' and IsOrderFilled(mainId) and IsOrderFilled(secId) then
newPosition()
LogWarning('Both orders got filled?')
end
if secId != '' and IsOrderOpen(secId) then
CancelOrder(secId)
secId = ''
end
local postfix = isLong and 'L' or 'S'
local entryCmd = isLong and PlaceGoLongOrder or PlaceGoShortOrder
if xeid != '' and IsOrderFilled(xeid) then
if mainId != '' and IsOrderOpen(mainId) then CancelOrder(mainId) end
newPosition()
tpHitCount = tpHitCount + 1
return
elseif mainId != '' and IsOrderOpen(mainId) == false then
if xeid != '' and IsOrderOpen(xeid) then
CancelOrder(xeid)
else
if IsOrderFilled(mainId) == true then
Save('lastEntryPrice', OrderContainer(mainId).price)
index = index + 1
orderHitCount = orderHitCount + 1
totalOrderHitCount = totalOrderHitCount + 1
if orderHitCount > maxOrderHitCount then
maxOrderHitCount = orderHitCount
end
end
local entryPrice = getPrice(index, isLong)
local entrySize = getAmount(index, entryPrice)
-- only add new orders if allowed
if index < orderCount then
mainId = entryCmd(
entryPrice,
entrySize,
{
type=MakerOrCancelOrderType,
note=postfix..(index+1),
timeout=604800,
positionId=posId
})
else
mainId = ''
end
end
end
-- update id's
leid = isLong and mainId or secId
seid = isLong and secId or mainId
-- update draw-down
posDD = ArrayGet(Min(posDD, position.roi), 1)
-- if all orders are in use, then we want to check for stop-loss
if index >= orderCount then
local lastEntryPrice = Load('lastEntryPrice', 0)
local slPrice = isLong
and SubPerc(lastEntryPrice, stopLossOffset)
or AddPerc(lastEntryPrice, stopLossOffset)
Plot(0, 'SL ('..stopLossOffset..'%)', slPrice, {c=Red, id=posId})
if (isLong and cp.bid < slPrice)
or (not isLong and cp.ask > slPrice)
then
execStopLossExit(shutdownTypes.stoploss)
end
end
-- placing exit order here to ensure it is where it has to be.
if (xeid == '' or (xeid != '' and IsOrderOpen(xeid) == false and IsOrderFilled(xeid) == false)) then
local exitPrice = getExitPrice(isLong)
xeid = PlaceExitPositionOrder({
positionId = posId,
price = exitPrice,
type=MakerOrCancelOrderType,
note=postfix..'X'..(index+1),
timeout=604800
})
end
end
-- da trading logic
local adxAllows = (adxLimit > 0 and adx <= adxLimit) or (adxLimit <= 0)
if adxAllows then
-- No position detected, place initial orders
if not (position.isLong or position.isShort) and timeout < Time() then
local longPrice = getPrice(index, true)
local shortPrice = getPrice(index, false)
local longSize = getAmount(index, longPrice)
local shortSize = getAmount(index, shortPrice)
if leid == '' or (leid != '' and IsOrderOpen(leid) == false) then
leid = PlaceGoLongOrder(longPrice, longSize, {type=MakerOrCancelOrderType, note='L'..(index+1), timeout=604800, positionId=posId})
end
if seid == '' or (seid != '' and IsOrderOpen(seid) == false) then
seid = PlaceGoShortOrder(shortPrice, shortSize, {type=MakerOrCancelOrderType, note='S'..(index+1), timeout=604800, positionId=posId})
end
elseif position.isLong then
updateOrders(true)
elseif position.isShort then
updateOrders(false)
end
else
-- execute forced exit
execStopLossExit(shutdownTypes.adx)
end
-- plot open orders for backtesting
if leid != '' and IsOrderOpen(leid) then
Plot(0, 'leid', OrderContainer(leid).price, {c=Green, id=posId})
end
if seid != '' and IsOrderOpen(seid) then
Plot(0, 'seid', OrderContainer(seid).price, {c=Red, id=posId})
end
if xeid != '' and IsOrderOpen(xeid) then
Plot(0, 'xeid', OrderContainer(xeid).price, {c=Purple, id=posId})
end
if position.amount > 0 or position.amount < 0 then
Plot(0, 'position', position.enterPrice, {c=Blue, id=posId})
end
-- update draw-downs
local lastPosId = Load('posId', '')
if #lastPosId > 0 and lastPosId != posId then
posDDs[#posDDs+1] = posDD
maxDD = ArrayGet(Min(maxDD, posDD), 1)
posDD = 0
end
-- save memory
Save('posId', posId)
Save('index', index)
Save('leid', leid)
Save('seid', seid)
Save('xeid', xeid)
Save('timeout', timeout)
Save('posDD', posDD)
Save('posDDs', posDDs)
Save('maxDD', maxDD)
Save('adxsc', adxShutdownCount)
Save('slhc', slHitCount)
Save('tphc', tpHitCount)
Save('ohc', orderHitCount)
Save('mohc', maxOrderHitCount)
Save('tohc', totalOrderHitCount)
Save('pc', positionCount)
-- finalize
Finalize(function()
local adxSlRatio = slHitCount > 0 and adxShutdownCount/slHitCount or 1
local tpSlRatio = slHitCount > 0 and tpHitCount/slHitCount or 1
local t = Round((timeout-Time())/60, 0)
local startBtc = Load('startBtc', 0)
local endBtc = WalletAmount(AccountGuid(), BaseCurrency())
CustomReport('Max. DrawDown', maxDD..' %', 'Performance')
CustomReport('Avg. DrawDown', Round(Average(posDDs), 4)..' %', 'Performance')
CustomReport('Last DrawDown', posDD..' %', 'Performance')
CustomReport('ADX Shutdowns', adxShutdownCount, 'Performance')
CustomReport('StopLoss hits', slHitCount, 'Performance')
CustomReport('TakeProfit hits', tpHitCount, 'Performance')
CustomReport('ADX-StopLoss ratio', slHitCount > 0 and adxSlRatio or 1, 'Performance')
CustomReport('TakeProfit-StopLoss ratio', slHitCount > 0 and tpSlRatio or 1, 'Performance')
CustomReport('Total positions', positionCount, 'Performance')
CustomReport('Total orders hit', totalOrderHitCount, 'Performance')
CustomReport('Max. orders hit/pos', maxOrderHitCount, 'Performance')
CustomReport('Avg. orders hit/pos', totalOrderHitCount/positionCount, 'Performance')
CustomReport('ADX', Round(adx,2)..'/'..Round(adxLimit,2), 'Safeties')
CustomReport('Timeout', t > 0 and (t..' minutes') or ('No active timeout'), 'Safeties')
CustomReport('Start '..BaseCurrency(), Round(startBtc,8), 'Wallet')
CustomReport('End '..BaseCurrency(), Round(endBtc,8), 'Wallet')
CustomReport('Delta', Round(Delta(startBtc, endBtc),4)..'%', 'Wallet')
CustomReport('Difference', Round(endBtc-startBtc,8)..' '..BaseCurrency(), 'Wallet')
end)
11 Comments
Sign in to leave a comment.
Hi, I started to test your script (simulated Kraken Future Accounts with 1 BTC) and I have encounter two questions:
- I am getting the following log messages: "WARNING: No maker template available for KrakenFutures. Falling back to normal limit order."
-- Q: Do I need to create the respective maker template?
- Although the bot is showing +26% , the overall wallet amount dropped to "0.99758157" (from 1.0).
Cheers
Andre
Hey @Busera.
1) With simulated accounts there are no Maker templates, so the warning is nothing to be worried about.
2) The bot reserves some assets for its orders (when it has open orders). You can add the "In Wallet" value with "In Order"/"In Use" value to get the real total (or simply stop the bot and cancel open orders).
@pshai Thx a lot for the prompt reply.
Hi @pshai,
I am running your script now exclusively for the Kraken Future Challenge, and it generates excellent results. I adjusted the settings based on 8W backtesting, which I update every week. I also use a more conservative setting to minimize the drawdown and not to maximize the return.
However, once in a while, the script gets stuck (Rather quite often), e.g., it sells only 99 of 100 contracts. In this case, I have to sell the remaining contracts manually, need to clear the statistics and restart the bot.
Are you facing the same problem?
Update: Today, during the BTC pump, all my gains were killed because the stop loss was not triggered. I am not sure why the script stops working.
Hey Busera,
Are you able to send me more information @ Haasonline Discord server? Anything you can provide regarding this will be very helpful!
Join the discord server by following this link: https://discord.gg/VxKxgQ
Hi Pshai,
I've got these errors while trying the script
5. 05 May 2020 23:54:14 ERROR: Unknown references: adjustSellPrice
4. 05 May 2020 23:54:14 ERROR: Unknown references: adjustSellPrice
can you provide your support please ?
Hey @BlockHash the script is now fixed. There was a bug on the site which broke the script... It is all now fixed! :)
Hi Pshai,
I'm using this bot with Bitmex/XBTUSD and I have a few questions:
- in the settings I set "order size" to 15000 and multiplier to 1, thus I'd expect the bot to always open positions of size 15000. Instead right now
I see the bot has opened a position of 195000 usd... looking at the position on Bitmex it is saying that margin is "0,0386 XBT (Cross)", so if I understand
corretly this means that the bot opened 195000 contracts with 100x leverage, because current BTC value was 53096 usd, so the position value in BTC
is 3.61 BTC and with a 100x leverage and including commissions this corresponds to 0,0386 magin. So my question is, why did the bot opened
a position of 195000 usd with 100x leverage?
- in the safety settings under stop loss I set 0,3%. So I'd expect the bot to close positions when the value of BTC has moved 0.3% away from the starting
value in the opposite direction of the position that the bot opened. But i don't see a counter order for this being placed by the bot.
I'd like to show you the current situation of the position and orders opened by the bot, and I'd like you to explain me if it is all correct:
current position:
XBTUSD
size: -195.000 USD
value: 3,6117 XBT
entry price: 53096.31
mark price: 53990.64 57114.0
margin 0,0386 XBT (Cross)
unrealised PNL -0,0608 XBT (-165.65%)
active orders:
order #1:
XBTUSD
size 195000 USD
order price 52970.5
filled ---.--
remaining 195000 USD
order value 3,6812 XBT
type Limit
order #2:
XBTUSD
size -15000 USD
order price 55860.5
filled: ---.--
remaining 15000 USD
order value 0,2685 XBT
type Limit
So the first order seem to me the take profit order for the position that was opened... in fact i can see the limit is set at 0.3% below the entry value of the
position (I'm using a take profit setting of 0.3%). But given that there is 100x in the position then I'd expect the value of this order to be 1% in value of the
position size... so about 1950 usd instead of 195000 usd, or am i wrong?
The second order I don't understand it... does this have to do with the stop loss? Can you explain me why it is 15000 usd ?
thanks
If the order size is set t o 15000 contracts and multiplier to 1, the orders will then be 15000 each time. If your order count is 3, your max position size will be 45000 after the 3rd order. After this, the bot will either hit its target or trigger the stoploss order. In other words, the bot does not care about your leverage setting, so the order size used is 1:1 the value you have set in the bot.
I really like this script!
Ive been playing around with it a bit,
Im trying to modify it so it can be short-only (or long-only ) by commenting out lines 278-280 for example...
It works for a few trades but then the price stops adjusting
Anyone want to suggest a tip as to how I would achieve this?
Thanks
Figured it out :)
just changed the timeout