Breaks and Retests
Стратегия входит при пробоях последних максимумов или минимумов и при необходимости на ретестах, управляя риском трейлинг-стопом.
Метод отслеживает уровни поддержки и сопротивления как максимум и минимум закрытий за заданный период. При пробое открывается позиция или ожидается возврат к пробитому уровню. Выходы используют первоначальный стоп-лосс, который превращается в трейлинг-стоп после достижения прибыли.
Детали
- Условия входа: пробой уровня сопротивления/поддержки, опционально ретест.
- Длинные/короткие: настраиваемо.
- Условия выхода: трейлинг-стоп или обратный пробой.
- Стопы: начальный стоп-лосс и трейлинг-стоп.
- Значения по умолчанию:
LookbackPeriod= 20RetestBarsSinceBreakout= 2RetestDetectionLimit= 2ProfitThresholdPercent= 5mTrailingStopGapPercent= 1mStopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Пробой
- Направление: Оба
- Индикаторы: Highest, Lowest
- Стопы: Есть
- Сложность: Средняя
- Таймфрейм: Внутридневной (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>
/// Breakout and retest strategy with trailing stop.
/// </summary>
public class BreaksAndRetestsStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _profitThresholdPercent;
private readonly StrategyParam<decimal> _trailingStopGapPercent;
private readonly StrategyParam<int> _maxHoldBars;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _prevHighest;
private decimal _prevLowest;
private decimal _entryPrice;
private bool _trailingStopActive;
private decimal _highestSinceTrailing;
private decimal _lowestSinceTrailing;
private int _barsInPosition;
private int _barsSinceExit;
public int LookbackPeriod { get => _lookbackPeriod.Value; set => _lookbackPeriod.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal ProfitThresholdPercent { get => _profitThresholdPercent.Value; set => _profitThresholdPercent.Value = value; }
public decimal TrailingStopGapPercent { get => _trailingStopGapPercent.Value; set => _trailingStopGapPercent.Value = value; }
public int MaxHoldBars { get => _maxHoldBars.Value; set => _maxHoldBars.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BreaksAndRetestsStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Number of bars for support/resistance", "Levels");
_stopLossPercent = Param(nameof(StopLossPercent), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Initial stop loss", "Risk");
_profitThresholdPercent = Param(nameof(ProfitThresholdPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Profit Threshold %", "Activate trailing after profit", "Risk");
_trailingStopGapPercent = Param(nameof(TrailingStopGapPercent), 0.8m)
.SetGreaterThanZero()
.SetDisplay("Trailing Gap %", "Gap for trailing stop", "Risk");
_maxHoldBars = Param(nameof(MaxHoldBars), 25)
.SetGreaterThanZero()
.SetDisplay("Max Hold Bars", "Max bars to hold position", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 3)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars to wait after exit", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candles for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_prevHighest = 0m;
_prevLowest = 0m;
_entryPrice = 0m;
_trailingStopActive = false;
_highestSinceTrailing = 0m;
_lowestSinceTrailing = 0m;
_barsInPosition = 0;
_barsSinceExit = 0;
}
/// <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 ClosePosition()
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_entryPrice = 0m;
_trailingStopActive = false;
_barsInPosition = 0;
_barsSinceExit = 0;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
_highs.RemoveAt(0);
if (_lows.Count > LookbackPeriod + 1)
_lows.RemoveAt(0);
if (_highs.Count <= LookbackPeriod)
return;
// Compute highest/lowest from previous N candles (excluding current)
var highest = decimal.MinValue;
var lowest = decimal.MaxValue;
for (var i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > highest) highest = _highs[i];
if (_lows[i] < lowest) lowest = _lows[i];
}
if (Position != 0)
{
_barsInPosition++;
// Handle stops
HandleStop(candle);
// Max hold exit
if (Position != 0 && _barsInPosition >= MaxHoldBars)
ClosePosition();
}
else
{
_barsSinceExit++;
// Breakout detection with cooldown
if (_barsSinceExit >= CooldownBars && _prevHighest > 0 && _prevLowest > 0)
{
if (candle.ClosePrice > _prevHighest)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_trailingStopActive = false;
_barsInPosition = 0;
}
else if (candle.ClosePrice < _prevLowest)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_trailingStopActive = false;
_barsInPosition = 0;
}
}
}
_prevHighest = highest;
_prevLowest = lowest;
}
private void HandleStop(ICandleMessage candle)
{
if (Position > 0 && _entryPrice > 0)
{
var profitPercent = (candle.ClosePrice - _entryPrice) / _entryPrice * 100m;
if (!_trailingStopActive && profitPercent >= ProfitThresholdPercent)
{
_trailingStopActive = true;
_highestSinceTrailing = candle.ClosePrice;
}
if (_trailingStopActive)
{
_highestSinceTrailing = Math.Max(_highestSinceTrailing, candle.ClosePrice);
var stop = _highestSinceTrailing * (1 - TrailingStopGapPercent / 100m);
if (candle.ClosePrice <= stop)
ClosePosition();
}
else
{
var stop = _entryPrice * (1 - StopLossPercent / 100m);
if (candle.ClosePrice <= stop)
ClosePosition();
}
}
else if (Position < 0 && _entryPrice > 0)
{
var profitPercent = (_entryPrice - candle.ClosePrice) / _entryPrice * 100m;
if (!_trailingStopActive && profitPercent >= ProfitThresholdPercent)
{
_trailingStopActive = true;
_lowestSinceTrailing = candle.ClosePrice;
}
if (_trailingStopActive)
{
_lowestSinceTrailing = Math.Min(_lowestSinceTrailing, candle.ClosePrice);
var stop = _lowestSinceTrailing * (1 + TrailingStopGapPercent / 100m);
if (candle.ClosePrice >= stop)
ClosePosition();
}
else
{
var stop = _entryPrice * (1 + StopLossPercent / 100m);
if (candle.ClosePrice >= stop)
ClosePosition();
}
}
}
}
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
class breaks_and_retests_strategy(Strategy):
def __init__(self):
super(breaks_and_retests_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Number of bars for support/resistance", "Levels")
self._stop_loss_percent = self.Param("StopLossPercent", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Initial stop loss", "Risk")
self._profit_threshold_percent = self.Param("ProfitThresholdPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Profit Threshold %", "Activate trailing after profit", "Risk")
self._trailing_stop_gap_percent = self.Param("TrailingStopGapPercent", 0.8) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Gap %", "Gap for trailing stop", "Risk")
self._max_hold_bars = self.Param("MaxHoldBars", 25) \
.SetGreaterThanZero() \
.SetDisplay("Max Hold Bars", "Max bars to hold position", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 3) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Bars to wait after exit", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candles for calculations", "General")
self._highs = []
self._lows = []
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._entry_price = 0.0
self._trailing_stop_active = False
self._highest_since_trailing = 0.0
self._lowest_since_trailing = 0.0
self._bars_in_position = 0
self._bars_since_exit = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(breaks_and_retests_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._entry_price = 0.0
self._trailing_stop_active = False
self._highest_since_trailing = 0.0
self._lowest_since_trailing = 0.0
self._bars_in_position = 0
self._bars_since_exit = 0
def OnStarted2(self, time):
super(breaks_and_retests_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _close_position(self):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
self._trailing_stop_active = False
self._bars_in_position = 0
self._bars_since_exit = 0
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback_period.Value
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > lookback + 1:
self._highs = self._highs[1:]
if len(self._lows) > lookback + 1:
self._lows = self._lows[1:]
if len(self._highs) <= lookback:
return
highest = max(self._highs[:-1])
lowest = min(self._lows[:-1])
close = float(candle.ClosePrice)
if self.Position != 0:
self._bars_in_position += 1
self._handle_stop(candle)
if self.Position != 0 and self._bars_in_position >= self._max_hold_bars.Value:
self._close_position()
else:
self._bars_since_exit += 1
if self._bars_since_exit >= self._cooldown_bars.Value and self._prev_highest > 0 and self._prev_lowest > 0:
if close > self._prev_highest:
self.BuyMarket()
self._entry_price = close
self._trailing_stop_active = False
self._bars_in_position = 0
elif close < self._prev_lowest:
self.SellMarket()
self._entry_price = close
self._trailing_stop_active = False
self._bars_in_position = 0
self._prev_highest = highest
self._prev_lowest = lowest
def _handle_stop(self, candle):
close = float(candle.ClosePrice)
sl_pct = float(self._stop_loss_percent.Value)
pt_pct = float(self._profit_threshold_percent.Value)
tg_pct = float(self._trailing_stop_gap_percent.Value)
if self.Position > 0 and self._entry_price > 0:
profit_pct = (close - self._entry_price) / self._entry_price * 100.0
if not self._trailing_stop_active and profit_pct >= pt_pct:
self._trailing_stop_active = True
self._highest_since_trailing = close
if self._trailing_stop_active:
if close > self._highest_since_trailing:
self._highest_since_trailing = close
stop = self._highest_since_trailing * (1.0 - tg_pct / 100.0)
if close <= stop:
self._close_position()
else:
stop = self._entry_price * (1.0 - sl_pct / 100.0)
if close <= stop:
self._close_position()
elif self.Position < 0 and self._entry_price > 0:
profit_pct = (self._entry_price - close) / self._entry_price * 100.0
if not self._trailing_stop_active and profit_pct >= pt_pct:
self._trailing_stop_active = True
self._lowest_since_trailing = close
if self._trailing_stop_active:
if close < self._lowest_since_trailing:
self._lowest_since_trailing = close
stop = self._lowest_since_trailing * (1.0 + tg_pct / 100.0)
if close >= stop:
self._close_position()
else:
stop = self._entry_price * (1.0 + sl_pct / 100.0)
if close >= stop:
self._close_position()
def CreateClone(self):
return breaks_and_retests_strategy()