Getting Started

Estimated reading time: 7 min

Below is a walkthrough of a simple strategy script for MachinaTrader.

It is standard C# language and is composed of 4 key areas which will be discussed below:

  • Imports
  • Parameters
  • Initialization
  • Trading Logic

Sample Script: QuickSMALight

using System; 
using System.Linq; 
using System.Drawing; 
using static TALib.Core; 
using MachinaEngine.Data; 
using MachinaEngine.Orders; 
using MachinaEngine.Brokerages; 
using MachinaEngine.Parameters; 
using MachinaEngine.Configuration; 
using MachinaEngine.Securities; 

namespace MachinaEngine.Algorithm.CSharp 
{ 
    /// <summary> 
    /// The demonstration algorithm shows some of the most common order methods when working with Crypto assets. 
    /// </summary> 
    public partial class QuickSMALight : MEAlgorithm 
    { 
        #region Parameters 

        [Parameter("SMA Fast")] 
        public int SMAFast { get; set; } = 6; 

        [Parameter("SMA Slow")] 
        public int SMASlow { get; set; } = 12; 

        public bool LogEvents { get; set; } 

        #endregion 

        /// <summary> 
        /// Initialise any algorithm related initialisation, all generic initialisation is handled in 
        /// the PreInitialise method so this method is only concearned with algorithm setup/configuration 
        /// </summary> 
        public override void Initialize() 
        { 
            foreach (string symbol in MTO.Symbols) 
            { 
                // Setup indicators or configure history requirements (using 'talib' indicators) 
                SetupHistory(symbol, MTO.PeriodSeconds, barsBack); 
            } 
        } 

        /// <summary> 
        /// OnTradeBar is called when a new 'trade bar' is available based on the MTO.PeriodSeconds value, this value  
        /// (in seconds) demotes the frequency which this method will be called with the latest trade bar data 
        /// </summary> 
        /// <param name="slice"></param> 
        public override void OnTradeBar(Slice slice) 
        { 
            foreach (Symbol symbol in slice.Symbols) 
            { 
                // Check to see if the history is ready for the current symbol / period 
                if (!IsHistoryReady(symbol, MTO.PeriodSeconds)) 
                    continue; 

                // Calculate SMA's 
                var tBars = SymbolHistory(symbol, MTO.PeriodSeconds); 
                var smaFasts = tBars.Sma(SMAFast); 
                var smaSlows = tBars.Sma(SMASlow); 
                var smaFast = smaFasts[smaFasts.Count - 1]; 
                var smaSlow = smaSlows[smaSlows.Count - 1]; 

                if (smaFast > smaSlow) 
                { 
                    Plotter($"Zone", "BUY", Securities[symbol.Value].Price, "green", SeriesType.Line, LineStyleType.Solid, 5); 
                } 
                else 
                { 
                    Plotter($"Zone", "Sell", Securities[symbol.Value].Price, "red", SeriesType.Line, LineStyleType.Solid, 5); 
                } 
                 
                var canSellQuote = CanSellQuote(symbol); 
                if (!canSellQuote && smaFast > smaSlow) 
                { 
                    // Calculate the trade using a Market or Limit order based on the parameters 
                    var buyTrade = CalculateTrade(symbol, OrderDirection.Buy, null); 
                    if (buyTrade.CanTrade) 
                        Order(buyTrade.Order); 
                } 
                else if (canSellQuote && fast.CrossUnder(slow)[histLen]) 
                { 
                    Sell(symbol, QuoteAmount(symbol)); 
                } 
 
                Plotter($"SMA", "Fast", smaFast.Value, "#487ed5", SeriesType.Line, LineStyleType.Solid, 4); 

                Plotter($"SMA", "Slow", smaSlow.Value, "#d7c847", SeriesType.Line, LineStyleType.Solid, 4); 
            } 
        }        
    } 
} 

The above script highlights the basic parts that are required in a MachinaTrader strategy.

They are detailed individually in the following sections.

Imports

The base import part is the same for all strategies and can be copied from an existing strategy / sample.

The area enables developers to import and use more C# libraries. Basically the whole .NET framework is available which provides unlimited freedom even for the most complex strategies.

using System; 
using System.Linq; 
using System.Drawing; 
using static TALib.Core; 
using MachinaEngine.Data; 
using MachinaEngine.Orders; 
using MachinaEngine.Brokerages; 
using MachinaEngine.Parameters; 
using MachinaEngine.Configuration; 
using MachinaEngine.Securities; 

Parameters

The Parameters area is optional but in most cases, a strategy developer will want to expose some settings to the end-user and once defined as Parameters, they are automatically displayed in the user interface and can be modified by the user prior to executing a backtest or running a strategy.

Parameters are defined as public properties in the script and marked with a parameter decorator.

