在 GitHub 上查看

MA Mirror 策略

概述

MA Mirror 策略是 MetaTrader 专家顾问 MA_MirrorEA 的移植版本。系统比较两个使用同一周期的简单移动平均线,但 分别基于蜡烛的收盘价和开盘价计算。当收盘价均线高于开盘价均线时,策略倾向于做多;当收盘价均线低于开盘价均线时, 策略倾向于做空。可配置的 MovingShift 参数允许从更早的蜡烛中读取均线数值,从而复现 MetaTrader 指标向右偏移 的视觉效果。

StockSharp 版本保持了原始 EA 的“镜像”行为:任意时刻只有一个净持仓,信号翻转时会先平掉原持仓,然后开立反向新 仓。此外,策略启动时默认持有一个虚拟的空头信号,因此只有当收盘均线首次上穿开盘均线后,才会发出第一笔真实交易。

交易逻辑

  1. 订阅由 CandleType 指定的蜡烛序列,并且只处理已经完成的蜡烛,避免在未收盘的数据上做决定。
  2. 使用蜡烛的收盘价和开盘价分别驱动两条简单移动平均线。两条均线共享同一个 MovingPeriod,方便直接比较。
  3. 将最新的均线数值存入环形缓冲区。缓冲区能够返回 MovingShift 根蜡烛之前的数值,从而在不调用受限指标方法的 情况下模拟 MetaTrader 的位移参数。
  4. 如果位移后的收盘均线高于开盘均线,则产生 买入 信号;如果低于则产生 卖出 信号;若两条均线相等,则保 持上一笔信号。
  5. 如果这是策略的首个信号并且不是看多信号,则保持空仓。否则,当目标信号与最近一次执行的信号不同时,先平掉当前净 持仓,再按照 TradeVolume 的数量开立新的市场订单。
  6. 更新内部的最新信号,使后续蜡烛在持仓方向未变化时不会重复下单。

参数

名称 类型 默认值 说明
CandleType DataType 1 分钟周期 策略处理的主要蜡烛时间框架。
MovingPeriod int 20 作用于收盘价和开盘价的简单移动平均线长度。
MovingShift int 0 向后偏移的已完成蜡烛数量,用于读取更早的均线数值。
TradeVolume decimal 1 每次下市场单使用的数量。

与原始 MetaTrader EA 的差异

  • MQL 附带的资金管理辅助功能(止损、止盈、追踪止损)没有迁移过来。StockSharp 版本始终按照固定的 TradeVolume 下单,需要额外的风控由用户自行实现。
  • MetaTrader 以订单为单位管理持仓,而 StockSharp 使用净头寸模式。移植版本会在开立反向仓位之前先平掉现有头寸, 使最终持仓与原 EA 的单票结构保持一致。
  • 指标计算通过 StockSharp 的蜡烛订阅 API 配合 SimpleMovingAverage 指标及内部缓冲区完成,而不是直接调用 iMA 函数。

使用建议

  • 在启动策略前调整 TradeVolume,使其符合交易品种的最小手数。构造函数也会同步设置 Strategy.Volume,因此帮 助方法会以期望的手数下单。
  • 如果希望读取更早蜡烛的均线数值,可增大 MovingShift,以匹配 MetaTrader 图表中向右偏移的效果。
  • 将策略添加到图表后可以同时查看蜡烛、两条均线以及成交记录,从而验证翻转是否准确发生在收盘均线与开盘均线交叉的 时刻。

使用的指标

  • 两条简单移动平均线(分别基于收盘价和开盘价),拥有相同的周期并支持向后偏移。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

public class MaMirrorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _movingPeriod;

	private SimpleMovingAverage _sma;
	private decimal? _prevDiff;

	public MaMirrorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle type", "Timeframe processed by the strategy.", "General");

		_movingPeriod = Param(nameof(MovingPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Moving period", "Length of the SMA.", "Indicator");
	}

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

	public int MovingPeriod
	{
		get => _movingPeriod.Value;
		set => _movingPeriod.Value = value;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevDiff = null;
		_sma = null;
	}

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

		_prevDiff = null;

		_sma = new SimpleMovingAverage { Length = MovingPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_sma, ProcessCandle)
			.Start();
	}

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

		if (!_sma.IsFormed)
			return;

		// Mirror: compare close vs SMA (of close). When close crosses above SMA = buy, below = sell.
		var diff = candle.ClosePrice - smaValue;

		if (_prevDiff is not null)
		{
			if (_prevDiff.Value <= 0 && diff > 0 && Position <= 0)
			{
				BuyMarket();
			}
			else if (_prevDiff.Value >= 0 && diff < 0 && Position >= 0)
			{
				SellMarket();
			}
		}

		_prevDiff = diff;
	}
}