Automatizando Estratégias de Trading em MQL5 (Parte 14): Estratégia de Layering de Trades com MACD, RSI e Métodos Estatísticos
Introdução
Em nosso artigo anterior (Parte 13), implementamos um algoritmo de trading Head and Shoulders em MetaQuotes Language 5 (MQL5)para automatizar um padrão clássico de reversão voltado a capturar viradas do mercado Agora, na Parte 14, passamos ao desenvolvimento de uma estratégia de trade layering com Moving Average Convergence Divergence (MACD) e Relative Strength Indicator (RSI), aprimorada por métodos estatísticos, para dimensionar dinamicamente as posições em mercados com tendência. Abordaremos os seguintes tópicos:
Ao final deste artigo, você terá um Expert Advisor robusto, projetado para realizar o layering de trades com precisão — vamos começar!
Arquitetura da Estratégia
A estratégia de trade layering que exploramos neste artigo foi desenvolvida para aproveitar tendências sustentadas do mercado, adicionando progressivamente posições à medida que o preço se move em uma direção favorável, um método frequentemente chamado de entrada em cascata. Ao contrário das estratégias tradicionais com uma única entrada que buscam um alvo fixo, essa abordagem aproveita o momentum adicionando novos trades em camadas sempre que um limite de lucro é atingido, ampliando o potencial de ganhos enquanto mantém o risco sob controle. Em sua essência, a estratégia combina dois indicadores técnicos amplamente conhecidos — MACD e RSI — com uma camada estatística para garantir que as entradas sejam oportunas e robustas, tornando-a adequada para mercados com movimentos direcionais claros.
Utilizaremos os pontos fortes do MACD e do RSI para estabelecer uma base sólida para os sinais de trading, definindo regras claras sobre quando iniciar o processo de layering. Nosso plano envolve utilizar o MACD para confirmar a direção e a força da tendência, garantindo que somente entremos em trades quando uma direção consistente, enquanto o RSI identificará os momentos ideais de entrada ao detectar mudanças a partir de níveis extremos de preço. Ao integrar esses indicadores, buscamos criar um mecanismo de acionamento confiável que inicie o primeiro trade, que então servirá como ponto de partida para nossa sequência em cascata, permitindo construir posições à medida que a tendência progride. Aqui está uma visualização da estratégia.

