First Ever Bot Engine (FEBE) for HaasScript

Updated

Introduction

FEBE is a new kind of command set that aims to fill the gap (or place itself) between Managed and Unmanaged Trading commands. This command set is designed to provide the ease-of-use from Managed Trading and the power + flexibility of the Unmanaged side of things. The engine uses Unmanaged Trading commands, but everything has been done for you; all you need to do, is to give the engine “commands” with parameters to execute and the engine does the rest!

I sincerely hope this system will help you create awesome bots without too much of a headache! 🙂

I won’t be providing much examples or tutorials for the system at the time being. There will be more later on, stay tuned!

Related Scripts

The Blocks

By default, FEBE contains 6 commands to work with – and that’s all you need to get started! There is a “tutorial” below on how to create a Trailing Stop-Loss command for FEBE.

  • CC_FEBE_Orders: Defines settings for orders
  • CC_FEBE_Buy: Defines settings for buying/long
  • CC_FEBE_Sell: Defines settings for selling/short
  • CC_FEBE_TakeProfit: Defines settings for take-profit
  • CC_FEBE_StopLoss: Defines settings for stop-loss
  • CC_FEBE_Execute: Contains the core logic which reads the information array and acts based on it

Ease-of-Use

I hope FEBE will be loved by the great people of VE. It is designed to help creating bots using HaasScript’s Visual Editor, but of course it also works in Script Editor.

Here are few example bots created using FEBE:

Simple RSI Scalper

Simple Market Maker

MadHatter Clone

Hopefully these give some idea of what is possible and how powerful FEBE is…

Expandable

The core idea of FEBE is really simple: build an array of arguments and provide them for the execution command to handle. There is only one thing that has to exist in all commands that control FEBE: the command must be able to input and output the parameters array.

Here is the CC_FEBE_Buy() command for example:

DefineCommand('FEBE_Buy', 'Used to set buy settings for FEBE')

-- the input for parameters
local params = DefineParameter(ListDynamicType, 'params', 'Parameters for FEBE', true, {}, 'NewArray')

-- command related parameters
local order_timeout = DefineParameter(NumberType, 'order_timeout',
        'Order timeout in seconds. Default: 600 (10 minutes)', false, 600, 'Input, Number')
local order_refill = DefineParameter(BooleanType, 'order_refill',
        'Whether or not the filled/cancelled orders should be refilled. Default: false', false, false, 'True, False')
local order_type = DefineParameter(EnumType, 'order_type',
        'Order type used for the buy orders. If MarketOrderType is used, the buy order(s) will execute immediately'
        .. ' and will re-trigger after order_timeout if order_refill is set to TRUE. Default: LimitOrderType',
        false, LimitOrderType, 'LimitOrderType, MarketOrderType, MakerOrCancelOrderType')

-- a little hack to bypass private vs shared resources error
local p = {}
for i=1, #params do
    p[i] = params[i]
end

p[#p + 1] = {
    's_buy', -- the command for CC_FEBE_Execute()
    {
        -- the parameters for this particular block
        {'order_timeout', order_timeout},
        {'order_refill', order_refill},
        {'order_type', CC_FEBE_ParseOrderType(order_type)}
    }
}

-- the output for parameters, for easy continuation
DefineOutput(ListDynamicType, p, 'Input parameters with added buy information')

That command above would be the one that activates the buying module within the CC_FEBE_Execute(). There is a section for supported commands:

-- supported modules array
local supported_modules = {
    's_orders',
    's_tp',
    's_sl',
    's_buy',
    's_sell'
}

And later in the execution command, we check if this command exists in the input parameters and act based on the parameters we got from it:

---------------------------------------------------------------------
-- setup and run buying
module = {}
module.name = supported_modules[4]
module.order_timeout = 'order_timeout'
module.order_refill = 'order_refill'
module.order_type = 'order_type'

if active_modules[ module.name ] == true then
    log('Found buy settings')

    local module_params = params[ module.name ]

    -- grab settings
    local buy = {}
    buy.active = true
    buy.order_timeout = module_params[ module.order_timeout ]
    buy.order_refill = module_params[ module.order_refill ]
    buy.order_type = module_params[ module.order_type ]
    buy.order_count = orders.max_open
    buy.order_size = orders.size
    buy.order_spread = orders.spread
    buy.order_cancel_dist = orders.cancel_dist

    -- update orders with buy settings
    updateOrderBundle( true, 'buy', buy )
else
    -- grab settings
    local buy = {}
    buy.active = false
    buy.order_count = orders.max_open

    -- cancel all open orders and reset
    updateOrderBundle( true, 'buy', buy )
end

If you want to for example create trailing stop-loss for FEBE, you would simply have something like this:

DefineCommand('FEBE_TrailingStopLoss', 'Used to set TSL settings for FEBE')

-- the input for parameters
local params = DefineParameter(ListDynamicType, 'params', 'Parameters for FEBE', true, {}, 'NewArray')

-- command related parameters
local percentage = DefineParameter(NumberType, 'percentage',
        'Distance for trailing, set in percentages. Default: 1', false, 1, 'Input, Number')

-- a little hack to bypass private vs shared resources error
local p = {}
for i=1, #params do
    p[i] = params[i]
end

p[#p + 1] = {
    's_tsl', -- the command for CC_FEBE_Execute()
    {
        -- the parameters for this particular block
        {'percentage', percentage}
    }
}

-- the output for parameters, for easy continuation
DefineOutput(ListDynamicType, p, 'Input parameters with added TSL information')

…and this

 -- supported modules array
local supported_modules = {
    's_orders',
    's_tp',
    's_sl',
    's_buy',
    's_sell',
    's_tsl' -- our new command must be supported
}

…aaaand this

---------------------------------------------------------------------
-- setup and run trailing stop loss
module.name = supported_modules[6]
module.percentage = 'percentage' -- our parameters
module.order_type = 'order_type'

if active_modules[ module.name ] == true then
    log('Found TSL settings')

    local module_params = params[ module.name ]

    -- grab settings
    local tsl = {}
    tsl.active = false -- this tells if TSL was triggered or not
    tsl.percentage = module_params[ module.percentage ]
    tsl.order_type = module_params[ module.order_type ]
    tsl.price = 0

    -- regular order types
    if tsl.order_type <= 4 then
        -- wait until TSL triggers before activating order
        if TrailingStopLoss(tsl.percentage) then
            tsl.active = true
            if pos.dir == PositionLong then
                tsl.price = cp.ask
            elseif pos.dir == PositionShort then
                tsl.price = cp.bid
            end
        end

    -- native stop types
    elseif tsl.order_type > 4 and tsl.order_type < 7 then
        -- place TSL beforehand when using native stops.
        -- the updateExitOrder() function will handle replacing
        -- order when ever the price or position size changes.
        tsl.active = true

        -- calculate trigger price
        if pos.dir == PositionLong then
            tsl.price = SubPerc( pos.aep, tsl.percentage )
        elseif pos.dir == PositionShort then
            tsl.price = AddPerc( pos.aep, tsl.percentage )
        end
    else
        error('Native Take-Profit order types not supported for Trailing Stop-Loss')
    end

    -- if we got a price, parse it properly
    if tsl.price > 0 then
        tsl.price = ParseTradePrice(PriceMarket(), tsl.price)
    end

    -- we want to only activate TSL when we have an open position
    tsl.active = tsl.active and pos.dir != NoPosition

    -- run
    updateExitOrder('tsl', 'Trailing Stop-Loss', tsl.price, tsl.order_type, tsl.active)
end