Auf GitHub ansehen

Morse Code Strategie

Überblick

Die Morse Code Strategie repliziert den ursprünglichen MetaTrader 5-Experten, der jede abgeschlossene Kerze als "Strich" oder "Punkt" behandelt. Eine bullische Kerze (Schlusskurs größer oder gleich der Eröffnung) wird als 1 kodiert, während eine bärische Kerze (Schlusskurs kleiner oder gleich der Eröffnung) als 0 kodiert wird. Die Strategie durchsucht die neueste Sequenz abgeschlossener Kerzen und vergleicht sie mit einer vom Benutzer ausgewählten binären Maske. Wenn die letzten Kerzen genau der konfigurierten Sequenz entsprechen, öffnet die Strategie eine Position in der gewählten Richtung und hängt sofort sowohl eine Take-Profit- als auch eine Stop-Loss-Order in Pips an.

Die Implementierung basiert ausschließlich auf High-Level-StockSharp-APIs: Kerzenabonnements liefern Daten, Binding übernimmt die Ereignisübermittlung, und das eingebaute Schutzmodul verwaltet Exits. Keine benutzerdefinierten Sammlungen oder direkter Indikatorwertzugriff erforderlich, was die Logik präzise und robust hält.

Musterlogik

  • Kerzen werden nur nach vollständigem Abschluss ausgewertet (CandleStates.Finished).
  • Jede Kerze wird zu einer binären Ziffer:
    • 1 – die Kerze ist bullisch oder neutral (Close >= Open).
    • 0 – die Kerze ist bärisch oder neutral (Close <= Open). Doji-Kerzen entsprechen beiden Ziffern, genau wie im ursprünglichen Experten.
  • Die Maske wird aus der MorsePatternMasks-Enumeration ausgewählt. Sie enthält jede binäre Sequenz der Länge 1 bis 5, die in der MT5-Version erschien (zum Beispiel 000, 1011, 11111).
  • Die Strategie hält ein gleitendes Fenster der neuesten Kerzen. Wenn das neueste Fenster der Maske entspricht, wird das Eintrittssignal ausgelöst.

Dieses Verhalten spiegelt die MT5-Implementierung wider, die CopyRates aufrief und jede Bar zeichenweise mit dem Muster-String verglich.

Handelsworkflow

  1. Den konfigurierten Kerzentyp abonnieren und warten, bis genügend Bars akkumuliert wurden, um die Maskenlänge abzudecken.
  2. Für jede abgeschlossene Kerze:
    • Die internen Masken aktualisieren, die die Kerze als bullisch, bärisch oder neutral klassifizieren.
    • Weitere Prüfungen ignorieren, bis mindestens so viele Kerzen verarbeitet wurden, wie die Maske erfordert.
    • Wenn die neuesten Kerzen genau der ausgewählten Maske entsprechen, die gewünschte Richtung prüfen.
    • Eine Marktorder in Richtung des Signals senden (BuyMarket oder SellMarket). Wenn eine entgegengesetzte Position existiert, schließt die Strategie sie zuerst durch Erhöhung des Ordervolumens und reproduziert damit das Verhalten des ursprünglichen Expertenberaters.
  3. StartProtection hängt sofort einen Stop-Loss und Take-Profit in Preiseinheiten an. Schutzorders werden von der StockSharp-Engine mit Marktausstiegen gehandhabt, um verpasste Fills zu vermeiden.

Parameter

Name Standard Beschreibung
CandleType 5-Minuten-Kerzen (TimeSpan.FromMinutes(5).TimeFrame()) Datentyp für den Aufbau der Morse-Sequenz.
Pattern _0 ("0") Binäre Maske zum Abgleich mit den neuesten Kerzen. Werte kommen direkt aus MorsePatternMasks.
Direction Sides.Buy Ob eine Long- (Buy) oder Short- (Sell) Position geöffnet werden soll, wenn das Muster erscheint.
TakeProfitPips 50 Abstand vom Einstieg zum Take-Profit in Pips. Die Strategie passt sich automatisch an 3- und 5-Dezimal-Forexkurse an, indem sie den Preisschritt mit zehn multipliziert.
StopLossPips 50 Abstand vom Einstieg zum Stop-Loss in Pips, mit derselben Pip-Berechnung wie oben.
Volume (Strategieeigenschaft) benutzerdefiniert Ordergröße in Lots/Kontrakten, entspricht InpLot im MT5-Experten.

Alle Parameter unterstützen das StockSharp-Parameterfenster, können optimiert und vor dem Start der Strategie geändert werden.

