preview
Introduction to MQL5 (Part 38): Mastering API and WebRequest Function in MQL5 (XII)

Introduction to MQL5 (Part 38): Mastering API and WebRequest Function in MQL5 (XII)

MetaTrader 5Integration |
160 0
Israel Pelumi Abioye
Israel Pelumi Abioye

Introduction

Welcome back to Part 38 of the Introduction to MQL5 series! In the previous article, we concentrated on creating a strong framework for utilizing MQL5 to communicate with external systems. We looked at the creation of API endpoints, the operation of the WebRequest function, the preparation of authentication components like timestamps and signatures, and the reception and interpretation of server responses. When working with any private or confidential API interaction, these ideas are essential. 

This article uses an entirely project-based methodology, just like the previous sections of this series. Instead of talking about theory in isolation, we will put each idea into practice in a real-world process. This approach aids in your comprehension of both the functioning of separate parts and how they interact in an actual integration situation. This article brings the API series to a close and completes the journey.

In this article, we will create an MQL5 Expert Advisor that retrieves and analyzes market data by interacting with an external site like Binance. The project starts by choosing a certain coin on the site and using the API to retrieve candlestick data. To find a bullish engulfing pattern in the most recent finished candle, we will retrieve the last three closed 30-minute candles. We will go over the mechanism for programmatically identifying this pattern so you can use MQL5 to accurately detect it. We’ll also discuss how orders are really sent based on these conditions through API.

We will concentrate on how market data is retrieved and analyzed, how MetaTrader 5 securely creates requests, and how trading patterns are programmatically assessed throughout this process. This project shows how MQL5 may function as a bridge between MetaTrader 5 and platforms outside the MetaTrader ecosystem, going beyond conventional chart-based logic. By the end of this article, you will have a clear and reusable workflow for fetching candlestick data, understanding candle patterns such as bullish engulfing, and preparing your Expert Advisor to act on market conditions. You will also gain a strong understanding of how to adapt this approach for other patterns, timeframes, and advanced automation scenarios.

Note:

This article is written strictly for educational purposes. All examples, projects, and demonstrations are meant to explain how MQL5 works in real-world integration scenarios. It is not a recommendation or encouragement to execute live orders or perform any financial action. The focus is entirely on learning, experimentation, and understanding how to work with APIs and the WebRequest function in MQL5 within a controlled and educational context.


Requesting and Handling Candlestick Data

Requesting the details of the three most recent candlesticks is the first stage in creating this Expert Advisor. This would normally be simple because the symbol would already be there on the MetaTrader 5 chart. However, because the data for this project comes straight from Binance, the asset we are evaluating does not exist on MetaTrader 5. We are unable to rely on the built-in MetaTrader 5 price series or indication capabilities as a result. We start by submitting an API call to Binance to obtain candlestick data to address this issue. This strategy is comparable to what was previously shown in this series' Part 29, where we handled candle data straight from a server response rather than using MetaTrader 5 graphic data. The concept is similar here: we ask Binance for the raw candle data, which we then manually process within our Expert Advisor.

Sending an API request to obtain recent candlestick data for the chosen coin on Binance is the first thing the EA does. Understanding the JSON structure that Binance uses to display candlestick data is the next step after receiving the response. Candles are returned by Binance as structured arrays, with numerous values in each candlestick. Once we comprehend the JSON pattern, we utilize different MQL5 string handling routines to extract the vital facts. The opening price, high price, low price, closing price, and opening time of each candlestick are examples of similar values that these functions enable us to sort and group together. We can quickly evaluate whether the OHLC data is bullish by identifying the last closed bar.

It's critical to realize that we shouldn't send an infinite amount of API queries just because we can. Sending queries too frequently may result in temporary restrictions or refused responses because Binance has stringent rate limits on its API. Because of this, the timing of a request is equally as crucial as its accuracy. The most effective method is to send the API request just at the opening time of each new 30-minute candle because this Expert Advisor works with closed 30-minute candlesticks. At that point, the prior candle has entirely closed, and its information is accurate and definitive. If the request was made at any other time, it would either yield incomplete candle data or waste API calls that don't yield new information.

We minimize needless network traffic, adhere to Binance's rate restrictions, and guarantee that the data we examine consistently depicts a fully closed candlestick by coordinating our API requests with the 30-minute candle opening periods. Because the Expert Advisor only conducts its analysis when fresh information becomes available, this method also maintains the Expert Advisor's efficiency and predictability.

Example:

