Открыть на GitHub

Стратегия Demarker Martingale (StockSharp)

Обзор

Demarker Martingale Strategy переносит эксперта MetaTrader «Demarker Martingale» на высокоуровневый API StockSharp. Система объединяет сигналы индикатора DeMarker на рабочем таймфрейме с фильтром тренда по MACD на более старшем периоде. После входа применяется мартингейл для объёмов, фиксированные стоп-лосс и тейк-профит, перевод в безубыток и трейлинг-стоп, повторяющие оригинальную логику.

Основной алгоритм

  1. Потоки данных – стратегия подписывается на свечи рабочего периода (по умолчанию 15 минут) и на отдельный ряд свечей высокого таймфрейма (по умолчанию месяц) для расчёта MACD.
  2. Сигнал DeMarker – когда значение DeMarker превышает порог DemarkerThreshold (0,5 по умолчанию) и выполняется условие перекрытия Low[2] < High[1], готовится длинная позиция. При DeMarker ниже порога и условии Low[1] < High[2] формируется короткий сценарий.
  3. Подтверждение MACD – MACD старшего периода должен совпадать по направлению: для лонга главная линия выше сигнальной, для шорта – ниже. Это воспроизводит месячный фильтр из MQL-версии.
  4. Исполнение сделки – при выполнении всех фильтров открывается рыночная позиция объёмом, скорректированным по мартингейлу. Одновременно допускается только одно направление.
  5. Сопровождение – после каждого закрытого бара проверяются условия срабатывания стоп-лосса, тейк-профита, безубытка или трейлинг-стопа. При достижении любого из уровней позиция закрывается по рынку целиком.

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

  • Базовый объём – первый ордер использует InitialVolume, приведённый к шагу VolumeStep и ограниченный VolumeMin/VolumeMax инструмента.
  • Мартингейл – убыточная сделка увеличивает следующий объём путём умножения на MartingaleMultiplier (если DoubleLotSize = true) либо прибавления LotIncrement. Положительный результат сбрасывает объём к исходному. Лестница ограничена параметром MaxMartingaleSteps.
  • Стопы – расстояния задаются в «пунктах» MetaTrader. Размер пункта автоматически корректируется при пяти- и трёхзнаковых котировках (логика ticksize * 10).
  • Перевод в безубыток – когда прибыль в пунктах достигает BreakEvenTriggerPips, стоп переносится на цену входа плюс/минус BreakEvenOffsetPips.
  • Трейлинг-стоп – после превышения TrailingStopPips порог подтягивается вслед за ценой, повторяя правило EA.

Параметры

Параметр Описание
CandleType Таймфрейм для сигналов DeMarker.
MacdCandleType Таймфрейм для расчёта фильтра MACD.
DemarkerPeriod Период индикатора DeMarker.
DemarkerThreshold Нейтральный порог между лонгом и шортом.
MacdFast / MacdSlow / MacdSignal Периоды EMA в MACD.
InitialVolume Стартовый объём позиции.
MartingaleMultiplier Множитель объёма при включённом удвоении.
LotIncrement Приращение объёма при аддитивном режиме.
DoubleLotSize Включает мультипликативный мартингейл.
MaxMartingaleSteps Максимум последовательных увеличений.
StopLossPips Стоп-лосс в пунктах.
TakeProfitPips Тейк-профит в пунктах.
TrailingStopPips Трейлинг-стоп в пунктах.
UseBreakEven Включение перевода в безубыток.
BreakEvenTriggerPips Порог прибыли для перевода в безубыток.
BreakEvenOffsetPips Отступ от цены входа после перевода.

Особенности конверсии

  • Расчёт пункта полностью повторяет MQL: если ticksize равен 0.00001 или 0.001, используется десятикратный множитель.
  • Для фильтра тренда применяется MovingAverageConvergenceDivergenceSignal, обрабатывающий отдельную свечную подписку, что позволяет имитировать месячный график.
  • Учёт мартингейла хранит средневзвешенную цену входа и реализованный результат, чтобы определить, увеличивать ли объём следующего ордера.
  • Все защитные действия выполняются рыночными заявками в связке с StartProtection, поскольку высокоуровневый API ограничивает прямое изменение активных ордеров.

Рекомендации по применению

  • Убедитесь, что инструмент заполнен шагами цены и объёма (PriceStep, VolumeStep, VolumeMin, VolumeMax) – это важно для корректного расчёта пунктов и округления объёмов.
  • При необходимости ускорить фильтр попробуйте изменить MacdCandleType на недельные или дневные свечи.
  • При оптимизации тестируйте DemarkerThreshold, TrailingStopPips и параметры мартингейла совместно для контроля просадки.
  • Мартингейл увеличивает нагрузку после убыточных сделок, поэтому в реальной торговле рекомендуется добавлять портфельные лимиты и ограничения по сессиям.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class DemarkerMartingaleStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public DemarkerMartingaleStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}