Открыть на GitHub

Стратегия Improve MA & RSI Hedge

Стратегия является переносом эксперта MetaTrader «Improve» на платформу StockSharp. Она одновременно торгует двумя инструментами: основным, выбранным в настройках стратегии, и хеджирующим. Направление сделки определяется положением двух сглаженных скользящих средних (SMMA) на основном инструменте и значением индикатора RSI. Хеджирующая нога открывается в том же направлении, формируя синхронную позицию на двух инструментах и уменьшая риск отдельного актива.

Логика стратегии

  • Рассчитываются две сглаженные скользящие средние (SMMA) с настраиваемыми периодами для основного инструмента.
  • RSI вычисляется по тем же свечам и сравнивается с уровнями перепроданности/перекупленности.
  • Длинная позиция открывается одновременно по обоим инструментам, когда медленная SMMA находится выше быстрой, а RSI ниже или равен уровню перепроданности.
  • Короткая позиция открывается по обоим инструментам, когда медленная SMMA ниже быстрой, а RSI выше или равен уровню перекупленности.
  • Открытые позиции удерживаются до тех пор, пока суммарная плавающая прибыль по двум ногам не превысит заданный денежный порог. После достижения цели обе позиции закрываются рыночными заявками.

Алгоритм хранит последние цены закрытия каждого инструмента и оценивает суммарную прибыль через разницу между текущей ценой и ценой входа. Стоп‑лоссов нет, поэтому позиции могут находиться в рынке долгое время, если цена не достигает цели по прибыли.

Параметры

Параметр Описание
Volume Объём заявки для основного и хеджирующего инструмента.
Profit Target Денежная цель для обеих ног. При достижении значения все позиции закрываются.
Hedge Security Хеджирующий инструмент, торгуемый вместе с основным.
Fast MA Период быстрой сглаженной скользящей средней (по умолчанию 8).
Slow MA Период медленной сглаженной скользящей средней (по умолчанию 21). Должен быть больше периода быстрой.
RSI Period Длина расчёта RSI (по умолчанию 21).
Oversold Уровень RSI для входа в покупки при выполнении условия по средним (по умолчанию 30).
Overbought Уровень RSI для входа в продажи при выполнении условия по средним (по умолчанию 70).
Candle Type Таймфрейм свечей для расчётов. По умолчанию используется часовой интервал.

Используемые индикаторы

  • Smoothed Moving Average (SMMA) — два индикатора с разными периодами для оценки направления тренда.
  • Relative Strength Index (RSI) — определяет зоны перепроданности и перекупленности.

Правила входа и выхода

  1. Покупка
    • Медленная SMMA выше быстрой.
    • RSI ≤ уровень перепроданности.
    • Рыночные заявки на покупку отправляются по основному и хеджирующему инструментам.
  2. Продажа
    • Медленная SMMA ниже быстрой.
    • RSI ≥ уровень перекупленности.
    • Рыночные заявки на продажу отправляются по обоим инструментам.
  3. Выход
    • Когда (прибыль по основному инструменту + прибыль по хеджу) ≥ Profit Target, обе позиции закрываются рыночными заявками.
    • Дополнительных стоп‑лоссов и трейлинг‑стопов нет. При необходимости их следует реализовывать отдельно.

Дополнительные рекомендации

  • Перед запуском стратегии необходимо указать как основной, так и хеджирующий инструменты, иначе будет сгенерировано исключение.
  • Суммарная прибыль рассчитывается по ценам закрытия свечей, поэтому фактический результат может отличаться из‑за проскальзывания и различий в исполнении.
  • Стратегия подходит для коррелированных инструментов (например, валютных пар или связанных фьючерсов), где ожидается синхронное движение.
  • Рекомендуется дополнить стратегию портфельными ограничениями по риску, поскольку единственным механизмом выхода остаётся виртуальная цель по прибыли.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5.
/// </summary>
public class ImproveMaRsiHedgeStrategy : Strategy
{
	private readonly StrategyParam<decimal> _profitTarget;
	private readonly StrategyParam<Security> _hedgeSecurity;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _baseLastClose;
	private decimal _hedgeLastClose;
	private decimal _baseEntryPrice;
	private decimal _hedgeEntryPrice;
	private bool _hasBaseClose;
	private bool _hasHedgeClose;
	private int _pairDirection;


	/// <summary>
	/// Profit target across both legs expressed in money.
	/// </summary>
	public decimal ProfitTarget
	{
		get => _profitTarget.Value;
		set => _profitTarget.Value = value;
	}

