GitHub で見る

DeMarker Gaining Position Volume 2 Strategy

This strategy reproduces the MetaTrader 5 expert advisor "DeMarker gaining position volume 2" using StockSharp's high-level API. It analyses a configurable candle series with the DeMarker oscillator and reacts when the value enters extreme zones. The implementation keeps the original money-management flavour with fixed lot sizing, optional reversal of signals, built-in stop-loss/take-profit handling and an optional trading-session filter.

Original expert behaviour

  • Platform: MetaTrader 5.
  • Indicator: classic DeMarker oscillator (DEM), default period 14.
  • Entries: open longs when DeMarker drops below a lower threshold, open shorts when it rises above an upper threshold.
  • Risk controls: fixed stop-loss/take-profit expressed in points, optional trailing stop with step, optional time window.
  • Position management: ensure only one trade per bar and close the opposite side before flipping direction.

The StockSharp conversion follows the same principles. Protective orders are implemented with StartProtection, so stop-loss, take-profit and trailing are managed automatically once a position is opened.

Trading logic

  1. Subscribe to the configured candle type (CandleType, 5-minute candles by default) and compute the DeMarker value with the chosen period (DeMarkerPeriod).
  2. When a candle closes, evaluate the oscillator:
    • If ReverseSignals is false (default):
      • Long setupDeMarker <= LowerLevel.
      • Short setupDeMarker >= UpperLevel.
    • If ReverseSignals is true, the long/short rules swap.
  3. Only trade within the optional session window defined by SessionStart/SessionEnd when UseTimeFilter is enabled. Overnight sessions are supported.
  4. Execute at most one new entry per candle. Before opening a new position the strategy closes any opposite holdings to mirror the MT5 logic.
  5. Volumes are fixed by the TradeVolume parameter. If the strategy is already partially in the desired direction it tops up to the requested volume.

Risk management

  • StopLossPoints and TakeProfitPoints (in price steps) map to the expert's point-based stop and take-profit distances.
  • Enabling EnableTrailing switches the stop distance to TrailingStopPoints and activates the built-in trailing engine using TrailingStepPoints as the adjustment step.
  • StartProtection is configured with useMarketOrders = true so protective orders will execute immediately, resembling the MT5 trade closure behaviour.

Parameters

Parameter Description
DeMarkerPeriod Averaging period of the DeMarker indicator.
UpperLevel / LowerLevel Overbought/oversold thresholds triggering shorts/longs.
ReverseSignals Swap long and short conditions.
StopLossPoints Initial protective stop distance measured in price steps.
TakeProfitPoints Take-profit distance measured in price steps.
EnableTrailing Enables the trailing stop block.
TrailingStopPoints Distance of the trailing stop once trailing is active.
TrailingStepPoints Minimum favourable move before the trailing stop is advanced.
UseTimeFilter Restricts trading to the SessionStartSessionEnd window.
SessionStart / SessionEnd Inclusive/exclusive session boundaries (supports wrap-around).
TradeVolume Quantity to send with each market order.
CandleType Candle series to analyse (default 5-minute).

Implementation notes

  • The MT5 expert included a "trailing activation" threshold. StockSharp's standard trailing protection does not expose the same parameter, therefore trailing activates immediately when EnableTrailing is true.
  • Error handling for invalid lot sizes, freeze levels and bid/ask refresh logic are handled by StockSharp's infrastructure, so they are omitted from the conversion.
  • Logging is performed via the base Strategy class (call LogInfo/LogError if additional tracing is required).
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Strategy converted from "DeMarker gaining position volume 2" expert advisor.
/// Uses DeMarker oscillator thresholds for entry signals.
/// </summary>
public class DeMarkerGainingPositionVolume2Strategy : Strategy
{
	private readonly StrategyParam<int> _deMarkerPeriod;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private decimal? _prevOscillator;

	/// <summary>
	/// DeMarker averaging period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Upper DeMarker threshold.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// Lower DeMarker threshold.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

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

	public DeMarkerGainingPositionVolume2Strategy()
	{
		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
			.SetDisplay("DeMarker Period", "Number of bars used by the oscillator.", "Indicator");

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

		_lowerLevel = Param(nameof(LowerLevel), 0.3m)
			.SetDisplay("Lower Level", "Oversold threshold.", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations.", "Data");
	}

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

		var oscillatorValue = rsiValue / 100m;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Cross below lower level => oversold => buy
		if (_prevOscillator is decimal prev)
		{
			var crossBelow = prev >= LowerLevel && oscillatorValue < LowerLevel;
			var crossAbove = prev <= UpperLevel && oscillatorValue > UpperLevel;

			if (crossBelow)
			{
				if (Position <= 0)
					BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
			}
			else if (crossAbove)
			{
				if (Position >= 0)
					SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
			}
		}

		_prevOscillator = oscillatorValue;
	}

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

		base.OnReseted();
	}
}