Ver no GitHub

e-TurboFx Momentum Strategy

Overview

The e-TurboFx Momentum Strategy is a direct port of the original MetaTrader 4 expert advisor "e-TurboFx". The system scans the most recent finished candles and looks for directional stretches where the candle bodies keep expanding. Consecutive bearish candles with growing body size signal a potential capitulation that can be faded with a long entry, whereas consecutive bullish candles with expanding bodies hint at an overextended rally that may be sold short. The StockSharp implementation keeps the logic event-driven through candle subscriptions and automatically attaches optional stop-loss and take-profit protection.

Trading logic

  1. Subscribe to a configurable candle type (time frame) and process only finished candles.
  2. Track two separate sequences: one for bearish candles and another for bullish candles.
  3. For each candle, measure the absolute body size (|Close - Open|).
  4. Reset the opposite-direction sequence as soon as a candle closes in the other direction.
  5. Within each sequence require strictly expanding bodies — every new candle must have a larger body than the previous one. Any contraction restarts the sequence counter from 1.
  6. When the number of candles in a sequence reaches DepthAnalysis, trigger a market entry in the opposite direction of the last sequence (buy after bearish streaks, sell after bullish streaks).
  7. Once a position is open, pause signal detection until the strategy returns to a flat position. Built-in StartProtection manages optional stop-loss and take-profit distances expressed in price steps (ticks).

This behaviour reproduces the MQL4 algorithm where the expert adviser checked the last N closed candles and confirmed that all bodies were aligned in the same direction and that each body was larger than the body of the next older candle.

Implementation details

  • Uses the high-level candle subscription API with SubscribeCandles and Bind to stay compliant with the project guidelines.
  • Keeps only scalar fields (_bearishSequence, _bullishSequence, _previousBearishBody, _previousBullishBody) to avoid custom collections and rely on internal state between events.
  • Calls StartProtection only once in OnStarted to configure optional stop-loss and take-profit orders in price steps. A value of 0 disables each protective order just like the original expert.
  • Provides extensive English comments in the source code, including explanations for resets and entry triggers.
  • Draws candles and own trades on a chart area when running inside Designer or the UI to ease debugging.

Parameters

Parameter Description Default
DepthAnalysis Number of consecutive finished candles required in one direction with expanding bodies before opening a trade. 3
TakeProfitSteps Take-profit distance measured in exchange price steps (ticks). Set to 0 to disable the take profit. 120
StopLossSteps Stop-loss distance measured in exchange price steps (ticks). Set to 0 to disable the stop loss. 70
TradeVolume Volume sent with each market order. Changing this parameter also updates the base Strategy.Volume. 0.1
CandleType Candle data type (time frame) subscribed for the analysis. 1 hour

All numeric parameters expose optimization metadata so that the strategy can be tuned in StockSharp optimizers if desired.

Notes and recommendations

  • Because the strategy reacts to candle body expansion, the chosen timeframe significantly affects signal frequency. Shorter intervals produce more trades but may require tighter protective distances.
  • Ensure that the connected security defines a valid PriceStep; otherwise the step-based protective distances cannot be converted to absolute prices.
  • Backtest the port inside the StockSharp Designer before live deployment to validate how the stop and target translate for the selected instrument.
  • The strategy keeps a single open position at a time. After every exit the counters are reset and the pattern must rebuild from scratch, mirroring the original MQL4 behaviour.
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 reversal strategy that tracks consecutive candles with expanding bodies.
/// </summary>
public class ETurboFxMomentumStrategy : Strategy
{
	private readonly StrategyParam<int> _depthAnalysis;
	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 recent candles analysed for momentum confirmation.
	/// </summary>
	public int DepthAnalysis
	{
		get => _depthAnalysis.Value;
		set => _depthAnalysis.Value = value;
	}

	/// <summary>
	/// Take profit distance measured 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 measured in price steps (ticks).
	/// A value of zero disables the protective stop.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Volume used when sending market orders.
	/// </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="ETurboFxMomentumStrategy" /> class.
	/// </summary>
	public ETurboFxMomentumStrategy()
	{
		_depthAnalysis = Param(nameof(DepthAnalysis), 3)
			.SetGreaterThanZero()
			.SetDisplay("Depth Analysis", "Number of finished candles used 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 entries", "Trading Rules")
			
			.SetOptimize(0.1m, 0.5m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe 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 protective orders once 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;

		// Convert the user-friendly tick distance into a StockSharp Unit instance.
		return new Unit(steps, UnitTypes.Absolute);
	}

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

		// indicators formed check removed

		if (Position != 0)
		{
			// Do not look for new signals while a position is active.
			ResetState();
			return;
		}

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

		// The original expert compared absolute bodies of the latest N closed candles.
		// Measuring the body here reproduces that behaviour candle by candle.

		if (candle.ClosePrice < candle.OpenPrice)
		{
			HandleBearishCandle(bodySize);
		}
		else if (candle.ClosePrice > candle.OpenPrice)
		{
			HandleBullishCandle(bodySize);
		}
		else
		{
			// Neutral candle breaks both sequences.
			ResetState();
		}
	}

	private void HandleBearishCandle(decimal bodySize)
	{
		// Bearish candles reset the bullish path and allow the downside streak to continue.
		ResetBullishSequence();

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

		if (_bearishSequence == 0 || bodySize > _previousBearishBody)
		{
			// Body is larger than the previous bearish candle, extend the sequence.
			_bearishSequence++;
		}
		else
		{
			// Sequence restarts because body did not expand.
			_bearishSequence = 1;
		}

		_previousBearishBody = bodySize;

		if (_bearishSequence >= DepthAnalysis)
		{
			// Expanding bearish bodies suggest exhaustion that can trigger a long entry.
			BuyMarket();
			ResetBearishSequence();
		}
	}

	private void HandleBullishCandle(decimal bodySize)
	{
		// Bullish candles reset the bearish path and allow the upside streak to continue.
		ResetBearishSequence();

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

		if (_bullishSequence == 0 || bodySize > _previousBullishBody)
		{
			// Body is larger than the previous bullish candle, extend the sequence.
			_bullishSequence++;
		}
		else
		{
			// Sequence restarts because body did not expand.
			_bullishSequence = 1;
		}

		_previousBullishBody = bodySize;

		if (_bullishSequence >= DepthAnalysis)
		{
			// Expanding bullish bodies suggest potential reversal to the downside.
			SellMarket();
			ResetBullishSequence();
		}
	}

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

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

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