在 GitHub 上查看

EMA RSI 波动自适应均线交叉策略

本策略是 MetaTrader 专家顾问 EA_MARSI_1-02 的直接移植版本。它监听两条 EMA_RSI_VA 指标线的交叉情况 —— 该指 标由 Integer 编写,利用 RSI 推动 EMA 的自适应周期。每当慢线与快线交叉,策略都会立即翻转净头寸,以完全再现原始 EA 的“信号即反手”逻辑,并遵循 StockSharp 的下单规范。

指标工作方式

原始 MQL 套件包含自定义指标 EMA_RSI_VA。它在价格上构建 EMA,并根据 RSI 偏离 50 的幅度动态调整平滑周期。本移 植版本实现了 EmaRsiVolatilityAdaptiveIndicator 类来还原该公式:

  1. 在选定的 AppliedPrice 价格源上计算周期为 RSIPeriod 的 RSI。
  2. 计算 RSI 与 50 的距离 |RSI - 50| + 1,作为波动度代理。
  3. 构造自适应系数 multi = (5 + 100 / RSIPeriod) / (0.06 + 0.92 * dist + 0.02 * dist^2)
  4. 使用该系数乘以基础 EMA 周期,得到动态周期 pdsx
  5. 以平滑系数 2 / (pdsx + 1) 执行标准 EMA 递推,并使用所选的价格类型作为输入。

当 RSI 远离 50 时,动态周期缩短,指标反应更快;RSI 平稳时,周期拉长以抑制噪声。慢线与快线均支持 StockSharp.Messages.AppliedPrice 的全部价格模式。

交易规则

  • 信号判断
    • 做空/卖出: 前一根 K 线的慢线 < 快线,且当前慢线 ≥ 快线。
    • 做多/买入: 前一根 K 线的慢线 > 快线,且当前慢线 ≤ 快线。
  • 执行细节
    • 只处理已完成的蜡烛(CandleStates.Finished)。
    • 触发信号时提交一笔市价单,其数量可同时平掉原有仓位并建立新的反向仓位。
    • 体量会自动匹配 Security.MinVolumeSecurity.VolumeStepSecurity.MaxVolume 等交易所限制。
  • 翻转
    • 通过一次 SellMarketBuyMarket 调整净头寸穿过零点,从而模拟 EA 在出现反向信号时立即反手的行为。

风险管理

  • TakeProfitPointsStopLossPoints 对应 EA 中的 TP/SL(以价格点表示)。任一参数大于零时会启动 StockSharp 的保护 管理器,使用绝对价差和 useMarketOrders = true,模拟原程序中不断修改止损/止盈的逻辑。
  • UseBalanceMultiplier 实现了 use_Multpl。启用后实际下单量变为 Volume * PortfolioEquity / MaxDrawdown,随后再按交易所要求进行归一化。
  • 依旧调用 StartProtection(),方便外部模块挂接跟踪止损或保本逻辑。

参数

参数 默认值 说明
Volume 0.1 启动时的基础下单手数,未应用资金乘数前的数值。
TakeProfitPoints 0 止盈距离(点);0 表示不启用。
StopLossPoints 0 止损距离(点);0 表示不启用。
UseBalanceMultiplier false 是否启用资金规模乘数,等价于 EA 的 use_Multpl
MaxDrawdown 10000 资金乘数的分母,对应 EA 的 Max_drawdown
SlowRsiPeriod 310 慢线 RSI 周期。
SlowEmaPeriod 40 慢线的基础 EMA 周期。
SlowAppliedPrice Close 慢线使用的价格类型。
FastRsiPeriod 200 快线 RSI 周期。
FastEmaPeriod 50 快线的基础 EMA 周期。
FastAppliedPrice Close 快线使用的价格类型。
CandleType TimeFrame(1m) 进行计算的蜡烛序列。

实现说明

  • 采用 StockSharp 高层 API(SubscribeCandles().Bind(...))完成订阅与指标绑定,无需手动遍历历史缓存。
  • 仅使用已完成的蜡烛,行为与 MQL 中 CopyBuffer(..., 1, 2, ...) 的读法一致。
  • 订单量通过 Security.MinVolumeSecurity.VolumeStepSecurity.MaxVolume 进行归一化,避免提交无效委托。
  • 根据要求暂不提供 Python 版本,目录内只有 C# 实现和多语言文档。

该移植版本忠实重现原始 EA 的交易逻辑,同时提供更符合 StockSharp 生态(Designer、Runner 等)的参数界面与风控选项。

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA RSI VA Crossover: Fast/slow EMA crossover with RSI volatility filter.
/// </summary>
public class EmaRsiVaCrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public EmaRsiVaCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 40)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");
	}

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

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = 14 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, rsi, atr, 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 fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow))
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow))
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 40 && rsiVal < 70)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal > 30 && rsiVal < 60)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}