A Estratégia de JK Synchro é um port StockSharp do consultor especialista MetaTrader 5 "JK synchro" (MQL ID 2415). O robô original conta quantas das velas mais recentes fecharam abaixo ou acima de suas aberturas e então abre uma posição na direção dominante. Este port replica o comportamento enquanto adiciona parâmetros fortemente tipados, hooks de gerenciamento de risco integrados e registro detalhado via StockSharp.
Lógica de trading
Subscrever à fonte de velas definida por CandleType e aguardar velas concluídas.
Manter uma janela deslizante de AnalysisPeriod velas. Para cada vela:
Incrementar o contador baixista quando Open > Close.
Incrementar o contador altista quando Open < Close.
Ignorar velas doji onde Open == Close.
Quando a janela estiver preenchida, verificar a dominância:
Se as velas baixistas superam as altistas, preparar uma entrada comprada.
Se as velas altistas superam as baixistas, preparar uma entrada vendida.
Antes de entrar em uma operação, a estratégia verifica:
A estratégia está online e tem permissão para negociar (IsFormedAndOnlineAndAllowTrading).
A hora atual está entre StartHour e EndHour (inclusive).
O resfriamento definido por PauseBetweenTradesSeconds decorreu desde a última entrada.
Adicionar outro lote manteria a exposição líquida dentro de MaxPositions * OrderVolume.
Quando um sinal aparece enquanto se mantém uma posição oposta, a estratégia primeiro fecha essa posição e aguarda a próxima vela antes de potencialmente entrar na nova direção.
Os níveis protetores de stop-loss, take-profit e trailing stop são expressos em pips e automaticamente traduzidos em offsets de preço com base no tamanho do tick do instrumento.
Gerenciamento de risco
Stop Loss / Take Profit: Níveis opcionais definidos em pips. São recalculados a cada mudança de posição e verificados a cada vela concluída.
Trailing Stop: Ativado quando tanto TrailingStopPips quanto TrailingStepPips são positivos. Uma vez que a operação se move a favor pelo menos TrailingStop + TrailingStep, o stop acompanha o preço usando o passo configurado.
Limite de posição: A posição líquida absoluta não pode exceder MaxPositions * OrderVolume.
Pausa de entrada: A estratégia registra o timestamp de cada execução e aplica uma pausa antes de abrir outra operação.
Parâmetros
Parâmetro
Padrão
Descrição
OrderVolume
0.1
Volume colocado com cada ordem de mercado.
MaxPositions
10
Número máximo de lotes permitidos por direção.
AnalysisPeriod
18
Número de velas concluídas consideradas ao contar movimentos altistas versus baixistas.
PauseBetweenTradesSeconds
540
Resfriamento em segundos após qualquer entrada antes de uma nova poder ser aberta.
StartHour
3
Hora de início (inclusive) da janela de trading, hora do servidor.
EndHour
6
Hora de fim (inclusive) da janela de trading, hora do servidor.
StopLossPips
50
Distância de stop-loss expressa em pips. Definir como 0 para desabilitar.
TakeProfitPips
150
Distância de take-profit em pips. Definir como 0 para desabilitar.
TrailingStopPips
15
Distância do trailing stop em pips. Definir como 0 para desabilitar o trailing.
TrailingStepPips
5
Distância adicional em pips antes de o trailing stop ser atualizado. Deve ser positivo quando o trailing está habilitado.
CandleType
Período de 15 minutos
Fonte de velas usada para todos os cálculos.
Notas de implementação
A API de alto nível do StockSharp é usada em todo momento (SubscribeCandles, .Bind, BuyMarket, SellMarket).
Os timestamps de entrada são capturados dentro de OnPositionChanged para implementar a lógica de pausa exatamente como o EA original, que aguardava um tempo fixo após cada entrada.
O tamanho do pip é derivado de Security.PriceStep e Security.Decimals; para instrumentos de 3 ou 5 dígitos o multiplicador é ajustado automaticamente.
As saídas são tratadas em velas fechadas comparando o máximo/mínimo com os níveis calculados de stop e alvo.
Os trailing stops imitam a lógica do MetaTrader: começam a mover somente após o lucro exceder TrailingStop + TrailingStep e nunca se revertem.
Dicas de uso
Alinhar OrderVolume e MaxPositions com o tamanho do contrato do seu broker para manter a exposição sob controle.
Escolher AnalysisPeriod de acordo com o período das velas. Períodos mais curtos geralmente requerem janelas maiores para evitar ruído.
Ajustar a janela de trading para coincidir com as horas ativas do instrumento (ex.: sessão europeia para pares baseados em EUR).
Fazer backtest de diferentes combinações de stop, alvo e configurações de trailing — o EA original frequentemente operava com alvos fixos ou trailing stops dependendo das condições de mercado.
Diferenças em relação à versão MQL
O port StockSharp usa um modelo de exposição líquida. Ao trocar de direção, a posição existente é fechada primeiro, enquanto a versão MetaTrader podia manter posições hedgeadas.
O registro e gerenciamento de parâmetros aproveitam as facilidades do StockSharp, tornando a otimização e integração com a interface mais fáceis.
O trailing stop é avaliado em velas concluídas, o que é consistente com outros ports de estratégias StockSharp e evita reagir a barras incompletas.
Com essas considerações, a estratégia JK Synchro pode ser negociada, analisada e otimizada diretamente dentro do ecossistema StockSharp.
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()