The Candle Strategy is a direct port of the classic MT5 expert "Candle.mq5". It evaluates the color of every finished candle on the selected timeframe and keeps the position aligned with the most recent close. Bullish candles drive the strategy long, bearish candles drive it short, and flat candles leave the position untouched. Risk is controlled by pip-based take-profit and trailing-stop distances that are converted to absolute prices through the instrument tick size.
The strategy only reacts after a candle is fully formed to avoid mid-bar noise. A mandatory lookback (MinBars * 2 finished candles) validates that the chart contains sufficient history, while a configurable cooldown waits between trade operations. This produces a faithful StockSharp implementation of the original MetaTrader logic without relying on low-level series access.
Trading Logic
Preparation
Process candles provided by CandleType; no other data sources are required.
Wait until at least 2 * MinBars finished candles have been processed before allowing entries.
Trade only when the strategy is online, formed, and allowed to execute orders.
Enforce the TradeCooldown interval (default 10 seconds) between any two trade operations.
Entry and Reversal Rules
Flat state:
Enter long (BuyMarket) when a candle closes above its open.
Enter short (SellMarket) when a candle closes below its open.
Existing position:
If a long position faces a bearish candle, sell |Position| + Volume to close and immediately reverse to a short position of size Volume.
If a short position faces a bullish candle, buy |Position| + Volume to close and immediately reverse to a long position of size Volume.
Neutral candles:
When the close equals the open, no manual action is taken; only the protective orders may exit the trade.
Risk Management and Exits
StartProtection attaches a take-profit and trailing stop measured in pips. The strategy multiplies each pip value by (PriceStep * 10) to match the MetaTrader adjustment for 3- and 5-digit quotes.
The trailing stop is activated only when TrailingStopPips is greater than zero; it follows price automatically once the trade moves in the favorable direction.
The take-profit closes the position when the configured distance is reached. Either protective level cancels the opposite order after execution.
Manual reversals caused by candle color also flatten the previous exposure before opening the new position.
Parameters
CandleType – timeframe of the candle series to analyze (default: 15-minute candles).
TakeProfitPips – distance to the take-profit target in pips (default: 50).
TrailingStopPips – trailing stop distance in pips (default: 30).
MinBars – minimum bar count required before the first trade (default: 26; strategy waits for 52 finished candles).
TradeCooldown – waiting period after any trade action (default: 10 seconds).
Set the strategy Volume property to the desired order size. When the market reverses, the strategy automatically submits enough volume to both exit the previous position and establish the new one.
Implementation Notes
Only finished candles (CandleStates.Finished) are processed. This mirrors the MetaTrader expert, which relied on closed bar values obtained via CopyOpen/CopyClose.
The code uses StockSharp's high-level API: SubscribeCandles for data, Bind to process incoming bars, and BuyMarket/SellMarket for order execution.
Protective orders are managed by StartProtection, so no manual stop-limit order bookkeeping is necessary.
The pip size computation PriceStep * 10 reproduces the MQL "digits adjust" logic for symbols quoted with 3 or 5 decimal places.
Because entries are triggered by the most recent candle body, the strategy tends to stay in the market continuously, alternating sides whenever candle color flips.
Adjust the pip distances, cooldown, and timeframe to match the instrument being traded. The default configuration mirrors the original MT5 sample but can be optimized through StockSharp's parameter framework.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Candle color reversal strategy with pip-based protection and trade cooldown.
/// </summary>
public class CandleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<int> _minBars;
private readonly StrategyParam<TimeSpan> _tradeCooldown;
private decimal _pipSize;
private int _finishedCandles;
private DateTimeOffset _nextAllowedTime;
/// <summary>
/// Initializes a new instance of the <see cref="CandleStrategy"/>.
/// </summary>
public CandleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for candle evaluation", "General");
_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
.SetDisplay("Take Profit (pips)", "Distance to take profit in pips", "Risk")
.SetGreaterThanZero();
_trailingStopPips = Param(nameof(TrailingStopPips), 30m)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
.SetGreaterThanZero();
_minBars = Param(nameof(MinBars), 26)
.SetDisplay("Minimum Bars", "History length required before trading", "General")
.SetGreaterThanZero();
_tradeCooldown = Param(nameof(TradeCooldown), TimeSpan.FromSeconds(10))
.SetDisplay("Trade Cooldown", "Waiting time after each trade", "Risk");
}
/// <summary>
/// Candle type and timeframe used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum number of bars required on the chart.
/// </summary>
public int MinBars
{
get => _minBars.Value;
set => _minBars.Value = value;
}
/// <summary>
/// Cooldown between consecutive trading operations.
/// </summary>
public TimeSpan TradeCooldown
{
get => _tradeCooldown.Value;
set => _tradeCooldown.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_finishedCandles = 0;
_nextAllowedTime = DateTimeOffset.MinValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = (Security?.PriceStep ?? 1m) * 10m;
Unit takeProfit = TakeProfitPips > 0m && _pipSize > 0m
? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
: null;
Unit trailingStop = TrailingStopPips > 0m && _pipSize > 0m
? new Unit(TrailingStopPips * _pipSize, UnitTypes.Absolute)
: null;
// Enable automatic protective orders and trailing stop handling.
if (trailingStop != null || takeProfit != null)
StartProtection(takeProfit, trailingStop);
// Subscribe to candle data for the configured timeframe.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
// Draw candles and executions on the chart if visualization is available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Skip unfinished candles to work only with final prices.
if (candle.State != CandleStates.Finished)
return;
_finishedCandles++;
// Wait until the chart has enough historical data.
if (_finishedCandles < MinBars * 2)
return;
var time = candle.CloseTime;
// Enforce cooldown between trading operations.
if (time < _nextAllowedTime)
return;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
var tradeExecuted = false;
if (Position > 0 && isBearish)
{
// Reverse from long to short when a bearish candle appears.
var volume = Math.Abs(Position) + Volume;
if (volume > 0m)
{
SellMarket();
tradeExecuted = true;
}
}
else if (Position < 0 && isBullish)
{
// Reverse from short to long when a bullish candle appears.
var volume = Math.Abs(Position) + Volume;
if (volume > 0m)
{
BuyMarket();
tradeExecuted = true;
}
}
else if (Position == 0)
{
if (isBullish)
{
// Enter long on bullish close.
if (Volume > 0m)
{
BuyMarket();
tradeExecuted = true;
}
}
else if (isBearish)
{
// Enter short on bearish close.
if (Volume > 0m)
{
SellMarket();
tradeExecuted = true;
}
}
}
if (tradeExecuted)
{
_nextAllowedTime = time + TradeCooldown;
}
}
}
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, DateTimeOffset
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
class candle_strategy(Strategy):
"""
Candle color reversal strategy with pip-based protection and trade cooldown.
"""
def __init__(self):
super(candle_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used for candle evaluation", "General")
self._take_profit_pips = self.Param("TakeProfitPips", 50.0) \
.SetDisplay("Take Profit (pips)", "Distance to take profit in pips", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 30.0) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
self._min_bars = self.Param("MinBars", 26) \
.SetDisplay("Minimum Bars", "History length required before trading", "General")
self._trade_cooldown = self.Param("TradeCooldown", TimeSpan.FromSeconds(10)) \
.SetDisplay("Trade Cooldown", "Waiting time after each trade", "Risk")
self._pip_size = 0.0
self._finished_candles = 0
self._next_allowed_time = DateTimeOffset.MinValue
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def take_profit_pips(self):
return self._take_profit_pips.Value
@take_profit_pips.setter
def take_profit_pips(self, value):
self._take_profit_pips.Value = value
@property
def trailing_stop_pips(self):
return self._trailing_stop_pips.Value
@trailing_stop_pips.setter
def trailing_stop_pips(self, value):
self._trailing_stop_pips.Value = value
@property
def min_bars(self):
return self._min_bars.Value
@min_bars.setter
def min_bars(self, value):
self._min_bars.Value = value
@property
def trade_cooldown(self):
return self._trade_cooldown.Value
@trade_cooldown.setter
def trade_cooldown(self, value):
self._trade_cooldown.Value = value
def OnReseted(self):
super(candle_strategy, self).OnReseted()
self._pip_size = 0.0
self._finished_candles = 0
self._next_allowed_time = DateTimeOffset.MinValue
def OnStarted2(self, time):
super(candle_strategy, self).OnStarted2(time)
self._pip_size = (self.Security.PriceStep if self.Security.PriceStep is not None else 1.0) * 10.0
tp = None
trailing = None
if self.take_profit_pips > 0 and self._pip_size > 0:
tp = Unit(self.take_profit_pips * self._pip_size, UnitTypes.Absolute)
if self.trailing_stop_pips > 0 and self._pip_size > 0:
trailing = Unit(self.trailing_stop_pips * self._pip_size, UnitTypes.Absolute)
if trailing is not None or tp is not None:
self.StartProtection(tp, trailing)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
self._finished_candles += 1
if self._finished_candles < self.min_bars * 2:
return
close_time = candle.CloseTime
if self._next_allowed_time != DateTimeOffset.MinValue:
try:
if close_time < self._next_allowed_time:
return
except:
if DateTimeOffset(close_time) < self._next_allowed_time:
return
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
trade_executed = False
if self.Position > 0 and is_bearish:
self.SellMarket()
trade_executed = True
elif self.Position < 0 and is_bullish:
self.BuyMarket()
trade_executed = True
elif self.Position == 0:
if is_bullish:
self.BuyMarket()
trade_executed = True
elif is_bearish:
self.SellMarket()
trade_executed = True
if trade_executed:
self._next_allowed_time = close_time + self.trade_cooldown
def CreateClone(self):
return candle_strategy()