在 GitHub 上查看

Sidus EMA RSI 策略

本策略是 MetaTrader 4 专家顾问 Exp_Sidus.mq4 在 StockSharp 平台上的移植版本。它保留了原始机器人“快速/慢速 EMA 交叉 + RSI 过滤”的全部交易规则,所有判断均在收盘完成的 K 线后执行,并确保每根信号 K 线最多只触发一次下单,与 MQL 版本完全一致。

交易逻辑

  • 指标组合
    • 快速指数移动平均线(默认周期 5)
    • 慢速指数移动平均线(默认周期 12)
    • 相对强弱指标 RSI(默认周期 21)
  • 做多条件
    1. 在上一根信号 K 线上,快速 EMA 小于或等于慢速 EMA。
    2. 在当前信号 K 线上,快速 EMA 上穿慢速 EMA。
    3. 同一根信号 K 线上的 RSI 严格大于 50。
  • 做空条件
    1. 在上一根信号 K 线上,快速 EMA 大于或等于慢速 EMA。
    2. 在当前信号 K 线上,快速 EMA 下穿慢速 EMA。
    3. 同一根信号 K 线上的 RSI 严格小于 50。
  • 信号偏移SignalShift 参数(默认 1)用于定义哪一根已完成的 K 线作为“当前信号”K 线。数值 1 表示上一根 K 线,0 表示刚刚收盘的 K 线,2 表示向前数第二根,以此类推。用于交叉比较的上一根 K 线自动取 SignalShift + 1
  • 重复保护 — 策略会记录信号 K 线的开盘时间,如果同一根 K 线已经触发过交易,则不会再次下单,对应 MQL 中的 LastTime 判断。

仓位管理

  • 任意时刻仅允许持有一个方向的仓位。
  • 当持仓期间出现反向信号时,策略先平掉现有仓位,再等待下一次处理周期在新方向上开仓,与原始 EA 的行为完全相同。
  • 通过 StartProtection 挂载以“价格步长”为单位的止盈/止损保护。默认止盈为 80 个点,止损为 20 个点,来自 MQL 输入参数。

参数说明

参数 描述 默认值 备注
TakeProfitPoints 止盈距离(价格步长数) 80 设为 0 可关闭止盈
StopLossPoints 止损距离(价格步长数) 20 设为 0 可关闭止损
TradeVolume 下单手数/合约数 0.1 启动时赋给基础 Volume 属性
FastPeriod 快速 EMA 周期 5 可优化
SlowPeriod 慢速 EMA 周期 12 可优化
RsiPeriod RSI 周期 21 可优化
SignalShift 用于计算信号的已完成 K 线数量 1 对应 MT4 中的 shif 输入
CandleType 使用的 K 线类型 1 小时周期 可配置任意 DataType

实现细节

  • 通过 SubscribeCandles(CandleType) 订阅 K 线,并仅在 CandleStates.Finished 状态下调用 ProcessCandle 进行计算。
  • 为了避免直接调用指标的 GetValue 方法,策略使用一个短队列缓存指标值,用以读取 SignalShift 指定的当前与上一根 K 线。
  • 交易执行遵循“先平仓后反手”的逻辑:当持有反向仓位时先调用 ClosePosition(),待仓位归零后再在下一次处理循环中调用 BuyMarket/SellMarket
  • 日志信息全部使用英文输出,方便排查和回测复现。

移植说明

  • 止盈与止损距离会与标的物的 PriceStep 相乘,复现 MetaTrader 中 Point 的含义。
  • 默认下单量 0.1 对应 MQL 源码中的 Lots 输入。
  • RSI 判定阈值固定为 50,与原代码保持一致。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Sidus EMA + RSI strategy: fast EMA crosses slow EMA confirmed by RSI above/below 50.
/// </summary>
public class SidusEmaRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

	private decimal _prevFast;
	private decimal _prevSlow;

	public SidusEmaRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 12)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetDisplay("RSI Period", "RSI period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		if (_prevFast == 0 || _prevSlow == 0)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var bullishCross = _prevFast <= _prevSlow && fastValue > slowValue;
		var bearishCross = _prevFast >= _prevSlow && fastValue < slowValue;

		// Exit existing positions on opposite cross
		if (Position > 0 && bearishCross)
		{
			SellMarket();
		}
		else if (Position < 0 && bullishCross)
		{
			BuyMarket();
		}

		// Entry on crossover confirmed by RSI
		if (Position == 0)
		{
			if (bullishCross && rsiValue > 50)
			{
				BuyMarket();
			}
			else if (bearishCross && rsiValue < 50)
			{
				SellMarket();
			}
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}