Create a Custom Evaluator Class in NinjaScript (C#)
This post discusses how to create your own evaluation methods in NinjaScript for use in automated trading systems and why you want to. Available to all subscribers.
In this post, we will look at creating our own evaluation functions in C#/NinjaScript for researching and developing automated trade systems (ATS). NinjaScript already has built-in analyzer methods that can be used during strategy development. The first few articles discussing strategy development used these functions to code our strategies. Today, we will discuss why these built-in functions may not be the best option and why you might want to make custom evaluation methods for use inside your trading strategy.
I want to start by saying there is nothing wrong with using the built-in NinjaScript methods. As a matter of fact, if you ever want the fantastic support team at NT to be able to help you with development issues, it is wise to use the methods they provide and structure your project the way that they suggest.1 If you don’t, you run the risk of them being unable to help. They seem to be able to provide support only if you follow their “best practice” suggestions.
I do things my own way. It doesn’t mean that I’m right or that I’m wrong, but it does mean that I remain in control. There will never be anyone to blame for failure other than myself. This is the foundation that drives my approach to everything. It is so crucial to my way of life that I implement it in every possible way. Shouldering the weight of my own mistakes has been the only thing that has allowed me to grow and improve. As a paramedic, you can assume how heavy that weight could be.
Lives may not be on the line when trading per se, but your account sure is, and losing a lot of money could lead to some heavy weight-lifting. That’s why I like to know what’s going on inside my code. I believe firmly in the saying, “Trust but verify.” Unfortunately, I cannot verify any of the underlying code in NinjaScript. That’s why I went down this little rabbit hole. How does the NinjaScript CrossAbove()
function work? Not sure, so I made my own.
The code below is not entirely free from NinjaScript, but it does give us the framework for developing more customizable and flexible evaluation operations for use in our NinjaScript trade systems. It also allows us to debug any issues that arise more thoroughly and without contacting a support specialist.
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
There are 6 different methods in the code below: 2 variations of CrossAbove()
and CrossBelow()
, and Isrising()
and IsFalling()
. They should sound familiar, as I didn’t see the need to name them differently. There shouldn’t be a conflict as they will be explicitly called inside the strategy as an extension of the Evaluators
class. I will provide an example of this at the end.
The CrossAbove()
and CrossBelow()
methods take in either two ISeries<double>
objects or one ISeries<double>
object and a double value. Creating two variations of the same class is called overloading. This allows us to have multiple methods in the same scope with the same name but different parameters. The built-in methods in NinjaScript have this capability, too, and all of these methods were modeled after them.
The ISeries<T>
interface in NinjaScript is really cool and worth discussing for a moment. This interface for a data series is designed specifically for time series data and has some neat features that make it better suited for trade systems. It’s capable of handling both historical and real-time data.2
First, it is dynamically indexed. A typical C# collection is static, meaning their indices always point to the same piece of data. There is no shifting. The ISeries<T>
shifts focus to the most current market bar, meaning that it continuously shifts data so you can access the most recent bar at [0]
.
Secondly, it is inherently designed for real-time updates. This is a critical feature to have for developing trade systems. It is built to update data as new market information comes in.
Here’s the code:
using NinjaTrader.NinjaScript;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UtilityFunctions
{
public static class Evaluators
{
public static bool CrossAbove(ISeries<double> seriesA, ISeries<double> seriesB, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || seriesA.Count <= lookbackPeriod || seriesB.Count <= lookbackPeriod)
return false;
// Check if seriesA was below value lookbackPeriod bars ago and is above seriesB the following bar.
return seriesA[lookbackPeriod] <= seriesB[lookbackPeriod] && seriesA[lookbackPeriod - 1] > seriesB[lookbackPeriod - 1];
}
public static bool CrossBelow(ISeries<double> seriesA, ISeries<double> seriesB, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || seriesA.Count <= lookbackPeriod || seriesB.Count <= lookbackPeriod)
return false;
// Check if seriesA was above value lookbackPeriod bars ago and is below seriesB the following bar.
return seriesA[lookbackPeriod] >= seriesB[lookbackPeriod] && seriesA[lookbackPeriod - 1] < seriesB[lookbackPeriod - 1];
}
public static bool CrossAbove(ISeries<double> seriesA, double value, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || seriesA.Count <= lookbackPeriod)
return false;
// Check if seriesA was below value lookbackPeriod bars ago and is above value the following bar.
return seriesA[lookbackPeriod] <= value && seriesA[lookbackPeriod - 1] > value;
}
public static bool CrossBelow(ISeries<double> seriesA, double value, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || seriesA.Count <= lookbackPeriod)
return false;
// Check if seriesA was above value lookbackPeriod bars ago and is below value the following bar.
return seriesA[lookbackPeriod] >= value && seriesA[lookbackPeriod - 1] < value;
}
public static bool IsRising(ISeries<double> series, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || series.Count <= lookbackPeriod)
return false;
// This logic will check each bar in the lookback period is greater than the one preceeding it.
return Enumerable.Range(0, lookbackPeriod - 1).All(i => series[i] > series[i + 1]);
}
public static bool IsFalling(ISeries<double> series, int lookbackPeriod)
{
// Validate the input
if (lookbackPeriod < 1 || series.Count <= lookbackPeriod)
return false;
// This logic will check each bar in the lookback period is less than the one preceeding it.
return Enumerable.Range(0, lookbackPeriod - 1).All(i => series[i] < series[i + 1]);
}
}
}
How to Use them in a Strategy
To use this custom class inside a strategy, or in this case, another custom class, we need to add the namespace to our using directive at the top of our file. This will give us access to the Evaluators class and the methods inside of it. Since they are static classes, they don’t require instantiation. We just need to pass in the correct arguments.
using UtilityFunctions;
public bool EntryLong(EMA ema, ISeries<double> barClose)
{
return Evaluators.CrossAbove(barClose, ema, 1);
}
Summary
While the built-in NinjaScript methods are robust, we explored the rationale and advantages of creating our own evaluator methods. As we develop more functions, we can gain more nuanced control and adaptability in our trade systems. We also embrace the responsibility of creating functional and reliable code. This article not only serves as a technical guide but also underscores the importance of independent thinking. By taking ownership of our tools and methods, we can set ourselves up for growth and success in the ever-changing world of trading.
This article has been made available for all subscribers. If you want to see how I use these methods in strategy development, you can subscribe and gain access to this code and everything else I cover in the community GitHub repository. Subscribers can access the code as I complete it and anything else I work on for HGT. We already have one strategy published, and the next one will come out this week.
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.