Automatização de estratégias de negociação em MQL5 (Parte 24): Sistema de negociação por rompimento da sessão de Londres com gerenciamento de risco e trailing stops
Introdução
No artigo anterior (Parte 23), aprimoramos o sistema Zone Recovery para negociação a favor da tendência, desenvolvido em MQL5 com o indicador Envelopes, adicionando trailing stops e múltiplas cestas de operações para melhorar a proteção dos lucros e o processamento dos sinais. Na Parte 24, desenvolveremos um sistema de rompimento da sessão de Londres que identifica a faixa de preços formada antes da abertura, posiciona ordens pendentes e incorpora ferramentas de gerenciamento de risco, como relação risco/retorno, limites de drawdown e um painel de controle para monitoramento em tempo real. Abordaremos os seguintes tópicos:
- Entendendo a estratégia de rompimento da sessão de Londres
- Implementação em MQL5
- BACKTESTING
- Conclusão
Ao final, você terá um programa completo em MQL5, com mecanismos avançados de controle de risco, pronto para testes e aprimoramentos. Vamos começar!
Entendendo a estratégia de rompimento da sessão de Londres
A estratégia de rompimento da sessão de Londres aproveita o aumento da volatilidade na abertura do mercado londrino. Ela identifica a faixa de preços formada nas horas que antecedem a abertura e posiciona ordens pendentes para operar seu rompimento. A sessão de Londres costuma apresentar alta liquidez e movimentos expressivos de preços, criando boas oportunidades de lucro. No entanto, a estratégia exige um gerenciamento de risco rigoroso para proteger o capital contra falsos rompimentos e drawdowns.
Para isso, calcularemos a máxima e a mínima do período anterior à sessão de Londres e utilizaremos esses níveis para posicionar ordens Buy Stop e Sell Stop com distâncias configuráveis em relação a eles. Também configuraremos a relação risco/retorno para determinar os níveis de take profit, trailing stops para assegurar os lucros e limites para o número de operações abertas e o drawdown diário, visando preservar o capital. Além disso, utilizaremos um painel de controle para monitoramento em tempo real e verificações do horário da sessão, garantindo que as operações sejam executadas apenas nos períodos definidos. O sistema também será capaz de se adaptar a diferentes condições de mercado. A seguir, apresentamos a estrutura conceitual do sistema que implementaremos.

Implementação em MQL5
Para criar o programa em MQL5, abra o MetaEditor, acesse o Navigator, localize a pasta Indicators, clique na aba New e siga as instruções para criar o arquivo. Após essa etapa, começaremos declarando os parâmetros de entrada e as estruturas no ambiente de desenvolvimento.
//+------------------------------------------------------------------+ //| London Breakout EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations //--- Enumerations enum ENUM_TRADE_TYPE { //--- Enumeration for trade types TRADE_ALL, // All Trades (Buy and Sell) TRADE_BUY_ONLY, // Buy Trades Only TRADE_SELL_ONLY // Sell Trades Only }; //--- Input parameters sinput group "General EA Settings" input double inpTradeLotsize = 0.01; // Lotsize input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection sinput int MagicNumber = 12345; // Magic Number input double RRRatio = 1.0; // Risk to Reward Ratio input int StopLossPoints = 500; // Stop loss in points input int OrderOffsetPoints = 10; // Points offset for Orders input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated? input bool UseTrailing = false; // Use Trailing Stop? input int TrailingPoints = 50; // Trailing Points (distance) input int MinProfitPoints = 100; // Minimum Profit Points to start trailing sinput group "London Session Settings" input int LondonStartHour = 9; // London Start Hour input int LondonStartMinute = 0; // London Start Minute input int LondonEndHour = 8; // London End Hour input int LondonEndMinute = 0; // London End Minute input int MinRangePoints = 100; // Min Pre-London Range in points input int MaxRangePoints = 300; // Max Pre-London Range in points sinput group "Risk Management" input int MaxOpenTrades = 2; // Maximum simultaneous open trades input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading //--- Structures struct PositionInfo { //--- Structure for position information ulong ticket; // Position ticket double openPrice; // Entry price double londonRange; // Pre-London range in points for this position datetime sessionID; // Session identifier (day) bool trailingActive; // Trailing active flag };
Começamos a implementação do sistema de rompimento da sessão de Londres incluindo a biblioteca <Trade\Trade.mqh> e definindo as enumerações, os parâmetros de entrada e a estrutura para monitoramento das posições. A biblioteca <Trade\Trade.mqh> fornece acesso à classe CTrade, que permite executar operações de negociação, como posicionar ordens e modificar posições. Definimos a enumeração ENUM_TRADE_TYPE com as opções TRADE_ALL para negociar em ambas as direções, TRADE_BUY_ONLY para permitir apenas compras e TRADE_SELL_ONLY para permitir apenas vendas, possibilitando restringir as direções das operações.
Em seguida, organizamos os parâmetros de entrada por grupos. Nas configurações gerais, inpTradeLotsize é definido como 0.01 para o tamanho do lote; TradeType utiliza a enumeração, com TRADE_ALL como valor padrão; MagicNumber recebe 12345 para identificar as operações do EA; RRRatio é definido como 1.0 para a relação risco/retorno; StopLossPoints recebe 500 para a distância do stop loss; OrderOffsetPoints = 10 define a distância de entrada em relação ao nível de referência; DeleteOppositeOrder é definido como true para excluir a ordem pendente oposta; UseTrailing é definido como false, deixando o trailing stop desativado por padrão; TrailingPoints recebe 50 para definir a distância do trailing stop em pontos; e MinProfitPoints recebe 100 para definir o lucro mínimo, em pontos, necessário à ativação do trailing stop.
Na seção London Session Settings, definimos o início da sessão com LondonStartHour = 9 e LondonStartMinute = 0, e o término com LondonEndHour = 8 e LondonEndMinute = 0. Também utilizamos MinRangePoints = 100 e MaxRangePoints = 300 para validar a amplitude da faixa de preços. Na seção Risk Management, MaxOpenTrades é definido como 2 para limitar o número de posições abertas simultaneamente, enquanto MaxDailyDrawdownPercent recebe 5.0 para interromper as operações quando o drawdown ultrapassar o limite estabelecido. Definimos a estrutura PositionInfo para monitorar as posições abertas, na qual ticket armazena o identificador da posição, openPrice representa o preço de abertura, londonRange corresponde à faixa de preços anterior à sessão de Londres, sessionID identifica o dia da sessão e trailingActive é um valor booleano que indica se o trailing stop está ativo. Após a compilação, obtemos o seguinte resultado.

