preview
Building A Candlestick Trend Constraint Model (Part 5): Notification System (Part III)

Building A Candlestick Trend Constraint Model (Part 5): Notification System (Part III)

MetaTrader 5Trading systems | 4 July 2024, 15:15
1 819 0
Clemence Benjamin
Clemence Benjamin

Contents:


Introduction

We have now expanded signal accessibility on our model, making it beneficial for all users. Additionally, we have motivated numerous upcoming developers on how to seamlessly integrate social networks for signals on our well-known MetaTrader 5 trading platform. Let's conclude by delving into WhatsApp integration details. Our goal is to automatically send signals generated by our custom MetaTrader 5 indicators to a WhatsApp number or group. Meta has introduced a new channel feature on WhatsApp, which can help signals reach a wider audience. As stated by Porter Gale in her book, " Your Network Is Your Net Worth," it is crucial to be part of a community to thrive. Through advancements like these, we can impact a large community, especially on popular platforms like Telegram and WhatsApp.

I have decided to include a flow chart outlining the process of integrating any social media platform with MetaTrader 5. This is a summarized step-by-step development based on my research. The critical stages are clearly outlined in the flow chart. Let me briefly explain some of the stages involved. The flow chart narrates the experiences encountered during development. It is important to be prepared to redo certain processes as errors may occur. In this article, I will only focus on what I have verified to be effective, although a considerable amount of debugging was done during the writing process. These are the stages we went through in (Part II) while integrating Telegram with the Trend Constraint Indicator for notifications.

Integration Of Social Platforms With Meta Trader 5 Flow Chart


Integration Of Social Platforms With Meta Trader 5 Flow Chart

The flowchart simplifies how we understand the entire process of integrating the final stage, the decision stage, which has two possible outcomes: Yes or No.

Is the Testing  Successful?

If Yes:
  • Deploy Integration
  • Monitor and Maintain( Everything is functioning well and we can start using our new system as per original goal)
  • End( The end of a successful integration)
If No:
  • Debug and Fix Issues (loop back to Set Up Development Environment, the idea is mainly go back through all the steps to check for errors, correct them and retest again)
  • Explore Other Options (loop back to try other Social Platforms thus the earlier stages if there is no API available)

Configuring a Messaging API

Based on the flow chart provided, we have decided to advance into integration WhatsApp, like we did for Telegram in the previous article. Both platforms offer APIs that align with our objectives. The decision to opt for WhatsApp is primarily due to its extensive user base. Personally, I use WhatsApp more frequently than any other social platform, and receiving signals through this platform will help me effectively monitor my MetaTrader5 business.

According to, whatsthebigdata, as of July 2023, WhatsApp had around 2.78 billion unique active users worldwide. This popular messaging app reached the 2 billion mark back in 2020 and is expected to reach 3.14 billion by 2025.

Statistical values should be taken into consideration when selecting platforms to help increase our net worth via these networks. Numerous messaging API providers can be found online, with varying levels of ease of use and user-friendliness. Many offer trial usage but require subscriptions to access advanced features. Based on my research, some options available on the internet include:

I have used Twilio for this project because it offers API access in a more user-friendly manner and is suitable for WhatsApp integration. The options provided are effective, albeit the configuration process can be complex, and subscriptions are required to access other key features. Nevertheless, they are worth exploring to broaden one's understanding.

I created my Twilio account and got everything set up. The key elements required from the site are account_sid, auth_token, the Whatsapp number assigned in the Twilio sandbox, and your own receiving number to initiate conversations. All the necessary credentials for the script can be found on your Twilio account page. 

Twilio API

By navigating to the console section of your Twilio account, you can locate where it sends WhatsApp messages. There, you can see your sandbox number, which will save on the API side to push the notifications incoming from MetaTrader 5 to your joined WhatsApp numbers. It is unfortunate that it only sends to the joined participants. See screenshot below showing a join message you need to send to the sandbox number to join. Start by saving the sandbox number to your contact list, then locate it and send "join so-cave." Automatically, once that is done, you can run integration script with test messages in command prompt and see it working.

