Equilibrium Candles Pattern Strategy
Strategy using equilibrium candles to detect short trends and enter on pullbacks. The equilibrium is the midpoint between the highest and lowest prices over a lookback window. After a bullish or bearish streak, a move back through the equilibrium triggers an entry. ATR is used for optional stop/target and to exit on unusually large candles.
Details
- Entry Criteria:
- Long: After bullish trend when price crosses below equilibrium.
- Short: After bearish trend when price crosses above equilibrium.
- Long/Short: Both
- Stops: ATR-based stop loss and take profit (optional)
- Default Values:
EquilibriumLength= 9CandlesForTrend= 7MaxPullbackCandles= 2AtrPeriod= 14StopMultiplier= 2UseTpSl= trueUseBigCandleExit= trueBigCandleMultiplier= 1UseReverse= falseCandleType= TimeSpan.FromMinutes(5).TimeFrame()
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>
/// Equilibrium candles pattern strategy.
/// The equilibrium is the midpoint of the highest high and lowest low over EquilibriumLength candles. At least CandlesForTrend
/// consecutive closes above it form a bullish trend; the first MaxPullbackCandles closes back below it are a pullback that goes
/// long. A bearish trend of closes below it followed by closes back above goes short. UseReverse swaps the directions and an
/// opposite signal reverses the position. With UseTpSl the stop and target lie StopMultiplier ATR from the entry, and with
/// UseBigCandleExit a candle body larger than BigCandleMultiplier ATR closes the position.
/// </summary>
public class EquilibriumCandlesPatternStrategy : Strategy
{
private readonly StrategyParam<int> _equilibriumLength;
private readonly StrategyParam<int> _candlesForTrend;
private readonly StrategyParam<int> _maxPullbackCandles;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stopMultiplier;
private readonly StrategyParam<bool> _useTpSl;
private readonly StrategyParam<bool> _useBigCandleExit;
private readonly StrategyParam<decimal> _bigCandleMultiplier;
private readonly StrategyParam<bool> _useReverse;
private readonly StrategyParam<DataType> _candleType;
private int _aboveCount;
private int _belowCount;
private int _bullTrendLength;
private int _bearTrendLength;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Lookback of the equilibrium.
/// </summary>
public int EquilibriumLength
{
get => _equilibriumLength.Value;
set => _equilibriumLength.Value = value;
}
/// <summary>
/// Consecutive closes on one side of the equilibrium that form a trend.
/// </summary>
public int CandlesForTrend
{
get => _candlesForTrend.Value;
set => _candlesForTrend.Value = value;
}
/// <summary>
/// Maximum pullback candles that still give an entry.
/// </summary>
public int MaxPullbackCandles
{
get => _maxPullbackCandles.Value;
set => _maxPullbackCandles.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop and target distance in ATR.
/// </summary>
public decimal StopMultiplier
{
get => _stopMultiplier.Value;
set => _stopMultiplier.Value = value;
}
/// <summary>
/// Use the ATR stop and target.
/// </summary>
public bool UseTpSl
{
get => _useTpSl.Value;
set => _useTpSl.Value = value;
}
/// <summary>
/// Close the position on a big candle.
/// </summary>
public bool UseBigCandleExit
{
get => _useBigCandleExit.Value;
set => _useBigCandleExit.Value = value;
}
/// <summary>
/// Body size in ATR that makes a big candle.
/// </summary>
public decimal BigCandleMultiplier
{
get => _bigCandleMultiplier.Value;
set => _bigCandleMultiplier.Value = value;
}
/// <summary>
/// Trade in the opposite direction.
/// </summary>
public bool UseReverse
{
get => _useReverse.Value;
set => _useReverse.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EquilibriumCandlesPatternStrategy()
{
_equilibriumLength = Param(nameof(EquilibriumLength), 9)
.SetGreaterThanZero()
.SetDisplay("Equilibrium Length", "Lookback of the equilibrium", "Pattern");
_candlesForTrend = Param(nameof(CandlesForTrend), 7)
.SetGreaterThanZero()
.SetDisplay("Candles For Trend", "Consecutive closes that form a trend", "Pattern");
_maxPullbackCandles = Param(nameof(MaxPullbackCandles), 2)
.SetGreaterThanZero()
.SetDisplay("Max Pullback Candles", "Maximum pullback candles that still give an entry", "Pattern");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Risk");
_stopMultiplier = Param(nameof(StopMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Multiplier", "Stop and target distance in ATR", "Risk");
_useTpSl = Param(nameof(UseTpSl), true)
.SetDisplay("Use TP/SL", "Use the ATR stop and target", "Risk");
_useBigCandleExit = Param(nameof(UseBigCandleExit), true)
.SetDisplay("Big Candle Exit", "Close the position on a big candle", "Risk");
_bigCandleMultiplier = Param(nameof(BigCandleMultiplier), 1m)
.SetGreaterThanZero()
.SetDisplay("Big Candle Multiplier", "Body size in ATR that makes a big candle", "Risk");
_useReverse = Param(nameof(UseReverse), false)
.SetDisplay("Reverse", "Trade in the opposite direction", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
ResetState();
}
private void ResetState()
{
_aboveCount = 0;
_belowCount = 0;
_bullTrendLength = 0;
_bearTrendLength = 0;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var highest = new Highest { Length = EquilibriumLength };
var lowest = new Lowest { Length = EquilibriumLength };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var equilibrium = (highest + lowest) / 2m;
var close = candle.ClosePrice;
var bullishPullback = false;
var bearishPullback = false;
if (close > equilibrium)
{
// The bearish trend that preceded this pullback.
if (_belowCount > 0)
_bearTrendLength = _belowCount;
_aboveCount++;
_belowCount = 0;
bearishPullback = _bearTrendLength >= CandlesForTrend && _aboveCount <= MaxPullbackCandles;
if (_aboveCount > MaxPullbackCandles)
_bearTrendLength = 0;
}
else if (close < equilibrium)
{
if (_aboveCount > 0)
_bullTrendLength = _aboveCount;
_belowCount++;
_aboveCount = 0;
bullishPullback = _bullTrendLength >= CandlesForTrend && _belowCount <= MaxPullbackCandles;
if (_belowCount > MaxPullbackCandles)
_bullTrendLength = 0;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0 && CheckExit(candle, atr))
return;
var goLong = UseReverse ? bearishPullback : bullishPullback;
var goShort = UseReverse ? bullishPullback : bearishPullback;
if (goLong && Position <= 0)
{
_stopPrice = close - atr * StopMultiplier;
_takePrice = close + atr * StopMultiplier;
BuyMarket(Volume + Math.Abs(Position));
}
else if (goShort && Position >= 0)
{
_stopPrice = close + atr * StopMultiplier;
_takePrice = close - atr * StopMultiplier;
SellMarket(Volume + Math.Abs(Position));
}
}
private bool CheckExit(ICandleMessage candle, decimal atr)
{
var bigCandle = UseBigCandleExit && Math.Abs(candle.ClosePrice - candle.OpenPrice) > atr * BigCandleMultiplier;
if (Position > 0)
{
if (bigCandle || (UseTpSl && (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)))
{
SellMarket(Position);
return true;
}
}
else if (bigCandle || (UseTpSl && (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)))
{
BuyMarket(-Position);
return true;
}
return false;
}
}
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.Indicators import Highest, Lowest, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class equilibrium_candles_pattern_strategy(Strategy):
"""
Equilibrium candles pattern strategy.
The equilibrium is the midpoint of the highest high and lowest low over EquilibriumLength candles. At least CandlesForTrend
consecutive closes above it form a bullish trend; the first MaxPullbackCandles closes back below it are a pullback that goes
long. A bearish trend of closes below it followed by closes back above goes short. UseReverse swaps the directions and an
opposite signal reverses the position. With UseTpSl the stop and target lie StopMultiplier ATR from the entry, and with
UseBigCandleExit a candle body larger than BigCandleMultiplier ATR closes the position.
"""
def __init__(self):
super(equilibrium_candles_pattern_strategy, self).__init__()
self._equilibrium_length = self.Param("EquilibriumLength", 9).SetGreaterThanZero().SetDisplay("Equilibrium Length", "Lookback of the equilibrium", "Pattern")
self._candles_for_trend = self.Param("CandlesForTrend", 7).SetGreaterThanZero().SetDisplay("Candles For Trend", "Consecutive closes that form a trend", "Pattern")
self._max_pullback_candles = self.Param("MaxPullbackCandles", 2).SetGreaterThanZero().SetDisplay("Max Pullback Candles", "Maximum pullback candles that still give an entry", "Pattern")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Risk")
self._stop_multiplier = self.Param("StopMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Stop Multiplier", "Stop and target distance in ATR", "Risk")
self._use_tp_sl = self.Param("UseTpSl", True).SetDisplay("Use TP/SL", "Use the ATR stop and target", "Risk")
self._use_big_candle_exit = self.Param("UseBigCandleExit", True).SetDisplay("Big Candle Exit", "Close the position on a big candle", "Risk")
self._big_candle_multiplier = self.Param("BigCandleMultiplier", 1.0).SetGreaterThanZero().SetDisplay("Big Candle Multiplier", "Body size in ATR that makes a big candle", "Risk")
self._use_reverse = self.Param("UseReverse", False).SetDisplay("Reverse", "Trade in the opposite direction", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._above_count = 0
self._below_count = 0
self._bull_trend_length = 0
self._bear_trend_length = 0
self._stop_price = 0.0
self._take_price = 0.0
def OnReseted(self):
super(equilibrium_candles_pattern_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(equilibrium_candles_pattern_strategy, self).OnStarted2(time)
self._reset_state()
highest = Highest()
highest.Length = self._equilibrium_length.Value
lowest = Lowest()
lowest.Length = self._equilibrium_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, highest_value, lowest_value, atr_value):
if candle.State != CandleStates.Finished:
return
equilibrium = (float(highest_value) + float(lowest_value)) / 2.0
atr = float(atr_value)
close = float(candle.ClosePrice)
trend_len = self._candles_for_trend.Value
max_pullback = self._max_pullback_candles.Value
bullish_pullback = False
bearish_pullback = False
if close > equilibrium:
# The bearish trend that preceded this pullback.
if self._below_count > 0:
self._bear_trend_length = self._below_count
self._above_count += 1
self._below_count = 0
bearish_pullback = self._bear_trend_length >= trend_len and self._above_count <= max_pullback
if self._above_count > max_pullback:
self._bear_trend_length = 0
elif close < equilibrium:
if self._above_count > 0:
self._bull_trend_length = self._above_count
self._below_count += 1
self._above_count = 0
bullish_pullback = self._bull_trend_length >= trend_len and self._below_count <= max_pullback
if self._below_count > max_pullback:
self._bull_trend_length = 0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position != 0 and self._check_exit(candle, atr):
return
reverse = self._use_reverse.Value
go_long = bearish_pullback if reverse else bullish_pullback
go_short = bullish_pullback if reverse else bearish_pullback
mult = float(self._stop_multiplier.Value)
if go_long and self.Position <= 0:
self._stop_price = close - atr * mult
self._take_price = close + atr * mult
self.BuyMarket(self.Volume + abs(self.Position))
elif go_short and self.Position >= 0:
self._stop_price = close + atr * mult
self._take_price = close - atr * mult
self.SellMarket(self.Volume + abs(self.Position))
def _check_exit(self, candle, atr):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
big_candle = self._use_big_candle_exit.Value and body > atr * float(self._big_candle_multiplier.Value)
use_tp_sl = self._use_tp_sl.Value
if self.Position > 0:
if big_candle or (use_tp_sl and (low <= self._stop_price or high >= self._take_price)):
self.SellMarket(self.Position)
return True
elif big_candle or (use_tp_sl and (high >= self._stop_price or low <= self._take_price)):
self.BuyMarket(-self.Position)
return True
return False
def CreateClone(self):
return equilibrium_candles_pattern_strategy()