Открыть на GitHub

Стратегия Sidus v1

Обзор

Sidus v1 — трендовая стратегия, сочетающая две пары экспоненциальных скользящих средних (EMA) и фильтры индикатора RSI. Исходный советник MetaTrader 4 открывает сделку, когда быстрая EMA заметно отклоняется от медленной, а RSI подтверждает перепроданность или перекупленность. В версии для StockSharp сохранена логика исходника: торговля разрешается только на свечах с низким объёмом, а для длинных и коротких позиций выставляются асимметричные защитные ордера.

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

  • Быстрая EMA (для покупок) — оценивает краткосрочный импульс.
  • Медленная EMA (для покупок) — фильтр направления тренда для длинных сделок.
  • Быстрая EMA (для продаж) — измеряет импульс перед короткими сделками.
  • Медленная EMA (для продаж) — фильтр тренда перед короткими сделками.
  • RSI (для покупок) — подтверждает зону перепроданности.
  • RSI (для продаж) — подтверждает зону перекупленности.

Логика торговли

  1. Подписка на свечной поток с выбранным таймфреймом (по умолчанию 15 минут).
  2. На каждой завершённой свече пересчитываются все EMA и RSI.
  3. Если объём свечи превышает заданный порог, сигналы игнорируются.
  4. Покупка выполняется, когда:
    • Разность быстрой и медленной EMA меньше заданного порога.
    • Значение RSI ниже порога для покупок.
    • Текущая позиция не длинная (нет положительного нетто-объёма).
  5. Продажа выполняется, когда:
    • Разность «коротких» EMA выше порога для продаж.
    • Значение RSI выше порога для продаж.
    • Текущая позиция не короткая (нет отрицательного нетто-объёма).
  6. При появлении сигнала отменяются активные защитные ордера, отправляется рыночный ордер нужного объёма (при необходимости разворот позиции), затем сразу выставляются соответствующие take-profit и stop-loss.

Управление рисками

  • Для длинных позиций take-profit размещается на entry + BuyTakeProfitPips * priceStep, stop-loss — на entry - BuyStopLossPips * priceStep.
  • Для коротких позиций take-profit размещается на entry - SellTakeProfitPips * priceStep, stop-loss — на entry + SellStopLossPips * priceStep.
  • Расстояния задаются в пунктах и переводятся в цену через шаг цены инструмента. При необходимости скорректируйте параметры под шаг цены выбранного актива.

Параметры

Параметр Описание Значение по умолчанию
FastEmaLength Длина быстрой EMA для покупок. 23
SlowEmaLength Длина медленной EMA для покупок. 62
FastEma2Length Длина быстрой EMA для продаж. 18
SlowEma2Length Длина медленной EMA для продаж. 54
RsiPeriod Период RSI для фильтра покупок. 67
RsiPeriod2 Период RSI для фильтра продаж. 97
BuyDifferenceThreshold Максимальная разница EMA для открытия покупок. 63
BuyRsiThreshold Максимальный уровень RSI для покупок. 59
SellDifferenceThreshold Минимальная разница EMA для открытия продаж. -57
SellRsiThreshold Минимальный уровень RSI для продаж. 60
BuyTakeProfitPips Дистанция take-profit (пункты) для длинных сделок. 95
BuyStopLossPips Дистанция stop-loss (пункты) для длинных сделок. 100
SellTakeProfitPips Дистанция take-profit (пункты) для коротких сделок. 17
SellStopLossPips Дистанция stop-loss (пункты) для коротких сделок. 69
OrderVolume Объём новой позиции. 0.5
MaxCandleVolume Максимальный объём свечи для входа. 10
CandleType Таймфрейм расчёта индикаторов. 15-минутные свечи

Рекомендации по использованию

  • Убедитесь, что выбранный брокер/площадка поддерживает одновременные рыночные, стоп- и лимитные ордера.
  • Подстройте параметры в пунктах под шаг цены конкретного инструмента — в MT4 стратегия использовала значение Point.
  • Стратегия работает с неттинговой позицией: при смене направления сначала закрывается противоположный объём и только затем открывается новая позиция.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on the Sidus v1 expert advisor using EMA and RSI filters.
/// Buys when the fast EMA is sufficiently below the slow EMA and RSI is oversold.
/// Sells when the fast EMA is sufficiently above the slow EMA and RSI is overbought.
/// </summary>
public class SidusV1Strategy : Strategy
{
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _fastEma2Length;
	private readonly StrategyParam<int> _slowEma2Length;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _rsiPeriod2;
	private readonly StrategyParam<decimal> _buyDifferenceThreshold;
	private readonly StrategyParam<decimal> _buyRsiThreshold;
	private readonly StrategyParam<decimal> _sellDifferenceThreshold;
	private readonly StrategyParam<decimal> _sellRsiThreshold;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<DataType> _candleType;

