Cronex DeMarker Crossover Strategy
Overview
The Cronex DeMarker Crossover Strategy reproduces the MetaTrader indicator Cronex DeMarker and transforms it into an automated trading system. The original indicator plots the DeMarker oscillator together with two linear weighted moving averages (LWMAs). The strategy mirrors that setup, evaluates bullish and bearish crossovers between the smoothed oscillator lines, and converts them into market orders. This allows the trading logic to react immediately when momentum shifts from downside to upside pressure (and vice versa) according to the indicator.
Indicator construction
- DeMarker oscillator – Measures the relationship between the current candle and the previous candle:
- If the current high is higher than the previous high, the positive pressure equals the difference of the highs; otherwise it is zero.
- If the current low is lower than the previous low, the negative pressure equals the distance between the lows; otherwise it is zero.
- The sums of positive and negative pressure over
DeMarkerPeriod bars form the oscillator value deMax / (deMax + deMin).
- Fast LWMA – A linear weighted moving average with period
FastMaPeriod is applied to the raw DeMarker values in order to emphasise the latest oscillator changes.
- Slow LWMA – Another linear weighted moving average with period
SlowMaPeriod smooths the same DeMarker stream to build a slower confirmation line.
The strategy feeds every finished candle to this indicator stack, exactly matching the buffer calculations from the original MQ4 file.
Trading logic
- Wait until the DeMarker oscillator and both LWMAs are fully formed.
- After each completed candle, compute the fresh DeMarker value and update both moving averages.
- Detect crossovers between the fast and slow LWMA series:
- Bullish crossover – The fast LWMA moves from below to above the slow LWMA. The strategy closes any short exposure and opens a long market position.
- Bearish crossover – The fast LWMA moves from above to below the slow LWMA. The strategy closes any long exposure and opens a short market position.
- Orders are skipped while the strategy is not yet formed, while it is offline, or when trading is disabled.
Positions are reversed immediately on opposite signals. Existing exposure is closed by adding the required quantity to the new market order.
Parameters
| Parameter |
Description |
Default |
DeMarkerPeriod |
Number of candles used to build the DeMarker oscillator. |
25 |
FastMaPeriod |
Period of the fast linear weighted moving average that reacts to new oscillator values. |
14 |
SlowMaPeriod |
Period of the slow linear weighted moving average that confirms the direction. |
25 |
CandleType |
Candle series processed by the strategy (time-frame or other DataType). |
1 Hour time-frame |
Implementation details
- Uses the high-level
SubscribeCandles API. Indicators are updated only when a candle reaches the Finished state to avoid mid-bar repainting.
- The strategy relies on the built-in
DeMarker and WeightedMovingAverage indicators from StockSharp to faithfully replicate the MQ4 buffers.
- A chart area is created automatically, plotting the price candles together with the oscillator and both moving averages for visual confirmation.
StartProtection() is invoked during startup so that position protection is engaged exactly once, as required by the project guidelines.
Usage
- Attach the strategy to the desired security and assign the preferred candle type (for example, 1-hour time-frame candles).
- Configure the DeMarker and moving average periods to match the original indicator or tune them for optimisation.
- Run the strategy. It will start trading once the indicators are fully formed and trading is allowed.
- Monitor the plotted chart to see the DeMarker oscillator and LWMA crossover signals driving the entries.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that replicates the Cronex DeMarker indicator setup and trades crossovers of its smoothed values.
/// </summary>
public class CronexDeMarkerCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _deMarkerPeriod;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<DataType> _candleType;
private DeMarker _deMarker;
private WeightedMovingAverage _fastMa;
private WeightedMovingAverage _slowMa;
private decimal? _previousFast;
private decimal? _previousSlow;
/// <summary>
/// DeMarker indicator period.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// Fast linear weighted moving average period.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Slow linear weighted moving average period.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CronexDeMarkerCrossoverStrategy"/>.
/// </summary>
public CronexDeMarkerCrossoverStrategy()
{
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 25)
.SetRange(2, 150)
.SetDisplay("DeMarker Period", "Length of the DeMarker oscillator", "Indicators")
;
_fastMaPeriod = Param(nameof(FastMaPeriod), 14)
.SetRange(2, 100)
.SetDisplay("Fast LWMA Period", "Length of the fast linear weighted moving average", "Indicators")
;
_slowMaPeriod = Param(nameof(SlowMaPeriod), 25)
.SetRange(2, 150)
.SetDisplay("Slow LWMA Period", "Length of the slow linear weighted moving average", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame of processed candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_deMarker = null;
_fastMa = null;
_slowMa = null;
_previousFast = null;
_previousSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Instantiate indicators matching the original MetaTrader logic.
_deMarker = new DeMarker
{
Length = DeMarkerPeriod
};
_fastMa = new WeightedMovingAverage
{
Length = FastMaPeriod
};
_slowMa = new WeightedMovingAverage
{
Length = SlowMaPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _deMarker);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle)
{
// Only act on completed candles to avoid repainting effects.
if (candle.State != CandleStates.Finished)
return;
if (_deMarker is null || _fastMa is null || _slowMa is null)
return;
// Update the DeMarker oscillator with the full candle data.
var deMarkerResult = _deMarker.Process(new CandleIndicatorValue(_deMarker, candle));
if (deMarkerResult.IsEmpty)
{
return;
}
var deMarkerValue = deMarkerResult.GetValue<decimal>();
// Smooth the oscillator with linear weighted moving averages.
var fastResult = _fastMa.Process(new DecimalIndicatorValue(_fastMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
if (fastResult.IsEmpty) return;
var fastValue = fastResult.GetValue<decimal>();
var slowResult = _slowMa.Process(new DecimalIndicatorValue(_slowMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
if (slowResult.IsEmpty) return;
var slowValue = slowResult.GetValue<decimal>();
// Ensure all indicators accumulated enough samples.
if (!_deMarker.IsFormed || !_fastMa.IsFormed || !_slowMa.IsFormed)
{
_previousFast = fastValue;
_previousSlow = slowValue;
return;
}
var previousFast = _previousFast;
var previousSlow = _previousSlow;
_previousFast = fastValue;
_previousSlow = slowValue;
if (!previousFast.HasValue || !previousSlow.HasValue)
return;
// Check readiness and trading permissions before sending orders.
// indicators formed check removed
var crossUp = previousFast.Value <= previousSlow.Value && fastValue > slowValue;
var crossDown = previousFast.Value >= previousSlow.Value && fastValue < slowValue;
if (crossUp)
{
// Close short exposure and establish a long position.
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (crossDown)
{
// Close long exposure and establish a short position.
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DeMarker, WeightedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class cronex_de_marker_crossover_strategy(Strategy):
"""Cronex DeMarker crossover strategy. Smooths the DeMarker oscillator with
fast and slow WMA and trades on their crossover."""
def __init__(self):
super(cronex_de_marker_crossover_strategy, self).__init__()
self._de_marker_period = self.Param("DeMarkerPeriod", 25) \
.SetDisplay("DeMarker Period", "Length of the DeMarker oscillator", "Indicators")
self._fast_ma_period = self.Param("FastMaPeriod", 14) \
.SetDisplay("Fast LWMA Period", "Length of the fast linear weighted moving average", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 25) \
.SetDisplay("Slow LWMA Period", "Length of the slow linear weighted moving average", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame of processed candles", "General")
self._de_marker = None
self._fast_ma = None
self._slow_ma = None
self._previous_fast = None
self._previous_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DeMarkerPeriod(self):
return self._de_marker_period.Value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
def OnReseted(self):
super(cronex_de_marker_crossover_strategy, self).OnReseted()
self._de_marker = None
self._fast_ma = None
self._slow_ma = None
self._previous_fast = None
self._previous_slow = None
def OnStarted2(self, time):
super(cronex_de_marker_crossover_strategy, self).OnStarted2(time)
self._de_marker = DeMarker()
self._de_marker.Length = self.DeMarkerPeriod
self._fast_ma = WeightedMovingAverage()
self._fast_ma.Length = self.FastMaPeriod
self._slow_ma = WeightedMovingAverage()
self._slow_ma.Length = self.SlowMaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._de_marker is None or self._fast_ma is None or self._slow_ma is None:
return
dm_input = CandleIndicatorValue(self._de_marker, candle)
dm_input.IsFinal = True
dm_result = self._de_marker.Process(dm_input)
if dm_result.IsEmpty:
return
dm_value = float(dm_result)
fast_result = process_float(self._fast_ma, dm_value, candle.OpenTime, True)
if fast_result.IsEmpty:
return
fast_value = float(fast_result)
slow_result = process_float(self._slow_ma, dm_value, candle.OpenTime, True)
if slow_result.IsEmpty:
return
slow_value = float(slow_result)
if not self._de_marker.IsFormed or not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
self._previous_fast = fast_value
self._previous_slow = slow_value
return
previous_fast = self._previous_fast
previous_slow = self._previous_slow
self._previous_fast = fast_value
self._previous_slow = slow_value
if previous_fast is None or previous_slow is None:
return
cross_up = previous_fast <= previous_slow and fast_value > slow_value
cross_down = previous_fast >= previous_slow and fast_value < slow_value
if cross_up:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif cross_down:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return cronex_de_marker_crossover_strategy()