通道追踪止损
该策略基于唐奇安通道突破开仓,并通过追踪止损管理风险。
当价格收于通道之外时开仓。追踪止损跟随通道另一侧并加上偏移量。可选的“套索”追踪保持止损与当前价格和止盈之间的等距。在成交后可清理挂单。
细节
- 入场条件:收盘价突破通道范围。
- 多空方向:双向。
- 出场条件:追踪止损或反向信号。
- 止损:追踪止损,可选套索。
- 默认值:
TrailPeriod= 5TrailStop= 50UseNooseTrailing= trueUseChannelTrailing= trueDeletePendingOrders= trueCandleType= TimeSpan.FromMinutes(5)
- 筛选器:
- 分类:趋势
- 方向:双向
- 指标:唐奇安通道
- 止损:追踪
- 复杂度:中等
- 时间框架:日内 (5分钟)
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy using manually calculated channel based trailing stops with optional "noose" adjustment.
/// </summary>
public class ChannelTrailingStopStrategy : Strategy
{
private readonly StrategyParam<int> _trailPeriod;
private readonly StrategyParam<decimal> _trailStop;
private readonly StrategyParam<bool> _useNooseTrailing;
private readonly StrategyParam<bool> _useChannelTrailing;
private readonly StrategyParam<bool> _deletePendingOrders;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private decimal _longStop;
private decimal _shortStop;
private decimal? _takeProfitPrice;
private int _cooldownRemaining;
/// <summary>
/// Period to calculate channel boundaries.
/// </summary>
public int TrailPeriod
{
get => _trailPeriod.Value;
set => _trailPeriod.Value = value;
}
/// <summary>
/// Offset added to channel boundaries.
/// </summary>
public decimal TrailStop
{
get => _trailStop.Value;
set => _trailStop.Value = value;
}
/// <summary>
/// Enable symmetrical "noose" trailing.
/// </summary>
public bool UseNooseTrailing
{
get => _useNooseTrailing.Value;
set => _useNooseTrailing.Value = value;
}
/// <summary>
/// Enable trailing stop based on channel levels.
/// </summary>
public bool UseChannelTrailing
{
get => _useChannelTrailing.Value;
set => _useChannelTrailing.Value = value;
}
/// <summary>
/// Delete pending orders after trade execution.
/// </summary>
public bool DeletePendingOrders
{
get => _deletePendingOrders.Value;
set => _deletePendingOrders.Value = value;
}
/// <summary>
/// Type of candles.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ChannelTrailingStopStrategy"/> class.
/// </summary>
public ChannelTrailingStopStrategy()
{
_trailPeriod = Param(nameof(TrailPeriod), 10)
.SetDisplay("Channel Period", "Lookback for channel calculation", "Parameters")
.SetOptimize(5, 50, 5);
_trailStop = Param(nameof(TrailStop), 100m)
.SetDisplay("Trail Stop", "Offset from channel boundaries", "Parameters");
_useNooseTrailing = Param(nameof(UseNooseTrailing), true)
.SetDisplay("Use Noose Trailing", "Mirror stop relative to take profit", "Parameters");
_useChannelTrailing = Param(nameof(UseChannelTrailing), true)
.SetDisplay("Use Channel Trailing", "Adjust stop to channel levels", "Parameters");
_deletePendingOrders = Param(nameof(DeletePendingOrders), true)
.SetDisplay("Delete Pending Orders", "Cancel pending orders after fill", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for candles", "General");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_longStop = 0m;
_shortStop = 0m;
_takeProfitPrice = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
while (_highs.Count > TrailPeriod)
_highs.Dequeue();
while (_lows.Count > TrailPeriod)
_lows.Dequeue();
if (_highs.Count < TrailPeriod || _lows.Count < TrailPeriod)
return;
var upper = GetHighest();
var lower = GetLowest();
var range = upper - lower;
if (range <= 0m)
return;
var threshold = range * 0.05m;
if (_cooldownRemaining == 0)
{
if (candle.ClosePrice >= upper - threshold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_longStop = candle.ClosePrice - TrailStop;
_takeProfitPrice = candle.ClosePrice + TrailStop;
_cooldownRemaining = CooldownBars;
}
else if (candle.ClosePrice <= lower + threshold && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_shortStop = candle.ClosePrice + TrailStop;
_takeProfitPrice = candle.ClosePrice - TrailStop;
_cooldownRemaining = CooldownBars;
}
}
if (UseChannelTrailing)
{
if (Position > 0)
{
var level = lower - TrailStop;
if (level > _longStop)
_longStop = level;
}
else if (Position < 0)
{
var level = upper + TrailStop;
if (_shortStop == 0m || level < _shortStop)
_shortStop = level;
}
}
if (UseNooseTrailing && _takeProfitPrice is decimal takeProfitPrice)
{
if (Position > 0)
{
var noose = candle.ClosePrice - (takeProfitPrice - candle.ClosePrice);
if (noose > _longStop)
_longStop = noose;
}
else if (Position < 0)
{
var noose = candle.ClosePrice + (candle.ClosePrice - takeProfitPrice);
if (_shortStop == 0m || noose < _shortStop)
_shortStop = noose;
}
}
if (Position > 0 && _longStop > 0m && candle.LowPrice <= _longStop)
{
SellMarket();
_longStop = 0m;
_takeProfitPrice = null;
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && _shortStop > 0m && candle.HighPrice >= _shortStop)
{
BuyMarket();
_shortStop = 0m;
_takeProfitPrice = null;
_cooldownRemaining = CooldownBars;
}
}
private decimal GetHighest()
{
var highest = decimal.MinValue;
foreach (var value in _highs)
{
if (value > highest)
highest = value;
}
return highest;
}
private decimal GetLowest()
{
var lowest = decimal.MaxValue;
foreach (var value in _lows)
{
if (value < lowest)
lowest = value;
}
return lowest;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class channel_trailing_stop_strategy(Strategy):
def __init__(self):
super(channel_trailing_stop_strategy, self).__init__()
self._trail_period = self.Param("TrailPeriod", 10) \
.SetDisplay("Channel Period", "Lookback for channel calculation", "Parameters")
self._trail_stop = self.Param("TrailStop", 100.0) \
.SetDisplay("Trail Stop", "Offset from channel boundaries", "Parameters")
self._use_noose_trailing = self.Param("UseNooseTrailing", True) \
.SetDisplay("Use Noose Trailing", "Mirror stop relative to take profit", "Parameters")
self._use_channel_trailing = self.Param("UseChannelTrailing", True) \
.SetDisplay("Use Channel Trailing", "Adjust stop to channel levels", "Parameters")
self._delete_pending_orders = self.Param("DeletePendingOrders", True) \
.SetDisplay("Delete Pending Orders", "Cancel pending orders after fill", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for candles", "General")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._highs = []
self._lows = []
self._long_stop = 0.0
self._short_stop = 0.0
self._take_profit_price = None
self._cooldown_remaining = 0
@property
def trail_period(self):
return self._trail_period.Value
@property
def trail_stop(self):
return self._trail_stop.Value
@property
def use_noose_trailing(self):
return self._use_noose_trailing.Value
@property
def use_channel_trailing(self):
return self._use_channel_trailing.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(channel_trailing_stop_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._long_stop = 0.0
self._short_stop = 0.0
self._take_profit_price = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(channel_trailing_stop_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
period = self.trail_period
while len(self._highs) > period:
self._highs.pop(0)
while len(self._lows) > period:
self._lows.pop(0)
if len(self._highs) < period or len(self._lows) < period:
return
upper = max(self._highs)
lower = min(self._lows)
rng = upper - lower
if rng <= 0:
return
threshold = rng * 0.05
close = float(candle.ClosePrice)
ts = float(self.trail_stop)
if self._cooldown_remaining == 0:
if close >= upper - threshold and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._long_stop = close - ts
self._take_profit_price = close + ts
self._cooldown_remaining = self.cooldown_bars
elif close <= lower + threshold and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._short_stop = close + ts
self._take_profit_price = close - ts
self._cooldown_remaining = self.cooldown_bars
if self.use_channel_trailing:
if self.Position > 0:
level = lower - ts
if level > self._long_stop:
self._long_stop = level
elif self.Position < 0:
level = upper + ts
if self._short_stop == 0 or level < self._short_stop:
self._short_stop = level
if self.use_noose_trailing and self._take_profit_price is not None:
if self.Position > 0:
noose = close - (self._take_profit_price - close)
if noose > self._long_stop:
self._long_stop = noose
elif self.Position < 0:
noose = close + (close - self._take_profit_price)
if self._short_stop == 0 or noose < self._short_stop:
self._short_stop = noose
if self.Position > 0 and self._long_stop > 0 and float(candle.LowPrice) <= self._long_stop:
self.SellMarket()
self._long_stop = 0.0
self._take_profit_price = None
self._cooldown_remaining = self.cooldown_bars
elif self.Position < 0 and self._short_stop > 0 and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket()
self._short_stop = 0.0
self._take_profit_price = None
self._cooldown_remaining = self.cooldown_bars
def CreateClone(self):
return channel_trailing_stop_strategy()