Demarker Martingale Strategy переносит эксперта MetaTrader «Demarker Martingale» на высокоуровневый API StockSharp. Система объединяет сигналы индикатора DeMarker на рабочем таймфрейме с фильтром тренда по MACD на более старшем периоде. После входа применяется мартингейл для объёмов, фиксированные стоп-лосс и тейк-профит, перевод в безубыток и трейлинг-стоп, повторяющие оригинальную логику.
Основной алгоритм
Потоки данных – стратегия подписывается на свечи рабочего периода (по умолчанию 15 минут) и на отдельный ряд свечей высокого таймфрейма (по умолчанию месяц) для расчёта MACD.
Сигнал DeMarker – когда значение DeMarker превышает порог DemarkerThreshold (0,5 по умолчанию) и выполняется условие перекрытия Low[2] < High[1], готовится длинная позиция. При DeMarker ниже порога и условии Low[1] < High[2] формируется короткий сценарий.
Подтверждение MACD – MACD старшего периода должен совпадать по направлению: для лонга главная линия выше сигнальной, для шорта – ниже. Это воспроизводит месячный фильтр из MQL-версии.
Исполнение сделки – при выполнении всех фильтров открывается рыночная позиция объёмом, скорректированным по мартингейлу. Одновременно допускается только одно направление.
Сопровождение – после каждого закрытого бара проверяются условия срабатывания стоп-лосса, тейк-профита, безубытка или трейлинг-стопа. При достижении любого из уровней позиция закрывается по рынку целиком.
Управление капиталом
Базовый объём – первый ордер использует 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class demarker_martingale_strategy(Strategy):
def __init__(self):
super(demarker_martingale_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(demarker_martingale_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(demarker_martingale_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return demarker_martingale_strategy()