Auf GitHub ansehen

Doji-Pfeile-Strategie

Konzept

Die Doji-Pfeile-Strategie konvertiert den ursprünglichen MetaTrader "Doji Arrows" Expert Advisor in die StockSharp-High-Level-API. Die Idee ist, auf eine echte Doji-Kerze zu warten und dann einen Ausbruch aus ihrem Bereich zu handeln. Eine Doji-Kerze repräsentiert Unentschlossenheit, daher deutet ein Schluss über dem Doji-Hoch auf bullische Stärke hin, während ein Schluss unter dem Doji-Tief bärische Kontrolle anzeigt.

  1. Die Strategie verarbeitet nur abgeschlossene Kerzen aus dem konfigurierten CandleType-Abonnement.
  2. Die vorherige Kerze wird analysiert, um zu bestimmen, ob es sich um ein Doji handelt. Die Kerze wird als Doji klassifiziert, wenn die absolute Differenz zwischen Eröffnung und Schluss kleiner oder gleich DojiBodyPoints multipliziert mit dem Sicherheitspreisschritt ist. Wenn der Parameter auf 0 gesetzt ist, wird ein einzelner Preisschritt als Toleranz verwendet, was der strikten Gleichheitsprüfung in der MQL5-Version entspricht.
  3. Wenn die nächste Kerze über dem Doji-Hoch schließt, sendet die Strategie eine Market-Kauforder. Wenn die nächste Kerze unter dem Doji-Tief schließt, wird eine Market-Verkauforder ausgegeben. Bestehende entgegengesetzte Positionen werden automatisch durch das Market-Order-Volumen ausgeglichen.

Diese Sequenz spiegelt den ursprünglichen Expert Advisor wider, der einmal bei der Eröffnung jedes neuen Balkens reagierte.

Risikomanagement

Die konvertierte Implementierung behält das Schutzverhalten des MQL-Skripts:

  • Stop Loss: StopLossPoints kontrolliert, wie weit in Preisschritten der anfängliche Stop Loss vom Einstiegspreis entfernt platziert wird. Auf null setzen, um den festen Stop zu deaktivieren.
  • Take Profit: TakeProfitPoints definiert die Distanz zum Gewinnziel in Preisschritten. Auf null setzen, um das Ziel zu überspringen.
  • Trailing Stop: TrailingStopPoints und TrailingStepPoints reproduzieren den Trailing-Mechanismus. Sobald der Trade mehr als TrailingStopPoints + TrailingStepPoints gewinnt, wird das Stop-Niveau auf TrailingStopPoints vom letzten Schluss (höchster Schluss für Long, niedrigster Schluss für Short) gezogen. Trailing ist optional und aktiviert sich nur, wenn TrailingStopPoints größer als null ist.

Stops und Ziele werden bei jeder abgeschlossenen Kerze bewertet. Wenn ein Niveau verletzt wird (unter Verwendung von Kerzenhoch/-tief), verlässt die Strategie die Position mit einer Market Order und setzt den Schutzstatus zurück.

Parameter

Parameter Standard Beschreibung
StopLossPoints 30 Distanz des anfänglichen Stop Loss in Preisschritten.
TakeProfitPoints 90 Distanz des Take Profit in Preisschritten.
TrailingStopPoints 15 Vom Trailing Stop verwendete Distanz in Preisschritten.
TrailingStepPoints 5 Zusätzlicher Gewinn erforderlich, bevor der Trailing Stop angepasst wird, in Preisschritten.
DojiBodyPoints 1 Maximal erlaubte Körpergröße der vorherigen Kerze in Preisschritten, um sie als Doji zu behandeln. 0 verwendet einen Preisschritt als Toleranz.
CandleType 1 Stunde Kerzentyp für die Signalgenerierung abonniert.

Implementierungshinweise

  • Die Strategie abonniert Kerzen durch SubscribeCandles(CandleType).Bind(ProcessCandle) und behält nur die letzte abgeschlossene Kerze im Speicher.
  • Der Sicherheitspreisschritt wird über Security?.PriceStep abgerufen. Wenn er nicht verfügbar ist, wird ein Fallback-Wert von 1 verwendet, damit die Strategie weiterhin mit synthetischen oder historischen Daten arbeiten kann.
  • Schutzlevels werden nach jedem Einstieg neu berechnet, und die Trailing-Logik kann einen Stop erstellen, auch wenn der feste Stop Loss deaktiviert ist (was dem MQL-Verhalten entspricht, bei dem der Trailing Stop von null aus starten konnte).
  • Alle Aktionen werden mit Market Orders ausgeführt, um mit dem ursprünglichen Advisor, der auf sofortige Marktausführung angewiesen war, konform zu bleiben.

