NinjaScript for Strategy Traders
by Michael R. Bryant
I've been eager to provide NinjaTrader's® NinjaScript® output for my Adaptrade Builder strategy generating software for some time and can now announce that it's almost ready (see below for an example of a NinjaScript strategy generated by Builder), with an expected release on or around June 30. This article provides a high-level overview of the NinjaScript language and how it compares to the other languages supported by Builder.*
Leveraging an Existing Language
NinjaTrader is a popular trading platform for stock, futures, and forex trading. Like MetaTrader, NinjaTrader is free for simulated trading and strategy development and is compatible with a wide variety of brokers and data providers. One of the reasons behind the popularity of NinjaTrader is the NinjaScript programming language for indicator and strategy development. The versatility and power of NinjaScript has created a large ecosystem of indicator and strategy code that effectively extends the NinjaTrader platform.
NinjaScript is based on the C# (pronounced "C Sharp") programming language developed by Microsoft and first released in 2002. C# is a general-purpose programming language based on the popular C and C++ languages and utilizing Microsoft's .NET framework. Basing NinjaScript on C# means that it can take advantage of all the functionality of C# and.NET and that it can be compiled with existing Microsoft C# compilers. This is different than other scripting languages, such as TradeStation's EasyLanguage, AmiBroker's AFL and MetaTrader 4's MQL4. While these languages all have C-like syntax, particularly MQL4, they're all built from the ground up. In principle, this should provide a design advantage; namely, that they can be custom-designed for trading. However, this has to be balanced against the advantages provided by the foundation of a mature, highly developed language like C#.
The C# and .NET libraries provide such basic features as date/time and math functions. Since C# and .NET have been in development for approximately 14 years, these basic features are quite mature and stable. NinjaScript not only takes advantage of the built-in library functions but the basic language features as well, including conditionals ("if then" branching), loops, functions, and so on. By building on the foundation of an existing, popular language, NinjaTrader leveraged a wide array of capabilities and features while avoiding the risks and pitfalls of developing a scripting language from scratch. The result is a feature-rich, highly capable and reliable scripting language.
A Modern Scripting Language
NinjaScript is consistent with the underlying design of C#, which is object-oriented, event-driven, and component-based. All strategies in NinjaTrader define their own class, which derives from the Strategy class within the NinjaTrader namespace. This is where the functionality added to C# to create NinjaScript is located.
Figure 1. The strategy class declaration is in the NinjaTrader.Strategy namespace. The Initialize function is called once at the start of the strategy.
In C#, most action is precipitated by external events, which trigger "event handlers". In NinjaScript, the main event handler is called OnBarUpdate, which is triggered on the close of each bar or, optionally, at every tick. This is where the main strategy logic resides. The other primary event handler is Initialize, which is triggered at the start of the strategy and is executed just once at that time. Other event handlers can be implemented optionally, such as OnPositionUpdate, which is triggered whenever the trade position changes, and OnOrderUpdate, which is triggered whenever an existing order changes. The ability to execute strategy code when particular events takes place can make it easier to program certain actions in NinjaScript compared to more traditional languages, such as EasyLanguage.
Figure 2. The NinjaScript OnBarUpdate function is called on the close of each bar or on every tick, depending on the setting in Initialize.
Abstraction, Control, and Versatility
All scripting languages for trading make trade-offs in how they abstract certain aspects of trading versus how much control they both give to and require from the user. Trading involves a series of fairly complex actions: evaluating strategy logic on each bar and/or tick, placing multiple trading orders for both entry and exit, tracking and updating the trading orders on each bar/tick, processing and recording fills, and cancelling open orders, including one-cancels-other and contingent orders. A high level of abstraction means that much of this complexity is hidden from the programmer and performed automatically behind-the-scenes, which makes it easier to program but provides less control and versatility. A lower level of abstraction leaves much of the work up to the programmer, which makes the programming more difficult but typically provides more control.
For example, some languages, such as MetaQuotes Language 4 (MQL4), provide fairly basic commands to handle orders and leave it up to the user to cancel open orders following a fill. This can make it difficult to implement a strategy that uses, for example, multiple exits (e.g., a protective stop, a target exit, and an exit based on a logical condition) because when one exit is hit, the other, pending orders have to be cancelled by the strategy code. On the other hand, this thin layer of abstraction provides maximum control and versatility.
AmiBroker Formula Language (AFL), on the other hand, provides a very deep abstraction in its basic form in that the user only provides the conditions under which entry and exit are to take place, and the language (and underlying platform) take care of when and how the actual trading orders are placed and executed. For simpler kinds of strategies, this makes it very easy to code the strategy. However, it provides very little flexibility and control for the strategy developer. In fact, AmiBroker apparently recognized this limitation when it added its so-called "portfolio backtester interface" (custom backtest procedure), which provides access to lower-level functions for controlling things such as order entry. Unfortunately, this separate approach doesn't integrate well with the original design of AFL.
EasyLanguage provides several types of complex abstractions. Inherent in each EasyLanguage strategy is the fact that most of the code, other than the variable initializations, is executed on each bar. If "look-inside back-bar testing" is enabled, an additional abstraction is present in that additional calculations are performed behind-the-scenes to evaluate more often than once every bar, depending on the user's setting. In effect, TradeStation has the equivalent of NinjaScript's OnBarUpdate, but it's hidden from the user and handled behind-the-scenes.
EasyLanguage also handles order processing in a way the minimizes the burden on the user. Multiple orders can be placed in code, and unexecuted ones will be cancelled as if "one-cancels-other" when one is filled. NinjaScript implements nearly identical abstractions for order placement and execution, including automatically cancelling each order at the close of each bar. This is consistent with the fact that the strategy code executes on each bar, which typically means the code statements for placing orders will be repeated on each bar. If the orders didn't automatically expire at the close of each bar, they would have to be cancelled by the strategy before placing new ones.
Array processing is another area that highlights the degree of abstraction among scripting languages. Since indicators and other elements of strategy logic need to be evaluated on each bar of a price chart or data series, scripting languages for trading are inherently array-based; that is, they benefit from performing calculations on arrays of price data, rather than performing the calculations bar-by-bar.
For example, if a strategy includes a moving average and a stochastic, one approach would be to calculate the moving average on the first bar, calculate the stochastic on the same bar, evaluate the strategy logic for that bar, then move to the next bar. However, it's much more efficient to calculate the moving average for every bar on the chart, then calculate the stochastic for every bar on the chart, then evaluate the strategy logic. The latter is an example of array processing, which can usually be performed behind-the-scenes even if it's not apparent from the way the code is written.
EasyLanguage provides the best example of this: the array processing is abstracted away so that the user doesn't have to be concerned with it. For example, when you code a function in EasyLanguage, you write the calculations to be performed on the current bar. It's up to the EasyLanguage compiler to make the calculations efficient, presumably through array processing. In AFL, on the other hand, the array processing is designed to be convenient but is explicit in that you have to be aware that you're processing arrays of data and take this into account in how you write the code.
NinjaScript is similar to MQL 4 in that the use of arrays is mostly explicit. However, in MQL4, the use of arbitrary arrays as inputs to indicators, such as a moving average of something other than price, is handled awkwardly, making it difficult to evaluate indicators of indicators, such as a moving average of a stochastic. NinjaScript overcomes this potential problem by providing a special array type called a DataSeries, which can be defined in the strategy code and which is accepted as an input by any indicator that inherently takes price as an input. This makes it relatively easy to code nested indicators.
Striking a Balance
As suggested above, all scripting languages for trading strike a balance somewhere along the spectrum between a highly abstracted language that handles a lot behind-the-scenes (e.g., AFL) and a thinly abstracted language that requires the user to specify each step of the trading process (e.g., MQL4). NinjaScript is much like EasyLanguage in that it falls somewhere in the middle.
Compared to EasyLanguage, NinjaScript arguably makes at least some of its abstractions clearer while not making them much more complex. For example, the code that executes on each bar is obvious in that it resides in the OnBarUpdate function. In EasyLanguage, it may not be immediately apparent to a newcomer that most of the strategy code is executed on the close of each bar and that the variables don't re-initialize on each bar. In NinjaScript, the variables are not part of OnBarUpdate, so it's clear that any initialization performed outside of that function, such as in the Initialize function, happens only once. On the other hand, EasyLanguage provides a higher overall level of abstraction, which can make it easier to code once the design features of the language are understood.
From the standpoint of a programmer, there' s much to admire about NinjaScript. Its design is consistent with modern programming conventions, it's easily extensible, it provides convenient trading abstractions for things such as order placement, and still manages to be clear about what the code does on a bar-by-bar basis. Moreover, it leverages the .NET library and C# language features.
Having programmed EasyLanguage for some 20 years, it's difficult to evaluate other scripting languages with fresh eyes. However, if I were learning to program trading strategies today – and putting aside any considerations related to the different trading platforms – I might very well choose NinjaScript.
* This article is not intended to be tutorial in nature and does not discuss NinjaTrader platform features.
NinjaTrader and NinjaScript are registered trademarks of NinjaTrader LLC.
This article appeared in the May 2014 issue of the Adaptrade Software newsletter.
If you'd like to be informed of new developments, news, and special offers from Adaptrade Software, please join our email list. Thank you.
For Email Marketing you can trust
Copyright (c) 2004-2019 Adaptrade Software. All rights reserved.