The Candle Patterns Test Strategy is a StockSharp high-level conversion of the original MetaTrader 5 expert advisor CandlePatternsTest EA. The strategy scans completed candles for a curated list of classical Japanese candlestick formations and reacts by entering long or short positions when bullish or bearish structures appear. The conversion focuses on the discretionary pattern logic of the source robot while leveraging StockSharp risk controls and data subscription API.
Trading Logic
Candle subscription – the strategy subscribes to the configured candle type and waits for finished bars before running pattern recognition.
Average body filter – a simple moving average of candle bodies acts as dynamic normalization. Only patterns whose constituent candles exceed this average are considered valid, mirroring the MQL implementation's AvgBody function.
Pattern recognition – the detector checks for:
Three White Soldiers / Three Black Crows
Piercing Line / Dark Cloud Cover
Morning Doji Star / Evening Doji Star
Bullish and Bearish Engulfing
Bullish and Bearish Harami
Meeting Lines
Entry management – once a bullish pattern is confirmed the strategy opens a market buy order; bearish patterns trigger a market sell order. Opposite signals automatically reverse the current position.
Exit management – protective stop-loss and take-profit levels are derived from the average candle body and tracked on each finished candle. If price touches either threshold the position is closed.
Parameters
Parameter
Description
CandleType
Data type of candles to subscribe to (default: 1-hour time frame).
AverageBodyPeriod
Number of candles used for the average body length. Controls pattern normalization.
EnableBullishPatterns
Enables or disables long entries.
EnableBearishPatterns
Enables or disables short entries.
StopLossFactor
Multiplier applied to the average body for stop-loss distance.
TakeProfitFactor
Multiplier applied to the average body for take-profit distance.
All parameters are exposed through StrategyParam<T> to support GUI configuration and optimizer runs.
Charting
When a chart area is available the strategy plots:
The subscribed candles
The close-price moving average used for trend context
Executed trades for visual verification
Differences from the Original EA
News filters, time windows, hedging toggles, and trailing grid management present in the original MQ5 file are intentionally omitted to focus on the candlestick pattern core.
Risk management is simplified to a symmetric stop/target model derived from candle volatility.
The StockSharp version uses the framework's position management and BuyMarket/SellMarket helpers instead of manual order tickets.
Usage Notes
Set the CandleType parameter to align with the market session you want to analyze; higher time frames produce fewer but stronger signals.
Adjust AverageBodyPeriod so that the average body approximates recent volatility. A smaller value reacts faster but may increase noise.
StopLossFactor and TakeProfitFactor can be optimized to match the instrument's risk profile.
Requirements
StockSharp environment with market data feed capable of generating the configured candle type.
The strategy expects sequential, non-overlapping candle series. Ensure the selected board supports regular bar updates.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Candle Patterns Test strategy: Doji + trend reversal.
/// Buys on doji in downtrend (below EMA), sells on doji in uptrend (above EMA).
/// </summary>
public class CandlePatternsTestStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _signalCooldownCandles;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public CandlePatternsTestStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter", "Indicators");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candlesSinceTrade = SignalCooldownCandles;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var range = candle.HighPrice - candle.LowPrice;
if (range <= 0) return;
// Doji: body is very small relative to range
var isDoji = body < range * 0.05m;
var close = candle.ClosePrice;
if (isDoji && close < emaValue && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (isDoji && close > emaValue && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class candle_patterns_test_strategy(Strategy):
def __init__(self):
super(candle_patterns_test_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._ema_period = self.Param("EmaPeriod", 50)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(candle_patterns_test_strategy, self).OnReseted()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(candle_patterns_test_strategy, self).OnStarted2(time)
self._candles_since_trade = self.SignalCooldownCandles
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self._process_candle).Start()
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
range_val = float(candle.HighPrice) - float(candle.LowPrice)
if range_val <= 0:
return
is_doji = body < range_val * 0.05
close = float(candle.ClosePrice)
ema_val = float(ema_value)
if is_doji and close < ema_val and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif is_doji and close > ema_val and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return candle_patterns_test_strategy()