Agora podemos definir as variáveis globais adicionais que serão utilizadas no programa.
//--- Global variables CTrade obj_Trade; //--- Trade object double PreLondonHigh = 0.0; //--- Pre-London session high double PreLondonLow = 0.0; //--- Pre-London session low datetime PreLondonHighTime = 0; //--- Time of Pre-London high datetime PreLondonLowTime = 0; //--- Time of Pre-London low ulong buyOrderTicket = 0; //--- Buy stop order ticket ulong sellOrderTicket = 0; //--- Sell stop order ticket bool panelVisible = true; //--- Panel visibility flag double LondonRangePoints = 0.0; //--- Current session's Pre-London range PositionInfo positionList[]; //--- Array to store position info datetime lastCheckedDay = 0; //--- Last checked day bool noTradeToday = false; //--- Flag to prevent trading today bool sessionChecksDone = false; //--- Flag for session checks completion datetime analysisTime = 0; //--- Time for London analysis double dailyDrawdown = 0.0; //--- Current daily drawdown bool isTrailing = false; //--- Global flag for any trailing active const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute
Declaramos as seguintes variáveis globais: obj_Trade, um objeto da classe CTrade para executar operações de negociação; PreLondonHigh e PreLondonLow, do tipo double, para armazenar os valores máximo e mínimo da faixa de preços anterior à sessão de Londres; PreLondonHighTime e PreLondonLowTime, do tipo datetime, para registrar os horários em que esses extremos foram formados; buyOrderTicket e sellOrderTicket, do tipo ulong, para armazenar os identificadores das ordens; panelVisible, inicializada como true, para controlar a exibição do painel; LondonRangePoints, inicializada como 0.0, para armazenar a amplitude atual da faixa de preços; positionList, um array de estruturas PositionInfo para monitorar as posições; lastCheckedDay, inicializada como 0, para controlar a verificação diária; noTradeToday e sessionChecksDone, inicializadas como false, para controlar as condições de negociação e as verificações da sessão; analysisTime, inicializada como 0, para registrar o horário da análise; dailyDrawdown, inicializada como 0.0, para acompanhar o drawdown diário; isTrailing, inicializada como false, para controlar o trailing stop; além das constantes PreLondonStartHour = 3 e PreLondonStartMinute - 0.
Com essas definições concluídas, começaremos pela criação do painel, que é a etapa mais simples, antes de avançarmos para a lógica de negociação, mais complexa. Primeiro, implementaremos as funções necessárias para criar o painel.
//+------------------------------------------------------------------+ //| Create a rectangle label for the panel background | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for panel elements | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 10, string font = "Arial") { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Nesta etapa, implementaremos funções auxiliares para criar os elementos da interface do painel de controle, utilizando rótulos retangulares para o fundo e rótulos de texto para exibir as informações. Primeiro, criamos a função createRecLabel, responsável pelos rótulos retangulares que compõem o fundo do painel, recebendo os parâmetros necessários. Redefinimos o código do último erro com ResetLastError e utilizamos ObjectCreate para criar um objeto do tipo OBJ_RECTANGLE_LABEL. Se a criação falhar, exibimos o erro com Print e retornamos false. Em seguida, configuramos OBJPROP_XDISTANCE e as demais propriedades de valores inteiros com ObjectSetInteger, forçamos o redesenho do gráfico com ChartRedraw e retornamos true.
Depois, criamos a função createLabel para os rótulos de texto do painel, com os parâmetros objName, xD, yD, txt, clrTxt, fontSize e font. Redefinimos o código do último erro com ResetLastError e utilizamos ObjectCreate para criar um objeto do tipo OBJ_LABEL. Em caso de falha, exibimos o erro com Print e retornamos false. As propriedades de valores inteiros são configuradas com ObjectSetInteger, como na função dos rótulos retangulares, mas também utilizamos ObjectSetString para definir OBJPROP_TEXT e OBJPROP_FONT. Por fim, forçamos o redesenho do gráfico com ChartRedraw e retornamos true. Essas funções permitirão criar um painel de controle dinâmico para monitorar os dados da sessão e o estado do programa. Agora podemos utilizá-las para criar e atualizar o painel.
string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects //+------------------------------------------------------------------+ //| Create the information panel | //+------------------------------------------------------------------+ void CreatePanel() { createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state } //+------------------------------------------------------------------+ //| Update panel with current data | //+------------------------------------------------------------------+ void UpdatePanel() { string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color }
Definimos a string panelPrefix com o valor LondonPanel_ para adicionar um prefixo aos nomes de todos os objetos do painel, facilitando a identificação de seus elementos. Criamos a função CreatePanel para montar a interface do painel informativo. Chamamos createRecLabel com panelPrefix + Background para criar o fundo do painel na posição 10,10, com dimensões de 270x200, cor clrMidnightBlue, borda prateada e espessura de 1. Em seguida, utilizamos createLabel para adicionar o título London Breakout Control Center na posição 20,15, em dourado e com tamanho de fonte 12. Também adicionamos rótulos para a amplitude da faixa de preços, o preço máximo, o preço mínimo, o nível de compra, o nível de venda, o saldo, o patrimônio líquido (equity) e o drawdown, nas respectivas posições, em branco e com tamanho de fonte 10.
Para criar o botão de ocultação, chamamos createRecLabel com panelPrefix + Hide na posição 250,10, com dimensões de 30x22 e fundo clrCrimson. Depois, utilizamos createLabel com panelPrefix + HideText, passando CharToString(251) da fonte Wingdings, na posição 258,12, com cor clrWhite e tamanho de fonte 13. Definimos OBJPROP_SELECTABLE e OBJPROP_STATE como true por meio de ObjectSetInteger para tornar o botão interativo. A escolha do código Wingdings depende de suas preferências visuais. A seguir, apresentamos a lista de códigos disponíveis.