Em seguida, aprimoraremos essa configuração incorporando métodos estatísticos para aumentar a precisão das entradas e orientar o processo de layering. Exploraremos como aplicar filtros estatísticos — como a análise do comportamento histórico do RSI — para validar os sinais, garantindo que os trades ocorram somente sob condições estatisticamente significativas. O plano então se estende à definição das regras de layering, nas quais descreveremos como cada novo trade é adicionado quando os alvos de lucro são atingidos, juntamente com ajustes nos níveis de risco para proteger os ganhos, culminando em uma estratégia dinâmica que se adapta ao momentum do mercado enquanto mantém uma execução disciplinada. Vamos começar.
Implementação em MQL5
Para criar o programa em MQL5, abra o MetaEditor, vá até o Navigator, localize a pasta Indicators, clique na aba "New" e siga as instruções para criar o arquivo. Depois de criado, no ambiente de codificação, precisaremos declarar algumas variáveis globais que utilizaremos ao longo de todo o programa.
//+------------------------------------------------------------------+ //| MACD-RSI LAYERING STRATEGY.mq5 | //| Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. | //| https://youtube.com/@ForexAlgo-Trader? | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader" #property link "https://youtube.com/@ForexAlgo-Trader?" #property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels" #property version "1.0" #include <Trade\Trade.mqh>//---- Includes the Trade.mqh library for trading operations CTrade obj_Trade;//---- Declares a CTrade object for executing trade operations int rsiHandle = INVALID_HANDLE;//---- Initializes RSI indicator handle as invalid double rsiValues[];//---- Declares an array to store RSI values int handleMACD = INVALID_HANDLE;//---- Initializes MACD indicator handle as invalid double macdMAIN[];//---- Declares an array to store MACD main line values double macdSIGNAL[];//---- Declares an array to store MACD signal line values double takeProfitLevel = 0;//---- Initializes the take profit level variable double stopLossLevel = 0;//---- Initializes the stop loss level variable bool buySequenceActive = false;//---- Flag to track if a buy sequence is active bool sellSequenceActive = false;//---- Flag to track if a sell sequence is active // Inputs with clear names input int stopLossPoints = 300; // Initial Stop Loss (points) input double tradeVolume = 0.01; // Trade Volume (lots) input int minStopLossPoints = 100; // Minimum SL for cascading orders (points) input int rsiLookbackPeriod = 14; // RSI Lookback Period input double rsiOverboughtLevel = 70.0; // RSI Overbought Threshold input double rsiOversoldLevel = 30.0; // RSI Oversold Threshold input bool useStatisticalFilter = true; // Enable Statistical Filter input int statAnalysisPeriod = 20; // Statistical Analysis Period (bars) input double statDeviationFactor = 1.0; // Statistical Deviation Factor input double riskRewardRatio = 1.0; // Risk:Reward Ratio // Object names for visualization string takeProfitLineName = "TakeProfitLine";//---- Name of the take profit line object for chart visualization string takeProfitTextName = "TakeProfitText";//---- Name of the take profit text object for chart visualization
Começamos configurando a estrutura essencial para nossa estratégia de trade layering, iniciando com a inclusão da biblioteca "Trade.mqh" e declarando um objeto "CTrade" chamado "obj_Trade" para lidar com todas as operações de trading. Inicializamos os principais handles dos indicadores — "rsiHandle" para RSI e "handleMACD" para MACD — ambos inicialmente definidos como INVALID_HANDLE, juntamente com arrays como "rsiValues", "macdMAIN" e "macdSIGNAL" para armazenar seus respectivos dados. Para acompanhar o estado da estratégia, definimos variáveis como "takeProfitLevel" e "stopLossLevel" para os níveis dos trades, além das flags booleanas "buySequenceActive" e "sellSequenceActive" para monitorar se uma sequência de layering de compra ou venda está em andamento, garantindo que o sistema saiba quando executar os trades em cascata.
Em seguida, estabelecemos entradas configuráveis pelo usuário para tornar a estratégia adaptável, incluindo "stopLossPoints" para a distância inicial do stop loss, "tradeVolume" para o tamanho do lote e "minStopLossPoints" para stops mais ajustados nos trades em cascata. Para os indicadores, definimos "rsiLookbackPeriod" para determinar a janela de cálculo do RSI, "rsiOverboughtLevel" e "rsiOversoldLevel" como limites de entrada e "riskRewardRatio" para controlar os alvos de lucro em relação ao risco. Para incorporar métodos estatísticos, introduzimos "useStatisticalFilter" como uma opção de ativação, juntamente com "statAnalysisPeriod" e "statDeviationFactor", que nos permitirão refinar os sinais com base no comportamento estatístico do RSI, garantindo que os trades estejam alinhados com desvios significativos do mercado.
Por fim, preparamos o feedback visual definindo "takeProfitLineName" e "takeProfitTextName" como nomes de objetos para linhas e rótulos de take-profit baseados no gráfico, aprimorando a capacidade do trader de monitorar os níveis em tempo real. Depois de compilarmos o programa, vemos a seguinte saída.

