Dual Stoploss 策略
该策略复刻了 MetaTrader 专家顾问 Dual StopLoss.mq4 的风险管理逻辑。它不会开仓,而是监控当前持仓所附带的止损委托,在价格距离止损仅剩少量点数时提前平仓,以此减少滑点并保护收益。
工作流程
- 订阅 Level1 数据,获取最新的买一/卖一报价以及经纪商提供的
StopLevel(或同义字段)。 - 每当价格、委托或自身成交发生变化时,都会在策略的
Orders集合中寻找当前合约最近的活动止损或止损限价单。 - 计算市场价格到该止损价格的距离,并与阈值比较:
- 阈值 =
WhenToClosePoints × pointValue + stopLevelDistance。 pointValue等同于 MetaTrader 的Point,会根据合约最小价位自动计算(多数外汇品种为 0.0001)。stopLevelDistance来自 Level1 字段StopLevel、MinStopPrice、StopPrice或StopDistance,若不可用则视为 0。
- 阈值 =
- 当剩余距离小于或等于阈值时,策略立即通过市价单平掉相应的多头或空头仓位。
参数
| 参数 | 说明 |
|---|---|
| WhenToClosePoints | 距离止损价还剩多少 MetaTrader 点数时提前平仓。默认值 10。设为 0 时仅依赖经纪商的最小止损距离。 |
注意事项
- 策略本身不负责开仓,只对已有仓位及其止损单进行管理。
- 若连接的交易所/经纪商会在 Level1 数据中提供 StopLevel 信息,策略会自动把它纳入阈值计算;否则仅使用参数中的点数距离。
StartProtection()会启用 StockSharp 的保护机制,确保策略启动后紧急平仓功能始终可用。- 为了让策略识别到止损,需要在同一个策略实例下注册保护性止损单。
- 同一方向存在多张止损时,会选择离市场价格最近的一张作为参考。
使用建议
- 将策略附加到需要保护的投资组合和证券上,确保止损单由该策略或其父策略注册。
- 根据可接受的缓冲距离设置
WhenToClosePoints。该值与 MetaTrader 中的“点”含义一致,而不是价格单位。 - 启动策略并留意日志。当价格逼近止损时,系统会自动发送市价单,提前退出仓位。
- 可与其他开仓或资金管理策略组合使用,构建完整的交易流程。
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;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dual_stoploss_strategy(Strategy):
"""
Dual Stoploss strategy: triple SMA crossover with confirmation.
Buys when fast SMA crosses above mid SMA and mid is above slow.
Sells on opposite crossover.
"""
def __init__(self):
super(dual_stoploss_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 3) \
.SetDisplay("Fast SMA", "Fast SMA period", "Indicators")
self._mid_period = self.Param("MidPeriod", 10) \
.SetDisplay("Mid SMA", "Mid SMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow SMA", "Slow SMA period", "Indicators")
self._prev_fast = 0.0
self._prev_mid = 0.0
self._has_prev = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dual_stoploss_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_mid = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(dual_stoploss_strategy, self).OnStarted2(time)
self._has_prev = False
fast = SimpleMovingAverage()
fast.Length = self._fast_period.Value
mid = SimpleMovingAverage()
mid.Length = self._mid_period.Value
slow = SimpleMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, mid, slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, mid)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, mid_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
mid_val = float(mid_val)
slow_val = float(slow_val)
if self._has_prev:
if self._prev_fast <= self._prev_mid and fast_val > mid_val and mid_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_mid and fast_val < mid_val and mid_val < slow_val and self.Position >= 0:
self.SellMarket()
else:
if fast_val > mid_val and mid_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_val < mid_val and mid_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_mid = mid_val
self._has_prev = True
def CreateClone(self):
return dual_stoploss_strategy()