Auf GitHub ansehen

Master Mind Triple WPR-Strategie

Überblick

  • Port des MetaTrader 4 Expert Advisors MasterMind3CE (Ordner MQL/8458).
  • Verwendet vier Williams %R-Indikatoren mit den Perioden 26, 27, 29 und 30, um extreme überkaufte/überverkaufte Bedingungen zu erkennen.
  • Konzipiert für Mean-Reversion-Einstiege: Kaufen nach einem tiefen Ausverkauf, Verkaufen nach einer überzogenen Rallye.
  • Beinhaltet konfigurierbare Stop-Loss-, Take-Profit- und optionale Trailing-Stop-Logik, ausgedrückt in Instrumentenpreisschritten.
  • Funktioniert in jedem Zeitrahmen, der vom verbundenen StockSharp-Terminal unterstützt wird; Der Standardwert sind 15-Minuten-Kerzen.

Handelslogik

Indikatoren

  • WilliamsR(26) – extrem schneller Oszillator.
  • WilliamsR(27) – schneller Oszillator zur Bestätigung.
  • WilliamsR(29) – mittlerer Oszillator, der das Signal glättet.
  • WilliamsR(30) – langsamer Oszillator, der extreme Werte über mehrere Lookbacks hinweg erfordert.

Alle vier Oszillatoren müssen gebildet werden. Das Abonnement verarbeitet nur fertige Kerzen, die dem TradeAtCloseBar = true-Verhalten des ursprünglichen Experten entsprechen.

Teilnahmebedingungen

  • Langer Eintrag: Alle vier Williams %R-Werte liegen unter oder gleich OversoldLevel (Standardwert -99.99). Die Strategie zielt auf eine Long-Position von TradeVolume ab. Wenn eine Short-Position offen ist, wird sie geschlossen und in einer einzigen Marktorder, die so dimensioniert ist, dass sie das Zielengagement erreicht, in eine Long-Position umgewandelt.
  • Kurzer Eintrag: Alle vier Williams %R-Werte liegen über oder gleich OverboughtLevel (Standardwert -0.01). Die Strategie zielt auf eine Short-Position von TradeVolume ab und schließt zunächst alle bestehenden Long-Positionen.

Ausstiegsbedingungen

  • Signalbasierter Ausstieg: Wenn eine Long-Position offen ist und eine Short-Einstiegsbedingung auftritt, schließt/dreht die Strategie die Position (und umgekehrt).
  • Schützender Stop-Loss: Optionaler Preisschrittabstand vom durchschnittlichen Einstiegspreis. Ein Treffer auf das Hoch/Tief der Kerze löst einen Marktausstieg aus.
  • Take-Profit: Optionales Preissprungziel vom durchschnittlichen Einstiegspreis. Sobald die Kerze erreicht ist, wird die Position geschlossen.
  • Trailing-Stop: Optionale Trailing-Logik, die startet, sobald sich der Preis um TrailingStopSteps + TrailingStepSteps zu seinen Gunsten bewegt. Der Stop wird dann TrailingStopSteps vom letzten Schlusskurs entfernt gehalten und erhöht sich erst, wenn er um mindestens TrailingStepSteps verbessert wird.

Risikomanagement

Preisabstände werden in den Preisschritten des Instruments angegeben. Bei PriceStep = 0.0001 und StopLossSteps = 2000 wird der Stopp beispielsweise 0,2000 vom Eingang entfernt platziert. Die Strategie berechnet den durchschnittlichen Einstiegspreis neu, wenn sie in die gleiche Richtung skaliert, um das Risikoniveau konstant zu halten. Trailing Stops sind deaktiviert, es sei denn, sowohl TrailingStopSteps als auch TrailingStepSteps sind positiv.

Parameter

Name Beschreibung Standard
TradeVolume Angestrebte Nettopositionsgröße (Lots/Kontrakte). 1
OversoldLevel Williams %R Schwellenwert, der überverkaufte Bedingungen bestätigt. -99.99
OverboughtLevel Williams %R-Schwellenwert, der überkaufte Bedingungen bestätigt. -0.01
StopLossSteps Stop-Loss-Distanz in PriceStep Einheiten. Stellen Sie 0 auf Deaktivieren ein. 2000
TakeProfitSteps Take-Profit-Distanz in PriceStep Einheiten. Stellen Sie 0 auf Deaktivieren ein. 0
TrailingStopSteps Trailing-Stop-Distanz in PriceStep Einheiten. Erfordert TrailingStepSteps > 0. 0
TrailingStepSteps Minimale Verbesserung, bevor der Trailing Stop verschoben wird (in PriceStep Einheiten). 1
CandleType Kerzendatentyp/Zeitrahmen, der von der Strategie verarbeitet wird. TimeFrame(15m)

