IntroductionMachina Trader offers an integrated python-based scripting engine for writing automated trading algorithms. This enables anyone to write scripts and run them in a MachinaTrader instance. The best way to get familiar with the structure and the possibilities is to have a closer-look at the open-source strategies available. In order to write strategies for MachinaTrader, there are some constants that are required to be defined and some methods which need to be implemented.
Mandatory ImportsIt is required to import the mtCommon library A typical script in MachinaTrading starts by an import statement looking like that:
import sys, talib, requests, json, numpy, base64, json, enum, datetime, array, mathimport mtCommon
Constants to defineThe following constants are expected to be defined in every strategy. They are used to identify the strategy name and version and also to define the number of candles the instance should provide to the trading strategy.
# CONSTANTSSTRAT_NAME = ‘❄️ Winter Trend’STRAT_TYPE = mtCommon.SimpleStrategyVERSION = ‘1.3’NUM_CANDLES = 45
STRAT_NAME and VERSION are used to give a name and a version number to the strategy.
STRAT_TYPE defines the type of the strategy and can have the following values: – mtCommon.SimpleStrategy (1) for a Simple Strategy (producing signals) – mtCommon.TradingStrategy (2) for a Trading Strategy (producing orders)NUM_CANDLES tells how many candles the strategy is expecting on each ticks. More candles means slower backtests and more trafic/power so it’s recommended to request only the candles that are going to be used by technical indicators.
Methods to implementThese are the functions in the script that will be called/triggered by the Core Engine in different situations:
onInitThe onInit function is called when a script is initialized, as soon as an instance is created or the script loaded for a backtest or a live trading instance, this method is going to be called only once at startup. Example
def onInit(executionId, config): print(‘onInit called’)
onSendParamsThe onSendParams function is called by Machina Trader in order to retrieve the list of parameters awaited by a strategy. By defining parameters in this method, the user will be able to provide the values in the Web Interface in order to configure/adjust parameters for a given strategy. Defining parameters is done by calling the defining parameters in the onSendParams method as you can see in the sample below. Example
def onSendParams():#_mt.addNumericParameter(SETTING_SMA_FAST, 13)_mt.addNumericParameter(SETTING_RSI_VALUE, RSI_VALUE)_mt.addNumericParameter(SETTING_RSI_HIGH, RSI_BULL_HI)_mt.addNumericParameter(SETTING_RSI_LOW, RSI_BULL_LO)_mt.addNumericParameter(SETTING_BUY_ORDER, 90)_mt.addNumericParameter(SETTING_SELL_ORDER, 90)
There are different helpers to use for requesting numeric, string, boolean or list parameters which are described below.
Most of the AddParameter helpers take 2 parameters, the name and the default value.
onStartThe onStart function is used when an instance is started (either right after initialization or when being started manually after having been stopped). It doesn’t receive any parameter and enables a strategy developer to execute actions only once each time the instance is started. Example
def onStart(): # The strategy execution is starting. This method is used prior to the onTick being # called for each new candle / period processed print(‘onStart called’)
onStopThe onStop function is called when an instance is stopped (manually or when a backtest has finished executing). It can be used for instance to render a finally summary on the performances of the strategy during the execution period. Exemple
def onStop(): # The strategy execution has completed, perform any final end of strategy processing within this method print(‘onStart called’)
onTickThe onTick function is called each time a new candle is being received. It’s probably the most important function and the one that will contain most of the logic of the actual trading strategy. Each time the method is called, the array of historical candles is being sliced and the newly received candle is inserted at the beginning. At the same time, the oldest candle that was present in the previous tick is removed from the array so that we always have a constant number of refreshed candles. Three parameters are provided: currentDate which contains the timestamp of the last candle candles which is the array of the candles that can be used to take a decision config object which provides information about the current instance, market and pair:
- EXCHANGE: Id of the target exchange platform
- MARKET: Id of the selected Symbol (ex: BTC-USD)
- BASE: Id of the base currency (ex: USD)
- ASSET: Id of the asset (ex: BTC)
- BASE_BALANCE: the amount of BASE currently in the wallet
- ASSET_BALANCE: the amount of QUOTE currently in the wallet
- BACKTESTING: Boolean flag to indicate if we are backtesting or running live
def onTick(currentDate, candles, config):rsis = talib.func.RSI(candles.C, RSI_VALUE)rsi = rsis[len(rsis) – 1]currentBase = config.BASEcurrentAsset = config.ASSETprint‘onTick called’)
The onTick method is the place where trading decisions are made, a Simple Strategy need to return a signal, either BUY, SELL or HOLD in each tick.
This can be achieved by calling _mt.buy, _mt.sell or _mt.hold as in the next example:
if sampleParam: _mt.buy(config.MARKET) else: _mt.sell(config.MARKET) #_mt.hold(config.MARKET)
onSendIndicatorModelsThe onSendIndicatorModels function is triggered by Machina Trader platform in order to retrieve the indicators to be rendered on the chart. Unlike the onTick method, when running a backtest, this method will receive all the candles at once so that it’s possible to calculate and return the value for each tick in a single call. The method will be called twice, once with the details parameter set to true in order to retrieve the definitions/list of parameters. Then a second time with the details parameter set to false so that all the calculated values can be returned. As you can see in the sample below, first we return the name of the indicator: RSI as well as the color to be used on the chart. Then we return the values which we have calculated using the talib library. Example
def onSendIndicatorModels(candles, details):if details:_mt.addIndicatorModelDetails(‘RSI’, ‘#0175B7’)else:# The RSI has a line plus typically two resistance lines as shown here (I also add two additional bull/bear lines)rsis = talib.func.RSI(candles.C, RSI_VALUE)#——————————————————-# Relative Strength Index#——————————————————-# if _mt.getParameter(‘Show RSI’):# The RSI has a line plus typically two resistance lines as shown here (I also add two additional bull/bear lines)mRSI = _mt.createIndicatorModel(‘RSI’, True)mRSI.addSimpleIndicator(‘RSI’, rsis, ‘#0175B7’, ‘line’)# Standard markers used for this indicatormRSI.addHorizontalLine(‘RSI_BL_HI’, RSI_BULL_HI, ‘#CCFFCC’)mRSI.addHorizontalLine(‘RSI_BL_LO’, RSI_BULL_LO, ‘#7a1CCFFCCbe0’)_mt.addIndicatorModel(mRSI)