Ver en GitHub

Estrategia de impulso de negociación intradía

Descripción general

La Estrategia DayTrading es una fiel conversión de C# del clásico MetaTrader 4 asesor experto "DayTrading" lanzado por NazFunds en 2005. El robot original fue diseñado para gráficos Forex de 5 minutos y combina múltiples indicadores de impulso y seguimiento de tendencias para capturar movimientos direccionales a corto plazo con un modesto objetivo fijo y un trailing stop opcional. Esta implementación StockSharp reproduce la lógica de decisión central al tiempo que expone cada umbral importante como un parámetro de estrategia para que pueda optimizarse o adaptarse a diferentes instrumentos.

Pila de indicadores

La estrategia evalúa cuatro indicadores en la serie de velas seleccionada:

  • Parabolic SAR (ParabolicSar) con aceleración, incremento y límite configurables. Define la dirección de la tendencia de referencia y tiene que girar por debajo o por encima del precio para permitir nuevas entradas.
  • MACD (12, 26, 9) (MovingAverageConvergenceDivergenceSignal). La línea MACD debe estar debajo de la línea de señal para largos y por encima para cortos, reflejando la comparación de histograma/señal original en MQL.
  • Stochastic Oscilador (5, 3, 3) (StochasticOscillator). La línea %K debe permanecer por debajo de 35 para largos y por encima de 60 para cortos para garantizar que el mercado salga de una zona de sobreventa/sobrecompra.
  • Impulso (14) (Momentum). Un valor inferior a 100 desbloquea operaciones largas, mientras que un valor superior a 100 autoriza operaciones cortas, exactamente como en el script MT4.

Todos los indicadores se procesan a través del proceso de alto nivel BindEx, por lo que no se requiere gestión manual del búfer ni indexación histórica.

Reglas de trading

Condiciones de entrada

Una posición larga se abre cuando se cumple todo lo siguiente en la última vela terminada:

  1. El punto Parabolic SAR se imprime al precio de venta actual o por debajo de él y el punto anterior estaba por encima del punto actual (nuevo cambio de SAR a alcista).
  2. El impulso está por debajo de 100.
  3. La línea MACD está debajo de su línea de señal.
  4. Stochastic %K está por debajo de 35.

Se abre una posición corta cuando se cumplen las condiciones simétricas:

  1. El punto Parabolic SAR se imprime al precio de oferta actual o por encima de él y el punto anterior estaba por debajo del punto actual (giro bajista).
  2. El impulso está por encima de 100.
  3. La línea MACD está por encima de su línea de señal.
  4. Stochastic %K está por encima de 60.

Sólo se puede abrir una posición a la vez. Siempre que aparece una señal opuesta, la posición existente se cierra y no se produce ningún reingreso en la misma vela, al igual que en la implementación MetaTrader donde el escaneo OrdersTotal evita la recarga inmediata.

Gestión de salidas

  • Stop Loss / Take Profit: Las distancias fijas opcionales (en puntos) se convierten a precios absolutos utilizando el tamaño del tick del instrumento. Se reevalúan en cada vela y cierran la posición si se infringe la intrabarra.
  • Trailing Stop: Una vez que el precio avanza según el número de puntos configurado, se activa un trailing stop. Para operaciones largas, el stop queda por debajo del cierre; para operaciones cortas, se sitúa por encima del cierre. El stop nunca retrocede, por lo que el beneficio se bloquea progresivamente.
  • Señal opuesta: Una configuración opuesta válida liquida inmediatamente la posición actual antes de que se considere cualquier nueva entrada.

No se agrega lógica adicional de cuadrícula, escalamiento o cobertura; la estrategia sigue siendo tan ligera y determinista como la EA original.

Parámetros

Parámetro Predeterminado Descripción
LotSize 1 Volumen de cada orden de mercado. La propiedad Strategy.Volume se sincroniza con este valor durante el inicio.
TrailingStopPoints 15 Distancia de seguimiento en puntos. Establezca en cero para desactivar el seguimiento.
TakeProfitPoints 20 Distancia de toma de ganancias fija en puntos. Establezca en cero para eliminar el objetivo.
StopLossPoints 0 Distancia de parada de protección en puntos. Zero reproduce el comportamiento original de "no parar".
SlippagePoints 3 Marcador de posición de deslizamiento máximo de ejecución (para compatibilidad con la entrada MT4). No se aplica automáticamente, pero se mantiene para que esté completo.
CandleType marco de tiempo de 5 minutos Serie de velas utilizadas por todos los indicadores. Manténgase en M5 para que coincida con la recomendación original de EA.
MacdFastPeriod 12 Longitud rápida de EMA en el cálculo de MACD.
MacdSlowPeriod 26 Longitud lenta de EMA en el cálculo de MACD.
MacdSignalPeriod 9 Longitud de la señal EMA en el cálculo MACD.
StochasticLength 5 %K longitud retrospectiva para el oscilador Stochastic.
StochasticSignal 3 %D longitud de suavizado.
StochasticSlow 3 Ralentización adicional aplicada a la línea %K.
MomentumPeriod 14 Longitud retrospectiva del impulso.
SarAcceleration 0,02 Factor de aceleración inicial para Parabolic SAR.
SarStep 0,02 Incremento aplicado al factor de aceleración después de cada nuevo extremo.
SarMaximum 0,2 Factor de aceleración máximo para Parabolic SAR.