Em seguida, implementamos a função UpdatePanel para atualizar as informações exibidas no painel. Formatamos rangeText com o valor de LondonRangePoints por meio de DoubleToString ou exibimos Calculating... quando o valor é zero. Depois, atualizamos o texto do objeto panelPrefix + RangePoints com ObjectSetString. Da mesma forma, formatamos e atualizamos os valores do preço máximo, do preço mínimo, do nível de compra (somando a PreLondonHigh o produto de OrderOffsetPoints por _Point), do nível de venda (subtraindo de PreLondonLow o produto de OrderOffsetPoints por _Point), do saldo obtido por AccountInfoDouble(ACCOUNT_BALANCE), do patrimônio líquido obtido por AccountInfoDouble(ACCOUNT_EQUITY) e do drawdown registrado em dailyDrawdown.
A cor do drawdown é definida com ObjectSetInteger: amarelo quando o drawdown diário ultrapassa MaxDailyDrawdownPercent / 2 e branco nos demais casos. Para colocar essas funções em funcionamento, chamamos cada uma delas na função de inicialização, conforme mostrado a seguir.
//+------------------------------------------------------------------+ //| Initialize EA | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number ArrayFree(positionList); //--- Free position list CreatePanel(); //--- Create panel panelVisible = true; //--- Set panel visible return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Deinitialize EA | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ArrayFree(positionList); //--- Free position list }
No manipulador de eventos OnInit, configuramos o magic number por meio do objeto de negociação, utilizamos ArrayFree para limpar o array de posições, chamamos CreatePanel para criar o painel e definimos o flag de visibilidade como true após sua criação. Em seguida, no manipulador de eventos OnDeinit, utilizamos ObjectsDeleteAll para excluir todos os objetos com o prefixo especificado e liberamos o array de posições, pois ele não será mais necessário. Após a compilação, obtemos o seguinte resultado.

Com o painel criado, adicionaremos interatividade ao botão de fechamento para que, ao ser clicado, ele remova o painel do gráfico. Implementaremos esse comportamento no manipulador de eventos OnChartEvent.
//+------------------------------------------------------------------+ //| Handle chart events (e.g., panel close) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click panelVisible = false; //--- Set panel hidden ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ChartRedraw(0); //--- Redraw chart } }
No manipulador OnChartEvent, verificamos se o identificador do evento corresponde a um clique em objeto e se o objeto clicado é o botão de ocultação ou fechamento. Em caso afirmativo, definimos o flag de visibilidade como false, excluímos os objetos do painel e forçamos o redesenho do gráfico para aplicar as alterações. Após a compilação, obtemos o seguinte resultado.

