Auf GitHub ansehen

Doji Trader-Strategie

Die Strategie repliziert die Kernlogik des klassischen Doji Trader Expert Advisors. Sie überwacht abgeschlossene Kerzen auf kompakt-körprige Doji-Muster und wartet auf einen Ausbruchs-Schlusskurs jenseits des Doji-Bereichs, um in die Ausbruchsrichtung in den Markt einzusteigen.

Handelslogik

  1. Nur abgeschlossene Kerzen werden verarbeitet. Der Standard-Zeitrahmen ist 1 Stunde, kann aber über den CandleType-Parameter angepasst werden.
  2. Der Handel ist nur erlaubt, wenn die Schlusszeit der letzten Kerze innerhalb des konfigurierbaren Session-Fensters [StartHour, EndHour) in Exchange-Zeit liegt.
  3. Der Algorithmus hält die drei zuletzt abgeschlossenen Kerzen im Speicher. Die gerade geschlossene Kerze wird gegen die zwei Kerzen verglichen, die ihr vorausgingen (-2 und -3).
  4. Eine Kerze gilt als Doji, wenn die absolute Differenz zwischen Eröffnung und Schluss kleiner als MaximumDojiHeight * pip ist, wobei der Pip-Wert aus dem Instrument-Preisschritt abgeleitet wird (3- oder 5-stellige Quotes werden automatisch mit ×10 skaliert).
  5. Wenn die neueste Kerze über dem Hoch des jüngsten qualifizierenden Dojis schließt, öffnet die Strategie (oder wechselt zu) eine Long-Position. Wenn sie unter dem Doji-Tief schließt, öffnet sie eine Short-Position. Kein Trade wird platziert, wenn der Preis innerhalb des Doji-Bereichs bleibt.
  6. Die Positionsgröße wird aus der Volume-Eigenschaft der Strategie entnommen. Wenn ein Umkehrsignal erscheint, sendet der Algorithmus genug Volumen, um die vorherige Position zu schließen und die gewünschte Exposition in der neuen Richtung herzustellen, sodass nur eine Netto-Position offen bleibt.

Risikomanagement

  • Stop-Loss- und Take-Profit-Distanzen werden über StopLossPips und TakeProfitPips in Pips konfiguriert. Ein Wert auf null zu setzen deaktiviert die entsprechende Schutzorder.
  • StartProtection wird einmal beim Start gestartet und verwendet Market-Orders für Ausstiege, sodass das Verhalten die MQL-Implementierung widerspiegelt, die Positionen direkt schloss und wieder öffnete.

Parameter

Name Beschreibung Standard
CandleType Zeitrahmen der verarbeiteten Kerzen. 1-Stunden-Zeitrahmen
StartHour Inklusive Öffnungsstunde des Handelsfensters. 8
EndHour Exklusive Schlussstunde des Handelsfensters. 17
MaximumDojiHeight Maximale Körperhöhe (in Pips) für eine Kerze, um als Doji behandelt zu werden. 1
StopLossPips Schutz-Stop-Distanz in Pips. 50
TakeProfitPips Gewinnziel-Distanz in Pips. 50

Zusätzliche Hinweise

  • Die Strategie geht davon aus, dass das Plattform-Konto Netto-Positionen verwendet. Wenn Ihr Feed gebrochene Pip-Schritte liefert (5- oder 3-stellige Quotes), wird der Pip-Wert mit 10 multipliziert, um traditionellen Pip-Messungen zu entsprechen.
  • Stellen Sie die gewünschte Lot-Größe in der Volume-Eigenschaft vor dem Ausführen der Strategie ein.
  • Es sind keine zusätzlichen Indikatoren erforderlich; die Logik hängt nur von rohen Kerzendaten ab.
  • Es gibt noch keinen Python-Port; nur die C#-Implementierung existiert.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy inspired by the Doji Trader Expert Advisor.
