Открыть на GitHub

Cronex DeMarker

Стратегия Cronex DeMarker переносит на StockSharp классического советника Cronex. Она использует осциллятор DeMarker и два последовательных простых скользящих средних. Быстрая средняя сглаживает значения DeMarker, а медленная повторно сглаживает результат, формируя сигнальную линию. Взаимное расположение линий позволяет находить точки разворота и открывать сделки против текущего импульса.

Оригинальная версия на MQL5 давала возможность управлять направлениями торговли и лучше всего работала на старших таймфреймах. Порт сохраняет эту идеологию: как только быстрая линия пересекает медленную, стратегия закрывает противоположную позицию и открывает новую в обратную сторону. Пересечение вниз запускает покупку, пересечение вверх — продажу. Любое направление можно отключить в параметрах, что облегчает интеграцию в портфель.

Алгоритм работы

  1. Подписаться на свечи выбранного таймфрейма (по умолчанию 4 часа).
  2. Рассчитать осциллятор DeMarker и сгладить его быстрой SMA длиной 14 баров.
  3. Применить вторую SMA длиной 25 баров к результату быстрой средней и получить сигнальную линию.
  4. Если на предыдущей свече быстрая линия была выше медленной, а на текущей закрывшейся свече опустилась ниже, стратегия покупает и закрывает короткие позиции.
  5. Если на предыдущей свече быстрая линия была ниже медленной, а на текущей закрывшейся свече поднялась выше, стратегия продаёт и закрывает длинные позиции.
  6. Объём сделки задаётся свойством Volume; при развороте используется абсолютная величина текущей позиции для немедленного переворота.

Такой подход хорошо ловит откаты после сильных импульсов и больше подходит для диапазонных рынков, ориентируясь на среднесрочную торговлю.

Параметры по умолчанию

Параметр Значение Описание
DeMarkerPeriod 25 Количество баров в расчёте осциллятора DeMarker.
FastPeriod 14 Длина быстрой SMA, сглаживающей DeMarker.
SlowPeriod 25 Длина сигнальной SMA, применяемой к быстрой линии.
CandleType 4 часа Таймфрейм свечей для расчётов.
EnableLongEntry true Разрешить открытия длинных позиций при пересечении вниз.
EnableShortEntry true Разрешить открытия коротких позиций при пересечении вверх.
EnableLongExit true Закрывать длинные позиции при появлении медвежьего сигнала.
EnableShortExit true Закрывать короткие позиции при появлении бычьего сигнала.

Теги и классификация

  • Категория: Mean Reversion, Oscillator
  • Направление: Длинные и короткие позиции (можно отключать)
  • Индикаторы: DeMarker, простые скользящие средние
  • Стопы: Нет, управление только по сигналу
  • Таймфрейм: Свинг-трейдинг (H4 по умолчанию)
  • Сложность: Средняя, используется цепочка индикаторов
  • Риск: Средний — контртрендовые входы могут пострадать в затяжных трендах
  • Автоматизация: Полностью реализована на высокоуровневом API 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 CronexDeMarkerStrategy : 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 CronexDeMarkerStrategy()
	{
		_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;
	}
}