Learn NinjaScript: Creating a Utility File for Shared Functions
Once you’ve copy-pasted the same ATR helper, the same session-time check, or the same WPF brush-freeze snippet into three indicators, it’s time for a shared utility file. Duplicating code is tolerable — until the third time you fix a bug and have to remember which of the six files needs the same patch. A utility file pays for itself the first time you fix a subtle math bug in one place instead of three.
This post covers the AddOns folder, the public static class pattern for stateless helpers, what belongs in each and what doesn’t, and the compilation-order pitfalls that bite when one indicator depends on another file. The anchor is a tiny TradingUtils with three helpers — time, brush, and math — and an indicator that consumes them.
📂 The AddOns Folder
Shared helper code lives in Documents\NinjaTrader 8\bin\Custom\AddOns\. NT compiles everything in AddOns/ as part of the NinjaTrader.Custom assembly alongside your indicators, so anything defined there is visible to every indicator, strategy, or add-on in the same assembly. Compilation is atomic — if one AddOn has a syntax error, nothing in the Custom assembly compiles.
Why not dump shared code into Indicators/? A few reasons. First, NT’s compilation report lists every file with errors by folder, and mixing “indicator” with “utility” muddies that. Second, an indicator file that only defines helpers will still show up in the Indicator list on the chart toolbar — users see clutter. Third, the conceptual split (indicators do things; AddOns support things) keeps the codebase legible as it grows.
When in doubt, here’s the cheat sheet:
Where to Put Shared Code
| Location | Use For | When Not To |
|---|---|---|
| AddOns/ folder | Stateless helpers reused across multiple indicators or strategies. Constants, math, brush utilities, session-time checks, serialization helpers. | When the helper needs to reach into indicator state directly (Bars, Time[0], CurrentBar). Pass those in as parameters instead. |
| Indicators/ folder — private method | Logic specific to one indicator, factored out for readability within that file. | When you catch yourself copying the same snippet into a second indicator. That is the signal to promote it to an AddOn. |
| Strategies/ folder | Strategy-only code — custom order-management, position-tracking helpers that only a strategy ever calls. | When the same helper is needed by indicators too. Move the shared parts to AddOns and keep only strategy-specific glue here. |
| Base class inheritance | Rarely the right answer in NinjaScript. Breaks the single-file distribution model that most shared indicators rely on. | Almost always. Static AddOns helpers are the NT-idiomatic way to share code and keep distribution simple. |
| Separate NinjaScript assembly (DLL) | Proprietary helpers you explicitly want compiled and distributed as binary. | For open-source or user-modifiable code. Users cannot tweak a DLL; they can edit a source-file AddOn. |
🏗️ The Static Class Pattern
Utility code should almost always be a public static class with public static methods. Static means no instance state, which forces you to design helpers that take everything they need as parameters — which happens to also be what makes them reusable across indicators and safe to call from multiple threads:
using System;
using System.Windows.Media;
namespace NinjaTrader.NinjaScript.AddOns
{
public static class TradingUtils
{
/// <summary>True if the given time falls within the RTH session
/// for US index futures (9:30 – 16:00 Eastern).</summary>
public static bool InRegularSession(DateTime t)
{
TimeSpan start = new TimeSpan(9, 30, 0);
TimeSpan end = new TimeSpan(16, 0, 0);
TimeSpan tod = t.TimeOfDay;
return tod >= start && tod < end;
}
/// <summary>Returns a frozen SolidColorBrush with the given
/// ARGB. Safe to assign to a WPF property.</summary>
public static Brush FrozenBrush(byte alpha, byte r, byte g, byte b)
{
Color c = Color.FromArgb(alpha, r, g, b);
SolidColorBrush brush = new SolidColorBrush(c);
if (brush.CanFreeze) brush.Freeze();
return brush;
}
/// <summary>Percentage of whole that part represents, or 0 if
/// whole is zero or the inputs aren't finite.</summary>
public static double Pct(double part, double whole)
{
if (whole == 0 || double.IsNaN(part) || double.IsNaN(whole)) return 0;
return part / whole * 100.0;
}
}
}
Namespace convention: NinjaTrader.NinjaScript.AddOns. The file lives at AddOns\TradingUtils.cs. One file can hold many classes, but prefer one responsibility per file — TimeUtils.cs, BrushUtils.cs, MathUtils.cs — once it outgrows a single screen of code.
🎯 Common Helper Categories
- Time helpers.
InSession,MinutesFromOpen,IsHoliday, timezone conversions. Things you’d otherwise write as one-offs scattered across indicators. - Brush helpers.
FrozenBrush,WithAlpha, a palette of named colors. TheFreeze()dance alone is enough reason to extract this — miss it once and you get a cross-thread DependencyObject exception at runtime. - Math helpers.
Pct,ClampToTick,RoundToTickSize, normalization helpers. Keep them pure — take numbers in, return numbers out. - Serialization helpers. If multiple indicators serialize the same shape (a list of price levels, a set of session rules), a shared writer and reader avoid the bug-by-bug divergence problem.
- Formatting helpers.
FormatTicks(double),FormatBars(int),FormatPct(double). Tiny, boring, and surprisingly useful once you have three indicators that all need to display a number.
🛠️ The Anchor Example — Indicator Consuming TradingUtils
Once the AddOn compiles, an indicator references it with a using directive. Extending MySma to use all three helpers — a session gate, a brush helper for a subtle fill, and the percent helper for a label:
using NinjaTrader.NinjaScript.AddOns;
public class MySma : Indicator
{
protected override void OnBarUpdate()
{
if (CurrentBar < Period) return;
// Session gate — only compute during regular trading hours.
if (!TradingUtils.InRegularSession(Time[0])) return;
double sma = SMA(Period)[0];
Values[0][0] = sma;
// Frozen brush for a subtle band fill.
Brush bandFill = TradingUtils.FrozenBrush(40, 74, 222, 128);
// Percent distance of close from SMA.
double distPct = TradingUtils.Pct(Close[0] - sma, sma);
if (Math.Abs(distPct) > 0.5)
Draw.TextFixed(this, "dist",
$"Close vs SMA: {distPct:F2}%", TextPosition.TopRight);
}
}
The indicator reads clean — session check, SMA compute, brush, label — with each step a short call to the utility class. None of the helpers know about Bars, Time[0], or CurrentBar; the indicator passes those in. That’s the line to hold.
📝 Pitfalls Checklist
- Helpers that take
this(NinjaScriptBase). Once a helper accepts the whole indicator, it’s coupled to indicator state — it can readBars,Time[0],Close[0]. Looks convenient at the call site and rots quickly. Pass the specific inputs the helper needs. - Implicit reference to indicator state. A utility that “knows” it’ll always be called from
OnBarUpdateand reaches forClose[0]via some side channel is a latent bug. Static helpers should compile and run outside an indicator’s context. - Namespace mismatch. AddOn declares
namespace NinjaTrader.NinjaScript.AddOnsbut the using directive saysusing NinjaTrader.AddOns;. Indicator compiles, then fails to find the helper at runtime. Match exactly. - Circular AddOn dependencies.
TradingUtilscalls something inDrawUtilswhich calls something inTradingUtils. Works once, breaks the moment anyone tries to reorganize. Keep AddOns one-directional: pure helpers at the bottom, composed helpers above, nothing cyclic. - A broken AddOn breaks every indicator. NT compiles the Custom assembly as a unit. One bad semicolon in
TradingUtils.csand every indicator on every chart fails to load. Make AddOn edits with the same care as production code; don’t leave half-written helpers in the folder. - Forgetting
publicon helpers.staticwithoutpublicgives internal access, which still works from indicators in the same assembly but breaks if you later split code into a separate project. Default topublic static.