A animação mostra que o painel está completamente pronto. Agora vamos atualizar o painel para inicializar todos os seus elementos. Para isso, precisaremos de funções que definam as faixas de preços diárias e calculem o drawdown.
//+------------------------------------------------------------------+ //| Check if it's a new trading day | //+------------------------------------------------------------------+ bool IsNewDay(datetime currentBarTime) { MqlDateTime barTime; //--- Bar time structure TimeToStruct(currentBarTime, barTime); //--- Convert time datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day if (currentDay != lastCheckedDay) { //--- Check new day lastCheckedDay = currentDay; //--- Update last day sessionChecksDone = false; //--- Reset checks noTradeToday = false; //--- Reset no trade buyOrderTicket = 0; //--- Reset buy ticket sellOrderTicket = 0; //--- Reset sell ticket LondonRangePoints = 0.0; //--- Reset range return true; //--- Return new day } return false; //--- Return not new day } //+------------------------------------------------------------------+ //| Update daily drawdown | //+------------------------------------------------------------------+ void UpdateDailyDrawdown() { static double maxEquity = 0.0; //--- Max equity tracker double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity if (equity > maxEquity) maxEquity = equity; //--- Update max equity dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded }
Implementamos a função IsNewDay para detectar o início de um novo dia de negociação. Criamos a estrutura MqlDateTime barTime e utilizamos TimeToStruct para converter currentBarTime nessa estrutura. Em seguida, calculamos currentDay com StringToTime e StringFormat, utilizando os campos barTime.year, barTime.mon e barTime.day. Se currentDay for diferente de lastCheckedDay, atualizamos lastCheckedDay, redefinimos sessionChecksDone e noTradeToday como false, zeramos buyOrderTicket e sellOrderTicket, definimos LondonRangePoints como 0.0 e retornamos true. Caso contrário, retornamos false. Essa função permite reinicializar diariamente os parâmetros de análise da sessão e os flags de controle da negociação.
Em seguida, implementamos a função UpdateDailyDrawdown para monitorar o risco diário. Utilizamos uma variável estática maxEquity, inicializada como 0.0, para acompanhar o maior valor registrado do patrimônio líquido (equity). Obtemos o valor atual com AccountInfoDouble(ACCOUNT_EQUITY) e atualizamos maxEquity sempre que o patrimônio líquido ultrapassa o máximo anterior. Depois, calculamos dailyDrawdown como a queda percentual em relação a maxEquity. Se dailyDrawdown atingir ou ultrapassar MaxDailyDrawdownPercent, definimos noTradeToday como true, impedindo novas operações para proteger o capital contra o aumento do drawdown. Para utilizar essas funções, nós as chamamos no manipulador OnTick, juntamente com a atualização dos dados do painel.
//+------------------------------------------------------------------+ //| Main tick handler | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time IsNewDay(currentBarTime); //--- Check new day UpdatePanel(); //--- Update panel UpdateDailyDrawdown(); //--- Update drawdown }
Aqui, apenas chamamos as funções responsáveis por detectar um novo dia, reinicializar os parâmetros da faixa de preços, atualizar o painel e calcular o drawdown diário. Ao executar o programa, obtemos o seguinte resultado.

