Descripción General
La Estrategia de Promediado de Posición MA MACD v2 es una traducción directa del asesor experto de MetaTrader de Vladimir
Karputov. Combina un filtro de media móvil ponderada, un bloque de confirmación MACD y un módulo de promediado que aumenta la
exposición cuando las operaciones existentes se mueven en contra de la posición. La versión de StockSharp mantiene la jerarquía
de señales original, procesa indicadores en velas terminadas y gestiona la lógica de protección (stop loss, take profit, trailing)
en código para reproducir el comportamiento del lado del broker en MQL.
Lógica de Trading
- Preparación de indicadores
- Una media móvil configurable calcula en el tipo de vela y componente de precio seleccionados. El parámetro
MaShift emula
el desplazamiento hacia adelante de MetaTrader leyendo valores de velas más antiguas, mientras que BarOffset permite
evaluar la barra actual o una anterior.
- Un indicador de señal MACD produce las líneas principal y de señal usando períodos rápido, lento y de señal personalizables
y un precio aplicado que coincide con el asesor experto original.
- Validación de señales
- Los setups largos requieren que ambas líneas MACD sean negativas, que el precio esté por encima de la media móvil
desplazada y que la distancia del precio a la media supere
MaIndentPips (convertido a precio absoluto usando el tamaño
de pip del instrumento).
- Los setups cortos reflejan las condiciones: ambas líneas MACD deben ser positivas, el precio debe mantenerse por debajo
de la media móvil desplazada y la brecha a la media debe ser al menos
MaIndentPips.
- El filtro de ratio
MacdRatio impone MACD_main / MACD_signal >= MacdRatio (usando división decimal absoluta) antes
de permitir una operación.
- Cuando
ReverseSignals = true, la dirección de la orden de mercado se invierte después de que pasan todas las condiciones.
- Ciclo de vida de la posición
- Si no existe posición, la estrategia abre una orden de mercado con el
OrderVolume configurado (redondeado por el
paso de volumen del instrumento) en la dirección calculada. Los niveles de stop-loss y take-profit se aplican
inmediatamente según StopLossPips y TakeProfitPips.
- Si ya existe una exposición, la estrategia nunca abre el lado opuesto. En cambio:
- Cierra todo si se detectan simultáneamente largos y cortos (red de seguridad que refleja la comprobación MQL), o
- Invoca el bloque de promediado para el lado actual.
- Módulo de promediado
- Para largos, el algoritmo encuentra el tramo abierto con el precio más bajo cuya pérdida no realizada supera
StepLossPips. Para cortos selecciona el tramo perdedor con el precio más alto.
- Una vez que se encuentra un candidato, se envía una nueva orden de mercado con volumen
CandidateVolume × LotCoefficient
(después de ajustar al paso/mín/máx de volumen permitido). Esto reproduce la progresión geométrica del experto original.
- Los nuevos tramos heredan las mismas distancias de stop-loss y take-profit y se vuelven elegibles para actualizaciones
de trailing.
- Controles de riesgo
- Un trailing stop se activa solo cuando tanto
TrailingStopPips como TrailingStepPips son mayores que cero. Para
largos, el stop se mueve a Close - TrailingStopPips una vez que el beneficio supera TrailingStopPips + TrailingStepPips;
los cortos se comportan simétricamente.
- Las comprobaciones manuales de stop-loss y take-profit se realizan en cada vela terminada. Cuando se activan, una orden
de mercado cierra el tramo exacto y lo elimina de la lista de promediado.
Parámetros
| Parámetro |
Descripción |
| OrderVolume |
Volumen base para la primera operación en un ciclo. |
| StopLossPips |
Distancia del stop-loss en pips. Poner en cero para deshabilitar el stop. |
| TakeProfitPips |
Distancia del take-profit en pips. Poner en cero para deshabilitar el objetivo. |
| TrailingStopPips |
Distancia entre el precio y el trailing stop. Funciona junto con TrailingStepPips. |
| TrailingStepPips |
Movimiento favorable adicional requerido antes de actualizar el trailing stop. |
| StepLossPips |
Pérdida mínima (en pips) requerida antes de añadir un tramo de promediado. |
| LotCoefficient |
Multiplicador aplicado al volumen del tramo perdedor seleccionado al promediar. |
| BarOffset |
Número de barras atrás para leer valores de indicadores (0 = barra terminada actual). |
| ReverseSignals |
Invierte la ejecución largo/corto manteniendo los mismos filtros. |
| MaPeriod |
Período de la media móvil. |
| MaShift |
Desplazamiento hacia adelante aplicado a la media móvil (estilo MetaTrader). |
| MaMethod |
Método de suavizado de la media móvil (Simple, Exponencial, Suavizado, Ponderado). |
| MaPrice |
Componente de precio de la vela usado para la media móvil. |
| MaIndentPips |
Distancia mínima del precio desde la media móvil antes de entrar. |
| MacdFastPeriod |
Período de EMA rápida para MACD. |
| MacdSlowPeriod |
Período de EMA lenta para MACD. |
| MacdSignalPeriod |
Período de EMA de señal para MACD. |
| MacdPrice |
Precio aplicado usado en el cálculo del MACD. |
| MacdRatio |
Ratio mínimo entre las líneas principal y de señal del MACD. |
| CandleType |
Serie de velas usada para todos los cálculos. |
Notas de Implementación
- El tamaño del pip se calcula a partir del paso de precio del instrumento, reproduciendo el ajuste de 3/5 dígitos de la
versión MQL. Esto mantiene idénticas las distancias basadas en pips en los símbolos Forex.
- Todos los búferes de indicadores usan colas para emular la indexación
ma_shift y bar de MetaTrader sin llamar a métodos
de búsqueda histórica prohibidos por las reglas del proyecto.
- Los ajustes de volumen respetan
Security.VolumeStep, Security.MinVolume y Security.MaxVolume, evitando tamaños de
orden inválidos cuando LotCoefficient multiplica la exposición.
- La lógica de protección (stops, takes, trailing) se ejecuta completamente en la capa de estrategia, por lo que no hay
dependencia de las APIs de modificación de posición del broker.
- La clase reside en el espacio de nombres
StockSharp.Samples.Strategies y sigue el requisito del repositorio de usar
sangría de tabulación y comentarios exclusivamente en inglés.
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>
/// MA MACD Position Averaging v2 strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class MaMacdPositionAveragingV2Strategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private ExponentialMovingAverage _ema;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// EMA trend filter period.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.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 strategy parameters.
/// </summary>
public MaMacdPositionAveragingV2Strategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_emaPeriod = Param(nameof(EmaPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter 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;
_ema = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, _ema, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed || !_ema.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;
}
}
// WMA crossover with EMA trend filter
if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && close < emaValue && 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 WeightedMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_v2_strategy(Strategy):
def __init__(self):
super(ma_macd_position_averaging_v2_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 100) \
.SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._ema_period = self.Param("EmaPeriod", 200) \
.SetDisplay("EMA Period", "EMA trend filter 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._ema = 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 ema_period(self):
return self._ema_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(ma_macd_position_averaging_v2_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ma_macd_position_averaging_v2_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self.fast_period
self._slow = WeightedMovingAverage()
self._slow.Length = self.slow_period
self._ema = ExponentialMovingAverage()
self._ema.Length = self.ema_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._ema, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value, ema_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
ema_val = float(ema_value)
if not self._fast.IsFormed or not self._slow.IsFormed or not self._ema.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
# Check SL/TP
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
# WMA crossover with EMA trend filter
if self._prev_fast <= self._prev_slow and fast_val > slow_val and close > ema_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 close < ema_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 ma_macd_position_averaging_v2_strategy()