GitHub で見る

DeMarker ポジションボリューム戦略の獲得

概要

この戦略は、MetaTrader エキスパート アドバイザー 「DeMarker のポジション ボリュームの獲得」 の StockSharp 移植です。 DeMarker オシレーターを使用して極端な売られすぎと買われすぎを検出し、市場が伸びた状態にあるときに徐々にエクスポージャーを蓄積します。この実装は完成したローソク足で動作し、バーごとに 1 つのシグナルのみが処理されるようにします。

C# バージョンは、高レベルの StockSharp API を採用しながら、元のスクリプトの中核となる任意ロジックに重点を置いています。注文管理、取引高の増加、およびオプションの反転動作は戦略パラメーターを介して利用できるため、アルゴリズムをさまざまな市場や時間枠に適応させることができます。

パラメーター

  • DeMarker Period – DeMarker インジケーターによって使用されるローソク足の数。
  • 上位レベル – 短時間露光を準備するオシレーターのしきい値 (デフォルトは 0.7)。
  • 下位レベル – 長時間露光を準備するオシレーターのしきい値 (デフォルトは 0.3)。
  • 取引量 – すべてのシグナルで送信された成行注文の量。
  • Only One Position – 有効にすると、新しい取引を開始する前に戦略がフラット化されるため、純エクスポージャーにロングとショートが混在することはありません。
  • リバースシグナル – 買いと売りのトリガーを交換し、戦略を逆張りまたはトレンドフォローのバージョンに変えます。
  • ローソク足タイプ – インジケーターとシグナル評価に使用されるローソク足の時間枠。

取引ロジック

  1. 選択した時間枠に対してローソク足サブスクリプションが開かれ、DeMarker インジケーターに入力されます。
  2. 最後に完成したローソク足が閉じると、現在の DeMarker 値が設定されたレベルと比較されます。
  3. 反転なし:
    • DeMarker が下位レベルを下回っている場合、戦略はロングポジションを構築または追加しようとします。
    • DeMarker が上位レベルを上回っている場合、戦略はショート ポジションを構築または追加しようとします。
  4. 反転を有効にすると、レベルの意味が逆転します (極端な低値はショートをトリガーし、極端な高値はロングをトリガーします)。
  5. アルゴリズムは、同じローソク足で複数のエントリーを避けるために、最後に実行された取引の足時間を記憶します。

ポジション管理

  • 方向を転換する前に、戦略は既存のポジションの未実現利益をチェックします。逆エクスポージャは、現在のローソク足価格がプラスの結果で取引を終了した場合にのみクローズされ、元の EA の保護行動を反映しています。
  • ポジションの平均は内部的に追跡されます。同じ方向に追加注文が追加されると、平均価格が再計算され、収益性が正しく評価されます。
  • オプションの Only One Position パラメーターは、新しい取引を開始する前にフラット状態を強制します。これは、ネット ポジション モードで実行する場合に役立ちます。
  • StartProtection() は、ポジションがゼロ以外になりアルゴリズムが停止した場合でも、緊急清算が可能な状態を維持するために戦略が開始されると呼び出されます。

注意事項

  • 変換は高レベルの StockSharp API 用に設計されており、カスタム コレクションや直接のインジケーター値のポーリングには依存しません。
  • MetaTrader バージョンのリスク サイズ設定モデル (固定マージン、リスク率など) は、定数の Trade Volume パラメータに意図的に単純化されています。動的なリスク管理が必要な場合は、ポジションサイジングを外部から調整します。
  • 約定はローソク終値の成行注文でシミュレートされるため、実際のブローカーの実行とスリッページの要件に対して構成を必ず検証してください。
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();
	}
}