在 GitHub 上查看

Dual Stoploss 策略

该策略复刻了 MetaTrader 专家顾问 Dual StopLoss.mq4 的风险管理逻辑。它不会开仓,而是监控当前持仓所附带的止损委托,在价格距离止损仅剩少量点数时提前平仓,以此减少滑点并保护收益。

工作流程

  1. 订阅 Level1 数据,获取最新的买一/卖一报价以及经纪商提供的 StopLevel(或同义字段)。
  2. 每当价格、委托或自身成交发生变化时,都会在策略的 Orders 集合中寻找当前合约最近的活动止损或止损限价单。
  3. 计算市场价格到该止损价格的距离,并与阈值比较:
    • 阈值 = WhenToClosePoints × pointValue + stopLevelDistance
    • pointValue 等同于 MetaTrader 的 Point,会根据合约最小价位自动计算(多数外汇品种为 0.0001)。
    • stopLevelDistance 来自 Level1 字段 StopLevelMinStopPriceStopPriceStopDistance,若不可用则视为 0。
  4. 当剩余距离小于或等于阈值时,策略立即通过市价单平掉相应的多头或空头仓位。

参数

参数 说明
WhenToClosePoints 距离止损价还剩多少 MetaTrader 点数时提前平仓。默认值 10。设为 0 时仅依赖经纪商的最小止损距离。

注意事项

  • 策略本身不负责开仓,只对已有仓位及其止损单进行管理。
  • 若连接的交易所/经纪商会在 Level1 数据中提供 StopLevel 信息,策略会自动把它纳入阈值计算;否则仅使用参数中的点数距离。
  • StartProtection() 会启用 StockSharp 的保护机制,确保策略启动后紧急平仓功能始终可用。
  • 为了让策略识别到止损,需要在同一个策略实例下注册保护性止损单。
  • 同一方向存在多张止损时,会选择离市场价格最近的一张作为参考。

使用建议

  1. 将策略附加到需要保护的投资组合和证券上,确保止损单由该策略或其父策略注册。
  2. 根据可接受的缓冲距离设置 WhenToClosePoints。该值与 MetaTrader 中的“点”含义一致,而不是价格单位。
  3. 启动策略并留意日志。当价格逼近止损时,系统会自动发送市价单,提前退出仓位。
  4. 可与其他开仓或资金管理策略组合使用,构建完整的交易流程。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Dual Stoploss strategy: dual SMA crossover with confirmation.
/// Buys when fast SMA crosses above mid SMA and mid is above slow, sells on opposite.
/// </summary>
public class DualStoplossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _midPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevMid;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int MidPeriod { get => _midPeriod.Value; set => _midPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public DualStoplossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");
		_midPeriod = Param(nameof(MidPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Mid SMA", "Mid SMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevMid = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var fast = new SimpleMovingAverage { Length = FastPeriod };
		var mid = new SimpleMovingAverage { Length = MidPeriod };
		var slow = new SimpleMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, mid, slow, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevFast <= _prevMid && fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (_prevFast >= _prevMid && fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}
		else
		{
			if (fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}

		_prevFast = fastValue;
		_prevMid = midValue;
		_hasPrev = true;
	}
}