Открыть на GitHub

Стратегия JK Synchro

Обзор

JK Synchro — порт советника MetaTrader 5 "JK synchro" (MQL ID 2415) на платформу StockSharp. Алгоритм анализирует последние свечи, подсчитывая сколько из них закрылись ниже открытия и сколько выше. Преобладание одной стороны служит сигналом для открытия позиции. Перенос на StockSharp сохраняет торговую логику и добавляет типизированные параметры, встроенный риск-контроль и удобные средства журналирования.

Логика торговли

  1. Подписка на поток свечей, указанный параметром CandleType. Обработка выполняется только по завершённым свечам.
  2. Поддерживается скользящее окно длиной AnalysisPeriod. Для каждой свечи:
    • При Open > Close увеличивается счётчик медвежьих свечей.
    • При Open < Close увеличивается счётчик бычьих свечей.
    • Свечи с равными ценами открытия и закрытия игнорируются.
  3. После заполнения окна определяется доминирующее направление:
    • Медвежьи свечи > бычьих → готовим длинную позицию.
    • Бычьи свечи > медвежьих → готовим короткую позицию.
  4. Перед входом стратегия проверяет:
    • Готовность к торговле (IsFormedAndOnlineAndAllowTrading).
    • Попадание текущего часа в интервал StartHourEndHour (включительно).
    • Истечение паузы PauseBetweenTradesSeconds с момента последнего входа.
    • Что новая заявка не превысит ограничение по объёму MaxPositions * OrderVolume.
  5. Если появляется противоположный сигнал при наличии позиции, сначала закрывается текущая позиция, а открытие в новую сторону рассматривается со следующей свечи.
  6. Стоп-лосс, тейк-профит и трейлинг выражаются в пунктах (pips) и автоматически переводятся в ценовое смещение исходя из шага цены инструмента.

Управление рисками

  • Стоп-лосс / тейк-профит: задаются в пунктах, пересчитываются при изменении позиции и контролируются на каждой завершённой свече.
  • Трейлинг-стоп: активируется при положительных TrailingStopPips и TrailingStepPips. После прохождения прибыли на величину TrailingStop + TrailingStep стоп следует за ценой с заданным шагом.
  • Ограничение позиции: абсолютная величина позиции не превышает MaxPositions * OrderVolume.
  • Пауза между входами: время каждого исполнения фиксируется в OnPositionChanged, что гарантирует соблюдение интервала между сделками.

Параметры

Параметр Значение по умолчанию Описание
OrderVolume 0.1 Объём каждого рыночного ордера.
MaxPositions 10 Максимальное число лотов в одном направлении.
AnalysisPeriod 18 Количество свечей в скользящем окне для голосования.
PauseBetweenTradesSeconds 540 Пауза (в секундах) между входами.
StartHour 3 Час начала торговли (включительно).
EndHour 6 Час окончания торговли (включительно).
StopLossPips 50 Расстояние стоп-лосса в пунктах. 0 — отключить.
TakeProfitPips 150 Расстояние тейк-профита в пунктах. 0 — отключить.
TrailingStopPips 15 Дистанция трейлинг-стопа в пунктах. 0 — отключить.
TrailingStepPips 5 Дополнительный ход цены перед обновлением трейлинг-стопа. Должен быть > 0 при включённом трейлинге.
CandleType 15-минутные свечи Источник данных для расчётов.

Особенности реализации

  • Используется высокоуровневый API StockSharp: SubscribeCandles, .Bind, BuyMarket, SellMarket.
  • Таймстемпы входов фиксируются в OnPositionChanged, что позволяет точно повторить паузу между сделками, присутствовавшую в оригинальном советнике.
  • Размер пункта вычисляется по PriceStep и Decimals; для трёх- и пятизнаковых инструментов автоматически применяется множитель 10.
  • Выходы контролируются по минимуму и максимуму завершённой свечи, что упрощает тестирование и исключает реакции на незавершённые бары.
  • Логика трейлинг-стопа повторяет MQL-вариант: стоп начинает смещаться только после достаточного движения и никогда не откатывается назад.

Рекомендации по использованию

  1. Настройте OrderVolume и MaxPositions в соответствии с размером контракта и допустимой нагрузкой на депозит.
  2. Подбирайте AnalysisPeriod под выбранный таймфрейм: на младших таймфреймах чаще требуется больше свечей в окне.
  3. Установите StartHour и EndHour так, чтобы стратегия работала в наиболее ликвидные часы для выбранного инструмента.
  4. Протестируйте различные комбинации стопов, тейков и трейлинга — в оригинале параметры часто менялись под рыночные условия.

Отличия от версии MQL

  • В StockSharp применяется учёт чистой позиции: перед сменой направления текущая позиция закрывается, тогда как MetaTrader позволял держать разнонаправленные сделки одновременно.
  • Управление параметрами и журналирование выполняется средствами StockSharp, что облегчает оптимизацию и интеграцию с интерфейсом.
  • Трейлинг оценивается на закрытии свечи, что согласуется с другими портами стратегий и устраняет реакции на неполные данные.

Следуя этим рекомендациям, вы сможете запускать, анализировать и оптимизировать стратегию JK Synchro в экосистеме 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;
	}
}