RSI Strategy for NinjaTrader:
Working NinjaScript Code + Backtest
This NinjaScript strategy implements RSI mean reversion for NinjaTrader 8. NinjaTrader is the platform of choice for futures traders, and NinjaScript (built on C#) gives you full access to the .NET framework for advanced logic. This strategy monitors RSI levels and enters long positions when the indicator signals oversold conditions, then exits at overbought levels. NinjaTrader's built-in Strategy Analyzer provides detailed performance reports including Monte Carlo simulations.
What is Relative Strength Index?
The Relative Strength Index (RSI) is a momentum oscillator that measures the speed and magnitude of recent price changes. Developed by J. Welles Wilder in 1978, RSI oscillates between 0 and 100 and is traditionally used to identify overbought and oversold conditions in a market. When RSI rises above 70, the asset is considered overbought and may be due for a pullback. When it drops below 30, it is considered oversold and may be due for a bounce.
| Parameter | Value |
|---|---|
| overbought | 70 |
| oversold | 30 |
| midline | 50 |
RSI Mean Reversion NinjaScript Strategy: The Setup
Entry Rules
- ▶Enter long when RSI(14) crosses below 30 on bar close
- ▶Default position size: 1 contract (futures) or 100 shares (equities)
- ▶Only one position at a time — managed by NinjaTrader's order handling
Exit Rules
- ■Exit long when RSI(14) crosses above 70 on bar close
- ■Configurable stop-loss in ticks (default: 0 = disabled)
- ■Strategy processes on bar close to avoid intrabar signal noise
Working NinjaScript Code
This NinjaScript strategy follows the standard NinjaTrader 8 structure. In `OnStateChange()`, we set defaults when the strategy loads and create the RSI indicator when data is loaded. The `Calculate = Calculate.OnBarClose` setting ensures signals only fire on completed bars, preventing false signals from partial bars. `OnBarUpdate()` checks RSI crossover conditions using NinjaTrader's built-in `CrossBelow()` and `CrossAbove()` helper methods. The stop-loss is optional and uses NinjaTrader's managed approach via `SetStopLoss()`. Properties decorated with `[NinjaScriptProperty]` appear as configurable parameters in the NinjaTrader strategy dialog.
// RSI Mean Reversion Strategy for NinjaTrader 8
// NinjaScript (C#)
#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NinjaTrader.Cbi;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.Strategies;
#endregion
namespace NinjaTrader.NinjaScript.Strategies
{
public class RSIMeanReversion : Strategy
{
private RSI rsi;
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "RSI Period", Order = 1, GroupName = "Parameters")]
public int RSIPeriod { get; set; }
[NinjaScriptProperty]
[Range(50, 100)]
[Display(Name = "Overbought Level", Order = 2, GroupName = "Parameters")]
public int OverboughtLevel { get; set; }
[NinjaScriptProperty]
[Range(0, 50)]
[Display(Name = "Oversold Level", Order = 3, GroupName = "Parameters")]
public int OversoldLevel { get; set; }
[NinjaScriptProperty]
[Range(0, int.MaxValue)]
[Display(Name = "Stop Loss Ticks", Order = 4, GroupName = "Parameters")]
public int StopLossTicks { get; set; }
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = "RSI Mean Reversion Strategy";
Name = "RSIMeanReversion";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.AllEntries;
IsExitOnSessionCloseStrategy = false;
RSIPeriod = 14;
OverboughtLevel = 70;
OversoldLevel = 30;
StopLossTicks = 0;
}
else if (State == State.DataLoaded)
{
rsi = RSI(Close, RSIPeriod, 1);
AddChartIndicator(rsi);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < RSIPeriod)
return;
// Entry: RSI crosses below oversold level
if (CrossBelow(rsi, OversoldLevel, 1))
{
EnterLong("RSI Long");
if (StopLossTicks > 0)
SetStopLoss("RSI Long", CalculationMode.Ticks, StopLossTicks, false);
}
// Exit: RSI crosses above overbought level
if (CrossAbove(rsi, OverboughtLevel, 1))
{
ExitLong("RSI Exit", "RSI Long");
}
}
}
}Backtest Results: ES (E-mini S&P 500) (1D, 2019-2025)
Total Return
+$18,450
Annualized
+$3,075/year
Win Rate
59%
Max Drawdown
-$6,200
Sharpe Ratio
0.71
Total Trades
41
Profit Factor
1.52
Past performance does not guarantee future results. Backtest results may not reflect actual trading conditions including slippage, commissions, and liquidity constraints.
Optimization Tips
Use NinjaTrader's Strategy Analyzer Optimization tool to sweep RSI periods from 7 to 21 and find the best parameter for your instrument. Run Walk-Forward optimization to validate.
Add a time filter to avoid overnight gaps in futures. Use `ToTime(Time[0])` to restrict entries to regular trading hours (9:30 AM - 4:00 PM ET for ES).
Implement position sizing based on account equity. Replace the fixed contract size with `(int)(Account.Get(AccountItem.CashValue, Currency.UsDollar) / 10000)` for risk-based sizing.
Add the ATR indicator for dynamic stop-loss placement. NinjaTrader's `ATR()` function integrates cleanly: `SetStopLoss(CalculationMode.Price, Low[0] - ATR(14)[0])`.
Test with NinjaTrader's Monte Carlo simulation after backtesting. This estimates the probability of drawdowns and helps set realistic expectations.
Common Mistakes with RSI on NinjaTrader
Not setting `Calculate = Calculate.OnBarClose`. Without this, the strategy processes every tick, which generates unreliable signals and slows down backtesting dramatically.
Forgetting the `CurrentBar < RSIPeriod` guard in `OnBarUpdate()`. Without this check, the strategy will throw an index out of range exception on the first bars of data.
Using `State.Configure` instead of `State.DataLoaded` to create indicators. Indicators must be created in `DataLoaded` when bar data is available.
Not accounting for futures contract rollovers. When backtesting ES over multiple years, ensure you are using continuous contract data or NinjaTrader's merge policy settings.
Build This Strategy in 60 Seconds with SpendDock
Instead of copying and debugging this code, describe your strategy in plain English. SpendDock generates the NinjaScript code, backtests it against real market data, and lets you iterate until it is right.
Try SpendDock Free