Открыть на GitHub

RSI Martingale

Обзор

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 StartTrading 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();
	}
}