The JK Synchro Strategy is a StockSharp port of the MetaTrader 5 expert advisor "JK synchro" (MQL ID 2415). The original robot counts how many of the most recent candles closed lower or higher than they opened and then opens a position in the direction that dominates. This port replicates the behaviour while adding strongly typed parameters, built-in risk management hooks, and rich logging through StockSharp.
Trading Logic
Subscribe to the candle source defined by CandleType and wait for finished candles.
Maintain a sliding window of AnalysisPeriod candles. For each candle:
Increment the bearish counter when Open > Close.
Increment the bullish counter when Open < Close.
Ignore doji candles where Open == Close.
Once the window is filled, check dominance:
If bearish candles outnumber bullish ones, prepare a long entry.
If bullish candles outnumber bearish ones, prepare a short entry.
Before entering a trade the strategy verifies:
The strategy is online and allowed to trade (IsFormedAndOnlineAndAllowTrading).
The current hour lies between StartHour and EndHour (inclusive).
The cooldown defined by PauseBetweenTradesSeconds has elapsed since the last entry.
Adding another lot would keep the net exposure within MaxPositions * OrderVolume.
When a signal appears while holding an opposite position, the strategy first closes that position and waits for the next candle before potentially entering in the new direction.
Protective stop-loss, take-profit, and trailing stop levels are expressed in pips and automatically translated into price offsets based on the instrument tick size.
Risk Management
Stop Loss / Take Profit: Optional levels defined in pips. They are recalculated on every position change and checked on each finished candle.
Trailing Stop: Activated when both TrailingStopPips and TrailingStepPips are positive. Once the trade moves in favour by at least TrailingStop + TrailingStep, the stop follows the price using the configured step.
Position Cap: The absolute net position cannot exceed MaxPositions * OrderVolume.
Entry Pause: The strategy records the timestamp of every fill and enforces a pause before opening another trade.
Parameters
Parameter
Default
Description
OrderVolume
0.1
Volume placed with every market order.
MaxPositions
10
Maximum number of lots allowed per direction.
AnalysisPeriod
18
Number of finished candles considered when counting bullish versus bearish moves.
PauseBetweenTradesSeconds
540
Cooldown in seconds after any entry before a new one can be opened.
StartHour
3
Start hour (inclusive) of the trading window, server time.
EndHour
6
End hour (inclusive) of the trading window, server time.
StopLossPips
50
Stop-loss distance expressed in pips. Set to 0 to disable.
TakeProfitPips
150
Take-profit distance in pips. Set to 0 to disable.
TrailingStopPips
15
Trailing stop distance in pips. Set to 0 to disable trailing.
TrailingStepPips
5
Extra distance in pips before the trailing stop is updated. Must be positive when trailing is enabled.
CandleType
15-minute time frame
Candle source used for all calculations.
Implementation Notes
High-level StockSharp API is used throughout (SubscribeCandles, .Bind, BuyMarket, SellMarket).
Entry timestamps are captured inside OnPositionChanged to implement the pause logic exactly like the original EA, which waited a fixed amount of time after each entry.
Pip size is derived from Security.PriceStep and Security.Decimals; for 3- or 5-digit instruments the multiplier is automatically adjusted.
Exits are handled on closed candles by comparing the high/low with the calculated stop and target levels.
Trailing stops mimic the MetaTrader logic: they start moving only after the profit exceeds TrailingStop + TrailingStep and never reverse.
Usage Tips
Align OrderVolume and MaxPositions with the contract size of your broker to keep exposure under control.
Choose AnalysisPeriod according to the candle timeframe. Shorter time frames usually require larger windows to avoid noise.
Adjust the trading window to match the active hours of the instrument (e.g., European session for EUR-based pairs).
Backtest different combinations of stop, target, and trailing settings—the original EA often ran with either fixed targets or trailing stops depending on market conditions.
Differences from the MQL Version
The StockSharp port uses a net exposure model. When switching direction the existing position is closed first, whereas the MetaTrader version could keep hedged positions.
Logging and parameter management leverage StockSharp facilities, making optimisation and UI integration easier.
The trailing stop is evaluated on finished candles, which is consistent with other StockSharp strategy ports and avoids reacting to incomplete bars.
With these considerations the JK Synchro strategy can be traded, analysed, and optimised directly within the StockSharp ecosystem.
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;
public class JkSynchroStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public JkSynchroStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 jk_synchro_strategy(Strategy):
def __init__(self):
super(jk_synchro_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(jk_synchro_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(jk_synchro_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return jk_synchro_strategy()