Estrategia de Confirmación de Tendencia de MA Dual
Descripción general
La Estrategia de Confirmación de Tendencia de MA Dual replica el experto original de MetaTrader que combina una media móvil exponencial lenta (EMA) con una media móvil ponderada linealmente rápida (LWMA). El sistema espera a que ambas medias móviles se alineen en la misma dirección y usa el cierre de la vela anterior como confirmación adicional antes de entrar en una posición. La idea es participar solo en fuertes oscilaciones de impulso cuando el filtro de tendencia lenta y el filtro de confirmación rápida simultáneamente se inclinan hacia arriba o hacia abajo.
La implementación de StockSharp procesa solo las velas completamente terminadas, rastrea la pendiente de cada media móvil durante las últimas tres barras, y gestiona automáticamente las órdenes protectoras mediante el mecanismo integrado StartProtection. La estrategia es agnóstica al instrumento: puede operar en cualquier valor y marco temporal que proporcionen velas y soporten el concepto de "puntos" mediante el paso de precio del instrumento.
Indicadores
- EMA lenta – Período predeterminado 57. Representa la dirección de la tendencia dominante. La estrategia requiere que la EMA aumente (o disminuya) durante dos velas consecutivas antes de operar.
- LWMA rápida – Período predeterminado 3. Actúa como filtro de confirmación de impulso. Su pendiente debe coincidir con la EMA lenta, reforzando que el impulso apoya la tendencia.
Parámetros
| Parámetro |
Valor predeterminado |
Descripción |
SlowMaLength |
57 |
Período del filtro de tendencia EMA lenta. |
FastMaLength |
3 |
Período del filtro de confirmación LWMA rápida. |
StopLossPoints |
100 |
Distancia de stop protector expresada en puntos del instrumento (multiplicado por Security.PriceStep). |
TakeProfitPoints |
100 |
Distancia de take-profit expresada en puntos del instrumento (multiplicado por Security.PriceStep). |
CandleType |
Marco temporal de 15 minutos |
Tipo de datos de vela usado para todos los cálculos. |
Todos los parámetros se exponen como valores StrategyParam<T> para que puedan modificarse en tiempo de ejecución o optimizarse a través de las herramientas de optimización de StockSharp.
Reglas de trading
Setup largo
- La EMA lenta está subiendo: valor actual > valor anterior > valor hace dos velas.
- La LWMA rápida está subiendo: valor actual > valor anterior > valor hace dos velas.
- El cierre de la vela anterior está por encima del valor anterior de la EMA lenta.
- El valor actual de la EMA lenta está por encima del valor actual de la LWMA rápida.
- La posición actual es plana o corta.
- Cuando todas las condiciones se cumplen, la estrategia envía una orden de compra de mercado por
Volume + |Position| para volcar a una posición larga.
Setup corto
- La EMA lenta está cayendo: valor actual < valor anterior < valor hace dos velas.
- La LWMA rápida está cayendo: valor actual < valor anterior < valor hace dos velas.
- El cierre de la vela anterior está por debajo del valor anterior de la EMA lenta.
- El valor actual de la EMA lenta está por debajo del valor actual de la LWMA rápida.
- La posición actual es plana o larga.
- Cuando todas las condiciones se cumplen, la estrategia envía una orden de venta de mercado por
Volume + |Position| para volcar a una posición corta.
Lógica protectora
StartProtection convierte StopLossPoints y TakeProfitPoints en desplazamientos de precio absoluto multiplicándolos con Security.PriceStep. Las órdenes de stop-loss y take-profit se emiten como salidas de mercado para que el motor pueda cerrar la posición incluso si las órdenes límite no están soportadas.
- Cuando aparece la señal opuesta, la estrategia invierte inmediatamente la posición independientemente de las órdenes protectoras.
Detalles de implementación
- Solo se procesan las velas terminadas, emulando la comprobación de nueva barra de la versión MQL original.
- La estrategia mantiene los últimos dos valores de la media móvil y el precio de cierre anterior en campos privados para evitar búsquedas en el historial del indicador.
IsFormedAndOnlineAndAllowTrading() asegura que el trading ocurra solo cuando todos los flujos de datos estén activos y se permita el trading.
- Los registros de dirección de trade (
LogInfo) proporcionan transparencia para depuración y monitoreo en vivo.
- La renderización de gráficos (si está disponible) dibuja las velas y ambas medias móviles para una rápida validación visual.
Notas de uso
- Elija
Volume según el tamaño del lote del instrumento. La estrategia siempre envía órdenes de mercado de tamaño Volume + |Position| para revertir eficientemente.
- Al ejecutar en instrumentos sin un
PriceStep definido, el código recurre a un valor de 1. Ajuste los parámetros en consecuencia si el tamaño del tick difiere.
- La optimización puede enfocarse en los períodos de la media móvil y las distancias protectoras para adaptar la estrategia a diferentes mercados.
- Combine con filtros adicionales (volatilidad, horarios de sesión, etc.) si es necesario. La estructura modular facilita su extensión.
Rangos de optimización sugeridos
SlowMaLength: 20 – 120 con paso 5–10.
FastMaLength: 2 – 10 con paso 1.
StopLossPoints / TakeProfitPoints: 50 – 200 dependiendo de la volatilidad del instrumento.
Estos rangos reflejan de cerca la configuración original del experto mientras brindan flexibilidad para otros instrumentos.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dual moving average trend confirmation strategy.
/// Uses a slow EMA and a fast LWMA to detect synchronized trends.
/// Enters long when both averages slope upward, price stays above the slow EMA, and the slow EMA is above the fast LWMA.
/// Enters short when both averages slope downward, price stays below the slow EMA, and the slow EMA is below the fast LWMA.
/// Built-in stop-loss and take-profit are defined in instrument points.
/// </summary>
public class DualMaTrendConfirmationStrategy : Strategy
{
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _previousClose;
private decimal _slowPrevious;
private decimal _slowPrevious2;
private decimal _fastPrevious;
private decimal _fastPrevious2;
private int _historyCount;
/// <summary>
/// Slow EMA period length.
/// </summary>
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
/// <summary>
/// Fast LWMA period length.
/// </summary>
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in instrument points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in instrument points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="DualMaTrendConfirmationStrategy"/> class.
/// </summary>
public DualMaTrendConfirmationStrategy()
{
_slowMaLength = Param(nameof(SlowMaLength), 57)
.SetDisplay("Slow EMA Length", "Period for the slow EMA trend filter", "Moving Averages")
.SetRange(10, 200)
;
_fastMaLength = Param(nameof(FastMaLength), 3)
.SetDisplay("Fast LWMA Length", "Period for the fast LWMA confirmation filter", "Moving Averages")
.SetRange(1, 50)
;
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in instrument points", "Risk Management")
.SetRange(10m, 500m)
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
.SetDisplay("Take Profit (points)", "Take-profit distance measured in instrument points", "Risk Management")
.SetRange(10m, 500m)
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for moving average calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Clear stored history so the next candle starts with a clean state.
_previousClose = 0m;
_slowPrevious = 0m;
_slowPrevious2 = 0m;
_fastPrevious = 0m;
_fastPrevious2 = 0m;
_historyCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var slowEma = new ExponentialMovingAverage
{
Length = SlowMaLength
};
var fastLwma = new WeightedMovingAverage
{
Length = FastMaLength
};
var subscription = SubscribeCandles(CandleType);
var step = Security.PriceStep ?? 1m;
// Enable automatic stop-loss and take-profit management based on point offsets.
StartProtection(
takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
useMarketOrders: true);
subscription
.Bind(slowEma, fastLwma, (candle, slowValue, fastValue) => ProcessCandle(candle, slowValue, fastValue, slowEma, fastLwma))
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, slowEma);
DrawIndicator(area, fastLwma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal slowValue, decimal fastValue, ExponentialMovingAverage slowEma, WeightedMovingAverage fastLwma)
{
// Work only with fully formed candles to avoid premature decisions.
if (candle.State != CandleStates.Finished)
return;
// Ensure both indicators produced reliable values before trading logic.
if (!slowEma.IsFormed || !fastLwma.IsFormed)
{
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
return;
}
// Accumulate at least two previous candles for slope calculations.
if (_historyCount < 2)
{
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
return;
}
var slowRising = slowValue > _slowPrevious && _slowPrevious > _slowPrevious2;
var fastRising = fastValue > _fastPrevious && _fastPrevious > _fastPrevious2;
var slowFalling = slowValue < _slowPrevious && _slowPrevious < _slowPrevious2;
var fastFalling = fastValue < _fastPrevious && _fastPrevious < _fastPrevious2;
var priceAboveSlow = _previousClose > _slowPrevious;
var priceBelowSlow = _previousClose < _slowPrevious;
var slowAboveFast = slowValue > fastValue;
var slowBelowFast = slowValue < fastValue;
if (slowRising && fastRising && priceAboveSlow && slowAboveFast && Position <= 0)
{
BuyMarket();
}
else if (slowFalling && fastFalling && priceBelowSlow && slowBelowFast && Position >= 0)
{
SellMarket();
}
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
}
private void UpdateHistory(decimal slowValue, decimal fastValue, decimal closePrice)
{
// Shift previous values so the last two candles are always available.
_slowPrevious2 = _slowPrevious;
_slowPrevious = slowValue;
_fastPrevious2 = _fastPrevious;
_fastPrevious = fastValue;
_previousClose = closePrice;
if (_historyCount < 2)
_historyCount++;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dual_ma_trend_confirmation_strategy(Strategy):
def __init__(self):
super(dual_ma_trend_confirmation_strategy, self).__init__()
self._slow_ma_length = self.Param("SlowMaLength", 57)
self._fast_ma_length = self.Param("FastMaLength", 3)
self._stop_loss_points = self.Param("StopLossPoints", 100.0)
self._take_profit_points = self.Param("TakeProfitPoints", 100.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
@property
def SlowMaLength(self):
return self._slow_ma_length.Value
@SlowMaLength.setter
def SlowMaLength(self, value):
self._slow_ma_length.Value = value
@property
def FastMaLength(self):
return self._fast_ma_length.Value
@FastMaLength.setter
def FastMaLength(self, value):
self._fast_ma_length.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(dual_ma_trend_confirmation_strategy, self).OnStarted2(time)
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowMaLength
self._fast_lwma = WeightedMovingAverage()
self._fast_lwma.Length = self.FastMaLength
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
self.StartProtection(
Unit(float(self.TakeProfitPoints) * step, UnitTypes.Absolute),
Unit(float(self.StopLossPoints) * step, UnitTypes.Absolute),
False, None, None, True)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._slow_ema, self._fast_lwma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, slow_value, fast_value):
if candle.State != CandleStates.Finished:
return
sv = float(slow_value)
fv = float(fast_value)
close = float(candle.ClosePrice)
if not self._slow_ema.IsFormed or not self._fast_lwma.IsFormed:
self._update_history(sv, fv, close)
return
if self._history_count < 2:
self._update_history(sv, fv, close)
return
slow_rising = sv > self._slow_previous and self._slow_previous > self._slow_previous2
fast_rising = fv > self._fast_previous and self._fast_previous > self._fast_previous2
slow_falling = sv < self._slow_previous and self._slow_previous < self._slow_previous2
fast_falling = fv < self._fast_previous and self._fast_previous < self._fast_previous2
price_above_slow = self._previous_close > self._slow_previous
price_below_slow = self._previous_close < self._slow_previous
slow_above_fast = sv > fv
slow_below_fast = sv < fv
if slow_rising and fast_rising and price_above_slow and slow_above_fast and self.Position <= 0:
self.BuyMarket()
elif slow_falling and fast_falling and price_below_slow and slow_below_fast and self.Position >= 0:
self.SellMarket()
self._update_history(sv, fv, close)
def _update_history(self, slow_value, fast_value, close):
self._slow_previous2 = self._slow_previous
self._slow_previous = slow_value
self._fast_previous2 = self._fast_previous
self._fast_previous = fast_value
self._previous_close = close
if self._history_count < 2:
self._history_count += 1
def OnReseted(self):
super(dual_ma_trend_confirmation_strategy, self).OnReseted()
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
def CreateClone(self):
return dual_ma_trend_confirmation_strategy()