Auf GitHub ansehen

e-TurboFx klassische Strategie

Überblick

Die e-TurboFx Classic-Strategie ist eine direkte C#-Portierung des MetaTrader 4 Expert Advisors, der in MQL/7262/e-TurboFx.mq4 zu finden ist. Es erkennt eine Impulserschöpfung nach einer Reihe starker Kerzen mit zunehmend größeren Körpern und tritt in die entgegengesetzte Richtung ein. Die StockSharp-Version verwendet die High-Level-Strategie API mit Kerzenabonnements, automatischen Schutzanordnungen und UI-freundlichen Parametern.

Handelslogik

  1. Abonnieren Sie den konfigurierten Kerzentyp und prüfen Sie nur fertige Kerzen.
  2. Messen Sie die Körpergröße der Kerze (|close - open|), um eine Ausdehnung zu erkennen.
  3. Pflegen Sie zwei Zähler:
    • Bärische Sequenz – zählt aufeinanderfolgende bärische Kerzen, deren Körper größer sind als die vorherige bärische Kerze.
    • Bullish-Sequenz – zählt aufeinanderfolgende bullische Kerzen, deren Körper größer als die vorherige bullische Kerze ist.
  4. Setzen Sie beide Sequenzen zurück, wenn ein Doji (eröffnet gleich geschlossen) erscheint oder wenn eine Position bereits offen ist. Dies ahmt das ursprüngliche EA-Verhalten nach, bei dem jeweils nur ein Trade aufrecht erhalten wird.
  5. Langer Einstieg: Wenn die Länge der rückläufigen Sequenz den konfigurierten SequenceLength erreicht, senden Sie eine Marktkauforder und setzen Sie die Zähler sofort zurück.
  6. Kurzer Einstieg: Wenn die bullische Sequenzlänge SequenceLength erreicht, senden Sie einen Marktverkaufsauftrag und setzen Sie die Zähler zurück.
  7. Optionale Stop-Loss- und Take-Profit-Level werden aus Punktabständen in StockSharp Preisschritte übersetzt.

Der Algorithmus wartet daher auf eine kapitulationsähnliche Bewegung, bei der jede Kerze in die gleiche Richtung beschleunigt. Die folgende Umkehrreihenfolge versucht, diese extreme Dynamik abzuschwächen.

Details zur Implementierung

  • Verwendet SubscribeCandles().Bind(ProcessCandle), um fertige Kerzen ohne manuelle Indikatorverwaltung zu verarbeiten.
  • Integriert sich in StartProtection, sodass Stop-Loss- und Take-Profit-Abstände in Börsenpreisschritte (UnitTypes.Step) umgewandelt werden.
  • Parameter werden über Param(...) registriert, sodass sie in der Benutzeroberfläche angezeigt werden und optimiert werden können.
  • Die Strategie funktioniert mit jedem Instrument, das ein gültiges PriceStep bereitstellt; andernfalls sollten die Stopp-/Zielentfernungen bei 0 bleiben.
  • Während eine Position aktiv ist, wird die Signalerkennung angehalten und interne Zähler werden gelöscht, genau wie beim ursprünglichen MQL-Skript, das sich weigerte, Aufträge zu stapeln.

Parameter

Parameter Beschreibung Standard
SequenceLength Anzahl der aufeinanderfolgenden fertigen Kerzen mit expandierenden Körpern, die erforderlich sind, um einen Eintrag auszulösen. 3
TakeProfitSteps Take-Profit-Distanz gemessen in Preisschritten (Ticks). 0 deaktiviert das Ziel. 120
StopLossSteps Stop-Loss-Distanz gemessen in Preisschritten (Ticks). 0 deaktiviert den Stopp. 70
TradeVolume Volumen für Markteintritte. Wenn Sie es ändern, wird die Eigenschaft Volume sofort aktualisiert. 0.1
CandleType Für die Analyse verwendeter Kerzenzeitrahmen. Standardmäßig werden 1-Stunden-Kerzen verwendet. 1 hour

Nutzungshinweise

  • Die Strategie erwartet saubere Kerzendaten. Wenn Sie Instrumente oder Zeitrahmen wechseln, lassen Sie die Caches neu aufbauen, sodass die Zähler nur neue Kerzen widerspiegeln.
  • Da das System auf einer strikten Körperexpansion beruht, setzen winzige oder gleichgroße Kerzenkörper die Reihenfolge zurück. Passen Sie SequenceLength an, wenn Sie in lauteren Zeitrahmen handeln.
  • Testen Sie mehrere Zeitrahmen-/Volumenkombinationen im Backtest, um Instrumente zu finden, bei denen Erschöpfungsbewegungen häufig genug sind, um Spreads und Slippage auszugleichen.
  • Überprüfen Sie das Verhalten immer in einer Sandbox-Umgebung, bevor Sie den Live-Handel aktivieren.
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>
/// Momentum exhaustion strategy converted from the original e-TurboFx MQL4 expert adviser.
/// </summary>
public class ETurboFxClassicStrategy : Strategy
{
	private readonly StrategyParam<int> _sequenceLength;
	private readonly StrategyParam<decimal> _takeProfitSteps;
	private readonly StrategyParam<decimal> _stopLossSteps;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private int _bearishSequence;
	private int _bullishSequence;
	private decimal _previousBearishBody;
	private decimal _previousBullishBody;