Todos los parámetros numéricos se pueden optimizar a través del flujo de trabajo de optimización de StockSharp gracias a las sugerencias de SetCanOptimize(true).

Notas de implementación

  • Los precios de oferta/demanda se derivan de datos en vivo de Nivel 1 cuando están disponibles; de lo contrario, el cierre de la vela actúa como un respaldo para que la lógica siga siendo sólida en las pruebas históricas.
  • La conversión de puntos depende del Step/PriceStep del instrumento. Si no se proporciona ninguno, se utiliza un respaldo conservador 0.0001, que coincide con un pip estándar de Forex.
  • La gestión de posiciones refleja el MT4 EA: la estrategia nunca es piramidal y nunca mantiene ambas direcciones simultáneamente.
  • Los comentarios dentro del código están en inglés según las pautas del proyecto, mientras que este README incluye documentación ampliada para facilitar la incorporación.

Consejos de uso

  1. Asigne el par de Forex deseado a la estrategia, deje el tipo de vela en 5 minutos y comience la estrategia. Los indicadores se calentarán automáticamente.
  2. Considere habilitar un stop loss distinto de cero cuando se ejecute con datos en vivo; el script original recomendaba operar sin él, pero los trailingstops por sí solos pueden no ser suficientes para el control de riesgos.
  3. Para carteras algorítmicas, puede agregar esta estrategia a un BasketStrategy y administrar la asignación de capital externamente mientras aún se beneficia de los parámetros expuestos para la optimización.

Esta documentación, junto con las traducciones al ruso y al chino en la misma carpeta, proporciona total transparencia de la lógica convertida.

namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// Intraday trend strategy converted from the MetaTrader "DayTrading" expert advisor.
/// Combines Parabolic SAR, MACD, Stochastic and Momentum filters with trailing exits.
/// </summary>
public class DayTradingImpulseStrategy : Strategy
{
	private readonly StrategyParam<decimal> _lotSize;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _slippagePoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _stochasticLength;
	private readonly StrategyParam<int> _stochasticSignal;
	private readonly StrategyParam<int> _stochasticSlow;
	private readonly StrategyParam<decimal> _stochasticBuyThreshold;
	private readonly StrategyParam<decimal> _stochasticSellThreshold;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _momentumNeutralLevel;
	private readonly StrategyParam<decimal> _sarAcceleration;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMaximum;

	private ParabolicSar _parabolicSar = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private StochasticOscillator _stochastic = null!;
	private Momentum _momentum = null!;

	private decimal? _previousSar;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortTakeProfit;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal _pointSize;