datetime last_bar_time = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

   datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0);

   if(current_m30_time != last_bar_time)
     {

      //SEND REQUEST

      last_bar_time =  current_m30_time;
     }
  }

Explanation:

To track the opening time of the most recent 30-minute candlestick it worked with, the Expert Advisor first declares a variable. The EA can easily identify the first valid candle and determine whether a new API call is required because this value is initialized to zero. Every time a new market tick for the chosen symbol is received, the EA's primary function is triggered. Every tick denotes a tiny change in price, and the function continuously assesses the market. The EA retrieves the opening time of the most recent 30-minute candlestick within this method. This offers a specific point of reference for the start of the current candle.

The EA then contrasts the stored last bar time with the opening time of the current candle. A new candle has begun since the last check if the two times differ. An API request to retrieve candlestick data is sent in response to this situation. The EA eliminates pointless calls and guarantees it runs effectively under API constraints by only sending requests when a new candle opens. The EA modifies the last bar time variable to reflect the current candle's opening time after sending the API request. This guarantees that the EA will only submit a fresh request when the subsequent 30-minute candle starts and that the same candle is not processed more than once. This method preserves data retrieval efficiency and accuracy.

Analogy:

Assume that you are recording everyday occurrences in a notepad. To prevent making the same entry twice a day, you only record the information once. To determine if the current day is new or the same as the one you previously documented, you also retain a separate note of the last date you recorded. Every market tick in this case is like repeatedly checking a calendar throughout the day. You would only take a new note when the date truly changed, not every time you looked. That date is represented by the opening time of the current 30-minute candle, which indicates when action is required.

Similar to sending an API request to obtain new candlestick data, you record the details when a new day starts or, for the EA, when a new candle opens. To prevent the same day from being registered again, you alter the last noted date once it has been recorded. Just as the EA restricts API queries to one per candle, this guarantees that each day is handled just once, keeping the record clean.

Example:
datetime last_bar_time = 0;
string method_bar = "GET";
string url_bar = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=30m&limit=3";
string headers_bar = "";
int time_out_bar = 5000;
char   data_bar[];
char   result_bar[];
string result_headers_bar;
string server_result;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

   datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0);

   if(current_m30_time != last_bar_time)
     {

      WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar);
      server_result = CharArrayToString(result_bar);

      Print(server_result);
      last_bar_time =  current_m30_time;
     }
  }

Output:

Explanation:

The variables required for the Binance API call are introduced in the first section. We make it very apparent that the request is meant to read data from the server rather than transmit updates or carry out modifications by setting the method to "GET." The URL variable holds the address of the Binance endpoint that provides candlestick data. While the address itself identifies the server, the route specifies the exact resource on the server, in this example the endpoint for candlestick information. The server is given additional instructions in the URL query string. For instance, the limit instructs the server how many candles to return, the interval determines the length of each candle, and the symbol parameter identifies the trading pair for which the data is needed.

Since there is no authentication or other metadata in the request, the headers are specified as empty. If no answer is received, the timeout value determines how long the program waits before canceling the request. The request data, the response headers, and the server's raw output are all captured by arrays. To facilitate analysis and parsing, the transformed server response is stored in an additional string variable. The WebRequest function is then invoked with the necessary parameters. This uses the given URL, headers, and timeout to send a GET request to the Binance server. The function delivers details about the request status in addition to saving the server's raw response in the response array. The byte data is then converted into a string for easy reading. When this string is displayed, the complete JSON response with the candlestick data is displayed.

Analogy:

Imagine requesting records from the previous three days by calling a library. The server address is represented by the library building itself, whereas historical data are handled by the particular department you request, much like the API path. You specify the city, the desired number of days, and the level of information in the records when submitting the request. These guidelines are similar to the URL's added symbol, interval, and limit parameters. After that, you send the letter and watch for a reply. When it returns, you have a tray ready to receive the response. The response is sent by the office as a series of symbols that are coded and cannot be immediately read. To comprehend the weather for the previous three days, you decode the message into a readable format and then read it.

Similarly, the EA uses the preset URL and query parameters to submit a GET request to Binance, pauses while the request is processed, receives the candlestick data as raw bytes, transforms it into a readable text format, and prints it for review. The EA can collect the most recent candle information for the selected trading pair through this approach.


Extracting OHLC and Time Information from Binance Candle Data

Here, we concentrate on extracting the relevant data from Binance's server response. The fact that JSON data formats are not platform-neutral is a crucial lesson in API integration. Because of this difference, candlestick data may be arranged differently between services. Understanding the precise format of the data being returned is the first step in handling any server response, as was covered earlier in Part 29 of this series. Each candle in Binance's candlestick data is sent as a structured JSON array with several values, including opening time, open price, high price, low price, close price, volume, and other metadata. The locations in the response that correspond to the OHLC values and the candle opening time must be clearly identified before we can make trading decisions.

