GitHub で見る

Simplest DeMarker Strategy

Overview

The Simplest DeMarker Strategy reproduces the logic of the original MetaTrader expert advisor. It tracks the DeMarker oscillator to detect when price momentum leaves overbought or oversold zones. When the oscillator crosses back inside the neutral range, the strategy opens a position in the direction of the expected reversal while managing risk via configurable stop-loss and take-profit distances.

Core Logic

  1. Subscribe to candles of the selected timeframe and calculate the DeMarker indicator with the configured period.
  2. Mark the market state as overbought whenever the previous DeMarker value is above the overbought threshold and as oversold when it is below the oversold threshold.
  3. Generate signals when the current DeMarker value crosses back inside the neutral area:
    • Sell when the oscillator falls below the overbought level after previously being above it.
    • Buy when the oscillator rises above the oversold level after previously being below it.
  4. Place only one position at a time. If Trade On Bar Open is enabled, the order is delayed until the next bar opens; otherwise, the position is entered immediately on the current bar close.
  5. Apply stop-loss and take-profit orders using the built-in protection service to mimic the fixed distances from the MQL version.

Parameters

  • Volume – order size in lots/contracts.
  • DeMarker Period – period of the DeMarker oscillator.
  • Overbought Level – upper DeMarker threshold that defines overbought conditions.
  • Oversold Level – lower DeMarker threshold that defines oversold conditions.
  • Trade On Bar Open – if enabled, entries are executed on the next bar open rather than immediately.
  • Stop Loss Points – protective stop-loss distance expressed in price points.
  • Take Profit Points – profit target distance expressed in price points.
  • Candle Type – candle type (timeframe) used for indicator calculations.

Money Management

  • Stop-loss and take-profit orders are registered automatically through StartProtection with distances converted to price points.
  • Only one position can be active at a time. New signals are ignored while a position exists.

Chart Elements

  • Price candles for the selected subscription.
  • The DeMarker indicator curve.
  • Own trades markers for visual validation of entries and exits.

Notes

  • Use sufficiently high liquidity instruments to ensure stop-loss and take-profit execution quality.
  • The Trade On Bar Open flag approximates the original expert advisor behaviour that waits for a new bar before sending the order.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Simplest DeMarker strategy: DeMarker oscillator crossover.
/// Buys when DeMarker crosses above oversold, sells when crosses below overbought.
/// </summary>
public class SimplestDeMarkerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _demarkerPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevValue;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int DemarkerPeriod { get => _demarkerPeriod.Value; set => _demarkerPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public SimplestDeMarkerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_demarkerPeriod = Param(nameof(DemarkerPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("DeMarker Period", "DeMarker period", "Indicators");
		_oversold = Param(nameof(Oversold), 0.2m)
			.SetDisplay("Oversold", "DeMarker oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 0.8m)
			.SetDisplay("Overbought", "DeMarker overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevValue = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevValue = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var demarker = new DeMarker { Length = DemarkerPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(demarker, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal demarkerValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevValue < Oversold && demarkerValue >= Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevValue > Overbought && demarkerValue <= Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevValue = demarkerValue;
		_hasPrev = true;
	}
}