在 GitHub 上查看

Mean Reverse 策略

概述

Mean Reverse 策略复刻了 "MeanReversionTrendEA" 智能交易系统,通过简单均线交叉的趋势模块与基于 ATR 波动区间的均值回归模块相结合来产生交易信号。当价格确认趋势反转或相对慢速均线出现过度偏离时,系统会开仓。

交易逻辑

  • 趋势模块:当快速简单移动平均线 (SMA) 上穿慢速 SMA 时产生买入信号;当快速 SMA 下穿慢速 SMA 时产生卖出信号。
  • 均值回归模块:当收盘价低于慢速 SMA 且偏离幅度超过 ATR × Multiplier 时触发买入;当收盘价高于慢速 SMA 且偏离幅度超过该阈值时触发卖出。
  • 信号合成:只要任一模块给出信号且当前没有持仓,就会按照设定的交易量开多或开空。

仓位管理

  • 止损:进场后立即根据 入场价 − StopLossPoints × Step(多头)或 入场价 + StopLossPoints × Step(空头)计算止损价位,一旦蜡烛的最高价或最低价触及该水平即平仓。
  • 止盈:同时设置 入场价 + TakeProfitPoints × Step(多头)或 入场价 − TakeProfitPoints × Step(空头)的目标价位,价格触及即止盈离场。
  • 单仓约束:策略同一时间只保持一笔持仓,持仓未平之前忽略新的入场信号。
  • 安全模块StartProtection() 调用复现了原始 EA 的安全检查逻辑,用于防止异常的持仓状态。

指标

  • FastMaPeriod 周期的简单移动平均线。
  • SlowMaPeriod 周期的简单移动平均线。
  • AtrPeriod 周期的平均真实波幅 (ATR)。

所有指标都使用 CandleType 指定的同一份 K 线订阅数据。

参数

名称 说明 默认值
FastMaPeriod 快速 SMA 的回溯长度,同时用于均值回归带。 20
SlowMaPeriod 慢速 SMA 的回溯长度,代表均值。 50
AtrPeriod ATR 波动度计算所需的蜡烛数量。 14
AtrMultiplier ATR 偏离宽度的乘数。 2.0
StopLossPoints 以品种最小步长 Security.Step 表示的止损距离。 500
TakeProfitPoints Security.Step 表示的止盈距离。 1000
TradeVolume 每次下单使用的交易量。 1
CandleType 为指标提供数据的 K 线类型。 1 小时周期

备注

  • 默认的 1 小时 K 线对应 MetaTrader 中的“当前周期”概念,可根据需要调整。
  • ATR 偏离带以收盘价为基准,与原策略取 Bid/Ask 均值的做法一致。
  • 参数均已启用优化标记,方便在不同市场上进行回测和调参。
namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

/// <summary>
/// Mean reversion and trend following hybrid that reacts to moving average crossovers and ATR deviations.
/// </summary>
public class MeanReverseStrategy : Strategy
{
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _fastMa = null!;
	private SimpleMovingAverage _slowMa = null!;
	private AverageTrueRange _atr = null!;

	private decimal? _prevFastMa;
	private decimal? _prevSlowMa;
	private decimal _stopLossPrice;
	private decimal _takeProfitPrice;

	/// <summary>
	/// Fast simple moving average period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow simple moving average period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// ATR lookback period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for mean reversion envelopes.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in security steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in security steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Volume used for a new position.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public MeanReverseStrategy()
	{
		_fastMaPeriod = Param(nameof(FastMaPeriod), 20)
			.SetRange(5, 200)
			.SetDisplay("Fast MA Period", "Length of the fast simple moving average", "Indicators")
			;

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 50)
			.SetRange(5, 300)
			.SetDisplay("Slow MA Period", "Length of the slow simple moving average", "Indicators")
			;

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetRange(5, 100)
			.SetDisplay("ATR Period", "Number of candles used for ATR calculation", "Indicators")
			;