A imagem mostra que o painel agora está preenchido com dados atualizados e exibe o estado atual do sistema. Vamos avançar para uma lógica mais complexa: a identificação das faixas de preços da sessão. Primeiro, verificaremos as condições de negociação e posicionaremos as ordens quando forem atendidas. Depois, implementaremos o gerenciamento das posições. Para isso, precisaremos de funções para calcular a faixa de preços, representá-la no gráfico e posicionar as ordens. A seguir, apresentamos a lógica de implementação.
//+------------------------------------------------------------------+ //| Fixed lot size | //+------------------------------------------------------------------+ double CalculateLotSize(double entryPrice, double stopLossPrice) { return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size } //+------------------------------------------------------------------+ //| Calculate session range (high-low) in points | //+------------------------------------------------------------------+ double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) { int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time return (highVal - lowVal) / _Point; //--- Return range in points } //+------------------------------------------------------------------+ //| Place pending buy/sell stop orders | //+------------------------------------------------------------------+ void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) { double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket } if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } } //+------------------------------------------------------------------+ //| Draw session ranges on the chart | //+------------------------------------------------------------------+ void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) { string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID string preRectName = "PreRect_" + sessionID; //--- Rectangle name ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background }
Para calcular a faixa de preços, posicionar as ordens e desenhar os elementos no gráfico, começamos pela função CalculateLotSize, que determina um tamanho de lote fixo. Passamos os parâmetros entryPrice e stopLossPrice, que não são utilizados no cálculo com lote fixo. A função retorna inpTradeLotsize, normalizado com NormalizeDouble para duas casas decimais, garantindo o mesmo tamanho de lote em todas as operações. Você pode ajustar o número de casas decimais de acordo com o tipo de conta.
Em seguida, criamos a função GetRange para calcular a faixa de preços anterior à sessão de Londres. Obtemos com iBarShift os índices startBar e endBar correspondentes a startTime e endTime e retornamos -1 se os valores forem inválidos ou se startBar < endBar. Identificamos a barra de máxima highestBar no intervalo especificado com iHighest e MODE_HIGH, e a barra de mínima lowestBar com iLowest e MODE_LOW. Obtemos highVal com iHigh para highestBar, lowVal com iLow para lowestBar, highTime com iTime para highestBar e lowTime com iTime para lowestBar. Por fim, retornamos a amplitude da faixa de preços, em pontos, calculada como (highVal - lowVal) / _Point.
Depois, definimos a função PlacePendingOrders para posicionar as ordens pendentes Buy Stop e Sell Stop. Calculamos o preço de compra buyPrice somando a preLondonHigh o produto de OrderOffsetPoints por _Point, e o preço de venda sellPrice subtraindo de preLondonLow esse mesmo deslocamento. Definimos slPoints como StopLossPoints, buySL como buyPrice menos slPoints multiplicado por _Point e sellSL como sellPrice mais slPoints multiplicado por _Point. Em seguida, calculamos tpPoints como slPoints multiplicado por RRRatio, buyTP como buyPrice mais tpPoints multiplicado por _Point e sellTP como sellPrice menos tpPoints multiplicado por _Point. Calculamos os tamanhos de lote lotSizeBuy e lotSizeSell com CalculateLotSize.
Se TradeType for TRADE_ALL ou TRADE_BUY_ONLY, posicionamos uma ordem Buy Stop por meio de obj_Trade.BuyStop, passando lotSizeBuy, buyPrice, buySL, buyTP e o comentário "Buy Stop - London". Armazenamos o ticket da ordem em buyOrderTicket, obtido por ResultOrder. Da mesma forma, posicionamos uma ordem Sell Stop se TradeType for TRADE_ALL ou TRADE_SELL_ONLY.
Por fim, implementamos a função DrawSessionRanges para representar visualmente a sessão no gráfico. Criamos sessionID concatenando o prefixo "Sess_" ao valor de lastCheckedDay, convertido em string com IntegerToString. Para o retângulo identificado por preRectName = "PreRect_" + sessionID, utilizamos ObjectCreate com o tipo OBJ_RECTANGLE, definindo os pontos de referência de PreLondonHighTime, PreLondonHigh até PreLondonLowTime, PreLondonLow. Configuramos OBJPROP_COLOR como clrTeal, OBJPROP_FILL como true para preencher o retângulo e OBJPROP_BACK como true para exibi-lo ao fundo do gráfico.
Para a linha superior, cujo nome é armazenado em preTopLineName e formado pela concatenação de "PreTopLine_" com sessionID, criamos um objeto OBJ_TREND que vai de preLondonStart, PreLondonHigh até londonEnd, PreLondonHigh. Configuramos OBJPROP_COLOR como clrBlack, OBJPROP_WIDTH como 1, OBJPROP_RAY_RIGHT como false e OBJPROP_BACK como true. Da mesma forma, criamos a linha inferior preBotLineName, concatenando "PreBottomLine_" com sessionID, de preLondonStart, PreLondonLow até londonEnd, PreLondonLow, utilizando a cor vermelha. Com essas funções implementadas, podemos definir a função responsável por verificar as condições de negociação.
//+------------------------------------------------------------------+ //| Check trading conditions and place orders | //+------------------------------------------------------------------+ void CheckTradingConditions(datetime currentTime) { MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert time datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end analysisTime = londonStart; //--- Set analysis time if (currentTime < analysisTime) return; //--- Exit if before analysis double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges return; //--- Exit } LondonRangePoints = preLondonRange; //--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges }
Implementamos a função CheckTradingConditions para avaliar as condições da sessão e posicionar as ordens em nosso sistema de rompimento da sessão de Londres. Criamos a estrutura MqlDateTime timeStruct e utilizamos TimeToStruct para converter o horário atual nessa estrutura. Em seguida, calculamos today com StringToTime e StringFormat, utilizando os campos timeStruct.year, timeStruct.mon e timeStruct.day. Definimos preLondonStart somando a today os valores de PreLondonStartHour e PreLondonStartMinute convertidos em segundos. Calculamos londonStart da mesma forma, utilizando LondonStartHour e LondonStartMinute, e londonEnd com LondonEndHour e LondonEndMinute. Atribuímos londonStart a analysisTime e encerramos a execução da função se o horário atual for anterior a esse valor.
Obtemos preLondonRange com a função GetRange, passando preLondonStart, currentTime e as referências a PreLondonHigh, PreLondonLow, PreLondonHighTime e PreLondonLowTime. Se preLondonRange for menor que MinRangePoints ou maior que MaxRangePoints, definimos os flags noTradeToday e sessionChecksDone como true, chamamos DrawSessionRanges com preLondonStart e londonEnd e encerramos a execução da função. Caso contrário, atribuímos preLondonRange a LondonRangePoints, chamamos PlacePendingOrders com PreLondonHigh, PreLondonLow e today, definimos noTradeToday e sessionChecksDone como true e chamamos DrawSessionRanges. Dessa forma, as ordens são posicionadas apenas quando a amplitude da faixa de preços atende aos limites estabelecidos. Agora podemos chamar essa função no manipulador OnTick para gerar os sinais de negociação.
if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions CheckTradingConditions(TimeCurrent()); //--- Check conditions }
Se ainda não houver operações no dia atual e a verificação da sessão não tiver sido realizada, chamamos a função de verificação das condições, passando o horário atual. Ao executar o programa, obtemos o seguinte resultado.

