在 GitHub 上查看

TP SL Trailing 策略

概览

本策略是 MetaTrader 5 专家顾问 "TP SL Trailing" 的直接移植版本。策略本身不会产生入场信号,而是负责管理已经存在的头寸:按照设定的点数距离自动挂出止损和止盈,并在交易进入盈利状态后对止损进行跟踪。所有参数均以点(pip)为单位设置,可在 StockSharp 支持的任意品种上使用。

交易逻辑

  • 当检测到新的头寸时,可以根据 Only Zero Values 标志决定是否立刻按设定的点数放置初始止损和止盈,与原始 EA 的行为完全一致。
  • 多头仓位在未实现利润大于“跟踪止损 + 跟踪步长”之和时,会把止损上移到 当前价格 - 跟踪止损,从而锁定部分利润。
  • 空头仓位采用镜像逻辑:当利润超过触发阈值时,将止损下移到 当前价格 + 跟踪止损
  • 如果跟踪止损和跟踪步长都为零,则不会调整止损位置。
  • 止盈不会被移动,只会在 Only Zero Values 启用时随初始订单一起下达,这与原脚本的实现一致。

参数说明

参数 描述
CandleType 用于跟踪价格变动的 K 线周期。周期越短,止损调整越及时。
StopLossPips 初始止损距离入场价的点数,仅在 Only Zero Values 启用时生效。
TakeProfitPips 初始止盈距离入场价的点数,仅在 Only Zero Values 启用时生效。
TrailingStopPips 跟踪止损的核心距离(点),决定止损离当前价格的最小间隔。
TrailingStepPips 决定每次移动止损前必须额外走出的点数,避免频繁改价。
OnlyZeroValues 与原 EA 中的标志相同,仅在当前仓位没有止损或止盈时才自动挂出保护单。

转换说明

  • 点数通过合约的 PriceStep 转换为实际价格增量,相当于原始脚本对 3/5 位报价的特殊处理方式。
  • 每当跟踪逻辑触发时会重新挂出止损订单,仓位归零时旧的保护单会被自动撤销。
  • 代码内所有注释均为英文,本文档提供了详细说明,方便理解移植时的每一个选择。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// TP SL Trailing strategy. Uses EMA with price crossover for entries.
/// </summary>
public class TpSlTrailingStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private decimal? _prevClose;
	private decimal? _prevEma;

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

	public TpSlTrailingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetGreaterThanZero().SetDisplay("EMA Period", "EMA lookback", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = null;
		_prevEma = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = null; _prevEma = null;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevClose = close; _prevEma = emaVal; return; }
		if (_prevClose == null || _prevEma == null) { _prevClose = close; _prevEma = emaVal; return; }
		if (_prevClose.Value < _prevEma.Value && close >= emaVal && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (_prevClose.Value > _prevEma.Value && close <= emaVal && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevClose = close; _prevEma = emaVal;
	}
}