RSI Martingale — порт советника MetaTrader 5 RSI&Martingale1.5. Стратегия отслеживает развороты импульса, пока индекс относительной силы (RSI) не обновит экстремум в заданном окне. При появлении минимума или максимума открывается сделка в направлении ожидаемого отката, а выход выполняется при пересечении уровнем RSI отметки 50 либо при достижении фиксированного тейк-профита/стоп-лосса. Дополнительно реализован модуль мартингейла, который после убыточной позиции может открыть обратную сделку с увеличенным объёмом. Ежедневные лимиты по прибыли и убытку, а также фильтры по часам помогают отключить торговлю в нежелательные периоды или после достижения финансовых целей.
Логика стратегии
Экстремумы RSI
Индикатор – одиночный RSI на выбранном типе свечей. До формирования индикатора (наличия достаточного количества данных) сигналы игнорируются.
Поиск минимума – если текущее значение RSI не превышает ни одного значения внутри окна Bars For Extremes и само значение ниже 50, открывается длинная позиция.
Поиск максимума – если текущее значение RSI не меньше всех значений в окне и выше 50, открывается короткая позиция.
Управление позицией
Выход – позиция закрывается, когда RSI пересекает уровень 50 в противоположную сторону (лонг закрывается выше 50, шорт – ниже 50).
Фиксированные цели – опциональные стоп-лосс и тейк-профит в пунктах. При включении стратегия сравнивает максимум/минимум последней свечи с целевыми уровнями и закрывает позицию при их достижении.
Выравнивание объёма – перед отправкой заявок объём приводится к шагу, минимуму и максимуму инструмента.
Мартингейл
Срабатывание – после закрытия позиции с отрицательным результатом сохраняются направление и объём убыточной сделки.
Повторный вход – при отсутствии открытой позиции на следующей подходящей свече стратегия может немедленно открыть сделку в противоположную сторону. Объём равен убыточному объёму, умноженному на Martingale Multiplier, либо базовому Initial Volume (в зависимости от флага Enable Martingale).
Сброс – после отправки мартингейлового ордера информация об убытке очищается, чтобы избежать повторных попыток.
Ежедневный контроль капитала
Базовая точка – в начале каждого торгового дня фиксируется текущая стоимость портфеля и сбрасывается флаг приостановки.
Окно контроля – лимиты проверяются только между часами Daily Control Start и Daily Control End.
Приостановка – если капитал вырос выше Daily Profit % или упал ниже Daily Loss %, стратегия закрывает позицию и пропускает новые сделки до следующего дня.
Фильтры по времени
Торговое окно – новые позиции открываются только если текущий час попадает в диапазон Trading Start–Trading End (включительно).
Исключаемые часы – 24 булевых параметра повторяют настройку «avoid news» из исходного советника и блокируют торговлю в выбранные часы.
Параметры
Initial Volume – базовый объём заявки для стандартных входов.
RSI Period – период расчёта индикатора RSI.
Bars For Extremes – количество завершённых свечей, анализируемых при поиске экстремума RSI.
Take Profit (pips) – расстояние до фиксированного тейк-профита; 0 отключает уровень.
Stop Loss (pips) – расстояние до стоп-лосса; 0 отключает уровень.
Enable Martingale – включает восстановительный модуль мартингейла после убытка.
Martingale Multiplier – множитель к объёму убыточной сделки при активном мартингейле.
Daily Targets – включает логику приостановки торговли по дневной прибыли/убытку.
Daily Profit % – процент прибыли, после которого торговля останавливается до конца дня.
Daily Loss % – процент убытка, после которого торговля останавливается до конца дня.
Daily Control Start / Daily Control End – границы часов, в которые оцениваются дневные лимиты.
Trading Start / Trading End – рабочий интервал часов для открытия новых позиций.
Avoid Hour 00 … Avoid Hour 23 – запрет торговли в соответствующий час.
Candle Type – тип свечей, используемых для расчёта RSI и логики стратегии.
Дополнительные замечания
Стратегия работает только с завершёнными свечами и не анализирует тики внутри бара.
Расчёт дневной прибыли комбинирует реализованный PnL стратегии и плавающий результат, основанный на последней цене закрытия.
В пакет входит только C#-версия стратегии; реализация на Python отсутствует.
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()