Stopreversal Trailing 策略
Stopreversal Trailing 策略复刻了 MT5 专家顾问 Exp_Stopreversal.mq5。它调用 Stopreversal 自定义指标,在所选的 K 线价格周围构建动态追踪止损线。当价格向上突破该追踪线时,被视为看涨反转,可选地平掉空头仓位并开多;向下突破则执行相反的看跌操作。为了与原始 EA 保持一致,可以通过参数将信号延后若干个已收盘的 K 线才执行。
细节
- 入场逻辑:响应 Stopreversal 指标在价格穿越自适应追踪止损时产生的箭头信号。
- 多空方向:同时支持多头与空头,并提供独立开仓开关。
- 出场逻辑:反向 Stopreversal 信号可关闭当前仓位,同时可启用保护性的止损与止盈。
- 止损/止盈:固定价格步长的止损、止盈,加上由指标触发的反转平仓。
- 数据来源:任意时间框架;默认使用 4 小时 K 线,复现原始专家的多时间框架调用。
- 信号延迟:
SignalBar参数会将下单延迟指定数量的已完成 K 线(默认 1 根)。 - 风险控制:启动时调用仓位保护服务,并可使用按价格步长设置的硬止损。
- 指标参数:
Npips控制价格与追踪线之间的距离;PriceMode指定用于计算的价格类型。 - 默认值:
Volume= 1StopLossSteps= 1000TakeProfitSteps= 2000BuyPositionOpen= trueSellPositionOpen= trueBuyPositionClose= trueSellPositionClose= trueNpips= 0.004PriceMode= CloseSignalBar= 1
参数
| 参数 | 说明 |
|---|---|
CandleType |
用于 Stopreversal 计算和交易的 K 线类型,默认是 4 小时。 |
Volume |
新建仓位时发送的基础下单量。 |
StopLossSteps |
止损距离(价格步长数量),0 表示关闭。 |
TakeProfitSteps |
止盈距离(价格步长数量),0 表示关闭。 |
BuyPositionOpen |
看涨信号出现时是否允许开多。 |
SellPositionOpen |
看跌信号出现时是否允许开空。 |
BuyPositionClose |
看跌信号出现时是否平掉已有多头。 |
SellPositionClose |
看涨信号出现时是否平掉已有空头。 |
Npips |
调整追踪止损距离的比例系数。 |
PriceMode |
所使用的价格类型(收盘价、开盘价、最高价、最低价、中位价、典型价、加权价、简单均价、四价平均、趋势跟随或 Demark)。 |
SignalBar |
在执行信号前需等待的已收盘 K 线数量,对应 MT5 中的同名参数。 |
筛选信息
- 类别:顺势反转
- 方向:双向
- 指标:Stopreversal(基于 ATR 的追踪止损)
- 止损:固定止损与止盈,可选
- 时间框架:可配置(默认 H4)
- 季节性:无
- 神经网络:无
- 背离:无
- 复杂度:中等(自定义追踪逻辑)
- 风险级别:可通过止损距离和追踪参数调节
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stopreversal indicator based trailing stop strategy.
/// </summary>
public class StopreversalTrailingStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stopLossSteps;
private readonly StrategyParam<int> _takeProfitSteps;
private readonly StrategyParam<bool> _buyPositionOpen;
private readonly StrategyParam<bool> _sellPositionOpen;
private readonly StrategyParam<bool> _buyPositionClose;
private readonly StrategyParam<bool> _sellPositionClose;
private readonly StrategyParam<decimal> _npips;
private readonly StrategyParam<AppliedPriceModes> _priceMode;
private readonly StrategyParam<int> _signalBar;
private readonly List<SignalInfo> _signals = new();
private AverageTrueRange _atr = null!;
private decimal? _previousStopLevel;
private decimal? _previousPrice;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
/// <summary>
/// Initializes a new instance of <see cref="StopreversalTrailingStrategy"/>.
/// </summary>
public StopreversalTrailingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Stopreversal timeframe", "General");
_atrPeriod = Param(nameof(AtrPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR lookback for trailing stop", "Indicator");
_stopLossSteps = Param(nameof(StopLossSteps), 10)
.SetNotNegative()
.SetDisplay("Stop Loss Steps", "Stop loss distance in price steps", "Risk")
;
_takeProfitSteps = Param(nameof(TakeProfitSteps), 20)
.SetNotNegative()
.SetDisplay("Take Profit Steps", "Take profit distance in price steps", "Risk")
;
_buyPositionOpen = Param(nameof(BuyPositionOpen), true)
.SetDisplay("Open Long", "Allow opening long positions", "Trading");
_sellPositionOpen = Param(nameof(SellPositionOpen), true)
.SetDisplay("Open Short", "Allow opening short positions", "Trading");
_buyPositionClose = Param(nameof(BuyPositionClose), true)
.SetDisplay("Close Long", "Close long positions on sell signals", "Trading");
_sellPositionClose = Param(nameof(SellPositionClose), true)
.SetDisplay("Close Short", "Close short positions on buy signals", "Trading");
_npips = Param(nameof(Npips), 0.004m)
.SetGreaterThanZero()
.SetDisplay("Trailing Offset", "Fractional offset applied to the stop line", "Indicator")
;
_priceMode = Param(nameof(PriceMode), AppliedPriceModes.Close)
.SetDisplay("Applied Price", "Price source used by the trailing stop", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Bar delay before acting on a signal", "Indicator")
;
}
/// <summary>
/// Candle subscription type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// ATR period used for the trailing stop calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop loss distance measured in price steps.
/// </summary>
public int StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Take profit distance measured in price steps.
/// </summary>
public int TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Enable long entries.
/// </summary>
public bool BuyPositionOpen
{
get => _buyPositionOpen.Value;
set => _buyPositionOpen.Value = value;
}
/// <summary>
/// Enable short entries.
/// </summary>
public bool SellPositionOpen
{
get => _sellPositionOpen.Value;
set => _sellPositionOpen.Value = value;
}
/// <summary>
/// Close long positions on short signals.
/// </summary>
public bool BuyPositionClose
{
get => _buyPositionClose.Value;
set => _buyPositionClose.Value = value;
}
/// <summary>
/// Close short positions on long signals.
/// </summary>
public bool SellPositionClose
{
get => _sellPositionClose.Value;
set => _sellPositionClose.Value = value;
}
/// <summary>
/// Fractional offset used by the trailing stop calculation.
/// </summary>
public decimal Npips
{
get => _npips.Value;
set => _npips.Value = value;
}
/// <summary>
/// Price source used when computing the trailing level.
/// </summary>
public AppliedPriceModes PriceMode
{
get => _priceMode.Value;
set => _priceMode.Value = value;
}
/// <summary>
/// Number of bars to delay before reacting to a signal.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_signals.Clear();
_previousStopLevel = null;
_previousPrice = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
// no protection
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateStops(candle);
var price = GetAppliedPrice(candle);
var prevStop = _previousStopLevel ?? price * (1m - Npips);
var prevPrice = _previousPrice ?? price;
var hasPrev = _previousStopLevel.HasValue && _previousPrice.HasValue;
var stop = CalculateStop(price, prevPrice, prevStop);
var buySignal = hasPrev && price > stop && prevPrice < prevStop && prevStop != 0m;
var sellSignal = hasPrev && price < stop && prevPrice > prevStop && prevStop != 0m;
_previousPrice = price;
_previousStopLevel = stop;
_signals.Add(new SignalInfo
{
BuySignal = buySignal,
SellSignal = sellSignal,
ClosePrice = candle.ClosePrice,
Time = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime
});
TrimSignals();
if (_signals.Count <= SignalBar)
return;
var index = _signals.Count - 1 - SignalBar;
if (index < 0)
return;
var signal = _signals[index];
var allowTrading = _atr.IsFormed;
ExecuteSignal(signal, allowTrading);
}
private void ExecuteSignal(SignalInfo signal, bool allowTrading)
{
if (SellPositionClose && signal.BuySignal && Position < 0)
{
BuyMarket();
ResetShortStops();
}
if (BuyPositionClose && signal.SellSignal && Position > 0)
{
SellMarket();
ResetLongStops();
}
if (!allowTrading || Position != 0)
return;
if (BuyPositionOpen && signal.BuySignal)
{
if (Volume > 0)
{
BuyMarket();
ResetShortStops();
SetLongStops(signal.ClosePrice);
}
}
else if (SellPositionOpen && signal.SellSignal)
{
if (Volume > 0)
{
SellMarket();
ResetLongStops();
SetShortStops(signal.ClosePrice);
}
}
}
private void UpdateStops(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop is decimal longStop && candle.LowPrice <= longStop)
{
SellMarket();
ResetLongStops();
return;
}
if (_longTake is decimal longTake && candle.HighPrice >= longTake)
{
SellMarket();
ResetLongStops();
}
}
else if (Position < 0)
{
if (_shortStop is decimal shortStop && candle.HighPrice >= shortStop)
{
BuyMarket();
ResetShortStops();
return;
}
if (_shortTake is decimal shortTake && candle.LowPrice <= shortTake)
{
BuyMarket();
ResetShortStops();
}
}
}
private void TrimSignals()
{
var max = Math.Max(SignalBar + 5, 10);
while (_signals.Count > max)
{
try { _signals.RemoveAt(0); } catch { break; }
}
}
private decimal CalculateStop(decimal price, decimal prevPrice, decimal prevStop)
{
var offset = Npips;
if (price == prevStop)
return prevStop;
if (prevPrice < prevStop && price < prevStop)
return Math.Min(prevStop, price * (1m + offset));
if (prevPrice > prevStop && price > prevStop)
return Math.Max(prevStop, price * (1m - offset));
return price > prevStop
? price * (1m - offset)
: price * (1m + offset);
}
private void SetLongStops(decimal basePrice)
{
var step = GetEffectiveStep();
_longStop = StopLossSteps > 0 ? basePrice - step * StopLossSteps : null;
_longTake = TakeProfitSteps > 0 ? basePrice + step * TakeProfitSteps : null;
}
private void SetShortStops(decimal basePrice)
{
var step = GetEffectiveStep();
_shortStop = StopLossSteps > 0 ? basePrice + step * StopLossSteps : null;
_shortTake = TakeProfitSteps > 0 ? basePrice - step * TakeProfitSteps : null;
}
private void ResetLongStops()
{
_longStop = null;
_longTake = null;
}
private void ResetShortStops()
{
_shortStop = null;
_shortTake = null;
}
private decimal GetEffectiveStep()
{
var step = Security?.PriceStep;
if (step is decimal s && s > 0)
return s;
return 0.0001m;
}
private decimal GetAppliedPrice(ICandleMessage candle)
{
var open = candle.OpenPrice;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
return PriceMode switch
{
AppliedPriceModes.Close => close,
AppliedPriceModes.Open => open,
AppliedPriceModes.High => high,
AppliedPriceModes.Low => low,
AppliedPriceModes.Median => (high + low) / 2m,
AppliedPriceModes.Typical => (close + high + low) / 3m,
AppliedPriceModes.Weighted => (2m * close + high + low) / 4m,
AppliedPriceModes.Simple => (open + close) / 2m,
AppliedPriceModes.Quarter => (open + close + high + low) / 4m,
AppliedPriceModes.TrendFollow0 => close > open ? high : close < open ? low : close,
AppliedPriceModes.TrendFollow1 => close > open ? (high + close) / 2m : close < open ? (low + close) / 2m : close,
AppliedPriceModes.Demark => CalculateDemarkPrice(open, high, low, close),
_ => close
};
}
private static decimal CalculateDemarkPrice(decimal open, decimal high, decimal low, decimal close)
{
var result = high + low + close;
if (close < open)
result = (result + low) / 2m;
else if (close > open)
result = (result + high) / 2m;
else
result = (result + close) / 2m;
return ((result - low) + (result - high)) / 2m;
}
private sealed class SignalInfo
{
public bool BuySignal { get; init; }
public bool SellSignal { get; init; }
public decimal ClosePrice { get; init; }
public DateTimeOffset Time { get; init; }
}
/// <summary>
/// Available price calculation modes.
/// </summary>
public enum AppliedPriceModes
{
/// <summary>
/// Closing price.
/// </summary>
Close,
/// <summary>
/// Opening price.
/// </summary>
Open,
/// <summary>
/// Highest price.
/// </summary>
High,
/// <summary>
/// Lowest price.
/// </summary>
Low,
/// <summary>
/// Median price (high + low) / 2.
/// </summary>
Median,
/// <summary>
/// Typical price (close + high + low) / 3.
/// </summary>
Typical,
/// <summary>
/// Weighted close price (2 * close + high + low) / 4.
/// </summary>
Weighted,
/// <summary>
/// Simple average of open and close.
/// </summary>
Simple,
/// <summary>
/// Average of open, close, high and low.
/// </summary>
Quarter,
/// <summary>
/// Trend follow price variant 0.
/// </summary>
TrendFollow0,
/// <summary>
/// Trend follow price variant 1.
/// </summary>
TrendFollow1,
/// <summary>
/// Demark price formula.
/// </summary>
Demark
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class stopreversal_trailing_strategy(Strategy):
"""Stopreversal indicator based trailing stop strategy with ATR and signal delay."""
def __init__(self):
super(stopreversal_trailing_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Stopreversal timeframe", "General")
self._atr_period = self.Param("AtrPeriod", 15) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "ATR lookback for trailing stop", "Indicator")
self._stop_loss_steps = self.Param("StopLossSteps", 10) \
.SetDisplay("Stop Loss Steps", "Stop loss distance in price steps", "Risk")
self._take_profit_steps = self.Param("TakeProfitSteps", 20) \
.SetDisplay("Take Profit Steps", "Take profit distance in price steps", "Risk")
self._buy_open = self.Param("BuyPositionOpen", True) \
.SetDisplay("Open Long", "Allow opening long positions", "Trading")
self._sell_open = self.Param("SellPositionOpen", True) \
.SetDisplay("Open Short", "Allow opening short positions", "Trading")
self._buy_close = self.Param("BuyPositionClose", True) \
.SetDisplay("Close Long", "Close long positions on sell signals", "Trading")
self._sell_close = self.Param("SellPositionClose", True) \
.SetDisplay("Close Short", "Close short positions on buy signals", "Trading")
self._npips = self.Param("Npips", 0.004) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Offset", "Fractional offset applied to the stop line", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Bar delay before acting on a signal", "Indicator")
self._prev_stop = None
self._prev_price = None
self._signals = []
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrPeriod(self):
return int(self._atr_period.Value)
@property
def StopLossSteps(self):
return int(self._stop_loss_steps.Value)
@property
def TakeProfitSteps(self):
return int(self._take_profit_steps.Value)
@property
def BuyPositionOpen(self):
return self._buy_open.Value
@property
def SellPositionOpen(self):
return self._sell_open.Value
@property
def BuyPositionClose(self):
return self._buy_close.Value
@property
def SellPositionClose(self):
return self._sell_close.Value
@property
def Npips(self):
return float(self._npips.Value)
@property
def SignalBar(self):
return int(self._signal_bar.Value)
def _get_step(self):
sec = self.Security
if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0:
return float(sec.PriceStep)
return 0.0001
def OnStarted2(self, time):
super(stopreversal_trailing_strategy, self).OnStarted2(time)
self._prev_stop = None
self._prev_price = None
self._signals = []
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._atr = AverageTrueRange()
self._atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
def process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
self._update_stops(candle)
price = float(candle.ClosePrice)
npips = self.Npips
prev_stop = self._prev_stop if self._prev_stop is not None else price * (1.0 - npips)
prev_price = self._prev_price if self._prev_price is not None else price
has_prev = self._prev_stop is not None and self._prev_price is not None
stop = self._calc_stop(price, prev_price, prev_stop)
buy_signal = has_prev and price > stop and prev_price < prev_stop and prev_stop != 0
sell_signal = has_prev and price < stop and prev_price > prev_stop and prev_stop != 0
self._prev_price = price
self._prev_stop = stop
self._signals.append((buy_signal, sell_signal, float(candle.ClosePrice)))
mx = max(self.SignalBar + 5, 10)
while len(self._signals) > mx:
self._signals.pop(0)
if len(self._signals) <= self.SignalBar:
return
idx = len(self._signals) - 1 - self.SignalBar
if idx < 0:
return
sig = self._signals[idx]
allow_trading = self._atr.IsFormed
self._execute_signal(sig, allow_trading)
def _execute_signal(self, sig, allow_trading):
buy_sig, sell_sig, close_price = sig
if self.SellPositionClose and buy_sig and self.Position < 0:
self.BuyMarket()
self._short_stop = None
self._short_take = None
if self.BuyPositionClose and sell_sig and self.Position > 0:
self.SellMarket()
self._long_stop = None
self._long_take = None
if not allow_trading or self.Position != 0:
return
step = self._get_step()
if self.BuyPositionOpen and buy_sig:
self.BuyMarket()
self._short_stop = None
self._short_take = None
self._long_stop = close_price - step * self.StopLossSteps if self.StopLossSteps > 0 else None
self._long_take = close_price + step * self.TakeProfitSteps if self.TakeProfitSteps > 0 else None
elif self.SellPositionOpen and sell_sig:
self.SellMarket()
self._long_stop = None
self._long_take = None
self._short_stop = close_price + step * self.StopLossSteps if self.StopLossSteps > 0 else None
self._short_take = close_price - step * self.TakeProfitSteps if self.TakeProfitSteps > 0 else None
def _update_stops(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self._long_stop is not None and lo <= self._long_stop:
self.SellMarket()
self._long_stop = None
self._long_take = None
return
if self._long_take is not None and h >= self._long_take:
self.SellMarket()
self._long_stop = None
self._long_take = None
elif self.Position < 0:
if self._short_stop is not None and h >= self._short_stop:
self.BuyMarket()
self._short_stop = None
self._short_take = None
return
if self._short_take is not None and lo <= self._short_take:
self.BuyMarket()
self._short_stop = None
self._short_take = None
def _calc_stop(self, price, prev_price, prev_stop):
offset = self.Npips
if price == prev_stop:
return prev_stop
if prev_price < prev_stop and price < prev_stop:
return min(prev_stop, price * (1.0 + offset))
if prev_price > prev_stop and price > prev_stop:
return max(prev_stop, price * (1.0 - offset))
if price > prev_stop:
return price * (1.0 - offset)
return price * (1.0 + offset)
def OnReseted(self):
super(stopreversal_trailing_strategy, self).OnReseted()
self._prev_stop = None
self._prev_price = None
self._signals = []
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def CreateClone(self):
return stopreversal_trailing_strategy()