Create a Custom Fitness Function for NinjaTrader 8 (NinjaScript)
In this post we will discuss the role of fitness functions in the NinjaTrader 8 platform and create a custom function to be used in optimizing and backtesting trading strategies.
Update:
This artcle 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.
Fitness functions, called Optimization Fitnesses (OF) in NT8, are used during the strategy optimization process. They tell the optimizer what to optimize for, such as net profit, profit factor, drawdown, etc. NT8 comes with many of the standard fitness functions that you need. Still, it also allows you to make your own with NinjaScript.
The profit factor OF has been my go-to fitness function when testing trading strategies. I like it because it factors in net loss and net profit. However, I always compare the drawdown of those results to see what variations are better suited for my risk tolerance. I think it would be cool if we had an OF similar to profit factor that minimized draw down, too, so I created one. With some help from TradeStation, that is.
If you haven't setup your environment for being able to code and debug NinjaScript files you can view the Trade Testing Environment article. Be sure to continuously check out the House Keeping post for updates as well. It is designed to help make navigation around the Substack easier and to keep you informed about what is going on with HGT and the logic behind it. Paid subscribers will have access to this code (and more) on the HGT private GitHub repository. Code will always be updated first on the GitHub before articles get updated and not all the code in the GitHub has an associated tutorial.
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.
The TradeStation Index
In this post, we will recreate the TradeStation Index fitness function for NT8 using NinjaScript. This fitness maximizes net profit and winners while minimizing intraday drawdown.1 It's important to think about keeping these functions as simple as possible. While we could create a multi-objective OF and calculate whatever ratios we think are important into one function, this could lead to overfitting. Ideally, you want to see the same (or similar) parameter optimizations perform well (enough) across different optimizations. In other words, we don't want to design a fitness function that will only show us outliers.
Code
The calculation for the TradeStation Index is:
Net Profit * NumWinTrades / AbsValue (Max. Intraday Drawdown)
We will create a few helper functions to help get the values needed for this OF. We will use these functions to define our calculation and create some simple logic to help remove any funky results. Lastly, we assign the "Value" of the OF the result of our calculation. All of the logic for our calculation will be performed inside the OnCalculatePerformanceValue()
method.2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NinjaTrader.NinjaScript.OptimizationFitnesses
{
internal class TradeStationIndex : OptimizationFitness
{
private double _drawDown;
private double _fitnessFunction;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
Name = "Trade Station Index";
}
protected override void OnCalculatePerformanceValue(StrategyBase strategy)
{
_drawDown = Math.Abs(GetDrawDown(strategy));
if (_drawDown == 0)
{
_drawDown = 1;
}
_fitnessFunction = GetNetProfit(strategy) * GetWins(strategy) / _drawDown;
if (double.IsInfinity(_fitnessFunction) || double.IsNaN(_fitnessFunction))
_fitnessFunction = 0;
Value = _fitnessFunction;
}
#region Helper Funtions
private double GetNetProfit(StrategyBase strategy)
{
return strategy.SystemPerformance.AllTrades.TradesPerformance.NetProfit;
}
private double GetDrawDown(StrategyBase strategy)
{
return strategy.SystemPerformance.AllTrades.TradesPerformance.Currency.Drawdown;
}
private int GetWins(StrategyBase strategy)
{
return strategy.SystemPerformance.AllTrades.WinningTrades.TradesCount;
}
#endregion
}
}
Conclusion
There is nothing incredibly complex about creating this particular Optimization Fitness. In the future, I will experiment with creating some multi-objective optimization functions using some of the more popular metrics that people like to look at when testing their strategies. It would appear that we could also create a custom optimizer to use (instead of the one built-in), if one were inclined to do so.
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.
Interesting. How does this match up against the "strength" fitness function?