Русский Español Português
preview
Neural Networks in Practice: Activation Functions

Neural Networks in Practice: Activation Functions

MetaTrader 5 — Machine learning |
60 0
Daniel Jose
Daniel Jose

Introduction

In the previous article, we developed an application that allowed us to visualize an activation function graph along with its derivative. However, what we saw there is just the foundation for something a little more complex, and that is precisely what will be the main topic of this article.

I know that many people may think it’s unnecessary to understand the activation function graph and its derivative, and that we could dive right into the topic, leaving such questions aside. However, it's much easier to understand why the sigmoid function can't be used in very deep networks if you understand its graph.

So here we'll focus specifically on that. We'll select a few activation functions and see how we can modify the code from the previous article to graph both the activation function and its derivative.


New Application

Everything stated in the previous article still holds true. In addition to what we saw there, we'll take a look at some new code here. We'll also briefly explain each of the selected functions. Before I begin, I'd like to remind you of something, dear reader. There are a vast number of activation functions. Some of them are more popular than others. Nevertheless, each of them has its own purpose and specific area of application, helping to optimize training speed in one way or another. Next, we'll look at how some of these functions are used in practice, so you can see just how much even a simple change in the choice of function can affect the results we obtain during network training. In any case, for now we'll focus only on the truly interesting part of the system, where we'll use the functions in complete isolation. That will make it easier to understand which option might be best in different situations. In any case, don't worry about it right now. Although the mathematical part is quite complex and confusing, we'll try to simplify it considerably by the end. I want you to really understand how a neural network works. The point isn't to tell you, "Do this. Don't do that." Instructions like these don't add anything to your knowledge. This merely creates an illusion that you understand something you actually don't.

So, before we go over each of the selected functions, let's take a quick look at the code. A few minor changes were made to it, and, in my opinion, some of them deserve an explanation. I'll show it in its entirety below. That will make it much easier to explain the new details that have been added. So don't be put off at first glance. This code is much simpler than it seems.
001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property indicator_chart_window
004. #property indicator_plots 0
005. //+------------------------------------------------------------------+
006. #include <Canvas\Canvas.mqh>
007. //+------------------------------------------------------------------+
008. enum eFnActivate {
009.                     Sigmoid, 
010.                     Tangh, 
011.                     ReLU,
012.                     eLU, 
013.                     SoftSign, 
014.                     SoftPlus, 
015.                 };
016. //+------------------------------------------------------------------+
017. input eFnActivate   user_00 = Sigmoid;   //Activation Function
018. input double        user_01 = 10.0;      //Limits
019. input double        user_02 = 0.1;       //Level of Detail
020. input double        user_03 = 9.0;       //Y-axis scale
021. //+------------------------------------------------------------------+
022. CCanvas *canvas;
023. //+------------------------------------------------------------------+
024. struct st_00
025. {
026.     int     x,
027.             y,
028.             maxX,
029.             maxY;
030.     double  Position,
031.             dx, fx;
032. }global;
033. //+------------------------------------------------------------------+
034. void PlotTextPosition(void)
035. {
036.     uint w, h;
037.     string sz0;
038. 
039.     sz0 = StringFormat("%s Position: [%.1f] <<  fx: %.8f    dx: %.8f  >>", EnumToString(user_00), global.Position, global.fx, global.dx);
040.     TextGetSize(sz0, w, h);
041.     (*canvas).TextOut(global.x - (w / 2), global.maxY + 5, sz0, ColorToARGB(clrBlack)); 
042. }
043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+
112. void UpdateGraphics(int direct)
113. {
114.     double value = global.Position + user_02 * direct;
115. 
116.     global.Position = MathAbs(value) <= user_01 ? value : global.Position;
117. 
118.     (*canvas).Erase(ColorToARGB(clrWhite, 255));
119. 
120.     (*canvas).LineVertical(global.x, 0, global.maxY, ColorToARGB(clrRoyalBlue, 255));
121.     (*canvas).LineHorizontal(0, global.maxX, global.y, ColorToARGB(clrRoyalBlue, 255));
122. 
123.     Function_Curve();
124.     PlotTextPosition();
125. 
126.     (*canvas).Update(true);
127. }
128. //+------------------------------------------------------------------+
129. void Resize(void)
130. {
131.     uint w, h;
132.    
133.     global.maxX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
134.     global.maxY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
135. 
136.     if (canvas != NULL)
137.     {
138.         (*canvas).Destroy();
139.         delete canvas;
140.     }
141.     canvas = new CCanvas;
142.     (*canvas).CreateBitmapLabel("BL", 0, 0, global.maxX, global.maxY, COLOR_FORMAT_ARGB_NORMALIZE);
143.     global.x = global.maxX / 2;
144.     global.y = global.maxY / 2;
145.     TextGetSize("M", w, h);
146.     global.maxY -= (int)(h * 2);
147. }
148. //+------------------------------------------------------------------+
149. int OnInit()
150. {        
151.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
152.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
153.     ZeroMemory(global);
154.     canvas = NULL;
155. 
156.     return INIT_SUCCEEDED;
157. }
158. //+------------------------------------------------------------------+
159. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
160. {
161.     return rates_total;
162. }
163. //+------------------------------------------------------------------+
164. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
165. {
166.     int direct = 0;
167. 
168.     switch (id)
169.     {
170.         case CHARTEVENT_CHART_CHANGE:
171.             Resize();
172.             break;
173.         case CHARTEVENT_KEYDOWN:
174.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT)) direct = -1;
175.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT)) direct = 1;
176.             break;
177.     }
178.     UpdateGraphics(direct);
179. }
180. //+------------------------------------------------------------------+
181. void OnDeinit(const int reason)
182. {
183.     (*canvas).Destroy();
184.     delete canvas;
185.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, true);
186.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, true);
187. }
188. //+------------------------------------------------------------------+

