Ver en GitHub

Estrategia clásica e-TurboFx

Descripción general

La estrategia e-TurboFx Classic es una adaptación directa de C# del asesor experto MetaTrader 4 que se encuentra en MQL/7262/e-TurboFx.mq4. Detecta el agotamiento del impulso tras una racha de velas fuertes con cuerpos progresivamente más grandes y entra en la dirección opuesta. La versión StockSharp utiliza la estrategia de alto nivel API con suscripciones de velas, órdenes de protección automáticas y parámetros compatibles con la interfaz de usuario.

Lógica comercial

  1. Suscríbase al tipo de vela configurado e inspeccione solo las velas terminadas.
  2. Mida el tamaño del cuerpo de la vela (|close - open|) para detectar la expansión.
  3. Mantener dos contadores:
    • Secuencia bajista: cuenta velas bajistas consecutivas con cuerpos más grandes que la vela bajista anterior.
    • Secuencia alcista: cuenta velas alcistas consecutivas con cuerpos más grandes que la vela alcista anterior.
  4. Restablezca ambas secuencias cuando aparezca un doji (abrir es igual a cerrar) o cuando una posición ya esté abierta. Esto imita el comportamiento original de EA que mantiene solo una operación a la vez.
  5. Entrada larga: cuando la longitud de la secuencia bajista alcance el SequenceLength configurado, envíe una orden de compra de mercado y reinicie inmediatamente los contadores.
  6. Entrada corta: cuando la longitud de la secuencia alcista alcance SequenceLength, envíe una orden de venta de mercado y reinicie los contadores.
  7. Los niveles opcionales de stop-loss y take-profit se traducen desde distancias de puntos a StockSharp pasos de precio.

Por lo tanto, el algoritmo espera un movimiento similar a una capitulación en el que cada vela se acelera en la misma dirección. La siguiente orden de reversión intenta atenuar ese impulso extremo.

Detalles de implementación

  • Utiliza SubscribeCandles().Bind(ProcessCandle) para procesar velas terminadas sin gestión manual de indicadores.
  • Se integra con StartProtection para que las distancias de stop-loss y take-profit se conviertan en pasos del precio de cambio (UnitTypes.Step).
  • Los parámetros se registran a través de Param(...) para que aparezcan en la interfaz de usuario y se puedan optimizar.
  • La estrategia funciona con cualquier instrumento que exponga un PriceStep válido; de lo contrario, las distancias de parada/objetivo deben permanecer en 0.
  • Mientras una posición está activa, la detección de señal se pausa y los contadores internos se borran, al igual que el script original MQL que se negaba a acumular órdenes.

Parámetros

Parámetro Descripción Predeterminado
SequenceLength Número de velas terminadas consecutivas con cuerpos en expansión necesarias para activar una entrada. 3
TakeProfitSteps Distancia de obtención de beneficios medida en pasos de precio (ticks). 0 desactiva el objetivo. 120
StopLossSteps Distancia de stop-loss medida en pasos de precio (ticks). 0 desactiva la parada. 70
TradeVolume Volumen de entradas al mercado. Cambiarlo actualiza la propiedad Volume al instante. 0.1
CandleType Plazo de vela utilizado para el análisis. El valor predeterminado es velas de 1 hora. 1 hour

Notas de uso

  • La estrategia espera datos de velas limpios. Al cambiar de instrumento o período de tiempo, permita que los cachés se reconstruyan para que los contadores reflejen únicamente velas nuevas.
  • Debido a que el sistema se basa en una estricta expansión del cuerpo, los cuerpos de velas pequeños o iguales restablecen la secuencia. Ajuste SequenceLength cuando opere en períodos de tiempo más ruidosos.
  • Realice una prueba retrospectiva de múltiples combinaciones de marco temporal/volumen para encontrar instrumentos en los que los movimientos de agotamiento sean lo suficientemente frecuentes como para compensar los diferenciales y los deslizamientos.
  • Valide siempre el comportamiento en un entorno sandbox antes de habilitar el comercio real.
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();
	}
}