Cleaning the response comes next after understanding the structure. This entails eliminating extraneous characters that are present in the JSON format but not helpful for computations, such as brackets, commas, and quote marks. Once the raw response has been cleaned, we can start combining similar data. For instance, to treat the open, high, low, and close values for a single candle as a single logical unit, they need to be grouped. We make the data much more manageable within MQL5 by arranging the extracted values into arrays. We may do comparisons, identify bullish or bearish candles, and apply logic depending on the most recent closed bar because each array can represent a certain sort of information, such as opening times or OHLC values.

Binance returns candlestick data in a JSON array format. The response is an array with several inner arrays at the top level. A single candlestick is represented by each inner array. The structure appears as follows in its simplest form:

[
[array 1],
[array 2],
[array 3]
]

Candle data are all stored in each inner array for a single candle. Because of the set arrangement of these values, every place within the inner array consistently conveys the same kind of data.

Example:

int array_count;
string candle_data[];
string first_bar_data;
string first_bar_data_array[];
string second_bar_data;
string second_bar_data_array[];
string third_bar_data;
string third_bar_data_array[];
if(current_m30_time != last_bar_time)
  {

   WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar);
   server_result = CharArrayToString(result_bar);

// Print(server_result);
   array_count = StringSplit(server_result,']', candle_data);

   first_bar_data = candle_data[0];
   StringReplace(first_bar_data,"[[","");
   StringReplace(first_bar_data,"\"","");
   StringSplit(first_bar_data,',',first_bar_data_array);

   second_bar_data = candle_data[1];
   StringReplace(second_bar_data,",[","");
   StringReplace(second_bar_data,"\"","");
   StringSplit(second_bar_data,',',second_bar_data_array);

   third_bar_data = candle_data[2];
   StringReplace(third_bar_data,",[","");
   StringReplace(third_bar_data,"\"","");
   StringSplit(third_bar_data,',',third_bar_data_array);

   last_bar_time =  current_m30_time;
  }

Explanation:

The server answer is handled using variables. The values of the first three candles are stored in different arrays, segments are counted, and candle blocks are stored in a string array. One element per candle is produced by splitting the response using the closing bracket while maintaining some JSON syntax. To process the first candle, the candle data array's first element is extracted. Opening brackets and quote marks are examples of superfluous characters that have been eliminated from this text to sanitize the data. The cleaned string is then divided into an array with each index denoting a distinct candle value by using commas. To ensure that it has the same structure as the first candle and is entirely separated, the second candle goes through the identical process: its array element is stripped of unnecessary characters and divided by commas.

The third candle proceeds in precisely the same manner. Unwanted characters are eliminated, the actual text is recovered, and the cleaned string is divided into distinct values. Each candle has its own array with ordered values that reflect the candle's data after this series.

Analogy:

Imagine the server response as a lengthy roll of paper with multiple receipts issued sequentially. One candlestick is represented by each receipt, but initially everything is adhered to the same roll, making it difficult to see or utilize. Cutting the roll into individual receipts is the first step. When the reaction is divided into several parts, this is what occurs. Even though each item now depicts a single candle, it still has unnecessary labels, borders, and symbols put on it.

The initial receipt is then stripped of any extraneous packaging, such as attractive borders or quotation marks, leaving only the actual content. After it has been cleaned, you read the receipt line by line and record every piece of information in a separate column. This clarifies and simplifies the content. For the second receipt, you next follow the identical procedure. You isolate it from the others, take off the unnecessary formatting, and arrange its contents into distinct fields. To guarantee that every receipt is processed precisely the same way, the third receipt follows the identical procedures.

What began as a lengthy, disorganized roll of paper has been transformed into multiple well-organized documents by the end of this process. Every record has distinct details that may now be compared, examined, and applied to decision-making. This is precisely how structured candle data that the Expert Advisor can dependably use is created from the raw server response. The retrieved values must then be stored in different variables. It is now simple to assign each value to a particular variable because the candlestick data has already been cleaned and organized into arrays. While the open, high, low, and close values are saved independently, the opening time can be recorded in its own variable.

Example:

long bar1_time_s;
datetime bar1_time;
long bar2_time_s;
datetime bar2_time;
long bar3_time_s;
datetime bar3_time;

