[pshaiBot] Bot Blueprint (ALPHA TEST)
alphaDescription
Here is my take on bot blueprint!!
It has quite a lot of inner works and I intend on adding more later on. Unfortunately I cannot upload screenshots of the bot... Will add them later!
Read the full script file carefully to understand what happens and why, I have written lots of comments to help with that.
Features:
- Ease-of-Use - All trade commands have default values so no matter what skill-level you are, you can easily start using this blueprint! If you want to skip/use default input values of any trading commands, simply give it a nil as input.
- Customizable "events" - Whether an order is open, filled, partially filled of cancelled, there is a built-in "even" function that fires accordingly. These functions are empty at the moment and be customized to update custom stats or to run any other actions you need.
- Chart Plots - Depending if you are running a backtest in the editor or a live bot, the plots done by bot are not forgotten; they are transformed into log messages when running a live bot and plotted normally in editor backtests!
- "You cannot exit more than you have" - The exit functions will check your position size before putting through the exit orders. Consider it as "automatic reduce-only".
- Built-in inputs
- Wallet checks
- TP and SL systems
- Proper custom reports and useful stats
-- init/load our 'testBot'
local testBot = bot('testBot')
-- prices and simple moving average with plotting
local c = ClosePrices()
local sma = SMA(c, 50)
testBot.PlotLine(0, 'sma', sma)
-- some simple strategy to control how bot trades
if CrossOver(c, sma) then
-- enable long and disable short entries
testBot.EnableLongs()
testBot.DisableShorts()
-- exit open short position
testBot.ExitShort()
elseif CrossUnder(c, sma) then
-- enable short and disable long entries
testBot.DisableLongs()
testBot.EnableShorts()
-- exit open long position
testBot.ExitLong()
end
-- run the inner works of the bot
testBot.Run()
-- just endlessly entry longs and shorts when allowed
testBot.GoLong()
testBot.GoShort()
-- save bot info for next update
testBot.SaveBot()
Another example:
-- init/load our 'testBot'
local testBot = bot('testBot')
-- prices and simple moving average with plotting
local c = ClosePrices()
local sma = SMA(c, 50)
testBot.PlotLine(0, 'sma', sma)
-- some simple strategy to control how bot trades
if CrossOver(c, sma) then
-- entry long
testBot.GoLong()
-- exit open short position
testBot.ExitShort()
elseif CrossUnder(c, sma) then
-- entry short
testBot.GoShort()
-- exit open long position
testBot.ExitLong()
end
-- run the inner works of the bot
testBot.Run()
-- save bot info for next update
testBot.SaveBot()
Inspired by romdisc's Blueprint of Trading Bot.
HaasScript
-- [pshaiBot] Bot Blueprint
-- Author: pshai
---- if you are using custom order type and trade amount settings,
---- uncomment these accordingly
--HideOrderSettings()
--HideTradeAmountSettings()
EnableHighSpeedUpdates(true) ----- DO NOT REMOVE!! -----
-------------------------------------------------------------------------------
-- the beast itself
function bot(name)
-------------------------------------------------------------------------------
-- private variables
local can_run = Load(name..':cr', false)
local init = Load(name..':init', true)
local long_pid = Load(name..':lpid', NewGuid())
local short_pid = Load(name..':spid', NewGuid())
local long_pos = PositionContainer(long_pid)
local short_pos = PositionContainer(short_pid)
local long_eids = Load(name..':leids', {}) -- long entry order ids
local short_eids = Load(name..':seids', {}) -- short entry order ids
local long_xids = Load(name..':lxids', {}) -- long exit order ids
local short_xids = Load(name..':sxids', {}) -- short exit order ids
local allow_longs = Load(name..':al', false)
local allow_shorts = Load(name..':as', false)
local isBT = Load(name..':ibt', false)
local plot_msgs = {} -- when we are live, we log plot info
local log = |msg, color| Log('['..name..']: ' .. msg, color or '')
local cp = CurrentPrice()
local mktType = MarketType()
local goLong_cmd = mktType == SpotTrading and PlaceBuyOrder or PlaceGoLongOrder
local goShort_cmd = mktType == SpotTrading and PlaceSellOrder or PlaceGoShortOrder
local exLong_cmd = mktType == SpotTrading and PlaceSellOrder or PlaceExitLongOrder
local exShort_cmd = mktType == SpotTrading and PlaceBuyOrder or PlaceExitShortOrder
-------------------------------------------------------------------------------
-- single/multi order mode. if enabled, we only allow 1 open order at a time,
-- otherwise we use max_open_orders (see below)
local single_order_mode = true
-------------------------------------------------------------------------------
-- max open entries/exits allowed (entries and exits do not block each other!)
-- change this to your liking
local max_open_orders = 3
-------------------------------------------------------------------------------
-- short-hand function for: if v then return v else return d end
local def = |v, d| v or d
-------------------------------------------------------------------------------
-- colors used for logging
local color = {
info = DarkGray,
blocked = Purple,
warn = Yellow,
fail = Red,
success = Green
}
-------------------------------------------------------------------------------
-- customizable functions
-------------------------------------------------------------------------------
-- initialization "event"
function onInit()
-- do initialization. runs only once when bot starts
end
-------------------------------------------------------------------------------
-- open order "event"
function onOpenOrder(order)
-- do smth when order is open.
-- [order] input is an OrderContainer object
end
-------------------------------------------------------------------------------
-- filled order "event"
function onFilledOrder(order)
-- do smth when order is filled.
-- [order] input is an OrderContainer object
log('Order was filled!', color.success)
end
-------------------------------------------------------------------------------
-- partially filled order "event"
function onPartiallyFilledOrder(order, isOpen)
-- do smth when order is partially filled.
-- [order] input is an OrderContainer object
local extra = isOpen and ' (order is open)' or ' (order is not open)'
log('Order was partially filled.' .. extra, color.info)
end
-------------------------------------------------------------------------------
-- cancelled/failed order "event"
function onFailedOrder(order)
-- do smth when order is failed/cancelled.
-- [order] input is an OrderContainer object
log('Order was cancelled!', color.warn)
end
-------------------------------------------------------------------------------
-- built-in private functions, getters and setters
-------------------------------------------------------------------------------
-- empty function to mask all output functions of bot unless we can run
function empty_function() end
-------------------------------------------------------------------------------
-- check if we are in backtest or not
-- will not work properly if high-speed mode is not used!
function check_is_bt()
-- dont check if we can already run
if can_run then
return
end
local t = Time() -- current time
local rt = t - (t % 60) -- get fraction
local dt = t - rt -- delta time
local dc = Load(name..':btdc', 0) -- delta count
local c = Load(name..':btc', 0) -- run count
if c < 3 then
if c > 0 and dt > 0 then
can_run = true
end
dc = dc + dt
c = c + 1
else
if dc == 0 then
isBT = true
end
can_run = true
end
if can_run then
log('(this is ' .. (not isBT and 'not' or '') .. ' a backtest...)', color.info)
end
Save(name..':btdc', dc)
Save(name..':btc', c)
CustomReport('Is backtest', (isBT and 'true' or 'false'))
end
-------------------------------------------------------------------------------
-- the juice that keeps this thing going
function saveBot()
if can_run then
Save(name..':init', false)
end
Save(name..':ibt', isBT)
Save(name..':cr', can_run)
Save(name..':lpid', long_pid)
Save(name..':spid', short_pid)
Save(name..':leids', long_eids) -- long entry order ids
Save(name..':seids', short_eids) -- short entry order ids
Save(name..':lxids', long_xids) -- long exit order ids
Save(name..':sxids', short_xids) -- short exit order ids
Save(name..':al', allow_longs)
Save(name..':as', allow_shorts)
end
-------------------------------------------------------------------------------
-- enable/disable longs/shorts
function enableLongs()
allow_longs = true
end
function disableLongs()
allow_longs = false
end
function enableShorts()
allow_shorts = true
end
function disableShorts()
allow_shorts = false
end
-------------------------------------------------------------------------------
-- when multi-order mode, get index of an empty slot
function getNewOID(list)
local count = Count(list)
for i = 1, max_open_orders do
if not list[i] or list[i] == '' then
return i
end
end
return -1
end
-------------------------------------------------------------------------------
-- update a single order by id
function updateOrder(id)
if id != '' then
local o = OrderContainer(id)
if o.isOpen then
onOpenOrder(o)
if o.executedAmount - o.filledAmount < o.executedAmount then
onPartiallyFilledOrder(o, true)
end
else
if o.executedAmount > o.filledAmount then
onPartiallyFilledOrder(o, false)
elseif o.isFilled then
onFilledOrder(o)
elseif o.isCancelled then
onFailedOrder(o)
end
id = '' -- id is reset
end
end
return id
end
-------------------------------------------------------------------------------
-- update order list
function updateOrderList(list)
local count = Count(list)
for i = 1, count do
local id = list[i] -- get id
-- only update ids that contain an order
if id != '' then
id = updateOrder(id) -- update order
list[i] = id -- update id
end
end
return list
end
-------------------------------------------------------------------------------
-- plot command. adds msg into queue if not a backtest
function plot(id, name, value, options)
-- if we doing backtest, plot the line
if isBT then
Plot(id, name, value, options)
return
end
-- if we live, queue a message of the plot info
addPlotMsg(id, name, value, options)
end
-------------------------------------------------------------------------------
-- add plot as a message in queue
function addPlotMsg(id, name, value, options)
options = def(options, SkyBlue)
local parsed_options = Parse(options, StringType)
local color
if Count(parsed_options) > 1 then
color = parsed_options[1]
else
color = parsed_options
end
local len = Count(plot_msgs)
local index = len + 1
plot_msgs[index] = {
msg = 'chartId: ' .. id .. ', ' ..
'name: ' .. name .. ', ' ..
'value: ' .. value,
color = color
}
end
-------------------------------------------------------------------------------
-- log chart plots if any and if not a backtest
function plotMsgs()
-- skip if backtest
if isBT then
return
end
-- count of messages in queue
local count = Count(plot_msgs)
-- skip if not any messages
if count == 0 then
return
end
-- log messages
log('---------------------', color.info)
for i = 1, count do
local item = plot_msgs[i]
log(item.msg, item.color)
end
log('-- Plots as Logs -------------------', color.info)
end
-------------------------------------------------------------------------------
-- long entry
function goLong(price, amount, type, note, timeout)
if not can_run then
return
end
if not allow_longs then
log('Not allowed to entry long. (bot.allow_longs = false)', color.blocked)
return
end
local oid = getNewOID(long_eids)
if not single_order_mode and oid == -1 then
log('Maximum amount of open long entries already in use, skipping entry.', color.fail)
return
end
price = def(price, cp.bid)
amount = def(amount, TradeAmount())
type = def(type, GetOrderType())
note = def(note, 'Long Entry')
timeout = def(timeout, 600)
if single_order_mode then
if not long_eids[1] or long_eids[1] == '' then
long_eids[1] = goLong_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = long_pid})
log('Long entry placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
else
log('Long entry order already open, skipping entry...', color.info)
end
else
long_eids[oid] = goLong_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = long_pid})
log('Long entry placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
end
end
-------------------------------------------------------------------------------
-- long exit
function exitLong(price, amount, type, note, timeout)
if not can_run then
return
end
-- get slot index for empty slot, if any (-1 if not any)
local oid = getNewOID(long_xids)
if not single_order_mode and oid == -1 then
log('Maximum amount of open long exits already in use, skipping exit.', color.fail)
return
end
price = def(price, cp.ask)
amount = def(amount, long_pos.amount) -- defaults to full position size
type = def(type, GetOrderType())
note = def(note, 'Long Exit')
timeout = def(timeout, 600)
-- dont allow exiting more than we have
if amount > long_pos.amount then
log('Long exit amount set to higher than position size, reducing to position size.', color.info)
amount = long_pos.amount
end
if single_order_mode then
if not long_xids[1] or long_xids[1] == '' then
long_xids[1] = exLong_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = long_pid})
log('Long exit placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
else
log('Long exit order already open, skipping exit...', color.info)
end
else
long_xids[oid] = exLong_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = long_pid})
log('Long exit placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
end
end
-------------------------------------------------------------------------------
-- short entry
function goShort(price, amount, type, note, timeout)
if not allow_shorts then
log('Not allowed to entry short. (bot.allow_shorts = false)', color.blocked)
return
end
local oid = getNewOID(short_eids)
if not single_order_mode and oid == -1 then
log('Maximum amount of open short entries already in use, skipping entry.', color.fail)
return
end
price = def(price, cp.ask)
amount = def(amount, TradeAmount())
type = def(type, GetOrderType())
note = def(note, 'Short Entry')
timeout = def(timeout, 600)
if single_order_mode then
if not short_eids[1] or short_eids[1] == '' then
short_eids[1] = goShort_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = short_pid})
log('Short entry placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
else
log('Short entry order already open, skipping entry....', color.info)
end
else
short_eids[oid] = goShort_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = short_pid})
log('Short entry placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
end
end
-------------------------------------------------------------------------------
-- short exit
function exitShort(price, amount, type, note, timeout)
if not can_run then
return
end
-- get slot index for empty slot, if any (-1 if not any)
local oid = getNewOID(short_xids)
if not single_order_mode and oid == -1 then
log('Maximum amount of open short exits already in use, skipping exit.', color.fail)
return
end
price = def(price, cp.ask)
amount = def(amount, short_pos.amount) -- defaults to full position size
type = def(type, GetOrderType())
note = def(note, 'Short Exit')
timeout = def(timeout, 600)
-- dont allow exiting more than we have
if amount > short_pos.amount then
log('Short exit amount set to higher than position size, reducing to position size.', color.info)
amount = short_pos.amount
end
if single_order_mode then
if not short_xids[1] or short_xids[1] == '' then
short_xids[1] = exShort_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = short_pid})
log('Short exit placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
else
log('Short exit order already open, skipping exit...', color.info)
end
else
short_xids[oid] = exShort_cmd(price, amount, {timeout = timeout, type = type, note = note, positionId = short_pid})
log('Short exit placed: price: ' .. price .. ', amount: ' .. amount .. ', note: "'..note..'".', color.info)
end
end
-------------------------------------------------------------------------------
-- main run function
function run()
if not can_run then
return
end
-- update order lists
long_eids = updateOrderList(long_eids)
short_eids = updateOrderList(short_eids)
-- log plot messages
plotMsgs()
end
-- check if we are in backtest or not
check_is_bt()
-- run initialization
if can_run and init then
log('Startup complete!', color.success)
-- fire custom init function
onInit()
elseif not can_run then
log('Bot is starting, please wait...', color.info)
end
-------------------------------------------------------------------------------
-- output functions and stuff to public.
-- if we are starting up (checking if we are in backtest), we
-- output empty functions so the bot logic can still run errorless,
-- but doesnt really do anything. once we are running, we output
-- the correct functions and bot will start working.
-- NOTE that saveBot gets out no matter what. this is to make
-- sure everything still gets saved, even though it doesnt really
-- matter... idk.
return {
EnableLongs = can_run and enableLongs or empty_function,
EnableShorts = can_run and enableShorts or empty_function,
DisableLongs = can_run and disableLongs or empty_function,
DisableShorts = can_run and disableShorts or empty_function,
GoLong = can_run and goLong or empty_function,
GoShort = can_run and goShort or empty_function,
ExitLong = can_run and exitLong or empty_function,
ExitShort = can_run and exitShort or empty_function,
Run = can_run and run or empty_function,
PlotLine = can_run and plot or empty_function,
SaveBot = saveBot
}
end
--[[ Example #1
-- init/load our 'testBot'
local testBot = bot('testBot')
-- prices and simple moving average with plotting
local c = ClosePrices()
local sma = SMA(c, 50)
testBot.PlotLine(0, 'sma', sma)
-- some simple strategy to control how bot trades
if CrossOver(c, sma) then
-- enable long and disable short entries
testBot.EnableLongs()
testBot.DisableShorts()
-- exit open short position
testBot.ExitShort()
elseif CrossUnder(c, sma) then
-- enable short and disable long entries
testBot.DisableLongs()
testBot.EnableShorts()
-- exit open long position
testBot.ExitLong()
end
-- run the inner works of the bot
testBot.Run()
-- just endlessly entry longs and shorts when allowed
testBot.GoLong()
testBot.GoShort()
-- save bot info for next update
testBot.SaveBot()
]]
---------------------------------------------------------------
-- Example #2
-- init/load our 'testBot'
local testBot = bot('testBot')
-- prices and simple moving average with plotting
local c = ClosePrices()
local sma = SMA(c, 50)
testBot.PlotLine(0, 'sma', sma)
testBot.EnableLongs()
testBot.EnableShorts()
-- some simple strategy to control how bot trades
if CrossOver(c, sma) then
-- entry long
testBot.GoLong()
-- exit open short position
testBot.ExitShort()
elseif CrossUnder(c, sma) then
-- entry short
testBot.GoShort()
-- exit open long position
testBot.ExitLong()
end
-- run the inner works of the bot
testBot.Run()
-- save bot info for next update
testBot.SaveBot()
1 Comment
Sign in to leave a comment.
Nice work on this one