Ver no GitHub

Estratégia Exp AFIRMA

Visão Geral

A Estratégia Exp AFIRMA reproduz o consultor especialista MetaTrader Exp_AFIRMA.mq5 usando a API de alto nível do StockSharp. O sistema original baseia-se no indicador AFIRMA (Adaptive Finite Impulse Response Moving Average) que combina um suavizador FIR com janela e uma previsão ARMA curta. A versão StockSharp mantém a mesma lógica de mercado: abre posições compradas quando o componente ARMA gira para cima e fecha ou inverte quando a previsão cai para o lado baixista.

As decisões de trading são tomadas em candles completadas de um período configurável (padrão: H4). A estratégia avalia valores ARMA de várias barras fechadas para confirmar uma mudança de inclinação. As ordens são colocadas a mercado com stops e objetivos de proteção opcionais implementados através do gerenciamento de risco do StockSharp.

Lógica de Trading

  1. Cálculo do indicador
    • O AfirmaIndicator embutido recria o filtro AFIRMA de dois estágios. Um suavizador FIR com janela (comprimento = Taps, largura de banda = Periods) produz uma média móvel base.
    • A previsão ARMA é calculada através dos mesmos coeficientes de mínimos quadrados do código fonte MQL. O indicador expõe valores FIR e ARMA; a estratégia consome apenas o componente ARMA.
  2. Avaliação de sinais
    • Em cada candle completada o valor ARMA mais recente é armazenado. O parâmetro SignalBar (padrão: 1) especifica quantas barras já fechadas devem ser ignoradas.
    • Setup altista: o valor ARMA anterior é menor que seu antecessor (ARMA[t-2] < ARMA[t-3]) e o valor mais recente está acima do anterior (ARMA[t-1] > ARMA[t-2]). Isso fecha a exposição vendida e abre/estende uma posição comprada se permitido.
    • Setup baixista: o valor ARMA anterior é maior que seu antecessor enquanto o valor mais recente está abaixo. Isso fecha a exposição comprada e abre/estende uma posição vendida se permitido.
  3. Gerenciamento de posições
    • Apenas uma posição é mantida. Novas entradas levam a posição em direção a ±TradeVolume. A exposição existente é fechada antes de inverter.
    • A proteção de risco opcional usa StartProtection com distâncias de stop-loss e take-profit baseadas em preço.

Parâmetros

Parâmetro Descrição
TradeVolume Tamanho de posição base usado para entradas compradas e vendidas.
CandleType Período/tipo de dados solicitado do adaptador de dados de mercado (padrão: candles de 4 horas).
Periods Largura de banda recíproca do estágio FIR (1 / (2 * Periods)), idêntico à entrada do EA original.
Taps Número de coeficientes FIR. Ajustado internamente ao valor ímpar mais próximo se necessário.
Window Função de janela aplicada ao filtro FIR (Rectangular, Hanning1, Hanning2, Blackman, BlackmanHarris).
SignalBar Número de barras já fechadas para olhar para trás em busca de confirmação. 1 corresponde à última barra completamente fechada.
EnableBuyEntries / EnableSellEntries Permitir abertura de posições compradas/vendidas.
EnableBuyExits / EnableSellExits Permitir fechamento de posições compradas/vendidas em sinais opostos.
StopLossPoints Stop de proteção opcional expresso em unidades de preço.
TakeProfitPoints Objetivo de proteção opcional expresso em unidades de preço.

Notas de Conversão

  • As opções de gerenciamento de dinheiro (MM, MMMode, Deviation_) da versão MetaTrader são substituídas pelo parâmetro mais simples TradeVolume.
  • O EA original envia valores de stop-loss e take-profit em pontos. Aqui eles são fornecidos em unidades de preço absolutas. Converta pontos para preço multiplicando pelo passo de preço apropriado.
  • Quando SignalBar = 1, a estratégia lê os últimos três valores ARMA completados e abre ordens na próxima barra. Definir SignalBar = 0 ainda funciona mas usa a barra mais recentemente fechada.
  • A implementação do indicador AFIRMA corresponde à matemática original, incluindo os tipos de janela e fórmulas de coeficientes suportados.

Dicas de Uso

  1. Conecte a estratégia a um instrumento e portfólio, configure TradeVolume e selecione o período através de CandleType.
  2. Habilite ou desabilite as direções comprada/vendida de acordo com seu plano de trading.
  3. Defina StopLossPoints e TakeProfitPoints se quiser gerenciamento de risco automatizado; caso contrário, deixe-os em zero para operar sem saídas fixas.
  4. Monitore o gráfico gerado para verificar as linhas AFIRMA e as operações executadas ao ajustar Periods, Taps e 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;
	}
}