在 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。
移动止损步骤
- 检查持仓:只有在存在持仓并且已经收到买卖价行情时才会运行移动止损。
- 利润判定:多头需满足
bid > entry,空头需满足 ask < entry,并且价格已经离开冻结区。
- 首次放置止损:若尚未激活移动止损,价格距离开仓价达到设定的点数时,会把止损放在买价(或卖价)附近的最小允许距离。
- 持续跟踪:持仓保持盈利时,止损会按照设定的点数加上缓冲区持续向价格推进,同时在启用时同步更新止盈价。
- 委托维护:策略使用高级 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;
}
}
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
class sample_trailing_stop_strategy(Strategy):
def __init__(self):
super(sample_trailing_stop_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._period = self.Param("Period", 50)
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.Value = value
def OnReseted(self):
super(sample_trailing_stop_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(sample_trailing_stop_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
smma = ExponentialMovingAverage()
smma.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(smma, self._process_candle).Start()
def _process_candle(self, candle, smma_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
smma_val = float(smma_value)
if self._has_prev:
if self._prev_close <= self._prev_sma and close > smma_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_close >= self._prev_sma and close < smma_val and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_sma = smma_val
self._has_prev = True
def CreateClone(self):
return sample_trailing_stop_strategy()