NinjaScript Best Practices
<< Click to Display Table of Contents >> NinjaScript Best Practices |
There are some best practices to be aware of when developing NinjaScript classes. The following tables present a non-exhaustive list of considerations to keep in mind when designing and implementing your code.
Note: NinjaTrader is multi-threaded and event driven. Always assume that any of the methods you implement in NinjaScript could be called from another thread. |
Managing ResourcesThe OnStateChange() method is called anytime there has been a change of State and can be used to help you setup, manage, and destroy several types of resources. Where these values are setup is highly dependent on the kind of resource you are using. The section below will cover how to manage various resources throughout different states.
Setting Default UI Property Grid valuesReserve State.SetDefaults for defaulting any public properties you wish to have exposed on the UI property grid. You should also use this State for setting default desired NinjaScript property behavior which can be overridden from the property grid (e.g. Calculate, IsOverlay, etc.). For Plots and Lines you wish to configure, AddPlot(), AddLine() should also have their default values set during this State
For public properties you do NOT wish exposed to the UI property grid, set the Browsable attribute to false:
On indicators, properties you wish to set from other objects, set the NinjaScriptPropertyAttribute:
The default behavior is to serialize any public properties and fields to a Workspace or Template file when saving. However, not all objects can be serialized - or you may wish to exclude a property from being saved and restored. For these scenarios, set the XMLIgnore attribute to the property:
As a best practice as well, your NinjaScript should not have any public fields, since those would get serialized as well - which means their state would be persisted, which in turn could lead to unexpected outcomes.
Calculating run-time object values
Setting class level variablesDo not set variables at the class level unless they are constant. You should delay setting or resetting variables until the State has reached State.Configure. You can use const keyword to differentiate values which do not change from variables which do change.
Resetting class level variables for Strategy Analyzer OptimizationTo take advantage of performance optimizations, developers may need to reset class level variables in the strategy otherwise unexpected results can occur.
Accessing properties related to market dataDo not attempt to access objects related to instrument market data until the State has reached State.DataLoaded
Setting up resources that rely on market dataFor objects which depend on market data, delay their construction until the State has reached State.DataLoaded
Accessing element on the UIFor objects which exist on the UI (e.g., ChartControl, ChartPanel, ChartBars, NTWindow, etc.) wait until the State has reached State.Historical. This practice is correct for both reading properties or should you wish to add custom elements to the existing UI.
Transitioning order references from historical to real-timeWhen dealing with strategy based orders which have transitioned from historical to real-time, you will need to ensure that locally stored order references are also updated.
Terminating custom resourcesUse a flag to track when resources have been set up properly before attempting to destroy them.
|
Safely accessing reference objectsAlthough there are documented States where objects are available, the implementation could change. If you are accessing a reference object, please do so by first checking that the object is not null.
Accessing objects which terminateTo protect against race conditions and access errors, you should temporarily check for reference errors any time you attempt to do something with an object.
Proving instructions for non-ninjascript propertiesDo not attempt to modify existing UI "Properties" to meet your specific needs. These features are exposed to allow you to read the environment state and make decisions to alter how your code executes, but should not be relied on to modify settings on behalf of the user. While these objects from these classes have setters for technical reasons, you should not attempt to amend the values through code. Instead, you should issue warnings or log errors instructing users to modify settings when required:
Modifying UI elements and multi-threadingWhen interacting with UI objects, such as obtaining UI information, or modifying the existing layout, always use the NinjaScript's Dispatcher asynchronously
Properly implementing try/catch blocksUnless you are specifically debugging a method, the use of a try-catch block should be scoped to a particular area of logic. Do NOT try to handle all of your execution logic under one giant try-catch block.
Using WPF brushesTry to use a static predefined Brush if possible. If you need to customize a new brush object, make sure to .Freeze() the brush before using it.
barsAgo indexer vs. absolute bar IndexAs you probably know, you can quickly look up the bar value on the chart by calling a PriceSeries<T> barsAgo indexer, e.g., Close[0]. However, the internal indexer and pointers about the barsAgo value are only guaranteed to be correctly synced and updated during a market data event. As a result, you should favor using the absolute GetValueAt() methods during events which are not driven by price
Casting safelyAvoid type casting and type conversion as much as possible. Casting from a mixed collection of types is also prone to exceptions especially in situations that may not occur when you originally test your code.
If you must cast, do so safely and avoid implicit casts to types which may not be guaranteed to succeeded
|
Referencing indicator methodsIn general, when calling an Indicator return method, there is some internal caching which occurs by design to help reduce memory consumption.
If you are reusing an indicator several times through your code (especially indicators with many parameters), you can take further steps to refine performance by storing a reference to the indicator instance yourself (although it is by no means a requirement, and this suggestion does not need to be followed strictly)
Marking object references for garbage collectionWhile it is not always necessary to set objects to null, doing so will mark them for garbage collection sooner and help prevent unnecessary memory resources from being utilized.
Disposing of custom resourcesDispose of objects that inherit from IDisposable or put into a Using statement.
Avoiding duplicate calculationsBe mindful where and when your potentially complex calculations would be recalculated and thus run the risk of being calculated redundantly. For example, you may have logic which only needs to calculate, e.g., once per instance, once per session, once per bar, etc.
The same considerations would apply to variables or function calls that would not change their output value for the currently processed bar on Calculate.OnEachTick or .OnPriceChange, thus there would be no need handling them outside of IsFirstTickOfBar
Caching values on bars which remove last barBuilding on the previous example, be careful when caching values on the first tick of bar if using bars types which are IsRemoveLastBarSupported. To see how to handle these situations best, take a look at the default SMA indicator which has an additional logic branch which disables caching on those bar types:
Precomputing values instead of calculating in OnRender()To preserve good performance, always err on the side of caution if you are using OnRender for any calculation logic.
Restricting OnRender() calculations to visible ChartBarsUse the ChartBars.FromIndex and ChartBars.ToIndex to limit calculations to only what is visible on the chart
Using DrawObjects vs custom graphics in OnRender()When using Draw methods, a new instance of the Draw object is created including its custom rendering and calculation logic. These methods are convenient in many situations, but can quickly introduce performance issues if used too liberally. In some situations, you may see better performance for rendering via SharpDX in OnRender().
With just a little extra code (much less than what is in the Draw methods) custom SharpDX rendering greatly reduces CPU and Memory consumption
Please ensure a Direct2D1 factory would only be instantiated from OnRender() or OnRenderTargetChanged() (which run in the UI thread), as access from other threads outside those methods could cause a degradation in performance.
Responding to user eventsDo NOT use OnRender() for purposes other than rendering. If you need events to hook into user interactions, consider adding your own event handler. The example below shows registering the ChartPanel MouseDown event and registering a custom WPF control
Delaying logic for a particular time intervalDo NOT call Thread.Sleep() as it will lock the Instrument thread executing your NinjaScript object.
Instead, try using a Timer object if you need to delay logic execution.
|
Floating-point comparisonBe aware of floating-point precision problems. It can sometimes be more reliable to check within a certain degree of tolerance, such as the TickSize.
Creating user defined parameter types / enumsWhen creating enums for your NinjaScript objects, it is strongly suggested to define those outside the class and in a custom namespace. A reference sample providing all details could be found here.
Efficiently debuggingExtremely liberal use of Log() and Print() methods can represent a performance hit on your PC as it takes memory and time to process each one of those method calls. When running custom NinjaScript, especially when using Calculate = Calculate.OnEachTick, please be mindful of how often Log() and Print() methods are processed as it can quickly consume PC resources.
•Log() method should not be used except for critical messages as each log entry makes it to the Control Center log which stays active till the end of the day. Excessive logging can result in huge amounts of memory being allocated just to display all the log messages which would mean less memory for NinjaTrader to do other tasks. •Print() method can be used more liberally than the Log() method, but can still represent a performance hit if used with extremely high frequency. Consider decreasing the printing from your script if you experience slowdowns when running the script.
Debug ModeThe debug mode should only be used if you are actively debugging a script and attached to a debugger.
To disable Debug Mode: •Right mouse click in any NinjaScript Editor •Ensure the "Debug Mode" menu item is unchecked •Press F5 to recompile your scripts •Your scripts will be re-built using "Release" mode
Known NinjaScript Wrappers limitations•The NinjaScript editor detects code changes in external editors, and will compile on code changes, however code will only be automatically generated by the NinjaScript editor if it's edited within the NinjaScript editor itself (or Visual Studio) •Wrappers cannot be generated automatically for partial and abstract classes •Code in the Properties region of the NinjaScript object cannot be commented out with the /* */ style commenting, as it will cause issues with the wrapper generation. Code must be commented out with the // style. •Subclassing would not allow for wrappers to be generated |