	/// <summary>
	/// Length of the fast EMA for buy signal calculation.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA for buy signal calculation.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the fast EMA for sell signal calculation.
	/// </summary>
	public int FastEma2Length
	{
		get => _fastEma2Length.Value;
		set => _fastEma2Length.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA for sell signal calculation.
	/// </summary>
	public int SlowEma2Length
	{
		get => _slowEma2Length.Value;
		set => _slowEma2Length.Value = value;
	}

	/// <summary>
	/// RSI period used for buy signals.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI period used for sell signals.
	/// </summary>
	public int RsiPeriod2
	{
		get => _rsiPeriod2.Value;
		set => _rsiPeriod2.Value = value;
	}

	/// <summary>
	/// Threshold for EMA difference to allow buy orders (negative means fast below slow).
	/// </summary>
	public decimal BuyDifferenceThreshold
	{
		get => _buyDifferenceThreshold.Value;
		set => _buyDifferenceThreshold.Value = value;
	}

	/// <summary>
	/// RSI threshold to confirm oversold conditions.
	/// </summary>
	public decimal BuyRsiThreshold
	{
		get => _buyRsiThreshold.Value;
		set => _buyRsiThreshold.Value = value;
	}

	/// <summary>
	/// Threshold for EMA difference to allow sell orders (positive means fast above slow).
	/// </summary>
	public decimal SellDifferenceThreshold
	{
		get => _sellDifferenceThreshold.Value;
		set => _sellDifferenceThreshold.Value = value;
	}

	/// <summary>
	/// RSI threshold to confirm overbought conditions.
	/// </summary>
	public decimal SellRsiThreshold
	{
		get => _sellRsiThreshold.Value;
		set => _sellRsiThreshold.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SidusV1Strategy"/> class.
	/// </summary>
	public SidusV1Strategy()
	{
		_fastEmaLength = Param(nameof(FastEmaLength), 23)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Length of the fast EMA for buy signals", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 62)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Length of the slow EMA for buy signals", "Indicators");

		_fastEma2Length = Param(nameof(FastEma2Length), 18)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length (Sell)", "Length of the fast EMA for sell signals", "Indicators");

		_slowEma2Length = Param(nameof(SlowEma2Length), 54)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length (Sell)", "Length of the slow EMA for sell signals", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 67)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period used for buy signals", "Indicators");

		_rsiPeriod2 = Param(nameof(RsiPeriod2), 97)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period (Sell)", "RSI period used for sell signals", "Indicators");

		_buyDifferenceThreshold = Param(nameof(BuyDifferenceThreshold), -100m)
			.SetDisplay("Buy EMA Threshold", "Maximum fast-slow EMA difference to allow buy", "Trading Rules");

		_buyRsiThreshold = Param(nameof(BuyRsiThreshold), 45m)
			.SetDisplay("Buy RSI Threshold", "Maximum RSI level to allow buy", "Trading Rules");

		_sellDifferenceThreshold = Param(nameof(SellDifferenceThreshold), 100m)
			.SetDisplay("Sell EMA Threshold", "Minimum fast-slow EMA difference to allow sell", "Trading Rules");

		_sellRsiThreshold = Param(nameof(SellRsiThreshold), 55m)
			.SetDisplay("Sell RSI Threshold", "Minimum RSI level to allow sell", "Trading Rules");

		_stopLoss = Param(nameof(StopLoss), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance in absolute price units", "Risk Management");

		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance in absolute price units", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for calculations", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return new[] { (Security, CandleType) };
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var fastEma = new EMA { Length = FastEmaLength };
		var slowEma = new EMA { Length = SlowEmaLength };
		var fastEma2 = new EMA { Length = FastEma2Length };
		var slowEma2 = new EMA { Length = SlowEma2Length };
		var rsi = new RSI { Length = RsiPeriod };
		var rsi2 = new RSI { Length = RsiPeriod2 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, fastEma2, slowEma2, rsi, rsi2, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}

		// Use StartProtection for SL/TP
		var tp = TakeProfit > 0 ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
		var sl = StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle,
		decimal fastEmaValue,
		decimal slowEmaValue,
		decimal fastEma2Value,
		decimal slowEma2Value,
		decimal rsiValue,
		decimal rsi2Value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var diffBuy = fastEmaValue - slowEmaValue;
		var diffSell = fastEma2Value - slowEma2Value;

		// Buy when fast EMA is sufficiently below slow EMA and RSI is oversold
		if (diffBuy < BuyDifferenceThreshold && rsiValue < BuyRsiThreshold && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
		}
		// Sell when fast EMA is sufficiently above slow EMA and RSI is overbought
		else if (diffSell > SellDifferenceThreshold && rsi2Value > SellRsiThreshold && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
		}
	}
}