Basic Strategy Example

Estimated reading time: 4 min

QuickSMA Strategy

Consider the following simple strategy written in C# (Python version available soon):

/*
 * MachinaTrader Starter Project
 */

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

namespace MachinaEngine.Algorithm.CSharp
{
    /// <summary>
    /// The demonstration algorithm shows some of the most common order methods when working with Crypto assets.
    /// </summary>
    public class QuickSMA : 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; } = false;

        #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()
        {
            // Log standard initialisation information
            LogInitialize();

            int barsBack = Math.Max(SMASlow, SMAFast) + 5;
            foreach (string symbol in MTO.Symbols)
            {
                // Setup idicators 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
                var histReady = IsHistoryReady(symbol.Value, MTO.PeriodSeconds);

                if (!IsHistoryReady(symbol.Value, MTO.PeriodSeconds))
                    continue;

                // Calculate SMA's
                var hist = SymbolHistory(symbol, MTO.PeriodSeconds);
                var histLen = hist.Count() - 1;
                var smaFast = hist.Sma(SMAFast);
                var smaSlow = hist.Sma(SMASlow);

                // Calculate Crossovers and Crossunders
                var crossUnder = smaFast.CrossUnder(smaSlow);
                var crossOver = smaFast.CrossOver(smaSlow);

                //Log(%%EDITORCONTENT%%quot;[QuickSMA::OnTradeBar] {Time.Date}::{Time.TimeOfDay} {symbol.Value} | Period: {MTO.PeriodSeconds}, B[{symbol.BaseCurrency}]: {BaseAmount(symbol)}, Q[{symbol.QuoteCurrency}]: {QuoteAmount(symbol)}, histReady: {histReady}");

                if (QuoteAmount(symbol) == 0 && BaseAmount(symbol) > 0 && crossOver[histLen])
                {
                    // Submit a buy limit order for BTC at 5% below the current price
                    var limitPrice = Math.Round(Securities[symbol.Value].Price * 0.95m, 8);
                    if (limitPrice != 0)
                    {
                        // NOTE: CalculateQuantity is a WIP METHOD
                        //var quantity = CalculateQuantity(symbol, BaseAmount(symbol), 80);
                        var quantity = ClampQuantity(symbol.Value, BaseAmount(symbol) * 0.5m / limitPrice);

                        if (ValidQuantity(symbol.Value, quantity))
                        {
                            var buyOrderResult = Buy(symbol.Value, quantity);
                            TraceOrder("Buy ", buyOrderResult);
                        }
                    }
                }
                else if (QuoteAmount(symbol) > 0 && crossUnder[histLen])
                {
                    var sellOrderResult = Sell(symbol.Value, QuoteAmount(symbol));

                    TraceOrder("Sell", sellOrderResult);
                }

                //Plot(%%EDITORCONTENT%%quot;{symbol.Value}-SMA", "Fast", smaFast.LastOrDefault().Value);
                //Plot(%%EDITORCONTENT%%quot;{symbol.Value}-SMA", "Slow", smaSlow.LastOrDefault().Value);
            }
        }

        /// <summary>
        /// The OnData method is called at the lowest frequency available (typically every minute), all data
        /// is passed to this method at the lowest available resolution
        /// </summary>
        /// <param name="slice">Slice object keyed by symbol containing the stock data</param>
        //public override void OnData(Slice slice)
        //{
        //}

        /// <summary>
        /// OnOrderEvent - this method is called each time an order event takes place
        /// </summary>
        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            if (!LogEvents)
                return;

            Log(Time + " " + orderEvent);
        }

        /// <summary>
        /// OnEndOfAlgorithm - this method is called at the end of the algorithm
        /// </summary>
        public override void OnEndOfAlgorithm()
        {
            Log(%%EDITORCONTENT%%quot;{Time} - TotalPortfolioValue: {Portfolio.TotalPortfolioValue}");
            Log(%%EDITORCONTENT%%quot;{Time} - CashBook: {Portfolio.CashBook}");
        }
    }
}

# Python example available soon.

Import Modules

First, we import core modules and call the engine algorithm:

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

namespace MachinaEngine.Algorithm.CSharp
{
    /// Strategy code
}

# Python example available soon.

Define Strategy Parameters

Now we look at the strategy code. The code below is inserted at the placeholder /// Strategy code shown above.

The demonstration algorithm, QuickSMA, shows some of the most common order methods when working with Crypto assets.

Declare a class, using the algorithm name (“QuickSMA”) and define parameters for a fast and a slow simple moving average (SMA).

Note that a boolean parameter LogEvents is also declared at the end of this stanza.

namespace MachinaEngine.Algorithm.CSharp
{
    /// The demonstration algorithm shows some of the most common order methods when working with Crypto assets.