	/// <summary>
	/// Second instrument traded alongside the primary security.
	/// </summary>
	public Security HedgeSecurity
	{
		get => _hedgeSecurity.Value;
		set => _hedgeSecurity.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the fast line.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the slow line.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// RSI calculation length.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI oversold threshold.
	/// </summary>
	public decimal OversoldLevel
	{
		get => _oversoldLevel.Value;
		set => _oversoldLevel.Value = value;
	}

	/// <summary>
	/// RSI overbought threshold.
	/// </summary>
	public decimal OverboughtLevel
	{
		get => _overboughtLevel.Value;
		set => _overboughtLevel.Value = value;
	}

	/// <summary>
	/// Type of candles used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ImproveMaRsiHedgeStrategy"/> class.
	/// </summary>
	public ImproveMaRsiHedgeStrategy()
	{

		_profitTarget = Param(nameof(ProfitTarget), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Profit Target", "Combined profit target across both legs", "Risk")
			;

		_hedgeSecurity = Param<Security>(nameof(HedgeSecurity))
			.SetDisplay("Hedge Security", "Secondary instrument to trade", "General");

		_fastPeriod = Param(nameof(FastMaPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast smoothed MA period", "Indicators")
			;

		_slowPeriod = Param(nameof(SlowMaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow smoothed MA period", "Indicators")
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
			;

		_oversoldLevel = Param(nameof(OversoldLevel), 30m)
			.SetDisplay("Oversold", "RSI oversold threshold", "Indicators")
			;

		_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
			.SetDisplay("Overbought", "RSI overbought threshold", "Indicators")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for calculations", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);

		if (HedgeSecurity != null)
			yield return (HedgeSecurity, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fastMa = null!;
		_slowMa = null!;
		_rsi = null!;
		_baseLastClose = 0m;
		_hedgeLastClose = 0m;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
		_hasBaseClose = false;
		_hasHedgeClose = false;
		_pairDirection = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		if (Security == null)
			throw new InvalidOperationException("Primary security must be specified.");

		if (HedgeSecurity == null)
			throw new InvalidOperationException("Hedge security must be specified.");

		if (FastMaPeriod >= SlowMaPeriod)
			throw new InvalidOperationException("Fast MA period must be less than slow MA period.");

		_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var baseSubscription = SubscribeCandles(CandleType);
		baseSubscription
			.Bind(_fastMa, _slowMa, _rsi, ProcessBaseCandle)
			.Start();

		var hedgeSubscription = SubscribeCandles(CandleType, false, HedgeSecurity);
		hedgeSubscription
			.Bind(ProcessHedgeCandle)
			.Start();
	}

	private void ProcessBaseCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_baseLastClose = candle.ClosePrice;
		_hasBaseClose = true;

		CheckProfitTarget();

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed)
			return;

		if (_pairDirection != 0)
			return;

		if (!_hasHedgeClose)
			return;

		if (slowValue > fastValue && rsiValue <= OversoldLevel)
		{
			OpenPair(1);
		}
		else if (slowValue < fastValue && rsiValue >= OverboughtLevel)
		{
			OpenPair(-1);
		}
	}

	private void ProcessHedgeCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_hedgeLastClose = candle.ClosePrice;
		_hasHedgeClose = true;

		CheckProfitTarget();
	}

	private void OpenPair(int direction)
	{
		if (direction == 0)
			return;

		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;

		if (basePos != 0m || hedgePos != 0m)
			return;

		var volume = Volume;

		if (direction > 0)
		{
			BuyMarket(volume, Security);
			BuyMarket(volume, HedgeSecurity);
		}
		else
		{
			SellMarket(volume, Security);
			SellMarket(volume, HedgeSecurity);
		}

		_pairDirection = direction;
		_baseEntryPrice = _baseLastClose;
		_hedgeEntryPrice = _hedgeLastClose;
	}

	private void CheckProfitTarget()
	{
		if (_pairDirection == 0 || !_hasBaseClose || !_hasHedgeClose)
			return;

		var baseProfit = _pairDirection > 0
			? (_baseLastClose - _baseEntryPrice) * Volume
			: (_baseEntryPrice - _baseLastClose) * Volume;

		var hedgeProfit = _pairDirection > 0
			? (_hedgeLastClose - _hedgeEntryPrice) * Volume
			: (_hedgeEntryPrice - _hedgeLastClose) * Volume;

		var totalProfit = baseProfit + hedgeProfit;

		if (totalProfit >= ProfitTarget)
		{
			ClosePair();
		}
	}

	private void ClosePair()
	{
		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		if (basePos > 0)
		{
			SellMarket(basePos, Security);
		}
		else if (basePos < 0)
		{
			BuyMarket(-basePos, Security);
		}

		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
		if (hedgePos > 0)
		{
			SellMarket(hedgePos, HedgeSecurity);
		}
		else if (hedgePos < 0)
		{
			BuyMarket(-hedgePos, HedgeSecurity);
		}

		_pairDirection = 0;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
	}
}