Podemos observar que as faixas de preços foram definidas e as ordens pendentes foram posicionadas. A amplitude da faixa de preços é de 100 pontos, atendendo às nossas condições de negociação. Agora podemos avançar para o gerenciamento das operações, mas primeiro adicionaremos uma verificação para excluir a ordem pendente oposta quando uma delas for ativada. Também incluiremos as posições na lista para gerenciá-las posteriormente.
//+------------------------------------------------------------------+ //| Delete opposite pending order when one is filled | //+------------------------------------------------------------------+ void CheckAndDeleteOppositeOrder() { if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable bool buyOrderExists = false; //--- Buy exists flag bool sellOrderExists = false; //--- Sell exists flag for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders ulong orderTicket = OrderGetTicket(i); //--- Get ticket if (OrderSelect(orderTicket)) { //--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order } } //+------------------------------------------------------------------+ //| Add position to tracking list when opened | //+------------------------------------------------------------------+ void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) { if (londonRange <= 0) return; //--- Exit if invalid range int index = ArraySize(positionList); //--- Get current size ArrayResize(positionList, index + 1); //--- Resize array positionList[index].ticket = ticket; //--- Set ticket positionList[index].openPrice = openPrice; //--- Set open price positionList[index].londonRange = londonRange; //--- Set range positionList[index].sessionID = sessionID; //--- Set session ID positionList[index].trailingActive = false; //--- Set trailing inactive }
Começamos pela função CheckAndDeleteOppositeOrder, responsável por excluir a ordem pendente oposta quando uma das ordens for ativada. Encerramos imediatamente a execução da função se DeleteOppositeOrder for false ou se TradeType for diferente de TRADE_ALL. Inicializamos as variáveis buyOrderExists e sellOrderExists como false. Em seguida, percorremos as ordens em ordem inversa, utilizando OrdersTotal e OrderGetTicket, e selecionamos cada uma com OrderSelect. Verificamos se a ordem pertence ao símbolo _Symbol e possui o MagicNumber correspondente, utilizando OrderGetString e OrderGetInteger. Se essas condições forem atendidas, definimos buyOrderExists ou sellOrderExists como true, conforme o ticket da ordem corresponda a buyOrderTicket ou sellOrderTicket.
Se a ordem de compra não existir, mas a ordem de venda ainda estiver pendente, excluímos sellOrderTicket com obj_Trade.OrderDelete. Da mesma forma, se a ordem de venda não existir e a ordem de compra ainda estiver pendente, excluímos a ordem correspondente. Essa função garante que, após a ativação de uma das ordens, apenas uma direção de negociação permaneça ativa.
Em seguida, criamos a função AddPositionToList para monitorar as posições abertas. Encerramos a execução da função se londonRange <= 0, pois a faixa de preços ainda não foi definida. Obtemos o índice do próximo elemento com ArraySize aplicado ao array positionList, aumentamos o tamanho do array com ArrayResize para incluir um novo elemento e preenchemos os campos ticket, openPrice, londonRange e sessionID de positionList[index], definindo trailingActive como false. Dessa forma, mantemos uma lista de posições para gerenciar os trailing stops e acompanhar os dados da sessão. Agora podemos integrar essa lógica ao manipulador de eventos OnTick.
CheckAndDeleteOppositeOrder(); //--- Delete opposite order // Add untracked positions for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position bool tracked = false; //--- Tracked flag for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked } if (!tracked) { //--- If not tracked double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list } } }
Aqui, chamamos a função CheckAndDeleteOppositeOrder para gerenciar as ordens pendentes. Quando uma ordem é executada, a ordem oposta é excluída conforme a configuração de DeleteOppositeOrder, evitando que operações em direções opostas sejam abertas.
Em seguida, adicionamos ao array positionList as posições que ainda não estão sendo monitoradas, garantindo que todas as posições abertas relevantes sejam consideradas no gerenciamento dos trailing stops. Percorremos todas as posições com PositionsTotal e PositionGetTicket, obtendo o ticket de cada uma. Se PositionSelectByTicket for executada com sucesso e a posição corresponder ao símbolo _Symbol e ao MagicNumber configurado, conforme os valores obtidos com PositionGetString e PositionGetInteger, inicializamos o flag tracked como false. Depois, percorremos o array positionList com ArraySize e um laço interno para verificar se o ticket já está registrado em positionList[j].ticket. Se a posição ainda não estiver sendo monitorada, obtemos openPrice com PositionGetDouble(POSITION_PRICE_OPEN) e chamamos AddPositionToList, passando ticket, openPrice, LondonRangePoints e lastCheckedDay. Isso garante que cada posição que atende aos critérios seja adicionada à lista sem duplicações. A seguir, apresentamos o resultado.