	/// <summary>
	/// Number of consecutive candles required to trigger a signal.
	/// </summary>
	public int SequenceLength
	{
		get => _sequenceLength.Value;
		set => _sequenceLength.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps (ticks).
	/// A value of zero disables the take profit order.
	/// </summary>
	public decimal TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps (ticks).
	/// A value of zero disables the protective stop.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Order volume sent with each market entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set
		{
			_tradeVolume.Value = value;
			Volume = value;
		}
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="ETurboFxClassicStrategy" /> class.
	/// </summary>
	public ETurboFxClassicStrategy()
	{
		_sequenceLength = Param(nameof(SequenceLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Sequence Length", "Number of consecutive finished candles analysed for pattern detection", "Trading Rules")
			
			.SetOptimize(2, 6, 1);

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 120m)
			.SetNotNegative()
			.SetDisplay("Take Profit (steps)", "Take profit distance in price steps (ticks)", "Risk Management")
			
			.SetOptimize(60m, 180m, 20m);

		_stopLossSteps = Param(nameof(StopLossSteps), 70m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps (ticks)", "Risk Management")
			
			.SetOptimize(40m, 120m, 10m);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume used for market entries", "Trading Rules")
			
			.SetOptimize(0.1m, 0.5m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame of the candles analysed by the strategy", "Market Data");
	}

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

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

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

		ResetState();
		Volume = TradeVolume;

		var takeProfitUnit = CreateStepUnit(TakeProfitSteps);
		var stopLossUnit = CreateStepUnit(StopLossSteps);

		if (takeProfitUnit != null || stopLossUnit != null)
		{
			// Configure protection block only once when the strategy starts.
			StartProtection(
				takeProfit: takeProfitUnit,
				stopLoss: stopLossUnit,
				isStopTrailing: false,
				useMarketOrders: true);
		}

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

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

	private Unit CreateStepUnit(decimal steps)
	{
		if (steps <= 0)
			return null;

		return new Unit(steps, UnitTypes.Absolute);
	}

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

		// no indicators to check

		if (Position != 0)
		{
			// Ignore new signals while a position is active and rebuild the sequences afterwards.
			ResetState();
			return;
		}

		var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);

		if (candle.ClosePrice < candle.OpenPrice)
		{
			HandleBearishCandle(bodySize);
		}
		else if (candle.ClosePrice > candle.OpenPrice)
		{
			HandleBullishCandle(bodySize);
		}
		else
		{
			// Flat candles break both sequences because momentum stalled.
			ResetState();
		}
	}

	private void HandleBearishCandle(decimal bodySize)
	{
		ResetBullishSequence();

		if (bodySize <= 0)
		{
			ResetBearishSequence();
			return;
		}

		if (_bearishSequence == 0 || bodySize > _previousBearishBody)
		{
			// Body expanded compared to the previous bearish candle.
			_bearishSequence++;
		}
		else
		{
			// Restart the sequence when the body fails to expand.
			_bearishSequence = 1;
		}

		_previousBearishBody = bodySize;

		if (_bearishSequence >= SequenceLength)
		{
			// A string of expanding bearish candles hints a bullish reversal.
			BuyMarket();
			ResetBearishSequence();
		}
	}

	private void HandleBullishCandle(decimal bodySize)
	{
		ResetBearishSequence();

		if (bodySize <= 0)
		{
			ResetBullishSequence();
			return;
		}

		if (_bullishSequence == 0 || bodySize > _previousBullishBody)
		{
			// Body expanded compared to the previous bullish candle.
			_bullishSequence++;
		}
		else
		{
			// Restart the sequence when the body fails to expand.
			_bullishSequence = 1;
		}

		_previousBullishBody = bodySize;

		if (_bullishSequence >= SequenceLength)
		{
			// A string of expanding bullish candles hints a bearish reversal.
			SellMarket();
			ResetBullishSequence();
		}
	}

	private void ResetBearishSequence()
	{
		_bearishSequence = 0;
		_previousBearishBody = 0m;
	}

	private void ResetBullishSequence()
	{
		_bullishSequence = 0;
		_previousBullishBody = 0m;
	}

	private void ResetState()
	{
		ResetBearishSequence();
		ResetBullishSequence();
	}
}