Join Twilio sandbox

Here is a screenshot of me joining:

Joining whatsapp number for twilio

At this point, we have obtained the API credentials required for the integration process as outlined in the flowchart. Let's now move on to the next stage, where we will delve into the application of these credentials in detail.

The (send_whatsapp_message.py) script

We can utilize IDLE for scripting. It comes pre-installed with the Python interpreter. Alternatively, I lean towards open-source choices such as Notepad++ for scripting. Notepad++ supports various languages with proper formatting. Begin by creating a new file, name it send_whatsapp_message.py. It is advisable to save your script file in the scripts folder along with the Python interpreter. I encountered no issues when testing the program from that directory. 

We will start by importing module in our script:

import sys
import requests

  • import sys: This module provides access to some variables used or maintained by the Python interpreter. It allows you to interact with the interpreter.
  • import requests: This module is used to send HTTP requests. It simplifies the process of making API requests.

The next stage define the functions:

def send_whatsapp_message(account_sid, auth_token, from_whatsapp_number, to_whatsapp_number, message):
    url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Messages.json"
    
    data = {
        "From": f"whatsapp:{from_whatsapp_number}",
        "To": f"whatsapp:{to_whatsapp_number}",
        "Body": message,
    }
    
    response = requests.post(url, data=data, auth=(account_sid, auth_token))
    
    if response.status_code == 201:
        print("Message sent successfully")
    else:
        print(f"Failed to send message: {response.status_code} - {response.text}")

send_whatsapp_message: This function sends a WhatsApp message using the Twilio API. 

In the function definition, we have several parameters required for the script to execute. Many of these were acquired while setting up the Twilio account. 

Parameters:

  • account_sid: The Account SID from your Twilio account.
  • auth_token: The Auth Token from your Twilio account.
  • from_whatsapp_number: The WhatsApp-enabled Twilio number, formatted with "whatsapp:" prefix.
  • to_whatsapp_number: The recipient's WhatsApp number, formatted with "whatsapp:" prefix.
  • message: The message text to be sent.

url: Constructs the URL for the Twilio API endpoint. data: A dictionary containing the message data to be sent in the POST request.

requests.post: Sends a POST request to the Twilio API with the message data and authentication credentials.
response: Captures the response from the Twilio API.
status_code: Checks the status code of the response to determine if the message was sent successfully (201 indicates success).

The final snippet of the script forms the main code and it is where the execution will take place. When this script is executed, it will:

  1. Read the message text from the command line arguments.
  2. Call the send_whatsapp_message function with the provided credentials, phone numbers, and message.
  3. Print a success or failure message based on the response from the Twilio API
# Replace with your actual Twilio credentials and phone numbers
account_sid = "**********************" # Your SID
auth_token = "***************************" # Your Auth Token
from_whatsapp_number = "+14**********" # Your WhatsApp-enabled Twilio number
to_whatsapp_number = "+************" # Recipient's WhatsApp number
message = sys.argv[1]

send_whatsapp_message(account_sid, auth_token, from_whatsapp_number, to_whatsapp_number, message)

Putting it together our final working send_whatsapp_message.py file will be like this:

import sys
import requests

def send_whatsapp_message(account_sid, auth_token, from_whatsapp_number, to_whatsapp_number, message):
    url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Messages.json"
    
    data = {
        "From": f"whatsapp:{from_whatsapp_number}",
        "To": f"whatsapp:{to_whatsapp_number}",
        "Body": message,
    }
    
    response = requests.post(url, data=data, auth=(account_sid, auth_token))
    
    if response.status_code == 201:
        print("Message sent successfully")
    else:
        print(f"Failed to send message: {response.status_code} - {response.text}")

# Replace with your actual Twilio credentials and phone numbers
account_sid = "****************"
auth_token = "**********************"
from_whatsapp_number = "+14***********" 
to_whatsapp_number = "+***************" 
message = sys.argv[1]

