Открыть на GitHub

Стратегия XDeMarker Histogram Vol

Стратегия повторяет оригинального советника MetaTrader Exp_XDeMarker_Histogram_Vol и переносит его на высокоуровневый API StockSharp. Осциллятор DeMarker превращается в объёмно-взвешенную гистограмму, обе серии сглаживаются выбранным типом скользящей средней, а торговые решения принимаются при переходах гистограммы между заданными зонами.

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

Концепция

  1. DeMarker, взвешенный объёмом
    • DeMarker рассчитывается с заданным периодом.
    • Значение нормируется в диапазон [-50; +50] и умножается на выбранный объём свечи.
    • Полученная серия и сам объём сглаживаются одной и той же скользящей средней. Доступны только четыре типа (простая, экспоненциальная, сглаженная, взвешенная), так как именно они реализованы в StockSharp.
  2. Динамические уровни
    • Четыре коэффициента (HighLevel1, HighLevel2, LowLevel1, LowLevel2) задают пороги для бычьей и медвежьей зон.
    • Пороговые значения умножаются на сглаженный объём, поэтому при росте активности допустимый диапазон автоматически расширяется.
  3. Машина состояний
    • Каждая закрывшаяся свеча попадает в одно из пяти состояний: 0 (крайне бычье), 1 (бычье), 2 (нейтральное), 3 (медвежье), 4 (крайне медвежье).
    • Сигнал появляется, когда состояние последней закрытой свечи (сдвиг задаётся параметром SignalBar) по сравнению с предыдущей указывает на вход в новую область.

Параметры

Параметр Описание
CandleType Основной таймфрейм. По умолчанию — 2 часа, как в исходном советнике.
DeMarkerPeriod Период осциллятора DeMarker.
HighLevel1 / HighLevel2 Положительные множители для первой и второй бычьих зон.
LowLevel1 / LowLevel2 Отрицательные множители для первой и второй медвежьих зон.
Smoothing Тип скользящей средней для гистограммы и объёма: Simple, Exponential, Smoothed, Weighted.
SmoothingLength Длина применяемых скользящих средних.
SignalBar Количество закрытых баров, используемых при сравнении. Значение 1 означает использование последней закрытой свечи.
VolumeType Источник объёма. Оба варианта сводятся к объёму свечи, так как StockSharp не всегда предоставляет количество тиков.
EnableLongEntries / EnableShortEntries Разрешают открытие длинных и коротких позиций соответственно.
EnableLongExits / EnableShortExits Разрешают закрытие существующих позиций по обратным сигналам.

Сигналы и управление позицией

  • Вход в лонг: рассматриваемая свеча переходит в состояние 1 или 0, а предыдущая имела номер больше 1. При наличии короткой позиции она сначала закрывается.
  • Вход в шорт: рассматриваемая свеча переходит в состояние 3 или 4, а предыдущая имела номер меньше 3 (или 4). При наличии длинной позиции она закрывается.
  • Выход: выполняется при противоположном сигнале, если разрешено закрытие для данного направления. Используется ClosePosition() для фиксации позиции перед разворотом.
  • Размер позиции: используется стандартное свойство Strategy.Volume. В оригинале две позиции с разными «магическими» номерами — здесь логика упрощена.

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

  • Обрабатываются только завершённые свечи. Подписка осуществляется через SubscribeCandles().WhenNew(ProcessCandle).
  • Реализация DeMarker поддерживает скользящую сумму DeMax/DeMin и ждёт накопления достаточного количества баров, чтобы воспроизвести расчёт MetaTrader.
  • При отсутствии объёма гистограмма становится нулевой, поскольку и взвешенный осциллятор, и уровни равны нулю.
  • Расширенные режимы сглаживания из исходного индикатора (JJMA, JurX, ParMA, T3, VIDYA, AMA) не реализованы. Выбирайте доступный вариант, наиболее близкий к желаемому поведению.
  • Буфер состояний хранит минимум значений (актуальное, предыдущее и дополнительный слот) и повторяет поведение CopyBuffer из MQL5.

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

  • Перед запуском в Designer или Runner настройте таймфрейм и объём. При необходимости проведите оптимизацию параметров.
  • Оптимизируйте DeMarkerPeriod, SmoothingLength и пороги совместно: небольшие сдвиги уровней заметно меняют частоту сделок.
  • Эффективность зависит от качества данных по объёму. Используйте провайдеров, предоставляющих корректные значения по свече.
  • При необходимости стоп-лоссов или тейк-профитов подключайте внешние модули управления риском — в исходном советнике их не было, и перенос сохраняет эту особенность.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// XDeMarker Histogram Vol strategy. Uses DeMarker 0.5-line crossover.
/// </summary>
public class XDeMarkerHistogramVolStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _demarkerPeriod;
	private decimal? _prevDm;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int DemarkerPeriod { get => _demarkerPeriod.Value; set => _demarkerPeriod.Value = value; }

	public XDeMarkerHistogramVolStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_demarkerPeriod = Param(nameof(DemarkerPeriod), 14).SetGreaterThanZero().SetDisplay("DeMarker Period", "DeMarker lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevDm = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevDm = null;
		var dm = new DeMarker { Length = DemarkerPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(dm, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal dmVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevDm = dmVal; return; }
		if (_prevDm == null) { _prevDm = dmVal; return; }
		if (_prevDm.Value < 0.45m && dmVal >= 0.55m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (_prevDm.Value > 0.55m && dmVal <= 0.45m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevDm = dmVal;
	}
}