La Estrategia de JK Synchro es un port de StockSharp del asesor experto de MetaTrader 5 "JK synchro" (MQL ID 2415). El robot original cuenta cuántas de las velas más recientes cerraron por debajo o por encima de su apertura y luego abre una posición en la dirección dominante. Este port replica el comportamiento mientras añade parámetros fuertemente tipados, hooks de gestión de riesgo integrados y registro enriquecido a través de StockSharp.
Lógica de trading
Suscribirse a la fuente de velas definida por CandleType y esperar velas terminadas.
Mantener una ventana deslizante de AnalysisPeriod velas. Para cada vela:
Incrementar el contador bajista cuando Open > Close.
Incrementar el contador alcista cuando Open < Close.
Ignorar velas doji donde Open == Close.
Una vez que la ventana está llena, verificar la dominancia:
Si las velas bajistas superan a las alcistas, preparar una entrada larga.
Si las velas alcistas superan a las bajistas, preparar una entrada corta.
Antes de entrar en una operación, la estrategia verifica:
La estrategia está en línea y tiene permitido operar (IsFormedAndOnlineAndAllowTrading).
La hora actual está entre StartHour y EndHour (inclusive).
El enfriamiento definido por PauseBetweenTradesSeconds ha transcurrido desde la última entrada.
Agregar otro lote mantendría la exposición neta dentro de MaxPositions * OrderVolume.
Cuando aparece una señal mientras se mantiene una posición opuesta, la estrategia primero cierra esa posición y espera la siguiente vela antes de potencialmente entrar en la nueva dirección.
Los niveles de stop-loss protector, take-profit y trailing stop se expresan en pips y se traducen automáticamente en offsets de precio basados en el tamaño del tick del instrumento.
Gestión de riesgo
Stop Loss / Take Profit: Niveles opcionales definidos en pips. Se recalculan en cada cambio de posición y se verifican en cada vela terminada.
Trailing Stop: Activado cuando tanto TrailingStopPips como TrailingStepPips son positivos. Una vez que la operación se mueve a favor al menos por TrailingStop + TrailingStep, el stop sigue el precio usando el paso configurado.
Límite de posición: La posición neta absoluta no puede superar MaxPositions * OrderVolume.
Pausa de entrada: La estrategia registra el timestamp de cada ejecución y aplica una pausa antes de abrir otra operación.
Parámetros
Parámetro
Predeterminado
Descripción
OrderVolume
0.1
Volumen colocado con cada orden de mercado.
MaxPositions
10
Número máximo de lotes permitidos por dirección.
AnalysisPeriod
18
Número de velas terminadas consideradas al contar movimientos alcistas versus bajistas.
PauseBetweenTradesSeconds
540
Enfriamiento en segundos después de cualquier entrada antes de que se pueda abrir una nueva.
StartHour
3
Hora de inicio (inclusive) de la ventana de trading, hora del servidor.
EndHour
6
Hora de fin (inclusive) de la ventana de trading, hora del servidor.
StopLossPips
50
Distancia de stop-loss expresada en pips. Establecer en 0 para deshabilitar.
TakeProfitPips
150
Distancia de take-profit en pips. Establecer en 0 para deshabilitar.
TrailingStopPips
15
Distancia del trailing stop en pips. Establecer en 0 para deshabilitar el trailing.
TrailingStepPips
5
Distancia adicional en pips antes de que se actualice el trailing stop. Debe ser positivo cuando el trailing está habilitado.
CandleType
Marco temporal de 15 minutos
Fuente de velas usada para todos los cálculos.
Notas de implementación
Se usa la API de alto nivel de StockSharp en todo momento (SubscribeCandles, .Bind, BuyMarket, SellMarket).
Los timestamps de entrada se capturan dentro de OnPositionChanged para implementar la lógica de pausa exactamente como el EA original, que esperaba una cantidad fija de tiempo después de cada entrada.
El tamaño del pip se deriva de Security.PriceStep y Security.Decimals; para instrumentos de 3 o 5 dígitos el multiplicador se ajusta automáticamente.
Las salidas se manejan en velas cerradas comparando el máximo/mínimo con los niveles calculados de stop y objetivo.
Los trailing stops imitan la lógica de MetaTrader: comienzan a moverse solo después de que el beneficio supera TrailingStop + TrailingStep y nunca se revierten.
Consejos de uso
Alinear OrderVolume y MaxPositions con el tamaño del contrato de su broker para mantener la exposición bajo control.
Elegir AnalysisPeriod según el marco temporal de las velas. Los marcos temporales más cortos generalmente requieren ventanas más grandes para evitar el ruido.
Ajustar la ventana de trading para que coincida con las horas activas del instrumento (p. ej., sesión europea para pares basados en EUR).
Hacer backtest de diferentes combinaciones de stop, objetivo y configuraciones de trailing: el EA original a menudo se ejecutaba con objetivos fijos o trailing stops dependiendo de las condiciones del mercado.
Diferencias con la versión MQL
El port de StockSharp usa un modelo de exposición neta. Al cambiar de dirección, la posición existente se cierra primero, mientras que la versión de MetaTrader podía mantener posiciones cubiertas.
El registro y la gestión de parámetros aprovechan las instalaciones de StockSharp, haciendo más fácil la optimización y la integración con la interfaz de usuario.
El trailing stop se evalúa en velas terminadas, lo que es coherente con otros ports de estrategias de StockSharp y evita reaccionar a barras incompletas.
Con estas consideraciones, la estrategia JK Synchro puede ser operada, analizada y optimizada directamente dentro del ecosistema 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()