The Expert610 Breakout strategy is a C# port of the MetaTrader 4 expert advisor Expert610.mq4. The original robot waits for a
wide candle and then parks both a buy stop and a sell stop order around the previous bar. Position size is derived from the
percentage of free capital the trader is willing to risk, and the stop-loss/take-profit distances are expressed in pips. This
StockSharp version mirrors that behaviour using the high-level API while exposing every tuning knob as a strategy parameter.
Trading Logic
Data collection
The strategy subscribes to a configurable candle type and stores the most recent finished bar.
Order book updates are monitored to estimate the current bid/ask spread. When no depth is available the spread contribution
defaults to zero, reproducing the original EA behaviour on brokers without live spreads.
Volatility filter
The previous candle high minus the current close and the current close minus the previous low must both exceed
ThresholdPips (converted to absolute price units).
The current candle open must lie strictly below the prior high to allow a buy setup and strictly above the prior low to
allow a sell setup. When both conditions hold the algorithm stages symmetric pending orders.
Order placement
Buy stops are placed at previous high + BreakoutOffset + spread, matching the MT4 code where the ask price is used.
Sell stops are placed at previous low - BreakoutOffset, also staying faithful to the original script that ignores the
spread on the bid side.
Only one pair of pending orders may be active at any time. If an order is already working the new signals are skipped.
Risk management
The lot size is derived from free capital (Portfolio.CurrentValue - Portfolio.BlockedValue) multiplied by
RiskPercent / 100. The amount is rounded to RoundingDigits and converted into lots using the same heuristic as the MT4
code: lot = risk / stopPips * 0.1, which assumes one pip of a 0.1 lot equals one unit of account currency.
The computed lot is aligned to exchange limits and the MinimumVolume parameter before being sent to the venue.
StartProtection attaches price-based stops and targets to every resulting position, so that fills immediately receive the
configured StopLossPips and TakeProfitPips offsets.
Parameters
Name
Description
Default
Notes
RoundingDigits
Decimal places used when rounding risk and volume calculations.
2
Must be non-negative.
RiskPercent
Percentage of free capital risked on each entry.
1
Set to 0 to disable dynamic sizing and fall back to MinimumVolume.
MinimumVolume
Hard lower bound for pending order volume.
0.1
Also respects the security's MinVolume and VolumeStep.
ThresholdPips
Minimum distance from the last close to the prior candle extremes.
5
Measured in pips and converted with the detected pip size.
BreakoutOffsetPips
Buffer added beyond the previous high/low when staging orders.
2
Applied symmetrically to both sides.
StopLossPips
Stop-loss distance attached to filled orders.
5
Expressed in pips and sent to StartProtection.
TakeProfitPips
Take-profit distance attached to filled orders.
10
Expressed in pips; set to 0 to disable the target.
CandleType
Candle series used to evaluate the breakout.
1 hour time frame
Accepts any DataType supported by StockSharp.
Implementation Notes
The pip size is derived from the instrument's PriceStep and Decimals (5-digit and 3-digit Forex symbols receive a ×10
adjustment) to keep the conversion identical to the MQL4 formula.
Order size rounding honours VolumeStep, clamps to MinVolume/MaxVolume, and finally enforces the strategy-level
MinimumVolume so that resulting requests are always tradable.
Spread compensation uses the best bid/ask extracted from the subscribed order book. This produces the same entry price as the
MT4 implementation when the platform provides live spreads and gracefully degrades otherwise.
Pending orders are cleared from the internal state once StockSharp reports them as filled, cancelled, or failed, allowing the
logic to submit fresh orders on the next qualified candle.
Differences vs. the MQL Version
The original EA rounded both risk and volume using Digits2Round. The port keeps that feature but additionally aligns the
result to exchange-specific volume steps.
Instead of attaching protective prices directly to the pending orders, the StockSharp strategy relies on StartProtection so
every filled position automatically receives stop-loss and take-profit orders.
Portfolio information replaces the MT4 functions AccountBalance() and AccountMargin() to obtain free capital; if this data
is unavailable the strategy gracefully falls back to MinimumVolume sizing.
All calculations operate on finished candles only, preventing intra-bar repainting and matching the start() tick-based loop
once the bar closes.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Expert610 Breakout: Previous candle high/low breakout with ATR stops.
/// </summary>
public class Expert610BreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
public Expert610BreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_prevHigh == 0 || _prevLow == 0 || atrVal <= 0)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
return;
}
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > _prevHigh && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < _prevLow && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class expert610_breakout_strategy(Strategy):
"""
Expert610 Breakout: Previous candle high/low breakout with EMA trend filter and ATR stops.
"""
def __init__(self):
super(expert610_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Trend filter", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(expert610_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(expert610_breakout_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema_val)
atr_val = float(atr_val)
if self._prev_high == 0.0 or self._prev_low == 0.0 or atr_val <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close >= self._entry_price + atr_val * 2.5 or close <= self._entry_price - atr_val * 1.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - atr_val * 2.5 or close >= self._entry_price + atr_val * 1.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > self._prev_high and close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ema_val:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return expert610_breakout_strategy()