Ver en GitHub

Estrategia E-Friday

Descripción general

  • Convierte el asesor experto MetaTrader original E-Friday.mq5 a la API de alto nivel de StockSharp.
  • Opera solo cuando el marco temporal del gráfico es H1 o inferior; de lo contrario, la estrategia registra una advertencia y permanece plana.
  • Entra en posiciones de forma contraria: una vela bajista abre una posición larga y una vela alcista abre una posición corta.
  • Deshabilita completamente el trading todos los viernes para coincidir con el comportamiento original de protección de fin de semana.
  • Restringe el trading a una ventana de tiempo configurable y puede forzar el cierre de posiciones después del fin de sesión.

Lógica de trading

  1. En cada vela finalizada, la estrategia comprueba el tiempo de cambio actual:
    • si el día es viernes, omite cualquier acción;
    • si la hora es anterior a la hora de inicio configurada, espera;
    • si la ventana de cierre está habilitada y la hora supera la hora de fin, aplana todas las posiciones y omite nuevas entradas.
  2. Cuando el trading está permitido, la última vela completada impulsa la señal:
    • si Open > Close (cuerpo bajista) la estrategia prepara una entrada larga;
    • si Open < Close (cuerpo alcista) la estrategia prepara una entrada corta;
    • precios de apertura y cierre iguales cancelan cualquier acción pendiente.
  3. Antes de entrar en una nueva posición, la exposición actual se aplana, por lo que nunca hay más de una posición neta.

Gestión de posición

  • Tamaño del lote – tomado de TradeVolume y enviado a órdenes BuyMarket / SellMarket.
  • Stop loss y take profit – medidos en pips. Los pips se calculan desde Security.PriceStep y se multiplican por 10 cuando el instrumento tiene 3 o 5 decimales, exactamente como en la versión MQL.
  • Trailing stop – se activa una vez que el precio se mueve TrailingStopPips + TrailingStepPips a favor de la posición. El stop se ajusta a precio actual - trailing stop (largo) o precio actual + trailing stop (corto).
  • Las salidas se evalúan usando los extremos de la vela:
    • una posición larga cierra si el mínimo de la vela toca el stop o el máximo alcanza el take profit;
    • una posición corta cierra si el máximo de la vela toca el stop o el mínimo alcanza el take profit.
  • Después de la hora de fin de sesión (cuando UseCloseHour = true) toda posición abierta se cierra mediante órdenes de mercado.

Parámetros

Nombre Descripción
CandleType Marco temporal de las velas procesadas. Debe definir un TimeSpan positivo y no debe exceder una hora.
TradeVolume Volumen de orden en lotes. Debe ser positivo.
StopLossPips Distancia desde el precio de entrada al stop protector, expresada en pips. Establezca en cero para deshabilitar el stop inicial.
TakeProfitPips Distancia desde el precio de entrada al objetivo de beneficio en pips. Establezca en cero para deshabilitar el objetivo.
TrailingStopPips Distancia del trailing stop en pips. Trabaja junto con TrailingStepPips.
TrailingStepPips Progreso adicional mínimo (en pips) requerido antes de que el trailing stop se ajuste. Debe ser positivo cuando el trailing stop está habilitado.
StartHour Hora (tiempo de cambio) cuando la estrategia puede comenzar a abrir posiciones.
UseCloseHour Habilita o deshabilita el cierre forzado después de la hora de fin.
EndHour Hora (tiempo de cambio) después de la cual la estrategia deja de operar y cierra posiciones existentes.

Notas de implementación

  • Usa SubscribeCandles y la API de alto nivel Bind para que los indicadores puedan añadirse más tarde si es necesario.
  • Valida la configuración de trailing al inicio: cuando se solicita un trailing stop, el paso de trailing debe ser estrictamente positivo.
  • La conversión de pips sigue la lógica original del EA (PriceStep * 10 para símbolos de 3/5 dígitos) para mantener consistentes las distancias del stop-loss.
  • La versión de StockSharp evalúa stops y objetivos una vez por vela finalizada. El EA original funcionaba en cada tick, por lo tanto el port de StockSharp puede salir algunos ticks después, pero la lógica sigue siendo equivalente.
  • La estrategia llama explícitamente a CloseActivePosition cuando termina la ventana de sesión. El script MQL contenía la misma idea pero retornaba antes de llegar a la rutina de cierre; la versión en C# implementa el comportamiento previsto.
  • Los registros informativos (AddInfoLog / AddWarningLog) se usan para mostrar los períodos de trading omitidos en la interfaz de usuario.
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;

/// <summary>
/// E-Friday strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class EFridayStrategy : 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 EFridayStrategy()
	{
		_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;
	}
}