Открыть на GitHub

Стратегия DeMarker Gaining Position Volume

Обзор

Эта стратегия представляет собой перенос советника MetaTrader «DeMarker gaining position volume» на платформу StockSharp. Она использует осциллятор DeMarker для выявления зон перекупленности и перепроданности и постепенно наращивает позицию, когда рынок удерживается в экстремальной области. Алгоритм работает только с завершёнными свечами и обрабатывает не более одного сигнала на бар.

Версия на C# сохранила ключевую торговую логику оригинального скрипта и использует высокоуровневый API StockSharp. Параметры стратегии позволяют управлять объёмом заявок, логикой переворота и режимом аккумулирования, что облегчает адаптацию под различные инструменты и таймфреймы.

Параметры

  • DeMarker Period – число свечей в расчёте индикатора DeMarker.
  • Upper Level – порог осциллятора, при превышении которого открываются или наращиваются короткие позиции (по умолчанию 0.7).
  • Lower Level – порог осциллятора, при падении ниже которого открываются или наращиваются длинные позиции (по умолчанию 0.3).
  • Trade Volume – объём рыночной заявки, отправляемой при каждом сигнале.
  • Only One Position – при включении стратегия перед открытием новой сделки закрывает текущую, чтобы не смешивать длинные и короткие позиции.
  • Reverse Signals – меняет местами условия входа, превращая стратегию в обратную или трендовую версию.
  • Candle Type – тип свечей (таймфрейм), на которых рассчитываются индикатор и сигналы.

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

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

Управление позицией

  • Перед сменой направления стратегия оценивает нереализованную прибыль существующей позиции. Переворот выполняется только если текущее закрытие даёт положительный результат, что повторяет защитную логику оригинального советника.
  • Внутри стратегии отслеживаются средние цены длинной и короткой позиции. При добавлении объёма средняя цена пересчитывается, чтобы корректно оценивать прибыльность выхода.
  • Параметр Only One Position помогает при работе в режиме чистой позиции, принудительно возвращая стратегию к нулевому балансу перед новым входом.
  • Метод StartProtection() вызывается при запуске, обеспечивая возможность аварийного закрытия позиций, если стратегия остановится при ненулевом остатке.

Примечания

  • Перенос выполнен на высокоуровневом API StockSharp: не используются собственные коллекции и прямое опросное получение значений индикаторов.
  • Системы управления капиталом из MetaTrader (фиксированный лот, расчёт по риску и т.д.) заменены простым параметром Trade Volume. Для динамического риск-менеджмента используйте внешние инструменты.
  • Рыночные заявки исполняются по цене закрытия свечи; перед запуском на реальном счёте проверьте настройки с учётом фактического исполнения и возможного проскальзывания.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Uses the DeMarker oscillator to accumulate positions when extreme levels are reached.
/// </summary>
public class DeMarkerGainingPositionVolumeStrategy : Strategy
{
	private readonly StrategyParam<int> _deMarkerPeriod;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private decimal? _prevOscillator;

	/// <summary>
	/// Number of candles used by the DeMarker indicator.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// DeMarker level that triggers short entries.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// DeMarker level that triggers long entries.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

	/// <summary>
	/// Candle type that defines the timeframe for the oscillator.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DeMarkerGainingPositionVolumeStrategy()
	{
		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
			.SetDisplay("DeMarker Period", "Number of bars used by the oscillator.", "Indicator");

		_upperLevel = Param(nameof(UpperLevel), 0.7m)
			.SetDisplay("Upper Level", "Threshold that prepares short exposure.", "Indicator");

		_lowerLevel = Param(nameof(LowerLevel), 0.3m)
			.SetDisplay("Lower Level", "Threshold that prepares long exposure.", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for DeMarker calculations.", "Data");
	}

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

		_prevOscillator = null;
		_rsi = new RelativeStrengthIndex { Length = DeMarkerPeriod };

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

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

	private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_rsi.IsFormed)
		{
			_prevOscillator = rsiValue / 100m;
			return;
		}

		var oscillatorValue = rsiValue / 100m;
		if (_prevOscillator is null)
		{
			_prevOscillator = oscillatorValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Oscillator crosses below the lower level => oversold => buy
		if (_prevOscillator > LowerLevel && oscillatorValue <= LowerLevel)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		// Oscillator crosses above the upper level => overbought => sell
		else if (_prevOscillator < UpperLevel && oscillatorValue >= UpperLevel)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevOscillator = oscillatorValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_rsi = null;
		_prevOscillator = null;

		base.OnReseted();
	}
}