503 lines
15 KiB
Markdown
503 lines
15 KiB
Markdown
o help ensure that NinjaTrader 8 (NT8) code compiles successfully the first time, every time, you can give your developers a clear set of compile-time directives, guardrails, and best practices. Below is a comprehensive guide you can use or adapt as a compile spec or code review checklist.
|
||
|
||
✅ NinjaTrader 8 Compile-Time Success Directives
|
||
🧱 1. Code Structure & Class Guidelines
|
||
|
||
Each script must extend only the correct NT8 base class:
|
||
|
||
Indicator, Strategy, MarketAnalyzerColumn, etc.
|
||
|
||
Use [GeneratedCode] attributes only where required.
|
||
|
||
Avoid partial classes unless absolutely necessary.
|
||
|
||
🔍 2. File & Naming Conventions
|
||
|
||
Class name and file name must match.
|
||
|
||
No duplicate class names, even across namespaces.
|
||
|
||
Avoid reserved words or NT8 system identifiers.
|
||
|
||
📦 3. Namespace Hygiene
|
||
|
||
Always use:
|
||
|
||
using System;
|
||
using NinjaTrader.Cbi;
|
||
using NinjaTrader.Gui.Tools;
|
||
using NinjaTrader.NinjaScript;
|
||
using NinjaTrader.Data;
|
||
using NinjaTrader.Gui;
|
||
using NinjaTrader.NinjaScript.Strategies;
|
||
using NinjaTrader.NinjaScript.Indicators;
|
||
|
||
|
||
Avoid unnecessary or ambiguous using directives (e.g., from other frameworks).
|
||
|
||
🧪 4. Method & Lifecycle Integrity
|
||
|
||
Ensure these NT8 methods are implemented correctly:
|
||
|
||
protected override void OnStateChange()
|
||
protected override void OnBarUpdate()
|
||
protected override void OnMarketData(MarketDataEventArgs e) // if used
|
||
|
||
|
||
Avoid:
|
||
|
||
Missing break statements in switch.
|
||
|
||
Logic in OnBarUpdate() without BarsInProgress checks when using multiple series.
|
||
|
||
🛡️ 5. Error-Free State Management
|
||
|
||
Always check states in OnStateChange():
|
||
|
||
if (State == State.SetDefaults) { ... }
|
||
if (State == State.Configure) { ... }
|
||
if (State == State.DataLoaded) { ... }
|
||
|
||
|
||
Avoid placing runtime logic or order submissions in SetDefaults or Configure.
|
||
|
||
⛔ 6. No Runtime Calls at Compile-Time
|
||
|
||
Do not call:
|
||
|
||
Print() inside SetDefaults
|
||
|
||
AddDataSeries() inside wrong state
|
||
|
||
CalculateXXX() outside Configure
|
||
|
||
🧯 7. Null Checks and Bounds
|
||
|
||
Always check:
|
||
|
||
if (CurrentBar < X) return;
|
||
if (BarsInProgress != 0) return; // If multi-series used
|
||
if (mySeries == null) return;
|
||
|
||
|
||
Prevent index out of range errors:
|
||
|
||
if (CurrentBar < myPeriod) return;
|
||
double value = Close[0]; // Only if safe
|
||
|
||
🧰 8. Avoid Common Pitfalls
|
||
|
||
No empty catch blocks or silent exceptions.
|
||
|
||
No hardcoded bar indexes or array sizes.
|
||
|
||
Avoid referencing objects that may be null (e.g., BarsArray[1], SMA() without initialization).
|
||
|
||
📚 9. Dependencies & Resources
|
||
|
||
No external libraries unless they are approved and included in the solution.
|
||
|
||
Ensure all custom indicators or referenced strategies exist and are compiled.
|
||
|
||
📏 10. Strategy Parameters & UI Defaults
|
||
|
||
Provide all necessary [NinjaScriptProperty] parameters.
|
||
|
||
Set default values cleanly inside State.SetDefaults.
|
||
|
||
Use Name = "MyStrategy" for naming.
|
||
|
||
🧹 11. Code Hygiene & Readability
|
||
|
||
Consistent indentation, spacing, and braces.
|
||
|
||
No commented-out blocks of old code in final delivery.
|
||
|
||
Regions (#region) for each main section: Inputs, Initialization, Logic, etc.
|
||
|
||
🧪 12. Pre-Compile Self-Test Macro (Optional)
|
||
|
||
If feasible, add a conditional debug directive:
|
||
|
||
#if DEBUG
|
||
Print("DEBUG: Compiling MyStrategy");
|
||
#endif
|
||
|
||
✅ Pre-Delivery Checklist for Developers
|
||
Checkpoint Status
|
||
No compile errors or warnings ✅
|
||
Clean OnStateChange() structure ✅
|
||
Safe OnBarUpdate() logic ✅
|
||
Proper BarsInProgress handling ✅
|
||
All inputs and parameters declared ✅
|
||
Class name matches file name ✅
|
||
No unused using directives ✅
|
||
Strategy or indicator tested ✅
|
||
|
||
|
||
|
||
|
||
Here’s a practical compile-spec + guardrails you can hand to any dev so their NinjaTrader 8 code compiles cleanly the first time—no surprises, no Order Flow+ dependencies, and no signature mismatches.
|
||
|
||
NinjaTrader 8 “First-Time Compile” Spec
|
||
0) Golden rules (pin these in the PR template)
|
||
|
||
Target base class: public class <Name> : Strategy in the namespace NinjaTrader.NinjaScript.Strategies.
|
||
|
||
File name = class name (e.g., ORBV4.cs contains public class ORBV4 : Strategy).
|
||
|
||
Correct override access: all NT8 overrides must be protected override, never public or private.
|
||
|
||
No dead APIs: do not implement OnStartUp() (doesn’t exist). Use OnStateChange() with state switches.
|
||
|
||
No Order Flow+ unless requested: never reference indicators like OrderFlow VWAP if the environment may not have it.
|
||
|
||
No invented enum values: never use things like OrderState.PendingSubmit or RejectedByExchange. Only use enums that exist in NT8.
|
||
|
||
Attributes present: using System.ComponentModel.DataAnnotations; for [Range] and [Display]. Use [NinjaScriptProperty] for user inputs.
|
||
|
||
Indicators & series created only in State.DataLoaded (not in State.SetDefaults).
|
||
|
||
Bar guards at top of OnBarUpdate: if (BarsInProgress != 0) return; if (CurrentBar < BarsRequiredToTrade) return;
|
||
|
||
Managed vs Unmanaged: pick exactly one model and stick to its API patterns in the whole file.
|
||
|
||
1) Required using directives (top of every strategy file)
|
||
using System;
|
||
using System.ComponentModel;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using NinjaTrader.Cbi;
|
||
using NinjaTrader.Data;
|
||
using NinjaTrader.Gui;
|
||
using NinjaTrader.Gui.Chart;
|
||
using NinjaTrader.Gui.Tools;
|
||
using NinjaTrader.NinjaScript;
|
||
using NinjaTrader.NinjaScript.Strategies;
|
||
using NinjaTrader.NinjaScript.Indicators;
|
||
|
||
2) Required lifecycle pattern (no OnStartUp)
|
||
protected override void OnStateChange()
|
||
{
|
||
if (State == State.SetDefaults)
|
||
{
|
||
Name = "StrategyName";
|
||
Description = "Short description";
|
||
Calculate = Calculate.OnBarClose; // Change only if truly needed
|
||
IsInstantiatedOnEachOptimizationIteration = true;
|
||
IsOverlay = false;
|
||
BarsRequiredToTrade = 20;
|
||
|
||
// Defaults for public properties
|
||
RiskTicks = 16;
|
||
UseRthOnly = true;
|
||
}
|
||
else if (State == State.Configure)
|
||
{
|
||
// Set up data series, trading hours behavior, etc. (no indicators here)
|
||
// Example: AddDataSeries(BarsPeriodType.Minute, 1);
|
||
}
|
||
else if (State == State.DataLoaded)
|
||
{
|
||
// Create indicators/series
|
||
sma = SMA(20); // ✅ Allowed; built-in indicator
|
||
// vwap = VWAP(...); // ❌ Avoid if Order Flow+ isn’t guaranteed
|
||
}
|
||
}
|
||
|
||
3) Correct signatures for event hooks (copy exactly)
|
||
|
||
OnBarUpdate
|
||
|
||
protected override void OnBarUpdate()
|
||
{
|
||
if (BarsInProgress != 0) return;
|
||
if (CurrentBar < BarsRequiredToTrade) return;
|
||
|
||
// Strategy logic here
|
||
}
|
||
|
||
|
||
OnOrderUpdate (Managed or Unmanaged—signature is the same)
|
||
|
||
protected override void OnOrderUpdate(
|
||
Order order,
|
||
double limitPrice,
|
||
double stopPrice,
|
||
int quantity,
|
||
int filled,
|
||
double averageFillPrice,
|
||
OrderState orderState,
|
||
DateTime time,
|
||
ErrorCode error,
|
||
string nativeError)
|
||
{
|
||
// Observe state transitions or errors here
|
||
}
|
||
|
||
|
||
OnExecutionUpdate
|
||
|
||
protected override void OnExecutionUpdate(
|
||
Execution execution,
|
||
string executionId,
|
||
double price,
|
||
int quantity,
|
||
MarketPosition marketPosition,
|
||
string orderId,
|
||
DateTime time)
|
||
{
|
||
// Post-fill logic here
|
||
}
|
||
|
||
|
||
OnPositionUpdate (when needed)
|
||
|
||
protected override void OnPositionUpdate(Position position, double averagePrice, int quantity, MarketPosition marketPosition)
|
||
{
|
||
// Position tracking here
|
||
}
|
||
|
||
|
||
Guardrail: If you ever see CS0507 (“cannot change access modifiers when overriding ‘protected’…”) it means you used public override or private override. Switch to protected override.
|
||
|
||
4) Property pattern (inputs that always compile)
|
||
|
||
Use [NinjaScriptProperty] + [Range] + [Display].
|
||
|
||
Put properties after fields, inside the strategy class.
|
||
|
||
#region Inputs
|
||
[NinjaScriptProperty]
|
||
[Range(1, int.MaxValue)]
|
||
[Display(Name = "RiskTicks", GroupName = "Parameters", Order = 1)]
|
||
public int RiskTicks { get; set; }
|
||
|
||
[NinjaScriptProperty]
|
||
[Display(Name = "UseRthOnly", GroupName = "Parameters", Order = 2)]
|
||
public bool UseRthOnly { get; set; }
|
||
#endregion
|
||
|
||
5) Indicator & resource creation rules
|
||
|
||
Only instantiate indicators/Series in State.DataLoaded.
|
||
|
||
Never new built-in indicators; call factory methods (e.g., SMA(20)).
|
||
|
||
Don’t assume Order Flow+. If you need VWAP, either:
|
||
|
||
Use a custom rolling VWAP you implement locally, or
|
||
|
||
Wrap the reference behind a feature flag and compile-time fallbacks.
|
||
|
||
6) Managed orders “compile-safe” usage
|
||
|
||
Set targets/stops before entry on the same bar:
|
||
|
||
SetStopLoss(CalculationMode.Ticks, RiskTicks);
|
||
SetProfitTarget(CalculationMode.Ticks, RiskTicks * 2);
|
||
EnterLong(); // or EnterShort()
|
||
|
||
|
||
Don’t mix EnterLong/Short with Unmanaged SubmitOrderUnmanaged() in the same strategy.
|
||
|
||
If using signals, use consistent signal names across entries/exits.
|
||
|
||
7) Unmanaged orders “compile-safe” usage (if chosen)
|
||
|
||
Opt-in once:
|
||
|
||
else if (State == State.Configure)
|
||
{
|
||
Calculate = Calculate.OnBarClose;
|
||
// Enable unmanaged if needed
|
||
// this.IsUnmanaged = true; // uncomment only if you’re actually using unmanaged
|
||
}
|
||
|
||
|
||
Always check Order objects for null before accessing fields.
|
||
|
||
Maintain your own OCO/quantity state.
|
||
|
||
Do not call Managed Set* methods in Unmanaged mode.
|
||
|
||
8) Enums & constants that trip compilers
|
||
|
||
Use only valid enum members. Examples that compile:
|
||
|
||
OrderAction.Buy, OrderAction.SellShort
|
||
|
||
OrderType.Market, OrderType.Limit, OrderType.StopMarket, OrderType.StopLimit
|
||
|
||
TimeInForce.Day, TimeInForce.Gtc
|
||
|
||
MarketPosition.Flat/Long/Short
|
||
|
||
OrderState.Accepted/Working/PartFilled/Filled/Cancelled/Rejected/Unknown (names vary by NT build; don’t invent “PendingSubmit”, “RejectedByBroker”, “RejectedByExchange”).
|
||
|
||
Use ToTime(Time[0]) or anchors like Times[0][0] for session-aware checks; avoid DateTime.Now for bar logic.
|
||
|
||
9) Safe OnBarUpdate header (paste into every strategy)
|
||
protected override void OnBarUpdate()
|
||
{
|
||
if (BarsInProgress != 0) return;
|
||
if (CurrentBar < BarsRequiredToTrade) return;
|
||
if (UseRthOnly && !TradingHours.Contains(Time[0])) return; // requires proper session template
|
||
|
||
// Logic...
|
||
}
|
||
|
||
10) Logging & messages
|
||
|
||
Use Print() for debug; never MessageBox.Show or Windows-only UI calls (breaks compile or runtime).
|
||
|
||
Wrap optional debug in a bool DebugMode input and guard if (DebugMode) Print(...).
|
||
|
||
11) Namespaces & class hygiene
|
||
|
||
Exactly one public strategy per file.
|
||
|
||
No top-level statements; everything inside the namespace/class.
|
||
|
||
No async/await; stick to synchronous NT8 patterns.
|
||
|
||
12) “No-surprises” build checks (optional but recommended)
|
||
|
||
If you run pre-lint or Roslyn analyzers externally, do not add NuGet packages inside NinjaTrader’s compile domain.
|
||
|
||
Keep analyzers in your editor/CI only, not as runtime dependencies in NT8.
|
||
|
||
13) Minimal, compile-safe template (drop-in)
|
||
|
||
Copy this as your starting point; it compiles on a vanilla NT8 (no Order Flow+).
|
||
|
||
// ============================================================================
|
||
// Strategy Name : CompileSafeTemplate
|
||
// Description : Minimal, first-time-compile-safe NinjaTrader 8 strategy
|
||
// ============================================================================
|
||
|
||
#region Using declarations
|
||
using System;
|
||
using System.ComponentModel;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using NinjaTrader.Cbi;
|
||
using NinjaTrader.Data;
|
||
using NinjaTrader.Gui;
|
||
using NinjaTrader.Gui.Chart;
|
||
using NinjaTrader.Gui.Tools;
|
||
using NinjaTrader.NinjaScript;
|
||
using NinjaTrader.NinjaScript.Strategies;
|
||
using NinjaTrader.NinjaScript.Indicators;
|
||
#endregion
|
||
|
||
namespace NinjaTrader.NinjaScript.Strategies
|
||
{
|
||
public class CompileSafeTemplate : Strategy
|
||
{
|
||
private SMA sma;
|
||
|
||
#region Inputs
|
||
[NinjaScriptProperty]
|
||
[Range(1, int.MaxValue)]
|
||
[Display(Name = "RiskTicks", GroupName = "Parameters", Order = 1)]
|
||
public int RiskTicks { get; set; }
|
||
|
||
[NinjaScriptProperty]
|
||
[Display(Name = "UseRthOnly", GroupName = "Parameters", Order = 2)]
|
||
public bool UseRthOnly { get; set; }
|
||
|
||
[NinjaScriptProperty]
|
||
[Display(Name = "DebugMode", GroupName = "Parameters", Order = 3)]
|
||
public bool DebugMode { get; set; }
|
||
#endregion
|
||
|
||
protected override void OnStateChange()
|
||
{
|
||
if (State == State.SetDefaults)
|
||
{
|
||
Name = "CompileSafeTemplate";
|
||
Description = "Minimal, compile-safe NT8 strategy template";
|
||
Calculate = Calculate.OnBarClose;
|
||
IsOverlay = false;
|
||
BarsRequiredToTrade = 20;
|
||
IsInstantiatedOnEachOptimizationIteration = true;
|
||
|
||
// Defaults
|
||
RiskTicks = 16;
|
||
UseRthOnly = true;
|
||
DebugMode = false;
|
||
}
|
||
else if (State == State.Configure)
|
||
{
|
||
// AddDataSeries(...) if needed
|
||
}
|
||
else if (State == State.DataLoaded)
|
||
{
|
||
sma = SMA(20);
|
||
}
|
||
}
|
||
|
||
protected override void OnBarUpdate()
|
||
{
|
||
if (BarsInProgress != 0) return;
|
||
if (CurrentBar < BarsRequiredToTrade) return;
|
||
if (UseRthOnly && !TradingHours.Contains(Time[0])) return;
|
||
|
||
// Example trivial logic just to show structure (does nothing fancy)
|
||
if (CrossAbove(Close, sma, 1) && Position.MarketPosition == MarketPosition.Flat)
|
||
{
|
||
SetStopLoss(CalculationMode.Ticks, RiskTicks);
|
||
SetProfitTarget(CalculationMode.Ticks, RiskTicks * 2);
|
||
EnterLong();
|
||
if (DebugMode) Print($"EnterLong at {Time[0]}");
|
||
}
|
||
else if (CrossBelow(Close, sma, 1) && Position.MarketPosition == MarketPosition.Flat)
|
||
{
|
||
SetStopLoss(CalculationMode.Ticks, RiskTicks);
|
||
SetProfitTarget(CalculationMode.Ticks, RiskTicks * 2);
|
||
EnterShort();
|
||
if (DebugMode) Print($"EnterShort at {Time[0]}");
|
||
}
|
||
}
|
||
|
||
protected override void OnOrderUpdate(
|
||
Order order, double limitPrice, double stopPrice, int quantity, int filled,
|
||
double averageFillPrice, OrderState orderState, DateTime time,
|
||
ErrorCode error, string nativeError)
|
||
{
|
||
if (DebugMode) Print($"OnOrderUpdate: {order?.Name} {orderState} {nativeError}");
|
||
}
|
||
|
||
protected override void OnExecutionUpdate(
|
||
Execution execution, string executionId, double price, int quantity,
|
||
MarketPosition marketPosition, string orderId, DateTime time)
|
||
{
|
||
if (DebugMode) Print($"OnExecutionUpdate: {execution?.Name} {quantity}@{price}");
|
||
}
|
||
}
|
||
}
|
||
|
||
14) Quick dev checklist (paste in your repo README)
|
||
|
||
File name matches class name; class derives from Strategy.
|
||
|
||
All overrides are protected override and signatures match exactly.
|
||
|
||
No OnStartUp(); lifecycle is handled in OnStateChange.
|
||
|
||
All indicators/Series created in State.DataLoaded.
|
||
|
||
No Order Flow+ indicators unless explicitly requested.
|
||
|
||
OnBarUpdate starts with BarsInProgress and CurrentBar guards.
|
||
|
||
Inputs use [NinjaScriptProperty], [Range], [Display].
|
||
|
||
No invented enum values; only valid OrderState, MarketPosition, etc.
|
||
|
||
Managed vs Unmanaged is consistent; do not mix APIs.
|
||
|
||
No MessageBox/UI calls; optional logs gated behind DebugMode.
|
||
|
||
|
||
|