Открыть на GitHub

Стратегия Cronex DeMarker Crossover

Обзор

Стратегия Cronex DeMarker повторяет индикатор Cronex DeMarker из MetaTrader и превращает его в полностью автоматизированную торговую систему. Исходный индикатор строит осциллятор DeMarker и две линейно взвешенные скользящие средние (LWMA). В стратегии воспроизводится та же связка: пересечения сглаженных линий интерпретируются как сигналы на покупку или продажу, что позволяет быстро реагировать на смену импульса между покупателями и продавцами.

Построение индикаторов

  1. Осциллятор DeMarker — оценивает отношение текущей свечи к предыдущей:
    • Если текущий максимум выше предыдущего, положительное давление равно разнице максимумов, иначе берётся ноль.
    • Если текущий минимум ниже предыдущего, отрицательное давление равно расстоянию между минимумами, иначе берётся ноль.
    • Суммы положительного и отрицательного давления за DeMarkerPeriod свечей формируют значение осциллятора deMax / (deMax + deMin).
  2. Быстрая LWMA — линейно взвешенная скользящая среднего с периодом FastMaPeriod применяется к значениям DeMarker, подчёркивая последние изменения осциллятора.
  3. Медленная LWMA — ещё одна линейно взвешенная средняя с периодом SlowMaPeriod сглаживает тот же поток значений DeMarker и служит подтверждающей линией.

Каждая завершённая свеча передаётся через эту цепочку индикаторов, полностью повторяя вычисления буферов из исходного MQ4-файла.

Торговая логика

  1. Дождаться, пока осциллятор DeMarker и обе LWMA полностью сформируются.
  2. После закрытия каждой свечи вычислить новое значение DeMarker и обновить обе скользящие средние.
  3. Определить пересечения между быстрой и медленной LWMA:
    • Бычье пересечение — быстрая LWMA переходит снизу вверх через медленную. Стратегия закрывает короткую позицию и открывает длинную.
    • Медвежье пересечение — быстрая LWMA опускается сверху вниз ниже медленной. Стратегия закрывает длинную позицию и открывает короткую.
  4. Заявки не отправляются, если стратегия ещё не сформирована, находится офлайн или торговля запрещена.

При смене сигнала позиция разворачивается немедленно: объём рыночного ордера увеличивается, чтобы закрыть противоположную позицию и открыть новую.

Параметры

Параметр Описание Значение по умолчанию
DeMarkerPeriod Количество свечей в расчёте осциллятора DeMarker. 25
FastMaPeriod Период быстрой линейно взвешенной скользящей средней. 14
SlowMaPeriod Период медленной линейно взвешенной скользящей средней. 25
CandleType Тип свечей (таймфрейм или другой DataType), с которым работает стратегия. Таймфрейм 1 час

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

  • Используется высокоуровневый метод SubscribeCandles. Индикаторы обновляются только после закрытия свечи (Finished), чтобы исключить перерисовку.
  • Применяются встроенные индикаторы StockSharp: DeMarker и WeightedMovingAverage, что гарантирует соответствие MQ4 версии.
  • Автоматически создаётся область графика с ценой, осциллятором и обеими LWMA для визуального контроля сигналов.
  • Метод StartProtection() вызывается при запуске один раз, соблюдая правила проекта по защите позиций.

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

  1. Подключите стратегию к нужному инструменту и задайте желаемый тип свечей (например, часовые).
  2. Настройте периоды DeMarker и скользящих средних, чтобы воспроизвести оригинальный индикатор или подготовить параметры для оптимизации.
  3. Запустите стратегию. Торговля начнётся после полной формировки индикаторов и разрешения сделок.
  4. Отслеживайте график: на нём отображаются свечи, осциллятор и LWMA, по которым генерируются входы.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that replicates the Cronex DeMarker indicator setup and trades crossovers of its smoothed values.
/// </summary>
public class CronexDeMarkerCrossoverStrategy : Strategy
{
	private readonly StrategyParam<int> _deMarkerPeriod;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private DeMarker _deMarker;
	private WeightedMovingAverage _fastMa;
	private WeightedMovingAverage _slowMa;

	private decimal? _previousFast;
	private decimal? _previousSlow;

	/// <summary>
	/// DeMarker indicator period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Fast linear weighted moving average period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow linear weighted moving average period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="CronexDeMarkerCrossoverStrategy"/>.
	/// </summary>
	public CronexDeMarkerCrossoverStrategy()
	{
		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 25)
			.SetRange(2, 150)
			.SetDisplay("DeMarker Period", "Length of the DeMarker oscillator", "Indicators")
			;

		_fastMaPeriod = Param(nameof(FastMaPeriod), 14)
			.SetRange(2, 100)
			.SetDisplay("Fast LWMA Period", "Length of the fast linear weighted moving average", "Indicators")
			;

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 25)
			.SetRange(2, 150)
			.SetDisplay("Slow LWMA Period", "Length of the slow linear weighted moving average", "Indicators")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame of processed candles", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_deMarker = null;
		_fastMa = null;
		_slowMa = null;
		_previousFast = null;
		_previousSlow = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Instantiate indicators matching the original MetaTrader logic.
		_deMarker = new DeMarker
		{
			Length = DeMarkerPeriod
		};

		_fastMa = new WeightedMovingAverage
		{
			Length = FastMaPeriod
		};

		_slowMa = new WeightedMovingAverage
		{
			Length = SlowMaPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _deMarker);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawOwnTrades(area);
		}

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Only act on completed candles to avoid repainting effects.
		if (candle.State != CandleStates.Finished)
			return;

		if (_deMarker is null || _fastMa is null || _slowMa is null)
			return;

		// Update the DeMarker oscillator with the full candle data.
		var deMarkerResult = _deMarker.Process(new CandleIndicatorValue(_deMarker, candle));
		if (deMarkerResult.IsEmpty)
		{
			return;
		}
		var deMarkerValue = deMarkerResult.GetValue<decimal>();

		// Smooth the oscillator with linear weighted moving averages.
		var fastResult = _fastMa.Process(new DecimalIndicatorValue(_fastMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
		if (fastResult.IsEmpty) return;
		var fastValue = fastResult.GetValue<decimal>();
		var slowResult = _slowMa.Process(new DecimalIndicatorValue(_slowMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
		if (slowResult.IsEmpty) return;
		var slowValue = slowResult.GetValue<decimal>();

		// Ensure all indicators accumulated enough samples.
		if (!_deMarker.IsFormed || !_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_previousFast = fastValue;
			_previousSlow = slowValue;
			return;
		}

		var previousFast = _previousFast;
		var previousSlow = _previousSlow;

		_previousFast = fastValue;
		_previousSlow = slowValue;

		if (!previousFast.HasValue || !previousSlow.HasValue)
			return;

		// Check readiness and trading permissions before sending orders.
		// indicators formed check removed

		var crossUp = previousFast.Value <= previousSlow.Value && fastValue > slowValue;
		var crossDown = previousFast.Value >= previousSlow.Value && fastValue < slowValue;

		if (crossUp)
		{
			// Close short exposure and establish a long position.
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (crossDown)
		{
			// Close long exposure and establish a short position.
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}