Type Converters and NinjaScript
Learn how to create custom Type Converters to be used with NinjaScript UI’s property grid. Notes on using traditional coding statements, ternary operators, and LINQ queries.
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.
You read it right: type converters. That’s where we are going today. Why? Because I like to suffer and because it shouldn’t be too hard to make a property read-only. Right? Well, it shouldn’t have been. Hot take: this would have been easier in JavaScript (TypeScript). But this isn’t a SubStack about web (or web-adjacent) development, is it?
In NinjaScript, we use NinjaScriptProperties
to expose user-definable variables such as stop (SL) and profit (TP) information. What if we have more than one exit method in our strategy, and when we switch it to trail or none, we want to make the SL/TP fields read-only? That’s what we will be discussing today.
This post will cover how to turn the SL/TP profits fields in a NinjaScript strategy to read-only when they aren’t necessary. We will also cover different techniques to make the code more concise and readable (subjective). The code will be broken into parts to make it easier to understand what each function is doing.
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
Creating this Type Converter took a lot more research and testing than I anticipated, but in the process, I learned some new ways to code that I want to share. I also believe I made something that should be easily convertible for future needs. I will admit that I don’t know if there are any merits in getting this detailed with strategy development in the research phase, as it was time-consuming and did nothing to help the strategy itself. It is a nice feature, though, and if you want to share strategies (or sell them if that’s your goal), it becomes more beneficial. The complete code will be available at the end of the article. The code will be added to GitHub once I have finalized January’s strategy. This type converter was made for the upcoming strategy, 9X. Followers of ATS might know that this is one of the Twilight Strategies, a strategy that never worked in the forward test. This strategy drop will bring updates to the repository’s code style and folder structure.
Strategy Parameters
The following is an example of the code in the strategy itself. Since the type converter uses properties from the strategy, they need to be typed the same way they are used in the converter class. You will notice that the ExitType
property has RefreshProperties(RefreshProperties.All)
added to it. This is so the UI grid will refresh everything whenever you change it. That allows it to make its calculations to determine whether or not SL/TP needs to be usable.
private double _stopTicks = 100;
private double _profitTicks = 20;
public enum ExitType
{
None,
Static,
TrailATR
}
[RefreshProperties(RefreshProperties.All), NinjaScriptProperty]
[Display(Name = "Exit Type", Order = 1, GroupName = "Order Management")]
public ExitType MyExitType
{
get { return _exitType; }
set { _exitType = 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; }
}
ModifyParameters
This is the first class we need to create in our Type Converter. We use it to assign the names of the properties we wish to change. The actual string (between the quotes) needs to be the same name as the variable for the property in the specific strategy.
public class ModifyParameters
{
public static string SL = "SL";
public static string TP = "TP";
}
This image illustrates where to find the correct name for the property.
Custom Converter
The following code is the main Type Converter class Converter9X
. It must inherit from the strategy base converter to be used correctly with a NinjaScript strategy. This class is also heavy on its use of ternary operations and LINQ. This is done for a few reasons. There are only a few places where changes need to be made to use it for different purposes, and it is far less code than the traditional way. However, it becomes more difficult to debug. I will provide an example of some of the code before it was converted and show how the traditional way allows for easier debugging during the development process.
public class Converter9X : StrategyBaseConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object component, Attribute[] attrs)
{
Strategy9X strategy = component as Strategy9X;
PropertyDescriptorCollection propertyDescriptorCollection = base.GetPropertiesSupported(context)
? base.GetProperties(context, component, attrs)
: TypeDescriptor.GetProperties(component, attrs);
return strategy == null || propertyDescriptorCollection == null
? propertyDescriptorCollection
: new PropertyDescriptorCollection(
propertyDescriptorCollection
.Cast<PropertyDescriptor>()
.Select(property =>
property.Name == ModifyParameters.SL || property.Name == ModifyParameters.TP
? new ReadOnlyPropertyDescriptor(strategy, property)
: property)
.ToArray());
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
}
Ternary Operator and Language Integrated Query (LINQ)
The ternary or conditional operator is a concise way to express simple conditional logic, such as if-else statements, in many coding languages, including C#. It is represented as condition ? expression1 : expression2
. It evaluates the condition and returns expression1
if true and expression2
if false. It can replace simple if-else statements and is particularly useful in assignment and return statements when used correctly. Both are used in the code example above.
LINQ is a .NET feature that allows us to query data in a more expressive way.1 It brings query capabilities to the C# (and thus NinjaScript) language itself and allows for querying various sources of data in a consistent manner. It is written in a functional style similar to SQL, helping readability. We use it above to filter our property list for the SL/TP properties and apply some custom changes.
The original code for looping through the propertyDescriptorCollection
is below. You can see the commented-out portions where I used the Output windows in NT8 to see if the function was correctly looping through the properties. This was very helpful when I initially coded it.
// Create a new collection to hold only the properties you want
PropertyDescriptorCollection filteredProperties = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor property in propertyDescriptorCollection)
{
// Check if the property is one of the properties you want to modify
if (property.Name == ModifyParameters.SL || property.Name == ModifyParameters.TP)
{
var readOnlyProperty = new ReadOnlyPropertyDescriptor(strategy, property);
filteredProperties.Add(readOnlyProperty);
//Output.Process(property.Name, PrintTo.OutputTab1);
}
else
{
// Add other properties unmodified
filteredProperties.Add(property);
//Output.Process(property.Name, PrintTo.OutputTab1);
}
}
If you were to just update the previous code section to use LINQ, it would look like this next section of code.
PropertyDescriptorCollection filteredProperties = new PropertyDescriptorCollection(
propertyDescriptorCollection
.Cast<PropertyDescriptor>()
.Select(property =>
property.Name == ModifyParameters.SL || property.Name == ModifyParameters.TP
? new ReadOnlyPropertyDescriptor(strategy, property)
: property)
.ToArray());
As you can see in the finished code above, I simplified this even more by turning the entire function into a single return statement. This removes any ambiguity in the code and leaves a single path for returning a value.
ReadOnlyPropertyDesciptor
This class is going to inherit from the abstract PropertyDescriptor
class. This is part of the System.ComponentModel
namespace in .NET.2 As an abstract class, it serves as a foundation for defining characteristics and behaviors of properties inside classes. It’s useful for type converting, so we will use it here.
Everything in the section goes into this custom class.
public class ReadOnlyPropertyDescriptor : PropertyDescriptor
{
// The custom logic. All the rections below are inside this class.
}
Create the variables and class constructor. We will need to pass in the instance of the strategy and the property descriptor obtained in the first class. We set the variables to the values passed into the function at instantiation.
private Strategy9X _strategyInstance;
private PropertyDescriptor _property;
public ReadOnlyPropertyDescriptor(Strategy9X strategy, PropertyDescriptor propertyDescriptor)
: base(propertyDescriptor.Name, propertyDescriptor.Attributes.OfType<Attribute>().ToArray())
{
_strategyInstance = strategy;
_property = propertyDescriptor;
}
Next, we need to override the GetValue()
object and set it to return the values we need. We will check this by checking the name of the property against the names we established in the ModifyParametrs()
function above.
public override object GetValue(object component)
{
Strategy9X targetInstance = component as Strategy9X;
if (targetInstance == null)
{
return null;
}
else if (_property.Name == ModifyParameters.SL)
{
return targetInstance.SL;
}
else if (_property.Name == ModifyParameters.TP)
{
return targetInstance.TP;
}
else
{
return null;
}
}
After we get the values, we need to set them.
public override void SetValue(object component, object value)
{
Strategy9X targetInstance = component as Strategy9X;
if (targetInstance != null)
{
if (_property.Name == ModifyParameters.SL)
targetInstance.SL = (double)value;
else if (_property.Name == ModifyParameters.TP) targetInstance.TP = (double)value;
}
}
The read-only override is where the main logic of this class is located. It determines whether or not to make a property read-only. This can be simplified, but I have left it in this verbose state to help illustrate the logic. If desired, it can be easily turned into a single return statement. I believe that leaving it like this is beneficial as a template. It can be altered and refactored as necessary for whatever the need.
public override bool IsReadOnly
{
get
{
if (_property.Name == ModifyParameters.SL || _property.Name == ModifyParameters.TP)
{
if (_strategyInstance.MyExitType != Strategy9X.ExitType.Static)
{
return true;
}
else
{
return false;
}
}
return false;
}
}
The last overrides. These are required by the inherited abstract class PropertyDescriptor
. The two to note are the PropertyType
and ComponentType
. The PropertyType
is dynamically defined so the function can be applied to any type of property (int, double, etc.). The ComponentType
is written to always return the object that represents the strategy class. It’s probably possible to write this dynamically too.
public override Type PropertyType {get { return _property.PropertyType; } }
public override Type ComponentType {get { return typeof(Strategy9X); } }
public override bool CanResetValue(object component) => true;
public override void ResetValue(object component) {}
public override bool ShouldSerializeValue(object component) => true;
The completed project.
using System;
using System.ComponentModel;
using System.Linq;
using NinjaTrader.Code;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Strategies;
namespace TypeConverters
{
public class ModifyParameters
{
public static string SL = "SL";
public static string TP = "TP";
}
public class Converter9X : StrategyBaseConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object component, Attribute[] attrs)
{
Strategy9X strategy = component as Strategy9X;
PropertyDescriptorCollection propertyDescriptorCollection = base.GetPropertiesSupported(context)
? base.GetProperties(context, component, attrs)
: TypeDescriptor.GetProperties(component, attrs);
if (strategy == null || propertyDescriptorCollection == null)
return propertyDescriptorCollection;
PropertyDescriptorCollection filteredProperties = new PropertyDescriptorCollection(
propertyDescriptorCollection
.Cast<PropertyDescriptor>()
.Select(property =>
property.Name == ModifyParameters.SL || property.Name == ModifyParameters.TP
? new ReadOnlyPropertyDescriptor(strategy, property)
: property)
.ToArray());
return filteredProperties;
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
}
public class ReadOnlyPropertyDescriptor : PropertyDescriptor
{
private Strategy9X _strategyInstance;
private PropertyDescriptor _property;
public ReadOnlyPropertyDescriptor(Strategy9X strategy, PropertyDescriptor propertyDescriptor)
: base(propertyDescriptor.Name, propertyDescriptor.Attributes.OfType<Attribute>().ToArray())
{
_strategyInstance = strategy;
_property = propertyDescriptor;
}
public override object GetValue(object component)
{
Strategy9X targetInstance = component as Strategy9X;
if (targetInstance == null)
{
return null;
}
else if (_property.Name == ModifyParameters.SL)
{
return targetInstance.SL;
}
else if (_property.Name == ModifyParameters.TP)
{
return targetInstance.TP;
}
else
{
return null;
}
}
public override void SetValue(object component, object value)
{
Strategy9X targetInstance = component as Strategy9X;
if (targetInstance != null)
{
if (_property.Name == ModifyParameters.SL) targetInstance.SL = (double)value;
else if (_property.Name == ModifyParameters.TP) targetInstance.TP = (double)value;
}
}
public override bool IsReadOnly
{
get
{
if (_property.Name == ModifyParameters.SL || _property.Name == ModifyParameters.TP)
{
if (_strategyInstance.MyExitType != Strategy9X.ExitType.Static)
{
return true;
}
else
{
return false;
}
}
return false;
}
}
public override Type PropertyType {get { return typeof(double); } }
public override Type ComponentType {get { return typeof(Strategy9X); } }
public override bool CanResetValue(object component) => true;
public override void ResetValue(object component) {}
public override bool ShouldSerializeValue(object component) => true;
}
}
Adding the Type Converter to the Strategy
To use this converter in your strategy it needs to be added above the strategy class. The following image depicts how to add it to the strategy.
Summary
Today, we learned how to code and implement a type converter for use in our NinjaScript strategies. This required about 115 lines of code in its current state. Getting it to that low of a number required using the ternary operator and LINQ queries.
This is a nice feature to have when creating and testing automated trade systems. However, it would be nice to implement it in different strategies without having to recreate this code for each one. I believe this is possible if we create an abstract strategy class that extends the NinjaScript Strategy class. This could be the template style of the future for HGT. For now, we will continue to do it this way.
Strategy 9X will drop next week. This strategy drop will be accompanied by one or two more posts that discuss creating our own custom classes to hold strategy logic and order management functions. Our previous strategy, Strategy 44X, had almost 700 lines of code in it. By removing as much logic from the strategy class as possible, we can get this down to a much smaller number while making the development of future strategies that much easier.
I am also working on a simple EMA crossover strategy I intend to publish once finished. Its purpose is to create an easy strategy to practice different trade management techniques and illustrate how even a common strategy can be used to create a functioning automated trade system.
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.
I managed to turn this into a generic type converter that will work on any strategy that includes the correct properties and enums. Next step would be to see if I can create an abstract strategy class of my own that supplies a base set of properties and parameters so I don't have to type in the same properties each time I want to develop a strategy.
Dont see the new code in Github Repo