Bringing your own strategy
The ten rules a NinjaScript strategy must meet to be taken in.
What a NinjaTrader 8 strategy must satisfy before R2 will accept it through the dashboard's Intake tab, size it, backtest it and let it trade.
R2 checks every rule and names all the ones you break, worst first — you do not fix one,
resubmit, and discover the next. One .cs file at a time.
The ten rules
1. One strategy per file
A single C# file containing exactly one class deriving from Strategy. Everything it uses
must be in that file or in NinjaTrader's own assemblies. No partner files, no shared helpers.
2. A name nobody else has
The class name must not already exist in NinjaTrader — not as a shipped R2 book, not in a
compiled assembly, not as a source file. Rename the class and its Name in SetDefaults.
3. One place to set size
Exactly one of:
- the slot inputs
Slot1Contracts…SlotNContracts(what R2's own books use), or - exactly one integer input named
Contracts,Quantity,QtyorSize, used by every entry.
No entry may carry a literal quantity — EnterLong(2, "x") is refused.
The one thing R2 repairs for you. If every entry shares the same literal quantity, R2 lifts it into its own contracts input and accepts the strategy. If entries carry different literals, it is refused: a ratio between entries is a sizing decision and R2 will not guess it.
4. Every entry protected
Each entry has a stop — SetStopLoss, SetTrailStop, an ExitLongStopMarket /
ExitShortStopLimit-style order, or a stop in the unmanaged bracket. The distance is yours;
R2 will never choose it.
5. Flat before the close
IsExitOnSessionCloseStrategy = true, or a time-based flatten of your own (a ToTime(Time[0])
check plus an exit call). In the Strategy Analyzer no round trip may last over 10 hours —
that is the signature of a backtest that did not close, and nothing may be sized on it.
6. Minute bars
The primary series must be minute-based — any instrument, any period. Further series come from
AddDataSeries with instrument inputs, never a hard-coded contract month.
7. Its own orders only
- No
Account.Submit/Cancel/Flatten/Create/Change - No touching another strategy's orders
- Never sets
IsEnableditself — R2 owns enabling and disabling
8. No side channels
No file, network, process, registry or reflection calls. Specifically refused:
System.IO.*, File.Read/Write/Append/Delete/Open/Exists/Copy, StreamReader,
StreamWriter, Directory.*, WebClient, HttpClient, HttpWebRequest, System.Net.*,
sockets, TcpClient, Process.*, Registry, Microsoft.Win32.*, System.Reflection.*,
.GetMethod(, .GetProperty(, Assembly.*.
A strategy talks to nothing. R2 does the talking.
9. Deterministic enough to backtest
DateTime.Now, Random and tick-driven calculation are warned, not refused. They make a
backtest irreproducible, so treat a warning as a reason to check the result twice.
10. It compiles
Against NinjaTrader 8.1's assemblies. The compiler's first error is the refusal reason, so compile it in NinjaTrader yourself before uploading and you will skip a round trip.
What R2 adds after you pass
R2 inserts its plumbing between // --- R2 plumbing markers and changes nothing else about
what your strategy trades:
- A read of
policy.json— the contract counts R2 worked out for that account are applied as the strategy configures. - A self-enable, and a self-disable. If the policy names a different strategy for that
account, yours switches itself off and prints why. If
policy.jsonis missing or unreadable, your dialog values stand.
Sizing is only ever applied for slots 1..N and only for values 0..100.
What happens next
| Stage | What R2 does |
|---|---|
| Received | your file is kept exactly as it came |
| Checked | the ten rules; a refusal names the line and the fix |
| Formatted | R2's plumbing added between markers, nothing else |
| Compiled or staged | into R2User.dll, or kept as source outside NinjaTrader until R2 can put it in and compile it safely (NinjaTrader closed, or the New York close with every account flat) - a running NinjaTrader compiles any source put into bin\Custom at once |
| In NinjaTrader | after its next start |
| Backtested | seven years in the Strategy Analyzer, on a simulation account |
| Candidate | if it clears the same gate as R2's own books — then into the nightly loop |
Things that are easy to get wrong
- A hard-coded contract month. Use instrument inputs. Contracts roll four times a year and a strategy pinned to an expired one sees no bars and cannot trade.
- A literal quantity in one entry only. R2 can lift a shared literal; it cannot lift one.
- Setting
IsEnabledinOnStateChange. Common, and always refused — R2 owns that. - Reading a settings file of your own. Rule 8 refuses it. Use a NinjaScript input.
- A backtest with an overnight hold. Usually a missing session-close flatten (rule 5).