Yes, this code does look significantly more complex. But don't worry. I'll explain each part so you can understand it. It's actually simpler than the previous code. First, an enumeration was added on line 8. Its purpose is to allow us to select the appropriate equation. Each of the names included in this enumeration actually corresponds to the name of an equation.

These six equations are used in activation functions. As I mentioned earlier, there are others as well. Many of them are slight variations of the ones listed here. If you'd like, you can add them without any problem. You only need to take one precaution: put the mathematical expression in the right place and in the correct form. If you do this, you'll be able to plot a graph for any mathematical function you want. And it's all quite simple and straightforward.

So, now that we have names for the equations, the next change concerns how the user selects an equation to plot. In the past, we had to compile the code every time we made changes—whether to the equation or to the parameters used. However, as I mentioned at the beginning, the goal here is to provide the user with a more interactive experience. This way, you won't have to constantly compile the code. Four input parameters were provided for this purpose. Previously, we had two: the range of values and the plotting step. Now two more have been added: equation selection and the Y-axis scale factor. This is because, in some cases, the values on the Y-axis are very close to 1.0. In that case, it would be a good idea to let the user change the scale of the Y-axis. This will change the scale of the graph, making it easier to analyze the plotted curve.

Okay, by default, the graph will be plotted using the equation of the sigmoid function, with values ranging from minus ten to ten and a plotting step of one-tenth. This results in a fairly smooth curve. In addition, a scale factor of nine will be used for the Y-axis. With these settings, you'll be able to view the following graph in MetaTrader 5.

Please note that some changes have been made to the display. In fact, the change affected only the part related to the displayed text. Previously, we only displayed the position along the X-axis. Now, in addition to this value, we also display the name of the equation, its value on the Y-axis as a function of the value on the X-axis, and the value of the derivative at that same point on the X-axis. And all of this was achieved by changing just a few lines of code. Essentially, line 39 of the code displays the fx and dx values you see in the image. Please note that in this line, we access the enumeration to retrieve the function name. We also access the values specified in line 31. These two variables in line 31 did not exist before.

So, since most of the code remained unchanged, the only changes were the addition of lines 151, 152, 185, and 186, which are intended to make the chart window as clean as possible. Since this is easy to understand, we can focus on what's important: the procedure on line 44.

This procedure has undergone some minor changes in its structure. All of these are designed to make the code much easier to write and modify—even for those who are just starting out in programming—should changes be needed in the future. This new code was designed so that you can easily add any equation to it. To see this more clearly, let's highlight the code and take a closer look at it, focusing only on the snippet shown below.

043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+

