Ver en GitHub

Estrategia Exp AFIRMA

Descripción General

La Estrategia Exp AFIRMA reproduce el asesor experto de MetaTrader Exp_AFIRMA.mq5 usando la API de alto nivel de StockSharp. El sistema original se basa en el indicador AFIRMA (Adaptive Finite Impulse Response Moving Average) que combina un suavizador FIR con ventana y una previsión ARMA corta. La versión de StockSharp mantiene la misma lógica de mercado: abre posiciones largas cuando el componente ARMA gira al alza y cierra o invierte cuando la previsión cae al lado bajista.

Las decisiones de trading se toman en velas completadas de un marco temporal configurable (por defecto: H4). La estrategia evalúa los valores ARMA de varias barras cerradas para confirmar un cambio de pendiente. Las órdenes se colocan a mercado con stops y objetivos de protección opcionales implementados a través de la gestión de riesgos de StockSharp.

Lógica de Trading

  1. Cálculo del indicador
    • El AfirmaIndicator integrado recrea el filtro AFIRMA de dos etapas. Un suavizador FIR con ventana (longitud = Taps, ancho de banda = Periods) produce una media móvil base.
    • La previsión ARMA se calcula a través de los mismos coeficientes de mínimos cuadrados que en el fuente MQL. El indicador expone los valores FIR y ARMA; la estrategia solo consume el componente ARMA.
  2. Evaluación de señales
    • En cada vela terminada se almacena el valor ARMA más reciente. El parámetro SignalBar (por defecto: 1) especifica cuántas barras ya cerradas deben omitirse.
    • Setup alcista: el valor ARMA previo es menor que su predecesor (ARMA[t-2] < ARMA[t-3]) y el valor más reciente está por encima del anterior (ARMA[t-1] > ARMA[t-2]). Esto cierra la exposición corta y abre/extiende una posición larga si está permitido.
    • Setup bajista: el valor ARMA previo es mayor que su predecesor mientras el valor más reciente está por debajo. Esto cierra la exposición larga y abre/extiende una posición corta si está permitido.
  3. Gestión de posiciones
    • Solo se mantiene una posición. Las nuevas entradas llevan la posición hacia ±TradeVolume. La exposición existente se cierra antes de invertir.
    • La protección de riesgo opcional usa StartProtection con distancias de stop-loss y take-profit basadas en precio.

Parámetros

Parámetro Descripción
TradeVolume Tamaño de posición base usado para entradas largas y cortas.
CandleType Marco temporal/tipo de datos solicitado del adaptador de datos de mercado (por defecto: velas de 4 horas).
Periods Ancho de banda recíproco de la etapa FIR (1 / (2 * Periods)), idéntico a la entrada del EA original.
Taps Número de coeficientes FIR. Ajustado internamente al valor impar más cercano si es necesario.
Window Función de ventana aplicada al filtro FIR (Rectangular, Hanning1, Hanning2, Blackman, BlackmanHarris).
SignalBar Número de barras ya cerradas para mirar atrás en busca de confirmación. 1 corresponde a la última barra completamente cerrada.
EnableBuyEntries / EnableSellEntries Permitir apertura de posiciones largas/cortas.
EnableBuyExits / EnableSellExits Permitir cierre de posiciones largas/cortas en señales opuestas.
StopLossPoints Stop de protección opcional expresado en unidades de precio.
TakeProfitPoints Objetivo de protección opcional expresado en unidades de precio.

Notas sobre la Conversión

  • Las opciones de gestión de dinero (MM, MMMode, Deviation_) de la versión MetaTrader son reemplazadas con el parámetro más simple TradeVolume.
  • El EA original envía valores de stop-loss y take-profit en puntos. Aquí se proporcionan en unidades de precio absolutas. Convierta puntos a precio multiplicando por el paso de precio apropiado.
  • Cuando SignalBar = 1, la estrategia lee los últimos tres valores ARMA completados y abre órdenes en la siguiente barra. Establecer SignalBar = 0 sigue funcionando pero usa la barra más recientemente cerrada.
  • La implementación del indicador AFIRMA coincide con la matemática original, incluyendo los tipos de ventana y fórmulas de coeficientes soportados.

Consejos de Uso

  1. Conecte la estrategia a un instrumento y cartera, configure TradeVolume y seleccione el marco temporal a través de CandleType.
  2. Habilite o deshabilite las direcciones largo/corto según su plan de trading.
  3. Establezca StopLossPoints y TakeProfitPoints si desea gestión de riesgo automatizada; de lo contrario déjelos en cero para operar sin salidas fijas.
  4. Monitorice el gráfico generado para verificar las líneas AFIRMA y las operaciones ejecutadas al ajustar Periods, Taps y SignalBar.
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>
/// Exp AFIRMA strategy using EMA crossover as adaptive filter approximation.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpAfirmaStrategy : 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;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ExpAfirmaStrategy"/> class.
	/// </summary>
	public ExpAfirmaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 100)
			.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");
	}

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

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	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;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}