GitHub で見る

Exp XWPR ヒストグラム Vol 戦略

概要

この戦略は MetaTrader エキスパート Exp_XWPR_Histogram_Vol の C# 変換です。Williams %R オシレーターをローソク足のボリュームで掛け合わせて結果を平滑化するカスタムインジケーター XWPR ヒストグラム Vol の色変化で取引します。ポートは元の2スロット資金管理スキーム (プライマリとセカンダリボリューム) を維持し、StockSharp の高レベル API を使用して同じ色駆動のエントリーおよび決済ルールを再現します。

アルゴリズムは完了したローソク足のみを処理します。新しいバーごとに、設定可能な数のバー前のヒストグラムの色を検査し、色の遷移がインジケーターで定義された強気または弱気のしきい値を超えたときに反応します。

インジケーターのロジック

  1. Williams %R (WprPeriod) が +50 シフトされ、選択されたローソク足ボリューム (VolumeMode) で掛け合わされます。
  2. 加重 Williams %R と生のボリュームの両方が同一の平滑化フィルター (SmoothingMethodSmoothingLengthSmoothingPhase) を通過します。
  3. 4つの動的レベルが平滑化されたボリュームから導出されます: HighLevel2HighLevel1LowLevel1LowLevel2
  4. ヒストグラムの色はこれらのレベルで定義されるゾーンに対応します:
    • 0 – ヒストグラムが HighLevel2 より上 (強い強気)。
    • 1 – ヒストグラムが HighLevel1HighLevel2 の間 (緩やかな強気)。
    • 2 – ヒストグラムが LowLevel1HighLevel1 の間 (ニュートラル)。
    • 3 – ヒストグラムが LowLevel2LowLevel1 の間 (緩やかな弱気)。
    • 4 – ヒストグラムが LowLevel2 より下 (強い弱気)。

シグナルルール

戦略は評価ごとに2つの履歴色を読みます: バー SignalBar + 1 (古い方) とバー SignalBar (より新しい方)。

  • プライマリロングを開く (ボリューム = PrimaryVolume): 古いバーの色が 1 で、新しいバーの色が 23 または 4 に移動した場合。この動きは同時にショートポジションのクローズを要求します。
  • セカンダリロングを開く (ボリューム = SecondaryVolume): 古いバーの色が 0 で、新しいバーの色が 0 以外になった場合。同じシグナルもショートをクローズします。
  • プライマリショートを開く (ボリューム = PrimaryVolume): 古いバーの色が 3 で、新しいバーの色が 01 または 2 に上昇し、ロングもクローズされます。
  • セカンダリショートを開く (ボリューム = SecondaryVolume): 古いバーの色が 4 で、新しいバーの色が 012 または 3 になり、再びロング決済が強制されます。
  • ロングをクローズ: 古い色が 3 または 4 (弱気ゾーン) の場合はいつでも。
  • ショートをクローズ: 古い色が 0 または 1 (強気ゾーン) の場合はいつでも。

各方向に対して2つの独立したポジションスロットが維持されます。シグナルは、対応するスロットが現在非アクティブで、関連するエントリーフラグ (AllowLongEntryAllowShortEntry) が許可している場合にのみ注文をトリガーします。

リスク管理

  • StopLossStepsTakeProfitStepsStartProtection を通じて StockSharp の保護注文に変換されます。値はインストゥルメントの価格ステップで表されます。
  • DeviationSteps は MQL 入力リストとの互換性のために保持されています。StockSharp の成行注文はそれを使用しません。

パラメーター

名前 説明
CandleType インジケーターに供給されるローソク足の構築に使用する時間軸。
PrimaryVolumeSecondaryVolume レベル1およびレベル2スロットによって適用されるボリューム。
AllowLongEntryAllowShortEntry 新しいロングまたはショートポジションの開設を有効化。
AllowLongExitAllowShortExit 決済シグナルが現れた場合のロングまたはショートエクスポージャーのクローズを有効化。
StopLossStepsTakeProfitSteps 価格ステップ単位のオプションの保護距離 (0 でそれぞれの保護が無効)。
DeviationSteps 互換性のために予約済み; StockSharp の注文には影響しません。
SignalBar シグナル評価をシフトするクローズしたローソク足の数 (0 = 最後の完了したローソク足)。
WprPeriod Williams %R 計算のルックバック期間。
VolumeMode ヒストグラムのティック数 (Tick) またはリアルボリューム (Real) を選択します。
HighLevel2HighLevel1 上部強気しきい値を定義する乗数。
LowLevel1LowLevel2 下部弱気しきい値を定義する乗数。
SmoothingMethod ヒストグラムとベースラインボリューム両方に使用する移動平均のタイプ。
SmoothingLength 平滑化フィルターの長さ。
SmoothingPhase Jurik ベースの平滑化装置に送られる位相 (他の方法では無視されます)。

