在 GitHub 上查看

DeMarker Gaining Position Volume 2 策略

该策略使用 StockSharp 高阶 API 重写 MetaTrader 5 专家顾问 “DeMarker gaining position volume 2”。它监控可配置的 K 线序列,计算 DeMarker 振荡指标,当数值进入极端区域时执行交易,并保留原版的资金管理特色:固定手数、信号反转开关、内置止损止盈以及可选的交易时段过滤。

原版专家概览

  • 平台:MetaTrader 5。
  • 指标:标准 DeMarker 振荡器,默认周期 14。
  • 入场:当 DeMarker 跌破下限时做多,当指标突破上限时做空。
  • 风险控制:以点数设置的固定止损/止盈,可选的拖尾止损步长以及交易时段限制。
  • 仓位管理:每根 K 线最多一次入场,切换方向前先平掉反向持仓。

StockSharp 版本遵循相同思路。通过 StartProtection 配置保护性指令,因此开仓后止损、止盈和拖尾都会自动维护。

交易逻辑

  1. 订阅 CandleType 指定的 K 线(默认 5 分钟)并用 DeMarkerPeriod 的周期计算 DeMarker。
  2. 在 K 线收盘时评估指标:
    • ReverseSignalsfalse(默认):
      • 做多DeMarker <= LowerLevel
      • 做空DeMarker >= UpperLevel
    • ReverseSignalstrue 时,多空条件互换。
  3. 启用 UseTimeFilter 时,只在 SessionStartSessionEnd 指定的时间窗口交易,支持跨越午夜的区间。
  4. 每根 K 线只允许一次新的建仓,并会在进场前关闭所有反向仓位,以复刻 MT5 中的处理方式。
  5. TradeVolume 决定下单数量;若已经持有同方向仓位则补足到目标仓位。

风险管理

  • StopLossPointsTakeProfitPoints(以价格步长计)对应原策略的止损和止盈距离。
  • 打开 EnableTrailing 后,止损距离改用 TrailingStopPoints,并使用 TrailingStepPoints 作为拖尾调整的最小步长。
  • StartProtection 通过 useMarketOrders = true 立即执行保护性指令,行为与 MT5 的市价平仓相近。

参数说明

参数 说明
DeMarkerPeriod DeMarker 指标的平滑周期。
UpperLevel / LowerLevel 触发做空/做多的阈值。
ReverseSignals 反转多空逻辑。
StopLossPoints 初始止损距离(价格步长数)。
TakeProfitPoints 止盈距离(价格步长数)。
EnableTrailing 是否启用拖尾止损。
TrailingStopPoints 拖尾止损距离。
TrailingStepPoints 每次上调拖尾所需的最小盈利幅度。
UseTimeFilter 限定交易在指定时段内进行。
SessionStart / SessionEnd 交易时段的起止时间(支持跨日)。
TradeVolume 每次下单的数量。
CandleType 参与分析的 K 线类型(默认 5 分钟)。

实现备注

  • 原版包含“拖尾激活”阈值。StockSharp 的标准保护模块没有对应设置,因此在 EnableTrailing = true 时拖尾会立即生效。
  • MT5 中的手数校验、冻结/最小止损距离以及报价刷新在 StockSharp 中由交易基础设施处理,因此未在代码中重复实现。
  • 如需更多日志,可调用基类 StrategyLogInfo/LogError 方法。
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();
	}
}