	/// <summary>
	/// Initializes a new instance of <see cref="DayTradingImpulseStrategy"/>.
	/// </summary>
	public DayTradingImpulseStrategy()
	{
		_lotSize = Param(nameof(LotSize), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Trade volume used for each market entry", "Trading")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (points)", "Distance used to trail profitable positions", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Fixed profit target measured in points", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (points)", "Protective stop distance measured in points", "Risk")
			;

		_slippagePoints = Param(nameof(SlippagePoints), 3m)
			.SetNotNegative()
			.SetDisplay("Slippage (points)", "Maximum acceptable execution slippage", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for indicator calculations", "Data");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Length of the fast EMA in MACD", "Indicators")
			;

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "Length of the slow EMA in MACD", "Indicators")
			;

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "Length of the MACD signal EMA", "Indicators")
			;

		_stochasticLength = Param(nameof(StochasticLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "Period of the %K line", "Indicators")
			;

		_stochasticSignal = Param(nameof(StochasticSignal), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "Period of the %D smoothing", "Indicators")
			;

		_stochasticSlow = Param(nameof(StochasticSlow), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Slowing", "Final smoothing applied to %K", "Indicators")
			;
		_stochasticBuyThreshold = Param(nameof(StochasticBuyThreshold), 35m)
			.SetDisplay("Stochastic Buy", "Oversold %K threshold for long entries", "Indicators")
			;

		_stochasticSellThreshold = Param(nameof(StochasticSellThreshold), 60m)
			.SetDisplay("Stochastic Sell", "Overbought %K threshold for short entries", "Indicators")
			;


		_momentumPeriod = Param(nameof(MomentumPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Number of candles used for Momentum", "Indicators")
			;

		_momentumNeutralLevel = Param(nameof(MomentumNeutralLevel), 100m)
			.SetDisplay("Momentum Neutral", "Neutral momentum value used for signal confirmation", "Indicators")
			;

		_sarAcceleration = Param(nameof(SarAcceleration), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Acceleration", "Initial acceleration factor of Parabolic SAR", "Indicators")
			;

		_sarStep = Param(nameof(SarStep), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Step", "Increment applied to the acceleration factor", "Indicators")
			;

		_sarMaximum = Param(nameof(SarMaximum), 0.2m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Maximum", "Maximum acceleration factor of Parabolic SAR", "Indicators")
			;
	}

	/// <summary>
	/// Trade volume used for each market entry.
	/// </summary>
	public decimal LotSize
	{
		get => _lotSize.Value;
		set => _lotSize.Value = value;
	}

	/// <summary>
	/// Distance used to trail profitable positions.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Fixed profit target measured in points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Protective stop distance measured in points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Maximum acceptable execution slippage.
	/// </summary>
	public decimal SlippagePoints
	{
		get => _slippagePoints.Value;
		set => _slippagePoints.Value = value;
	}

	/// <summary>
	/// Time frame used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the fast EMA in MACD.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA in MACD.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Length of the MACD signal EMA.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Period of the %K line.
	/// </summary>
	public int StochasticLength
	{
		get => _stochasticLength.Value;
		set => _stochasticLength.Value = value;
	}

	/// <summary>
	/// Period of the %D smoothing.
	/// </summary>
	public int StochasticSignal
	{
		get => _stochasticSignal.Value;
		set => _stochasticSignal.Value = value;
	}

	/// <summary>
	/// Final smoothing applied to %K.
	/// </summary>
	public int StochasticSlow
	{
		get => _stochasticSlow.Value;
		set => _stochasticSlow.Value = value;
	}

	/// <summary>
	/// Stochastic %K level that qualifies oversold conditions.
	/// </summary>
	public decimal StochasticBuyThreshold
	{
		get => _stochasticBuyThreshold.Value;
		set => _stochasticBuyThreshold.Value = value;
	}

	/// <summary>
	/// Stochastic %K level that qualifies overbought conditions.
	/// </summary>
	public decimal StochasticSellThreshold
	{
		get => _stochasticSellThreshold.Value;
		set => _stochasticSellThreshold.Value = value;
	}

	/// <summary>
	/// Number of candles used for Momentum.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Momentum value considered neutral for trend confirmation.
	/// </summary>
	public decimal MomentumNeutralLevel
	{
		get => _momentumNeutralLevel.Value;
		set => _momentumNeutralLevel.Value = value;
	}

	/// <summary>
	/// Initial acceleration factor of Parabolic SAR.
	/// </summary>
	public decimal SarAcceleration
	{
		get => _sarAcceleration.Value;
		set => _sarAcceleration.Value = value;
	}

	/// <summary>
	/// Increment applied to the acceleration factor.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	/// <summary>
	/// Maximum acceleration factor of Parabolic SAR.
	/// </summary>
	public decimal SarMaximum
	{
		get => _sarMaximum.Value;
		set => _sarMaximum.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousSar = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakeProfit = null;
		_shortTakeProfit = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_pointSize = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = LotSize;
		_pointSize = CalculatePointSize();

		_parabolicSar = new ParabolicSar
		{
			Acceleration = SarAcceleration,
			AccelerationStep = SarStep,
			AccelerationMax = SarMaximum,
		};

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod },
			},
			SignalMa = { Length = MacdSignalPeriod },
		};

		_stochastic = new StochasticOscillator();
		_stochastic.K.Length = StochasticLength;
		_stochastic.D.Length = StochasticSignal;

		_momentum = new Momentum
		{
			Length = MomentumPeriod,
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_parabolicSar, _macd, _stochastic, _momentum, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _parabolicSar);
			DrawIndicator(area, _macd);
			DrawIndicator(area, _stochastic);
			DrawIndicator(area, _momentum);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(
		ICandleMessage candle,
		IIndicatorValue sarValue,
		IIndicatorValue macdValue,
		IIndicatorValue stochasticValue,
		IIndicatorValue momentumValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!sarValue.IsFinal || !macdValue.IsFinal || !stochasticValue.IsFinal || !momentumValue.IsFinal)
			return;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macd)
			return;

		if (stochasticValue is not StochasticOscillatorValue stochastic)
			return;

		var sar = sarValue.ToDecimal();
		var previousSar = _previousSar;
		_previousSar = sar;

		if (previousSar is null)
			return;

		var momentum = momentumValue.ToDecimal();
		var ask = GetAskPrice(candle);
		var bid = GetBidPrice(candle);

		var buySignal = sar <= ask && previousSar.Value > sar && momentum < MomentumNeutralLevel &&
			macd.Macd < macd.Signal && stochastic.K < StochasticBuyThreshold;
		var sellSignal = sar >= bid && previousSar.Value < sar && momentum > MomentumNeutralLevel &&
			macd.Macd > macd.Signal && stochastic.K > StochasticSellThreshold;

		var closedPosition = false;

		if (Position > 0)
		{
			if (sellSignal)
			{
				SellMarket(Math.Abs(Position));
				ResetLongState();
				closedPosition = true;
			}
			else if (HandleLongRisk(candle))
			{
				closedPosition = true;
			}
		}
		else if (Position < 0)
		{
			if (buySignal)
			{
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				closedPosition = true;
			}
			else if (HandleShortRisk(candle))
			{
				closedPosition = true;
			}
		}

		if (closedPosition)
			return;

		if (Position == 0)
		{
			if (buySignal)
			{
				var entryPrice = ask;
				BuyMarket(Volume);
				_longEntryPrice = entryPrice;
				_longStopPrice = StopLossPoints > 0m ? entryPrice - ConvertPoints(StopLossPoints) : null;
				_longTakeProfit = TakeProfitPoints > 0m ? entryPrice + ConvertPoints(TakeProfitPoints) : null;
			}
			else if (sellSignal)
			{
				var entryPrice = bid;
				SellMarket(Volume);
				_shortEntryPrice = entryPrice;
				_shortStopPrice = StopLossPoints > 0m ? entryPrice + ConvertPoints(StopLossPoints) : null;
				_shortTakeProfit = TakeProfitPoints > 0m ? entryPrice - ConvertPoints(TakeProfitPoints) : null;
			}
		}
	}

	private bool HandleLongRisk(ICandleMessage candle)
	{
		if (Math.Abs(Position) <= 0m)
			return false;

		if (_longTakeProfit is decimal takeProfit && candle.HighPrice >= takeProfit)
		{
			SellMarket(Math.Abs(Position));
			ResetLongState();
			return true;
		}

		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket(Math.Abs(Position));
			ResetLongState();
			return true;
		}

		var trailingDistance = ConvertPoints(TrailingStopPoints);
		if (trailingDistance > 0m && _longEntryPrice is decimal entry)
		{
			var progressed = candle.HighPrice - entry;
			if (progressed >= trailingDistance)
			{
				var candidate = candle.ClosePrice - trailingDistance;
				if (!_longStopPrice.HasValue || candidate > _longStopPrice.Value)
					_longStopPrice = candidate;
			}
		}

		return false;
	}

	private bool HandleShortRisk(ICandleMessage candle)
	{
		if (Math.Abs(Position) <= 0m)
			return false;

		if (_shortTakeProfit is decimal takeProfit && candle.LowPrice <= takeProfit)
		{
			BuyMarket(Math.Abs(Position));
			ResetShortState();
			return true;
		}

		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket(Math.Abs(Position));
			ResetShortState();
			return true;
		}

		var trailingDistance = ConvertPoints(TrailingStopPoints);
		if (trailingDistance > 0m && _shortEntryPrice is decimal entry)
		{
			var progressed = entry - candle.LowPrice;
			if (progressed >= trailingDistance)
			{
				var candidate = candle.ClosePrice + trailingDistance;
				if (!_shortStopPrice.HasValue || candidate < _shortStopPrice.Value)
					_shortStopPrice = candidate;
			}
		}

		return false;
	}

	private void ResetLongState()
	{
		_longEntryPrice = null;
		_longStopPrice = null;
		_longTakeProfit = null;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = null;
		_shortStopPrice = null;
		_shortTakeProfit = null;
	}

	private decimal GetBidPrice(ICandleMessage candle)
	{
		return candle.ClosePrice;
	}

	private decimal GetAskPrice(ICandleMessage candle)
	{
		return candle.ClosePrice;
	}

	private decimal ConvertPoints(decimal points)
	{
		if (points <= 0m)
			return 0m;

		if (_pointSize > 0m)
			return points * _pointSize;

		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? points * step : points;
	}

	private decimal CalculatePointSize()
	{
		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? step : 0.0001m;
	}
}