Открыть на GitHub

Стратегия KSRobot 1.5

Обзор

KSRobot 1.5 — это портированная на C# реализация эксперта MetaTrader 4 KSRobot_1_5_h1_v1.mq4. Версия для StockSharp сохраняет оригинальную идею: входить по пробою цены через линию Kijun-sen, подтверждая сигнал 20-периодной линейно-взвешенной скользящей средней (LWMA), и одновременно ограничивать торговлю временным окном и каскадом защитных правил. По умолчанию расчёты ведутся на 30-минутных свечах, однако тип данных можно изменить через параметр.

Данные и индикаторы

  • Ichimoku с периодами Tenkan/Kijun/Senkou Span B, равными 6/12/24.
  • Линейно-взвешенная скользящая средняя (LWMA) длиной 20 баров для оценки наклона и фильтра минимальной дистанции.
  • Свечи выбранного таймфрейма через параметр CandleType (по умолчанию M30).

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

Алгоритм для лонга

  1. Свеча должна взаимодействовать с линией Kijun снизу вверх: либо открыться ниже и закрыться выше, либо предыдущий Close был ниже, а текущий закрывается выше, либо минимум свечи касается уровня.
  2. Текущий Kijun не падает относительно значения два бара назад — это предотвращает сделки против свежего снижения базовой линии.
  3. LWMA находится минимум на MaFilterPips (в пересчёте в цену) ниже Kijun, как и требовалось в оригинальном советнике.
  4. Наклон LWMA положительный: текущее значение больше предыдущего.
  5. Условие фиксируется как «ожидающий» лонг. Одновременно может быть активен только один сигнал (реализация флагов longcross/shortcross из MQL).
  6. Когда все критерии выполнены и чистая позиция не лонговая, отправляется рыночная покупка. Цена входа сохраняется для дальнейшего управления стопом, безубытком и трейлингом.

Алгоритм для шорта

Условия зеркальные:

  1. Свеча взаимодействует с Kijun сверху вниз (открылась выше и закрылась ниже, предыдущий Close выше, а текущий ниже, либо максимум коснулся уровня).
  2. Kijun плоский либо ниже значения двух баров назад.
  3. LWMA располагается на MaFilterPips выше Kijun.
  4. Наклон LWMA отрицательный по сравнению с предыдущим баром.
  5. Ожидающий шорт очищается при появлении лонгового сигнала, как в оригинальном эксперте.
  6. Если условия соблюдены и нет чистой короткой позиции, стратегия продаёт по рынку.

Правила выхода и управление риском

  • Торговое окно — новые сделки рассматриваются только если время открытия свечи попадает в диапазон [TradingStartHour, TradingEndHour) (по умолчанию 07:00–19:00 по времени площадки).
  • Начальный стоп-лосс — устанавливается на StopLossPips ниже/выше цены входа (значение переводится в цену по шагу инструмента). Ноль отключает стоп.
  • Перенос в безубыток — как только плавающая прибыль превышает BreakEvenPips, стоп переносится на цену входа плюс один пункт для лонга (минус один для шорта), повторяя логику "BE+1" из MT4.
  • Трейлинг-стоп — после движения цены на TrailingStopPips в прибыльную сторону стоп подтягивается, сохраняя заданную дистанцию.
  • Тейк-профит — опциональная цель TakeProfitPips. Ноль отключает уровень.
  • Выход по наклону — если LWMA разворачивается против позиции до того, как стоп пересёк точку входа, позиция закрывается сразу (аналог правила "MA wrong way").
  • Приоритет уровней — при одновременном достижении стопа и тейка приоритет отдаётся стоп-лоссу, чтобы быть консервативными на свечных данных.

Параметры

Параметр Значение по умолчанию Описание
TenkanPeriod 6 Период Tenkan-sen индикатора Ichimoku.
KijunPeriod 12 Период Kijun-sen, основной триггер.
SenkouSpanBPeriod 24 Период Senkou Span B.
LwmaPeriod 20 Длина подтверждающей LWMA.
MaFilterPips 6 Минимальная дистанция между LWMA и Kijun в пунктах.
StopLossPips 50 Дистанция начального стоп-лосса.
BreakEvenPips 9 Прибыль для перевода стопа в безубыток.
TrailingStopPips 10 Дистанция трейлинг-стопа после выхода в плюс.
TakeProfitPips 120 Опциональный тейк-профит.
TradingStartHour 7 Час начала торговли (включительно).
TradingEndHour 19 Час окончания торговли (исключительно).
CandleType Таймфрейм 30 минут Тип данных для подписки на свечи.

Все параметры в пунктах переводятся в цену через Security.PriceStep (или MinPriceStep). Для инструментов с тремя или пятью знаками после запятой применяется множитель ×10, чтобы имитировать стандартный FX-пипс.

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

  • Ichimoku и LWMA подключаются одной подпиской SubscribeCandles().BindEx(...), что избавляет от ручных коллекций данных.
  • Ожидающие уровни повторяют логику longcross/shortcross, очищаясь после фактического входа.
  • После открытия сделки все защитные уровни кешируются, поэтому безубыток и трейлинг работают на свечных данных даже без модификации заявок.
  • StartProtection вызывается с нулевыми параметрами — вся защита реализована вручную, как в MQL-версии.
  • Используются только рыночные приказы. Комбинация лимит/маркет из MT4 опиралась на поток Bid/Ask и не воспроизводится на свечных данных.

Использование

  1. Создайте экземпляр стратегии, назначьте Security, Portfolio, Volume и запустите его в среде StockSharp.
  2. При необходимости скорректируйте параметры в пунктах под конкретный инструмент. Оптимальные значения из комментариев MQL (GBPUSD, EURUSD) можно ввести перед запуском.
  3. Отслеживайте журнал: входы, переход в безубыток, трейлинг и аварийные выходы логируются через LogInfo.
  4. Подключите создаваемую область графика (свечи, Ichimoku, LWMA, собственные сделки), чтобы визуализировать работу стратегии в дизайнере или тестере.

Предоставлена только C#-версия. Папка с Python-реализацией специально не создаётся.

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class KsRobotV15Strategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	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 SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public KsRobotV15Strategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_smaPeriod = Param(nameof(SmaPeriod), 50).SetDisplay("SMA Period", "SMA filter", "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 sma = new SimpleMovingAverage { Length = SmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, sma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal sma)
	{
		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 && close > sma && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevEma && close < ema && close < sma && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevEma = ema;
	}
}