The Synchronized Hour Breakout Strategy is a StockSharp port of the MetaTrader 4 expert advisor JK_sinkhro1. It analyses the balance of bullish and bearish candles during the recent trading window and only trades during two carefully selected synchronization hours (by default 19:00 and 22:00 plus an offset). The strategy focuses on capturing directional breakouts while enforcing conservative risk management rules similar to the original EA.
Trading Logic
Works on the candle series selected by the Candle Type parameter (default: 1-hour candles).
Maintains a sliding window of the latest Analysis Period completed candles and counts how many closed bullish vs. bearish.
When the bullish count exceeds the bearish count, the strategy prepares for a long breakout during the first synchronization hour (22 + Hour Offset).
When the bearish count exceeds the bullish count, it prepares for a short breakout during the second synchronization hour (19 + Hour Offset).
Signals are only valid within the first five minutes of the hour so that the order is synchronized with the new bar open, as in the MQL original.
New trades are ignored if there are already Max Active Orders registered or an open position is present.
Risk Management and Trade Management
Positions are opened with either a fixed lot size (Fixed Volume) or a risk-based size using the account cash and Risk % parameter. The risk model divides the permitted cash risk by the stop distance in price steps to approximate the behaviour of the source EA.
Every position uses three layers of exit logic:
A primary take-profit at Take Profit (pts) from the entry price.
A secondary faster take-profit at Secondary TP (pts) to mimic the early manual close in the original code.
A hard stop-loss at Stop Loss (pts) below/above the entry price.
Optional trailing stop: once the price advances more than Trailing Stop (pts), the trailing threshold follows the favourable extreme and closes the position if price retreats beyond the trailing distance.
Position state is reset after every full exit to prepare for the next synchronization window.
Parameters
Parameter
Description
Take Profit (pts)
Primary take-profit distance in security price steps.
Secondary TP (pts)
Faster take-profit distance triggered before the main target.
Stop Loss (pts)
Stop-loss distance measured in price steps.
Trailing Stop (pts)
Trailing stop distance; set to 0 to disable.
Analysis Period
Number of recent candles inspected when counting bullish/bearish closes.
Hour Offset
Offset added to the original 19:00 and 22:00 trading hours.
Max Active Orders
Maximum number of simultaneously active orders allowed before new entries are blocked.
Fixed Volume
Trade volume used when risk-based sizing is disabled.
Use Risk Volume
Enables dynamic position sizing based on portfolio cash and stop distance.
Risk %
Percentage of portfolio cash risked per trade in risk-based mode.
Candle Type
Candle type/timeframe used for calculations and signal generation.
Usage Notes
The default configuration emulates the MetaTrader version that traded EURUSD during the New York session; adjust the hour offset to match your broker/server time zone.
Ensure the security definition provides accurate PriceStep, VolumeStep, and MinVolume values so that the risk-based position sizing can align volumes with the exchange lot increments.
Because the strategy relies on candle close data, attach it to a history provider or live data feed that can deliver the selected candle series with minimal delay.
The trailing exit uses close prices from finished candles, which closely matches the tick-based trailing logic from the source EA while remaining compatible with StockSharp's high-level API.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class SynchronizedHourBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevEma;
private bool _hasPrev;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public SynchronizedHourBreakoutStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14).SetDisplay("ATR Period", "ATR lookback", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevEma = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_prevEma = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal atr)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevClose = close;
_prevEma = ema;
return;
}
if (_prevClose <= _prevEma && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevClose >= _prevEma && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevClose = close;
_prevEma = ema;
}
}
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 synchronized_hour_breakout_strategy(Strategy):
def __init__(self):
super(synchronized_hour_breakout_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR lookback", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(synchronized_hour_breakout_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(synchronized_hour_breakout_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self.process_candle).Start()
def process_candle(self, candle, ema, atr):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = close
self._prev_ema = ema_val
return
if self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_close = close
self._prev_ema = ema_val
def CreateClone(self):
return synchronized_hour_breakout_strategy()