Risikomanagement

  • StartProtection hängt beide Ziele mit preisbasierten Offsets aus den Pip-Einstellungen an. Exits werden mit Marktorders ausgeführt, sodass das Verhalten der MT5-Trade-Klasse entspricht, die Stop-Loss- und Take-Profit-Werte beim Positionseinstieg setzte.
  • Da die Strategie nicht pyramidiert, wird ein neuer Trade ignoriert, solange eine bestehende Position in dieselbe Richtung offen ist. Wenn das Muster erscheint, während die entgegengesetzte Richtung gehalten wird, wird das Volumen automatisch erhöht, um die Position umzudrehen.
  • Standard-StockSharp-Logging meldet jeden Einstieg im Strategiejournal.

Verwendungshinweise

  • Die binären Masken sind absichtlich kurz (bis zu fünf Kerzen), um die Logik der ursprünglichen Idee treu zu halten. Erwägen Sie, mehrere Mustermappen durch Portfolio-Orchestrierung zu kombinieren, wenn ein reicheres Vokabular benötigt wird.
  • Die Pip-Konvertierung basiert auf dem Instrumentpreisschritt. Für exotische Symbole mit nicht standardmäßigen Inkrementen können Sie TakeProfitPips und StopLossPips manuell anpassen.
  • Die Strategie filtert nicht nach Tageszeit oder Volatilität. Sie können sie in eine übergeordnete Strategie einbetten, die Sitzungen oder zusätzliche Indikatoren handhabt, wenn erforderlich.
  • Beim Testen stellen Sie sicher, dass die Volume-Eigenschaft der erwarteten Lotgröße entspricht. Der StockSharp-Tester verwendet dieselben Schutzfunktionen und den Orderfluss wie der Live-Modus.

Musterreferenz

Beispiele für Enumerationswerte:

  • _0"0" (einzelne bärische Kerze)
  • _5"11" (zwei bullische Kerzen in Folge)
  • _20"0110" (bärisch-bullische Sequenz, die ein Zig-Zag bildet)
  • _33"00011" (drei bärische Kerzen gefolgt von zwei bullischen)
  • _61"11111" (fünf aufeinanderfolgende bullische Kerzen)

Jede der 62 Masken kann aus dem Parameterfenster ausgewählt werden, um die genaue Morse-Code-Signatur zu reproduzieren, die der Handelsplan erfordert.

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>
/// Strategy that trades when a selected Morse code candle pattern appears.
/// </summary>
public class MorseCodeStrategy : Strategy
{
	/// <summary>
	/// Available Morse code style patterns where '1' is bullish and '0' is bearish.
	/// </summary>
	public enum MorsePatternMasks
	{
		_0 = 0,
		_1 = 1,
		_2 = 2,
		_3 = 3,
		_4 = 4,
		_5 = 5,
		_6 = 6,
		_7 = 7,
		_8 = 8,
		_9 = 9,
		_10 = 10,
		_11 = 11,
		_12 = 12,
		_13 = 13,
		_14 = 14,
		_15 = 15,
		_16 = 16,
		_17 = 17,
		_18 = 18,
		_19 = 19,
		_20 = 20,
		_21 = 21,
		_22 = 22,
		_23 = 23,
		_24 = 24,
		_25 = 25,
		_26 = 26,
		_27 = 27,
		_28 = 28,
		_29 = 29,
		_30 = 30,
		_31 = 31,
		_32 = 32,
		_33 = 33,
		_34 = 34,
		_35 = 35,
		_36 = 36,
		_37 = 37,
		_38 = 38,
		_39 = 39,
		_40 = 40,
		_41 = 41,
		_42 = 42,
		_43 = 43,
		_44 = 44,
		_45 = 45,
		_46 = 46,
		_47 = 47,
		_48 = 48,
		_49 = 49,
		_50 = 50,
		_51 = 51,
		_52 = 52,
		_53 = 53,
		_54 = 54,
		_55 = 55,
		_56 = 56,
		_57 = 57,
		_58 = 58,
		_59 = 59,
		_60 = 60,
		_61 = 61
	}

	private static readonly string[] PatternValues = new[]
	{
		"0",
		"1",
		"00",
		"01",
		"10",
		"11",
		"000",
		"001",
		"010",
		"011",
		"100",
		"101",
		"110",
		"111",
		"0000",
		"0001",
		"0010",
		"0011",
		"0100",
		"0101",
		"0110",
		"0111",
		"1000",
		"1001",
		"1010",
		"1011",
		"1100",
		"1101",
		"1110",
		"1111",
		"00000",
		"00000",
		"00010",
		"00011",
		"00100",
		"00101",
		"00111",
		"00111",
		"01000",
		"01001",
		"01010",
		"01011",
		"01100",
		"01101",
		"01110",
		"01111",
		"10000",
		"10001",
		"10010",
		"10011",
		"10100",
		"10101",
		"10110",
		"10111",
		"11000",
		"11001",
		"11010",
		"11011",
		"11100",
		"11101",
		"11110",
		"11111"
	};

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<MorsePatternMasks> _patternMask;
	private readonly StrategyParam<Sides> _direction;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;

