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
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.
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.
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
Conecte la estrategia a un instrumento y cartera, configure TradeVolume y seleccione el marco temporal a través de
CandleType.
Habilite o deshabilite las direcciones largo/corto según su plan de trading.
Establezca StopLossPoints y TakeProfitPoints si desea gestión de riesgo automatizada; de lo contrario déjelos en
cero para operar sin salidas fijas.
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;
}
}
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()