在 GitHub 上查看

Cronex DeMarker 交叉策略

概览

Cronex DeMarker 策略复刻了 MetaTrader 指标 Cronex DeMarker,并将其转化为自动化交易系统。原始指标绘制 DeMarker 振荡器以及两条线性加权移动平均线(LWMA)。该策略完全复制这一结构:在两条平滑曲线发生金叉或死叉时生成交易信号,让系统能够及时捕捉动量由空头转为多头或由多头转为空头的变化。

指标构建

  1. DeMarker 振荡器:比较当前蜡烛与上一根蜡烛。
    • 若当前最高价高于上一根最高价,则正向压力等于两个最高价的差,否则为零。
    • 若当前最低价低于上一根最低价,则负向压力等于两个最低价的距离,否则为零。
    • DeMarkerPeriod 根蜡烛上累计正向与负向压力,计算振荡器值 deMax / (deMax + deMin)
  2. 快速 LWMA:将周期为 FastMaPeriod 的线性加权均线作用于 DeMarker 序列,以强调最新的振荡器变化。
  3. 慢速 LWMA:对同一 DeMarker 序列再应用周期为 SlowMaPeriod 的线性加权均线,得到平滑的确认曲线。

策略仅在蜡烛收盘后更新上述指示器,确保计算结果与 MQ4 源代码中的缓冲区完全一致。

交易逻辑

  1. 等待 DeMarker 振荡器及两条 LWMA 都完成初始化。
  2. 每根收盘蜡烛到来时,计算新的 DeMarker 值,并同步更新两条线性加权均线。
  3. 检测快慢 LWMA 的交叉:
    • 金叉:快速 LWMA 从下向上穿越慢速 LWMA。策略平掉空头仓位并以市价开多。
    • 死叉:快速 LWMA 从上向下跌破慢速 LWMA。策略平掉多头仓位并以市价开空。
  4. 若策略尚未形成、处于离线状态或被禁止交易,则忽略信号。

当出现反向信号时,策略立即反转仓位,通过增加下单数量来同时平仓与开新仓。

参数

参数 说明 默认值
DeMarkerPeriod 计算 DeMarker 振荡器所使用的蜡烛数量。 25
FastMaPeriod 快速线性加权均线的周期,响应最新的振荡器变化。 14
SlowMaPeriod 慢速线性加权均线的周期,用于确认趋势方向。 25
CandleType 策略处理的蜡烛类型(时间周期或其他 DataType)。 1 小时蜡烛

实现细节

  • 采用高层 SubscribeCandles API,仅在蜡烛状态为 Finished 时更新指标,避免盘中重绘。
  • 直接使用 StockSharp 内置的 DeMarkerWeightedMovingAverage 指标,确保与 MQ4 版本一致。
  • 启动时自动创建图表区域,同时绘制价格蜡烛、DeMarker 振荡器及两条 LWMA,便于观察信号。
  • OnStarted 中调用 StartProtection(),按照项目要求仅初始化一次持仓保护。

使用方法

  1. 将策略附加到目标交易品种,并选择需要的蜡烛类型(例如 1 小时)。
  2. 设置 DeMarker 及两条 LWMA 的周期,可以使用默认值以复刻原指标,或根据需要进行优化。
  3. 启动策略。在指标完全形成并允许交易后,系统会自动执行买卖。
  4. 通过图表观察蜡烛和三条曲线,了解策略触发的多空信号。
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();
		}
	}
}