Exp XWPR ヒストグラム Vol 戦略
概要
この戦略は MetaTrader エキスパート Exp_XWPR_Histogram_Vol の C# 変換です。Williams %R オシレーターをローソク足のボリュームで掛け合わせて結果を平滑化するカスタムインジケーター XWPR ヒストグラム Vol の色変化で取引します。ポートは元の2スロット資金管理スキーム (プライマリとセカンダリボリューム) を維持し、StockSharp の高レベル API を使用して同じ色駆動のエントリーおよび決済ルールを再現します。
アルゴリズムは完了したローソク足のみを処理します。新しいバーごとに、設定可能な数のバー前のヒストグラムの色を検査し、色の遷移がインジケーターで定義された強気または弱気のしきい値を超えたときに反応します。
インジケーターのロジック
- Williams %R (
WprPeriod) が +50 シフトされ、選択されたローソク足ボリューム (VolumeMode) で掛け合わされます。
- 加重 Williams %R と生のボリュームの両方が同一の平滑化フィルター (
SmoothingMethod、SmoothingLength、SmoothingPhase) を通過します。
- 4つの動的レベルが平滑化されたボリュームから導出されます:
HighLevel2、HighLevel1、LowLevel1、LowLevel2。
- ヒストグラムの色はこれらのレベルで定義されるゾーンに対応します:
- 0 – ヒストグラムが
HighLevel2 より上 (強い強気)。
- 1 – ヒストグラムが
HighLevel1 と HighLevel2 の間 (緩やかな強気)。
- 2 – ヒストグラムが
LowLevel1 と HighLevel1 の間 (ニュートラル)。
- 3 – ヒストグラムが
LowLevel2 と LowLevel1 の間 (緩やかな弱気)。
- 4 – ヒストグラムが
LowLevel2 より下 (強い弱気)。
シグナルルール
戦略は評価ごとに2つの履歴色を読みます: バー SignalBar + 1 (古い方) とバー SignalBar (より新しい方)。
- プライマリロングを開く (ボリューム =
PrimaryVolume): 古いバーの色が 1 で、新しいバーの色が 2、3 または 4 に移動した場合。この動きは同時にショートポジションのクローズを要求します。
- セカンダリロングを開く (ボリューム =
SecondaryVolume): 古いバーの色が 0 で、新しいバーの色が 0 以外になった場合。同じシグナルもショートをクローズします。
- プライマリショートを開く (ボリューム =
PrimaryVolume): 古いバーの色が 3 で、新しいバーの色が 0、1 または 2 に上昇し、ロングもクローズされます。
- セカンダリショートを開く (ボリューム =
SecondaryVolume): 古いバーの色が 4 で、新しいバーの色が 0、1、2 または 3 になり、再びロング決済が強制されます。
- ロングをクローズ: 古い色が
3 または 4 (弱気ゾーン) の場合はいつでも。
- ショートをクローズ: 古い色が
0 または 1 (強気ゾーン) の場合はいつでも。
各方向に対して2つの独立したポジションスロットが維持されます。シグナルは、対応するスロットが現在非アクティブで、関連するエントリーフラグ (AllowLongEntry、AllowShortEntry) が許可している場合にのみ注文をトリガーします。
リスク管理
StopLossSteps と TakeProfitSteps は StartProtection を通じて StockSharp の保護注文に変換されます。値はインストゥルメントの価格ステップで表されます。
DeviationSteps は MQL 入力リストとの互換性のために保持されています。StockSharp の成行注文はそれを使用しません。
パラメーター
| 名前 |
説明 |
CandleType |
インジケーターに供給されるローソク足の構築に使用する時間軸。 |
PrimaryVolume、SecondaryVolume |
レベル1およびレベル2スロットによって適用されるボリューム。 |
AllowLongEntry、AllowShortEntry |
新しいロングまたはショートポジションの開設を有効化。 |
AllowLongExit、AllowShortExit |
決済シグナルが現れた場合のロングまたはショートエクスポージャーのクローズを有効化。 |
StopLossSteps、TakeProfitSteps |
価格ステップ単位のオプションの保護距離 (0 でそれぞれの保護が無効)。 |
DeviationSteps |
互換性のために予約済み; StockSharp の注文には影響しません。 |
SignalBar |
シグナル評価をシフトするクローズしたローソク足の数 (0 = 最後の完了したローソク足)。 |
WprPeriod |
Williams %R 計算のルックバック期間。 |
VolumeMode |
ヒストグラムのティック数 (Tick) またはリアルボリューム (Real) を選択します。 |
HighLevel2、HighLevel1 |
上部強気しきい値を定義する乗数。 |
LowLevel1、LowLevel2 |
下部弱気しきい値を定義する乗数。 |
SmoothingMethod |
ヒストグラムとベースラインボリューム両方に使用する移動平均のタイプ。 |
SmoothingLength |
平滑化フィルターの長さ。 |
SmoothingPhase |
Jurik ベースの平滑化装置に送られる位相 (他の方法では無視されます)。 |
使用上の注意
- 戦略は
GetWorkingSecurities() が返す単一のセキュリティで取引し、すべての操作に成行注文を使用します。
- シグナルは完了したローソク足ごとに一度評価されます。追加の履歴バッファにより同じバーでの重複注文が防止されます。
- 2つのエントリースロットは独立して機能します。対応するボリュームを
0 に設定するか Allow*Entry フラグを無効にすることでスロットを無効化できます。
- 変換は MetaTrader のマジックナンバーやマージンモードを複製しません。ポートフォリオのサイジングは完全に
PrimaryVolume と SecondaryVolume パラメーターによって制御されます。
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CandleIndicatorValue, SimpleMovingAverage, WilliamsR
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_xwpr_histogram_vol_strategy(Strategy):
def __init__(self):
super(exp_xwpr_histogram_vol_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._wpr_period = self.Param("WprPeriod", 7) \
.SetDisplay("WPR Period", "Williams %R lookback", "Indicator")
self._smoothing_length = self.Param("SmoothingLength", 5) \
.SetDisplay("Smoothing", "Smoothing length", "Indicator")
self._high_level2 = self.Param("HighLevel2", Decimal(17)) \
.SetDisplay("High Level 2", "Strong bullish zone", "Indicator")
self._low_level2 = self.Param("LowLevel2", Decimal(-17)) \
.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 48) \
.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading")
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def WprPeriod(self):
return self._wpr_period.Value
@property
def SmoothingLength(self):
return self._smoothing_length.Value
@property
def HighLevel2(self):
return self._high_level2.Value
@property
def LowLevel2(self):
return self._low_level2.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(exp_xwpr_histogram_vol_strategy, self).OnReseted()
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exp_xwpr_histogram_vol_strategy, self).OnStarted2(time)
self._prev_color = None
self._cooldown_remaining = 0
self._wpr = WilliamsR()
self._wpr.Length = self.WprPeriod
self._hist_sma = SimpleMovingAverage()
self._hist_sma.Length = self.SmoothingLength
self._vol_sma = SimpleMovingAverage()
self._vol_sma.Length = self.SmoothingLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
wpr_result = self._wpr.Process(CandleIndicatorValue(self._wpr, candle))
if not wpr_result.IsFormed:
return
wpr = wpr_result.Value
volume = candle.TotalVolume if candle.TotalVolume > Decimal(0) else Decimal(1)
hist_raw = (wpr + Decimal(50)) * volume
hist_smoothed = process_float(self._hist_sma, hist_raw, candle.OpenTime, True)
vol_smoothed = process_float(self._vol_sma, volume, candle.OpenTime, True)
if not hist_smoothed.IsFormed or not vol_smoothed.IsFormed:
return
baseline = vol_smoothed.Value
if baseline == Decimal(0):
return
hist = hist_smoothed.Value
strong_bull_level = self.HighLevel2 * baseline
strong_bear_level = self.LowLevel2 * baseline
if hist >= strong_bull_level:
color = 0
elif hist <= strong_bear_level:
color = 4
else:
color = 2
if self._prev_color is None:
self._prev_color = color
return
previous_color = self._prev_color
self._prev_color = color
if self._cooldown_remaining > 0:
return
if previous_color != 0 and color == 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif previous_color != 4 and color == 4 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
def CreateClone(self):
return exp_xwpr_histogram_vol_strategy()