		_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
			.SetRange(0.5m, 5m)
			.SetDisplay("ATR Multiplier", "Multiplier applied to ATR for deviation bands", "Indicators")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 500)
			.SetRange(10, 5000)
			.SetDisplay("Stop Loss Points", "Stop-loss distance in security steps", "Risk Management")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 1000)
			.SetRange(10, 10000)
			.SetDisplay("Take Profit Points", "Take-profit distance in security steps", "Risk Management")
			;

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetRange(0.01m, 100m)
			.SetDisplay("Trade Volume", "Volume opened with a new signal", "Execution")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of market data processed by the strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_prevFastMa = null;
		_prevSlowMa = null;
		_stopLossPrice = default;
		_takeProfitPrice = default;
	}

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

		_fastMa = new SimpleMovingAverage { Length = FastMaPeriod };
		_slowMa = new SimpleMovingAverage { Length = SlowMaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastMa, _slowMa, _atr, 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 fastMa, decimal slowMa, decimal atr)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// indicators checked via Bind

		var step = Security?.PriceStep ?? 1m;

		if (_prevFastMa is null || _prevSlowMa is null)
		{
			_prevFastMa = fastMa;
			_prevSlowMa = slowMa;
			return;
		}

		var prevFast = _prevFastMa.Value;
		var prevSlow = _prevSlowMa.Value;

		var trendBuySignal = prevFast <= prevSlow && fastMa > slowMa;
		var trendSellSignal = prevFast >= prevSlow && fastMa < slowMa;

		var deviation = atr * AtrMultiplier;
		var reversionBuySignal = candle.ClosePrice < slowMa - deviation;
		var reversionSellSignal = candle.ClosePrice > slowMa + deviation;

		var buySignal = trendBuySignal || reversionBuySignal;
		var sellSignal = trendSellSignal || reversionSellSignal;

		if (Position == 0)
		{
			if (buySignal)
			{
				EnterLong(candle.ClosePrice, step);
			}
			else if (sellSignal)
			{
				EnterShort(candle.ClosePrice, step);
			}
		}
		else if (Position > 0)
		{
			ManageLongPosition(candle);
		}
		else
		{
			ManageShortPosition(candle);
		}

		_prevFastMa = fastMa;
		_prevSlowMa = slowMa;
	}

	private void EnterLong(decimal entryPrice, decimal step)
	{
		// Open a new long position and pre-calculate risk levels.
		BuyMarket(TradeVolume);

		_stopLossPrice = entryPrice - StopLossPoints * step;
		_takeProfitPrice = entryPrice + TakeProfitPoints * step;
	}

	private void EnterShort(decimal entryPrice, decimal step)
	{
		// Open a new short position and pre-calculate risk levels.
		SellMarket(TradeVolume);

		_stopLossPrice = entryPrice + StopLossPoints * step;
		_takeProfitPrice = entryPrice - TakeProfitPoints * step;
	}

	private void ManageLongPosition(ICandleMessage candle)
	{
		// Exit logic for the long position.
		if (candle.LowPrice <= _stopLossPrice)
		{
			SellMarket(Position);
			ClearRiskLevels();
			return;
		}

		if (_takeProfitPrice != default && candle.HighPrice >= _takeProfitPrice)
		{
			SellMarket(Position);
			ClearRiskLevels();
		}
	}

	private void ManageShortPosition(ICandleMessage candle)
	{
		// Exit logic for the short position.
		if (candle.HighPrice >= _stopLossPrice)
		{
			BuyMarket(Math.Abs(Position));
			ClearRiskLevels();
			return;
		}

		if (_takeProfitPrice != default && candle.LowPrice <= _takeProfitPrice)
		{
			BuyMarket(Math.Abs(Position));
			ClearRiskLevels();
		}
	}

	private void ClearRiskLevels()
	{
		// Reset stored risk levels after an exit.
		_stopLossPrice = default;
		_takeProfitPrice = default;
	}
}