NinjaScript Tutorial: Code Your First NinjaTrader 8 Strategy
By HorizonAI Team · 13 min read · Beginner
You have a setup that works on your charts, and NinjaTrader has no button for it. The Strategy Builder gets you most of the way there, then hits a wall the first time you need a condition it doesn't expose. That wall is where NinjaScript starts, and it's a lot less intimidating than the C# in front of it suggests.
Short answer: NinjaScript is C# with NinjaTrader's trading API attached. To code your first NinjaTrader 8 strategy, you create a strategy file in the NinjaScript Editor, set defaults and parameters inside OnStateChange(), put your per-bar logic in OnBarUpdate(), and submit orders with managed methods like EnterLong() and SetStopLoss(). Press F5 to compile, then load it in the Strategy Analyzer to backtest.
That's the whole shape of it. Every NinjaTrader strategy, from a two-line moving average cross to a multi-instrument order-flow system, is the same four pieces: a class that inherits from Strategy, a state machine that configures it, a per-bar method that decides, and order methods that act. The strategy below is a fast/slow SMA crossover with a fixed stop and target, under 100 lines including parameters, and it compiles and backtests as written. Once you understand why each line is where it is, swapping the crossover for your own logic is a five-minute edit.
What NinjaScript is, and how it compares to Pine Script and MQL5
NinjaScript isn't a language. It's a set of base classes, methods, and lifecycle rules layered on C#, compiled by NinjaTrader into a .NET assembly that the platform loads. Anything legal in C# is legal in NinjaScript: LINQ, generics, custom classes, List<T>, your own helper methods. That's the payoff and the tax at once, because you get a real language instead of a sandbox, and in exchange you deal with types, semicolons, and a compiler that refuses ambiguity.
If your reference point is TradingView, here's the honest mapping:
| NinjaScript | Pine Script v6 | MQL5 | |
|---|---|---|---|
| Language | C# (.NET) | Purpose-built DSL | C++-style |
| Runs in | NinjaTrader 8 desktop | TradingView (cloud) | MetaTrader 5 desktop |
| Per-bar hook | OnBarUpdate() | whole script re-runs per bar | OnTick() / OnCalculate() |
| Bar indexing | Close[0] current, Close[1] prior | close[0] current, close[1] prior | CopyBuffer() into arrays |
| Entries | EnterLong(), EnterShort() | strategy.entry() | CTrade.Buy() / OrderSend() |
| Protective orders | SetStopLoss(), SetProfitTarget() | strategy.exit(stop=, limit=) | sl / tp on the request |
| Parameters | [NinjaScriptProperty] properties | input.int() | input variables |
| Backtesting | Strategy Analyzer | Strategy Tester | Strategy Tester |
| Rough time to a first working script | an afternoon | an hour | a weekend |
The bar-indexing convention is the single most transferable idea. In all three, index 0 is the current bar and higher indices go back in time, so Close[1] > Open[1] means "the previous candle closed green." If you've written a TradingView script before, the Pine Script beginner walkthrough covers the same crossover logic in the language you already know, which makes the C# version mostly a syntax exercise. The same reasoning applies going the other direction between platforms, as the manual Pine to MQL5 translation guide works through in detail.
Where NinjaScript pulls ahead: it runs locally against your own data, it handles futures and order flow natively, and the Strategy Analyzer's optimizer is genuinely good. The trade-off is no cloud sharing, no one-click publishing, and a much steeper first day.
Before you start: the setup that saves you an hour
Three things, all free:
- NinjaTrader 8, desktop, connected to at least the free simulated data feed. NinjaScript won't compile against NinjaTrader 7 syntax, and most of the blog posts you'll find in a search are NT7. Check that anything you copy uses
OnBarUpdate()with aStatemachine, not NT7'sInitialize(). - Historical data for one instrument. Control Center → Tools → Historical Data → Load. Pull at least six months of 5-minute bars for ES or a liquid stock. Without data loaded, the Strategy Analyzer will run and report nothing, which reads exactly like a broken strategy.
- A scratch chart with the same instrument and timeframe open, so you can apply the strategy visually and watch it mark entries.
You don't need Visual Studio. The built-in NinjaScript Editor compiles everything, and external editors add a synchronisation problem you don't want on day one.
Step 1: Create the strategy file in the NinjaScript Editor
From the Control Center: New → NinjaScript Editor. In the editor's explorer pane on the left, expand Strategies, right-click, and choose New Strategy. That opens the Strategy Builder wizard.
You have two paths from here, and beginners pick the wrong one about half the time:
- Click through the wizard, then hit "Unlock Code" at the end. The wizard writes real NinjaScript for you and unlocking makes the file editable. Good for seeing how the platform structures things. Once unlocked, the file no longer opens in the Builder, and that's permanent for that file.
- Skip the wizard's conditions entirely, name the strategy, finish, and unlock. You get a clean skeleton with your parameter stubs already wired up.
Take the second path. Name it SmaCrossStrategy. What lands on disk is Documents\NinjaTrader 8\bin\Custom\Strategies\SmaCrossStrategy.cs, and the skeleton looks like this:
namespace NinjaTrader.NinjaScript.Strategies
{
public class SmaCrossStrategy : Strategy
{
protected override void OnStateChange()
{
}
protected override void OnBarUpdate()
{
}
}
}
Two methods, both empty. Everything you write for the rest of this walkthrough goes inside them or alongside them.
Step 2: The strategy lifecycle and the states that matter
NinjaTrader doesn't just run your strategy. It walks it through a sequence of states, and OnStateChange() fires on every transition. Putting the right code in the right state is the difference between a strategy that runs and one that throws a null reference the moment it touches the chart.
Five states matter:
State.SetDefaultsruns whenever the platform needs to display your script, including when someone merely opens the Strategies window. Keep it lean: names, defaults, and calculation settings only. Never put data access or indicator instantiation here, because there is no data yet.State.Configureruns once the user adds the strategy and confirms it. This is where you add secondary data series withAddDataSeries()and where protective order methods likeSetStopLoss()belong.State.DataLoadedruns after the price series exists. Indicators get instantiated here, not earlier.State.HistoricalandState.Realtimemark the transition from backfill to live ticks. Useful when you want behaviour to differ between the two.State.Terminatedis cleanup.
The official OnStateChange reference spells out the full table, and it's worth a read once you're past this article, because the SetDefaults-runs-constantly behaviour surprises everyone eventually.
Here's the configured version:
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Fast/slow SMA crossover with a fixed tick stop and target.";
Name = "SmaCrossStrategy";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.AllEntries;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 30;
BarsRequiredToTrade = 20;
IncludeCommission = true;
// Defaults for the parameters exposed in the UI
FastPeriod = 9;
SlowPeriod = 21;
StopLossTicks = 40;
ProfitTargetTicks = 80;
}
else if (State == State.Configure)
{
// Brackets for both entry signals.
// These cannot be called during State.SetDefaults.
SetStopLoss("MA Long", CalculationMode.Ticks, StopLossTicks, false);
SetStopLoss("MA Short", CalculationMode.Ticks, StopLossTicks, false);
SetProfitTarget("MA Long", CalculationMode.Ticks, ProfitTargetTicks);
SetProfitTarget("MA Short", CalculationMode.Ticks, ProfitTargetTicks);
}
else if (State == State.DataLoaded)
{
// Indicators are instantiated once the price series exists
fastSma = SMA(Close, FastPeriod);
slowSma = SMA(Close, SlowPeriod);
fastSma.Plots[0].Brush = Brushes.DodgerBlue;
slowSma.Plots[0].Brush = Brushes.Goldenrod;
AddChartIndicator(fastSma);
AddChartIndicator(slowSma);
}
}
Calculate.OnBarClose is the setting to start with. It evaluates your logic once, on the close of each bar, which makes backtest and live behaviour agree. Calculate.OnEachTick fires on every incoming tick and is where beginners accidentally submit forty orders in a row.
Exposing parameters the UI can see
Hard-coded periods are fine until you want to optimise them. A public property tagged [NinjaScriptProperty] shows up in the strategy dialog and, more usefully, in the Strategy Analyzer's optimizer:
#region Properties
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Fast period", GroupName = "Parameters", Order = 0)]
public int FastPeriod { get; set; }
[NinjaScriptProperty]
[Range(2, int.MaxValue)]
[Display(Name = "Slow period", GroupName = "Parameters", Order = 1)]
public int SlowPeriod { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Stop loss (ticks)", GroupName = "Parameters", Order = 2)]
public int StopLossTicks { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Profit target (ticks)", GroupName = "Parameters", Order = 3)]
public int ProfitTargetTicks { get; set; }
#endregion
[Range] isn't decoration. It stops someone typing a zero period and handing you a divide-by-zero at bar one.
Step 3: Per-bar logic with OnBarUpdate and CrossAbove()
OnBarUpdate() is called once per bar close (given Calculate.OnBarClose), including for every historical bar during a backtest. It is the only place your decision logic should live.
Two guards belong at the top of it. Always.
protected override void OnBarUpdate()
{
// Only act on the primary series
if (BarsInProgress != 0)
return;
// Enough bars for the slow average to be meaningful
if (CurrentBar < SlowPeriod + 1)
return;
// ... logic goes here
}
CurrentBar is the index of the bar being processed, counting up from 0 at the start of the loaded series. On bar 3, a 21-period SMA is nonsense, and asking for Close[25] throws. Guarding on CurrentBar is the single most common fix for "my strategy crashed on load."
BarsInProgress matters the moment you add a second data series. It tells you which series triggered this call. Even in a single-series strategy, writing the guard now means adding a higher-timeframe filter later doesn't silently double your order count.
For the crossover itself, don't hand-roll the comparison. NinjaTrader ships CrossAbove() and CrossBelow(), which take two series and a lookback:
if (CrossAbove(fastSma, slowSma, 1))
EnterLong("MA Long");
else if (CrossBelow(fastSma, slowSma, 1))
EnterShort("MA Short");
The 1 is the lookback window in bars: "did this cross happen within the last bar?" A lookback of 1 gives you exactly one signal per cross. Raising it to 5 makes the condition stay true for five bars, which is occasionally what you want for a confirmation filter and almost never what you want for an entry trigger.
If you want the mechanics of why a fast/slow cross behaves the way it does before you tune the periods, the SMA crossover strategy guide covers the whipsaw problem and the filters that address it.
Step 4: Orders, stops, and targets
NinjaScript gives you two order paradigms. The managed approach handles order state, rejection, and reversal for you, and blocks combinations that would leave you in an inconsistent position; the unmanaged approach hands you raw control and expects you to track everything yourself. Start managed. The managed approach documentation lists the rules it enforces, and those rules are doing you a favour for at least your first six months.
EnterLong() submits a buy market order. The overload that takes a signal name is the one to use, because that string is how you later attach a stop, reference the entry in exit logic, and identify the trade in the Strategy Analyzer's trade list:
EnterLong("MA Long"); // quantity comes from the strategy dialog
EnterLong(2, "MA Long"); // explicit quantity
With EntriesPerDirection = 1, a second EnterLong("MA Long") while already long is ignored. An EnterShort() while long reverses the position: the managed approach closes the long and opens the short in one step. That's what makes this strategy always-in-market, which is a property of crossover systems generally, not a bug in the code.
Stops and targets come from SetStopLoss() and SetProfitTarget(). These are set-and-forget methods: once configured, NinjaTrader submits the protective order automatically the moment your entry fills, and cancels it when the position closes. Two rules from the SetStopLoss reference that catch people out:
- They cannot be called during
State.SetDefaults. That's why they sit inState.Configurein the code above. - They must be called before the entry order is submitted, so an initial level exists when the fill arrives.
CalculationMode.Ticks is the sane default for futures. The alternatives are Price, Percent, Pips, and Currency, and Currency quietly changes StopTargetHandling to by-strategy-position, which will confuse you later if you don't know it happened.
Forty ticks of stop against eighty of target is a 1:2 ratio, chosen here because it's clean, not because it's optimal. Sizing that stop against your account rather than against a round number is the actual work, and the risk management guide has the position-sizing maths.
The complete NinjaScript strategy
Everything assembled. Paste this over the contents of your SmaCrossStrategy.cs:
#region Using declarations
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Indicators;
#endregion
namespace NinjaTrader.NinjaScript.Strategies
{
public class SmaCrossStrategy : Strategy
{
private SMA fastSma;
private SMA slowSma;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Fast/slow SMA crossover with a fixed tick stop and target.";
Name = "SmaCrossStrategy";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.AllEntries;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 30;
BarsRequiredToTrade = 20;
IncludeCommission = true;
FastPeriod = 9;
SlowPeriod = 21;
StopLossTicks = 40;
ProfitTargetTicks = 80;
}
else if (State == State.Configure)
{
SetStopLoss("MA Long", CalculationMode.Ticks, StopLossTicks, false);
SetStopLoss("MA Short", CalculationMode.Ticks, StopLossTicks, false);
SetProfitTarget("MA Long", CalculationMode.Ticks, ProfitTargetTicks);
SetProfitTarget("MA Short", CalculationMode.Ticks, ProfitTargetTicks);
}
else if (State == State.DataLoaded)
{
fastSma = SMA(Close, FastPeriod);
slowSma = SMA(Close, SlowPeriod);
fastSma.Plots[0].Brush = Brushes.DodgerBlue;
slowSma.Plots[0].Brush = Brushes.Goldenrod;
AddChartIndicator(fastSma);
AddChartIndicator(slowSma);
}
}
protected override void OnBarUpdate()
{
if (BarsInProgress != 0)
return;
if (CurrentBar < SlowPeriod + 1)
return;
if (CrossAbove(fastSma, slowSma, 1))
EnterLong("MA Long");
else if (CrossBelow(fastSma, slowSma, 1))
EnterShort("MA Short");
}
#region Properties
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Fast period", GroupName = "Parameters", Order = 0)]
public int FastPeriod { get; set; }
[NinjaScriptProperty]
[Range(2, int.MaxValue)]
[Display(Name = "Slow period", GroupName = "Parameters", Order = 1)]
public int SlowPeriod { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Stop loss (ticks)", GroupName = "Parameters", Order = 2)]
public int StopLossTicks { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "Profit target (ticks)", GroupName = "Parameters", Order = 3)]
public int ProfitTargetTicks { get; set; }
#endregion
}
}
Note where the two SMA fields are declared: at class level, outside every method. Declare them inside OnStateChange() and OnBarUpdate() can't see them, which produces the first compile error most people hit.
Step 5: Compile with F5 and read the errors properly
Press F5 in the editor. NinjaTrader compiles every custom script in one pass, so a broken file you edited last week will fail this build too. Errors appear in the panel at the bottom with file, line, and message.
The four you'll actually meet:
CS0103: The name 'fastSma' does not exist in the current context. Your variable is scoped inside a method. Move the declaration to class level.
CS0246: The type or namespace name 'SMA' could not be found. A missing using NinjaTrader.NinjaScript.Indicators;, or a typo in the indicator name. NinjaScript is case-sensitive: SMA compiles, Sma doesn't.
CS1061: 'Strategy' does not contain a definition for 'SetStopLoss' with odd arguments listed. You're passing arguments in the wrong order. The signature is (string fromEntrySignal, CalculationMode mode, double value, bool isSimulatedStop), and the signal name comes first when you use the four-argument form.
Object reference not set to an instance of an object at runtime rather than compile time. Almost always an indicator used before it was instantiated, meaning you assigned it in State.SetDefaults instead of State.DataLoaded, or you forgot the CurrentBar guard.
One habit worth forming immediately: after a clean compile, open Control Center → Tools → Output Window and add a Print() to your logic. Print(Time[0] + " fast=" + fastSma[0].ToString("F2") + " slow=" + slowSma[0].ToString("F2")); at the top of OnBarUpdate() tells you in ten seconds whether your conditions are ever true. Half of all "my strategy doesn't take trades" problems are a condition that never fires, and Print() finds it faster than any amount of re-reading.
Step 6: Backtest it in the Strategy Analyzer
Control Center → New → Strategy Analyzer. Pick your instrument, set the date range, choose SmaCrossStrategy from the strategy list, set your parameters, and run. The Strategy Analyzer help guide covers the full window, but three settings change your results more than anything else:
- Order Fill Resolution. The default fills using the bar's OHLC, which on a 5-minute chart means the engine has to guess whether your stop or your target was hit first inside a bar that touched both. Set it to High with a 1-tick or 1-minute series and the fill assumptions get far closer to reality. Backtests that look excellent on Standard resolution and fall apart on High were never real.
- Slippage and commission.
IncludeCommission = trueis already in the code. Add slippage in the Analyzer settings. A strategy trading 40-tick stops survives a tick of slippage; one scalping 4 ticks does not. - Date range. Test across at least one regime change. A crossover system tested only through a trending stretch will look like genius and stop working the week you fund it.
Read the results as a set, not as a single net-profit number. Profit factor, max drawdown, trade count, and average trade all have to be acceptable together, and a strategy with 14 trades has told you nothing regardless of what those 14 trades did.
Apply the same strategy to a chart afterwards. Right-click the chart → Strategies → select it → OK. It will mark entries and exits visually, and seeing where it entered is worth more than any statistic when you're deciding whether the logic is doing what you meant.
Common mistakes that cost beginners days
❌ Instantiating indicators in State.SetDefaults.
✅ Instantiate in State.DataLoaded. SetDefaults runs before any data exists, and it runs every time the Strategies window opens, which means heavy work there slows down the whole platform.
❌ No CurrentBar guard.
✅ if (CurrentBar < SlowPeriod + 1) return; as the first real line of OnBarUpdate(). Any lookback deeper than the bars available throws.
❌ Running Calculate.OnEachTick on day one.
✅ Start with Calculate.OnBarClose. When you do move to per-tick evaluation, gate entry logic with if (IsFirstTickOfBar) or you'll fire the same signal hundreds of times inside one bar.
❌ Mixing managed and unmanaged order methods. ✅ Pick one per strategy. The managed approach rejects the mix, and the resulting error message doesn't obviously say so.
❌ Copying NinjaTrader 7 code from a forum post.
✅ If it has Initialize(), Variables(), or SetStopLoss inside Initialize(), it's NT7 and won't compile. The State machine is the tell that code is NT8.
❌ Judging a strategy on Standard Order Fill Resolution. ✅ Re-run anything promising on High resolution before you believe it. Intrabar fill assumptions flatter strategies whose stop and target can both be touched within a single bar.
❌ Using Position when you meant PositionAccount.
✅ Position is the strategy's own position. PositionAccount is the account's. They diverge the moment you run two strategies on the same instrument or take a manual trade alongside an automated one.
Pro tips for your first month in NinjaScript
Version by copy, not by edit. Duplicate the file as SmaCrossStrategy_v2.cs, rename the class inside to match, and compile. Two classes with the same name in the same namespace is a compile error, so the rename is mandatory, and having v1 intact when v2 gets worse is worth the ten seconds.
Set BarsRequiredToTrade explicitly. It defaults to 20. If your slowest input is a 200-period EMA, the default lets the strategy trade on bar 21 with a meaningless average. Match it to your longest lookback.
Use TraceOrders = true while debugging entries. Add it in State.SetDefaults and the Output Window logs every order submission, amendment, and rejection with a reason. It turns "why didn't it fill" from guesswork into reading.
Name every signal. EnterLong("MA Long") costs nothing over EnterLong() and makes stops, targets, exits, and the trade list all traceable to the rule that caused them.
Keep IsExitOnSessionCloseStrategy = true for intraday futures. Holding an index futures position through the daily close because your strategy had no reason to exit is a lesson people only need once.
Optimise last, and shallowly. The Strategy Analyzer's optimizer will happily search thousands of parameter combinations and hand you the one that fit the noise best. Two parameters over a coarse grid, validated on a date range you didn't optimise over, is the useful version.
Generating this without writing the code yourself
The strategy above runs to about 90 lines, and maybe 15 of them are the actual trading idea. A version with a session filter, an ATR-based stop, and a higher-timeframe trend condition is three times as long, and most of the extra is boilerplate you'll write identically every time.
HorizonAI generates NinjaScript from a plain description, then compiles it for real so syntax errors come back explained rather than as a red panel you have to decode. The prompt for exactly what this article built:
Write a NinjaScript strategy for NinjaTrader 8 that goes long when a 9-period SMA crosses above a 21-period SMA and short on the opposite cross. Add a 40-tick stop loss and an 80-tick profit target using SetStopLoss and SetProfitTarget in State.Configure. Expose fast period, slow period, stop ticks, and target ticks as NinjaScriptProperty parameters, use Calculate.OnBarClose, and guard OnBarUpdate with a CurrentBar check.
Or take a script you already trust somewhere else and move it across:
Convert this Pine Script v6 strategy to NinjaScript for NinjaTrader 8, keeping the ATR period at 14 and the stop multiplier at 2.5, and using managed order methods.
What comes back is compile-checked C# you can keep editing in the same chat, then paste into the NinjaScript Editor and press F5. It generates and converts code across Pine Script, MQL5, and NinjaScript; running the strategy, backtesting it in the Strategy Analyzer, and connecting to a broker stay entirely on your side of the line.
FAQs
Do I need to know C# before learning NinjaScript?
No, but you need to be willing to learn the parts you hit. Variables, if statements, methods, and types get you through a crossover strategy. Classes, interfaces, and collections come up when you build something with state. Trying to write NinjaScript while actively avoiding C# is where people stall.
What's the difference between NinjaScript and the Strategy Builder?
The Strategy Builder is a visual rule editor that generates NinjaScript underneath. It handles standard indicator-condition-action logic well and breaks down when you need custom calculations, loops, or anything referencing more than a couple of bars back. You can unlock any Builder strategy into editable code, which is the natural migration path, though it's a one-way trip for that file.
Why does my strategy compile but never take a trade?
Check three things in order: whether BarsRequiredToTrade is higher than the bars available, whether your condition is ever actually true (add a Print() and watch the Output Window), and whether the strategy is enabled with a chart data series that matches what the logic assumes. That last one catches more people than the first two combined.
Can I convert a Pine Script or MQL5 strategy into NinjaScript?
Yes, and the logic almost always survives. What changes is structure: Pine's implicit per-bar re-execution becomes an explicit OnBarUpdate() call, strategy.entry() becomes EnterLong(), and Pine's input.int() becomes a [NinjaScriptProperty]. Series indexing carries over unchanged, which handles most of the arithmetic.
Where do NinjaScript files live on disk?
Documents\NinjaTrader 8\bin\Custom\Strategies\ for strategies and \Indicators\ for indicators. Back that folder up. It's not synced anywhere, and a platform reinstall doesn't preserve it by default.
Final thoughts
The hard part of NinjaScript isn't the C#. It's the lifecycle: knowing that SetDefaults runs constantly and must stay lean, that indicators belong in DataLoaded, that protective orders go in Configure and must exist before the entry fills. Get those four placements right and the rest is ordinary programming.
One tip to leave with: before you touch a single strategy parameter, re-run your backtest with Order Fill Resolution set to High. A crossover system with a stop and a target inside the same bar is exactly the case where the default resolution guesses in your favour, and you'd rather find out now than after a month of tuning a number that was never real.
Related articles
- Pine Script Tutorial for Beginners — Build Your First TradingView Strategy in 10 Minutes — the same first-strategy walkthrough in TradingView's language.
- How to Convert Pine Script to MQL5: A Manual Translation Guide — the concept map for moving strategy logic between platforms.
- Simple Moving Average Crossover Strategy: Complete Guide — why crossovers whipsaw and which filters fix it.
- Pine Script vs MQL5 — Which Programming Language Should You Learn? — choosing a platform language before you commit months to it.
- EMA vs SMA — Which Moving Average is Better for Trading? (Backtested Results) — pick the right average before you optimise its period.
- How to Backtest a Trading Strategy (Complete Step-by-Step Guide) — the process that makes Strategy Analyzer output mean something.
- Understanding Backtesting Metrics — Win Rate, Profit Factor, Sharpe Ratio Explained — how to read the results panel without fooling yourself.
- How to Backtest Trading Strategies Like a Pro — Avoid These 7 Deadly Mistakes — the fill-assumption and curve-fitting traps in detail.
- Risk Management in Trading — Position Sizing, Stop Losses, and Risk-Reward Ratios — sizing that 40-tick stop against your actual account.
- How to Create a Trading Bot for MT5 (No Coding Required) — the MetaTrader equivalent of everything above.
Questions about NinjaScript? Join our Discord to discuss with other traders!