Nutzungstipps

  1. Konfigurieren Sie die Eigenschaften Security, Portfolio und Volume, bevor Sie die Strategie starten.
  2. Passen Sie die punktbasierten Parameter entsprechend dem gehandelten Instrument an. Für mit fraktionalen Pips quotierte Instrumente erhöhen Sie die Werte, um zur Tick-Größe des Brokers zu passen.
  3. Kombinieren Sie die Strategie mit StockSharp-Risikokontrollen oder Analysemodule, wenn eine fortgeschrittenere Positionsgrößenbestimmung erforderlich ist, da die Konvertierung die Fixed-Volume-Logik des ursprünglichen Codes beibehält.
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>
/// Doji breakout strategy with optional fixed and trailing protection.
/// </summary>
public class DojiArrowsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _trailingStepPoints;
	private readonly StrategyParam<decimal> _dojiBodyPoints;
	private readonly StrategyParam<DataType> _candleType;

	private bool _hasPreviousCandle;
	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	public decimal TrailingStepPoints
	{
		get => _trailingStepPoints.Value;
		set => _trailingStepPoints.Value = value;
	}

	public decimal DojiBodyPoints
	{
		get => _dojiBodyPoints.Value;
		set => _dojiBodyPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DojiArrowsStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
			.SetNotNegative()
			.SetDisplay("Stop Loss Points", "Stop loss distance in price steps.", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 90m)
			.SetNotNegative()
			.SetDisplay("Take Profit Points", "Take profit distance in price steps.", "Risk")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop Points", "Trailing distance in price steps.", "Risk")
			;

		_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Trailing Step Points", "Minimum profit before the trailing stop moves.", "Risk")
			;

		_dojiBodyPoints = Param(nameof(DojiBodyPoints), 1m)
			.SetNotNegative()
			.SetDisplay("Doji Body Points", "Maximum difference between open and close to treat the candle as a doji.", "Pattern")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for signal generation.", "General");
	}

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

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

		_hasPreviousCandle = false;
		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		ResetProtection();
	}

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

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		ManageActivePosition(candle);

		if (!_hasPreviousCandle)
		{
			CachePreviousCandle(candle);
			return;
		}

		var step = Security?.PriceStep ?? 1m;
		var tolerance = DojiBodyPoints <= 0m ? step : DojiBodyPoints * step;
		var bodySize = Math.Abs(_prevOpen - _prevClose);
		var isDoji = bodySize <= tolerance;

		var breakoutUp = isDoji && candle.ClosePrice > _prevHigh;
		var breakoutDown = isDoji && candle.ClosePrice < _prevLow;

		if (breakoutUp && Position == 0)
		{
			BuyMarket();
		}
		else if (breakoutDown && Position == 0)
		{
			SellMarket();
		}

		CachePreviousCandle(candle);
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position == 0)
			return;

		var step = Security?.PriceStep ?? 1m;
		var trailingDistance = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
		var trailingStep = TrailingStepPoints > 0m ? TrailingStepPoints * step : 0m;

		if (Position > 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = candle.ClosePrice - _entryPrice.Value;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice - trailingDistance;

					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
		else if (Position < 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = _entryPrice.Value - candle.ClosePrice;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice + trailingDistance;

					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
	}

	private void InitializeProtection(decimal price, bool isLong, decimal step)
	{
		_entryPrice = price;

		if (StopLossPoints > 0m)
		{
			var offset = StopLossPoints * step;
			_stopPrice = isLong ? price - offset : price + offset;
		}
		else
		{
			_stopPrice = null;
		}

		if (TakeProfitPoints > 0m)
		{
			var offset = TakeProfitPoints * step;
			_takePrice = isLong ? price + offset : price - offset;
		}
		else
		{
			_takePrice = null;
		}
	}

	private void ResetProtection()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

	private void CachePreviousCandle(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}
}