在 GitHub 上查看

Two MA Other TimeFrame Correct Intersection 策略

概览

该策略将 MetaTrader 5 上的 "Two MA Other TimeFrame Correct Intersection" 专家顾问移植到 StockSharp 平台。原始 EA 使用两个分别位于不同时间框架的移动平均线(例如 H1 与 D1),而交易决策在图表时间框架上执行。本移植版本完整保留多时间框架结构:当快速均线向上穿越慢速均线时开多单;当快速均线向下穿越慢速均线时开空单。所有订单均以市价成交,并在建立新仓位前关闭相反方向的仓位,以符合 MQL5 交易引擎的执行方式。

交易逻辑

  • 同时订阅三个蜡烛流:主要交易时间框架、快速均线时间框架、慢速均线时间框架。
  • 在各自的时间框架上计算快速与慢速移动平均线,支持与原始 iCustom 指标一致的平滑方法与价格来源。
  • 可选地对两个移动平均输出应用水平偏移,以还原输入参数 ma_shift 的行为。
  • 每当主要交易时间框架的蜡烛收盘时,比较当前与上一时刻的均线数值:
    • 若上一时刻快速均线低于慢速均线,而当前值高于慢速均线,则平掉空头并开多头。
    • 若上一时刻快速均线高于慢速均线,而当前值低于慢速均线,则平掉多头并开空头。
  • 所有开仓都使用配置的交易量;在反向操作时,会把现有反向仓位的绝对值加入订单量,使仓位能通过一次市价单完成反转。

参数

参数 说明
TradeVolume 市价单的基础下单量,适用于多头与空头。
CandleType 主要交易时间框架,每当该蜡烛收盘就评估一次信号。
FastTimeFrame 计算快速均线所用的时间框架。
SlowTimeFrame 计算慢速均线所用的时间框架。
FastLength 快速均线的周期长度。
SlowLength 慢速均线的周期长度。
FastShift 快速均线在比较前的水平偏移。
SlowShift 慢速均线在比较前的水平偏移。
FastMethod 快速均线的平滑方式(简单、指数、平滑或线性加权)。
SlowMethod 慢速均线的平滑方式。
FastAppliedPrice 快速均线使用的价格类型(开盘、最高、最低、收盘、中位、典型或加权)。
SlowAppliedPrice 慢速均线使用的价格类型。

实现说明

  • 移动平均线通过 StockSharp 高层 API SubscribeCandles().Bind(...) 计算,即使计算时间框架与交易时间框架不同也能持续更新。
  • 偏移参数通过小型队列实现,将指标输出延迟指定的柱数,从而复刻 ma_shift 的效果。
  • OnStarted 中调用 StartProtection(),与原始交易引擎一样开启基础的仓位保护。
  • 回测时在图表上绘制主要蜡烛以及两条移动平均线,便于观察交叉信号。
  • 原始 EA 未实现止损、止盈或追踪止损模块,若需要额外的风险控制,可在 StockSharp 中另行组合管理策略。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving average crossover strategy.
/// Converted from the "Two MA Other TimeFrame Correct Intersection" MQL5 expert advisor.
/// Uses fast and slow SMA on a single timeframe with crossover signals.
/// </summary>
public class TwoMAOtherTimeFrameCorrectIntersectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;

	private ExponentialMovingAverage _fastMa;
	private ExponentialMovingAverage _slowMa;
	private decimal? _prevFast;
	private decimal? _prevSlow;

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public TwoMAOtherTimeFrameCorrectIntersectionStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe used for signal evaluation", "General");

		_fastLength = Param(nameof(FastLength), 20)
		.SetDisplay("Fast MA Length", "Number of bars for the fast moving average", "Indicators")
		.SetGreaterThanZero();

		_slowLength = Param(nameof(SlowLength), 50)
		.SetDisplay("Slow MA Length", "Number of bars for the slow moving average", "Indicators")
		.SetGreaterThanZero();
	}

	/// <summary>
	/// Primary candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Number of bars used by the fast moving average.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Number of bars used by the slow moving average.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

		_prevFast = null;
		_prevSlow = null;

		_fastMa = new ExponentialMovingAverage { Length = FastLength };
		_slowMa = new ExponentialMovingAverage { Length = SlowLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_fastMa, _slowMa, ProcessCandle)
		.Start();

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

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

		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

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

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Fast crosses above slow => buy
		if (_prevFast < _prevSlow && fastValue > slowValue)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		// Fast crosses below slow => sell
		else if (_prevFast > _prevSlow && fastValue < slowValue)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_prevFast = null;
		_prevSlow = null;
		_fastMa = null;
		_slowMa = null;

		base.OnReseted();
	}
}