使用上の注意

  • 戦略は GetWorkingSecurities() が返す単一のセキュリティで取引し、すべての操作に成行注文を使用します。
  • シグナルは完了したローソク足ごとに一度評価されます。追加の履歴バッファにより同じバーでの重複注文が防止されます。
  • 2つのエントリースロットは独立して機能します。対応するボリュームを 0 に設定するか Allow*Entry フラグを無効にすることでスロットを無効化できます。
  • 変換は MetaTrader のマジックナンバーやマージンモードを複製しません。ポートフォリオのサイジングは完全に PrimaryVolumeSecondaryVolume パラメーターによって制御されます。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Strategy converted from the MetaTrader expert Exp_XWPR_Histogram_Vol.
/// Computes a volume-weighted Williams %R histogram inline and trades on strong colour transitions.
/// </summary>
public class ExpXwprHistogramVolStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprPeriod;
	private readonly StrategyParam<int> _smoothingLength;
	private readonly StrategyParam<decimal> _highLevel2;
	private readonly StrategyParam<decimal> _lowLevel2;
	private readonly StrategyParam<int> _signalCooldownBars;

	private WilliamsR _wpr;
	private SimpleMovingAverage _histSma;
	private SimpleMovingAverage _volSma;
	private int? _prevColor;
	private int _cooldownRemaining;
	private DateTimeOffset? _lastEntryTime;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
	public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
	public decimal HighLevel2 { get => _highLevel2.Value; set => _highLevel2.Value = value; }
	public decimal LowLevel2 { get => _lowLevel2.Value; set => _lowLevel2.Value = value; }
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }

	public ExpXwprHistogramVolStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_wprPeriod = Param(nameof(WprPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("WPR Period", "Williams %R lookback", "Indicator");

		_smoothingLength = Param(nameof(SmoothingLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Smoothing", "Smoothing length", "Indicator");

		_highLevel2 = Param(nameof(HighLevel2), 17m)
			.SetDisplay("High Level 2", "Strong bullish zone", "Indicator");

		_lowLevel2 = Param(nameof(LowLevel2), -17m)
			.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 48)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_wpr = null;
		_histSma = null;
		_volSma = null;
		_prevColor = null;
		_cooldownRemaining = 0;
		_lastEntryTime = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevColor = null;
		_cooldownRemaining = 0;
		_lastEntryTime = null;

		_wpr = new WilliamsR { Length = WprPeriod };
		_histSma = new SimpleMovingAverage { Length = SmoothingLength };
		_volSma = new SimpleMovingAverage { Length = SmoothingLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var wprValue = _wpr.Process(candle);
		if (!wprValue.IsFormed)
			return;

		var wpr = wprValue.ToDecimal();
		var volume = candle.TotalVolume > 0m ? candle.TotalVolume : 1m;
		var histRaw = (wpr + 50m) * volume;
		var histSmoothed = _histSma.Process(new DecimalIndicatorValue(_histSma, histRaw, candle.OpenTime) { IsFinal = true });
		var volSmoothed = _volSma.Process(new DecimalIndicatorValue(_volSma, volume, candle.OpenTime) { IsFinal = true });

		if (!histSmoothed.IsFormed || !volSmoothed.IsFormed)
			return;

		var baseline = volSmoothed.ToDecimal();
		if (baseline == 0m)
			return;

		var hist = histSmoothed.ToDecimal();
		var strongBullLevel = HighLevel2 * baseline;
		var strongBearLevel = LowLevel2 * baseline;

		var color = hist >= strongBullLevel ? 0 : hist <= strongBearLevel ? 4 : 2;

		if (_prevColor == null)
		{
			_prevColor = color;
			return;
		}

		var previousColor = _prevColor.Value;
		_prevColor = color;

		if (_cooldownRemaining > 0 || HasRecentEntry(candle))
			return;

		if (previousColor != 0 && color == 0 && Position <= 0)
		{
			var volumeToBuy = Volume + Math.Abs(Position);
			BuyMarket(volumeToBuy);
			_cooldownRemaining = SignalCooldownBars;
			_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		}
		else if (previousColor != 4 && color == 4 && Position >= 0)
		{
			var volumeToSell = Volume + Math.Abs(Position);
			SellMarket(volumeToSell);
			_cooldownRemaining = SignalCooldownBars;
			_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		}
	}

	private bool HasRecentEntry(ICandleMessage candle)
	{
		if (!_lastEntryTime.HasValue)
			return false;

		var candleTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		return candleTime.Date == _lastEntryTime.Value.Date;
	}
}