GitHub で見る
Vlado Williams %R しきい値戦略
概要
Vlado Williams %R しきい値戦略 は、MetaTrader 4 エキスパート アドバイザー Vlado_www_forex-instruments_info.mq4 を直接変換したものです。オリジナルのロボットは単一の Williams %R オシレーターを取引し、インジケーターがユーザー定義のレベルを横切るたびに市場エクスポージャーを反転します。この StockSharp ポートは、同じレジーム切り替え動作を再現しながら、最適化と UI 制御のための戦略パラメーターとして各調整可能な値を公開します。
主要な概念
- Williams %R オシレーターの方向をしきい値 (デフォルト
-50) を基準にしてトレードします。
- 一度に最大 1 つの市場ポジションを保持し、前の取引が終了した後にのみ反転します。
- MetaTrader の資金管理式
AccountFreeMargin * MaximumRisk / price を模倣したオプションのリスクベースのポジションサイジング。
CandleType パラメータを介して任意のローソク足の時間枠で動作します(デフォルトの 15 分足)。
取引ロジック
- 構成されたローソク足ストリームをサブスクライブし、長さ
WprLength (デフォルトは 100) の Williams %R を計算します。
- Williams %R が
WprLevel を上に上昇すると、戦略は強気バイアスを示します。
- オープンなポジションがなく、前の取引が長くなかった場合は、成行買い注文を送信します。
- ショートポジションが存在する場合は、直ちにクローズされます。ポジションがフラットになった後、新しいロングは次のローソク足で検討されます。
- Williams %R が
WprLevel を 下回る と、バイアスは弱気に反転します。
- オープンなポジションがなく、前回の取引がショートではなかった場合は、成行売り注文を送信します。
- ロングポジションが存在する場合、すぐにフラット化されます。
- ポジションのサイズは
CalculateOrderVolume によって決まります。
UseRiskMoneyManagement が true の場合、ストラテジーは現在のポートフォリオ値から取引可能量を推定します: Portfolio.CurrentValue × MaximumRiskPercent ÷ 100 ÷ ClosePrice。
- それ以外の場合は、ベースの
Strategy.Volume が使用されます。
- 結果として得られるロットは商品
VolumeStep に合わせて調整され、これらの境界が利用可能な場合は MinVolume / MaxVolume によってクランプされます。
この戦略は、元の EA フローに合わせて、出口をトリガーしたのと同じローソク足で反転ポジションを開くことを意図的に回避します (CheckForClose は CheckForOpen の前に実行されます)。
変換メモ
- 資金管理のデフォルトは MT4 スクリプトに従います:
MaximumRiskPercent は 10 で始まり、取引ごとにおよそ 1 つのミニロットを対象とした元の MaximumRisk = 10 定数と一致します。
- MetaTrader の
shift パラメータ (インジケータ シフト) は、ソース ファイルでは常に 0 です。したがって省略されました。
- MT4 の色の引数 (例:
Red、Blue) には、同等の StockSharp がなく、無視されます。
- StockSharp 成行注文ではすでに現在の最良価格が使用されているため、スリッページの入力は必要ありません。
パラメーター
| パラメータ |
種類 |
デフォルト |
説明 |
CandleType |
DataType |
15分の時間枠 |
シグナル計算と注文トリガーの両方の時間枠。 |
WprLength |
int |
100 |
Williams %R オシレーターのルックバック期間。 |
WprLevel |
decimal |
-50 |
強気体制と弱気体制を分けるしきい値。 |
UseRiskMoneyManagement |
bool |
false |
リスクベースのポジションサイジングを切り替えます。 |
MaximumRiskPercent |
decimal |
10 |
リスク管理が有効な場合に、取引ごとに展開されるポートフォリオの株式の割合。 |
ヒント: 自動ストップロス処理が必要な場合は、戦略を StartProtection() または外部リスク管理と組み合わせてください。オリジナルの EA も手動による監視に依存しており、ハード ストップを定義していませんでした。
使用ガイドライン
- 正確な
PriceStep、StepPrice、VolumeStep、および出来高制限を公開する証券にストラテジーをアタッチすると、ポジションサイジング ヘルパーが注文を正しく正規化できるようになります。
Volume を希望のフォールバック ロット サイズに設定します。これは、ポートフォリオの資産が利用できない場合、または UseRiskMoneyManagement が無効になっている場合に使用されます。
WprLevel と WprLength を最適化して、システムをさまざまな市場に適応させます。狭いレベル(例: -20 / -80)では戦略がより選択的になりますが、広いしきい値(-50)ではほぼ常に投資されることが保証されます。
- この戦略はトレンドに従うものであり、レンジ条件では頻繁に反転します。必要に応じて、より高い時間枠のトレンド チェックやボラティリティのしきい値などのフィルターと組み合わせることを検討してください。
- StockSharp の高レベル API からのローソク足サブスクリプションとインジケーター バインディングを使用します。手動による注文ループや履歴のスキャンはありません。
- リスクのサイジングは
Portfolio.CurrentValue に依存します。アカウント評価が欠落している場合、ロジックは静的な Volume にフォールバックし、mm=0 が固定ロットサイズを強制する MT4 の動作と一致します。
- リポジトリのガイドラインとの整合性を図るため、すべてのコメントとパラメータの説明は英語で記載されています。
検証チェックリスト
- ✅ StockSharp 戦略テンプレート規則 (タブ、ファイル スコープの名前空間、XML 継承ドキュメント) に従ってコンパイルされた戦略ファイル。
- ✅ パラメータは
Param() によって作成され、必要に応じて最適化のマークが付けられます。
- ✅ Williams %R 値は、
GetValue() に直接アクセスすることなく、Bind を通じて消費されます。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the "Vlado" MetaTrader expert advisor that trades Williams %R level breakouts.
/// </summary>
public class VladoWilliamsPercentRangeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wprLength;
private readonly StrategyParam<decimal> _wprLevel;
private readonly StrategyParam<bool> _useRiskMoneyManagement;
private readonly StrategyParam<decimal> _maximumRiskPercent;
private bool _buySignal;
private bool _sellSignal;
private int _lastSignal;
private WilliamsR _williamsR;
/// <summary>
/// Initializes a new instance of the <see cref="VladoWilliamsPercentRangeStrategy"/> class.
/// </summary>
public VladoWilliamsPercentRangeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");
_wprLength = Param(nameof(WprLength), 100)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Lookback period for Williams %R", "Indicators")
;
_wprLevel = Param(nameof(WprLevel), -50m)
.SetDisplay("Williams %R Level", "Threshold that flips the bias", "Signals")
;
_useRiskMoneyManagement = Param(nameof(UseRiskMoneyManagement), false)
.SetDisplay("Risk Money Management", "Recalculate volume from equity before entries", "Risk")
;
_maximumRiskPercent = Param(nameof(MaximumRiskPercent), 10m)
.SetDisplay("Maximum Risk Percent", "Equity percentage used when sizing orders", "Risk")
;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Williams %R lookback length.
/// </summary>
public int WprLength
{
get => _wprLength.Value;
set => _wprLength.Value = value;
}
/// <summary>
/// Threshold that toggles bullish or bearish bias.
/// </summary>
public decimal WprLevel
{
get => _wprLevel.Value;
set => _wprLevel.Value = value;
}
/// <summary>
/// Enables risk based volume sizing similar to the MetaTrader version.
/// </summary>
public bool UseRiskMoneyManagement
{
get => _useRiskMoneyManagement.Value;
set => _useRiskMoneyManagement.Value = value;
}
/// <summary>
/// Fraction of the current equity used to size entries when risk management is enabled.
/// </summary>
public decimal MaximumRiskPercent
{
get => _maximumRiskPercent.Value;
set => _maximumRiskPercent.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_williamsR = null;
_buySignal = false;
_sellSignal = false;
_lastSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_williamsR = new WilliamsR
{
Length = WprLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_williamsR, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsR);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wprValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateSignals(wprValue);
if (Position != 0)
{
if (Position > 0 && _sellSignal)
{
// Exit long positions when the bearish Williams %R regime appears.
if (Position > 0) SellMarket(); else BuyMarket();
return;
}
if (Position < 0 && _buySignal)
{
// Exit short positions when the bullish Williams %R regime appears.
if (Position > 0) SellMarket(); else BuyMarket();
return;
}
return;
}
// No open position - evaluate fresh entries.
var volume = CalculateOrderVolume(candle.ClosePrice);
if (volume <= 0m)
return;
if (_sellSignal && _lastSignal != -1)
{
// Enter short once Williams %R falls below the chosen level.
SellMarket();
_lastSignal = -1;
return;
}
if (_buySignal && _lastSignal != 1)
{
// Enter long once Williams %R rises above the chosen level.
BuyMarket();
_lastSignal = 1;
}
}
private void UpdateSignals(decimal wprValue)
{
// Williams %R values are negative: less negative indicates bullish momentum.
if (wprValue > WprLevel)
{
_buySignal = true;
_sellSignal = false;
}
else if (wprValue < WprLevel)
{
_sellSignal = true;
_buySignal = false;
}
}
private decimal CalculateOrderVolume(decimal referencePrice)
{
var volume = Volume;
if (UseRiskMoneyManagement && MaximumRiskPercent > 0m && referencePrice > 0m)
{
var equity = Portfolio?.CurrentValue ?? 0m;
if (equity > 0m)
{
// Convert risk capital to volume using the latest close price as approximation.
volume = equity * (MaximumRiskPercent / 100m) / referencePrice;
}
}
return NormalizeVolume(volume);
}
private decimal NormalizeVolume(decimal volume)
{
var normalized = volume;
if (Security?.VolumeStep is decimal step && step > 0m)
{
var steps = decimal.Floor(normalized / step);
normalized = steps * step;
if (normalized <= 0m)
normalized = step;
}
if (Security?.MinVolume is decimal minVolume && minVolume > 0m && normalized < minVolume)
normalized = minVolume;
if (Security?.MaxVolume is decimal maxVolume && maxVolume > 0m && normalized > maxVolume)
normalized = maxVolume;
if (normalized <= 0m && volume > 0m)
normalized = volume;
return normalized;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
class vlado_williams_percent_range_strategy(Strategy):
"""Vlado Williams %R strategy. Trades level breakouts: goes long when WPR rises
above the threshold, short when it falls below. Exits on opposite signal."""
def __init__(self):
super(vlado_williams_percent_range_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General")
self._wpr_length = self.Param("WprLength", 100) \
.SetGreaterThanZero() \
.SetDisplay("Williams %R Period", "Lookback period for Williams %R", "Indicators")
self._wpr_level = self.Param("WprLevel", -50.0) \
.SetDisplay("Williams %R Level", "Threshold that flips the bias", "Signals")
self._buy_signal = False
self._sell_signal = False
self._last_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WprLength(self):
return self._wpr_length.Value
@property
def WprLevel(self):
return self._wpr_level.Value
def OnReseted(self):
super(vlado_williams_percent_range_strategy, self).OnReseted()
self._buy_signal = False
self._sell_signal = False
self._last_signal = 0
def OnStarted2(self, time):
super(vlado_williams_percent_range_strategy, self).OnStarted2(time)
williams_r = WilliamsR()
williams_r.Length = self.WprLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(williams_r, self._process_candle).Start()
def _process_candle(self, candle, wpr_value):
if candle.State != CandleStates.Finished:
return
wpr = float(wpr_value)
wpr_level = float(self.WprLevel)
# Update signals based on Williams %R relative to threshold
if wpr > wpr_level:
self._buy_signal = True
self._sell_signal = False
elif wpr < wpr_level:
self._sell_signal = True
self._buy_signal = False
if self.Position != 0:
if self.Position > 0 and self._sell_signal:
# Exit long on bearish regime
self.SellMarket()
return
if self.Position < 0 and self._buy_signal:
# Exit short on bullish regime
self.BuyMarket()
return
return
# No open position - evaluate entries
if self._sell_signal and self._last_signal != -1:
self.SellMarket()
self._last_signal = -1
return
if self._buy_signal and self._last_signal != 1:
self.BuyMarket()
self._last_signal = 1
def CreateClone(self):
return vlado_williams_percent_range_strategy()