RSI Martingale is a port of the MetaTrader 5 expert advisor RSI&Martingale1.5. The strategy searches for momentum reversals by waiting until the Relative Strength Index (RSI) reaches an extreme value within a configurable lookback window. When an extreme appears, it opens a trade in the direction of the expected mean reversion and exits when RSI crosses the 50 midline or when a fixed stop/take target is reached. A martingale module can optionally reopen the position in the opposite direction with an increased volume after a losing trade. Daily profit and loss limits, along with hourly filters, make it possible to suspend trading during riskier sessions or after meeting capital preservation goals.
Strategy logic
RSI extremes
Indicator – a single RSI calculated on the selected candle type. The indicator must be formed (enough historical data) before trades are considered.
Minimum detection – if the latest RSI value is less than or equal to every RSI value inside the configured Bars For Extremes window and the value is below 50, the strategy opens a long position.
Maximum detection – if the latest RSI value is greater than or equal to every value inside the lookback window and the value is above 50, the strategy opens a short position.
Position management
Exit trigger – positions are closed when RSI crosses the neutral 50 line to the opposite side (longs exit above 50, shorts exit below 50).
Fixed targets – optional stop-loss and take-profit distances expressed in pips. When enabled, the strategy compares the most recent candle’s high/low to those target prices and closes the position if either level is hit.
Volume alignment – every order volume is aligned to the security’s step, minimum, and maximum settings before submission.
Martingale recovery
Trigger – after a position closes with a negative profit, the strategy remembers the direction and volume of the losing trade.
Re-entry – on the next eligible candle, and only if no position is open, it can immediately open a trade in the opposite direction. The volume is either the losing volume multiplied by the Martingale Multiplier or the base Initial Volume depending on the Enable Martingale switch.
Reset – once the martingale order is submitted, the stored loss information is cleared to avoid repeated attempts.
Daily capital control
Baseline – the strategy captures the account equity at the start of each trading day and resets the suspension flag.
Monitoring window – daily limits are evaluated only between Daily Control Start and Daily Control End hours.
Suspension – if the equity grows beyond Daily Profit % or drops below Daily Loss %, the strategy closes any open position and skips new trades until the next day.
Session filters
Trading window – new positions are allowed only when the current hour is between Trading Start and Trading End (inclusive).
Hour avoidance – 24 boolean parameters mirror the source EA’s “news avoidance” settings and block trading during the selected hours.
Parameters
Initial Volume – base order volume for standard entries.
RSI Period – number of periods used by the RSI indicator.
Bars For Extremes – how many finished candles are scanned when looking for the latest RSI minimum or maximum.
Take Profit (pips) – distance to the fixed take-profit; set to 0 to disable.
Stop Loss (pips) – distance to the fixed stop-loss; set to 0 to disable.
Enable Martingale – enables the martingale recovery module after a losing trade.
Martingale Multiplier – multiplier applied to the previous losing volume when martingale is active.
Daily Targets – toggles the daily profit/loss suspension logic.
Daily Profit % – profit percentage that halts trading for the current day.
Daily Loss % – loss percentage that halts trading for the current day.
Daily Control Start / Daily Control End – hour boundaries for evaluating the daily limits.
Trading Start / Trading End – hour boundaries that allow new positions.
Avoid Hour 00 … Avoid Hour 23 – disable trading during the corresponding clock hour.
Candle Type – candle subscription used for the RSI indicator and all calculations.
Additional notes
The strategy operates on finished candles only and does not evaluate intrabar ticks.
Daily profit calculations combine realized strategy PnL with floating PnL based on the latest close price.
There is no Python implementation for this strategy in the package; only the C# version is provided.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI extremes strategy with martingale recovery.
/// Buys when RSI is at a local minimum below 50, sells when RSI is at a local maximum above 50.
/// Closes on RSI crossing 50. Doubles volume after a losing trade.
/// </summary>
public class RSIMartingaleStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _barsForCondition;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _recentRsi = new();
private decimal _entryPrice;
private int _direction; // 1=long, -1=short, 0=flat
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public int BarsForCondition
{
get => _barsForCondition.Value;
set => _barsForCondition.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public RSIMartingaleStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "RSI indicator period", "Indicator");
_barsForCondition = Param(nameof(BarsForCondition), 10)
.SetDisplay("Bars For Extremes", "Number of RSI values to check for extremes", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "Data");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_recentRsi.Add(rsiValue);
if (_recentRsi.Count > BarsForCondition)
_recentRsi.RemoveAt(0);
if (_recentRsi.Count < BarsForCondition)
return;
// Check exit: close on RSI crossing 50
if (_direction > 0 && rsiValue > 50)
{
SellMarket();
_direction = 0;
return;
}
else if (_direction < 0 && rsiValue < 50)
{
BuyMarket();
_direction = 0;
return;
}
if (Position != 0)
return;
// Check if current RSI is local minimum (oversold entry)
if (IsLocalMinimum() && rsiValue < 50 && Position <= 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_direction = 1;
}
// Check if current RSI is local maximum (overbought entry)
else if (IsLocalMaximum() && rsiValue > 50 && Position >= 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_direction = -1;
}
}
private bool IsLocalMinimum()
{
if (_recentRsi.Count < 2)
return false;
var current = _recentRsi[_recentRsi.Count - 1];
for (var i = 0; i < _recentRsi.Count - 1; i++)
{
if (current > _recentRsi[i])
return false;
}
return true;
}
private bool IsLocalMaximum()
{
if (_recentRsi.Count < 2)
return false;
var current = _recentRsi[_recentRsi.Count - 1];
for (var i = 0; i < _recentRsi.Count - 1; i++)
{
if (current < _recentRsi[i])
return false;
}
return true;
}
/// <inheritdoc />
protected override void OnReseted()
{
_recentRsi.Clear();
_entryPrice = 0;
_direction = 0;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class rsi_martingale_strategy(Strategy):
def __init__(self):
super(rsi_martingale_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._bars_for_condition = self.Param("BarsForCondition", 10)
self._recent_rsi = []
self._entry_price = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def BarsForCondition(self):
return self._bars_for_condition.Value
@BarsForCondition.setter
def BarsForCondition(self, value):
self._bars_for_condition.Value = value
def OnReseted(self):
super(rsi_martingale_strategy, self).OnReseted()
self._recent_rsi = []
self._entry_price = 0.0
self._direction = 0
def OnStarted2(self, time):
super(rsi_martingale_strategy, self).OnStarted2(time)
self._recent_rsi = []
self._entry_price = 0.0
self._direction = 0
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _is_local_minimum(self):
if len(self._recent_rsi) < 2:
return False
current = self._recent_rsi[-1]
for i in range(len(self._recent_rsi) - 1):
if current > self._recent_rsi[i]:
return False
return True
def _is_local_maximum(self):
if len(self._recent_rsi) < 2:
return False
current = self._recent_rsi[-1]
for i in range(len(self._recent_rsi) - 1):
if current < self._recent_rsi[i]:
return False
return True
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
bars_for_cond = self.BarsForCondition
self._recent_rsi.append(rsi_val)
while len(self._recent_rsi) > bars_for_cond:
self._recent_rsi.pop(0)
if len(self._recent_rsi) < bars_for_cond:
return
# Check exit: close on RSI crossing 50
if self._direction > 0 and rsi_val > 50:
self.SellMarket()
self._direction = 0
return
elif self._direction < 0 and rsi_val < 50:
self.BuyMarket()
self._direction = 0
return
if self.Position != 0:
return
close = float(candle.ClosePrice)
# Local minimum + RSI below 50 -> buy
if self._is_local_minimum() and rsi_val < 50 and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._direction = 1
# Local maximum + RSI above 50 -> sell
elif self._is_local_maximum() and rsi_val > 50 and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._direction = -1
def CreateClone(self):
return rsi_martingale_strategy()