Открыть на GitHub

Стратегия DeMarker Pending 2

Общее описание

Стратегия переносит основную логику эксперта MetaTrader «DeMarker Pending 2» на высокий уровень StockSharp. Индикатор DeMarker рассчитывается на выбранном таймфрейме, и при пересечении заданных уровней формируются отложенные заявки на покупку или продажу. Заявки можно размещать как стоповыми, так и лимитными с дополнительным отступом от текущей цены. Доступны фильтр торговой сессии, контроль спреда и проверка минимального расстояния до существующих позиций.

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

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

Параметры

Название Описание
Working Candles Таймфрейм для сигналов и контроля позиций.
Order Volume Объём для отложенных заявок.
Stop Loss (pts) Начальный стоп-лосс в пунктах.
Take Profit (pts) Начальный тейк-профит в пунктах.
Trailing Activate (pts) Прибыль для запуска трейлинга.
Trailing Stop (pts) Расстояние от цены до трейлинг-стопа.
Trailing Step (pts) Дополнительная прибыль для переноса трейлинга.
Trail On Close При включении трейлинг обновляется только на закрытых свечах.
Max Positions Максимум открытых позиций и активных заявок; 0 отключает ограничение.
Min Distance (pts) Минимальное расстояние от текущей цены позиции до новой заявки.
Use Stop Orders Использовать стоп-заявки (true) или лимитные (false).
Single Pending Разрешать только одну активную отложенную заявку.
Replace Pendings Перед постановкой новой заявки отменять существующие.
Pending Offset (pts) Отступ от текущей цены при расчёте заявки.
Max Spread (pts) Максимальный допустимый спред.
Use Session Filter Включает фильтр торговой сессии.
Start Hour/Minute, End Hour/Minute Границы торгового окна при активном фильтре.
DeMarker Period Период усреднения индикатора DeMarker.
Upper Level Уровень, формирующий короткие сигналы.
Lower Level Уровень, формирующий длинные сигналы.

Примечания

  • Механизмы истечения отложенных заявок и риск-менеджмент по проценту не перенесены — используется фиксированный объём.
  • Стоп-лосс и тейк-профит контролируются по максимумам/минимумам закрывшейся свечи, что может отличаться от внутрисессионной логики MetaTrader.
  • Трейлинг работает только после закрытия свечи и не реагирует на тиковые изменения в реальном времени.
  • Для выставления заявок используются лучшие котировки из Level1. Убедитесь, что подписка на стакан доступна.
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Pending order strategy driven by the DeMarker oscillator.
/// Simplified from "DeMarker Pending 2" to use market orders on threshold crossovers.
/// </summary>
public class DeMarkerPending2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _demarkerPeriod;
	private readonly StrategyParam<decimal> _demarkerUpperLevel;
	private readonly StrategyParam<decimal> _demarkerLowerLevel;

	private RelativeStrengthIndex _rsi;
	private decimal? _prevOscillator;

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

	/// <summary>
	/// DeMarker oscillator period.
	/// </summary>
	public int DemarkerPeriod
	{
		get => _demarkerPeriod.Value;
		set => _demarkerPeriod.Value = value;
	}

	/// <summary>
	/// Upper DeMarker threshold for sell signal.
	/// </summary>
	public decimal DemarkerUpperLevel
	{
		get => _demarkerUpperLevel.Value;
		set => _demarkerUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower DeMarker threshold for buy signal.
	/// </summary>
	public decimal DemarkerLowerLevel
	{
		get => _demarkerLowerLevel.Value;
		set => _demarkerLowerLevel.Value = value;
	}

	public DeMarkerPending2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations", "General");

		_demarkerPeriod = Param(nameof(DemarkerPeriod), 14)
			.SetDisplay("DeMarker Period", "DeMarker oscillator period", "Indicator")
			.SetGreaterThanZero();

		_demarkerUpperLevel = Param(nameof(DemarkerUpperLevel), 0.7m)
			.SetDisplay("Upper Level", "Overbought threshold", "Indicator");

		_demarkerLowerLevel = Param(nameof(DemarkerLowerLevel), 0.3m)
			.SetDisplay("Lower Level", "Oversold threshold", "Indicator");
	}

	/// <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;
		}

		if (_prevOscillator is not decimal prev)
		{
			_prevOscillator = rsiValue / 100m;
			return;
		}

		var oscillatorValue = rsiValue / 100m;
		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Cross above lower level from below => buy
		if (prev < DemarkerLowerLevel && oscillatorValue >= DemarkerLowerLevel)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		// Cross below upper level from above => sell
		else if (prev > DemarkerUpperLevel && oscillatorValue <= DemarkerUpperLevel)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevOscillator = oscillatorValue;
	}

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

		base.OnReseted();
	}
}