double bar1_open;
double bar2_open;
double bar3_open;

double bar1_high;
double bar2_high;
double bar3_high;

double bar1_low;
double bar2_low;
double bar3_low;

double bar1_close;
double bar2_close;
double bar3_close;
if(current_m30_time != last_bar_time)
  {

   WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar);
   server_result = CharArrayToString(result_bar);

// Print(server_result);

   array_count = StringSplit(server_result,']', candle_data);

   first_bar_data = candle_data[0];
   StringReplace(first_bar_data,"[[","");
   StringReplace(first_bar_data,"\"","");
   StringSplit(first_bar_data,',',first_bar_data_array);

   second_bar_data = candle_data[1];
   StringReplace(second_bar_data,",[","");
   StringReplace(second_bar_data,"\"","");
   StringSplit(second_bar_data,',',second_bar_data_array);

   third_bar_data = candle_data[2];
   StringReplace(third_bar_data,",[","");
   StringReplace(third_bar_data,"\"","");
   StringSplit(third_bar_data,',',third_bar_data_array);

   bar1_time_s = (long)StringToInteger(first_bar_data_array[0])/1000;
   bar1_time = (datetime)bar1_time_s;
   bar2_time_s = (long)StringToInteger(second_bar_data_array[0])/1000;
   bar2_time = (datetime)bar2_time_s;
   bar3_time_s = (long)StringToInteger(third_bar_data_array[0])/1000;
   bar3_time = (datetime)bar3_time_s;

   bar1_open = StringToDouble(first_bar_data_array[1]);
   bar2_open = StringToDouble(second_bar_data_array[1]);
   bar3_open = StringToDouble(third_bar_data_array[1]);

   bar1_high = StringToDouble(first_bar_data_array[2]);
   bar2_high = StringToDouble(second_bar_data_array[2]);
   bar3_high = StringToDouble(third_bar_data_array[2]);

   bar1_low = StringToDouble(first_bar_data_array[3]);
   bar2_low = StringToDouble(second_bar_data_array[3]);
   bar3_low = StringToDouble(third_bar_data_array[3]);

   bar1_close = StringToDouble(first_bar_data_array[4]);
   bar2_close = StringToDouble(second_bar_data_array[4]);
   bar3_close = StringToDouble(third_bar_data_array[4]);

   last_bar_time =  current_m30_time;
  }

Explanation:

The retrieved candlestick data is eventually allocated to precisely specified variables that indicate time and price information in this section of the code. The opening time and OHLC values for each candle are stated in a collection of variables before any assignment. Two variables are stated for each candle in terms of time. One is a datetime variable that will store the converted time in a format that MetaTrader can comprehend, while the other is a long integer that is used to temporarily store the raw timestamp value. Separate double variables are declared for each candle's value in price data. Declaring these variables beforehand guarantees that every piece of information has a distinct and significant place in the program and clarifies the data structure.

Each candle's data is arranged into distinct arrays in the sequence specified by Binance. The first element shows the opening time of the candle at index zero. It started off as a string in milliseconds. The string is transformed into an integer and split by 1,000 to deal with it in MQL5 and convert milliseconds to seconds. This changes the timestamp to seconds and eliminates the millisecond precision. The value can be securely cast into a datetime variable after this conversion is complete. To ensure that all opening timings are accurately in line with MetaTrader's time system, this procedure is done for every candle.

The opening price of the candle is represented by the following element in the candle array, located at index position one. This value is placed in the relevant open price variable after being translated from text to a double. The candle's high price is represented by the value at index position two, which is transformed in the same manner. The low price is located in index position three, while the close price is located in index position four. To be utilized in numerical computations and comparisons, each of these values is separately transformed from a string into a double.

Every candle array follows the same indexing pattern. The opening time is constantly represented by index zero, the open price by index one, the high price by index two, the low price by index three, and the closing price by index four. The Expert Advisor can accurately read and assign each value without confusion by using this fixed structure. The raw text data obtained from the Binance API have been entirely converted into correctly typed variables by the end of this stage. Price values are now kept as numerical types that can be analyzed, while time values are stored as datetime objects that integrate easily with MetaTrader. The Expert Advisor can accurately assess candle direction, such as whether the most recent closed candle is bullish, thanks to this last modification.

Analogy:

Imagine the server answers with three envelopes, each of which has a 30-minute candle summary. Every envelope has a set design. The candle's beginning time is shown in the first line, the opening price is shown in the second, the highest price is shown in the third, the lowest price is shown in the fourth, and the closing price is shown in the fifth. Imagine putting each envelope on a shelf before you apply this knowledge. For easy access, each envelope must be emptied and its contents neatly placed on different shelves. The first line indicates the time the candle began, much like a little card inside the envelope. It does not match the clock on your shelf because it is expressed in milliseconds. Before putting it in the appropriate slot, you convert it to a format that your system can comprehend.

The remaining lines in the envelope that display the opening, high, low, and closing values are then arranged onto the appropriate shelves. You make sure everything is arranged correctly by using the same procedures for each of the three envelopes. With each shelf holding the same type of data from all three candles, the bookshelf ultimately displays a clear and uniform structure that makes analysis simple. The following stage is to gather comparable kinds of data into their own arrays after each candle's details have been carefully arranged. Put all the opening cards from the shelves in one row, then all the high cards in a different row, the low cards in a third row, and the closing cards in a fourth. Each kind of data is easy to obtain and handle because of this structure.

This makes working with the data much simpler because each array contains the same type of information for every candle. For instance, rather than going through each candle by candle, you can just refer to the related array if you want to quickly check the most recent opening price or compare highs across the last three candles. This arrangement facilitates the application of logic based on time, price, or other candle qualities and helps to speed analysis.

Example:

if(current_m30_time != last_bar_time)
  {

   WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar);
   server_result = CharArrayToString(result_bar);

// Print(server_result);

   array_count = StringSplit(server_result,']', candle_data);

   first_bar_data = candle_data[0];
   StringReplace(first_bar_data,"[[","");
   StringReplace(first_bar_data,"\"","");
   StringSplit(first_bar_data,',',first_bar_data_array);

   second_bar_data = candle_data[1];
   StringReplace(second_bar_data,",[","");
   StringReplace(second_bar_data,"\"","");
   StringSplit(second_bar_data,',',second_bar_data_array);

   third_bar_data = candle_data[2];
   StringReplace(third_bar_data,",[","");
   StringReplace(third_bar_data,"\"","");
   StringSplit(third_bar_data,',',third_bar_data_array);

   bar1_time_s = (long)StringToInteger(first_bar_data_array[0])/1000;
   bar1_time = (datetime)bar1_time_s;
   bar2_time_s = (long)StringToInteger(second_bar_data_array[0])/1000;
   bar2_time = (datetime)bar2_time_s;
   bar3_time_s = (long)StringToInteger(third_bar_data_array[0])/1000;
   bar3_time = (datetime)bar3_time_s;

   bar1_open = StringToDouble(first_bar_data_array[1]);
   bar2_open = StringToDouble(second_bar_data_array[1]);
   bar3_open = StringToDouble(third_bar_data_array[1]);

   bar1_high = StringToDouble(first_bar_data_array[2]);
   bar2_high = StringToDouble(second_bar_data_array[2]);
   bar3_high = StringToDouble(third_bar_data_array[2]);

   bar1_low = StringToDouble(first_bar_data_array[3]);
   bar2_low = StringToDouble(second_bar_data_array[3]);
   bar3_low = StringToDouble(third_bar_data_array[3]);

   bar1_close = StringToDouble(first_bar_data_array[4]);
   bar2_close = StringToDouble(second_bar_data_array[4]);
   bar3_close = StringToDouble(third_bar_data_array[4]);

   datetime OpenTime[3] = {bar1_time, bar2_time, bar3_time};
   double   OpenPrice[3] = {bar1_open, bar2_open, bar3_open};
   double   ClosePrice[3] = {bar1_close, bar2_close, bar3_close};
   double   LowPrice[3] = {bar1_low, bar2_low, bar3_low};
   double   HighPrice[3] = {bar1_high, bar2_high, bar3_high};

   last_bar_time =  current_m30_time;
  }

Explanation:

The EA groups similar types of information together to organize the data from the three candles. Without having to manage different values for each candle, it is simpler to access or refer to the opening timings of the oldest, middle, and most recent candles because they are gathered into a single group. The EA may swiftly compare each candle's initial price by using the same method for starting prices. Along with the lowest and highest prices of each candle, closing prices are also grouped together. Calculations like trend analysis, high and low comparisons, or spotting bullish and bearish patterns are made much easier by organizing these linked values.

In essence, this organization makes a structured table in which time, opening, closing, low, and high are all consistently maintained in columns, and each candle is represented as a row. This simplifies data management and lays the groundwork for automated trading logic in the EA or candlestick pattern analysis.

Analogy:

Let's say you have three boxes that correspond to the previous three 30-minute candles on Binance. The moment the candle opened, the opening price, the closing price, the lowest price, and the maximum price were the five bits of information contained in each box.

