Auf GitHub ansehen

Gewichteter Oszillator Direkt-Strategie

Übersicht

Diese Strategie reproduziert den MetaTrader-Experten Exp_WeightOscillator_Direct in der High-Level-API von StockSharp. Sie kombiniert vier klassische Oszillatoren—RSI, Money Flow Index, Williams %R und DeMarker—zu einem einzigen gewichteten Komposit. Das zusammengesetzte Signal wird durch einen konfigurierbaren gleitenden Durchschnitt geglättet und zur Erkennung von Momentum-Schwingungen verwendet. Ein steigendes Komposit öffnet Long-Trades (oder schließt Shorts), wenn die Strategie im „Direct"-Modus arbeitet, während der „Against"-Modus die Logik für konträres Trading umkehrt.

Indikatorpipeline

  1. Relative Strength Index (RSI) – normalisierte 0..100-Skala.
  2. Money Flow Index (MFI) – liquiditätssensitiver Oszillator im Bereich 0..100.
  3. Williams %R (WPR) – um +100 verschoben, um mit der 0..100-Skala auszurichten.
  4. DeMarker – mit 100 multipliziert, um mit den anderen Oszillatoren übereinzustimmen.
  5. Glättungsdurchschnitt – einer der unterstützten gleitenden Durchschnitte (Einfach, Exponentiell, Geglättet, Gewichtet, Jurik, Kaufman).
  6. Zusammengesetzter Oszillator – gewichteter Durchschnitt der normalisierten Eingaben, geglättet zur Rauschunterdrückung.

Der gewichtete Oszillatorwert wird für jede abgeschlossene Kerze gespeichert. Signale analysieren die letzten drei gespeicherten Werte und können optional eine Anzahl der neuesten Balken über den Parameter Signal Bar überspringen, um das ursprüngliche Expertenverhalten nachzuahmen.

Handelslogik

  1. Warten, bis alle Indikatoren und der Glättungsdurchschnitt vollständig gebildet sind.
  2. Den geglätteten zusammengesetzten Oszillator für den aktuellen abgeschlossenen Balken berechnen und zum Verlauf hinzufügen.
  3. Drei Verlaufswerte abrufen: current, previous, prior, mit durch Signal Bar gesteuerten Indizes.
  4. Steigungsänderungen erkennen:
    • Steigend wenn previous < prior und current > previous.
    • Fallend wenn previous > prior und current < previous.
  5. Abhängig vom ausgewählten Trend Mode:
    • Direct: mit der Steigung handeln (steigend → Long-Signal, fallend → Short-Signal).
    • Against: gegen die Steigung handeln (steigend → Short, fallend → Long).
  6. Die Ein-/Ausstiegsschalter anwenden:
    • Entgegengesetztes Exposure schließen, wenn der entsprechende Close-Schalter aktiviert ist.
    • Neue Positionen nur öffnen, wenn der jeweilige Allow-Schalter aktiviert ist. Die Ordergröße entspricht Volume + |Position|, damit die Strategie mit einer einzelnen Marktorder von Short auf Long (oder umgekehrt) wechseln kann.
  7. Optionale Stop-Loss- und Take-Profit-Schutzmaßnahmen werden über StartProtection mit Abständen in Preisschritten aktiviert.

Parameter

Gruppe Name Beschreibung
General Candle Type Zeitrahmen für Datenabonnement und Indikatorrechnungen.
Trading Trend Mode Direct folgt der Oszillatorsteigung, Against handelt konträr.
Trading Signal Bar Anzahl der zu überspringenden neuesten geschlossenen Balken (1 = letzter geschlossener Balken).
Oscillator RSI / MFI / WPR / DeMarker Weight Relativer Beitrag jedes Oszillators in der gewichteten Mischung. Null deaktiviert eine Komponente.
Oscillator RSI / MFI / WPR / DeMarker Period Lookback-Länge für jeden Oszillator.
Oscillator Smoothing Method Auf das Komposit angewendeter gleitender Durchschnitt (Einfach, Exponentiell, Geglättet, Gewichtet, Jurik, Kaufman).
Oscillator Smoothing Length Periode für den Glättungsdurchschnitt.
Risk Management Stop Loss Points Abstand in Preisschritten; 0 deaktiviert den Stop.
Risk Management Take Profit Points Abstand in Preisschritten; 0 deaktiviert das Ziel.
Trading Allow Long/Short Entries Öffnen neuer Long/Short-Positionen aktivieren oder deaktivieren.
Trading Close Shorts/Longs on Signal Erlauben, bestehendes Exposure zu schließen, wenn ein entgegengesetztes Signal eintrifft.

Alle numerischen Parameter sind als StrategyParam-Objekte exponiert und ermöglichen Optimierung im StockSharp Designer.

Verwendungshinweise

  • Die Basis-Volume-Eigenschaft vor dem Starten der Strategie setzen. Marktorders skalieren automatisch beim Umkehren von Positionen.
  • Die Strategie abonniert genau eine Kerzenserie, die von GetWorkingSecurities() zurückgegeben wird.
  • Schutz-Stops verwenden den PriceStep des Instruments, um Punktabstände in absolute Preiswerte umzuwandeln.
  • Wenn Trend Mode auf Against gesetzt ist, ändert sich nur die Signalpolarität; alle anderen Mechaniken bleiben identisch mit dem ursprünglichen Expertenberater.
  • Williams %R und DeMarker werden normalisiert, um dieselbe 0..100-Skala wie RSI/MFI zu teilen, entsprechend der ursprünglichen Indikatorlogik.

Unterschiede zum MQL-Experten

  • Der ursprüngliche Indikator unterstützte zusätzliche Glättungstypen (ParMA, JurX, VIDYA, T3). In StockSharp bietet die Strategie hochwertige Entsprechungen (Jurik und Kaufman), wobei Jurik für Kompatibilität als Standard verwendet wird.
  • Money Flow Index verwendet immer das aggregierte Kerzenvolumen. MetaTrader konnte zwischen Tick- und echten Volumina wechseln; diese Wahl hängt in StockSharp von der Datenquelle ab.
  • Risikomanagement wird über StartProtection (preisschrittbasiert) statt punktbasierter Anfragen implementiert, liefert aber dasselbe Verhalten, wenn PriceStep der Instrument-Kontraktgröße entspricht.

Einstieg

  1. Die Strategie an ein Portfolio und Wertpapier anhängen, das den konfigurierten Kerzentyp unterstützt.
  2. Indikatorgewichte/-perioden anpassen und Einstiegsschalter aktivieren oder deaktivieren.
  3. Glättungsmethode und -länge wählen, die am besten zur Volatilität des Instruments passen.
  4. Stop-Loss/Take-Profit-Abstände in Preisschritten konfigurieren, wenn Schutz erforderlich ist.
  5. Strategie ausführen; Signale werden nur auf abgeschlossenen Kerzen ausgeführt, was deterministisches Verhalten gewährleistet.
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];
	}
}