Em seguida, passamos para o manipulador de eventos de inicialização (OnInit), onde tratamos as propriedades de inicialização.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){//---- Expert advisor initialization function rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);//---- Creates RSI indicator handle with specified parameters handleMACD = iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);//---- Creates MACD indicator handle with standard 12,26,9 settings if(rsiHandle == INVALID_HANDLE){//---- Checks if RSI handle creation failed Print("UNABLE TO LOAD RSI, REVERTING NOW");//---- Prints error message if RSI failed to load return(INIT_FAILED);//---- Returns initialization failure code } if(handleMACD == INVALID_HANDLE){//---- Checks if MACD handle creation failed Print("UNABLE TO LOAD MACD, REVERTING NOW");//---- Prints error message if MACD failed to load return(INIT_FAILED);//---- Returns initialization failure code } ArraySetAsSeries(rsiValues, true);//---- Sets RSI values array as a time series (latest data at index 0) ArraySetAsSeries(macdMAIN,true);//---- Sets MACD main line array as a time series ArraySetAsSeries(macdSIGNAL,true);//---- Sets MACD signal line array as a time series return(INIT_SUCCEEDED);//---- Returns successful initialization code }
Aqui, começamos a implementar nossa estratégia de trade layering na função OnInit, o ponto de partida onde inicializamos os principais componentes antes que o EA comece a trabalhar com os dados do mercado. Configuramos o indicador RSI utilizando a função iRSI para atribuir um handle a "rsiHandle", passando _Symbol para o gráfico atual, _Period para o prazo, "rsiLookbackPeriod" para a janela de lookback e "PRICE_CLOSE" para utilizar os preços de fechamento, seguido pela configuração do MACD com a função iMACD, armazenando seu handle em "handleMACD" e utilizando os períodos padrão 12, 26, 9 em "PRICE_CLOSE" para análise de tendência.
Para garantir a robustez, verificamos se "rsiHandle" é igual a INVALID_HANDLE e, em caso afirmativo, utilizamos a função Print para registrar "UNABLE TO LOAD RSI, REVERTING NOW" e retornar INIT_FAILED, repetindo esse procedimento para "handleMACD" com "UNABLE TO LOAD MACD, REVERTING NOW" para interromper a execução em caso de falha. Após a confirmação, configuramos "rsiValues", "macdMAIN" e "macdSIGNAL" como arrays de séries temporais utilizando a função ArraySetAsSeries com true, alinhando os dados mais recentes no índice zero, e então retornamos INIT_SUCCEEDED para indicar uma configuração bem-sucedida e pronta para o trading. Em seguida, podemos ir para o manipulador de eventos OnTick e definir nossa lógica de trading propriamente dita.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){//---- Function called on each price tick double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);//---- Gets and normalizes current ask price double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);//---- Gets and normalizes current bid price if(CopyBuffer(rsiHandle, 0, 1, 3, rsiValues) < 3){//---- Copies 3 RSI values into array, checks if successful Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");//---- Prints error if insufficient RSI data return;//---- Exits function if data copy fails } if (!CopyBuffer(handleMACD,MAIN_LINE,0,3,macdMAIN))return;//---- Copies 3 MACD main line values, exits if fails if (!CopyBuffer(handleMACD,SIGNAL_LINE,0,3,macdSIGNAL))return;//---- Copies 3 MACD signal line values, exits if fails }
Aqui, avançamos nossa estratégia de trade layering implementando a função OnTick, que é ativada a cada atualização de preço para conduzir a tomada de decisões em tempo real no Expert Advisor. Começamos capturando os preços atuais do mercado, utilizando a função NormalizeDouble para definir "askPrice" com SymbolInfoDouble para _Symbol e "SYMBOL_ASK", ajustado à precisão de "_Digits", e "bidPrice" com "SYMBOL_BID", garantindo dados de preço precisos para os cálculos dos trades. Essa etapa estabelece a base para monitorar os movimentos de preço que acionarão nossa lógica de layering com base nas condições mais recentes do mercado.
Em seguida, coletamos os dados dos indicadores para analisar os sinais, começando pelo RSI utilizando a função CopyBuffer para carregar três valores em "rsiValues" a partir de "rsiHandle", começando no índice 1 com o buffer 0, e verificando se menos de 3 valores foram copiados — em caso afirmativo, utilizamos a função "Print" para registrar "INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK" e retornamos para sair do tick, evitando decisões com dados incompletos. Em seguida, aplicamos a mesma abordagem ao MACD, utilizando "CopyBuffer" para preencher "macdMAIN" com três valores da linha principal de "handleMACD" em MAIN_LINE, e "macdSIGNAL" com os valores da linha de sinal em SIGNAL_LINE, retornando imediatamente caso qualquer uma das operações falhe, garantindo que prossigamos somente com conjuntos completos de dados do RSI e MACD. No entanto, também precisaremos obter dados estatísticos e incorporá-los aqui. Então, vamos criar as funções.
//+------------------------------------------------------------------+ //| Calculate RSI Average | //+------------------------------------------------------------------+ double CalculateRSIAverage(int bars){//---- Function to calculate RSI average double sum = 0;//---- Initializes sum variable double buffer[];//---- Declares buffer array for RSI values ArraySetAsSeries(buffer, true);//---- Sets buffer as time series if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails for(int i = 0; i < bars; i++){//---- Loops through specified number of bars sum += buffer[i];//---- Adds each RSI value to sum } return sum / bars;//---- Returns average RSI value } //+------------------------------------------------------------------+ //| Calculate RSI STDDev | //+------------------------------------------------------------------+ double CalculateRSIStandardDeviation(int bars){//---- Function to calculate RSI standard deviation double average = CalculateRSIAverage(bars);//---- Calculates RSI average double sumSquaredDiff = 0;//---- Initializes sum of squared differences double buffer[];//---- Declares buffer array for RSI values ArraySetAsSeries(buffer, true);//---- Sets buffer as time series if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails for(int i = 0; i < bars; i++){//---- Loops through specified number of bars double diff = buffer[i] - average;//---- Calculates difference from average sumSquaredDiff += diff * diff;//---- Adds squared difference to sum } return MathSqrt(sumSquaredDiff / bars);//---- Returns standard deviation }
Implementamos duas funções principais — "CalculateRSIAverage" e "CalculateRSIStandardDeviation" — para introduzir a análise estatística dos dados do RSI, aumentando a precisão dos sinais. Em "CalculateRSIAverage", definimos uma função que recebe "bars" como entrada, inicializando "sum" com 0 e declarando um array "buffer", que configuramos como uma série temporal utilizando a função ArraySetAsSeries com true, garantindo que os valores mais recentes do RSI estejam alinhados no índice zero. Em seguida, utilizamos a função CopyBuffer para carregar em "buffer" a quantidade "bars" de valores do RSI a partir de "rsiHandle", retornando 0 se forem copiados menos valores que "bars", e percorremos o array para adicionar cada "buffer[i]" a "sum", retornando finalmente "sum/bars" como a média do RSI para utilização na filtragem estatística.
Em seguida, em "CalculateRSIStandardDeviation", desenvolvemos essa lógica calculando o desvio padrão do RSI durante o mesmo período "bars", começando pela chamada de "CalculateRSIAverage" para armazenar o resultado em "average" e definindo "sumSquaredDiff" como 0 para as diferenças ao quadrado. Declaramos novamente um array "buffer", configuramos como uma série temporal com ArraySetAsSeries e utilizamos a função "CopyBuffer" para obter os valores do RSI a partir de "rsiHandle", retornando 0 se a cópia falhar, garantindo a integridade dos dados. Percorremos "buffer", calculando cada "diff" como "buffer[i] - average", adicionando "diff * diff" a "sumSquaredDiff", e retornamos o desvio padrão utilizando a função MathSqrt em "sumSquaredDiff/bars", fornecendo uma medida estatística para refinar nossas decisões de trade layering. Agora podemos utilizar esses dados para a análise estatística, mas precisaremos executá-la uma vez por barra para evitar ambiguidades. Então, vamos definir uma função para isso.
//+------------------------------------------------------------------+ //| Is New Bar | //+------------------------------------------------------------------+ bool IsNewBar(){//---- Function to detect a new bar static int previousBarCount = 0;//---- Stores the previous bar count int currentBarCount = iBars(_Symbol, _Period);//---- Gets current number of bars if(previousBarCount == currentBarCount) return false;//---- Returns false if no new bar previousBarCount = currentBarCount;//---- Updates previous bar count return true;//---- Returns true if new bar detected }
Aqui, adicionamos a função "IsNewBar" à nossa estratégia para detectar novas barras, utilizando um "previousBarCount" estático definido como 0 para acompanhar a última contagem de barras e a função iBars para obter "currentBarCount" para _Symbol e _Period, retornando false se a contagem permanecer inalterada ou true após atualizar "previousBarCount" quando uma nova barra for formada. Com essa função, agora podemos definir as regras de trading.
// Calculate statistical measures if enabled double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : 0;//---- Calculates RSI average if filter enabled double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : 0;//---- Calculates RSI std dev if filter enabled
Implementamos aprimoramentos estatísticos calculando "rsiAverage" por meio da função "CalculateRSIAverage" com "statAnalysisPeriod" se "useStatisticalFilter" for true; caso contrário, definimos como 0. Da mesma forma, calculamos "rsiStdDeviation" com a função "CalculateRSIStandardDeviation" ou definimos como 0 por padrão, permitindo uma filtragem de sinais mais refinada quando ativada. Podemos então utilizar os resultados para definir as condições de trading. Começaremos pelas condições de compra.
if(PositionsTotal() == 0 && IsNewBar()){//---- Checks for no positions and new bar // Buy Signal bool buyCondition = rsiValues[1] <= rsiOversoldLevel && rsiValues[0] > rsiOversoldLevel;//---- Checks RSI crossing above oversold if(useStatisticalFilter){//---- Applies statistical filter if enabled buyCondition = buyCondition && (rsiValues[0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition } buyCondition = macdMAIN[0] < 0 && macdSIGNAL[0] < 0;//---- Confirms MACD below zero for buy signal }
Definimos a lógica do sinal de compra verificando se PositionsTotal é 0 e utilizando a função "IsNewBar" para confirmar uma nova barra, garantindo que os trades sejam acionados somente na abertura das barras e quando não houver posições abertas. Definimos "buyCondition" como true se "rsiValues[1]" estiver abaixo de "rsiOversoldLevel" e "rsiValues[0]" subir acima desse nível e, se "useStatisticalFilter" estiver habilitado, refinamos ainda mais a condição exigindo que "rsiValues[0]" seja menor que "rsiAverage" menos "statDeviationFactor" vezes "rsiStdDeviation", adicionando precisão estatística. Por fim, confirmamos o sinal com "macdMAIN[0]" e "macdSIGNAL[0]" ambos abaixo de zero, alinhando a compra com uma zona negativa do MACD para validação da tendência. Se as condições forem atendidas, prosseguimos para abrir as posições, inicializar as variáveis de acompanhamento e desenhar os níveis confirmados no gráfico, especificamente o nível de take-profit. Portanto, precisaremos de uma função personalizada para desenhar os níveis.
//+------------------------------------------------------------------+ //| Draw TrendLine | //+------------------------------------------------------------------+ void DrawTradeLevelLine(double price, bool isBuy){//---- Function to draw take profit line on chart // Delete existing objects first DeleteTradeLevelObjects();//---- Removes existing trade level objects // Create horizontal line ObjectCreate(0, takeProfitLineName, OBJ_HLINE, 0, 0, price);//---- Creates a horizontal line at specified price ObjectSetInteger(0, takeProfitLineName, OBJPROP_COLOR, clrBlue);//---- Sets line color to blue ObjectSetInteger(0, takeProfitLineName, OBJPROP_WIDTH, 2);//---- Sets line width to 2 ObjectSetInteger(0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);//---- Sets line style to solid // Create text, above for buy, below for sell with increased spacing datetime currentTime = TimeCurrent();//---- Gets current time double textOffset = 30.0 * _Point;//---- Sets text offset distance from line double textPrice = isBuy ? price + textOffset : price - textOffset;//---- Calculates text position based on buy/sell ObjectCreate(0, takeProfitTextName, OBJ_TEXT, 0, currentTime + PeriodSeconds(_Period) * 5, textPrice);//---- Creates text object ObjectSetString(0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));//---- Sets text to price value ObjectSetInteger(0, takeProfitTextName, OBJPROP_COLOR, clrBlue);//---- Sets text color to blue ObjectSetInteger(0, takeProfitTextName, OBJPROP_FONTSIZE, 10);//---- Sets text font size to 10 ObjectSetInteger(0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);//---- Sets text anchor based on buy/sell }
Aqui, implementamos a função "DrawTradeLevelLine" para visualizar os níveis de take-profit, começando pela chamada de "DeleteTradeLevelObjects" para remover os objetos existentes e, em seguida, utilizando a função ObjectCreate para desenhar uma linha horizontal em "price" com "takeProfitLineName" como OBJ_HLINE, configurada com ObjectSetInteger na cor azul "clrBlue", largura 2 e "STYLE_SOLID". Adicionamos um rótulo de texto obtendo "currentTime" com "TimeCurrent", definindo "textOffset" como "30.0 * _Point" e calculando "textPrice" acima ou abaixo de "price" com base em "isBuy", criando-o com "ObjectCreate" como "takeProfitTextName" em "OBJ_TEXT". Configuramos o texto utilizando ObjectSetString para exibir "price" por meio de DoubleToString com "_Digits", e "ObjectSetInteger" para a cor azul "clrBlue", tamanho 10 e "ANCHOR_BOTTOM" ou "ANCHOR_TOP" dependendo de "isBuy", melhorando a legibilidade do gráfico. Agora podemos utilizar a função para visualizar os níveis-alvo.
if(buyCondition){//---- Executes if buy conditions are met Print("BUY SIGNAL - RSI: ", rsiValues[0],//---- Prints buy signal details useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : ""); stopLossLevel = askPrice - stopLossPoints * _Point;//---- Calculates stop loss level for buy takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for buy obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Signal Position");//---- Places buy order buySequenceActive = true;//---- Activates buy sequence flag DrawTradeLevelLine(takeProfitLevel, true);//---- Draws take profit line for buy }
Aqui, tratamos a execução do trade de compra quando "buyCondition" é true, utilizando a função Print para registrar "BUY SIGNAL - RSI: " com "rsiValues[0]", acrescentando "rsiAverage" e "rsiStdDeviation" por meio da função DoubleToString se "useStatisticalFilter" estiver habilitado, fornecendo um feedback detalhado. Calculamos "stopLossLevel" como "askPrice" menos "stopLossPoints * _Point" e "takeProfitLevel" como "askPrice" mais "stopLossPoints * riskRewardRatio * _Point", e então utilizamos o método "obj_Trade.Buy" para colocar uma ordem de compra com "tradeVolume", _Symbol, "askPrice", "stopLossLevel" e "takeProfitLevel", identificada como "Signal Position". Por fim, definimos "buySequenceActive" como true e chamamos "DrawTradeLevelLine" com "takeProfitLevel" e true para visualizar a linha de take-profit da compra. Ao executar o programa, obtemos o seguinte resultado.

Na imagem, podemos ver que todas as condições do sinal de compra foram atendidas e marcamos automaticamente o próximo nível no gráfico. Assim, podemos continuar fazendo o mesmo para um sinal de venda.
// Sell Signal bool sellCondition = rsiValues[1] >= rsiOverboughtLevel && rsiValues[0] < rsiOverboughtLevel;//---- Checks RSI crossing below overbought if(useStatisticalFilter){//---- Applies statistical filter if enabled sellCondition = sellCondition && (rsiValues[0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition } sellCondition = macdMAIN[0] > 0 && macdSIGNAL[0] > 0;//---- Confirms MACD above zero for sell signal if(sellCondition){//---- Executes if sell conditions are met Print("SELL SIGNAL - RSI: ", rsiValues[0],//---- Prints sell signal details useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : ""); stopLossLevel = bidPrice + stopLossPoints * _Point;//---- Calculates stop loss level for sell takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for sell obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Signal Position");//---- Places sell order sellSequenceActive = true;//---- Activates sell sequence flag DrawTradeLevelLine(takeProfitLevel, false);//---- Draws take profit line for sell }
Aqui, aplicamos a lógica oposta à do trade de compra, definindo "sellCondition" se "rsiValues[1]" exceder "rsiOverboughtLevel" e "rsiValues[0]" cair abaixo desse nível, adicionando "useStatisticalFilter" para verificar se "rsiValues[0]" está acima de "rsiAverage + statDeviationFactor * rsiStdDeviation" e confirmando com "macdMAIN[0]" e "macdSIGNAL[0]" acima de zero. Se for true, utilizamos "Print" para "SELL SIGNAL - RSI: " com "rsiValues[0]" e as estatísticas por meio de DoubleToString, definimos "stopLossLevel" como "bidPrice + stopLossPoints * _Point" e "takeProfitLevel" como "bidPrice - (stopLossPoints * riskRewardRatio) * _Point", em seguida, chamamos "obj_Trade.Sell" e "DrawTradeLevelLine" com false, ativando "sellSequenceActive". Agora que as posições estão abertas, precisamos escalonar as posições vencedoras, acompanhando a tendência e modificando as posições. Aqui está uma função para modificar os trades.
//+------------------------------------------------------------------+ //| Modify Trades | //+------------------------------------------------------------------+ void ModifyTrades(ENUM_POSITION_TYPE positionType, double newStopLoss){//---- Function to modify open trades for(int i = 0; i < PositionsTotal(); i++){//---- Loops through all open positions ulong ticket = PositionGetTicket(i);//---- Gets ticket number of position if(ticket > 0 && PositionSelectByTicket(ticket)){//---- Checks if ticket is valid and selectable ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);//---- Gets position type if(type == positionType){//---- Checks if position matches specified type obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));//---- Modifies position with new stop loss } } } }
Aqui, implementamos a função "ModifyTrades" para atualizar os trades abertos, recebendo "positionType" e "newStopLoss" como entrada e, em seguida, utilizando a função PositionsTotal para percorrer todas as posições com um loop for de 0 até "i". Para cada uma delas, utilizamos a função PositionGetTicket para obter o número do "ticket", verificamos se ele é válido e pode ser selecionado com PositionSelectByTicket e utilizamos "PositionGetInteger" para obter o "type" como ENUM_POSITION_TYPE, comparando-o com "positionType". Se houver correspondência, utilizamos "obj_Trade.PositionModify" para ajustar o stop loss do trade para "newStopLoss", mantendo o take-profit obtido por PositionGetDouble com "POSITION_TP", garantindo um gerenciamento preciso do trade. Podemos então utilizar a função para colocar os trades em cascata.
else {//---- Handles cascading logic when positions exist // Cascading Buy Logic if(buySequenceActive && askPrice >= takeProfitLevel){//---- Checks if buy sequence active and price hit take profit double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level stopLossLevel = askPrice - minStopLossPoints * _Point;//---- Sets new stop loss level obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Cascade Position");//---- Places new buy order ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);//---- Modifies existing buy trades with new stop loss Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading buy details DrawTradeLevelLine(takeProfitLevel, true);//---- Updates take profit line for buy } // Cascading Sell Logic else if(sellSequenceActive && bidPrice <= takeProfitLevel){//---- Checks if sell sequence active and price hit take profit double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level stopLossLevel = bidPrice + minStopLossPoints * _Point;//---- Sets new stop loss level obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Cascade Position");//---- Places new sell order ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);//---- Modifies existing sell trades with new stop loss Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading sell details DrawTradeLevelLine(takeProfitLevel, false);//---- Updates take profit line for sell } }
Aqui, tratamos a lógica em cascata quando existem posições, verificando se "buySequenceActive" é true e se "askPrice" atinge "takeProfitLevel"; então armazenamos "previousTakeProfit", definimos um novo "takeProfitLevel" adicionando "stopLossPoints * riskRewardRatio * _Point" e "stopLossLevel" como "askPrice - minStopLossPoints * _Point", utilizando "obj_Trade.Buy" para uma nova ordem e "ModifyTrades" para atualizar os stops de POSITION_TYPE_BUY, com "Print" registrando os detalhes de "CASCADING BUY" e "DrawTradeLevelLine" atualizando a linha de compra.
Para as vendas, se "sellSequenceActive" for true e "bidPrice" atingir "takeProfitLevel", espelhamos essa lógica subtraindo de "previousTakeProfit" para definir o novo "takeProfitLevel", configurando "stopLossLevel" como "bidPrice + minStopLossPoints * _Point", chamando "obj_Trade.Sell" e "ModifyTrades" para POSITION_TYPE_SELL, registrando com "Print" e atualizando a linha de venda com "DrawTradeLevelLine". Ao executar o programa, obtemos o seguinte resultado.

Na imagem, podemos confirmar que as posições são colocadas em cascata e que o stop loss é modificado para todas as posições. Agora, precisamos garantir a limpeza dos objetos que adicionamos quando não precisarmos mais do sistema. Podemos fazer isso por meio do manipulador de eventos OnDeinit, mas primeiro precisaremos de uma função de limpeza.
//+------------------------------------------------------------------+ //| Delete Level Objects | //+------------------------------------------------------------------+ void DeleteTradeLevelObjects(){//---- Function to delete trade level objects ObjectDelete(0, takeProfitLineName);//---- Deletes take profit line object ObjectDelete(0, takeProfitTextName);//---- Deletes take profit text object }
Aqui, implementamos a função "DeleteTradeLevelObjects" para limpar os elementos visuais do gráfico, utilizando a função ObjectDelete para remover o objeto de linha "takeProfitLineName" e o objeto de texto "takeProfitTextName", garantindo que os níveis antigos de take-profit sejam removidos antes que novos sejam desenhados. Agora chamamos essa função no manipulador de eventos OnDeinit.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){//---- Expert advisor deinitialization function DeleteTradeLevelObjects();//---- Removes trade level visualization objects from chart }
Aqui, simplesmente chamamos a função para excluir os objetos do gráfico, garantindo que o gráfico seja limpo quando removermos o programa e, assim, atingindo nosso objetivo. O que resta agora é realizar o backtesting do programa, e isso será abordado na próxima seção.
Backtesting
Após um backtesting completo, obtivemos os seguintes resultados.
Gráfico de backtest:

Relatório de backtesting:

Conclusão
Em conclusão, desenvolvemos com sucesso uma estratégia de trade layering em MQL5, combinando MACD e RSI com métodos estatísticos para automatizar o dimensionamento dinâmico de posições em mercados com tendência. O programa apresenta detecção robusta de sinais, lógica de trades em cascata e níveis visuais de take-profit, adaptando-se com precisão às mudanças de momentum. Você pode utilizar essa base para aprimorá-la ainda mais com ajustes como a otimização de "rsiLookbackPeriod" ou a alteração de "riskRewardRatio" para obter um melhor desempenho.
Aviso: Este artigo destina-se apenas a fins educacionais. O trading envolve riscos financeiros significativos, e o comportamento do mercado pode ser volátil. Um Backtests exaustivos e um gerenciamento de risco adequado são fundamentais antes da utilização em uma conta real.
Com esta ilustração, você pode aprimorar suas habilidades de automação e refinar a estratégia. Experimente e otimize. Bons trades!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17741
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Engenharia de Atributos com Python e MQL5 (Parte IV): Reconhecimento de Padrões de Candlestick com Regressão UMAP
Desenvolvendo uma Estratégia de Pullback: Validação
Está chegando o novo MetaTrader 5 e MQL5
Redes neurais em trading: Treinamento de modelos spiking profundos (SEW-ResNet)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
A ideia de ir adicionando posições à medida que a tendência avança faz sentido, mas imagino que exija bastante disciplina para não se alavancar excessivamente em movimentos fortes. Você já testou essa estratégia em mercados laterais? Fico imaginando como ela se comporta quando a tendência enfraquece.