You chose to arrange everything by type rather than carrying three different boxes with all the information mixed. The three boxes' opening times are all combined, and the same is true for opening, closing, lowest, and highest pricing. You no longer need to look through each box to get the opening price of the second candle or the highest price of the three. It is quick and simple to access, compare, or analyze the data because everything is well organized.

Example:
if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0])
  {

   Print("BULLISH ENGULFING CONFIRMED");
//SEND A BUY ORDER TO BINANCE

  }
else
  {

// NO BULLSH ENGULFING
   Print("NO BULLISH ENGULFING CONFIRMED");

  }

Explanation:

Using the candlestick arrays we previously produced, the condition is intended to identify a bullish engulfing pattern. Only the final two closed candles in this arrangement are of interest to us. In particular, index 0 denotes the third candle, which is the oldest of the three; index 1 denotes the second candle, which is the middle one; and index 2 denotes the current, still-forming candle, which is not utilized in this pattern identification due to its incompleteness.

The condition's first section determines whether the preceding candle (index 0, the third candle) is bearish. This is accomplished by contrasting the candle's open and close prices. This indicates that the candle moved downward during its period if the closure is lower than the open. The second section determines if the most recent completed candle (index 1, the second candle) is bullish. This is verified when the candle's closure price exceeds its opening price, signifying an upward trend.

The last section makes sure that the preceding bearish candle is fully absorbed by the present bullish candle. This is confirmed by determining whether the bullish candle's closing is higher than the bearish candle's high. If this requirement is met, a bullish engulfing pattern is formed as the body of the most recent candle entirely encompasses the body of the preceding one. The code indicates that the pattern has been found by printing "BULLISH ENGULFING CONFIRMED" if all of these requirements are met. In a real trading scenario, a buy order could be issued to Binance at this point.

The otherwise block runs if any of the requirements are not satisfied, printing "NO BULLISH ENGULFING CONFIRMED" to show that there isn't a legitimate bullish engulfing pattern. While omitting the still-forming current candle (index 2), which cannot be used for pattern confirmation, this reasoning offers a straightforward, programmed method of identifying a bullish engulfing formation using the sorted candlestick data.


Automating Orders through the Binance API

This chapter's next step is to automatically execute trades now that we know how to spot a bullish engulfing pattern. Here, we'll concentrate on using the WebRequest function in MQL5 with the API to send orders to Binance. Only the timestamp and the secret key were used to generate the signature in the previous article. This was adequate as only the timestamp and the generated signature were included in the query string used to obtain the account balance. But this time, sending an order calls for more details than just the timestamp.

The trading symbol, order side, order type, amount, and timestamp are just a few of the elements that are included in the query string when placing orders via the Binance API. It is crucial to realize that each parameter used in the query string needs to be used when creating the signature. Binance will deny the request if any parameters are absent during the signature generation procedure.

Example:

string url_t = "https://api.binance.com/api/v3/time";
string headers_t = "";
char result_t[];
string response_headers_t;
char data_t[];

string time;
string pattern = "{\"serverTime\":";
int pattern_lenght;
int end;
string server_time = "";
string time_stamp = "";

string symbol;
string sQty;
string side;
string params;
string message;
string secrete_key;
if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0])
  {

// Print("BULLISH ENGULFING CONFIRMED");
//SEND A BUY ORDER TO BINANCE

   WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   time = CharArrayToString(result_t);

   pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   end = StringFind(time,"}",pattern_lenght + 1);

   server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);
   time_stamp = "timestamp=" + server_time;

   symbol    = "DOGEUSDT";
   sQty = DoubleToString(6, 8);
   StringReplace(sQty, ",", ".");
   side      = "BUY";

   params = "symbol=" + symbol +
            "&side=" + side +
            "&type=MARKET" +
            "&quoteOrderQty=" + sQty +
            "&" + time_stamp;

   message = params;
   secrete_key = "AbcdefGhmb91TXxKK0Ct51sjh9312345eIdLkRpYThugopIMe4EZVjaOCYabcdef";

Explanation:

A request to Binance's server time endpoint is prepared using the first set of variables. The Binance API path that yields the current server time is indicated by the URL. Because Binance expects timestamps to originate from its own server rather than your local system time, this endpoint is crucial. Since authentication is not needed for this request, the headers variable is left empty. The raw server response and the raw request data are stored in character arrays, and any headers that Binance returns are stored in the response headers variable.