Konvertierungshinweise

  • Auf Warnungen, akustische Benachrichtigungen, Protokollierung in Dateien und E-Mail-Funktionen des MQL-Experten wird bewusst verzichtet; Stattdessen können StockSharp-Protokolle verwendet werden.
  • Der ursprüngliche Berater erlaubte den Handel vor Börsenschluss. Der Port behält die standardmäßige „Trade on Close“-Logik bei, indem er nur fertige Kerzen verarbeitet.
  • Magische Zahlen, wiederholte Bestellwiederholungen und manuelle Objektzeichnung waren spezifisch für MetaTrader und haben keine direkten StockSharp-Entsprechungen, daher wurden sie entfernt.
  • Das Risikomanagement wird innerhalb der Strategie konsolidiert, anstatt externe Auftragsänderungsschleifen zu verwenden. Stop/Take-Checks werden für jede Kerze ausgewertet.

Nutzung

  1. Konfigurieren Sie das gewünschte Instrument und den gewünschten Zeitrahmen, passend zum Diagramm, dem der Experte ursprünglich zugeordnet war.
  2. Passen Sie Schwellenwerte oder Risikoparameter an, wenn das Instrument ein anderes Volatilitätsprofil aufweist.
  3. Starten Sie die Strategie; Es abonniert die angegebene Kerzenserie, überwacht Williams %R-Extreme und verwaltet die Positionen entsprechend.
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>
/// MasterMind3 port that relies on four Williams %R oscillators hitting extremes.
/// </summary>
public class MasterMindTripleWprStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<int> _stopLossSteps;
	private readonly StrategyParam<int> _takeProfitSteps;
	private readonly StrategyParam<int> _trailingStopSteps;
	private readonly StrategyParam<int> _trailingStepSteps;
	private readonly StrategyParam<DataType> _candleType;

	private WilliamsR _wpr26 = null!;
	private WilliamsR _wpr27 = null!;
	private WilliamsR _wpr29 = null!;
	private WilliamsR _wpr30 = null!;

	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortTakePrice;

	/// <summary>
	/// Target net position volume.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Threshold that defines oversold conditions for all oscillators.
	/// </summary>
	public decimal OversoldLevel
	{
		get => _oversoldLevel.Value;
		set => _oversoldLevel.Value = value;
	}

	/// <summary>
	/// Threshold that defines overbought conditions for all oscillators.
	/// </summary>
	public decimal OverboughtLevel
	{
		get => _overboughtLevel.Value;
		set => _overboughtLevel.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in instrument price steps.
	/// </summary>
	public int StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in instrument price steps.
	/// </summary>
	public int TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Trailing-stop distance expressed in instrument price steps.
	/// </summary>
	public int TrailingStopSteps
	{
		get => _trailingStopSteps.Value;
		set => _trailingStopSteps.Value = value;
	}

	/// <summary>
	/// Minimal improvement in price steps required before trailing stop is moved.
	/// </summary>
	public int TrailingStepSteps
	{
		get => _trailingStepSteps.Value;
		set => _trailingStepSteps.Value = value;
	}

	/// <summary>
	/// Candle series processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="MasterMindTripleWprStrategy"/>.
	/// </summary>
	public MasterMindTripleWprStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Trade Volume", "Target net position volume", "Trading")
		;

		_oversoldLevel = Param(nameof(OversoldLevel), -99.99m)
		.SetDisplay("Oversold Level", "All Williams %R must be below this level", "Signals")
		;

		_overboughtLevel = Param(nameof(OverboughtLevel), -0.01m)
		.SetDisplay("Overbought Level", "All Williams %R must be above this level", "Signals")
		;

		_stopLossSteps = Param(nameof(StopLossSteps), 2000)
		.SetDisplay("Stop Loss (steps)", "Protective stop distance in price steps", "Risk");

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 0)
		.SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk");

		_trailingStopSteps = Param(nameof(TrailingStopSteps), 0)
		.SetDisplay("Trailing Stop (steps)", "Trailing stop distance in price steps", "Risk");

		_trailingStepSteps = Param(nameof(TrailingStepSteps), 1)
		.SetDisplay("Trailing Step (steps)", "Minimal improvement before trailing adjusts", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe to process", "General");
	}

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

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

		_longEntryPrice = null;
		_shortEntryPrice = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakePrice = null;
		_shortTakePrice = null;
	}

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

		Volume = TradeVolume;

		// Initialize Williams %R oscillators with the original periods.
		_wpr26 = new WilliamsR { Length = 26 };
		_wpr27 = new WilliamsR { Length = 27 };
		_wpr29 = new WilliamsR { Length = 29 };
		_wpr30 = new WilliamsR { Length = 30 };

		// Subscribe to candle data and bind all four oscillators.
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_wpr26, _wpr27, _wpr29, _wpr30, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _wpr26);
			DrawIndicator(area, _wpr27);
			DrawIndicator(area, _wpr29);
			DrawIndicator(area, _wpr30);
			DrawOwnTrades(area);
		}

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle, decimal wpr26Value, decimal wpr27Value, decimal wpr29Value, decimal wpr30Value)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!_wpr26.IsFormed || !_wpr27.IsFormed || !_wpr29.IsFormed || !_wpr30.IsFormed)
		return;

		// Update trailing stops before evaluating exits.
		UpdateTrailingStops(candle);
		TryCloseByRisk(candle);

		var oversoldLevel = OversoldLevel;
		var overboughtLevel = OverboughtLevel;

		var isOversold = wpr26Value <= oversoldLevel &&
		wpr27Value <= oversoldLevel &&
		wpr29Value <= oversoldLevel &&
		wpr30Value <= oversoldLevel;

		var isOverbought = wpr26Value >= overboughtLevel &&
		wpr27Value >= overboughtLevel &&
		wpr29Value >= overboughtLevel &&
		wpr30Value >= overboughtLevel;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (isOversold)
		{
			OpenLong(candle);
		}
		else if (isOverbought)
		{
			OpenShort(candle);
		}
	}

	private void OpenLong(ICandleMessage candle)
	{
		var target = TradeVolume;
		if (target <= 0m)
		return;

		var current = Position;
		var difference = target - current;
		if (difference <= 0m)
		return;

		var existingLong = Math.Max(current, 0m);

		// A single market order flips the position when needed.
		BuyMarket(difference);

		var entryPrice = candle.ClosePrice;
		UpdateLongState(existingLong, difference, entryPrice);
	}

	private void OpenShort(ICandleMessage candle)
	{
		var target = -TradeVolume;
		if (target >= 0m)
		return;

		var current = Position;
		var difference = current - target;
		if (difference <= 0m)
		return;

		var existingShort = Math.Max(-current, 0m);

		SellMarket(difference);

		var entryPrice = candle.ClosePrice;
		UpdateShortState(existingShort, difference, entryPrice);
	}

	private void TryCloseByRisk(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
			{
				// Stop-loss for long positions.
				SellMarket(Position);
				ResetLongState();
				return;
			}

			if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
			{
				// Take-profit for long positions.
				SellMarket(Position);
				ResetLongState();
			}
		}
		else if (Position < 0m)
		{
			var shortVolume = Math.Abs(Position);

			if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
			{
				BuyMarket(shortVolume);
				ResetShortState();
				return;
			}

			if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
			{
				BuyMarket(shortVolume);
				ResetShortState();
			}
		}
	}

	private void UpdateTrailingStops(ICandleMessage candle)
	{
		if (TrailingStopSteps <= 0 || TrailingStepSteps <= 0)
		return;

		var step = GetStepSize();
		var trailingDistance = TrailingStopSteps * step;
		var trailingStep = TrailingStepSteps * step;

		if (Position > 0m && _longEntryPrice.HasValue)
		{
			var profit = candle.ClosePrice - _longEntryPrice.Value;
			if (profit > trailingDistance + trailingStep)
			{
				var newStop = candle.ClosePrice - trailingDistance;

				if (!_longStopPrice.HasValue || newStop > _longStopPrice.Value + trailingStep)
				{
					_longStopPrice = newStop;
				}
			}
		}
		else if (Position < 0m && _shortEntryPrice.HasValue)
		{
			var profit = _shortEntryPrice.Value - candle.ClosePrice;
			if (profit > trailingDistance + trailingStep)
			{
				var newStop = candle.ClosePrice + trailingDistance;

				if (!_shortStopPrice.HasValue || newStop < _shortStopPrice.Value - trailingStep)
				{
					_shortStopPrice = newStop;
				}
			}
		}
	}

	private void UpdateLongState(decimal existingVolume, decimal addedVolume, decimal entryPrice)
	{
		var total = existingVolume + addedVolume;
		if (total <= 0m)
		{
			ResetLongState();
			return;
		}

		if (_longEntryPrice is null || existingVolume <= 0m)
		{
			_longEntryPrice = entryPrice;
		}
		else
		{
			_longEntryPrice = ((_longEntryPrice.Value * existingVolume) + entryPrice * addedVolume) / total;
		}

		var step = GetStepSize();

		if (StopLossSteps > 0)
		{
			_longStopPrice = _longEntryPrice.Value - StopLossSteps * step;
		}
		else if (TrailingStopSteps <= 0)
		{
			_longStopPrice = null;
		}

		_longTakePrice = TakeProfitSteps > 0 ? _longEntryPrice.Value + TakeProfitSteps * step : null;

		ResetShortState();
	}

	private void UpdateShortState(decimal existingVolume, decimal addedVolume, decimal entryPrice)
	{
		var total = existingVolume + addedVolume;
		if (total <= 0m)
		{
			ResetShortState();
			return;
		}

		if (_shortEntryPrice is null || existingVolume <= 0m)
		{
			_shortEntryPrice = entryPrice;
		}
		else
		{
			_shortEntryPrice = ((_shortEntryPrice.Value * existingVolume) + entryPrice * addedVolume) / total;
		}

		var step = GetStepSize();

		if (StopLossSteps > 0)
		{
			_shortStopPrice = _shortEntryPrice.Value + StopLossSteps * step;
		}
		else if (TrailingStopSteps <= 0)
		{
			_shortStopPrice = null;
		}

		_shortTakePrice = TakeProfitSteps > 0 ? _shortEntryPrice.Value - TakeProfitSteps * step : null;

		ResetLongState();
	}

	private void ResetLongState()
	{
		_longEntryPrice = null;
		_longStopPrice = null;
		_longTakePrice = null;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
	}

	private decimal GetStepSize()
	{
		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? step : 1m;
	}
}