Открыть на GitHub

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

Стратегия повторяет работу эксперта MetaTrader 5 «DeMarker gaining position volume 2» средствами StockSharp. Она анализирует выбранный таймфрейм свечей, рассчитывает осциллятор DeMarker и открывает сделки при попадании значения в зоны перекупленности/перепроданности. Реализация сохраняет ключевые особенности оригинала: фиксированный объём, возможность инверсии сигналов, встроенные стоп-лоссы/тейк-профиты и фильтр торговой сессии.

Поведение оригинального эксперта

  • Платформа: MetaTrader 5.
  • Индикатор: DeMarker с периодом 14 по умолчанию.
  • Входы: покупка при значении ниже нижнего уровня, продажа при значении выше верхнего уровня.
  • Управление риском: стоп-лосс и тейк-профит в пунктах, опциональный трейлинг-стоп с шагом, ограничение по времени торговли.
  • Менеджмент позиции: не более одной сделки на свечу, перед сменой направления закрываются противоположные позиции.

Конверсия для StockSharp придерживается тех же принципов. Защитные ордера запускаются через StartProtection, поэтому стопы и трейлинг автоматически сопровождают позицию после открытия.

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

  1. Подписка на указанный тип свечей (CandleType, по умолчанию 5 минут) и вычисление DeMarker с периодом DeMarkerPeriod.
  2. После закрытия свечи проверяется индикатор:
    • При ReverseSignals = false (по умолчанию):
      • ЛонгDeMarker <= LowerLevel.
      • ШортDeMarker >= UpperLevel.
    • При ReverseSignals = true условия зеркально меняются.
  3. При активном UseTimeFilter торговля ведётся только в интервале SessionStartSessionEnd (поддерживаются интервалы через полночь).
  4. На каждой свече допускается только один новый вход. Перед открытием сделки стратегия закрывает противоположные позиции, что повторяет оригинал.
  5. Объём сделок фиксируется параметром TradeVolume. При наличии части позиции в нужную сторону объём доводится до требуемого.

Управление риском

  • StopLossPoints и TakeProfitPoints (в шагах цены) соответствуют точечным стопу и тейку эксперта.
  • Включение EnableTrailing переключает стоп на TrailingStopPoints и активирует стандартный трейлинг с шагом TrailingStepPoints.
  • StartProtection запускается с параметром useMarketOrders = true, поэтому защитные заявки исполняются рыночным образом, как и в MT5.

Параметры

Параметр Описание
DeMarkerPeriod Период усреднения индикатора DeMarker.
UpperLevel / LowerLevel Пороговые уровни перекупленности/перепроданности.
ReverseSignals Инвертировать логику входов.
StopLossPoints Дистанция начального стоп-лосса в шагах цены.
TakeProfitPoints Дистанция тейк-профита в шагах цены.
EnableTrailing Активировать блок трейлинг-стопа.
TrailingStopPoints Дистанция трейлинг-стопа при активном сопровождении.
TrailingStepPoints Минимальный шаг подтяжки трейлинга.
UseTimeFilter Ограничить торговлю интервалом SessionStartSessionEnd.
SessionStart / SessionEnd Границы торговой сессии (поддерживается переход через сутки).
TradeVolume Объём заявки при каждом сигнале.
CandleType Анализируемый тип свечей (по умолчанию 5-минутные).

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

  • В оригинале использовался параметр «активации трейлинга». В стандартном механизме StockSharp аналог отсутствует, поэтому трейлинг включается сразу при EnableTrailing = true.
  • Проверки на корректность лота, уровни Freeze/Stops и обновление котировок выполняются инфраструктурой StockSharp, поэтому в коде не дублируются.
  • Для дополнительного протоколирования можно использовать методы LogInfo/LogError базового класса Strategy.
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Strategy converted from "DeMarker gaining position volume 2" expert advisor.
/// Uses DeMarker oscillator thresholds for entry signals.
/// </summary>
public class DeMarkerGainingPositionVolume2Strategy : 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>
	/// DeMarker averaging period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Upper DeMarker threshold.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// Lower DeMarker threshold.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

	/// <summary>
	/// Candle type for DeMarker calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DeMarkerGainingPositionVolume2Strategy()
	{
		_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", "Overbought threshold.", "Indicator");

		_lowerLevel = Param(nameof(LowerLevel), 0.3m)
			.SetDisplay("Lower Level", "Oversold threshold.", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for 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;

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

		// Cross below lower level => oversold => buy
		if (_prevOscillator is decimal prev)
		{
			var crossBelow = prev >= LowerLevel && oscillatorValue < LowerLevel;
			var crossAbove = prev <= UpperLevel && oscillatorValue > UpperLevel;

			if (crossBelow)
			{
				if (Position <= 0)
					BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
			}
			else if (crossAbove)
			{
				if (Position >= 0)
					SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
			}
		}

		_prevOscillator = oscillatorValue;
	}

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

		base.OnReseted();
	}
}