GitHub で見る

加重オシレーター直接戦略

概要

この戦略はMetaTraderエキスパート Exp_WeightOscillator_Direct をStockSharpの高レベルAPI内で再現します。4つの古典的なオシレーター(RSI、Money Flow Index、Williams %R、DeMarker)を単一の加重コンポジットにブレンドします。コンポジットシグナルは設定可能な移動平均によって平滑化され、モメンタムのスイングを検出するために使用されます。コンポジットが上昇すると、戦略が「Direct」モードで動作する場合はロング取引を開く(またはショートをクローズする)一方、「Against」モードでは逆張り取引のためにロジックを反転させます。

インジケーターパイプライン

  1. Relative Strength Index (RSI) – 正規化された0..100スケール。
  2. Money Flow Index (MFI) – 0..100の範囲の流動性に敏感なオシレーター。
  3. Williams %R (WPR) – 0..100スケールに合わせるために+100だけシフト。
  4. DeMarker – 他のオシレーターに合わせるために100倍。
  5. 平滑化平均 – サポートされている移動平均の1つ(シンプル、指数、スムーズ、加重、Jurik、Kaufman)。
  6. コンポジットオシレーター – ノイズを除去するために平滑化された正規化入力の加重平均。

加重オシレーター値は完成した各ローソク足のために保存されます。シグナルは最後の3つの保存された値を分析し、元のエキスパートの動作を模倣するためにSignal Barパラメーターを介してオプションで最新のバーの数をスキップします。

取引ロジック

  1. すべてのインジケーターと平滑化移動平均が完全に形成されるまで待機します。
  2. 現在の完成したバーの平滑化コンポジットオシレーターを計算し、履歴に追加します。
  3. 3つの履歴値を取得します:currentpreviousprior、インデックスはSignal Barによって制御されます。
  4. 傾斜変化を検出します:
    • 上昇 previous < prior かつ current > previousの場合。
    • 下降 previous > prior かつ current < previousの場合。
  5. 選択したTrend Modeに応じて:
    • Direct:傾斜に沿って取引(上昇 → ロングシグナル、下降 → ショートシグナル)。
    • Against:傾斜に逆らって取引(上昇 → ショート、下降 → ロング)。
  6. エントリー/エグジットスイッチを適用します:
    • 対応するCloseスイッチが有効な場合、反対方向のエクスポージャーをクローズします。
    • Allowスイッチが有効な場合のみ新しいポジションを開きます。注文サイズはVolume + |Position|で、戦略が単一の成行注文でショートからロング(またはその逆)に切り替えられるようにします。
  7. オプションのストップロスとテイクプロフィットの保護は、価格ステップで表された距離を使用してStartProtectionを通じてアクティブ化されます。

パラメーター

グループ 名前 説明
General Candle Type データ購読とインジケーター計算の時間軸。
Trading Trend Mode Directはオシレーターの傾斜に従い、Againstは逆張り取引をします。
Trading Signal Bar スキップする最新の閉じたバーの数(1 = 最後に閉じたバー)。
Oscillator RSI / MFI / WPR / DeMarker Weight 加重ブレンドにおける各オシレーターの相対的な貢献度。ゼロはコンポーネントを無効にします。
Oscillator RSI / MFI / WPR / DeMarker Period 各オシレーターのルックバック長さ。
Oscillator Smoothing Method コンポジットに適用される移動平均(シンプル、指数、スムーズ、加重、Jurik、Kaufman)。
Oscillator Smoothing Length 平滑化平均の期間。
Risk Management Stop Loss Points 価格ステップでの距離;0でストップを無効にします。
Risk Management Take Profit Points 価格ステップでの距離;0で目標を無効にします。
Trading Allow Long/Short Entries 新しいロング/ショートポジションの開設を有効または無効にします。
Trading Close Shorts/Longs on Signal 反対のシグナルが来たときに既存のエクスポージャーをクローズすることを許可します。

すべての数値パラメーターはStrategyParamオブジェクトとして公開され、StockSharp Designer内での最適化が可能です。

使用上の注意

  • 戦略を開始する前にベースのVolumeプロパティを設定します。ポジションを反転するときに成行注文が自動的にスケールします。
  • 戦略はGetWorkingSecurities()が返す1つのローソク足シリーズのみを購読します。
  • 保護ストップはポイント距離を絶対価格値に変換するために銘柄のPriceStepを使用します。
  • Trend ModeAgainstに設定されている場合、シグナルの極性のみが変わります;他のすべてのメカニクスは元のエキスパートアドバイザーと同一です。
  • Williams %RとDeMarkerは元のインジケーターロジックに合わせてRSI/MFIと同じ0..100スケールを共有するように正規化されます。