If you compare this code to what we looked at in the previous article, you'll immediately notice that two new variables have been added here. They are on line 48. Even the variables fx and dx now serve a different purpose. Now both variables will be used to temporarily store the calculated values.

This may seem complicated, but if you've never programmed before, pay close attention—because you're about to learn how to do it in just a few steps.

Let's say you want to add a new activation function to this application so that you can graph both the values calculated by its equation and its derivative. What should you do? So, to start, go to line 8, where the function names are listed, and add the name of your new function. Remember that you CANNOT use spaces in the name. If you need to separate words, use the underscore ( _ ).

After that, you will be able to select this equation when using the compiled application in MetaTrader 5. However, you won't see any graph just yet. To plot the function, simply go to the procedure shown in the previous code snippet and do the following: locate the switch statement on line 60. It will determine which code to execute based on the value corresponding to the equation selected by the user.

So, on line 61, after the opening curly brace, press ENTER, type "case" followed by a space, and then type the name of your equation followed by a colon ( : ). Press ENTER again and type "break" followed by a semicolon ( ; ). This will create a structure very similar to the one shown in the code. And now for the most interesting part. Between the colon ( : ) and the word “break,” you need to write something very similar to what is shown in each of the structures between lines 62 and 86. Note that fx is assigned one value and dx another. So what does that mean? For fx, you need to specify the expression for the function, and for dx, the expression for its derivative. And that's all you need to do. The rest is handled by the code itself or during its use in MetaTrader 5. If you're not sure how to use the switch statement, check out another article I wrote. In that article, I explain in detail how to use the switch statement.

Now that we've covered the simplest part, we can move on to the next topics, where we'll examine each of the equations that will be implemented by default.


Sigmoid activation function

This function has already been discussed in another article, where I showed that an activation function is needed to overcome stagnation. Check out the previous articles to gain a better understanding of this topic. So, when we add the indicator to the chart and select the sigmoid function, we'll be able to interact with the chart as shown in the following animation.

Here, we follow the same rules that were described in the previous article. So, if you look at the code on line 63, you'll see which equation is used there. The same equation is shown below in mathematical notation.

Its derivative, defined on line 64, is given by the equation shown below.

However, if you simply look at these mathematical expressions, you might not immediately notice just how much the derivative of the sigmoid function reduces the values. To some extent, this is rather inconvenient, as it has significant implications when we want to use this function in a deeper network. We need to be careful when deciding at which point in the network we will use the sigmoid function. If you use it indiscriminately, at some point you'll prevent the network from continuing to propagate data. This will become clearer later. Anyone with even a basic understanding of math and derivatives is probably already thinking about the next topic: THE CHAIN RULE. That's what this problem is called. However, this actually affects us only when using derivatives and only during backpropagation. As I mentioned earlier, we'll go over all of this in more detail later.

Even with this issue in mind, the sigmoid function is extremely useful because it normalizes the perceptron's output values, keeping them within the range of zero to one. In addition, it is often used when we need a model that can predict the probability of something based on a neural network's output value. Since the output value always falls within the range from zero to one, it is ideal for converting data into percentages. The sigmoid function has another major advantage, provided it is used with caution: it helps avoid abrupt changes during gradient descent. This is because this function is differentiable, which means we can determine the slope of the curve using any two points.

However, although I've mentioned all of this, for now you only need to observe and understand what happens to each value substituted into the equation and what result you get when you evaluate the derivative at that same value. All right, now we have some food for thought about the sigmoid function. We can move on to the next function in the list.


"Hyperbolic tangent" activation function

This activation function attempts to address one of the problems with the sigmoid function: its output cannot be negative. Furthermore, in some cases, the fact that the sigmoid function is restricted to values between zero and one creates an additional complication. This is noticeable when its values approach zero. In many cases, this effect can be devastating for a perceptron network, as it essentially “kills off” some of the weights being used, causing them to be completely ignored, even though they could actually help classify new data entering the network.

Although many perceptron networks are designed solely to learn specific patterns, some of them continue to receive new data over time. When this happens, the sigmoid function may ultimately have a negative impact on a specific network weight. And if the weight in question is important for the incoming data, part of the training process may become noticeably more difficult.

