A Rejection Candle forms when price probes a level but fails to hold beyond it, leaving a long wick and small body.
Such candles indicate an attempt to move in one direction was firmly rejected by the market.
Testing indicates an average annual return of about 49%. It performs best in the crypto market.
The strategy enters in the opposite direction of the wick once the candle closes, expecting price to reverse back through the range.
Stops are set outside the rejected high or low to cap risk, and trades exit if momentum fails to materialize.
Details
Entry Criteria: pattern match
Long/Short: Both
Exit Criteria: stop-loss or opposite signal
Stops: Yes, percent based
Default Values:
CandleType = 15 minute
WickRatio = 1.5
StopLossPercent = 2
A rejection candle probes beyond the previous candle's low (high), closes the other way, and has a wick longer than WickRatio bodies. The stop lies StopLossPercent beyond the rejected low or high, and a close beyond it closes the position.
Filters:
Category: Pattern
Direction: Both
Indicators: Candlestick
Stops: Yes
Complexity: Intermediate
Timeframe: Intraday
Seasonality: No
Neural networks: No
Divergence: No
Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rejection Candle strategy.
/// A bullish rejection probes below the previous candle's low, closes up, and has a lower wick longer than WickRatio bodies;
/// a bearish rejection mirrors it above the previous high. While flat the strategy trades against the wick.
/// The stop lies StopLossPercent beyond the rejected low or high, and a close beyond it closes the position.
/// </summary>
public class RejectionCandleStrategy : Strategy
{
private readonly StrategyParam<decimal> _wickRatio;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private ICandleMessage _prevCandle;
private decimal _stopPrice;
/// <summary>
/// How many bodies long the rejecting wick must be.
/// </summary>
public decimal WickRatio
{
get => _wickRatio.Value;
set => _wickRatio.Value = value;
}
/// <summary>
/// Distance of the stop beyond the rejected extreme, in percent.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public RejectionCandleStrategy()
{
_wickRatio = Param(nameof(WickRatio), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Wick Ratio", "How many bodies long the rejecting wick must be", "Pattern");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Distance of the stop beyond the rejected extreme, in percent", "Risk");
_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)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCandle = null;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCandle = null;
_stopPrice = default;
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;
var previous = _prevCandle;
_prevCandle = candle;
if (previous == null || !IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position > 0)
{
if (close <= _stopPrice)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (close >= _stopPrice)
BuyMarket(-Position);
return;
}
var body = Math.Abs(close - candle.OpenPrice);
var upperWick = candle.HighPrice - Math.Max(candle.OpenPrice, close);
var lowerWick = Math.Min(candle.OpenPrice, close) - candle.LowPrice;
if (candle.LowPrice < previous.LowPrice && close > candle.OpenPrice && lowerWick > body * WickRatio)
{
BuyMarket(Volume);
_stopPrice = candle.LowPrice * (1 - StopLossPercent / 100m);
}
else if (candle.HighPrice > previous.HighPrice && close < candle.OpenPrice && upperWick > body * WickRatio)
{
SellMarket(Volume);
_stopPrice = candle.HighPrice * (1 + StopLossPercent / 100m);
}
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class rejection_candle_strategy(Strategy):
"""
Rejection Candle strategy.
A bullish rejection probes below the previous candle's low, closes up, and has a lower wick longer than WickRatio bodies;
a bearish rejection mirrors it above the previous high. While flat the strategy trades against the wick.
The stop lies StopLossPercent beyond the rejected low or high, and a close beyond it closes the position.
"""
def __init__(self):
super(rejection_candle_strategy, self).__init__()
self._wick_ratio = self.Param("WickRatio", 1.5).SetGreaterThanZero().SetDisplay("Wick Ratio", "How many bodies long the rejecting wick must be", "Pattern")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Distance of the stop beyond the rejected extreme, in percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_candle = None
self._stop_price = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rejection_candle_strategy, self).OnReseted()
self._prev_candle = None
self._stop_price = Decimal(0)
def OnStarted2(self, time):
super(rejection_candle_strategy, self).OnStarted2(time)
self._prev_candle = None
self._stop_price = Decimal(0)
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
previous = self._prev_candle
self._prev_candle = candle
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position > 0:
if close <= self._stop_price:
self.SellMarket(self.Position)
return
if self.Position < 0:
if close >= self._stop_price:
self.BuyMarket(-self.Position)
return
body = Math.Abs(close - candle.OpenPrice)
upper_wick = candle.HighPrice - Math.Max(candle.OpenPrice, close)
lower_wick = Math.Min(candle.OpenPrice, close) - candle.LowPrice
ratio = Decimal(self._wick_ratio.Value)
percent = Decimal(self._stop_loss_percent.Value) / Decimal(100)
if candle.LowPrice < previous.LowPrice and close > candle.OpenPrice and lower_wick > body * ratio:
self.BuyMarket(self.Volume)
self._stop_price = candle.LowPrice * (Decimal(1) - percent)
elif candle.HighPrice > previous.HighPrice and close < candle.OpenPrice and upper_wick > body * ratio:
self.SellMarket(self.Volume)
self._stop_price = candle.HighPrice * (Decimal(1) + percent)
def CreateClone(self):
return rejection_candle_strategy()