MQLエキスパートとの違い

  • 元のインジケーターは追加の平滑化タイプ(ParMAJurXVIDYAT3)をサポートしていました。StockSharpでは戦略は高品質な対応物(JurikとKaufman)を提供し、互換性のためにJurikをデフォルトとして使用します。
  • Money Flow Indexは常にローソク足の集計ボリュームを使用します。MetaTraderはティックボリュームと実際のボリュームを切り替えられましたが、この選択はStockSharpではデータソースに依存します。
  • リスク管理はポイントベースのリクエストの代わりにStartProtection(価格ステップベース)を通じて実装されますが、PriceStepが銘柄の契約サイズと一致する場合は同じ動作を提供します。

開始方法

  1. 設定されたローソク足タイプをサポートするポートフォリオとセキュリティに戦略をアタッチします。
  2. インジケーターの重み/期間を調整し、エントリースイッチを有効または無効にします。
  3. 銘柄のボラティリティに最も適した平滑化メソッドと長さを選択します。
  4. 保護が必要な場合は価格ステップでストップロス/テイクプロフィット距離を設定します。
  5. 戦略を実行します;シグナルは完成したローソク足のみで実行され、決定論的な動作を保証します。
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>
/// Trading strategy that combines several oscillators into a weighted composite signal.
/// </summary>
public class WeightOscillatorDirectStrategy : Strategy
{
	/// <summary>
	/// Defines how the strategy reacts to the oscillator slope.
	/// </summary>
	public enum WeightOscillatorTrendModes
	{
		/// <summary>
		/// Trade in the direction of the oscillator slope.
		/// </summary>
		Direct,

		/// <summary>
		/// Trade against the oscillator slope.
		/// </summary>
		Against,
	}

	/// <summary>
	/// Available smoothing methods for the blended oscillator.
	/// </summary>
	public enum WeightOscillatorSmoothingMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed (RMA) moving average.
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Weighted,

		/// <summary>
		/// Jurik moving average.
		/// </summary>
		Jurik,

