Create a Custom Time Window in NinjaScript
How to create a user-defined time window in NinjaScript for use in automated trade systems and backtesting.
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.
In the trading world, everything revolves around efficiency. Automated trade systems allow traders to trade strategies when they aren’t at the computer. This can save many hours of charting and research if done correctly. However, just because your strategy can trade during every hour of the session doesn’t mean it should.
When backtesting strategies, you will notice that some strategies perform better at different times of the day. In this article, we will create a time window feature for use in our NinjaScript trade strategies. The focus will be on simple and effective code use. We will also discuss a software development principle and how it can help us organize our code for more efficient use and testing in the future.
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 Review
There are three parts to this section. The first two parts will cover creating the time window feature inside the strategy, and the third part will discuss separating certain logic from the strategy and creating a custom class. The code can be used as is at any of the stages. Each section is designed to build on the previous one by applying some design principles to the code.
Attributes and Basic Usage
We first need to create our properties that allow the user to define the hours they wish the strategy to trade in. We will need hours and minutes for both a start and end time. We will use these inputs to define our private TimeSpan
variables that we will use for comparison operations.
We will use TimeSpan
objects in this code instead of DateTime
objects, as they are generally more efficient, less error-prone, and more logically fitting. There are a few reasons they are beneficial:
TimeSpan
objects avoid any date-related issues that could arise when comparingDateTime
objects. This is because they do not have a date component, only a time component.They are not affected by factors such as daylight saving time and time zones.
Mathematical operations (like adding and subtracting hours or minutes) are more intuitive and much easier to perform.
It aligns with financial practices that often focus on market hours, time slots, sessions, etc., which are inherently time-of-day based rather than date and time-specific.
The following code is the first step in this process, but it can technically be used as is.
private TimeSpan _startTime = TimeSpan.FromHours(0);
private TimeSpan _endTime = TimeSpan.FromHours(0);
[Range(0, 23), NinjaScriptProperty]
[Display(Name = "Start Hour", Order = 3, GroupName = "Time Window")]
public int StartHour
{
get { return _startTime.Hours; }
set { _startTime = TimeSpan.FromHours(value); }
}
[Range(0, 59), NinjaScriptProperty]
[Display(Name = "Start Minute", Order = 4, 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 = 5, GroupName = "Time Window")]
public int EndHour
{
get { return _endTime.Hours; }
set { _endTime = TimeSpan.FromHours(value); }
}
[Range(0, 59), NinjaScriptProperty]
[Display(Name = "End Minute", Order = 6, GroupName = "Time Window")]
public int EndMinute
{
get { return _endTime.Minutes; }
set { _endTime = TimeSpan.FromHours(EndHour).Add(TimeSpan.FromMinutes(value)); }
}
protected override void OnBarUpdate()
{
// Check if the current time is within the user-defined time window
// This can be defined however you wish, but it can get messy.
if (ToTime(Time[0]) >= ToTime(startTime) && ToTime(Time[0]) <= ToTime(endTime))
{
// Your trading logic here
}
}
Creating a Private Function
In this step, we will tidy up the code by creating a private helper function to determine whether or not we are inside the trade window. This allows us to keep the code inside OnBarUpdate()
clean and readable.
You will notice that the logic in InsideTradeWindow()
accounts for all possibilities of comparison. The purpose of this is to limit errors and ensure that time windows can be established through midnight and that you can set it to trade for all 24 hours a day.
private static bool InsideTradeWindow()
{
// If tradeStart and tradeEnd are the same, it might mean trading is allowed all day
// or not allowed at any time, based on your trading strategy's logic
if (tradeStart == tradeEnd)
{
return true; // or return false; depending on your requirement
}
if (tradeStart < tradeEnd)
{
return _timeNow >= tradeStart && _timeNow <= tradeEnd;
}
else // This implies the time window spans across midnight
{
return _timeNow >= tradeStart || _timeNow <= tradeEnd;
}
}
protected override void OnBarUpdate()
{
TimeSpan _timeNow = TimeSpan.FromHours(Time[0].Hour).Add(TimeSpan.FromMinutes(Time[0].Minute));
if (InsideTradeWindow()
{
// Your trading logic here
}
}
Separation of Concerns
Let’s take this one step further and discuss a development concept. We already separated the logic of the trade window into a helper function above, but what if we removed it from the strategy file entirely and created our own custom class of helper functions? In software design, this is called separation of concerns (SoC). There are a few benefits to doing this:
It allows the function to be used across multiple strategies. This would eliminate the need for copying and pasting or having large amounts of code in a strategy template.
Code becomes more accessible to read and maintain.
Reduces complexity by breaking the system down into more manageable pieces.
Scalability becomes easier.
To achieve this SoC, we will create our own custom class in C# and move the logic for the time window into it. This function will become available for use by declaring it in the strategy. The custom class will be static, which makes it stateless and less likely to interfere with the underlying logic in NinjaScript. The values needed for the comparison will be passed in from the strategy, where they are declared initialized.
namespace UtilityFunctions
{
public static class Evaluators
{
public static bool InsideTradeWindow(TimeSpan tradeStart, TimeSpan tradeEnd, TimeSpan timeNow)
{
if (tradeStart == tradeEnd)
{
return true;
}
if (tradeStart < tradeEnd)
{
return timeNow >= tradeStart && timeNow <= tradeEnd;
}
else
{
return timeNow >= tradeStart || timeNow <= tradeEnd;
}
}
}
}
Once you define the evaluator function, it can be used inside any strategy you create by calling the public function Evaluators.InsideTradeWindow(args)
. You have to pass in the arguments in the correct order. Note that your variables do not need to share the same name, just the correct order.
using UtilityFunctions;
// This can be done directly in the OnBarUpdate(), or it can be put into a helper function
protected override void OnBarUpdate()
{
TimeSpan _timeNow = TimeSpan.FromHours(Time[0].Hour).Add(TimeSpan.FromMinutes(Time[0].Minute));
if (InsideTradeWindow(_startTime, _endTime, _timeNow))
{
// Your trading logic here
}
}
Summary
In this article, we explored how to use TimeSpan
objects to define the trading hours for a strategy and their advantages over DateTime
objects for this use case. We emphasized the importance of clean, maintainable code by introducing a private function to encapsulate the logic. Advancing further, we applied the principle of separation of concerns by creating a custom static class Evaluators
to house our trade window logic. This structure helps with re-usability and scalability and aligns with best practices in software design.
I will be using this concept with the strategy that drops this month. By the end of the month, I hope to have refactored everything we have worked on in the GitHub repo. I plan to write an article covering the new concepts for managing strategy research and the GitHub workflow.
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.