send_whatsapp_message(account_sid, auth_token, from_whatsapp_number, to_whatsapp_number, message)
 

 We can go on to testing in command prompt to see if the script  is working. Open the path to where the send_whatapp_message.py file is stored and type in the follow command:

python send_whatsapp_message.py "Test Message"

If everything is okay, the message will be sent to your WhatsApp number and a "Message sent successfully" will be displayed on the command prompt window. If you encounter errors, you need to recheck your code and credentials, especially because the provided code has been debugged and corrected to work fine. I will share a screenshot of my working integration in a few sections below.


Modifying Trend Constraint Indicator for Whatsapp Notifications

To complete the integration without significantly altering the indicator code, I decided to concentrate on adjusting myAlert block to support Whatsapp messages. It is crucial to pay close attention to accurately inputting the path to your python interpreter and the script. These are key components of the integration when transitioning from this code snippet. I kept most of the code from our previous Telegram integration unchanged. Additionally, I included a testing initialization feature that allows a welcome message to be sent upon startup to verify the system's functionality before receiving real signals.

int OnInit()
{
// Send test message on launch
   myAlert("info", "Thank you for subscribing. You shall be receiving Trend Constraint signal alerts via Whatsapp.");
  
return(INIT_SUCCEEDED);
}

Included in the OnInit() will ensure that message will be sent to your numbers  when you add the indicator to any chat.

Below is the most crucial part of our integration logic for sending messages to WhatsApp. I have highlighted the sections of the code that I modified in comparison to the code used for Telegram integration.

//--- ShellExecuteW declaration ----------------------------------------------
#import "shell32.dll"
int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
#import

//--- global variables ------------------------------------------------------
datetime last_alert_time;
input int alert_cooldown_seconds = 60; // Cooldown period in seconds


