Ver en GitHub

Estrategia de Reverse Day Fractal

Descripción general

Reverse Day Fractal es una estrategia de acción del precio que busca reversiones bruscas después de una ruptura intradía. El algoritmo analiza las últimas tres velas terminadas. Cuando la barra actual forma un nuevo extremo más allá de las dos velas anteriores y cierra de vuelta en la dirección opuesta, lo trata como una ruptura fallida y entra en una operación de reversión. Las órdenes protectoras se gestionan mediante distancias configurables de take-profit, stop-loss y trailing stop medidas en pasos de precio.

Lógica de trading

  • Configuración alcista:
    • La vela terminada actual establece un mínimo más bajo que cada una de las dos velas anteriores.
    • La vela cierra por encima de su precio de apertura, indicando un rechazo alcista del nuevo mínimo.
    • Cuando se cumplen estas condiciones y la estrategia puede operar, abre una posición larga. Opcionalmente puede cerrar primero un corto existente.
  • Configuración bajista:
    • La vela terminada actual establece un máximo más alto que cada una de las dos velas anteriores.
    • La vela cierra por debajo de su precio de apertura, indicando un rechazo bajista del nuevo máximo.
    • Cuando se satisfacen estas condiciones, abre una posición corta, cerrando opcionalmente un largo existente primero.
  • Gestión de posición: la estrategia puede configurarse para permitir solo una posición abierta a la vez (comportamiento predeterminado). Cuando está deshabilitado, revertirá una posición existente añadiendo el volumen requerido para cambiar la dirección.
  • Controles de riesgo: al inicio, la estrategia llama a StartProtection para aplicar protecciones de take-profit, stop-loss y trailing stop usando las distancias de punto configuradas. Cuando un trailing stop está habilitado, el stop protector seguirá el precio en pasos discretos.

Parámetros

  • Trade Volume – volumen de orden para nuevas entradas.
  • Take Profit – distancia al objetivo de beneficio medida en pasos de precio. Cero para deshabilitar.
  • Stop Loss – distancia al stop protector medida en pasos de precio. Cero para deshabilitar.
  • Trailing Stop – distancia de trailing stop en pasos de precio. Cero para deshabilitar.
  • Trailing Step – movimiento mínimo (en pasos) requerido antes de ajustar el trailing stop.
  • Only One Position – cuando está habilitado, la estrategia ignora nuevas señales mientras hay una posición abierta.
  • Candle Type – tipo de datos de velas usado para los cálculos (predeterminado: marco temporal de 1 hora).

Notas

  • Las señales se generan solo en velas terminadas proporcionadas por la suscripción configurada.
  • La estrategia mantiene en memoria los dos extremos de vela más recientes; por lo tanto, necesita al menos dos velas completadas después del inicio antes de poder generar una señal.
  • Los valores de parámetros predeterminados replican el asesor experto MQL4 original: volumen de 0.01 lote, stop loss de 20 puntos, take profit de 10 puntos, trailing stop de 25 puntos y trailing step de 5 puntos.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class ReverseDayFractalStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ReverseDayFractalStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}