    public class QuickSMA : 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; } = false;

        #endregion

# Python example available soon.

Initialize the Algorithm

Every strategy must be initialized. Generic engine initialisation is handled by the PreInitialise() method, and the Initialize() method is only concerned with algorithm-specific setup/configuration.

        public override void Initialize()
        {
            // Log standard information about this initialisation process
            LogInitialize();

            // determine how many bars (before the current bar) are needed to initialize SMAs
            int barsBack = Math.Max(SMASlow, SMAFast) + 5;

            // Get and setup the required indicator history For each symbol we are tracking.
            foreach (string symbol in MTO.Symbols)
            {
                // Setup idicators or configure history requirements (using 'talib' indicators)
                SetupHistory(symbol, MTO.PeriodSeconds, barsBack);
            }
        }

# Python example available soon.

Consume Instrument Data

MTO (“MTObject”) is an object that exists in the base engine code and derives its values from your dashboard settings. In the code example, above, the symbols you have selected for trade are stored in MTO.Symbols and the chart timeframe you specified in the dashboard is stored in MTO.PerioddSeconds.

The OnData() method processes data received via websocket at the smallest engine timeframe, namely every minute. However, here we use OnTradeBar() method that processes data at your specified timeframe.

OnTradeBar() is called when a new ‘trade bar’ is available, based on the MTO.PeriodSeconds value. This timeframe value (in seconds) specifies the frequency at which this method will be called with the latest trade bar data.

        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
                var histReady = IsHistoryReady(symbol.Value, MTO.PeriodSeconds);
                if (!IsHistoryReady(symbol.Value, MTO.PeriodSeconds))
                    continue;

                // Calculate SMA's
                var hist = SymbolHistory(symbol, MTO.PeriodSeconds);
                var histLen = hist.Count() - 1;
                var smaFast = hist.Sma(SMAFast);
                var smaSlow = hist.Sma(SMASlow);

                // Calculate Crossovers and Crossunders
                var crossUnder = smaFast.CrossUnder(smaSlow);
                var crossOver = smaFast.CrossOver(smaSlow);

                //Log(%%EDITORCONTENT%%quot;[QuickSMA::OnTradeBar] {Time.Date}::{Time.TimeOfDay} {symbol.Value} | Period: {MTO.PeriodSeconds}, B[{symbol.BaseCurrency}]: {BaseAmount(symbol)}, Q[{symbol.QuoteCurrency}]: {QuoteAmount(symbol)}, histReady: {histReady}");

                if (QuoteAmount(symbol) == 0 && BaseAmount(symbol) > 0 && crossOver[histLen])
                {
                    // Submit a buy limit order for BTC at 5% below the current price
                    var limitPrice = Math.Round(Securities[symbol.Value].Price * 0.95m, 8);
                    if (limitPrice != 0)
                    {
                        // NOTE: CalculateQuantity is a WIP METHOD
                        //var quantity = CalculateQuantity(symbol, BaseAmount(symbol), 80);
                        var quantity = ClampQuantity(symbol.Value, BaseAmount(symbol) * 0.5m / limitPrice);
    
                        if (ValidQuantity(symbol.Value, quantity))
                        {
                            var buyOrderResult = Buy(symbol.Value, quantity);
                            TraceOrder("Buy ", buyOrderResult);
                        }
                    }
                }
                else if (QuoteAmount(symbol) > 0 && crossUnder[histLen])
                {
                    var sellOrderResult = Sell(symbol.Value, QuoteAmount(symbol));
    
                    TraceOrder("Sell", sellOrderResult);
                }
    
                //Plot(%%EDITORCONTENT%%quot;{symbol.Value}-SMA", "Fast", smaFast.LastOrDefault().Value);
                //Plot(%%EDITORCONTENT%%quot;{symbol.Value}-SMA", "Slow", smaSlow.LastOrDefault().Value);
            }
        }

# Python example available soon.

Handle Order Events

The method OnOrderEvent() is called every time an order event occurs.

        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            if (!LogEvents)
                return;
    
            Log(Time + " " + orderEvent);
        }

# Python example available soon.

Handle Algorithm Completion

OnEndOfAlgorithm() – this method must always be called at the end your algorithm.

        public override void OnEndOfAlgorithm()
        {
            Log(%%EDITORCONTENT%%quot;{Time} - TotalPortfolioValue: {Portfolio.TotalPortfolioValue}");
            Log(%%EDITORCONTENT%%quot;{Time} - CashBook: {Portfolio.CashBook}");
        }
    }
}

# Python example available soon.

Summary

This strategy algorithm does not contain any decision-making or order execution. However, as a skeleton script it will compile and pass backtesting.

Be sure to read through it several times and make sure that you follow the code logic at every step.

This script will be available in Python within the next week or two.

Was this article helpful?
Dislike 0