在 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class tp_sl_trailing_strategy(Strategy):
"""EMA price crossover for entries."""
def __init__(self):
super(tp_sl_trailing_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tp_sl_trailing_strategy, self).OnReseted()
self._prev_close = 0
self._prev_ema = 0
def OnStarted2(self, time):
super(tp_sl_trailing_strategy, self).OnStarted2(time)
self._prev_close = 0
self._prev_ema = 0
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._prev_close == 0 or self._prev_ema == 0:
self._prev_close = close
self._prev_ema = float(ema_val)
return
ema_f = float(ema_val)
if self._prev_close < self._prev_ema and close >= ema_f and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close > self._prev_ema and close <= ema_f and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_ema = ema_f
def CreateClone(self):
return tp_sl_trailing_strategy()