//--- myAlert function ------------------------------------------------------
void myAlert(string type, string message) {
    datetime current_time = TimeCurrent();
    if (current_time - last_alert_time < alert_cooldown_seconds) {
        // Skip alert if within cooldown period
        return;
    }

    last_alert_time = current_time;
    string full_message = type + " | Trend Constraint V1.06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message;
    if (type == "print") {
        Print(message);
    } else if (type == "error") {
        Print(type + " | Trend Constraint V1.06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message);
    } else if (type == "order") {
        // Add order alert handling if needed
    } else if (type == "modify") {
        // Add modify alert handling if needed
    } else if (type == "indicator" || type == "info") {
        if (Audible_Alerts) {
            Alert(full_message);
        }
        if (Push_Notifications) {
            SendNotification(full_message);
        }

        // Send to WhatsApp //Edit to match your actual path, these I gave as an example for my computer
        string python_path = "C:\\Users\\******\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
        string script_path = "C:\\Users\\******\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py";
        string command = python_path + " \"" + script_path + "\" \"" + full_message + "\"";
        
        // Debugging: Print the command being executed
        Print("Executing command to send WhatsApp message: ", command);

        // Use cmd.exe to execute the command and then wait for 5 seconds
        string final_command = "/c " + command + " && timeout 5";
        int result = ShellExecuteW(0, "open", "cmd.exe", final_command, NULL, 1);
        if (result <= 32) {
            int error_code = GetLastError();
            Print("Failed to execute Python script. Error code: ", error_code);
        } else {
            Print("Successfully executed Python script. Result code: ", result);
        }
    }
}

Here is the main program After adjustments to give us Trend Constraint V1.06:

//+------------------------------------------------------------------+
//|                                       Trend Constraint V1.06.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com"
#property version   "1.06"
#property description "A model that seeks to produce sell signals when D1 candle is Bearish only and buy signals when it is Bullish"


//--- indicator settings
#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots 6

#property indicator_type1 DRAW_ARROW
#property indicator_width1 5
#property indicator_color1 0xFF3C00
#property indicator_label1 "Buy"

#property indicator_type2 DRAW_ARROW
#property indicator_width2 5
#property indicator_color2 0x0000FF
#property indicator_label2 "Sell"

#property indicator_type3 DRAW_ARROW
#property indicator_width3 2
#property indicator_color3 0xE8351A
#property indicator_label3 "Buy Reversal"

#property indicator_type4 DRAW_ARROW
#property indicator_width4 2
#property indicator_color4 0x1A1AE8
#property indicator_label4 "Sell Reversal"

#property indicator_type5 DRAW_LINE
#property indicator_style5 STYLE_SOLID
#property indicator_width5 2
#property indicator_color5 0xFFAA00
#property indicator_label5 "Buy"

#property indicator_type6 DRAW_LINE
#property indicator_style6 STYLE_SOLID
#property indicator_width6 2
#property indicator_color6 0x0000FF
#property indicator_label6 "Sell"

#define PLOT_MAXIMUM_BARS_BACK 5000
#define OMIT_OLDEST_BARS 50

//--- indicator buffers
double Buffer1[];
double Buffer2[];
double Buffer3[];
double Buffer4[];
double Buffer5[];
double Buffer6[];

input double Oversold = 30;
input double Overbought = 70;
input int Slow_MA_period = 200;
input int Fast_MA_period = 100;
datetime time_alert; //used when sending alert
input bool Audible_Alerts = true;
input bool Push_Notifications = true;
double myPoint; //initialized in OnInit
int RSI_handle;
double RSI[];
double Open[];
double Close[];
int MA_handle;
double MA[];
int MA_handle2;
double MA2[];
int MA_handle3;
double MA3[];
int MA_handle4;
double MA4[];
double Low[];
double High[];
int MA_handle5;
double MA5[];
int MA_handle6;
double MA6[];
int MA_handle7;
double MA7[];

//--- ShellExecuteW declaration ----------------------------------------------
#import "shell32.dll"
int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
#import

//--- global variables ------------------------------------------------------
datetime last_alert_time;
input int alert_cooldown_seconds = 60; // Cooldown period in seconds


//--- myAlert function ------------------------------------------------------
void myAlert(string type, string message) {
    datetime current_time = TimeCurrent();
    if (current_time - last_alert_time < alert_cooldown_seconds) {
        // Skip alert if within cooldown period
        return;
    }

    last_alert_time = current_time;
    string full_message = type + " | Trend Constraint V1.06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message;
    if (type == "print") {
        Print(message);
    } else if (type == "error") {
        Print(type + " | Trend Constraint V1.06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message);
    } else if (type == "order") {
        // Add order alert handling if needed
    } else if (type == "modify") {
        // Add modify alert handling if needed
    } else if (type == "indicator" || type == "info") {
        if (Audible_Alerts) {
            Alert(full_message);
        }
        if (Push_Notifications) {
            SendNotification(full_message);
        }

        // Send to WhatsApp
        string python_path = "C:\\Users\\****\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
        string script_path = "C:\\Users\\****\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py";
        string command = python_path + " \"" + script_path + "\" \"" + full_message + "\"";
        
        // Debugging: Print the command being executed
        Print("Executing command to send WhatsApp message: ", command);

        // Use cmd.exe to execute the command and then wait for 5 seconds
        string final_command = "/c " + command + " && timeout 5";
        int result = ShellExecuteW(0, "open", "cmd.exe", final_command, NULL, 1);
        if (result <= 32) {
            int error_code = GetLastError();
            Print("Failed to execute Python script. Error code: ", error_code);
        } else {
            Print("Successfully executed Python script. Result code: ", result);
        }
    }
}


//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {   
   SetIndexBuffer(0, Buffer1);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   PlotIndexSetInteger(0, PLOT_ARROW, 241);
   SetIndexBuffer(1, Buffer2);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   PlotIndexSetInteger(1, PLOT_ARROW, 242);
   SetIndexBuffer(2, Buffer3);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   PlotIndexSetInteger(2, PLOT_ARROW, 236);
   SetIndexBuffer(3, Buffer4);
   PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   PlotIndexSetInteger(3, PLOT_ARROW, 238);
   SetIndexBuffer(4, Buffer5);
   PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(4, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   SetIndexBuffer(5, Buffer6);
   PlotIndexSetDouble(5, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(5, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
   // Send test message on launch
   myAlert("info", "Thank you for subscribing. You shall be receiving Trend Constraint signal alerts via Whatsapp.");
   //initialize myPoint
   myPoint = Point();
   if(Digits() == 5 || Digits() == 3)
     {
      myPoint *= 10;
     }
   RSI_handle = iRSI(NULL, PERIOD_CURRENT, 14, PRICE_CLOSE);
   if(RSI_handle < 0)
     {
      Print("The creation of iRSI has failed: RSI_handle=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle = iMA(NULL, PERIOD_CURRENT, 7, 0, MODE_SMMA, PRICE_CLOSE);
   if(MA_handle < 0)
     {
      Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle2 = iMA(NULL, PERIOD_CURRENT, 400, 0, MODE_SMA, PRICE_CLOSE);
   if(MA_handle2 < 0)
     {
      Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle3 = iMA(NULL, PERIOD_CURRENT, 100, 0, MODE_EMA, PRICE_CLOSE);
   if(MA_handle3 < 0)
     {
      Print("The creation of iMA has failed: MA_handle3=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle4 = iMA(NULL, PERIOD_CURRENT, 200, 0, MODE_SMA, PRICE_CLOSE);
   if(MA_handle4 < 0)
     {
      Print("The creation of iMA has failed: MA_handle4=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle5 = iMA(NULL, PERIOD_CURRENT, Fast_MA_period, 0, MODE_SMA, PRICE_CLOSE);
   if(MA_handle5 < 0)
     {
      Print("The creation of iMA has failed: MA_handle5=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle6 = iMA(NULL, PERIOD_CURRENT, Slow_MA_period, 0, MODE_SMA, PRICE_CLOSE);
   if(MA_handle6 < 0)
     {
      Print("The creation of iMA has failed: MA_handle6=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   MA_handle7 = iMA(NULL, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE);
   if(MA_handle7 < 0)
     {
      Print("The creation of iMA has failed: MA_handle7=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      return(INIT_FAILED);
     }
   
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
   int limit = rates_total - prev_calculated;
   //--- counting from 0 to rates_total
   ArraySetAsSeries(Buffer1, true);
   ArraySetAsSeries(Buffer2, true);
   ArraySetAsSeries(Buffer3, true);
   ArraySetAsSeries(Buffer4, true);
   ArraySetAsSeries(Buffer5, true);
   ArraySetAsSeries(Buffer6, true);
   //--- initial zero
   if(prev_calculated < 1)
     {
      ArrayInitialize(Buffer1, EMPTY_VALUE);
      ArrayInitialize(Buffer2, EMPTY_VALUE);
      ArrayInitialize(Buffer3, EMPTY_VALUE);
      ArrayInitialize(Buffer4, EMPTY_VALUE);
      ArrayInitialize(Buffer5, EMPTY_VALUE);
      ArrayInitialize(Buffer6, EMPTY_VALUE);
     }
   else
      limit++;
   datetime Time[];
   
   datetime TimeShift[];
   if(CopyTime(Symbol(), PERIOD_CURRENT, 0, rates_total, TimeShift) <= 0) return(rates_total);
   ArraySetAsSeries(TimeShift, true);
   int barshift_M1[];
   ArrayResize(barshift_M1, rates_total);
   int barshift_D1[];
   ArrayResize(barshift_D1, rates_total);
   for(int i = 0; i < rates_total; i++)
     {
      barshift_M1[i] = iBarShift(Symbol(), PERIOD_M1, TimeShift[i]);
      barshift_D1[i] = iBarShift(Symbol(), PERIOD_D1, TimeShift[i]);
   }
   if(BarsCalculated(RSI_handle) <= 0) 
      return(0);
   if(CopyBuffer(RSI_handle, 0, 0, rates_total, RSI) <= 0) return(rates_total);
   ArraySetAsSeries(RSI, true);
   if(CopyOpen(Symbol(), PERIOD_M1, 0, rates_total, Open) <= 0) return(rates_total);
   ArraySetAsSeries(Open, true);
   if(CopyClose(Symbol(), PERIOD_D1, 0, rates_total, Close) <= 0) return(rates_total);
   ArraySetAsSeries(Close, true);
   if(BarsCalculated(MA_handle) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle, 0, 0, rates_total, MA) <= 0) return(rates_total);
   ArraySetAsSeries(MA, true);
   if(BarsCalculated(MA_handle2) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle2, 0, 0, rates_total, MA2) <= 0) return(rates_total);
   ArraySetAsSeries(MA2, true);
   if(BarsCalculated(MA_handle3) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle3, 0, 0, rates_total, MA3) <= 0) return(rates_total);
   ArraySetAsSeries(MA3, true);
   if(BarsCalculated(MA_handle4) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle4, 0, 0, rates_total, MA4) <= 0) return(rates_total);
   ArraySetAsSeries(MA4, true);
   if(CopyLow(Symbol(), PERIOD_CURRENT, 0, rates_total, Low) <= 0) return(rates_total);
   ArraySetAsSeries(Low, true);
   if(CopyHigh(Symbol(), PERIOD_CURRENT, 0, rates_total, High) <= 0) return(rates_total);
   ArraySetAsSeries(High, true);
   if(BarsCalculated(MA_handle5) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle5, 0, 0, rates_total, MA5) <= 0) return(rates_total);
   ArraySetAsSeries(MA5, true);
   if(BarsCalculated(MA_handle6) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle6, 0, 0, rates_total, MA6) <= 0) return(rates_total);
   ArraySetAsSeries(MA6, true);
   if(BarsCalculated(MA_handle7) <= 0) 
      return(0);
   if(CopyBuffer(MA_handle7, 0, 0, rates_total, MA7) <= 0) return(rates_total);
   ArraySetAsSeries(MA7, true);
   if(CopyTime(Symbol(), Period(), 0, rates_total, Time) <= 0) return(rates_total);
   ArraySetAsSeries(Time, true);
   //--- main loop
   for(int i = limit-1; i >= 0; i--)
     {
      if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS)) continue; //omit some old rates to prevent "Array out of range" or slow calculation   
      
      if(barshift_M1[i] < 0 || barshift_M1[i] >= rates_total) continue;
      if(barshift_D1[i] < 0 || barshift_D1[i] >= rates_total) continue;
      
      //Indicator Buffer 1
      if(RSI[i] < Oversold
      && RSI[i+1] > Oversold //Relative Strength Index crosses below fixed value
      && Open[barshift_M1[i]] >= Close[1+barshift_D1[i]] //Candlestick Open >= Candlestick Close
      && MA[i] > MA2[i] //Moving Average > Moving Average
      && MA3[i] > MA4[i] //Moving Average > Moving Average
      )
        {
         Buffer1[i] = Low[1+i]; //Set indicator value at Candlestick Low
         if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Buy"); //Alert on next bar open
         time_alert = Time[1];
        }
      else
        {
         Buffer1[i] = EMPTY_VALUE;
        }
      //Indicator Buffer 2
      if(RSI[i] > Overbought
      && RSI[i+1] < Overbought //Relative Strength Index crosses above fixed value
      && Open[barshift_M1[i]] <= Close[1+barshift_D1[i]] //Candlestick Open <= Candlestick Close
      && MA[i] < MA2[i] //Moving Average < Moving Average
      && MA3[i] < MA4[i] //Moving Average < Moving Average
      )
        {
         Buffer2[i] = High[1+i]; //Set indicator value at Candlestick High
         if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Sell"); //Alert on next bar open
         time_alert = Time[1];
        }
      else
        {
         Buffer2[i] = EMPTY_VALUE;
        }
      //Indicator Buffer 3
      if(MA5[i] > MA6[i]
      && MA5[i+1] < MA6[i+1] //Moving Average crosses above Moving Average
      )
        {
         Buffer3[i] = Low[i]; //Set indicator value at Candlestick Low
         if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Buy Reversal"); //Alert on next bar open
         time_alert = Time[1];
        }
      else
        {
         Buffer3[i] = EMPTY_VALUE;
        }
      //Indicator Buffer 4
      if(MA5[i] < MA6[i]
      && MA5[i+1] > MA6[i+1] //Moving Average crosses below Moving Average
      )
        {
         Buffer4[i] = High[i]; //Set indicator value at Candlestick High
         if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Sell Reversal"); //Alert on next bar open
         time_alert = Time[1];
        }
      else
        {
         Buffer4[i] = EMPTY_VALUE;
        }
      //Indicator Buffer 5, Alert muted by turning it into a comment
      if(MA3[i] > MA7[i] //Moving Average > Moving Average
      )
        {
         Buffer5[i] = MA3[i]; //Set indicator value at Moving Average
         //if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Buy"); //Alert on next bar open
         //time_alert = Time[1];
        }
      else
        {
         Buffer5[i] = EMPTY_VALUE;
        }
      //Indicator Buffer 6, Alert muted by turning it into a comment
      if(MA3[i] < MA7[i] //Moving Average < Moving Average
      )
        {
         Buffer6[i] = MA3[i]; //Set indicator value at Moving Average
         //if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Sell"); //Alert on next bar open
         //time_alert = Time[1];
        }
      else
        {
         Buffer6[i] = EMPTY_VALUE;
        }
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+


Testing and Results

We have conducted testing in two of the above subsections. Our project is functioning properly on my end. I have included some screenshots of the signals received below.

A working whatsapp integration

The indicator is functioning well, drawing according to the goal and sending signals to my social channels as designed. Attached is an image of the Boom 500 Index chart below:

Boom 500 index

Our program opens a command prompt window upon launch to display the ongoing processes and sends an opening note. It follows the same procedure when transmitting signals over time. However, the popping window can be disruptive when working on tasks, so the program can be coded to function without displaying the Command Prompt window. In this project, we chose not to hide the window for the observation of the ongoing process. To prevent the Command Prompt window from appearing, you can include the following line of code..

// execute the command without showing the window
string final_command = "/c " + command + " && timeout 5";
int result = ShellExecuteW(0, "open", "cmd.exe", final_command, NULL, 0); // Change the last parameter to 0 (SW_HIDE)
if (result <= 32) {
    int error_code = GetLastError();
    Print("Failed to execute Python script. Error code: ", error_code);
} else {
    Print("Successfully executed Python script. Result code: ", result);
}

 The last parameter is called the nShowCmd, it controls how the application window is displayed. By changing the parameters  to 0 (SW_HIDE) instead of 1 (SW_SHOWNORMAL).


Security

The technique used involves DLL files that are considered a potential threat. Through my research, I have identified the associated risks for awareness purposes below. Be sure to safeguard your credentials for APIs, as I have made an effort to conceal mine at some point in this article. Only disclose them to trusted entities.

Some dangers associated with DLL files:

  • Malware: Loading a malicious DLL can execute harmful code on your system. This is a significant risk if the DLL is sourced from an untrusted or unknown location.
  • DLL Hijacking: If an attacker can place a malicious DLL in a location that the application loads from, they can execute arbitrary code. This is particularly dangerous if the application runs with elevated privileges.
  • Crashes and Instability: Incorrect usage of DLL functions or calling functions with invalid parameters can cause application crashes or system instability.
  • Resource Leaks: Improper handling of resources (e.g., memory, file handles) within DLL functions can lead to resource leaks, affecting system performance over time.
  • Version Conflicts: Different versions of a DLL might have different function signatures or behaviors, leading to compatibility issues.
  • Dependency Hell: Managing dependencies on multiple DLLs and their versions can be complex and error-prone, leading to runtime errors.

To combat such threats, it is crucial to incorporate security features into our program. The following code line can address security concerns if implemented in our model:

// You can secure the program by adding SecureExecuteCommand(command); below these lines.
string python_path = "C:\\Users\\*****\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
string script_path = "C:\\Users\\*****\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py"; //***** is your computer user name, the rest of the path txt is correct based on the python version being used.
string message = "Hello, this is a test message";
string command = python_path + " \"" + script_path + "\" \"" + message + "\"";

SecureExecuteCommand(command);

The SecureExecuteCommand() function checks for invalid characters in the command string, which can help prevent command injection attacks.

Some best practices to combat the risks are:

  • Use Trusted Sources: Only use DLLs from trusted and verified sources.
  • Code Signing: Ensure DLLs are signed by a trusted authority, which helps verify their integrity and origin.
  • Least Privilege: Run applications with the least privilege necessary to reduce the impact of any potential exploitation.
  • Validate Inputs: Always validate and sanitize input parameters, especially when constructing command strings.
  • Error Handling: Implement robust error handling to manage and log errors gracefully.
  • Regular Updates: Keep your software and libraries up to date with security patches and updates.
  • Isolation: Use sandboxing or other isolation techniques to run potentially risky operations in a constrained environment.


Conclusion

WhatsApp integration has been successfully completed. Our model can now transmit signals from MetaTrader 5 to Telegram and WhatsApp. It is feasible to consolidate the integration into a single program and broadcast the signals on both platforms concurrently. I have observed slight discrepancies in signal delivery, with push notifications arriving slightly earlier than those on social platforms, albeit with a very small time difference, perhaps around 7 seconds. Telegram signals outpaced WhatsApp signals by a few seconds. Nevertheless, our primary objective was to ensure the arrival of our signals, and this objective has been achieved.

I hope this article has enriched you with something valuable. As developers, we should not give up but keep the flowchart operational until we achieve our goals. We must leverage the power of our community to enhance our skills in algorithmic trading programming. Discussions are always open to everyone so that ideas can be openly shared. In our next project, we will build upon our existing foundation to explore further. Find the attached files and their description below.

Attachment Description
send_whatsapp_message.py Advocates communication MetaTrader 5 to Twilio and then to WhatsApp.
Trend Constraint V1.06.mq5 WhatsApp integrated but needs you to update credentials and path directories to work on your computer.
Secure Command.mq5  Modified to hide Command Prompt Window and secure the program from external attacks.


Neural networks made easy (Part 77): Cross-Covariance Transformer (XCiT) Neural networks made easy (Part 77): Cross-Covariance Transformer (XCiT)
In our models, we often use various attention algorithms. And, probably, most often we use Transformers. Their main disadvantage is the resource requirement. In this article, we will consider a new algorithm that can help reduce computing costs without losing quality.
Developing a Replay System (Part 41): Starting the second phase (II) Developing a Replay System (Part 41): Starting the second phase (II)
If everything seemed right to you up to this point, it means you're not really thinking about the long term, when you start developing applications. Over time you will no longer need to program new applications, you will just have to make them work together. So let's see how to finish assembling the mouse indicator.
Reimagining Classic Strategies in Python: MA Crossovers Reimagining Classic Strategies in Python: MA Crossovers
In this article, we revisit the classic moving average crossover strategy to assess its current effectiveness. Given the amount of time time since its inception, we explore the potential enhancements that AI can bring to this traditional trading strategy. By incorporating AI techniques, we aim to leverage advanced predictive capabilities to potentially optimize trade entry and exit points, adapt to varying market conditions, and enhance overall performance compared to conventional approaches.
MQL5 Wizard Techniques you should know (Part 25): Multi-Timeframe Testing and Trading MQL5 Wizard Techniques you should know (Part 25): Multi-Timeframe Testing and Trading
Strategies that are based on multiple time frames cannot be tested in wizard assembled Expert Advisors by default because of the MQL5 code architecture used in the assembly classes. We explore a possible work around this limitation for strategies that look to use multiple time frames in a case study with the quadratic moving average.