Como tudo está funcionando corretamente nesta etapa, podemos definir a função de gerenciamento das posições.
//+------------------------------------------------------------------+ //| Remove position from tracking list when closed | //+------------------------------------------------------------------+ void RemovePositionFromList(ulong ticket) { for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list if (positionList[i].ticket == ticket) { //--- Match ticket for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements positionList[j] = positionList[j + 1]; //--- Copy next } ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array break; //--- Exit loop } } } //+------------------------------------------------------------------+ //| Manage trailing stops | //+------------------------------------------------------------------+ void ManagePositions() { if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing isTrailing = false; //--- Reset trailing flag double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double point = _Point; //--- Get point value for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions ulong ticket = positionList[i].ticket; //--- Get ticket if (!PositionSelectByTicket(ticket)) { //--- Select position RemovePositionFromList(ticket); //--- Remove if not selected continue; //--- Skip } if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching double openPrice = positionList[i].openPrice; //--- Get open price long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing double newSL = 0.0; //--- New SL variable if (positionType == POSITION_TYPE_BUY) { //--- Buy position newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL } else { //--- Sell position newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL } double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position positionList[i].trailingActive = true; //--- Set trailing active isTrailing = true; //--- Set global trailing } } } } }
Aqui, implementamos as funções para remover as posições fechadas da lista de monitoramento e gerenciar os trailing stops. Começamos pela função RemovePositionFromList, que recebe o parâmetro ticket e remove do array positionList a posição correspondente quando ela é fechada. Percorremos positionList utilizando ArraySize e, se positionList[i].ticket corresponder a ticket, deslocamos os elementos seguintes uma posição para trás por meio de um laço interno, copiando positionList[j + 1] para positionList[j]. Em seguida, reduzimos o tamanho do array com ArrayResize e encerramos o laço. Essa função mantém a lista atualizada e evita verificações desnecessárias de posições já fechadas, especialmente durante o gerenciamento dos trailing stops e após o fechamento das posições.
Em seguida, criamos a função ManagePositions para gerenciar os trailing stops das posições abertas. Encerramos imediatamente a execução da função se PositionsTotal for igual a 0 ou UseTrailing for false. Redefinimos isTrailing como false, obtemos currentBid e currentAsk com SymbolInfoDouble, utilizando SYMBOL_BID e SYMBOL_ASK, respectivamente, e atribuímos o valor de _Point à variável point. Percorremos o array positionList com ArraySize, obtemos o ticket de cada posição e tentamos selecioná-la com PositionSelectByTicket. Se a seleção falhar, chamamos RemovePositionFromList e continuamos para a próxima iteração. Se a posição não corresponder ao símbolo _Symbol ou ao MagicNumber configurado, conforme os valores obtidos com PositionGetString e PositionGetInteger, nós a ignoramos. Obtemos openPrice de positionList[i], identificamos o tipo da posição positionType com PositionGetInteger, determinamos currentPrice conforme o tipo de posição e calculamos profitPoints dividindo a diferença entre os preços por point.
Se profitPoints for maior ou igual a MinProfitPoints + TrailingPoints, calculamos o novo nível de stop loss newSL subtraindo de currentPrice o produto de TrailingPoints por point, no caso das posições de compra, ou somando esse valor a currentPrice, no caso das posições de venda. Obtemos o stop loss atual currentSL com PositionGetDouble e, se newSL representar uma melhoria de pelo menos um ponto em relação ao nível atual, modificamos a posição com obj_Trade.PositionModify, passando o valor normalizado de newSL e o take profit atual obtido com PositionGetDouble. Se a modificação for bem-sucedida, definimos positionList[i].trailingActive e isTrailing como true. Dessa forma, o stop loss acompanha dinamicamente os movimentos favoráveis do preço, protegendo os lucros e permitindo que as operações lucrativas continuem acumulando ganhos. Agora basta chamar essa função a cada tick para gerenciar as posições. Após a compilação, obtemos o seguinte resultado.

A imagem mostra que o sistema verifica as condições de negociação, posiciona as ordens e as gerencia de acordo com a estratégia. Agora resta testar o programa. Para isso, passaremos à próxima seção.
BACKTESTING
Após um backtesting detalhado, obtivemos os seguintes resultados.
Gráfico do backtesting:

Relatório do backtesting:

Conclusão
Neste artigo, desenvolvemos um sistema de negociação por rompimento da sessão de Londres em MQL5. O sistema analisa a faixa de preços formada antes da abertura da sessão e posiciona ordens pendentes com relação risco/retorno configurável, trailing stops e limites para o número de operações. Também implementamos um painel de controle para monitorar as faixas de preços, os níveis de negociação e o drawdown em tempo real. Graças à sua estrutura modular, que inclui a estrutura PositionInfo, o programa oferece uma abordagem eficiente para negociar rompimentos, com possibilidade de otimização adicional por meio do ajuste dos parâmetros da sessão e de risco.
Atenção: todo o conteúdo deste artigo destina-se exclusivamente a fins educacionais e não deve ser utilizado para outras finalidades. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade pode provocar perdas. Antes de utilizar o programa em uma conta real, é fundamental realizar testes rigorosos e adotar práticas adequadas de gerenciamento de risco.
Você pode utilizar livremente os conceitos e a implementação apresentados para adaptar o sistema ao seu estilo de negociação ou aprimorar suas próprias estratégias algorítmicas. Boas operações!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18867
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.
Introdução ao MQL5 (Parte 19): Automação da detecção de ondas de Wolfe
Recursos do Assistente MQL5 que você precisa conhecer (Parte 76): Uso de padrões do Awesome Oscillator e canais de envelopes com aprendizado supervisionado
Ferramentas de trading em MQL5 (Parte 6): Painel holográfico dinâmico com animação pulsante e controles
Machine Learning e Data Science (Parte 42): Previsão dos mercados acionários com N-BEATS em Python
- 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
O segundo problema é que os níveis de alta/baixa e os níveis de compra/venda no painel de controle não são atualizados.
Os níveis de alta e baixa estão claramente sendo identificados no gráfico, então imagino que os níveis de compra e venda também devam ser exibidos no gráfico e atualizados no painel de controle, já que são derivados diretamente dos níveis de alta e baixa.
Quais sugestões vocês têm para que isso funcione corretamente?
Agradeço desde já.
Quanto à sua segunda questão, o artigo explica isso, mas, supondo que o problema se deva a dados de teste insuficientes e para dar uma dica: quando a faixa está sendo calculada, você sempre verá o status “Calculando...” até que haja dados suficientes para definir a sessão da faixa de Londres ou qualquer outra sessão que você definir nas configurações. Supondo que você esteja usando as configurações padrão, com 3 horas pré-Londres, e que a data na captura de tela compartilhada seja 13 de fevereiro, duas barras após as 22h, o que equivale a 2 × 15 minutos = 30, o que resulta em 22h30, está fora do horário de cálculo do intervalo; portanto, os dados no painel devem continuar visíveis, já que o intervalo definido anteriormente ainda está em vigor, a menos que a primeira sessão ainda não tenha sido encontrada, e serão apagados assim que o cálculo do intervalo for concluído a partir da meia-noite. Veja abaixo:
Talvez seja necessário conferir a lógica abaixo para identificar o intervalo
e como ele é definido.
Veja a imagem abaixo; embora não saibamos o ano do seu teste, vamos considerar 2025. Se for 2020, como no seu caso, não temos dados de qualidade para esse ano; de qualquer forma, usamos 2025 e, portanto, o cálculo do intervalo deve começar à meia-noite.
Na imagem, você pode ver que os dados às 23h55 ainda estão intactos. No entanto, quando chega a meia-noite, devemos reiniciar. Veja abaixo.
Você pode ver que reiniciamos os dados à meia-noite para o cálculo do outro intervalo. Na verdade, quando o cálculo do intervalo é concluído, a visualização pode ajudar você a entender o que realmente aconteceu. Por exemplo, no seu caso, usando as configurações padrão, veremos as barras de intervalo das 03h00 às 08h00, pois foi isso que definimos. Veja abaixo:
Espero que isso esclareça as coisas novamente. Você pode ajustar tudo de acordo com seu estilo de negociação. Para evitar os problemas que você está enfrentando, é recomendável usar dados de teste confiáveis. Obrigado.
Quanto à sua segunda dúvida, o artigo explica isso, mas, supondo que o problema se deva a dados de teste insuficientes e para dar uma dica: quando o intervalo está sendo calculado, você sempre verá o status “Calculando...” até que haja dados suficientes para definir o intervalo da sessão de Londres ou qualquer outra sessão que você definir nas entradas. Supondo que você esteja usando as configurações padrão, com 3 horas antes da sessão de Londres, e que a data na captura de tela compartilhada seja 13 de fevereiro, duas barras após as 22h, o que equivale a 2 × 15 minutos = 30, o que resulta em 22h30, está fora do horário de cálculo do intervalo; portanto, os dados no painel devem continuar visíveis, já que o intervalo definido anteriormente ainda está em vigor, a menos que a primeira sessão ainda não tenha sido encontrada, e serão apagados assim que o cálculo do intervalo for concluído a partir da meia-noite. Veja abaixo:
Talvez seja necessário consultar a lógica abaixo para determinar o intervalo
e como ele é definido.
Veja a imagem abaixo; embora não saibamos o ano do seu teste, vamos considerar 2025. Se for 2020, como no seu caso, não temos dados de qualidade para esse ano; portanto, de qualquer forma, usaremos 2025 e, assim, o cálculo do intervalo deve começar à meia-noite.
Pela imagem, você pode ver que os dados às 23h55 ainda estão intactos. No entanto, quando chegar à meia-noite, devemos reiniciar. Veja abaixo.
Você pode ver que reiniciamos os dados à meia-noite para o cálculo do outro intervalo. Na verdade, quando o cálculo do intervalo é concluído, a visualização pode ajudar a entender o que realmente aconteceu. Por exemplo, no seu caso, usando as configurações padrão, veremos as barras de intervalo das 03h00 às 08h00, pois foi isso que definimos. Veja abaixo:
Espero que isso tenha esclarecido as coisas novamente. Você pode ajustar tudo de acordo com seu estilo de negociação. Para evitar os problemas que você está enfrentando, é recomendável usar dados de teste confiáveis. Obrigado.
Muito obrigado pela resposta completa.
Sim, eu li o artigo e segui as instruções para codificar minha própria versão até encontrar os problemas que descrevi. O que observei foi que o painel não atualizou, mesmo durante os horários padrão. Minha captura de tela tinha o objetivo de mostrar que, embora a caixa tivesse sido desenhada no gráfico e os dados tivessem sido coletados, o painel não havia sido atualizado. Além disso, não havia mensagens de erro nos logs relacionadas a preços ou níveis inválidos.
Adicionei mensagens de log à minha versão; a partir delas, percebo que o painel não é atualizado quando o intervalo é muito grande ou muito pequeno; então, isso pode ser parte do motivo.
Vou verificar novamente a qualidade dos dados de teste. E obrigado por indicar em qual par você fez o teste; certamente farei ajustes para os pares que escolhi.
Muito obrigado pela sua ajuda.
Muito obrigado pela resposta detalhada.
Sim, eu li o artigo e segui as instruções para codificar minha própria versão até encontrar os problemas que descrevi. O que observei foi que o painel não atualizou, mesmo nos intervalos padrão. Minha captura de tela tinha o objetivo de mostrar que, embora a caixa tivesse sido desenhada no gráfico e os dados tivessem sido coletados, o painel não havia sido atualizado. Além disso, não havia mensagens de erro nos logs relacionadas a preços ou níveis inválidos.
Adicionei mensagens de log à minha versão; a partir delas, percebo que o painel não é atualizado quando o intervalo é muito grande ou muito pequeno; então, isso pode ser parte do motivo.
Vou verificar novamente a qualidade dos dados de teste. E obrigado por indicar em qual par você fez o teste; certamente farei ajustes para os pares que escolhi.
Muito obrigado pela sua ajuda.
Claro. De nada.
Obrigado por compartilhar seu código conosco.
Como eu mesmo já escrevi EAs dependentes de sessão, posso te dizer que o código só funciona se a sua corretora estiver sempre no fuso horário GMT+1 e também adotar o horário de verão britânico.
Em todos os outros casos, infelizmente, sua hora de início não está correta. Por quê? Porque a sessão de Londres começa às 8h, horário do Reino Unido. No inverno, são 8h GMT, e no verão, 7h GMT.
A função `TimeCurrent()` não retorna sua hora local, mas sempre a hora do servidor de negociação.