In this step-by-step guide we show how to use a Test Driven Development (TDD) approach when developing indicators and strategies for NinjaTrader 8 (NT8) in its scripting language NinjaScript. We will use NUnit as the testing framework and Microsoft Visual Studio (VS) Community Edition as an IDE.
Let's get started.
Prerequisites
Make sure to have the following in place:
NinjaTrader 8 installed
Visual Studio (VS) Community Edition installed in either in the 2019 or 2022 version
In a nutshell we will...
Open the Custom project in NT and VS
Create a Testproject in VS
Create the relevant references between the two projects
Establish a few test cases and test (fail)
Write the code to pass the cases (pass)
Embed our code inside a Strategy
...and talk about the gotchas along the way.
Let's get started...
Open NinjaTrader and a new NinjaScript Editor window. To do that, in NT navigate to New -> NinjaScript Editor.
Next open Visual Studio and open the Custom Project. You have two options to accomplish this:
In the NinjaScript Editor click on the little Visual Studio icon. With a bit of luck VS2019 will open already with the custom project open.
Alternatively in the initial VS splash screen, choose "Open a project or solution" and navigate to the following path (in Win10): Documents\NinjaTrader 8\bin\Custom
Once in the Custom folder, select the NinjaTrader.Custom.csproj file and open it in VS.
Note that the "Documents" portion you can usually replace with C:\Users\<YourUserName>\Documents
Create a test project in the NinjaTrader.Custom Solution
As you may have noticed, VS has opened the Custom project within the NinjaTrader.Custom Solution. As the next step we are creating a test project within this Solution, parallel to the Custom project. Start with a right-click on the Solution, navigate to Add -> New Project...
From here search for and select a new "NUnit Test Project". Make sure to pick the C# variety. (I presume the other test frameworks work the same, but not tested.)
Next give your Testproject a name and a location. As you could end up having a test project for each Indicator and Strategy, it makes sense to think about your naming convention here. A suggestion could be:
<yourInitials>.Indi.<Indicatorname>.Tests
<yourInitials>.Strat.<Strategyname>.Tests
For the Location a common practice is to put your (test) projects in parallel to the Custom project. So inside of NinjaTrader 8\bin.
Select the .NET framework 4.5 (I've had no issues todate with 4.8) and click Create.
Save your Solution
At this point VS may prompt you to save you NinjaTrader.Custom Solution. You can savely save this unmodified.
Congratulation you have now created a stand-along test project. -> Celebrate :)
Now we want to be able to run our tests against our NinjaTrader codebase. To do this we need references by which the Testproject gains access to the Ninjatrader functionality. Three references need to be set on the Testproject.
First right-click your Testproject and navigate to Add -> Project Reference...
Select your NinjaTrader.Custom Solution.
Next right-click your Testproject and navigate to Add -> Assembly Reference...
Click on Browse (bottom of the window) and navigate to
C:\Program Files (x86)\NinjaTrader 8\bin
GOTCHA LURKING: The Assembly References are in the Program Files folder, not your Documents folder!!
Select the files: NinjaTrader.Core.dll and NinjaTrader.Gui.dll
Click Add.
...and click OK to complete the references for our Testproject.
Time for a recap - what have we done so far?
We have created a test project and linked this to the NinjaTrader DLLs and the Custom project. By doing so our Testproject can now use the Classes provided in the Namespace(s) used by NinjaTrader. So next we will write some code with tests.
How to approach the testing with NinjaTrader
Here comes the catch - You cannot test the NinjaTrader built-in functionality. This is partly because without running the Ninjatrader software you cannot properly instantiate Indicators and Strategies. Well that sucks. So I've wasted all my time reading this??? Well not all hope is lost.
The trick is to leave the code within the indicators and strategies as slim as possible and encapsulate all your functionality into your own Class(es). These can then be tested and can be used by the strategy without problem.
By convention here at ScaleInTrading we place our SiT classes into the same file that the associated Strategy resides in, just below the Strategy Class but still within the NinjaTrader.NinjaScript.Strategies namespace. An alternative approach could be to place them in the AddOns folder.
Our First Testcase
In our scenario we want to write a strategy that goes Long when a bar closed higher than its open. Except for a standard test harness we are so far on a blank canvas, so let's create some test cases (Yes! Before having written the code...). This article is not really about how to write tests or NUnit, so we will use the following simple test case. All the NUnit bells and whistles can be done though:
using NUnit.Framework; namespace SiT.Strat.SimpleEntry.Test { public class Tests { [Test] public void IsHigherClose_InvalidInputs_ReturnsFalse() { //The following class does not yet exist, neither does the method.
TddDemoStrategyCustom tddDemo = new TddDemoStrategyCustom(); var currentClose = 0; //We would never expect price to be 0. var priorClose = 12114; bool result = tddDemo.IsHigherClose(currentClose, priorClose); Assert.IsFalse(result); } } }
So at this stage we would like to run a failing (!) test. However we have a few compile error which need to be cleaned up first.
Gotcha Lurking: If you followed all of the above on a NT Installation with a lot of history, you may find, that you have excluded Indicators and Strategies from Compilation in the NinjaScript Editor (right-click 'Exclude from Compilation', which adds a @@ in front of the file name.). Visual Studio does not respect this nomenclature and will try to compile your potentially broken code snippets. Make sure that all compile errors are resolved in Visual Studio, otherwise the unit testing will not work! An easy way could be moving your @@ files out of the Custom folder all together into a folder under bin called "Under Development" or similar. Either way - the code has to compile in VS!
Creating your Strategy Helper Class
Ok, to fix up the compile errors we will use the VS refactoring magic to create the relevant code components. Right-click over the underlined Class name and click on "Quick Actions..."
...and extract the Class signature by clicking on "Generate Class..."
Now proceed just the same with the Method name that is also underlined in red and magically a fitting (but so far empty) method signature is created for you. At this point we should be ready to run a test with all code compiling without errors.
Save the test file (CTRL-S)
Build the Solution (F6)
Let's do some testing.
Open the Test Explorer in VS by navigating to Test -> Test Explorer
When you open the Tests on the left-hand side you can explore your test structure down to the test we have just written. The blue exclamation icons indicate the test has not been run so far.
Click on the Green Double-Play Icon at the top-left to run our test. Remember we are expecting this to fail since the method is not implemented yet.
Let's implement the method to satisfy our test.
The following code should do the job...
internal class TddDemoStrategyCustom
{
internal bool IsHigherClose(double currentClose, double priorClose)
{
if(currentClose == 0 || priorClose == 0)
return false;
return currentClose >= priorClose;
}
}
Now let's re-run our test suite aaaannd... it failed again. Well here is another "Gotcha Lurking". We forgot to re-compile the code which became obvious when clicking on the IsHigherClose Testcase which still showed a System.NotImplementedException.
So, F6 Compile and try again...Evoila!
Ok, so far so good.
Bring the CodeBase together
Now we need to still use our newly written function within our NinjaTrader Strategy. So firstly we move the Class code for our Helper Class from our test file into the strategy file. Simple cut & paste will do. Paste your class code into the NinjaTrader.NinjaScript.Strategies namespace, but below (and not into) your strategy class.
For the test script to be able to access your Class and your method, both should be public, so let's change the scope for both accordingly:
public class TddDemoStrategyCustom
{
public bool IsHigherClose(double currentClose, double priorClose) {... }
}
Further we need to make sure that the UnitTest project/class can find your Class. To do this let's tell the UnitTest Class to use the NinjaTrader.NinjaScript.Strategies namespace. So add at the UnitTest1.cs the following at the top:
using NUnit.Framework; using NinjaTrader.NinjaScript.Strategies;
Ok, at this point we should be able to re-run our test to validate everything still works as expected. Save all files (CTRL-S), hit F6 (Build Solution) and then CTRL-R & V (run all tests).
Once everything checks out let's use our new function in our strategy.
Using your code within the NinjaTrader Strategy
Firstly we need to instantiate our own class within the NT strategy. So let's create a class variable to access an instance of our Class within the strategy like so right after the Class declaration:
TddDemoStrategyCustom my = new TddDemoStrategyCustom();
Then let's implement the strategy logic in OnBarUpdate():
protected override void OnBarUpdate()...and that is pretty much it! You now have a unit tested piece of code running in Ninjatrader and are set up to happily practice Test Driven Development (TDD) in NT8.
{
if (State == State.Historical || CurrentBar < BarsRequiredToTrade) return;
if (my.IsHigherClose(Close[0], Close[1]))
{
EnterLong();
}
}
A few final remarks
It is not possible to instantiate the strategy itself without the NT framework live and running around it. Hence we pull every bit of logic to test out of the Strategy itself into our own helper class which is happy to be tested.
Don't bother writing tests for the NT8 functionality itself. The friendly folks at NinjaTrader are doing this for you already.
The above article is obviously just a glimpse at how to get started, but here the fun of TDD really only starts
The approach taken here would suggest the following viewpoint to Strategy and Indicator development in Ninjatrader (happy to hear your thoughts!):
Apart from the native NinjaScript Event Methods there should be no methods added to the strategy itself. They should all live in your helper class and get called from OnBarUpdate and Co.
All properties and bar objects etc. should be passed to your methods via dependency injection. That will allow for fakes, stubs and all the TDD magic and will automatically help make your code more testable.
It should be noted that VS runs its tests against a dll of the Custom project compiled by VS under the following path:
C:\Users\<username>\Documents\NinjaTrader 8\bin\Custom\bin\Debug
If VS cannot compile the code, then this path is empty and the tests cannot be run. On the flipside once NT picks up changes in its operating directory it recompiles its version of the Custom project which is located as a dll under
C:\Users\<username>\Documents\NinjaTrader 8\bin\Custom
The two file versions usually live happily next to each other, but sometimes it's good to keep in mind that there are two files at play (for instance if you have compiled in VS but NT has not yet, you would be working off different files.)Lastly please check out some of the work we do to improve our own trading. Especially our Power Scale-In approach might just blow your mind...
If you found this helpful, share the article with your friends and colleagues!