在 GitHub 上查看

Cross 策略(MQL 27596 移植)

概述

Cross 策略移植自 MetaTrader 专家顾问 Cross.mq4(仓库目录 MQL/27596)。原始 EA 依据开盘价与指数移动平均线(EMA)的相对位置来反向开仓,并为每笔交易同时设置固定止盈和止损。StockSharp 版本在保持原有交易思想的同时,使用高阶 API(K 线订阅、指标绑定、持仓管理)实现完全自动化。

交易逻辑

  1. 指标:单一 EMA,以收盘价计算,周期默认为 200,可按需调整。
  2. 信号判定
    • 当某根已完成 K 线的开盘价首次突破 EMA 上方时产生做多信号,对应 MQL 代码中的 Cross(0, Open[0] > EMA)
    • 当开盘价首次跌破 EMA 下方时产生做空信号,对应 Cross(1, Open[0] < EMA)
  3. 仓位切换:信号出现后策略会平掉当前反向持仓并建立新仓:
    • 多头信号出现时,若账户为空仓或持有空单,则一次性买入 Volume + |Position| 手数,以实现“平空并做多”。
    • 空头信号出现时执行对称操作,卖出 Volume + |Position| 手数来“平多并做空”。
  4. 风控执行:持仓期间持续监控每根完成 K 线的最高价和最低价,当价格触及设定的止盈或止损步长时即时平仓,复刻原始 EA 中 OrderSend 所携带的止盈/止损参数。

参数说明

参数 默认值 说明
EMA Length 200 EMA 的周期,用于判断趋势与信号触发。必须大于 0。
Take Profit (steps) 200 止盈距离,按价格步长(PriceStep)计量。设为 0 可关闭止盈。
Stop Loss (steps) 100 止损距离,同样以价格步长表示。设为 0 可关闭止损。
Candle Type 1 分钟 用于计算的 K 线类型,可切换为任意 StockSharp 支持的时间框架或自定义 K 线。

成交量由策略属性 Volume 控制。当发生反手信号时,发单数量始终为 Volume + |Position|,保证既能平掉旧仓也能建立新仓。

执行流程

  1. OnStarted 中订阅目标 K 线并通过 Bind 将 EMA 指标绑定到订阅。
  2. 只有在 K 线完成且 EMA 已形成的情况下才会继续处理:
    • 调用内部 ManageOpenPosition 方法,根据最高/最低价检查止盈和止损。
    • 基于开盘价与 EMA 的关系判断是否出现新的多空跨越。
    • 需要反手时发送市价单。
  3. OnNewMyTrade 负责维护持仓方向、加仓后的加权平均入场价以及最近持仓量,确保止盈止损计算精确。
  4. 如有可用图表,会自动绘制 K 线、EMA 以及成交标记,方便回测或实时监控。

风险控制细节

  • 止损:多头仓位的止损价为 entryPrice - StopLoss × PriceStep,空头则为 entryPrice + StopLoss × PriceStep。当 K 线最低价(多头)或最高价(空头)触及该水平时立即平仓。
  • 止盈:采用与止损相同的价格步长逻辑,当 K 线高/低价突破目标时立即实现盈利。
  • 账户保护:策略启动时调用 StartProtection(),从而遵循 StockSharp 中统一的风控保护设置。

调优建议

  • 缩短 EMA 周期或时间框架会显著增加交易频次,需要与更大的止损配合以降低噪声。
  • 想同时交易多品种时,可为每个标的实例化独立策略对象,并为其配置对应的 SecurityCandleType
  • 参数优化时请结合标的波动率与最小跳动单位,避免出现不合理的极端设定。

移植要点

  • MQL 中的 crossed[2] 布尔数组在 C# 中被实现为两个持续跟踪状态的布尔字段。
  • OrderSend 调用转换为 BuyMarketSellMarket,既能平仓也能反向建仓,忠实还原原始策略的行为。
  • EMA 数值通过 Bind 回调直接传入,完全符合仓库要求,不涉及 GetValue 或其他低阶访问方式。

借助上述细节,您可以在 StockSharp 环境中获得与原版 MetaTrader EA 等效的策略表现,同时享受更丰富的数据接入与扩展能力。

namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Cross strategy: opens long when candle open crosses above EMA,
/// short when candle open crosses below EMA.
/// </summary>
public class CrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private bool _prevAbove;
	private bool _hasPrev;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaLength = Param(nameof(EmaLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period for cross detection", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 3)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevAbove = false;
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var above = candle.OpenPrice > ema && candle.ClosePrice > ema;

		if (_hasPrev && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (above && !_prevAbove && Position <= 0)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (!above && _prevAbove && Position >= 0)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevAbove = above;
		_hasPrev = true;
	}
}