The New Strategy Template
We redesign our strategy template to include the new custom classes and methods we have been creating. Spoiler: There is a new strategy in the template and this time it works.
I posted the first strategy template back in November. It’s only been a month and a half since then, and we are already updating our strategy template. The original template still works, and there are probably NinjaScript developers who would still use the initial method over this new one. However, I believe we could accomplish more by separating out our logic, and my recent posts have expressed this ad nauseam. If you have yet to go through those posts, please read them first. They set the foundation for what we are going to do today.
In this post, we will discuss the new template by designing an EMA Breakout strategy. We will use the signal we created in the last post and incorporate all of the previous classes we created. We will also introduce a new static class to define some order status logic we use in every strategy.
After we discuss the code, I will share some screenshots of the backtest results for this strategy. They will show how using different exit criteria can change the results of the strategy. The template is designed to be a starting point for individual strategy development, so it has several position exit methods I like to experiment with during the testing phase.
Most of this post (all the code) is available to all subscribers of HGT. However, many of the custom classes and methods used in this strategy are covered in posts only available to paid members. It should not be difficult to convert for your needs if you do not wish to become a paid subscriber. Paid subscribers will have access to this code and more in the private HGT GitHub repo.
Disclaimer: the following post is an organized representation of my research and project notes. It doesn’t represent any type of advice, financial or otherwise. Its purpose is to be informative and educational. Backtest results are based on historical data, not real-time data. There is no guarantee that these hypothetical results will continue in the future. Day trading is extremely risky, and I do not suggest running any of these strategies live.
Code
The code is broken into the custom order class and the template. The custom order status class is a simple, static class that takes in an execution object and the entry order object to determine what stage an order is currently in. The strategy template is self-explanatory, and where the majority of the focus is for this post.
Custom Order Status Class
I use several helper methods to determine what stage an order is in. The following code is a simple static class I created to encapsulate these functions.
using NinjaTrader.Cbi;
namespace OrderOperations
{
public static class OrderStatus
{
// Checks if the OrderState is Filled, PartFilled, or Cancelled with some portion of the order (Filled > 0) being filled.
public static bool IsEffectivelyFilled(Execution execution, Order entryOrder)
{
return execution.Order.OrderState == OrderState.Filled ||
execution.Order.OrderState == OrderState.PartFilled ||
execution.Order.OrderState == OrderState.Cancelled &&
execution.Order.Filled > 0 &&
entryOrder != null &&
entryOrder == execution.Order;
}
public static bool IsOrderPartlyFilled(Execution execution, Order entryOrder)
{
return execution.Order.OrderState == OrderState.PartFilled &&
entryOrder != null &&
entryOrder == execution.Order;
}
// Checks to see if the entire order was filled completely.
public static bool IsEntryOrderFilled(Execution execution, Order entryOrder)
{
return execution.Order.OrderState == OrderState.Filled &&
entryOrder != null &&
entryOrder == execution.Order;
}
}
}
The Strategy Template + an EMA Breakout Strategy
Essentially, everything from the previous template remains, but instead of coding all of our helper functions inside the actual strategy, they have been abstracted into their own custom classes. To use them, we need to make sure we declare them at the top of the strategy.
This template also makes use of the generic type converter that was created after the post covering type converters. It (along with all the custom classes) is available on the GitHub for paid subscribers.
I kept three helper functions inside the strategy template: SetDefaultSettings()
, InitializeVariables()
, and AddIndicators
. Two of them should be familiar. The InitializeVariables()
function was previously called ResetVariables
. This is still necessary for speeding up the backtesting process.
Two new enums have been added to the properties: ExitType
and TradeDirection
. This allows us to experiment with different variations of the strategy. This strategy has 4 different exit types. The CrossEMA
type is the only one specific to this strategy. It is used to test exiting the strategy with the exit signals defined in the EMABreakout
signal class. I use here as an example of how the template can be adjusted to a strategy’s needs. This gives us 12 different variations we can test.
We also implemented the InsideTradeWindow()
method we created earlier this month. This means we can set the time window for trades on any strategy we use this template with. This method resides inside the Evaluators
class.
After removing all of the helper functions, I determined that it wasn’t necessary to create a separate function to hold out strategy logic. It is now written directly inside the OnBarUpdate
method. This makes it easier to read and understand. We use the built-in NinjaScript functions to check our market position before determining how to manage our trade entries and exits.
All logic for creating stop loss or profit target orders is handled inside the OnExecutionUpdate
method. They will check to see what exit type we chose for the strategy before executing. This is also where the order status methods mentioned above will be called.
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NinjaTrader.Cbi;
using NinjaTrader.NinjaScript.Indicators;
using UtilityFunctions;
using SignalLibrary;
using NinjaTrader.NinjaScript.OrderOperations;
using TradeFunctions;
namespace NinjaTrader.NinjaScript.Strategies
{
[TypeConverter("NinjaTrader.NinjaScript.GenericConverter")]
public class StrategyTemplate : Strategy
{
#region Strategy Variables
private const string StrategyName = "Deviated Template";
private TradeManager _tradeManager;
private EMA _ema;
private ATR _atr;
private EMABreakout _signal;
private Order _entryOrder;
private Order _stopOrder;
private Order _profitOrder;
private int _sumFilled;
private double _sl;
private double _tp;
#endregion
#region Properties
private double _stopTicks = 100;
private double _profitTicks = 20;
private bool _traceorders = false;
private int _period = 9;
private int _atrPeriod = 14;
private TimeSpan _startTime = TimeSpan.FromHours(0);
private TimeSpan _endTime = TimeSpan.FromHours(0);
private ExitType _exitType;
private TradeDirection _tradeDirection;
public enum ExitType
{
None,
Static,
TrailATR,
CrossEMA
}
public enum TradeDirection
{
Both,
Long,
Short
}
[RefreshProperties(RefreshProperties.All), NinjaScriptProperty]
[Display(Name = "Exit Type", Order = 1, GroupName = "Order Management")]
public ExitType MyExitType
{
get { return _exitType; }
set { _exitType = value; }
}
[NinjaScriptProperty]
[Display(Name = "Trade Direction", Order = 2, GroupName = "Order Management")]
public TradeDirection MyTradeDirection
{
get { return _tradeDirection; }
set { _tradeDirection = value; }
}
[Range(1, int.MaxValue), NinjaScriptProperty]
[Display(Name = "Stop Loss", Order = 3, GroupName = "Order Management")]
public double SL
{
get { return _stopTicks; }
set { _stopTicks = value; }
}
[Range(1, int.MaxValue), NinjaScriptProperty]
[Display(Name = "Take Profit", Order = 4, GroupName = "Order Management")]
public double TP
{
get { return _profitTicks; }
set { _profitTicks = value; }
}
[NinjaScriptProperty]
[Display(Name = "Trace Orders", Order = 5, GroupName = "Order Management")]
public bool EnableTraceOrders
{
get { return _traceorders; }
set { _traceorders = value; }
}
[Range(1, int.MaxValue), NinjaScriptProperty]
[Display(Name = "EMA Period 1", Order = 6, GroupName = "Parameters")]
public int Period1
{
get { return _period; }
set { _period = value; }
}
[Range(1, int.MaxValue), NinjaScriptProperty]
[Display(Name = "ATR Period", Order = 7, GroupName = "Parameters")]
public int ATRPeriod
{
get { return _atrPeriod; }
set { _atrPeriod = value; }
}
[Range(0, 23), NinjaScriptProperty]
[Display(Name = "Start Hour", Order = 8, GroupName = "Time Window")]
public int StartHour
{
get { return _startTime.Hours; }
set { _startTime = TimeSpan.FromHours(value); }
}
[Range(0, 59), NinjaScriptProperty]
[Display(Name = "Start Minute", Order = 9, GroupName = "Time Window")]
public int StartMinute
{
get { return _startTime.Minutes; }
set { _startTime = TimeSpan.FromHours(StartHour).Add(TimeSpan.FromMinutes(value)); }
}
[Range(0, 23), NinjaScriptProperty]
[Display(Name = "End Hour", Order = 10, GroupName = "Time Window")]
public int EndHour
{
get { return _endTime.Hours; }
set { _endTime = TimeSpan.FromHours(value); }
}
[Range(0, 59), NinjaScriptProperty]
[Display(Name = "End Minute", Order = 11, GroupName = "Time Window")]
public int EndMinute
{
get { return _endTime.Minutes; }
set { _endTime = TimeSpan.FromHours(EndHour).Add(TimeSpan.FromMinutes(value)); }
}
#endregion
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
SetDefaultSettings();
}
else if (State == State.Configure)
{
ClearOutputWindow();
}
else if (State == State.DataLoaded)
{
InitializeVariables();
AddIndicators();
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < 2)
return;
TimeSpan _timeNow = TimeSpan.FromHours(Time[0].Hour).Add(TimeSpan.FromMinutes(Time[0].Minute));
if (Evaluators.InsideTradeWindow(_startTime, _endTime, _timeNow))
{
if (Position.MarketPosition == MarketPosition.Flat)
{
if (_signal.EntryLong(_ema, Close) && _tradeDirection != TradeDirection.Short)
{
_entryOrder = EnterLong(Convert.ToInt32(DefaultQuantity), _signal.LongEntryName);
}
else if (_signal.EntryShort(_ema, Close) && _tradeDirection != TradeDirection.Long)
{
_entryOrder = EnterShort(Convert.ToInt32(DefaultQuantity), _signal.ShortEntryName);
}
}
else if (Position.MarketPosition != MarketPosition.Flat)
{
if (_exitType == ExitType.TrailATR)
{
//ResetTrail();
(double newSL, Order newStopOrder) = _tradeManager.ResetTrailIndicator(_atr, _entryOrder.Name, _stopOrder, _sl);
_sl = newSL;
_stopOrder = newStopOrder;
}
else if (_exitType == ExitType.None && _tradeDirection == TradeDirection.Both)
{
if (Position.MarketPosition == MarketPosition.Long && _signal.EntryShort(_ema, Close))
{
_entryOrder = EnterShort(Convert.ToInt32(DefaultQuantity), _signal.ShortEntryName);
}
else if (Position.MarketPosition == MarketPosition.Short && _signal.EntryLong(_ema, Close))
{
_entryOrder = EnterLong(Convert.ToInt32(DefaultQuantity), _signal.LongEntryName);
}
} else if (_exitType == ExitType.CrossEMA)
{
if (Position.MarketPosition == MarketPosition.Long && _signal.ExitLong(_ema, Close))
{
_entryOrder = ExitLong(Convert.ToInt32(DefaultQuantity), _signal.LongExitName, _signal.LongEntryName);
}
else if (Position.MarketPosition == MarketPosition.Short && _signal.ExitShort(_ema, Close))
{
_entryOrder = ExitShort(Convert.ToInt32(DefaultQuantity), _signal.ShortExitName, _signal.ShortEntryName);
}
}
}
}
}
protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time)
{
if (OrderStatus.IsEffectivelyFilled(execution, _entryOrder))
{
_sumFilled += execution.Quantity;
if (OrderStatus.IsEntryOrderFilled(execution, _entryOrder))
{
if (_exitType == ExitType.Static)
{
(Order StopOrder, Order ProfitOrder) = _tradeManager.SetStaticStopProfit(execution, _stopTicks, _profitTicks);
_stopOrder = StopOrder;
_profitOrder = ProfitOrder;
}
else if (_exitType == ExitType.TrailATR)
{
(double newSL, Order newStopOrder) = _tradeManager.SetTrailIndicator(execution, _atr);
_sl = newSL;
_stopOrder = newStopOrder;
}
else if (_exitType == ExitType.None)
{
return;
}
}
if (execution.Order.OrderState != OrderState.PartFilled && _sumFilled == execution.Order.Filled)
{
_sumFilled = 0;
}
}
}
private void SetDefaultSettings()
{
Name = StrategyName;
IsOverlay = true;
Calculate = Calculate.OnBarClose;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 2700;
OrderFillResolution = OrderFillResolution.Standard;
TraceOrders = false;
BarsRequiredToTrade = 5;
IsInstantiatedOnEachOptimizationIteration = false;
}
private void InitializeVariables()
{
TraceOrders = _traceorders;
_tradeManager = new TradeManager(this);
_signal = new EMABreakout();
_sumFilled = 0;
_entryOrder = new Order();
_stopOrder = new Order();
_profitOrder = new Order();
_sl = double.NaN;
_tp = double.NaN;
_sumFilled = 0;
}
private void AddIndicators()
{
_ema = EMA(_period);
_atr = ATR(_atrPeriod);
AddChartIndicator(_ema);
}
}
}
Backtesting the EMA Breakout Strategy
Before we get into the backtest results, I want to reiterate that these results are based on historical data. That makes them hypothetical. This strategy is not intended to be run live. Remember, day trading is risky no matter what the instrument is. If you trade this strategy live, you should be prepared to lose the money in your account. This template is designed to be a starting point for researching automated trade systems.