So, when you select the “hyperbolic tangent” function in the app, you’ll see the following animation.

As for the equation, there are slight variations in its notation. One of these appears on line 67 of the code and is shown below.

There is another way to write this same equation. You can see it below.

In the previous equation, you can clearly see that, contrary to what many people think, trigonometric expressions can be used in perceptron networks. Sometimes using trigonometric equations offers far more advantages than you might imagine, since they can be derived using well-known trigonometric identities. However, the previous example was just an interesting aside. Below is another way to write the same expression.

Despite these differences in how the equation is written, the part relating to its derivative does not change significantly. Nevertheless, there are several ways to express it. In the code, you can see the derivative on line 68, where the equation shown below is used.

We can also use an equation similar to the one shown below, which also allows us to find the derivative.

And, of course, there is also the trigonometric form shown below.

Of course, these expressions represent only a small selection of the many different ways to express the same thing. In other words, you shouldn't blindly follow formulas or expressions you find somewhere just because someone said they're the best option. As I mentioned earlier, humanity does not yet know exactly how to build a universal perceptron network. The only thing we've managed to figure out is how to create a network that adapts more or less adequately to a specific type of task.

All right, let's continue, because there's still more to show in this article.


ReLU activation function

We already discussed this function in the previous article. So we'll quickly go over it here. When you select it, the following graph will appear, and we'll be able to interact with it.

The equation from line 72 is shown in the expression below.

The derivative from line 73 is shown in the expression below.

Although this function was already discussed in a previous article, it appears here again for a very simple reason: it is, without a doubt, the activation function with the greatest number of variations. Personally, I think it's probably everyone's favorite, even ahead of the sigmoid function. There aren't that many variations on the original expression for the sigmoid function. ReLU, on the other hand, has given rise to many other functions that, in one way or another, incorporate some element of ReLU into their formulation. Among all the variants you can find, there is one that is also used in engineering calculations. That's why I chose it, and that's exactly what we'll look at in the next section.


eLU activation function

This is one of the many existing variations of the ReLU function. It was mentioned in the previous section specifically so that we could show its mathematical expression here. If you have never encountered variations of mathematical expressions, this will let you compare one such variation with the original expression, so to speak.

When you select eLU, you'll see something similar to the following animation.

Notice that its graph differs from that of the original ReLU function. This is due to a slight difference in how the values are calculated. Below is the expression used in line 76 of the code.

"Wow, what a cumbersome expression. Are you sure that's how it's written?" Yes, dear reader. This is the mathematical notation for the expression implemented in line 76. I know this looks daunting and intimidating. However, this is due to the very way certain concepts are represented in mathematical notation. Before we continue, let's take a look at the expression for the derivative. It is located on line 77, and its mathematical notation is shown below.

"Oh my God, we're definitely going to have to call an exorcist for these two expressions. How terrifying." Well, regardless of what you're thinking right now and how much these expressions might scare you, there's something they both have in common: the Greek letter ALPHA. It is located on line 75 of the code. The purpose of the alpha parameter is to adjust the curvature of the graph. Depending on the value you set, you can get a steeper or a gentler curve. In other words, the radius of the arc you see on the graph may be slightly smaller or larger. This results in a slight difference in the perceptron's output values. However, if we look at both expressions, we can see that, unlike the original ReLU function, the eLU function does not have any issues with zero, since the equation itself can handle a zero value in the derivative.

Great, we're almost done. There are two more equations that were chosen for inclusion in this article and the demonstration code. So, let's move on to the penultimate equation, which we'll examine in the next section.


SoftSign activation function

This is a very interesting function that is widely used in certain types of perceptron networks. It is mainly used in speech recognition and handwriting recognition networks, as well as in other applications where it is important that gradient descent does not lose its effectiveness too quickly. Below, you can see an animation of this function.

The code on line 80 implements exactly what is shown in the following expression.

The derivative on line 81 is defined by the expression given below.

If you look closely, you'll notice that SoftSign lies somewhere between the sigmoid function and the hyperbolic tangent. However, in certain situations, it may prove to be much more suitable than either of them. Depending on the problem you want to solve, it can help the gradient find the minimum somewhat more efficiently than the sigmoid function or the hyperbolic tangent. In addition, because of how it is calculated, this function can be faster to compute in deeper networks. In some cases, this makes a noticeable difference during training. Models that use TensorFlow often favor this function over the sigmoid function and the hyperbolic tangent precisely because of this factor during model training.