/// Looks for a recent doji candle and trades when the next candle closes beyond the doji range.
/// </summary>
public class DojiTraderStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<decimal> _maximumDojiHeight;
	private readonly StrategyParam<DataType> _candleType;

	private ICandleMessage _previousCandle;
	private ICandleMessage _twoAgoCandle;
	private ICandleMessage _threeAgoCandle;
	private decimal _pipSize;

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

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

	/// <summary>
	/// First trading hour (inclusive) using exchange time.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Last trading hour (exclusive) using exchange time.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Maximum body height for a candle to be considered a doji (in pips).
	/// </summary>
	public decimal MaximumDojiHeight
	{
		get => _maximumDojiHeight.Value;
		set => _maximumDojiHeight.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="DojiTraderStrategy"/>.
	/// </summary>
	public DojiTraderStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Protection")
			.SetRange(0m, 500m);

		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetDisplay("Take Profit", "Take-profit distance in pips", "Protection")
			.SetRange(0m, 500m);

		_startHour = Param(nameof(StartHour), 8)
			.SetDisplay("Start Hour", "Hour when trading becomes active", "Session")
			.SetRange(0, 23);

		_endHour = Param(nameof(EndHour), 17)
			.SetDisplay("End Hour", "Hour when trading stops (exclusive)", "Session")
			.SetRange(1, 24);

		_maximumDojiHeight = Param(nameof(MaximumDojiHeight), 1m)
			.SetDisplay("Doji Height", "Maximum doji body height in pips", "Pattern")
			.SetRange(0.1m, 20m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for doji detection", "General");
	}

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

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

		_previousCandle = null;
		_twoAgoCandle = null;
		_threeAgoCandle = null;
		_pipSize = 0m;
	}

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

		_pipSize = CalculatePipSize();

		// Configure stop-loss and take-profit protection once at start.
		var takeProfitUnit = TakeProfitPips > 0m ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : default;
		var stopLossUnit = StopLossPips > 0m ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : default;

		if (takeProfitUnit != default || stopLossUnit != default)
		{
			StartProtection(takeProfitUnit, stopLossUnit, useMarketOrders: true);
		}
		else
		{
			StartProtection(null, null);
		}

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

	private void ProcessCandle(ICandleMessage candle)
	{
		// Process only finished candles.
		if (candle.State != CandleStates.Finished)
			return;

		// Skip trading outside the configured session window.
		var nextHour = candle.CloseTime.Hour;
		if (nextHour < StartHour || nextHour >= EndHour)
		{
			ShiftHistory(candle);
			return;
		}

		// We need at least three completed candles for pattern detection.
		if (_twoAgoCandle is null)
		{
			ShiftHistory(candle);
			return;
		}

		var pipSize = _pipSize > 0m ? _pipSize : (_pipSize = CalculatePipSize());
		var dojiHeight = MaximumDojiHeight * pipSize;

		var dojiHigh = 0m;
		var dojiLow = 0m;

		// Check the two candles before the current close for the most recent doji.
		if (IsDoji(_twoAgoCandle, dojiHeight))
		{
			dojiHigh = _twoAgoCandle.HighPrice;
			dojiLow = _twoAgoCandle.LowPrice;
		}
		else if (_threeAgoCandle is not null && IsDoji(_threeAgoCandle, dojiHeight))
		{
			dojiHigh = _threeAgoCandle.HighPrice;
			dojiLow = _threeAgoCandle.LowPrice;
		}
		else
		{
			ShiftHistory(candle);
			return;
		}

		var direction = 0;

		// Long signal when the latest candle closes above the doji range.
		if (candle.ClosePrice > dojiHigh)
		{
			direction = 1;
		}
		// Short signal when the latest candle closes below the doji range.
		else if (candle.ClosePrice < dojiLow)
		{
			direction = -1;
		}

		if (direction != 0 && Volume > 0m)
		{
			if (direction > 0)
			{
				// Buy enough volume to cover a short position and establish the target long size.
				var volume = Volume + Math.Max(0m, -Position);
				if (volume > 0m)
					BuyMarket(volume);
			}
			else
			{
				// Sell enough volume to cover a long position and establish the target short size.
				var volume = Volume + Math.Max(0m, Position);
				if (volume > 0m)
					SellMarket(volume);
			}
		}

		ShiftHistory(candle);
	}

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

		var digits = 0;
		var value = step;

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

		var multiplier = (digits == 3 || digits == 5) ? 10m : 1m;
		return step * multiplier;
	}

	private static bool IsDoji(ICandleMessage candle, decimal threshold)
	{
		var body = Math.Abs(candle.OpenPrice - candle.ClosePrice);
		return body <= threshold;
	}

	private void ShiftHistory(ICandleMessage candle)
	{
		// Maintain the three most recent completed candles for doji detection.
		_threeAgoCandle = _twoAgoCandle;
		_twoAgoCandle = _previousCandle;
		_previousCandle = candle;
	}
}