Documented with Chat.gpt the Hassbot Order bot By pshai
stableDescription
Put in comments to further understand how an order bot works.
Learning Lua code...
HaasScript
-- commemts : Sbyte
-- Description: HaasOnline Order Bot
-- Author: pshai
--[[
Input table order parameters:
Order directions:
For spot markets,
a buy order is set with a Direction value of +
a sell order is set with a Direction value of -
For leverage markets,
go long order is set with a Direction value of L+ and exit long with L-
go short order is set with a Direction value of S+ and exit short with S-
Trade amount is set in BASE value, which means, that if you are
trading BTC/USDT, the amount is then set as BTC.
Trigger Types:
< means "Lower Than" trigger price
> means "Higher Than" trigger price
Stop-Loss example (see default settings):
- "buy" order is set to trigger at price 62000.
- "sl" order is set to trigger when price "Less Than" 61800,
but is allowed to be monitored only After "buy" order
and Before "sell" order. "sl" is also set to be a market order.
- "sell" order is set to trigger at price 63000, but only After "buy"
order has completed.
All parameters with * are optional. Leave them empty when not used.
]]
-- Input table for orders
local orderTable = InputTable(
InputTableOptions('Orders'),
InputTableColumn('ID', 'buy', 'sl', 'sell'),
InputTableColumn('Market Order', false, true, false),
InputTableColumn('Direction', '+', '-', '-'),
InputTableColumn('Target Price', 62000, 61800, 63000),
InputTableColumn('Amount', 0.002, 0.002, 0.002),
InputTableColumn('Before *', '', 'sell', ''),
InputTableColumn('After *', '', 'buy', 'buy'),
InputTableColumn('Trigger Type *', '', '<', ''),
InputTableColumn('Trigger Price *', '', 61800, '')
)
-- Debug mode flag
local isDebug = Input('Debug Mode', false)
-- Function to log debug messages
function debuglog(msg, color)
if not isDebug then return end
Log('[DEBUG] ' .. msg, color or '')
end
-- Enable high-speed updates and hide order settings
EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()
-- ===============================================================
-- Config object
-- Provides methods to determine market type
local Config = {}
function Config:isSpot()
return MarketType() == SpotTrading
end
-- ===============================================================
-- Positions
-- Handles loading, updating, and saving of positions
local PosMan = {}
--PosMan:load():
-- This function is responsible for loading positions.
-- It initializes the long_pid and short_pid properties by loading their values from storage using the
-- Load function, which retrieves data associated with a given key. If no data is found, it generates new
-- unique identifiers (NewGuid()).
-- It then creates PositionContainer instances for both long and short positions
-- using the loaded or generated identifiers.
function PosMan:load()
self.long_pid = Load('pm:lpid', NewGuid())
self.short_pid = Load('pm:spid', NewGuid()) --remove
self.long_pos = PositionContainer(self.long_pid)
self.short_pos = PositionContainer(self.short_pid) --remove
end
-- PosMan:getPID(isLong):
-- This function takes a boolean parameter isLong, indicating whether to retrieve the identifier
-- for the long position (true)
-- or the short position (false). *** can not short with crypto exchanges ? ?? ***
-- It returns the appropriate position identifier based on the value of isLong.
--
function PosMan:getPID(isLong)
return isLong and self.long_pid or self.short_pid
end
-- PosMan:update():
-- This function is responsible for updating positions.
-- It retrieves the current LONG and short positions
-- stored in long_pos and short_pos, respectively.
--- ********GO LONG ************
-- If the,( lpos."enterprice" & lpos.AMOUNT ) of the long position is greater than > 0,
-- and its amount is = 0 (indicating it's closed),
-- it generates:
-- 1. new unique identifier for the long position
-- 2. creates a new "PositionContainer" instance for it.
--
--*** NOT USING SHORTS ********
-- ***Similarly, if the enter price of the SHORT position is greater than 0
.
function PosMan:update()
local lpos = self.long_pos
local spos = self.short_pos --remove
if lpos.enterPrice > 0 and lpos.amount == 0 then
self.long_pid = NewGuid()
self.long_pos = PositionContainer(self.long_pid)
end
if spos.enterPrice > 0 and spos.amount == 0 then -- remove
self.short_pid = NewGuid() -- remove
self.short_pos = PositionContainer(self.short_pid) -- remove
end -- remove
self:save()
end
PosMan:save():
-- This function is responsible for saving position identifiers.
-- It stores the current long and short position identifiers (long_pid and short_pid, respectively)
-- into storage using the Save function, which stores data associated with a given key.
-- *** "name <=> key", dictionay type identifier...?
function PosMan:save()
Save('pm:lpid', self.long_pid)
Save('pm:spid', self.short_pid) -- not needed
end
-- ===============================================================
-- Enums
-- Define various enumerations for order directions, trigger types, etc.
local TableItem =
{
Id = 1,
IsMarket = 2,
Direction = 3,
TargetPrice = 4,
Amount = 5,
Before = 6,
After = 7,
TriggerType = 8,
TriggerPrice = 9
}
local TriggerType =
{
LowerThan = '<',
HigherThan = '>',
Normal = ''
}
local OrderDirection =
{
Buy = '+',
Sell = '-',
GoLong = 'L+',
GoShort = 'S+',
ExitLong = 'L-',
ExitShort = 'S-'
}
local OrderStatus =
{
Undefined = -1,
Created = 1,
Executing = 2,
Completed = 3,
Cancelled = 4,
Redundant = 5
}
-- ===============================================================
-- Handy functions
-- Utility functions for deep cloning objects and trimming strings
function clone(original) -- Returns copy, of dictionary
local copy = {}
-- The function iterates over each key-value pair
-- in the original object using the pairs iterator.
for k, v in pairs(original) do
-- If the value (v) associated with a key (k)
-- is a table (determined by GetType(v) == ArrayDataType),
-- the function recursively calls itself (clone(v)) to clone the nested table.
-- Otherwise, the value is copied directly.
if GetType(v) == ArrayDataType then
v = clone(v)
end
copy[k] = v
end
return copy
end
--Trim spaces from string....
function StringTrim(str)
return str:gsub("%s+", "")
end
-- ===============================================================
-- PreOrder object
-- Represents an order before execution
local PreOrder =
{
Id = '',
OrderId = '',
Status = OrderStatus.Undefined,
Direction = '',
Before = '',
After = '',
Amount = 0,
Price = 0,
IsMarket = false,
TriggerType = '',
TriggerPrice = 0
}
--Parameters:
--table: The table containing preorder data.
--index: The index of the preorder item within the table.
-- Load a "Blank" preorder and Clone it for usage.
function PreOrder:load(table, index)
local item = table[ index ]
local order = clone(PreOrder)
order.Id = StringTrim( item[ TableItem.Id ])
order.OrderId = Load(order.Id .. ':oid', '')
order.Status = Load(order.Id .. 's', OrderStatus.Created)
order.Direction = StringTrim( item[ TableItem.Direction ])
order.Before = StringTrim( item[ TableItem.Before ])
order.After = StringTrim( item[ TableItem.After ])
order.Amount = Parse(item[ TableItem.Amount ], NumberType)
order.Price = Parse(item[ TableItem.TargetPrice ], NumberType)
order.IsMarket = Parse(item[ TableItem.IsMarket ], BooleanType)
order.TriggerType = StringTrim( item[ TableItem.TriggerType ])
order.TriggerPrice = Parse(item[ TableItem.TriggerPrice ], NumberType)
------------------------------------
-- This piece of code checks if the order.TriggerPrice is falsy. If order.
-- TriggerPrice is nil or false, it sets order.TriggerPrice to -1.
-- Here's a breakdown:
-- if not order.TriggerPrice then: This line checks if order.TriggerPrice evaluates to false.
-- In Lua, nil and false are considered falsey values, while any other value (including 0) is considered truthy.
-- order.TriggerPrice = -1: If order.TriggerPrice is indeed falsey, this line assigns -1 to "order.TriggerPrice".
-- This effectively sets a default value of -1 if order.TriggerPrice is not provided or is falsey.
-- This code ensures that order.TriggerPrice has a valid value (either the provided value or -1)
-- to avoid potential errors or unexpected behavior when using order.TriggerPrice later in the code.
if not order.TriggerPrice then
order.TriggerPrice = -1
end
return order
end -- end of preorder:load RETURN order table
-- Store the preorder's order ID and status into persistent storage
function PreOrder:save()
Save(self.Id .. ':oid', self.OrderId)
Save(self.Id .. 's', self.Status)
end
function PreOrder:statusString()
local status = self.Status
--CHECK STATUS ******** (Could use case or is enum possible ? )*******
if status == OrderStatus.Undefined then
return 'Undefined'
elseif status == OrderStatus.Created then
return 'Awaiting'
elseif status == OrderStatus.Executing then
return 'Executing'
elseif status == OrderStatus.Completed then
return 'Completed'
elseif status == OrderStatus.Cancelled then
return 'Cancelled'
end
return '[Wrong status enum: ' .. status .. ']'
end
-- More functions for PreOrder object...
-- ===============================================================
-- Order Manager object
-- Manages a collection of orders
-- this code initializes an OrderMan table with an empty Orders table
-- and provides a method load to populate the Orders table with preorders
-- from another table (orderTable).
local OrderMan =
{
Orders = {}
}
-- Define a method named 'load' for the OrderMan table
function OrderMan:load()
-- Assign the value of the 'orderTable' variable to a local variable named 'table'
local table = orderTable
-- Calculate the number of elements in the 'table' and assign it to the local variable 'count'
local count = #table
-- Iterate over the elements of 'table'
for i = 1, count do
-- Load a preorder from 'table' at index 'i' using the PreOrder:load() method
local order = PreOrder:load(table, i)
-- Assign the loaded preorder to the 'Orders' table of the 'OrderMan' at index 'i'
self.Orders[i] = order
-- Log a debug message indicating the loaded order index
debuglog('Loaded order table at index ' .. i)
end
end
-- More functions for OrderMan object...
-- Skip very first update cycle
if Load('first_update', true) then
Save('first_update', false)
else
PosMan :load()
PosMan :update()
OrderMan :load()
OrderMan :update()
end
0 Comments
Sign in to leave a comment.
No comments yet. Be the first!