	private string _patternText = string.Empty;
	private int _patternLength;
	private int _maskLimit;
	private int _bullMask;
	private int _bearMask;
	private int _processedBars;
	private decimal _pipSize;
	private decimal _takeProfitDistance;
	private decimal _stopLossDistance;

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

		_patternMask = Param(nameof(Pattern), MorsePatternMasks._14)
			.SetDisplay("Pattern", "Morse code pattern where 1= bullish and 0 = bearish", "Pattern");

		_direction = Param(nameof(Direction), Sides.Buy)
			.SetDisplay("Direction", "Side to trade when the pattern appears", "Trading");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance from entry to take profit in pips", "Risk Management");

		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Distance from entry to stop loss in pips", "Risk Management");
	}

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

	/// <summary>
	/// Selected Morse code pattern.
	/// </summary>
	public MorsePatternMasks Pattern
	{
		get => _patternMask.Value;
		set => _patternMask.Value = value;
	}

	/// <summary>
	/// Trade direction used when the pattern is detected.
	/// </summary>
	public Sides Direction
	{
		get => _direction.Value;
		set => _direction.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

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

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

		_patternText = string.Empty;
		_patternLength = 0;
		_maskLimit = 0;
		_bullMask = 0;
		_bearMask = 0;
		_processedBars = 0;
		_pipSize = 0m;
		_takeProfitDistance = 0m;
		_stopLossDistance = 0m;
	}

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

		_patternText = GetPatternText(Pattern);
		_patternLength = _patternText.Length;
		if (_patternLength == 0)
			throw new InvalidOperationException("Pattern cannot be empty.");

		_maskLimit = (1 << _patternLength) - 1;
		_bullMask = 0;
		_bearMask = 0;
		_processedBars = 0;

		_pipSize = CalculatePipSize();
		_takeProfitDistance = TakeProfitPips * _pipSize;
		_stopLossDistance = StopLossPips * _pipSize;

		// Configure automatic take profit and stop loss handling
		StartProtection(
			takeProfit: new Unit(_takeProfitDistance, UnitTypes.Absolute),
			stopLoss: new Unit(_stopLossDistance, UnitTypes.Absolute),
			useMarketOrders: true);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle)
	{
		// Only act on completed candles
		if (candle.State != CandleStates.Finished)
			return;

		UpdatePatternMasks(candle);

		// Wait until enough candles were processed to match the pattern
		if (_processedBars < _patternLength)
			return;

		if (!IsPatternMatched())
			return;

		var closePrice = candle.ClosePrice;

		if (Direction == Sides.Buy)
		{
			if (Position > 0m)
				return; // Already in a long position

			EnterLong(closePrice);
		}
		else
		{
			if (Position < 0m)
				return; // Already in a short position

			EnterShort(closePrice);
		}
	}

	private static string GetPatternText(MorsePatternMasks mask)
	{
		var index = (int)mask;
		if (index < 0 || index >= PatternValues.Length)
			throw new ArgumentOutOfRangeException(nameof(mask));

		return PatternValues[index];
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;
		if (step <= 0m)
			return 1m;

		var value = step;
		var digits = 0;
		while (value < 1m && digits < 10)
		{
			value *= 10m;
			digits++;
		}

		if (digits == 3 || digits == 5)
			step *= 10m;

		return step;
	}

	private void UpdatePatternMasks(ICandleMessage candle)
	{
		if (_patternLength == 0)
			return;

		var strictBull = candle.ClosePrice > candle.OpenPrice;
		var strictBear = candle.ClosePrice < candle.OpenPrice;

		_bullMask = ((_bullMask << 1) | (strictBull ? 1 : 0)) & _maskLimit;
		_bearMask = ((_bearMask << 1) | (strictBear ? 1 : 0)) & _maskLimit;

		if (_processedBars < _patternLength)
			_processedBars++;
	}

	private bool IsPatternMatched()
	{
		for (var i = 0; i < _patternLength; i++)
		{
			var expected = _patternText[i];
			var isStrictBull = ((_bullMask >> i) & 1) == 1;
			var isStrictBear = ((_bearMask >> i) & 1) == 1;

			if (expected == '1')
			{
				if (isStrictBear)
					return false; // Pattern expects bullish or neutral candle
			}
			else
			{
				if (isStrictBull)
					return false; // Pattern expects bearish or neutral candle
			}
		}

		return true;
	}

	private void EnterLong(decimal price)
	{
		BuyMarket();
		LogInfo($"Entered long position at price {price}.");
	}

	private void EnterShort(decimal price)
	{
		SellMarket();
		LogInfo($"Entered short position at price {price}.");
	}
}