Ver en GitHub

Estrategia e-TurboFx Steps

Descripción General

La estrategia e-TurboFx es un sistema de reversión por agotamiento del impulso originalmente escrito para MetaTrader 5. Monitorea las velas terminadas más recientes y busca secuencias donde los cuerpos de las velas siguen expandiéndose en la misma dirección. Una serie creciente de velas bajistas indica capitulación y por tanto un posible setup largo, mientras que una serie creciente de velas alcistas anuncia una posible oportunidad corta. El puerto de StockSharp usa la API de alto nivel con suscripciones a velas y protección automatizada de posición.

Lógica de Trading

  • Inspeccionar las últimas DepthAnalysis velas terminadas del CandleType seleccionado.
  • Contar cuántas velas consecutivas cerraron por debajo de su apertura (bajistas) y cuántas cerraron por encima de su apertura (alcistas).
  • Seguir la progresión del tamaño del cuerpo: cada nueva vela en la secuencia debe tener un cuerpo absoluto más grande que la anterior. Cuando esta condición falla, la secuencia se reinicia.
  • Entrada larga: DepthAnalysis velas bajistas consecutivas con cuerpos estrictamente en expansión desencadenan una compra a mercado, siempre que no haya posición abierta actualmente.
  • Entrada corta: DepthAnalysis velas alcistas consecutivas con cuerpos estrictamente en expansión desencadenan una venta a mercado, igualmente solo cuando la posición está plana.
  • Mientras una posición está activa, la estrategia pausa la detección de señales para evitar apilar operaciones. La gestión del riesgo se delega al bloque de protección integrado configurado al inicio.

Gestión de Posición

  • StartProtection registra automáticamente órdenes de stop-loss y take-profit usando distancias medidas en pasos de precio (ticks del exchange). Establecer una distancia en cero deshabilita la orden de protección correspondiente.
  • La estrategia mantiene solo una posición abierta. Cuando aparece una nueva señal después de que la operación anterior se cierra, las secuencias de velas se reconstruyen desde cero basándose en datos de mercado frescos.
  • Las entradas a mercado usan el parámetro TradeVolume. Cambiar el parámetro en la UI actualiza inmediatamente el volumen de la estrategia.

Parámetros

Parámetro Descripción Predeterminado
DepthAnalysis Número de velas terminadas recientes usadas para validar el patrón de expansión. Valores más altos exigen rachas más largas antes de operar. 3
TakeProfitSteps Distancia del take-profit en pasos de precio del exchange (ticks). 0 deshabilita el take-profit. 120
StopLossSteps Distancia del stop-loss en pasos de precio del exchange (ticks). 0 deshabilita el stop-loss. 70
TradeVolume Volumen de orden enviado con cada entrada a mercado. 0.1
CandleType Tipo de datos de vela (marco temporal) suscrito para el análisis. Marco temporal de 1 hora

Todos los parámetros numéricos tienen metadatos de optimización para que puedan incluirse en las optimizaciones de StockSharp si se desea.

Notas y Recomendaciones

  • El asesor experto MQL5 original recalculaba los datos de velas en cada tick; la implementación de StockSharp logra el mismo comportamiento con eventos de velas terminadas y contadores internos.
  • Debido a que la estrategia se basa en comparaciones del cuerpo de velas, es sensible al marco temporal seleccionado. Los marcos temporales más cortos producirán más señales pero pueden requerir stops más ajustados.
  • Asegúrese de que el instrumento conectado exponga un PriceStep válido para que las distancias de stop-loss y take-profit definidas en pasos se traduzcan correctamente a precios.
  • Antes de operar en vivo, valide el comportamiento en el Designer/Backtester para confirmar que las distancias de stop y objetivo se alineen con el instrumento elegido.
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 ETurboFxStepsStrategy : 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="ETurboFxStepsStrategy" /> class.
	/// </summary>
	public ETurboFxStepsStrategy()
	{
		_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(stopLossUnit, takeProfitUnit);
		}

		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;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
		{
			// Do not look for new signals while a position is active.
			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
		{
			// Neutral candle breaks both sequences.
			ResetState();
		}
	}

	private void HandleBearishCandle(decimal bodySize)
	{
		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)
	{
		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();
	}
}