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.
Update:
This article was originally published for paid subscribers but has since then been set free. It has been some time since I have visited this article or used NinjaScript/NinjaTrader. Reach out if you have any questions.
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.
I am going to show three different backtest results for this strategy. The first one will be trading both directions with no exit type specified. That means the strategy will alternate between long and short positions throughout the day.
The next one is using the CrossEMA
exit type. This variation tells the strategy to exit a position when the price crosses back over the EMA after a breakout.
Both of the previous variations have a positive net profit, but they are very different from each other. They also both show that short trades are unprofitable with these exit strategies (in their current state). This next photo shows long-only trades with the CrossEMA
exit type.
The results shown above may not be the sexiest, but they are all positive and show that even a simple strategy can present opportunities for trades. It also expresses the idea that we can turn simple strategy ideas into functioning trade systems with some good research and properly handled trade positions.
Summary
We redesigned our strategy template in this post using the custom classes we created in previous posts. In the process, we created a simple EMA breakout strategy to test the new template and look at the different results for different exit types.
It’s important to note that when coding strategies this way in NinjaScript, some of the features in NinjaTrader, such as exporting or seeing the code in the NT editor, might not work. This is because it doesn’t adhere to the folder structure of NinjaScript. I did attempt to rename the functions and move them into a recognized folder (the strategy and add-on folders), but it didn’t help the export issue and caused it to not recognize the custom classes entirely in the code. It works for local compilation, though, meaning it backtests and works in the forward test. I will have to dive deeper to figure out how to solve the other issues, but currently, it seems like they want you to use as much of their built-in functionality as possible.
The next post will be the official strategy post for January. It is strategy 9 from ATS and is one of the twilight strategies, strategies that didn’t function in the forward test. We will create a custom signal class for the strategy and implement it into the new template.
Feel free to comment below or e-mail me if you need help with anything, wish to criticize, or have thoughts on improvements. Paid subscribers can access this code and more at the private HGT GitHub repo. As always, this newsletter represents refined versions of my research notes. That means these notes are plastic. There could be mistakes or better ways to accomplish what I am trying to do. Nothing is perfect, and I always look for ways to improve my techniques.
Love this. Especially the Time and Order Management Window.