在 GitHub 上查看

Simplest DeMarker 策略

概述

Simplest DeMarker 策略复刻了原始 MetaTrader 智能交易系统的逻辑。它监测 DeMarker 振荡指标,以判断价格动量是否离开超买或超卖区域。当指标重新回到中性区间时,策略按照预期的反转方向建立仓位,并通过可配置的止损和止盈距离来控制风险。

核心逻辑

  1. 订阅所选周期的K线,并使用设定的周期计算 DeMarker 指标。
  2. 当上一根 DeMarker 值高于超买阈值时标记为超买,当低于超卖阈值时标记为超卖
  3. 当指标重新回到中性区间时产生信号:
    • 若指标从超买区间下穿阈值,则开空。
    • 若指标从超卖区间上穿阈值,则开多。
  4. 同一时间仅持有一笔仓位。启用 Trade On Bar Open 参数时,订单会延迟到下一根K线开盘发送;否则在当前K线收盘后立即入场。
  5. 通过 StartProtection 服务设置止损和止盈,以模拟 MQL 版本中的固定距离。

参数

  • Volume – 订单手数/合约数量。
  • DeMarker Period – DeMarker 指标周期。
  • Overbought Level – 判定超买区域的上限阈值。
  • Oversold Level – 判定超卖区域的下限阈值。
  • Trade On Bar Open – 若启用,则在下一根K线开盘时入场。
  • Stop Loss Points – 止损距离(价格点)。
  • Take Profit Points – 止盈距离(价格点)。
  • Candle Type – 用于计算的K线类型/周期。

风险控制

  • 使用 StartProtection 自动注册止损和止盈,将距离转换为价格点。
  • 任意时刻仅保留一笔仓位,存在持仓时会忽略新的信号。

图表元素

  • 所订阅数据的价格K线。
  • DeMarker 指标曲线。
  • 自有交易标记,用于验证进出场位置。

备注

  • 建议选择流动性较好的交易品种,以保证止损和止盈的执行质量。
  • Trade On Bar Open 参数用于近似还原原始智能交易系统在新K线出现时入场的行为。
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;
	}
}