In the example below, we have 2 parameters SMA Fast and SMA Slow.


[Parameter("SMA Fast")] 
public int SMAFast { get; set; } = 6; 

[Parameter("SMA Slow")] 
public int SMASlow { get; set; } = 12; 

Types of Parameters

Machina Engine offers three types of parameters, and they are always declared as follows:

The Parameter property defines a user-friendly name for the setting which will be displayed in the user interface.

        [Parameter("Decimal Value")] 
        public double DecimalValue = 5.2; 

        [Parameter("Integer Value")] 
        public int IntegerValue = 12; 

        [Parameter("Boolean Value")] 
        public bool BooleanValue = true; 

        [Parameter("String Value")] 
        public string StringSelection { get; set; } = "Blue,*Yellow*,Green"; 

The above example exposes the following parameters to the user:

The defined variables for the parameters are public, meaning that once declared they can be used from anywhere in the strategy code, generally in the OnTradeBar method.

Numeric parameters

Numeric parameters are defined using the int or double type and enable the user to provide a numeric value that will affect a strategy before backtesting and running it live.

int is for integer values (1,2,3) while double is for decimal values (1.0, 3.5, 6.79)

        [Parameter("Decimal Value")] 
        public double DecimalValue = 5.2; 

        [Parameter("Integer Value")] 
        public int IntegerValue = 12; 
Boolean parameters

Boolean parameters is the simplest form of parameters and let the user enable or disable a functionality. They have only 2 states (True and False) and are represented in the user interface by a switch.

        [Parameter("Boolean Value")] 
        public bool BooleanValue = true; 
String parameters

String parameters are letting the user select between various options.

        [Parameter("String Value")] 
        public string StringSelection { get; set; } = "Blue,*Yellow*,Green"; 

The options are separated by a , character and the default option is decorated by a starting and a leading * character (making Yellow the default). This will allow the user to select one of the values from a drop-down list before executing a backtest or a strategy.

Note: every time a change is done to the code, the parameters in the user interface will get reset to their default value.

Intialization

When running a strategy for backtesting or live trading, there is an initialization phase. On the scripting side this results in having the Initialize method being fired.

There isn't much code in there and the most important part is about telling the engine how many historical candles should be returned at each tick / new candle.

        public override void Initialize() 
        {             
            foreach (string symbol in MTO.Symbols) 
            { 
                // Setup idicators or configure history requirements (using 'talib' indicators) 
                SetupHistory(symbol, MTO.PeriodSeconds, barsBack); 
            } 
        } 

This can be changed / overridden in the Initialize method if needed.

For instance if we always want 24 hours of candles to be returned you can change this code by the following:

        public override void Initialize() 
        { 
            var _oneDay = 24 * (3660 / MTO.PeriodSeconds); 

            foreach (string symbol in MTO.Symbols) 
            { 
                // Setup idicators or configure history requirements (using 'talib' indicators) 
                SetupHistory(symbol, MTO.PeriodSeconds, _oneDay); 
            } 
        } 

Here, we are forcing the engine to always return last 24 hours of candles history each time a new candle is provided and this will apply to every interval.

Trading Logic

This is the interesting part of a strategy and where all the trading logic and rules is going to take place.

Every time a new candle is available (for instance every 15 minutes), the OnTradeBar method is fired. This method will provide a Slice which consists of the new/latest candle as well as all the the historical candles for the period defined in the Intitalization part.

The base structure below should always be present:

  1. Iteration of the symbols
  2. Verification that enough history is available
  3. Retrieval of the usable history for internal indicators
public override void OnTradeBar(Slice slice) 
        { 
            foreach (Symbol symbol in slice.Symbols) 
            { 
                // Check to see if the history is ready for the current symbol / period 
                if (!IsHistoryReady(symbol, MTO.PeriodSeconds)) 
                    continue; 
 
                // Calculate SMA's 
                var hist = SymbolHistory(symbol, MTO.PeriodSeconds); 
               
            } 
        }        

From this point, you can start using the history to calculate values using Technical Indicators and to take trading decisions to place buy or sell orders.

Available information / variables

While writing strategies, you also need to have access to the wallet / portfolio to know in which position the execution stands and how much Base and Quote is available for trading.

You have the following objects available at any time inside the OnTradeBar method which return this information:

Base Amount (ex: USDT):


BaseAmount(symbol)  

Quote Amount (ex: BTC):


QuoteAmount(symbol) 

Placing Orders

One of the key goals of an automated strategy is to place orders based on decisions / rules / trading logic.

In the MachinaEngine you can use either LIMIT or MARKET orders in both directions (BUY or SELL).

The easiest way is to place Market orders because they will be executed directly at market price.

This is achieved using the Buy or Sell methods.

CalculateTrade Helper