To wrap things up, let's move on to the last selected function. We'll discuss it in the next section.


SoftPlus Activation Function

This function is based on ReLU. This is also a variation on how ReLU is calculated and used. When you select it in the demo app, you'll see the animation shown below.

You can clearly see that it smooths out the transition between negative and positive values quite significantly, following the same principle as the original ReLU. However, unlike the original ReLU and eLU, which we just discussed, SoftPlus behaves much more gently and smoothly. In other words, starting at a certain value, the transition near zero becomes much smoother. This differs from ReLU, which simply switches abruptly to zero or one depending on whether the values are negative or positive.

The code for this function can be found on line eighty-four; it is the expression shown below.

The derivative, in turn, is found on line eighty-five, and its expression is shown below.

Since SoftPlus serves essentially the same purposes as ReLU, in some cases you might try using it in a deep neural network. This is because gradient descent will be much smoother with SoftPlus than with ReLU in the same situation. However, as we already know, there is no rule that dictates the use of any one specific function. It all depends on the specific situation.


Concluding Thoughts

I have to admit, the final result in this article surprised me, as it turned out to be much more interactive. You don't need to make major changes to the code to add new functions for exploring possible mathematical models. All you need to do is add the name of the equation, followed by its mathematical expression.

Thus, in this article, you have not only seen how different activation functions affect the perceptron's output in different ways, but you have also learned that even small changes in the way calculations are performed can significantly influence the choice of activation function. Keep in mind that not all perceptron networks use functions like the ones shown here. However, we'll talk about that another time.

I hope this article has sparked your curiosity and interest in the mathematics underlying perceptron networks. Although this may seem very complicated, a perceptron network—like any other neural network—is nothing more than a set of mathematical expressions that programmers and developers use in quite creative ways. This is by no means some kind of magical app capable of understanding something that no human has yet been able to understand or even imagine. So take your time to review what we've covered here, and see you in the next article.









Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/13909

Attached files |
Anexo.mq5 (6.26 KB)
Model Drift Watchdog: PSI-Based Auto-Retrain Alerts via a Lightweight Webhook Model Drift Watchdog: PSI-Based Auto-Retrain Alerts via a Lightweight Webhook
This article implements a PSI-based drift watchdog for MQL5 EAs. It compares a feature and the model's predicted probability against a quantile-based reference using a rolling window at a configurable cadence, applies hysteresis to avoid flapping, and raises a webhook on state transitions. You get tested math, an audit CSV and a plotting indicator, plus tuning guidance for thresholds, window size, and cooldown.
From Basic to Intermediate: Subwindows (IV) From Basic to Intermediate: Subwindows (IV)
In this article, we'll see that things aren't quite what many people initially think they are. One of the most interesting aspects of programming is that we can guarantee that everything will always turn out exactly as we planned. So please read this article carefully to understand some of the most confusing concepts related to the use of subwindows. If you understand what is explained here, you'll be able to understand much of what we'll be doing next.
Forecasting Realized Volatility with Option-Implied Variance for Multiple Instruments in MQL5 Forecasting Realized Volatility with Option-Implied Variance for Multiple Instruments in MQL5
A published daily history of option-implied variance is combined with a broker symbol's daily bars to fit a HAR model of realized volatility in MQL5; the implied term enters the regression as a predictor, not as a ready-made forecast. Out-of-sample tests on four funds paired with their Cboe indices, ten US equities, and two broker symbols show where it improves the forecast. A background service refits on the broker's bars and draws a five-session price range on the chart, and a self-test matches independent C++ and Python implementations.
Neural Networks in Trading: A Spiking Architecture for Spatio-Temporal Market Analysis (Encoder) Neural Networks in Trading: A Spiking Architecture for Spatio-Temporal Market Analysis (Encoder)
The article presents an adaptation of the SDformerFlow framework that achieves high adaptability by integrating spiking attention with multi-window convolution and weighted summation of query elements. The architecture allows each attention head to learn its own parameters, which improves the model's accuracy and sensitivity to the structure of the data being analyzed.