在 GitHub 上查看

Sample Trailing Stop 策略

概述

SampleTrailingStopStrategy 是 MetaTrader 智能交易系统 SampleTrailingstop.mq4 的 C# 版本。策略本身不会产生进场信号,而是持续监控当前持仓,并根据经纪商限制维护止损与止盈委托。代码完全复刻原始 EA 的思路:在考虑止损级别和冻结级别的前提下,以价格点(points)为单位执行移动止损。

当多头持仓出现浮盈且买价远离开仓价时,策略先把止损移动到买价下方的最小允许距离;之后会把止损保持在买价下方指定的点数,同时补上经纪商所需的缓冲。空头逻辑完全对称,将止损放在卖价上方,并在每次更新时重算可选的止盈价位。

数据流程

  • 订阅 Level1 行情,以获取最新买卖价。
  • 通过基础 Strategy API 访问当前持仓均价。
  • 每当计算出新的保护价位时重新注册止损或止盈委托。

参数

参数 默认值 说明
TrailingStopPoints 200 移动止损与市场之间的距离,单位为价格点。最终的跟踪距离会叠加经纪商缓冲。
TakeProfitPoints 1000 可选的止盈距离(点)。设置为 0 表示不使用止盈。
StopLevelPoints 0 经纪商要求的止损级别(点)。该值与跟踪距离相加,确保委托有效。
FreezeLevelPoints 0 经纪商要求的冻结级别(点)。只有当价格突破该缓冲区后才会移动止损。

所有点值都会依据品种的最小价格变动转换成实际价格,等价于 MetaTrader 中的 _Point

移动止损步骤

  1. 检查持仓:只有在存在持仓并且已经收到买卖价行情时才会运行移动止损。
  2. 利润判定:多头需满足 bid > entry,空头需满足 ask < entry,并且价格已经离开冻结区。
  3. 首次放置止损:若尚未激活移动止损,价格距离开仓价达到设定的点数时,会把止损放在买价(或卖价)附近的最小允许距离。
  4. 持续跟踪:持仓保持盈利时,止损会按照设定的点数加上缓冲区持续向价格推进,同时在启用时同步更新止盈价。
  5. 委托维护:策略使用高级 API 自动创建、修改或取消保护性委托,保证经纪商看到的始终是最新价格。

使用提示

  • 请与其他负责开仓的策略或手动交易结合使用,本策略仅负责离场管理。
  • 确认交易品种已设置正确的价格步长与数量步长,策略会对生成的价格与数量做归一化处理。
  • 当持仓方向发生反转时,旧的保护性委托会先被取消,再为新方向重新计算移动止损。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Sample Trailing Stop strategy: Smoothed MA crossover.
/// Buys when close crosses above Smoothed MA, sells when crosses below.
/// </summary>
public class SampleTrailingStopStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;

	private decimal _prevClose;
	private decimal _prevSma;
	private bool _hasPrev;

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

	public SampleTrailingStopStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_period = Param(nameof(Period), 50)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Smoothed MA period", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevSma = 0;
		_hasPrev = false;
		var smma = new ExponentialMovingAverage { Length = Period };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(smma, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevClose <= _prevSma && candle.ClosePrice > smmaValue && Position <= 0)
				BuyMarket();
			else if (_prevClose >= _prevSma && candle.ClosePrice < smmaValue && Position >= 0)
				SellMarket();
		}

		_prevClose = candle.ClosePrice;
		_prevSma = smmaValue;
		_hasPrev = true;
	}
}