False Breakout Trap Strategy
The False Breakout Trap aims to capitalize on breaks that fail to hold beyond key support or resistance. Traders often jump into a breakout only to see price quickly reverse, leaving them trapped.
Testing indicates an average annual return of about 52%. It performs best in the crypto market.
This strategy waits for that failure, entering in the opposite direction once price closes back inside the range.
Stop placement is tight, just beyond the failed breakout level, ensuring losses stay small if the reversal doesn't materialize.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLoss= 2%
- Filters:
- Category: Reversal
- Direction: Both
- Indicators: Price Action
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// False Breakout Trap strategy.
/// Detects when price breaks a recent high/low range then reverses back.
/// Trades against the failed breakout direction.
/// Exits on the opposite failed breakout or a percent stop beyond the failed breakout level.
/// </summary>
public class FalseBreakoutTrapStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _stopPrice;
private Order _exitOrder;
/// <summary>
/// Lookback period for range.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in percent beyond the failed breakout level.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FalseBreakoutTrapStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetRange(5, 50)
.SetDisplay("Lookback", "Period for high/low range", "Range");
_stopLoss = Param(nameof(StopLoss), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop distance in percent beyond the failed breakout level", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_stopPrice = null;
_exitOrder = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
_stopPrice = null;
_exitOrder = null;
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ProcessQuote).Start();
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool IsExitPending => _exitOrder is not null &&
_exitOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessQuote(Level1ChangeMessage message)
{
var price = Position > 0 ? message.TryGetDecimal(Level1Fields.BestBidPrice)
: Position < 0 ? message.TryGetDecimal(Level1Fields.BestAskPrice)
: null;
if (price is decimal executable && executable > 0m)
TryStopOut(executable);
}
// Long positions are tested against a bid or bar low, short positions against an ask or bar high.
private bool TryStopOut(decimal price)
{
if (_stopPrice is not decimal stop || IsExitPending)
return false;
if (Position > 0 && price <= stop || Position < 0 && price >= stop)
{
ExitPosition();
return true;
}
return false;
}
private void ExitPosition()
{
_exitOrder = Position > 0 ? SellMarket(Position) : BuyMarket(-Position);
_stopPrice = null;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Maintain rolling high/low window
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < LookbackPeriod + 1)
return;
if (TryStopOut(Position > 0 ? candle.LowPrice : candle.HighPrice))
return;
// Find highest high and lowest low of the previous N bars (excluding current)
decimal rangeHigh = decimal.MinValue;
decimal rangeLow = decimal.MaxValue;
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
if (_lows[i] < rangeLow) rangeLow = _lows[i];
}
// False upside breakout: candle broke above range high but closed below it
var falseBreakUp = candle.HighPrice > rangeHigh && candle.ClosePrice < rangeHigh;
// False downside breakout: candle broke below range low but closed above it
var falseBreakDown = candle.LowPrice < rangeLow && candle.ClosePrice > rangeLow;
if (Position > 0 && falseBreakUp || Position < 0 && falseBreakDown)
{
if (!IsExitPending)
ExitPosition();
}
else if (Position == 0 && falseBreakDown)
{
_stopPrice = rangeLow * (1m - StopLoss / 100m);
BuyMarket();
}
else if (Position == 0 && falseBreakUp)
{
_stopPrice = rangeHigh * (1m + StopLoss / 100m);
SellMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class false_breakout_trap_strategy(Strategy):
"""
False Breakout Trap strategy.
Detects when price breaks a recent high/low range then reverses back.
Trades against the failed breakout direction.
Exits on the opposite failed breakout or a percent stop beyond the failed breakout level.
"""
def __init__(self):
super(false_breakout_trap_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20).SetRange(5, 50).SetDisplay("Lookback", "Period for high/low range", "Range")
self._stop_loss = self.Param("StopLoss", 2.0).SetGreaterThanZero().SetDisplay("Stop Loss", "Stop distance in percent beyond the failed breakout level", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._highs = []
self._lows = []
self._stop_price = None
self._exit_order = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def OnReseted(self):
super(false_breakout_trap_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._stop_price = None
self._exit_order = None
def OnStarted2(self, time):
super(false_breakout_trap_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._stop_price = None
self._exit_order = None
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._process_quote).Start()
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 _is_exit_pending(self):
return self._exit_order is not None and self._exit_order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_quote(self, message):
if self.Position > 0:
field = Level1Fields.BestBidPrice
elif self.Position < 0:
field = Level1Fields.BestAskPrice
else:
return
if not message.Changes.ContainsKey(field):
return
price = message.Changes[field]
if price is not None and price > Decimal(0):
self._try_stop_out(price)
# Long positions are tested against a bid or bar low, short positions against an ask or bar high.
def _try_stop_out(self, price):
if self._stop_price is None or self._is_exit_pending():
return False
if (self.Position > 0 and price <= self._stop_price) or (self.Position < 0 and price >= self._stop_price):
self._exit_position()
return True
return False
def _exit_position(self):
if self.Position > 0:
self._exit_order = self.SellMarket(self.Position)
else:
self._exit_order = self.BuyMarket(-self.Position)
self._stop_price = None
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
lookback = self._lookback_period.Value
# Maintain rolling high/low window
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > lookback + 1:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < lookback + 1:
return
if self._try_stop_out(candle.LowPrice if self.Position > 0 else candle.HighPrice):
return
# Find highest high and lowest low of the previous N bars (excluding current)
range_high = max(self._highs[:-1])
range_low = min(self._lows[:-1])
# False upside breakout: candle broke above range high but closed below it
false_break_up = candle.HighPrice > range_high and candle.ClosePrice < range_high
# False downside breakout: candle broke below range low but closed above it
false_break_down = candle.LowPrice < range_low and candle.ClosePrice > range_low
stop_fraction = Decimal(self._stop_loss.Value) / Decimal(100)
if (self.Position > 0 and false_break_up) or (self.Position < 0 and false_break_down):
if not self._is_exit_pending():
self._exit_position()
elif self.Position == 0 and false_break_down:
self._stop_price = range_low * (Decimal(1) - stop_fraction)
self.BuyMarket()
elif self.Position == 0 and false_break_up:
self._stop_price = range_high * (Decimal(1) + stop_fraction)
self.SellMarket()
def CreateClone(self):
return false_breakout_trap_strategy()