There is a helper method called CalculateTrades which can be used to calculate the amount of Base / Quote which can be bought or sold based on the current portfolio.

The signature of this method is as follows:

public TradeDetails CalculateTrade(Symbol symbol, OrderDirection direction, decimal? priceArg = null) 

It requires knowing the symbol that we are targeting, the direction (Buy or Sell) and optionally the price (if the price is null a Market Order will be assumed).

Market Orders

The following examples illustrates how to place Market Buy or Sell Orders with the maximum amount available in the wallet.

Using the CalculateTrade helper for the Buy and the Sell method for the Sell

        var canSellQuote = CanSellQuote(symbol); 

        if (!canSellQuote && smaFast > smaSlow) 
        { 
            // Calculate the trade using a Market or Limit order based on the parameters 
            var buyTrade = CalculateTrade(symbol, OrderDirection.Buy, null); 

            if (buyTrade.CanTrade) 
                Order(buyTrade.Order); 
        } 
        else if (canSellQuote && fast.CrossUnder(slow)[histLen]) 
        { 
            Sell(symbol, QuoteAmount(symbol)); 
        } 

Buy example

var buyTrade = CalculateTrade(symbol, OrderDirection.Buy, null); 
if (buyTrade.CanTrade) 
Order(buyTrade.Order); 

Sell example

Sell(symbol, QuoteAmount(symbol)); 

The Sell method which is a straight-forward helper and doesn?t even require the use of CalculateTrade. Just indicate the symbol and the amount of assets to be sold and it will be executed as a Market Order.

Limit Orders

When using Limit Orders and the CalculateTrade helper method, the approach is very similar to Market Orders, the only difference is that case is that we have to provide the target price to CalculateTrade (we passed null for Market Orders).

In this context, we can use another helper PercentBelow to automatically use the ticker price minus 0.5% in this example:

var buyTrade = CalculateTrade(symbol, OrderDirection.Buy, PercentBelow(symbol, 0.5))? 
if (buyTrade.CanTrade) 
Order(buyTrade.Order); 

Accessing the currently opened (not filled) Limit Orders can be achieved using the following code:

 var openOrders = Transactions.GetOpenOrders(); 

Logging

While developing a strategy and to monitor what it does during execution, it is useful to be able to output some logs. The MachinaTrader engine offers this functionality through the Log method.

Log method is straight-forward since it takes only one parameter which is the text/information to be logged. The timestamp will be applied automatically (execution timestamp).

Log($"{Time} - Portfolio: {Portfolio.TotalPortfolioValue}"); 

During and after the execution of the strategy, the logs are reported in the user interface under the Logs tab.

Plotting

The ability to render indicators and information on the chart really helps while developing strategies and for users to understand when to expect signals.

For this purpose we can use the Plotter method which has the following signature:

public void Plotter(string chart, string series, decimal value, string color = "Green", SeriesType lineType = SeriesType.Line, LineStyleType lineStyleType = LineStyleType.Solid, int thickness = 1, bool requiresOwnChart = false) 

To use it we need to provide the following parameters:

  • Group Name
  • Serie Name
  • Value (Y value at current tick)
  • Color
  • Series Type
  • Line Type
  • Thickness
  • Dedicated Chart required (if false or not set will be rendered over the main chart)

To render two custom series on the main chart, for instance a Fast and a Slow Moving average, the following code can be used:

Plotter("Fast", "Fast", fast, "red", SeriesType.Line, LineStyleType.Solid, 2); 
Plotter("Slow", "Slow", slow, "black", SeriesType.Line, LineStyleType.Solid, 2); 

The above code would provide this result on the chart:

Some indicators or information are not using values based on the price (such as RSI, MFI, etc.) and therefore should not be rendered on the main chart, in this case it?s better to request a dedicated chart and have it plotted separately.

This can be achieved with the requiresOwnChart parameter set to true as shown here:

Plotter("RSI", "RSI", rsi, "navy", SeriesType.Line, LineStyleType.Solid, 2, true); 

As a result, we have our RSI being rendered below the main chart instead of over it as shown on the following screenshot.

Series and Line Types

Since there are many variations and possibilities, there are the available options for the different types of charts and lines.

Please note that the main chart is a Line-Type chart, therefore to use all the other types (Bars, etc.), it is required to create a new Group and request a Dedicated Chart (requiresOwnChart = true).

    public enum SeriesType 
    { 
        Line, 
        Scatter, 
        Candle, 
        Bar, 
        Flag, 
        StackedArea, 
        Pie, 
        Treemap 
    } 
    public enum LineStyleType : int 
    { 
        Solid = 1, 
        Dashed = 2, 
        Dotted = 3, 
        Shapes = 4 
    }
Was this article helpful?
Dislike 0