The request is sent to Binance when the WebRequest function is called using the GET method and the server time URL. The current server time in milliseconds is included in the JSON message that Binance replies with. The result array contains the response as raw bytes. A character array to string conversion function is then used to transform this raw byte data into a readable string. The complete JSON response from Binance, including the server timestamp, is now contained in this string. For authorized queries, this timestamp is now the first necessary parameter. 

The timestamp is then extracted from the server response using a number of string variables. The JSON key that Binance uses for server time is matched by a predetermined pattern string. To monitor the beginning and end of the timestamp within the response message, more variables are declared. The extracted server time and the final formatted timestamp parameter are both stored in empty strings.

The program looks for the pattern that indicates the start of the server time field in the response string. After the pattern is located, its length is added so that the extraction begins precisely after the label rather than from the label itself. The program then looks for the closing bracket, which indicates that the server time value has ended. Only the numeric timestamp value is contained in the substring that is extracted using these two places. As provided by Binance, this figure is still expressed in milliseconds. After that, the retrieved value is formatted into a valid query string parameter by preceding it with the word "timestamp=." This generates the timestamp parameter that the API request will use.

The remaining parameters needed to place an order are defined by the following set of variables. The trading symbol is ready to be held in a string. For the quantity, another string is ready. One is used to combine all arguments into a single query string, while another is used for the order side. The final message that will be signed and the secret key that will be used to create the signature are stored in two additional variables. The trading pair to which the order will be delivered is specified by the symbol variable.

To conform to Binance's anticipated numeric format, the quantity variable is transformed into a string with appropriate decimal formatting, and any comma formatting is substituted with a dot. The action type is specified by the side variable. Next, all necessary fields are concatenated into a single query string to create the parameters string. This comprises the quantity, order type, side, symbol, and timestamp. As mandated by Binance's API definition, each argument is connected using the "&" sign.

After that, the message variable is assigned the entire question string. The whole set of parameters that will be submitted to Binance is represented by this message. The private API key used to create the signature is stored in the secret key variable. Everything needed to generate signatures is currently available. All query parameters are included in the message, and cryptographic signature is possible with the secret key. This is crucial because Binance mandates that the entire query string, not just the timestamp, be used to generate the signature. The request will fail authentication if the signature input contains any missing parameters.

Example:
uchar uMsg[], uSKey[];
int blockSize = 64;
uchar ipad[], opad[];
uchar innerData[], innerHash[];
uchar outerData[], finalHash[];

string API_KEY = "abcdefkuoeX7gUvCb8nX0Ros4Jsabcdef3kA5nw8e12345udVZwWYHgOjyabcdef";
string headers = "X-MBX-APIKEY: " + API_KEY + "\r\n";

char   result[];
string response_headers;
char   datas[];

string url;
int status;
string server_response;
if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0])
  {

// Print("BULLISH ENGULFING CONFIRMED");
//SEND A BUY ORDER TO BINANCE

   WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   time = CharArrayToString(result_t);

   pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   end = StringFind(time,"}",pattern_lenght + 1);

   server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);
   time_stamp = "timestamp=" + server_time;

   symbol    = "DOGEUSDT";
   sQty = DoubleToString(6, 8);
   StringReplace(sQty, ",", ".");
   side      = "BUY";

   params = "symbol=" + symbol +
            "&side=" + side +
            "&type=MARKET" +
            "&quoteOrderQty=" + sQty +
            "&" + time_stamp;

   message = params;
   secrete_key = "AbcdefGhmb91TXxKK0Ct51sjh9312345eIdLkRpYThugopIMe4EZVjaOCYabcdef";

   StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8);
   StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8);

   ArrayResize(ipad, blockSize);
   ArrayResize(opad, blockSize);

   for(int i = 0; i < blockSize; i++)
     {
      ipad[i] = uSKey [i] ^ 0x36;
      opad[i] = uSKey [i] ^ 0x5C;
     }

   ArrayCopy(innerData, ipad);
   ArrayCopy(innerData, uMsg, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash);

   ArrayCopy(outerData, opad);
   ArrayCopy(outerData, innerHash, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, outerData, uSKey, finalHash);

   string signature = "";
   for(int i = 0; i < ArraySize(finalHash); i++)
      signature += StringFormat("%02x", finalHash[i]);

   Print(signature);

   string query_string = "";
   query_string += "&signature=" + signature;

   url = "https://api.binance.com/api/v3/order?" + params + "&signature=" + signature;
   status = WebRequest("POST", url, headers, 5000, datas, result, response_headers);
   server_response = CharArrayToString(result);

   Print(server_response);

  }

