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
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.
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.
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
Conecte a estratégia a um instrumento e portfólio, configure TradeVolume e selecione o período através de CandleType.
Habilite ou desabilite as direções comprada/vendida de acordo com seu plano de trading.
Defina StopLossPoints e TakeProfitPoints se quiser gerenciamento de risco automatizado; caso contrário, deixe-os
em zero para operar sem saídas fixas.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_afirma_strategy(Strategy):
def __init__(self):
super(exp_afirma_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 21) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 100) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(exp_afirma_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_afirma_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_afirma_strategy()