[pshaiBot] Simple Grid Bot (SPOT)
betaDescription
Hey YOU! Having trouble setting up FlashCrash bot? Don't worry! The solution is here!
Simple Grid Bot makes grid trading simple AF. All you need to do, is set your price range, amount of grids and the amount of your total investment and the bot will take care of the rest.
SGB also contains start and stop conditions which you can use to limit and preserve your gains and investments.
Settings
Price Range
Lower Limit - Lower price limit.
Upper Limit - Upper price limit.
Grid mode & quantity
Mode - Grid mode. Arithmetic grid mode will place all grids evenly spaced based on a constant price difference between grids. Geometric grid mode will place all grids evenly spaced based on a constant percentage difference between grids. (not yet fully functioning)
Quantity - The amount of grids the price range is split into.
Investment amount
Asset - Select the asset type in which the investment amount is based on. Example: on BTC/USDT market the Base option would point to BTC, and Quote option would point to USDT.
Amount - Total amount to be invested into the grid range. This amount is split among grids.
Advanced Settings
Trade to fill grids? - If true, bot will automatically buy or sell assets to fill both buy and sell grids.
Start condition - Instant condition will start the bot immediately. Price condition activates bot once price is above or below set start value. RSI condition will activate bot once RSI is above or below set start value. (NOTE: RSI uses the main interval setting and has 14 period length)
Start type - Activation on above or below start value.
Start value - The value for Price and RSI start conditions.
Stop condition - Manual condition never deactivate the bot. Price condition deactivates bot once price is above or below set stop value. RSI condition will deactivate bot once RSI is above or below set stop value. (NOTE: RSI uses the main interval setting and has 14 period length)
Stop type - Deactivation on above or below stop value.
Stop value - The value for Price and RSI stop conditions.
Stop exits - If true, bot will sell all bought assets when using [Below value] stop condition, or buy back when using [Above value] stop condition.
NOTE: Bot is marked to be in beta test simply because the Geometric grid mode doesn't yet function as it should.
HaasScript
-- [HaasOnline] Simple Grid Bot v1.2
-- Author: pshai
HideOrderSettings()
HideTradeAmountSettings()
EnableHighSpeedUpdates(true)
InputGroupHeader('Price Range')
local pr_lower = Input('Lower Limit', 0, 'Lower price limit.')
local pr_upper = Input('Upper Limit', 0, 'Upper price limit.')
InputGroupHeader('Grid mode & quantity')
local mode = InputOptions('Mode', 'Arithmetic', {'Arithmetic', 'Geometric'}, 'Grid mode. Arithmetic grid mode will place all grids evenly spaced based on a constant price difference between grids. Geometric grid mode will place all grids evenly spaced based on a constant percentage difference between grids.')
local grids = Input('Quantity', 0, 'The amount of grids the price range is split into.')
InputGroupHeader('Investment amount')
local asset = InputOptions('Asset', 'Base', {'Base', 'Quote'}, 'Select the asset type in which the investment amount is based on. Example: on BTC/USDT market the Base option would point to BTC, and Quote option would point to USDT.')
local amount = Input('Amount', 0, 'Total amount to be invested into the grid range. This amount is split among grids.')
InputGroupHeader('Advanced Settings')
local init_wallet = Input('Trade to fill grids?', false, 'If true, bot will automatically buy or sell assets to fill both buy and sell grids.')
local start_cond = InputOptions('Start condition', 'Instant', {'Instant', 'Price', 'RSI'}, 'Instant condition will start the bot immediately. Price condition activates bot once price is above or below set start value. RSI condition will activate bot once RSI is above or below set start value. (NOTE: RSI uses the main interval setting and has 14 period length)')
local start_type = InputOptions('Start type', 'Above value', {'Above value', 'Below value'}, 'Activation on above or below start value.')
local start_value = Input('Start value', 0, 'The value for Price and RSI start conditions.')
local stop_cond = InputOptions('Stop condition', 'Manual', {'Manual', 'Price', 'RSI'}, 'Manual condition never deactivate the bot. Price condition deactivates bot once price is above or below set stop value. RSI condition will deactivate bot once RSI is above or below set stop value. (NOTE: RSI uses the main interval setting and has 14 period length)')
local stop_type = InputOptions('Stop type', 'Above value', {'Above value', 'Below value'}, 'Deactivation on above or below stop value.')
local stop_value = Input('Stop value', 0, 'The value for Price and RSI stop conditions.')
local stop_exit = Input('Stop exits', false, 'If true, bot will sell all bought assets when using [Below value] stop condition, or buy back when using [Above value] stop condition.')
-- check settings
if pr_lower <= 0 then
DeactivateBot('Lower limit not set.')
end
if pr_upper <= 0 then
DeactivateBot('Upper limit not set.')
end
if grids <= 2 then
DeactivateBot('Grid quantity not set (must be 2 or more).')
end
if amount == 0 then
DeactivateBot('Investment amount not set.')
end
---------------
function createGridItem(level, amt)
return {
p = level,
amt = amt,
used = false,
isMid = false,
isBuy = false,
pid = NewGuid(),
oid = ''
}
end
function createGrid()
local cp = CurrentPrice()
local cur_pr = cp.ask
local pr_interval = grids > 0 and (pr_upper - pr_lower) / grids or 0
local min_pr_interval = MakersFee() * cur_pr * 0.02
local slot_size = amount / grids
local wallet = {
base = Balance({coin = BaseCurrency()}).available,
quote = Balance({coin = QuoteCurrency()}).available,
}
local grid = {}
-- with geometric grid mode, calculate the % based interval instead
if mode == 'Geometric' then
pr_interval = grids > 0 and (pr_upper / pr_lower - 1) / grids * 100 or 0
min_pr_interval = MakersFee() * 2
end
Log('Grid interval: ' .. pr_interval)
Log('Breakeven interval: ' .. min_pr_interval)
if pr_interval <= min_pr_interval then
DeactivateBot('The grid will not be able to produce profits; grid interval is too small (increase total range or decrease grid quantity).')
end
-- build grid and find the middle
local mid_found = false
local closest = nil
local closest_dist = NumberMax
for i = 1, grids+1 do
local level = 0
if mode == 'Arithmetic' then
level = pr_lower + pr_interval * (i - 1)
else
if i > 1 then
level = AddPerc(grid[i-1].p, pr_interval)
else
level = pr_lower
end
end
local dist = Abs(cur_pr - level)
local amt = (asset == 'Base' and slot_size or slot_size / level)
grid[i] = createGridItem(level, amt) --{p = level, isMid = false}
if dist < closest_dist
then
if closest then
closest.isMid = false
end
closest_dist = dist
closest = grid[i]
closest.isMid = true
mid_found = true
end
end
-- second pass to set buy/sell types
min_profit, max_profit = NumberMax, NumberMin
local total_buy, total_sell = 0, 0
local prev_level = 0
for i = 1, grids+1 do
if grid[i].p <= closest.p then
grid[i].isBuy = true
total_buy = total_buy + (grid[i].amt * grid[i].p)
else
total_sell = total_sell + grid[i].amt
end
if prev_level > 0 then
local p = Abs(prev_level / grid[i].p - 1)
if p < min_profit then min_profit = p end
if p > max_profit then max_profit = p end
end
prev_level = grid[i].p
end
Log('total sell: ' .. total_sell)
Log('wallet base: ' .. wallet.base)
Log('total buy: ' .. total_buy)
Log('wallet quote: ' .. wallet.quote)
-- TODO: check that wallet amounts are sufficient to fill whole grid range
if init_wallet then
if (total_buy > 0 and total_buy > wallet.quote)
and (total_sell > 0 and total_sell > wallet.base) then
-- both sides are fucked
LogError('Not enough assets to fill buy and sell grids.')
return
elseif (total_sell > 0 and wallet.base < total_sell) then
-- buy coins to make up for sell side
local diff = (total_sell - wallet.base) * (1.0 + TakersFee()*0.01) + AmountStep()
if diff > 0 then
if (wallet.quote - total_buy) / cp.ask < diff then
LogWarning('Need to buy '..diff..' '..BaseCurrency()..' to fill sell grid.')
DeactivateBot('Not enough '..QuoteCurrency()..' to buy '..BaseCurrency()..'.')
return
end
LogWarning('Buying '..diff..' '..BaseCurrency()..' to fill sell-side grids.')
local pid = CreatePosition(PositionSold, cp.ask, diff, {positionId = 'Initial Buy'})
PlaceBuyOrder(1, diff, {type = MarketOrderType, positionId = 'Initial Buy'})
end
elseif (total_buy > 0 and wallet.quote < total_buy) then
-- sell coins to make up for buy side
local q = (total_buy - wallet.quote)
local diff = q / cp.bid * (1.0 + TakersFee()*0.01) + AmountStep()
if diff > 0 then
if wallet.base - total_sell < diff then
LogWarning('Need to sell '..diff..' '..BaseCurrency()..' (or approx. '..q..' '..QuoteCurrency()..' in wallet) to fill buy grid.')
DeactivateBot('Not enough '..BaseCurrency()..' to sell for '..QuoteCurrency()..'.')
return
end
LogWarning('Selling '..diff..' '..BaseCurrency()..' to fill buy-side grids.')
local pid = CreatePosition(PositionBought, cp.bid, diff, {positionId = 'Initial Sell'})
PlaceSellOrder(1, diff, {type = MarketOrderType, positionId = 'Initial Sell'})
end
end
end
--DeactivateBot()
return grid
end
function drawGrid()
local cp = CurrentPrice()
for i = 1, #g_grids do
local grid = g_grids[i]
local c = Green
local pos = PositionContainer(grid.pid)
if grid.oid != '' then
local o = OrderContainer(grid.oid)
if grid.isBuy then
if pos.amount == 0 then
Plot(0, i..'-Buy', o.price, {c = Green, id = grid.oid})
else
Plot(0, i..'-Sell', o.price, {c = Yellow, id = grid.oid})
end
else
if pos.amount == 0 then
Plot(0, i..'-Sell', o.price, {c = Red, id = grid.oid})
else
Plot(0, i..'-Buy', o.price, {c = Yellow, id = grid.oid})
end
end
end
end
end
function updateGrid()
local sell = PlaceSellOrder
local buy = PlaceBuyOrder
for i = 1, #g_grids do
local grid = g_grids[i]
local pos = PositionContainer(grid.pid)
local cmd = grid.isBuy and buy or sell
--local cmd2 = grid.isBuy and sell or buy
if grid.oid != '' then
local o = OrderContainer(grid.oid)
if not o.isOpen then
grid.oid = ''
if grid.used and IsPositionClosed(grid.pid) then
grid.pid = NewGuid()
end
end
end
if not grid.isMid then
if pos.amount == 0 then
if grid.oid == '' then
grid.oid = cmd(grid.p, grid.amt, {positionId = grid.pid, timeout = -1, type = LimitOrderType})
end
else
if grid.oid == '' then
local p = grid.isBuy and g_grids[i+1].p or g_grids[i-1].p
grid.oid = PlaceExitPositionOrder(grid.pid, p, LimitOrderType, '', -1)
grid.used = true
end
end
end
end
end
function exitGrid(selling)
local cp = CurrentPrice()
local total_amt = 0
for i = 1, #g_grids do
local grid = g_grids[i]
local pos = PositionContainer(grid.pid)
if pos.amount > 0 then
total_amt = total_amt + pos.amount
CloseVPosition(cp.close, grid.pid)
end
end
if total_amt == 0 then
return
end
local pid
if selling then
pid = CreatePosition(PositionBought, cp.bid, total_amt, {positionId = 'Safety Sell'})
else
pid = CreatePosition(PositionSold, cp.ask, total_amt, {positionId = 'Safety Buy'})
end
PlaceExitPositionOrder(pid, {type = MarketOrderType, note = pid})
end
if not init then
active = Load('active', false)
deactivated = Load('deactivated', false)
min_profit = 0
max_profit = 0
init = Load('init', true)
end
function backupGlobals()
Save('active', active)
Save('deactivated', deactivated)
Save('init', init)
end
if not active then
if not deactivated then
local cp = CurrentPrice()
if start_cond == 'Price' then
-- check price and activate
if (start_type == 'Above value' and cp.high > start_value)
or (start_type == 'Below value' and cp.low < start_value)
then
active = true
end
elseif start_cond == 'RSI' then
-- check RSI and activate
local rsi = RSI(ClosePrices(), 14)
if (start_type == 'Above value' and rsi > start_value)
or (start_type == 'Below value' and rsi < start_value)
then
active = true
end
else
active = true -- activate instantly
end
else
-- check if any orders are open and cancel
if IsAnyOrderOpen() then
CancelAllOrders()
elseif stop_exit then
exitGrid(stop_type == 'Below value')
end
end
else
if not g_grids then
g_grids = createGrid()
end
updateGrid()
drawGrid()
local cp = CurrentPrice()
if stop_cond == 'Price' then
-- check price and deactivate
if (stop_type == 'Above value' and cp.high > stop_value)
or (stop_type == 'Below value' and cp.low < stop_value)
then
active = false
deactivated = true
LogWarning('------------------------------------------------------------------------')
LogWarning('-- Bot deactivated by PRICE: please stop and clean bot before a new run.')
LogWarning('------------------------------------------------------------------------')
end
elseif stop_cond == 'RSI' then
-- check RSI and deactivate
local rsi = RSI(ClosePrices(), 14)
if (stop_type == 'Above value' and rsi > stop_value)
or (stop_type == 'Below value' and rsi < stop_value)
then
active = false
deactivated = true
LogWarning('------------------------------------------------------------------------')
LogWarning('-- Bot deactivated by RSI: please stop and clean bot before a new run.')
LogWarning('------------------------------------------------------------------------')
end
end
CustomReport('Potential profit/grid', Round(min_profit*100, 2) .. ' - ' .. Round(max_profit*100, 2) .. ' %')
end
------------------------------------------
backupGlobals()
1 Comment
Sign in to leave a comment.
Nice! Thanks for sharing!