Esta estratégia é uma conversão fiel do consultor especialista do MetaTrader "MA MACD Position averaging". Combina um filtro de
média móvel ponderada com uma verificação da proporção MACD e adiciona um módulo de médio estilo martingale que aumenta o tamanho
da posição sempre que o preço se move adversamente por um número configurável de pips. Todos os parâmetros de risco são
configurados em unidades de pips e convertidos internamente em deslocamentos de preço usando os metadados do instrumento
fornecidos pelo StockSharp.
Lógica de Trading
Preparação de indicadores
Uma média móvel configurável (MaPeriod, MaMethod, MaAppliedPrice) é amostrada em candles completadas. Os parâmetros
SignalBar e MaShift emulam a capacidade do MetaTrader de olhar para trás um número específico de barras e plotar a
média móvel com um deslocamento horizontal.
Um indicador MACD (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) é processado nas mesmas
candles. A estratégia armazena as linhas principal e de sinal do MACD em um pequeno buffer circular para que valores
históricos possam ser acessados sem chamar APIs de indicadores diretamente.
Condições de entrada
Comprado: ambas as linhas MACD estão abaixo de zero, a proporção MACDmain / MACDsignal é maior ou igual a MacdRatio,
o fechamento da candle está acima da média móvel amostrada e a distância entre o preço e a média é pelo menos IndentPips
pips.
Vendido: ambas as linhas MACD estão acima de zero, a proporção está acima de MacdRatio, o fechamento da candle está
abaixo da média móvel e a distância entre elas é pelo menos IndentPips pips.
Novas entradas só são permitidas quando a estratégia não tem exposição. Quando um ciclo de médio já está em andamento, a
lógica de sinal é ignorada e apenas as regras de médio se aplicam.
Módulo de médio
Quando existe uma posição comprada e o preço cai pelo menos StepLossingPips da melhor entrada comprada (a mais baixa),
a estratégia abre uma operação comprada adicional cujo volume é igual ao volume do último tramo multiplicado por
LotCoefficient (arredondado pelo passo de volume do instrumento).
Quando existe uma posição vendida e o preço sobe pelo menos StepLossingPips da melhor entrada vendida (a mais alta),
um novo tramo vendido é adicionado usando o mesmo multiplicador LotCoefficient.
Se for detectada exposição em ambas as direções (nunca deveria acontecer em condições normais), a estratégia fecha
imediatamente todos os tramos para restaurar a consistência.
Saídas de proteção
Cada tramo armazena níveis individuais de stop-loss e take-profit expressos em unidades de preço (StopLossPips,
TakeProfitPips). Em cada candle terminada, a estratégia verifica se o intervalo da candle cruzou algum dos níveis
armazenados e, em caso afirmativo, fecha o tramo com uma ordem a mercado.
Um trailing stop (TrailingStopPips, TrailingStepPips) é opcional. Uma vez que o preço avança em favor de um tramo em
TrailingStopPips + TrailingStepPips, o stop é movido para TrailingStopPips pips atrás do fechamento atual. O stop
só se ajusta se o preço fizer um progresso adicional de pelo menos TrailingStepPips pips.
Manutenção
Os comandos de volume são alinhados ao passo de volume do instrumento e recortados ao mínimo/máximo permitido. A estratégia
executa apenas em candles completamente formadas (CandleStates.Finished) para evitar o duplo processamento.
Parâmetros
Parâmetro
Tipo
Padrão
Descrição
CandleType
DataType
TimeSpan.FromHours(1).TimeFrame()
Período usado para cálculos de indicadores.
OrderVolume
decimal
0.1
Tamanho de lote base para a entrada inicial.
StopLossPips
int
50
Distância do stop-loss em pips (0 desativa o stop).
TakeProfitPips
int
50
Distância do take-profit em pips (0 desativa o alvo).
TrailingStopPips
int
5
Offset do trailing stop em pips. Deve ser positivo para habilitar o trailing.
TrailingStepPips
int
5
Distância pip extra necessária antes que o trailing stop se mova novamente.
StepLossingPips
int
30
Recuo de preço em pips que aciona um novo tramo de médio.
LotCoefficient
decimal
2.0
Multiplicador aplicado ao volume do tramo anterior ao fazer médio.
SignalBar
int
0
Número de barras completadas para olhar para trás ao amostrar indicadores.
MaPeriod
int
15
Comprimento da média móvel em barras.
MaShift
int
0
Deslocamento horizontal (em barras) aplicado aos valores da média móvel.
MaMethod
MovingAverageMethod
Weighted
Algoritmo de suavização da média móvel (simples, exponencial, suavizado, ponderado).
MaAppliedPrice
AppliedPriceType
Weighted
Preço da candle usado como entrada para a média móvel.
IndentPips
int
4
Diferença mínima em pips necessária entre o preço e a média móvel antes de entrar.
MacdFastPeriod
int
12
Comprimento de EMA rápido do filtro MACD.
MacdSlowPeriod
int
26
Comprimento de EMA lento do filtro MACD.
MacdSignalPeriod
int
9
Comprimento da linha de sinal do filtro MACD.
MacdAppliedPrice
AppliedPriceType
Weighted
Preço aplicado usado para o cálculo do MACD.
MacdRatio
decimal
0.9
Proporção mínima MACD principal/sinal necessária para permitir trading.
Conversão de pips
Todas as configurações baseadas em pips (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips,
StepLossingPips, IndentPips) são multiplicadas pelo PriceStep do instrumento. Quando o instrumento tem 3 ou 5 casas
decimais, o valor é multiplicado adicionalmente por 10 para reproduzir a definição de "pip" do MetaTrader para cotações
fracionárias. Se não há passo de preço disponível, um valor de fallback de 0.0001 é usado.
Notas de Implementação
A estratégia mantém uma lista interna de tramos de posição porque o StockSharp opera em modo netting. Cada tramo rastreia seu
próprio preço de entrada, stop e níveis de take para que o médio se comporte como o EA original do MetaTrader.
Ordens de proteção são simuladas em software: quando uma candle toca um nível de stop-loss ou take-profit, a posição é
fechada com uma ordem a mercado nessa barra.
O médio é desativado automaticamente quando StepLossingPips é zero. Caso contrário, cada tramo adicional usa o volume do
tramo anterior multiplicado por LotCoefficient e arredondado para baixo ao passo de volume mais próximo.
As atualizações do trailing stop usam o fechamento da candle como proxy do preço atual. O stop nunca se move na direção
adversa e permanece inativo até que o progresso do preço exceda TrailingStopPips + TrailingStepPips.
Os buffers de indicadores respeitam os deslocamentos SignalBar e MaShift para que a lógica de decisão veja exatamente
os mesmos valores que o especialista do MetaTrader obteria de seus buffers 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._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._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)
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, 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 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()