This strategy is a StockSharp port of the MetaTrader expert advisor "DeMarker gaining position volume". It uses the DeMarker oscillator to detect oversold and overbought extremes, gradually accumulating exposure when the market stays in a stretched condition. The implementation operates on completed candles and ensures that only one signal per bar is processed.
The C# version focuses on the core discretionary logic of the original script while adopting the high-level StockSharp API. Order management, volume growth, and optional reversal behaviour are available via strategy parameters, allowing the algorithm to be adapted to different markets and timeframes.
Parameters
DeMarker Period – number of candles used by the DeMarker indicator.
Upper Level – oscillator threshold that prepares short exposure (default 0.7).
Lower Level – oscillator threshold that prepares long exposure (default 0.3).
Trade Volume – market order volume submitted on every signal.
Only One Position – when enabled, the strategy flattens before opening a new trade so that net exposure never mixes longs and shorts.
Reverse Signals – swaps buy and sell triggers, turning the strategy into a contrarian or trend-following version.
Candle Type – timeframe of the candles used for the indicator and signal evaluation.
Trading Logic
A candle subscription is opened for the selected timeframe and fed into a DeMarker indicator.
When the latest finished candle closes, the current DeMarker value is compared with the configured levels.
Without reversal:
If DeMarker is below the lower level, the strategy tries to build or add to a long position.
If DeMarker is above the upper level, the strategy tries to build or add to a short position.
With reversal enabled, the meaning of the levels is inverted (extreme lows trigger shorts and extreme highs trigger longs).
The algorithm remembers the bar time of the last executed trade to avoid multiple entries on the same candle.
Position Management
Before flipping direction the strategy checks the unrealised profit of the existing position. Opposite exposure is closed only if the current candle price exits the trade with a positive result, mirroring the protective behaviour of the original EA.
Position averages are tracked internally. When additional orders are added in the same direction, the average price is recalculated to evaluate profitability correctly.
The optional Only One Position parameter forces a flat state before entering a new trade, which is helpful when running in net position mode.
StartProtection() is invoked once the strategy starts to ensure that emergency liquidation remains available if the position becomes non-zero and the algorithm stops.
Notes
The conversion is designed for the high-level StockSharp API and does not rely on any custom collections or direct indicator value polling.
Risk sizing models from the MetaTrader version (fixed margin, percentage risk, etc.) are intentionally simplified to the constant Trade Volume parameter. Adjust position sizing externally if dynamic risk control is required.
Because fills are simulated with market orders at candle close prices, remember to validate the configuration against actual broker execution and slippage requirements.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Uses the DeMarker oscillator to accumulate positions when extreme levels are reached.
/// </summary>
public class DeMarkerGainingPositionVolumeStrategy : 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>
/// Number of candles used by the DeMarker indicator.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// DeMarker level that triggers short entries.
/// </summary>
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
/// <summary>
/// DeMarker level that triggers long entries.
/// </summary>
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <summary>
/// Candle type that defines the timeframe for the oscillator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DeMarkerGainingPositionVolumeStrategy()
{
_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", "Threshold that prepares short exposure.", "Indicator");
_lowerLevel = Param(nameof(LowerLevel), 0.3m)
.SetDisplay("Lower Level", "Threshold that prepares long exposure.", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for DeMarker 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;
if (_prevOscillator is null)
{
_prevOscillator = oscillatorValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Oscillator crosses below the lower level => oversold => buy
if (_prevOscillator > LowerLevel && oscillatorValue <= LowerLevel)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Oscillator crosses above the upper level => overbought => sell
else if (_prevOscillator < UpperLevel && oscillatorValue >= UpperLevel)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevOscillator = oscillatorValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_prevOscillator = null;
base.OnReseted();
}
}