在 GitHub 上查看

Divergence Trader 策略

概述

该策略是 MetaTrader "Divergence Trader" 专家的 StockSharp 版本。算法比较两个可配置价格源的简单移动平均线,监测快 慢均线之间的差值(离差)。当离差落入设定的中性通道时,策略假设动量即将恢复,并按照当前趋势方向开仓。实现只 处理所选周期的已完成蜡烛,并使用高层 API 与指标绑定。

参数

  • Lot Size – 每次开仓的交易量,会自动对齐到品种的最小成交量步长。
  • Fast SMA Period / Price – 快速简单移动平均线的周期和价格类型。
  • Slow SMA Period / Price – 慢速简单移动平均线的周期和价格类型。
  • Buy Threshold – 开多单所需的最小正离差。
  • Stay-Out Threshold – 允许入场的最大离差,超出范围时暂停交易。
  • Take Profit (pips) – 以点数表示的止盈目标,设置为零时关闭功能。
  • Stop Loss (pips) – 以点数表示的止损距离,设置为零时关闭功能。
  • Trailing Stop (pips) – 进入盈利区间后启动的跟踪止损距离。
  • Break-Even Trigger / Buffer (pips) – 将仓位移动到保本价所需的利润点数以及可选的缓冲偏移。
  • Basket Profit / Basket Loss – 基于账户净值的全局止盈/止损阈值,触发时关闭所有仓位;损失阈值默认关闭。
  • Start Hour / Stop Hour – 当地时间的交易时间窗口;两个值相同时表示全天交易。
  • Candle Type – 用于计算信号和管理仓位的蜡烛类型。

交易逻辑

  1. 订阅所选蜡烛序列,并计算快、慢两条简单移动平均线。
  2. 仅使用完全收盘的蜡烛,避免盘中噪声并保持与原始 EA 一致。
  3. 追踪上一根收盘蜡烛的离差(快均线减去慢均线):
    • 当离差为正且介于 Buy ThresholdStay-Out Threshold 之间时,发送市价买单。
    • 当离差为负且绝对值位于该区间时,发送市价卖单。
  4. 若当前已有仓位或不在允许的时间窗口内,则忽略信号。

仓位管理

  • 保本控制 – 当浮动盈利达到触发阈值时,记录保本价(可选缓冲);若后续蜡烛触及该价位,立即平仓。
  • 跟踪止损 – 利润超过设定距离后,止损价格跟随最有利的价格移动,并始终保持固定点数的安全距离。
  • 止盈 / 止损 – 根据入场价按点数计算的固定退出水平。
  • 账户保护 – 将投资组合净值与设定的盈亏阈值比较,达到任一阈值时关闭当前仓位并取消挂单,复刻 MQL 版本中 CloseEverything 的行为。

使用说明

  • 离差通道是对称的:增大 Stay-Out Threshold 可以让持仓延续更久,缩小该值会提高信号频率。
  • 价格类型与 StockSharp 的 CandlePrice 枚举相对应,可选择开盘价、收盘价、中位价、典型价等,与 MetaTrader 保持一致。
  • 策略会在图表区域绘制蜡烛、两条均线以及实际成交,方便监控与调试。
  • 资金管理功能依赖投资组合数据;在缺少账户统计的环境中,篮子阈值会被自动忽略。
using System;
using System.Linq;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Divergence-based strategy converted from the Divergence Trader MQL expert advisor.
/// Trades based on the divergence between fast and slow moving averages.
/// </summary>
public class DivergenceTraderBasketStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<decimal> _buyThreshold;
	private readonly StrategyParam<decimal> _stayOutThreshold;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _previousDifference;
	private decimal _entryPrice;

	public DivergenceTraderBasketStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 7)
			.SetDisplay("Fast SMA Period", "Length of the fast simple moving average.", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 88)
			.SetDisplay("Slow SMA Period", "Length of the slow simple moving average.", "Indicators");

		_buyThreshold = Param(nameof(BuyThreshold), 0.0001m)
			.SetDisplay("Buy Threshold", "Minimum divergence value required before buying.", "Signals");

		_stayOutThreshold = Param(nameof(StayOutThreshold), 1000m)
			.SetDisplay("Stay-Out Threshold", "Upper divergence limit that disables new entries.", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe used for calculations.", "General");
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public decimal BuyThreshold
	{
		get => _buyThreshold.Value;
		set => _buyThreshold.Value = value;
	}

	public decimal StayOutThreshold
	{
		get => _stayOutThreshold.Value;
		set => _stayOutThreshold.Value = value;
	}

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

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

		_previousDifference = null;
		_entryPrice = 0;
	}

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

		_previousDifference = null;
		_entryPrice = 0;

		var fastMa = new SimpleMovingAverage { Length = FastPeriod };
		var slowMa = new SimpleMovingAverage { Length = SlowPeriod };

		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;

		var currentDiff = fastValue - slowValue;

		if (_previousDifference == null)
		{
			_previousDifference = currentDiff;
			return;
		}

		var prevDiff = _previousDifference.Value;
		_previousDifference = currentDiff;

		// Manage open position
		if (Position != 0)
		{
			// Exit on divergence sign change
			if (Position > 0 && currentDiff < 0)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (Position < 0 && currentDiff > 0)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			return;
		}

		// Entry logic: divergence crosses zero line
		if (currentDiff > 0 && prevDiff <= 0)
		{
			// Bullish divergence crossover
			BuyMarket();
			_entryPrice = candle.ClosePrice;
		}
		else if (currentDiff < 0 && prevDiff >= 0)
		{
			// Bearish divergence crossover
			SellMarket();
			_entryPrice = candle.ClosePrice;
		}
	}
}