		/// <summary>
		/// Kaufman adaptive moving average.
		/// </summary>
		Kaufman,
	}
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<WeightOscillatorTrendModes> _trendMode;
	private readonly StrategyParam<int> _signalBar;

	private readonly StrategyParam<decimal> _rsiWeight;
	private readonly StrategyParam<int> _rsiPeriod;

	private readonly StrategyParam<decimal> _mfiWeight;
	private readonly StrategyParam<int> _mfiPeriod;

	private readonly StrategyParam<decimal> _wprWeight;
	private readonly StrategyParam<int> _wprPeriod;

	private readonly StrategyParam<decimal> _deMarkerWeight;
	private readonly StrategyParam<int> _deMarkerPeriod;

	private readonly StrategyParam<WeightOscillatorSmoothingMethods> _smoothingMethod;
	private readonly StrategyParam<int> _smoothingLength;

	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private readonly StrategyParam<bool> _buyOpenEnabled;
	private readonly StrategyParam<bool> _sellOpenEnabled;
	private readonly StrategyParam<bool> _buyCloseEnabled;
	private readonly StrategyParam<bool> _sellCloseEnabled;

	private RelativeStrengthIndex _rsi = null!;
	private MoneyFlowIndex _mfi = null!;
	private WilliamsR _wpr = null!;
	private DeMarker _deMarker = null!;
	private IIndicator _smoothing = null!;

	private readonly List<decimal> _oscillatorHistory = new();

	/// <summary>
	/// Initializes a new instance of the <see cref="WeightOscillatorDirectStrategy"/> class.
	/// </summary>
	public WeightOscillatorDirectStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");

		_trendMode = Param(nameof(TrendMode), WeightOscillatorTrendModes.Direct)
		.SetDisplay("Trend Mode", "Trade with the oscillator slope or against it", "Trading");

		_signalBar = Param(nameof(SignalBar), 2)
		.SetDisplay("Signal Bar", "Number of closed bars to skip before evaluating signals", "Trading")
		.SetRange(1, 5)
		;

		_rsiWeight = Param(nameof(RsiWeight), 1m)
		.SetDisplay("RSI Weight", "Weight of RSI in the composite score", "Oscillator")
		.SetRange(0m, 5m)
		;

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetDisplay("RSI Period", "Number of bars used for RSI", "Oscillator")
		.SetRange(2, 200)
		;

		_mfiWeight = Param(nameof(MfiWeight), 1m)
		.SetDisplay("MFI Weight", "Weight of Money Flow Index", "Oscillator")
		.SetRange(0m, 5m)
		;

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetDisplay("MFI Period", "Number of bars used for MFI", "Oscillator")
		.SetRange(2, 200)
		;

		_wprWeight = Param(nameof(WprWeight), 1m)
		.SetDisplay("WPR Weight", "Weight of Williams %R", "Oscillator")
		.SetRange(0m, 5m)
		;

		_wprPeriod = Param(nameof(WprPeriod), 14)
		.SetDisplay("WPR Period", "Number of bars used for Williams %R", "Oscillator")
		.SetRange(2, 200)
		;

		_deMarkerWeight = Param(nameof(DeMarkerWeight), 1m)
		.SetDisplay("DeMarker Weight", "Weight of DeMarker oscillator", "Oscillator")
		.SetRange(0m, 5m)
		;

		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
		.SetDisplay("DeMarker Period", "Number of bars used for DeMarker", "Oscillator")
		.SetRange(2, 200)
		;

		_smoothingMethod = Param(nameof(SmoothingMethod), WeightOscillatorSmoothingMethods.Jurik)
		.SetDisplay("Smoothing Method", "Moving average applied to the blended oscillator", "Oscillator");

		_smoothingLength = Param(nameof(SmoothingLength), 10)
		.SetDisplay("Smoothing Length", "Length of the smoothing moving average", "Oscillator")
		.SetRange(1, 200)
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
		.SetDisplay("Stop Loss Points", "Protective stop in price steps (0 disables)", "Risk Management")
		.SetRange(0, 10000)
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
		.SetDisplay("Take Profit Points", "Profit target in price steps (0 disables)", "Risk Management")
		.SetRange(0, 20000)
		;

		_buyOpenEnabled = Param(nameof(BuyOpenEnabled), true)
		.SetDisplay("Allow Long Entries", "Enable opening long positions", "Trading");

		_sellOpenEnabled = Param(nameof(SellOpenEnabled), true)
		.SetDisplay("Allow Short Entries", "Enable opening short positions", "Trading");

		_buyCloseEnabled = Param(nameof(BuyCloseEnabled), true)
		.SetDisplay("Close Shorts on Long Signal", "Allow closing shorts when a long signal appears", "Trading");

		_sellCloseEnabled = Param(nameof(SellCloseEnabled), true)
		.SetDisplay("Close Longs on Short Signal", "Allow closing longs when a short signal appears", "Trading");
	}

	/// <summary>
	/// Candle type used for the calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Defines whether the strategy trades with or against the oscillator direction.
	/// </summary>
	public WeightOscillatorTrendModes TrendMode
	{
		get => _trendMode.Value;
		set => _trendMode.Value = value;
	}

	/// <summary>
	/// Number of closed bars to skip when evaluating the composite oscillator.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Weight assigned to RSI.
	/// </summary>
	public decimal RsiWeight
	{
		get => _rsiWeight.Value;
		set => _rsiWeight.Value = value;
	}

	/// <summary>
	/// RSI lookback period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Weight assigned to MFI.
	/// </summary>
	public decimal MfiWeight
	{
		get => _mfiWeight.Value;
		set => _mfiWeight.Value = value;
	}

	/// <summary>
	/// MFI lookback period.
	/// </summary>
	public int MfiPeriod
	{
		get => _mfiPeriod.Value;
		set => _mfiPeriod.Value = value;
	}

	/// <summary>
	/// Weight assigned to Williams %R.
	/// </summary>
	public decimal WprWeight
	{
		get => _wprWeight.Value;
		set => _wprWeight.Value = value;
	}

	/// <summary>
	/// Williams %R lookback period.
	/// </summary>
	public int WprPeriod
	{
		get => _wprPeriod.Value;
		set => _wprPeriod.Value = value;
	}

	/// <summary>
	/// Weight assigned to DeMarker oscillator.
	/// </summary>
	public decimal DeMarkerWeight
	{
		get => _deMarkerWeight.Value;
		set => _deMarkerWeight.Value = value;
	}

	/// <summary>
	/// DeMarker lookback period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the blended oscillator.
	/// </summary>
	public WeightOscillatorSmoothingMethods SmoothingMethod
	{
		get => _smoothingMethod.Value;
		set => _smoothingMethod.Value = value;
	}

	/// <summary>
	/// Length of the smoothing moving average.
	/// </summary>
	public int SmoothingLength
	{
		get => _smoothingLength.Value;
		set => _smoothingLength.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Enables opening long positions.
	/// </summary>
	public bool BuyOpenEnabled
	{
		get => _buyOpenEnabled.Value;
		set => _buyOpenEnabled.Value = value;
	}

	/// <summary>
	/// Enables opening short positions.
	/// </summary>
	public bool SellOpenEnabled
	{
		get => _sellOpenEnabled.Value;
		set => _sellOpenEnabled.Value = value;
	}

	/// <summary>
	/// Enables closing short positions on a long signal.
	/// </summary>
	public bool BuyCloseEnabled
	{
		get => _buyCloseEnabled.Value;
		set => _buyCloseEnabled.Value = value;
	}

	/// <summary>
	/// Enables closing long positions on a short signal.
	/// </summary>
	public bool SellCloseEnabled
	{
		get => _sellCloseEnabled.Value;
		set => _sellCloseEnabled.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_oscillatorHistory.Clear();
	}

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

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_mfi = new MoneyFlowIndex { Length = MfiPeriod };
		_wpr = new WilliamsR { Length = WprPeriod };
		_deMarker = new DeMarker { Length = DeMarkerPeriod };
		_smoothing = CreateSmoothingIndicator();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_rsi, _mfi, _wpr, _deMarker, ProcessCandle)
			.Start();

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

		var step = Security?.PriceStep ?? 1m;
		var takeProfit = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : null;
		var stopLoss = StopLossPoints > 0 ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;

		StartProtection(stopLoss, takeProfit);
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal mfiValue, decimal wprValue, decimal deMarkerValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var totalWeight = RsiWeight + MfiWeight + WprWeight + DeMarkerWeight;
		if (totalWeight <= 0)
		{
		this.LogInfo("Total oscillator weight must be positive to generate signals.");
		return;
		}

		// Williams %R is negative in StockSharp, so shift it into the 0..100 range.
		var normalizedWpr = wprValue + 100m;
		// DeMarker returns 0..1; scale to match other oscillators.
		var normalizedDeMarker = deMarkerValue * 100m;

		var blended = (RsiWeight * rsiValue + MfiWeight * mfiValue + WprWeight * normalizedWpr + DeMarkerWeight * normalizedDeMarker) / totalWeight;

		var smoothedValue = _smoothing.Process(new DecimalIndicatorValue(_smoothing, blended, candle.OpenTime) { IsFinal = true });
		if (!smoothedValue.IsFinal)
		return;

		var oscillator = smoothedValue.ToDecimal();

		_oscillatorHistory.Add(oscillator);
		if (_oscillatorHistory.Count > 512)
		_oscillatorHistory.RemoveAt(0);

		var requiredCount = SignalBar + 2;
		if (_oscillatorHistory.Count < requiredCount)
		return;

		var current = GetHistoryValue(SignalBar);
		var previous = GetHistoryValue(SignalBar + 1);
		var prior = GetHistoryValue(SignalBar + 2);

		// Rising when slope turns up over the last two steps.
		var rising = previous < prior && current > previous;
		// Falling when slope turns down over the last two steps.
		var falling = previous > prior && current < previous;

		bool longSignal;
		bool shortSignal;

		if (TrendMode == WeightOscillatorTrendModes.Direct)
		{
		longSignal = rising;
		shortSignal = falling;
		}
		else
		{
		longSignal = falling;
		shortSignal = rising;
		}

		if (longSignal)
		{
		if (BuyCloseEnabled && Position < 0)
		{
		BuyMarket(Math.Abs(Position));
		}

		if (BuyOpenEnabled && Position <= 0)
		{
		BuyMarket(Volume > 0m ? Volume : 1m);
		}
		}

		if (shortSignal)
		{
		if (SellCloseEnabled && Position > 0)
		{
		SellMarket(Math.Abs(Position));
		}

		if (SellOpenEnabled && Position >= 0)
		{
		SellMarket(Volume > 0m ? Volume : 1m);
		}
		}
	}

	private IIndicator CreateSmoothingIndicator()
	{
		return SmoothingMethod switch
		{
			WeightOscillatorSmoothingMethods.Simple => new SMA { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Exponential => new EMA { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Weighted => new WeightedMovingAverage { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Kaufman => new KaufmanAdaptiveMovingAverage { Length = SmoothingLength },
			_ => new JurikMovingAverage { Length = SmoothingLength },
		};
	}

	private decimal GetHistoryValue(int shift)
	{
		return _oscillatorHistory[_oscillatorHistory.Count - shift];
	}
}