Esta estrategia es una conversión fiel del asesor experto de MetaTrader "MA MACD Position averaging". Combina un filtro de media
móvil ponderada con una verificación del ratio MACD y añade un módulo de promediado al estilo martingala que aumenta el tamaño de
la posición cada vez que el precio se mueve adversamente en un número configurable de pips. Todos los parámetros de riesgo se
configuran en unidades de pips y se convierten internamente a desplazamientos de precio utilizando los metadatos del instrumento
proporcionados por StockSharp.
Lógica de Trading
Preparación de indicadores
Una media móvil configurable (MaPeriod, MaMethod, MaAppliedPrice) se muestrea en velas completadas. Los parámetros
SignalBar y MaShift emulan la capacidad de MetaTrader para mirar hacia atrás un número dado de barras y trazar la media
móvil con un desplazamiento horizontal.
Un indicador MACD (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) se procesa en las mismas
velas. La estrategia almacena las líneas principal y de señal del MACD en un pequeño búfer circular para que se pueda acceder
a valores históricos sin llamar directamente a las APIs de indicadores.
Condiciones de entrada
Largo: ambas líneas MACD están por debajo de cero, el ratio MACDmain / MACDsignal es mayor o igual a MacdRatio,
el cierre de la vela está por encima de la media móvil muestreada y la distancia entre el precio y la media es al menos
IndentPips pips.
Corto: ambas líneas MACD están por encima de cero, el ratio está por encima de MacdRatio, el cierre de la vela está
por debajo de la media móvil y la distancia entre ellas es al menos IndentPips pips.
Las nuevas entradas solo se permiten cuando la estrategia no tiene exposición. Cuando ya hay un ciclo de promediado en
progreso, la lógica de señal se omite y solo se aplican las reglas de promediado.
Módulo de promediado
Cuando existe una posición larga y el precio baja al menos StepLossingPips desde la mejor entrada larga (la más baja),
la estrategia abre una operación larga adicional cuyo volumen es igual al volumen del último tramo multiplicado por
LotCoefficient (redondeado al paso de volumen del instrumento).
Cuando existe una posición corta y el precio sube al menos StepLossingPips desde la mejor entrada corta (la más alta),
se añade un nuevo tramo corto usando el mismo multiplicador LotCoefficient.
Si se detecta exposición en ambas direcciones (nunca debería ocurrir en condiciones normales), la estrategia cierra
inmediatamente todos los tramos para restaurar la consistencia.
Salidas protectoras
Cada tramo almacena niveles individuales de stop-loss y take-profit expresados en unidades de precio (StopLossPips,
TakeProfitPips). En cada vela terminada, la estrategia verifica si el rango de la vela cruzó alguno de los niveles
almacenados y, si es así, cierra el tramo con una orden de mercado.
Un trailing stop (TrailingStopPips, TrailingStepPips) es opcional. Una vez que el precio avanza en favor de un tramo
en TrailingStopPips + TrailingStepPips, el stop se mueve a TrailingStopPips pips detrás del cierre actual. El stop
solo se ajusta si el precio hace un progreso adicional de al menos TrailingStepPips pips.
Mantenimiento
Los comandos de volumen se alinean al paso de volumen del instrumento y se recortan al mínimo/máximo permitido. La
estrategia ejecuta solo en velas completamente formadas (CandleStates.Finished) para evitar el doble procesamiento.
Parámetros
Parámetro
Tipo
Valor predeterminado
Descripción
CandleType
DataType
TimeSpan.FromHours(1).TimeFrame()
Marco temporal usado para los cálculos de indicadores.
OrderVolume
decimal
0.1
Tamaño de lote base para la entrada inicial.
StopLossPips
int
50
Distancia del stop-loss en pips (0 deshabilita el stop).
TakeProfitPips
int
50
Distancia del take-profit en pips (0 deshabilita el objetivo).
TrailingStopPips
int
5
Desplazamiento del trailing stop en pips. Debe ser positivo para habilitar el trailing.
TrailingStepPips
int
5
Distancia pip extra requerida antes de que el trailing stop se mueva de nuevo.
StepLossingPips
int
30
Retroceso de precio en pips que activa un nuevo tramo de promediado.
LotCoefficient
decimal
2.0
Multiplicador aplicado al volumen del tramo anterior al promediar.
SignalBar
int
0
Número de barras completadas para mirar atrás al muestrear indicadores.
MaPeriod
int
15
Longitud de la media móvil en barras.
MaShift
int
0
Desplazamiento horizontal (en barras) aplicado a los valores de la media móvil.
MaMethod
MovingAverageMethod
Weighted
Algoritmo de suavizado de la media móvil (simple, exponencial, suavizado, ponderado).
MaAppliedPrice
AppliedPriceType
Weighted
Precio de la vela usado como entrada para la media móvil.
IndentPips
int
4
Brecha mínima en pips requerida entre el precio y la media móvil antes de entrar.
MacdFastPeriod
int
12
Longitud de EMA rápida del filtro MACD.
MacdSlowPeriod
int
26
Longitud de EMA lenta del filtro MACD.
MacdSignalPeriod
int
9
Longitud de la línea de señal del filtro MACD.
MacdAppliedPrice
AppliedPriceType
Weighted
Precio aplicado usado para el cálculo del MACD.
MacdRatio
decimal
0.9
Ratio mínimo MACD principal/señal requerido para permitir trading.
Conversión de pips
Todos los ajustes basados en pips (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips, StepLossingPips,
IndentPips) se multiplican por el PriceStep del instrumento. Cuando el instrumento tiene 3 o 5 decimales, el valor se
multiplica adicionalmente por 10 para reproducir la definición de "pip" de MetaTrader para cotizaciones fraccionales. Si no
hay paso de precio disponible, se usa un valor de respaldo de 0.0001.
Notas de Implementación
La estrategia mantiene una lista interna de tramos de posición porque StockSharp opera en modo netting. Cada tramo rastrea
su propio precio de entrada, stop y niveles de take para que el promediado se comporte como el EA original de MetaTrader.
Las órdenes protectoras se simulan en software: cuando una vela toca un nivel de stop-loss o take-profit, la posición se
cierra con una orden de mercado en esa barra.
El promediado se deshabilita automáticamente cuando StepLossingPips es cero. De lo contrario, cada tramo adicional usa el
volumen del tramo anterior multiplicado por LotCoefficient y redondeado hacia abajo al paso de volumen más cercano.
Las actualizaciones del trailing stop usan el cierre de la vela como proxy del precio actual. El stop nunca se mueve en la
dirección adversa y permanece inactivo hasta que el progreso del precio excede TrailingStopPips + TrailingStepPips.
Los búferes de indicadores respetan los desplazamientos SignalBar y MaShift para que la lógica de decisión vea
exactamente los mismos valores que el experto de MetaTrader obtendría de sus búferes de indicadores.
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 strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
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>
/// 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 MaMacdPositionAveragingStrategy()
{
_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");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance 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 WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { 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;
}
}
// WMA 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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_strategy(Strategy):
"""
WMA crossover with SL/TP in price steps.
"""
def __init__(self):
super(ma_macd_position_averaging_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 100).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
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 candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_macd_position_averaging_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(ma_macd_position_averaging_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self._fast_period.Value
self._slow = WeightedMovingAverage()
self._slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast, self._slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
f = float(fast_val)
s = float(slow_val)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = f
self._prev_slow = s
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = f
self._prev_slow = s
return
close = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._prev_fast <= self._prev_slow and f > s 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 f < s and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_macd_position_averaging_strategy()