Explanation:

The initial set of variables is used to get everything ready for the final API request and authentication. The message and the secret key are stored in two byte arrays in a raw byte format. This is necessary as cryptographic hashing uses bytes rather than strings. The SHA256 algorithm's standard block size for HMAC operations is set to 64 bytes. One padding array is stated for the outer padding and another for the interior padding. The HMAC procedure consists of several essential elements. To store hash results and intermediate data throughout the signing process, more arrays are made. It is simpler to easily follow the HMAC steps thanks to this division.

The secret key and the API key string are stated independently. Your account is solely identified by the API key in the request headers. It is not included in the signature. The necessary Binance header format is used to generate the headers string, which is then followed by a line break. The order request will be submitted with this header. The server answer is then handled by declaring more variables. These include variables that hold the final server response text, arrays for raw response data, and strings for response headers. Additionally prepared for later use are the URL, status code, and final response string.

After that, byte arrays are created from the message and secret key strings. Because the signature technique works at the byte level, this step is essential. To make sure the byte representation matches what Binance anticipates, UTF-8 encoding is utilized. Both the secret key and the query string arguments are currently in the form of raw byte data. The SHA256 block size is matched by resizing the inner and outer padding arrays. These arrays are subsequently filled by a loop that combines fixed hexadecimal values with the secret key bytes. One value is used for the interior padding and another for the outer padding. This transformation guarantees that the secret key is safely incorporated into the hashing process and is a component of the HMAC standard.

The inner data is constructed by copying the inner padding and then attaching the message bytes after the padding has been prepared. The initial step of the HMAC computation is represented by this combined data. The inner hash is created by creating a SHA256 hash from this inner data. The secret key influence is already securely included in this inner hash. The outer padding is then copied, and the inner hash result is added to create the outer data. The HMAC process uses this second combination as its final input. The final hash is created by applying another SHA256 hash to this data. The cryptographic signature that verifies the request was created with the appropriate secret key is this final hash.

The final hash is transformed into a readable hexadecimal string because it is still in raw byte form. Each byte is inserted into a string after being prepared as a two-character hexadecimal value. The final string has the precise signature format that Binance demands. Debugging or verification may benefit from printing it. When the signature is prepared, it is appended as an extra parameter to the query string.

The signature must be transmitted as part of the request URL, not in the headers, according to Binance. The Binance order endpoint, the previously generated parameters, and the generated signature are then combined to create the final request URL. A POST request with the complete signed URL and the authentication header is sent to Binance using the WebRequest method. After processing the request, Binance stores the response as raw bytes.


Conclusion

This article covered the full process of working with the Binance API in MQL5, from retrieving and organizing candlestick data to identifying a bullish engulfing pattern and sending authenticated trade orders. We explored how to build signed requests, handle parameters correctly, and execute real trades using the WebRequest function. With this final step of placing orders through the Binance API, this article brings the API series to a close and completes the journey from market data analysis to live trade execution in MetaTrader 5.

Attached files |
Price Action Analysis Toolkit Development (Part 58): Range Contraction Analysis and Maturity Classification Module Price Action Analysis Toolkit Development (Part 58): Range Contraction Analysis and Maturity Classification Module
Building on the previous article that introduced the market state classification module, this installment focuses on implementing the core logic for identifying and evaluating compression zones. It presents a range contraction detection and maturity grading system in MQL5 that analyzes market congestion using price action alone.
Swap Arbitrage in Forex: Building a Synthetic Portfolio and Generating a Consistent Swap Flow Swap Arbitrage in Forex: Building a Synthetic Portfolio and Generating a Consistent Swap Flow
Do you want to know how to benefit from the difference in interest rates? This article considers how to use swap arbitrage in Forex to earn stable profit every night, creating a portfolio that is resistant to market fluctuations.
From Novice to Expert: Developing a Liquidity Strategy From Novice to Expert: Developing a Liquidity Strategy
Liquidity zones are commonly traded by waiting for the price to return and retest the zone of interest, often through the placement of pending orders within these areas. In this article, we leverage MQL5 to bring this concept to life, demonstrating how such zones can be identified programmatically and how risk management can be systematically applied. Join the discussion as we explore both the logic behind liquidity-based trading and its practical implementation.
From Novice to Expert: Creating a Liquidity Zone Indicator From Novice to Expert: Creating a Liquidity Zone Indicator
The extent of liquidity zones and the magnitude of the breakout range are key variables that substantially affect the probability of a retest occurring. In this discussion, we outline the complete process for developing an indicator that incorporates these ratios.