Auf GitHub ansehen

e-TurboFx Classic Strategy

Overview

The e-TurboFx Classic strategy is a direct C# port of the MetaTrader 4 expert adviser found in MQL/7262/e-TurboFx.mq4. It detects momentum exhaustion after a streak of strong candles with progressively larger bodies and enters in the opposite direction. The StockSharp version uses the high-level strategy API with candle subscriptions, automatic protective orders and UI-friendly parameters.

Trading logic

  1. Subscribe to the configured candle type and inspect only finished candles.
  2. Measure the candle body size (|close - open|) to detect expansion.
  3. Maintain two counters:
    • Bearish sequence – counts consecutive bearish candles with bodies larger than the previous bearish candle.
    • Bullish sequence – counts consecutive bullish candles with bodies larger than the previous bullish candle.
  4. Reset both sequences when a doji (open equals close) appears or whenever a position is already open. This mimics the original EA behaviour that keeps only one trade at a time.
  5. Long entry: when the bearish sequence length reaches the configured SequenceLength, send a market buy order and immediately reset the counters.
  6. Short entry: when the bullish sequence length reaches SequenceLength, send a market sell order and reset the counters.
  7. Optional stop-loss and take-profit levels are translated from point distances into StockSharp price steps.

The algorithm therefore waits for a capitulation-like move where each candle accelerates in the same direction. The following reversal order attempts to fade that extreme momentum.

Implementation details

  • Uses SubscribeCandles().Bind(ProcessCandle) to process finished candles without manual indicator management.
  • Integrates with StartProtection so that stop-loss and take-profit distances are converted into exchange price steps (UnitTypes.Step).
  • Parameters are registered through Param(...) so they appear in the UI and can be optimised.
  • The strategy works with any instrument that exposes a valid PriceStep; otherwise stop/target distances should stay at 0.
  • While a position is active the signal detection is paused and internal counters are cleared, just like the original MQL script which refused to stack orders.

Parameters

Parameter Description Default
SequenceLength Number of consecutive finished candles with expanding bodies required to trigger an entry. 3
TakeProfitSteps Take-profit distance measured in price steps (ticks). 0 disables the target. 120
StopLossSteps Stop-loss distance measured in price steps (ticks). 0 disables the stop. 70
TradeVolume Volume for market entries. Changing it updates the Volume property instantly. 0.1
CandleType Candle timeframe used for analysis. Defaults to 1-hour candles. 1 hour

Usage notes

  • The strategy expects clean candle data. When switching instruments or timeframes allow the caches to rebuild so that the counters reflect fresh candles only.
  • Because the system relies on strict body expansion, tiny or equal candle bodies reset the sequence. Adjust SequenceLength when trading on noisier timeframes.
  • Backtest multiple timeframe/volume combinations to find instruments where exhaustion moves are frequent enough to compensate for spreads and slippage.
  • Always validate the behaviour in a sandbox environment before enabling live trading.
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();
	}
}