Documented with Chat.gpt the Hassbot Order bot By pshai

stable
By Sbyte in Other Published April 2024 👁 692 views 💬 0 comments

Description

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!