Estratégia Ma SAR ADX Bind
Visão geral
Esta estratégia é uma conversão para a API de alto nível do StockSharp do consultor especialista original MaSarADX.mq5 do MetaTrader 5. O sistema combina um filtro de tendência de média móvel simples com sinais do Índice de Movimento Direcional (ADX) e o trailing stop Parabolic SAR. As decisões de trading são avaliadas apenas em candles concluídos, replicando o comportamento do "primeiro tick de uma nova barra" da versão MQL. Quando o fechamento do candle está alinhado tanto com a tendência da média móvel quanto com o equilíbrio direcional do ADX, uma posição é aberta. O Parabolic SAR orienta tanto a direção do trade quanto as saídas forçando uma liquidação total quando o preço cruza para o lado oposto dos pontos SAR.
Indicadores e dados
- Média Móvel Simples (SMA) – fornece o filtro de direção de tendência primária. Comprimento padrão: 100 candles.
- Índice Direcional Médio (ADX) – fornece +DI e −DI para confirmar a força direcional. Comprimento padrão: 14.
- Parabolic SAR – atua como uma sobreposição de stop-and-reverse e define as condições de saída. Aceleração padrão: 0.02; aceleração máxima: 0.10.
- Candles – qualquer timeframe pode ser solicitado. Por padrão a estratégia assina candles de 1 hora, mas o parâmetro pode ser ajustado para corresponder ao símbolo e regime de teste.
A implementação assina fluxos de candles do StockSharp e vincula os três indicadores usando o helper BindEx de modo que cada callback recebe valores sincronizados para o mesmo candle.
Lógica de trading
Entrada comprada
- O fechamento do candle está acima da média móvel.
- +DI é maior ou igual a −DI, indicando pressão direcional de alta.
- O fechamento do candle está acima do valor do Parabolic SAR.
- Não há posição comprada atualmente aberta (
Position <= 0).
Quando todas as regras se alinham, uma ordem de compra de mercado é enviada pelo volume base configurado mais qualquer tamanho necessário para cobrir uma posição vendida.
Entrada vendida
- O fechamento do candle está abaixo da média móvel.
- +DI é menor ou igual a −DI, indicando pressão direcional de baixa.
- O fechamento do candle está abaixo do valor do Parabolic SAR.
- Não há posição vendida atualmente aberta (
Position >= 0).
Uma ordem de venda de mercado é colocada quando todas as regras de venda correspondem.
Saídas
- As posições compradas são fechadas imediatamente assim que o preço cai abaixo do Parabolic SAR.
- As posições vendidas são cobertas quando o preço sobe acima do Parabolic SAR.
Nenhum nível separado de stop-loss ou take-profit é adicionado; o cruzamento do SAR é o único sinal de saída, seguindo o consultor especialista original. Como as saídas são avaliadas antes de novas entradas, a estratégia não vai inverter de vendido para comprado (ou vice-versa) no mesmo candle, espelhando o ciclo de abertura/fechamento em duas etapas do script MQL.
Parâmetros
| Nome |
Descrição |
Padrão |
Notas |
MaPeriod |
Comprimento da média móvel simples usada para definir o filtro de tendência. |
100 |
Otimizável, deve ser maior que zero. |
AdxPeriod |
Período do cálculo de ADX que produz +DI e −DI. |
14 |
Otimizável, deve ser maior que zero. |
SarStep |
Fator de aceleração e incremento para o Parabolic SAR. |
0.02 |
Equivalente ao parâmetro step do MQL. |
SarMax |
Fator máximo de aceleração para Parabolic SAR. |
0.10 |
Espelha a configuração maximum do MQL. |
Volume |
Tamanho de ordem base para novas entradas. |
1 |
Substitui o dimensionamento de lotes baseado em margem da versão MetaTrader. O tamanho real da ordem é Volume + |Position| para que as reversões liquidem a exposição existente. |
CandleType |
O tipo de dados de candles assinado através do StockSharp. |
1 hora |
Ajustável para qualquer timeframe. |
Notas de implementação
- O processamento de indicadores usa o pipeline de alto nível
BindEx do StockSharp, garantindo que SMA, ADX e SAR sejam atualizados em sincronia perfeita sem buffering manual.
- As saídas são executadas mesmo se
AllowTrading estiver temporariamente desabilitado, mantendo os controles de risco ativos em todos os momentos.
- Helpers de graficação estão incluídos: o painel principal mostra preço, SMA e SAR, enquanto um painel secundário mostra o indicador ADX para diagnósticos.
- Declarações de log descrevem cada decisão de trading com os valores subjacentes do indicador para simplificar testes futuros e depuração.
Diretrizes de uso
- Anexe a estratégia a um título e portfólio no Designer ou Backtester.
- Ajuste o tipo de candle para corresponder ao seu horizonte de trading (p.ex., candles M15, H1, ou D1).
- Ajuste o período da média móvel, o período do ADX e os parâmetros SAR para se adaptar à volatilidade do instrumento.
- Defina o parâmetro
Volume para o tamanho de posição preferido. Se precisar do gerenciamento de dinheiro adaptativo usado no script original, integre seu próprio dimensionamento baseado em portfólio antes de enviar ordens.
- Execute a estratégia. Trades serão acionados somente após todos os indicadores terem produzido valores históricos suficientes para estarem formados.
Diferenças em relação ao consultor especialista original
- O cálculo de lotes baseado em margem foi substituído por um parâmetro fixo
Volume para manter a estratégia broker-neutral dentro do StockSharp.
- A gestão de trades, os valores do indicador e a ordem de avaliação (saída antes de entrada) seguem estritamente a lógica de referência do MetaTrader.
- Todos os comentários dentro do código-fonte estão em inglês para cumprir com as diretrizes do projeto.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Conversion of the MaSarADX MetaTrader strategy to StockSharp high level API.
/// Combines a moving average, ADX directional movement and Parabolic SAR for entries and exits.
/// </summary>
public class MaSarAdxBindStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMax;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousHigh;
private decimal? _previousLow;
private decimal? _previousClose;
private decimal _smoothedPlusDm;
private decimal _smoothedMinusDm;
private decimal _smoothedTrueRange;
private int _adxSamples;
/// <summary>
/// Moving average period used for the trend filter.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Average Directional Index calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Acceleration step for the Parabolic SAR indicator.
/// </summary>
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
/// <summary>
/// Maximum acceleration factor for the Parabolic SAR indicator.
/// </summary>
public decimal SarMax
{
get => _sarMax.Value;
set => _sarMax.Value = value;
}
/// <summary>
/// Type of candles processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MaSarAdxBindStrategy"/>.
/// </summary>
public MaSarAdxBindStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Length of the trend moving average", "Indicators")
.SetOptimize(20, 200, 10);
_adxPeriod = Param(nameof(AdxPeriod), 18)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Length of the Average Directional Index", "Indicators")
.SetOptimize(7, 28, 1);
_sarStep = Param(nameof(SarStep), 0.02m)
.SetRange(0.005m, 0.2m)
.SetDisplay("SAR Step", "Acceleration step for Parabolic SAR", "Indicators")
;
_sarMax = Param(nameof(SarMax), 0.1m)
.SetRange(0.05m, 1m)
.SetDisplay("SAR Maximum", "Maximum acceleration for Parabolic SAR", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to request", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = null;
_previousLow = null;
_previousClose = null;
_smoothedPlusDm = 0m;
_smoothedMinusDm = 0m;
_smoothedTrueRange = 0m;
_adxSamples = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Instantiate indicators used in the original MetaTrader script.
var movingAverage = new SimpleMovingAverage
{
Length = MaPeriod
};
var parabolicSar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationStep = SarStep,
AccelerationMax = SarMax
};
// Subscribe to candle data and bind indicator updates to a single handler.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(movingAverage, parabolicSar, ProcessCandle)
.Start();
// Draw the trading context for visual debugging when charts are available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, movingAverage);
DrawIndicator(area, parabolicSar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal movingAverage, decimal sar)
{
// Work only with completed candles to mirror the original first-tick logic.
if (candle.State != CandleStates.Finished)
return;
var (plusDi, minusDi, isReady) = UpdateDirectionalMovement(candle);
if (!isReady)
return;
// Always allow risk exits even if trading is temporarily disabled.
if (Position > 0 && candle.ClosePrice < sar)
{
SellMarket();
return;
}
if (Position < 0 && candle.ClosePrice > sar)
{
BuyMarket();
return;
}
// Entry conditions replicated from the MetaTrader version.
var bullishSignal = candle.ClosePrice > movingAverage && plusDi >= minusDi && candle.ClosePrice > sar;
var bearishSignal = candle.ClosePrice < movingAverage && plusDi <= minusDi && candle.ClosePrice < sar;
if (bullishSignal && Position <= 0)
{
BuyMarket();
return;
}
if (bearishSignal && Position >= 0)
{
SellMarket();
}
}
private (decimal plusDi, decimal minusDi, bool isReady) UpdateDirectionalMovement(ICandleMessage candle)
{
if (_previousHigh is not decimal previousHigh ||
_previousLow is not decimal previousLow ||
_previousClose is not decimal previousClose)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
return (0m, 0m, false);
}
var upMove = candle.HighPrice - previousHigh;
var downMove = previousLow - candle.LowPrice;
var plusDm = upMove > downMove && upMove > 0m ? upMove : 0m;
var minusDm = downMove > upMove && downMove > 0m ? downMove : 0m;
var trueRange = Math.Max(
candle.HighPrice - candle.LowPrice,
Math.Max(
Math.Abs(candle.HighPrice - previousClose),
Math.Abs(candle.LowPrice - previousClose)));
if (_adxSamples < AdxPeriod)
{
_smoothedPlusDm += plusDm;
_smoothedMinusDm += minusDm;
_smoothedTrueRange += trueRange;
_adxSamples++;
}
else
{
_smoothedPlusDm = _smoothedPlusDm - (_smoothedPlusDm / AdxPeriod) + plusDm;
_smoothedMinusDm = _smoothedMinusDm - (_smoothedMinusDm / AdxPeriod) + minusDm;
_smoothedTrueRange = _smoothedTrueRange - (_smoothedTrueRange / AdxPeriod) + trueRange;
}
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
if (_adxSamples < AdxPeriod || _smoothedTrueRange <= 0m)
return (0m, 0m, false);
return (
100m * _smoothedPlusDm / _smoothedTrueRange,
100m * _smoothedMinusDm / _smoothedTrueRange,
true);
}
}
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 SimpleMovingAverage, ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class ma_sar_adx_bind_strategy(Strategy):
def __init__(self):
super(ma_sar_adx_bind_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 120)
self._adx_period = self.Param("AdxPeriod", 18)
self._sar_step = self.Param("SarStep", 0.02)
self._sar_max = self.Param("SarMax", 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def SarStep(self):
return self._sar_step.Value
@SarStep.setter
def SarStep(self, value):
self._sar_step.Value = value
@property
def SarMax(self):
return self._sar_max.Value
@SarMax.setter
def SarMax(self, value):
self._sar_max.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(ma_sar_adx_bind_strategy, self).OnStarted2(time)
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
ma = SimpleMovingAverage()
ma.Length = self.MaPeriod
sar = ParabolicSar()
sar.Acceleration = float(self.SarStep)
sar.AccelerationStep = float(self.SarStep)
sar.AccelerationMax = float(self.SarMax)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ma, sar, self.ProcessCandle).Start()
def ProcessCandle(self, candle, moving_average, sar):
if candle.State != CandleStates.Finished:
return
ma_val = float(moving_average)
sar_val = float(sar)
plus_di, minus_di, is_ready = self._update_directional_movement(candle)
if not is_ready:
return
close = float(candle.ClosePrice)
# Exit conditions
if self.Position > 0 and close < sar_val:
self.SellMarket()
return
if self.Position < 0 and close > sar_val:
self.BuyMarket()
return
# Entry conditions
bullish_signal = close > ma_val and plus_di >= minus_di and close > sar_val
bearish_signal = close < ma_val and plus_di <= minus_di and close < sar_val
if bullish_signal and self.Position <= 0:
self.BuyMarket()
return
if bearish_signal and self.Position >= 0:
self.SellMarket()
def _update_directional_movement(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._previous_high is None or self._previous_low is None or self._previous_close is None:
self._previous_high = high
self._previous_low = low
self._previous_close = close
return (0.0, 0.0, False)
up_move = high - self._previous_high
down_move = self._previous_low - low
plus_dm = up_move if (up_move > down_move and up_move > 0.0) else 0.0
minus_dm = down_move if (down_move > up_move and down_move > 0.0) else 0.0
true_range = max(high - low, max(abs(high - self._previous_close), abs(low - self._previous_close)))
adx_period = int(self.AdxPeriod)
if self._adx_samples < adx_period:
self._smoothed_plus_dm += plus_dm
self._smoothed_minus_dm += minus_dm
self._smoothed_true_range += true_range
self._adx_samples += 1
else:
self._smoothed_plus_dm = self._smoothed_plus_dm - (self._smoothed_plus_dm / adx_period) + plus_dm
self._smoothed_minus_dm = self._smoothed_minus_dm - (self._smoothed_minus_dm / adx_period) + minus_dm
self._smoothed_true_range = self._smoothed_true_range - (self._smoothed_true_range / adx_period) + true_range
self._previous_high = high
self._previous_low = low
self._previous_close = close
if self._adx_samples < adx_period or self._smoothed_true_range <= 0.0:
return (0.0, 0.0, False)
return (
100.0 * self._smoothed_plus_dm / self._smoothed_true_range,
100.0 * self._smoothed_minus_dm / self._smoothed_true_range,
True)
def OnReseted(self):
super(ma_sar_adx_bind_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
def CreateClone(self):
return ma_sar_adx_bind_strategy()