Ver no GitHub

Demarker Martingale Strategy (StockSharp)

Overview

The Demarker Martingale Strategy recreates the MetaTrader expert advisor "Demarker Martingale" using the StockSharp high-level API. The system combines a medium-term DeMarker oscillator signal with a higher timeframe MACD trend filter. Entries are followed by martingale-style position sizing, hard stop-loss and take-profit levels, break-even protection, and a trailing stop that mimics the original expert's money management toolkit.

Core Trading Logic

  1. Data feeds – the strategy subscribes to a user-defined trading timeframe (default 15-minute candles) for signal generation and a higher timeframe series (default monthly candles) to calculate the MACD filter.
  2. DeMarker trigger – when the DeMarker value exceeds the neutral DemarkerThreshold (default 0.5) and the recent price action forms a bullish overlap (Low[2] < High[1]), a long setup is considered. Conversely, a bearish overlap with DeMarker below the threshold prepares a short.
  3. MACD confirmation – the higher timeframe MACD must agree with the direction. A bullish signal requires the MACD main line to be above its signal line, while a bearish signal expects the opposite relationship. This reproduces the MQL expert's monthly MACD filter.
  4. Order execution – valid signals place market orders with the current martingale-adjusted volume. Only one directional position is maintained at a time.
  5. Position monitoring – while a position is open, the strategy evaluates every finished candle to detect stop-loss, take-profit, break-even, or trailing-stop triggers. Breach events close the full position via market orders.

Money Management

  • Initial sizing – orders start with InitialVolume aligned to the instrument's VolumeStep and bounded by VolumeMin/VolumeMax.
  • Martingale escalation – after a losing trade the next volume is either multiplied by MartingaleMultiplier (DoubleLotSize = true) or incremented by LotIncrement. Profitable trades reset the ladder to the base volume. The escalation depth is limited by MaxMartingaleSteps to prevent runaway exposure.
  • Stop-loss & take-profit – distances are expressed in MetaTrader-style pips. The pip size automatically adapts to 3/5-digit Forex quotes, matching the original ticksize logic.
  • Break-even – once unrealised profit reaches BreakEvenTriggerPips, the stop-loss is shifted to entry plus BreakEvenOffsetPips (long) or minus the offset (short).
  • Trailing stop – profits beyond TrailingStopPips move an internal trailing threshold that tightens with every candle, replicating the EA's TrailingStop behaviour.

Parameters

Name Description
CandleType Trading timeframe used for DeMarker signals.
MacdCandleType Higher timeframe used to compute the MACD trend filter.
DemarkerPeriod DeMarker lookback period.
DemarkerThreshold Neutral boundary between bullish and bearish setups.
MacdFast / MacdSlow / MacdSignal MACD EMA lengths.
InitialVolume Base order size before martingale adjustments.
MartingaleMultiplier Multiplication factor when DoubleLotSize is enabled.
LotIncrement Additive increase when doubling is disabled.
DoubleLotSize Toggle between multiplicative and additive martingale.
MaxMartingaleSteps Maximum number of consecutive escalations.
StopLossPips Stop-loss distance in pips.
TakeProfitPips Take-profit distance in pips.
TrailingStopPips Trailing stop distance in pips.
UseBreakEven Enable or disable break-even logic.
BreakEvenTriggerPips Profit threshold (in pips) before shifting to break-even.
BreakEvenOffsetPips Buffer applied to the break-even stop.

Conversion Notes

  • The pip conversion mirrors the MQL EA (ticksize == 0.00001 or 0.001 implies a 10x pip scale). This preserves consistent risk distances on 3/5-digit quotes.
  • The MACD trend filter uses MovingAverageConvergenceDivergenceSignal with the original EMA lengths and processes a separate candle series to emulate the monthly chart logic.
  • Martingale bookkeeping tracks weighted-average entry prices and realised PnL to decide whether the next trade should escalate or reset.
  • All protective actions (stop-loss, take-profit, break-even, trailing) execute via market exits because the high-level API discourages direct order modifications under the StartProtection guard.

Usage Tips

  • Ensure the assigned security exposes PriceStep, VolumeStep, VolumeMin, and VolumeMax to align pip calculations and volume rounding with exchange constraints.
  • Experiment with MacdCandleType (e.g., weekly candles) to fine-tune the trend filter for faster markets.
  • When optimising, jointly adjust DemarkerThreshold, TrailingStopPips, and martingale parameters to keep drawdowns in check.
  • Combine the strategy with portfolio-level risk controls or trading session filters when deploying live, as martingale sequences inherently increase exposure after losses.
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;
	}
}