Cruzamento ADX MA
Visão geral
Esta estratégia reproduz o Consultor Especialista "ADX & MA" combinando uma média móvel suavizada com um filtro de tendência de Índice Direcional Médio (ADX). A lógica analisa as últimas duas velas concluídas no período selecionado e reage somente após tanto a média móvel quanto o ADX produzirem valores confirmados. É projetada para entradas no estilo de hedge, mas implementada em um modelo de posição líquida, revertendo automaticamente a posição quando sinais opostos aparecem.
A média móvel é calculada sobre o preço mediano de cada vela, correspondendo à versão do MetaTrader que usou uma SMMA construída sobre (High + Low) / 2. O limite do ADX evita operações quando a força da tendência é fraca, reduzindo sinais falsos de cruzamentos de curta duração.
Lógica de entrada
- Aguardar até que tanto a média móvel suavizada quanto o ADX produzam valores finais.
- Avaliar o fechamento da vela anterior (
n-1) em relação ao valor da MA suavizada tomado na mesma vela.
- Ir comprado quando:
- O fechamento da vela
n-1 está acima do valor MA de n-1.
- O fechamento da vela
n-2 estava abaixo desse valor MA (cruzamento altista), e
- O valor ADX da vela
n-1 é maior ou igual a AdxThreshold.
- Ir vendido quando as condições inversas ocorrem (cruzamento baixista com confirmação do ADX).
- O tamanho da posição usa o
Volume da estratégia mais o valor absoluto de qualquer exposição oposta para garantir uma reversão com sinais opostos.
Lógica de saída
As operações compradas são encerradas quando qualquer uma das seguintes condições é acionada:
- O último fechamento confirmado (
n-1) cai de volta abaixo da MA suavizada (cruzamento oposto).
- O preço atinge a distância de take-profit longa configurada em pips.
- O preço cai até a distância de stop-loss longa configurada em pips.
- O trailing stop para operações compradas bloqueia lucros assim que o preço se move
TrailingStopBuy pips além do preço de entrada.
As operações vendidas espelham as mesmas regras com seus respectivos parâmetros e lógica de trailing. Cada vez que um sinal oposto aparece, a estratégia envia uma ordem de mercado grande o suficiente para fechar a posição atual e abrir uma na nova direção.
Gestão de risco e operações
- As distâncias para take-profit, stop-loss e trailing stop são expressas em pips. A estratégia deriva o tamanho do pip de
Security.PriceStep; quando o símbolo usa 3 ou 5 decimais, o pip é definido como PriceStep × 10, correspondendo ao ajuste original do MetaTrader.
InitializeLongTargets e InitializeShortTargets calculam níveis de preço absolutos imediatamente após o envio da ordem de mercado, armazenando a aproximação do preço de entrada com base no último fechamento confirmado.
- Quando os trailing stops estão ativados e o preço se move favoravelmente além da distância configurada, o nível de stop é deslocado para preservar o lucro não realizado.
- Ambos os conjuntos de alvos são redefinidos quando a posição é fechada, para que níveis obsoletos nunca sejam reutilizados.
Parâmetros
MaPeriod – comprimento da média móvel suavizada (padrão 15).
AdxPeriod – período de suavização do ADX (padrão 12).
AdxThreshold – valor ADX mínimo necessário para confirmar uma tendência (padrão 16).
TakeProfitBuy / StopLossBuy / TrailingStopBuy – distâncias em pips para operações compradas.
TakeProfitSell / StopLossSell / TrailingStopSell – distâncias em pips para operações vendidas.
CandleType – período para as velas de entrada, padrão 1 minuto.
Configure o Volume da estratégia para controlar o tamanho base da ordem. A implementação mantém o comportamento original onde as operações vendidas recebem suas próprias configurações de risco em vez de reutilizar os parâmetros comprados.
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 System.Globalization;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ADX filtered smoothed moving average crossover strategy.
/// Opens trades when the previous candle crosses the smoothed MA and ADX confirms the trend.
/// Adds configurable take profit, stop loss and trailing stop distances measured in pips.
/// </summary>
public class AdxMaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _takeProfitBuy;
private readonly StrategyParam<decimal> _stopLossBuy;
private readonly StrategyParam<decimal> _trailingStopBuy;
private readonly StrategyParam<decimal> _takeProfitSell;
private readonly StrategyParam<decimal> _stopLossSell;
private readonly StrategyParam<decimal> _trailingStopSell;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _ma = null!;
private AverageDirectionalIndex _adx = null!;
private decimal _pipSize;
private decimal _prevClose;
private decimal _prevPrevClose;
private decimal _prevMa;
private decimal _prevAdx;
private bool _hasPrev;
private bool _hasPrevPrev;
private decimal _longEntryPrice;
private decimal _longStopPrice;
private decimal _longTakeProfitPrice;
private decimal _shortEntryPrice;
private decimal _shortStopPrice;
private decimal _shortTakeProfitPrice;
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
public decimal TakeProfitBuy
{
get => _takeProfitBuy.Value;
set => _takeProfitBuy.Value = value;
}
public decimal StopLossBuy
{
get => _stopLossBuy.Value;
set => _stopLossBuy.Value = value;
}
public decimal TrailingStopBuy
{
get => _trailingStopBuy.Value;
set => _trailingStopBuy.Value = value;
}
public decimal TakeProfitSell
{
get => _takeProfitSell.Value;
set => _takeProfitSell.Value = value;
}
public decimal StopLossSell
{
get => _stopLossSell.Value;
set => _stopLossSell.Value = value;
}
public decimal TrailingStopSell
{
get => _trailingStopSell.Value;
set => _trailingStopSell.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public AdxMaCrossoverStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the smoothed moving average", "General")
;
_adxPeriod = Param(nameof(AdxPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Smoothing period for Average Directional Index", "Indicators")
;
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "Minimum ADX value required to trade", "Indicators")
;
_takeProfitBuy = Param(nameof(TakeProfitBuy), 83m)
.SetDisplay("Buy Take Profit (pips)", "Take profit distance for long trades", "Risk Management")
.SetNotNegative();
_stopLossBuy = Param(nameof(StopLossBuy), 55m)
.SetDisplay("Buy Stop Loss (pips)", "Stop loss distance for long trades", "Risk Management")
.SetNotNegative();
_trailingStopBuy = Param(nameof(TrailingStopBuy), 27m)
.SetDisplay("Buy Trailing Stop (pips)", "Trailing stop distance for long trades", "Risk Management")
.SetNotNegative();
_takeProfitSell = Param(nameof(TakeProfitSell), 63m)
.SetDisplay("Sell Take Profit (pips)", "Take profit distance for short trades", "Risk Management")
.SetNotNegative();
_stopLossSell = Param(nameof(StopLossSell), 50m)
.SetDisplay("Sell Stop Loss (pips)", "Stop loss distance for short trades", "Risk Management")
.SetNotNegative();
_trailingStopSell = Param(nameof(TrailingStopSell), 27m)
.SetDisplay("Sell Trailing Stop (pips)", "Trailing stop distance for short trades", "Risk Management")
.SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma?.Reset();
_adx?.Reset();
_pipSize = 0m;
_prevClose = 0m;
_prevPrevClose = 0m;
_prevMa = 0m;
_prevAdx = 0m;
_hasPrev = false;
_hasPrevPrev = false;
ResetLongTargets();
ResetShortTargets();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = new SmoothedMovingAverage { Length = MaPeriod };
_adx = new AverageDirectionalIndex { Length = AdxPeriod };
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
var adxArea = CreateChartArea();
if (adxArea != null)
{
DrawIndicator(adxArea, _adx);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
// Only react to closed candles to match the MQL implementation.
if (candle.State != CandleStates.Finished)
return;
var median = (candle.HighPrice + candle.LowPrice) / 2m;
var maValue = _ma.Process(new DecimalIndicatorValue(_ma, median, candle.OpenTime) { IsFinal = true });
if (!maValue.IsFinal || !adxValue.IsFinal)
return;
var ma = maValue.GetValue<decimal>();
var adx = ((AverageDirectionalIndexValue)adxValue).MovingAverage ?? 0m;
var close = candle.ClosePrice;
if (_hasPrev && _hasPrevPrev)
{
ManageOpenPositions(close);
var longSignal = _prevClose > _prevMa && _prevPrevClose < _prevMa && _prevAdx >= AdxThreshold;
var shortSignal = _prevClose < _prevMa && _prevPrevClose > _prevMa && _prevAdx >= AdxThreshold;
if (longSignal && Position <= 0)
{
BuyMarket();
InitializeLongTargets(_prevClose);
}
else if (shortSignal && Position >= 0)
{
SellMarket();
InitializeShortTargets(_prevClose);
}
}
UpdateHistory(close, ma, adx);
}
private void ManageOpenPositions(decimal currentClose)
{
// Manage long position exits before evaluating new entries.
if (Position > 0)
{
if (_prevClose < _prevMa)
{
SellMarket();
ResetLongTargets();
return;
}
UpdateLongTrailing(currentClose);
if (_longTakeProfitPrice > 0m && currentClose >= _longTakeProfitPrice)
{
SellMarket();
ResetLongTargets();
return;
}
if (_longStopPrice > 0m && currentClose <= _longStopPrice)
{
SellMarket();
ResetLongTargets();
return;
}
}
else if (Position < 0)
{
if (_prevClose > _prevMa)
{
BuyMarket();
ResetShortTargets();
return;
}
UpdateShortTrailing(currentClose);
if (_shortTakeProfitPrice > 0m && currentClose <= _shortTakeProfitPrice)
{
BuyMarket();
ResetShortTargets();
return;
}
if (_shortStopPrice > 0m && currentClose >= _shortStopPrice)
{
BuyMarket();
ResetShortTargets();
return;
}
}
else
{
ResetLongTargets();
ResetShortTargets();
}
}
private void UpdateLongTrailing(decimal currentClose)
{
if (TrailingStopBuy <= 0m || _longEntryPrice <= 0m)
return;
var trailingDistance = TrailingStopBuy * _pipSize;
if (trailingDistance <= 0m)
return;
var profit = currentClose - _longEntryPrice;
if (profit <= trailingDistance)
return;
var newStop = currentClose - trailingDistance;
if (newStop > _longStopPrice)
_longStopPrice = newStop;
}
private void UpdateShortTrailing(decimal currentClose)
{
if (TrailingStopSell <= 0m || _shortEntryPrice <= 0m)
return;
var trailingDistance = TrailingStopSell * _pipSize;
if (trailingDistance <= 0m)
return;
var profit = _shortEntryPrice - currentClose;
if (profit <= trailingDistance)
return;
var newStop = currentClose + trailingDistance;
if (_shortStopPrice == 0m || newStop < _shortStopPrice)
_shortStopPrice = newStop;
}
private void InitializeLongTargets(decimal entryPrice)
{
_longEntryPrice = entryPrice;
_longStopPrice = StopLossBuy > 0m ? entryPrice - StopLossBuy * _pipSize : 0m;
_longTakeProfitPrice = TakeProfitBuy > 0m ? entryPrice + TakeProfitBuy * _pipSize : 0m;
ResetShortTargets();
}
private void InitializeShortTargets(decimal entryPrice)
{
_shortEntryPrice = entryPrice;
_shortStopPrice = StopLossSell > 0m ? entryPrice + StopLossSell * _pipSize : 0m;
_shortTakeProfitPrice = TakeProfitSell > 0m ? entryPrice - TakeProfitSell * _pipSize : 0m;
ResetLongTargets();
}
private void ResetLongTargets()
{
_longEntryPrice = 0m;
_longStopPrice = 0m;
_longTakeProfitPrice = 0m;
}
private void ResetShortTargets()
{
_shortEntryPrice = 0m;
_shortStopPrice = 0m;
_shortTakeProfitPrice = 0m;
}
private void UpdateHistory(decimal close, decimal ma, decimal adx)
{
if (_hasPrev)
{
_prevPrevClose = _prevClose;
_hasPrevPrev = true;
}
else
{
_hasPrevPrev = false;
}
_prevClose = close;
_prevMa = ma;
_prevAdx = adx;
_hasPrev = true;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
var decimals = GetDecimalPlaces(step);
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private static int GetDecimalPlaces(decimal value)
{
var text = value.ToString(CultureInfo.InvariantCulture);
var separatorIndex = text.IndexOf('.') >= 0 ? text.IndexOf('.') : text.IndexOf(',');
if (separatorIndex < 0)
return 0;
return text.Length - separatorIndex - 1;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SmoothedMovingAverage, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adx_ma_crossover_strategy(Strategy):
def __init__(self):
super(adx_ma_crossover_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 15)
self._adx_period = self.Param("AdxPeriod", 12)
self._adx_threshold = self.Param("AdxThreshold", 25.0)
self._take_profit_buy = self.Param("TakeProfitBuy", 83.0)
self._stop_loss_buy = self.Param("StopLossBuy", 55.0)
self._trailing_stop_buy = self.Param("TrailingStopBuy", 27.0)
self._take_profit_sell = self.Param("TakeProfitSell", 63.0)
self._stop_loss_sell = self.Param("StopLossSell", 50.0)
self._trailing_stop_sell = self.Param("TrailingStopSell", 27.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._pip_size = 1.0
self._prev_close = 0.0
self._prev_prev_close = 0.0
self._prev_ma = 0.0
self._prev_adx = 0.0
self._has_prev = False
self._has_prev_prev = False
self._long_entry = 0.0
self._long_stop = 0.0
self._long_tp = 0.0
self._short_entry = 0.0
self._short_stop = 0.0
self._short_tp = 0.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 AdxThreshold(self):
return self._adx_threshold.Value
@AdxThreshold.setter
def AdxThreshold(self, value):
self._adx_threshold.Value = value
@property
def TakeProfitBuy(self):
return self._take_profit_buy.Value
@TakeProfitBuy.setter
def TakeProfitBuy(self, value):
self._take_profit_buy.Value = value
@property
def StopLossBuy(self):
return self._stop_loss_buy.Value
@StopLossBuy.setter
def StopLossBuy(self, value):
self._stop_loss_buy.Value = value
@property
def TrailingStopBuy(self):
return self._trailing_stop_buy.Value
@TrailingStopBuy.setter
def TrailingStopBuy(self, value):
self._trailing_stop_buy.Value = value
@property
def TakeProfitSell(self):
return self._take_profit_sell.Value
@TakeProfitSell.setter
def TakeProfitSell(self, value):
self._take_profit_sell.Value = value
@property
def StopLossSell(self):
return self._stop_loss_sell.Value
@StopLossSell.setter
def StopLossSell(self, value):
self._stop_loss_sell.Value = value
@property
def TrailingStopSell(self):
return self._trailing_stop_sell.Value
@TrailingStopSell.setter
def TrailingStopSell(self, value):
self._trailing_stop_sell.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(adx_ma_crossover_strategy, self).OnStarted2(time)
self._pip_size = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self._pip_size <= 0.0:
self._pip_size = 1.0
self._prev_close = 0.0
self._prev_prev_close = 0.0
self._prev_ma = 0.0
self._prev_adx = 0.0
self._has_prev = False
self._has_prev_prev = False
self._reset_long_targets()
self._reset_short_targets()
self._ma = SmoothedMovingAverage()
self._ma.Length = self.MaPeriod
self._adx = AverageDirectionalIndex()
self._adx.Length = self.AdxPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._adx, self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFinal:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
median = (high + low) / 2.0
ma_result = process_float(self._ma, Decimal(median), candle.OpenTime, True)
if not ma_result.IsFinal:
return
ma_val = float(ma_result)
adx_typed = adx_value
adx_ma = adx_typed.MovingAverage
adx_val = float(adx_ma) if adx_ma is not None else 0.0
if self._has_prev and self._has_prev_prev:
self._manage_open_positions(close)
long_signal = self._prev_close > self._prev_ma and self._prev_prev_close < self._prev_ma and self._prev_adx >= float(self.AdxThreshold)
short_signal = self._prev_close < self._prev_ma and self._prev_prev_close > self._prev_ma and self._prev_adx >= float(self.AdxThreshold)
if long_signal and self.Position <= 0:
self.BuyMarket()
self._init_long_targets(self._prev_close)
elif short_signal and self.Position >= 0:
self.SellMarket()
self._init_short_targets(self._prev_close)
self._update_history(close, ma_val, adx_val)
def _manage_open_positions(self, current_close):
if self.Position > 0:
if self._prev_close < self._prev_ma:
self.SellMarket()
self._reset_long_targets()
return
self._update_long_trailing(current_close)
if self._long_tp > 0.0 and current_close >= self._long_tp:
self.SellMarket()
self._reset_long_targets()
return
if self._long_stop > 0.0 and current_close <= self._long_stop:
self.SellMarket()
self._reset_long_targets()
return
elif self.Position < 0:
if self._prev_close > self._prev_ma:
self.BuyMarket()
self._reset_short_targets()
return
self._update_short_trailing(current_close)
if self._short_tp > 0.0 and current_close <= self._short_tp:
self.BuyMarket()
self._reset_short_targets()
return
if self._short_stop > 0.0 and current_close >= self._short_stop:
self.BuyMarket()
self._reset_short_targets()
return
else:
self._reset_long_targets()
self._reset_short_targets()
def _update_long_trailing(self, current_close):
trail_buy = float(self.TrailingStopBuy)
if trail_buy <= 0.0 or self._long_entry <= 0.0:
return
trail_dist = trail_buy * self._pip_size
if trail_dist <= 0.0:
return
profit = current_close - self._long_entry
if profit <= trail_dist:
return
new_stop = current_close - trail_dist
if new_stop > self._long_stop:
self._long_stop = new_stop
def _update_short_trailing(self, current_close):
trail_sell = float(self.TrailingStopSell)
if trail_sell <= 0.0 or self._short_entry <= 0.0:
return
trail_dist = trail_sell * self._pip_size
if trail_dist <= 0.0:
return
profit = self._short_entry - current_close
if profit <= trail_dist:
return
new_stop = current_close + trail_dist
if self._short_stop == 0.0 or new_stop < self._short_stop:
self._short_stop = new_stop
def _init_long_targets(self, entry):
self._long_entry = entry
sl_buy = float(self.StopLossBuy)
tp_buy = float(self.TakeProfitBuy)
self._long_stop = entry - sl_buy * self._pip_size if sl_buy > 0.0 else 0.0
self._long_tp = entry + tp_buy * self._pip_size if tp_buy > 0.0 else 0.0
self._reset_short_targets()
def _init_short_targets(self, entry):
self._short_entry = entry
sl_sell = float(self.StopLossSell)
tp_sell = float(self.TakeProfitSell)
self._short_stop = entry + sl_sell * self._pip_size if sl_sell > 0.0 else 0.0
self._short_tp = entry - tp_sell * self._pip_size if tp_sell > 0.0 else 0.0
self._reset_long_targets()
def _reset_long_targets(self):
self._long_entry = 0.0
self._long_stop = 0.0
self._long_tp = 0.0
def _reset_short_targets(self):
self._short_entry = 0.0
self._short_stop = 0.0
self._short_tp = 0.0
def _update_history(self, close, ma, adx):
if self._has_prev:
self._prev_prev_close = self._prev_close
self._has_prev_prev = True
self._prev_close = close
self._prev_ma = ma
self._prev_adx = adx
self._has_prev = True
def OnReseted(self):
super(adx_ma_crossover_strategy, self).OnReseted()
self._pip_size = 1.0
self._prev_close = 0.0
self._prev_prev_close = 0.0
self._prev_ma = 0.0
self._prev_adx = 0.0
self._has_prev = False
self._has_prev_prev = False
self._reset_long_targets()
self._reset_short_targets()
def CreateClone(self):
return adx_ma_crossover_strategy()