
Trading with the MQL5 Economic Calendar (Part 4): Implementing Real-Time News Updates in the Dashboard
Introduction
In this article, we advance our work on the MetaQuotes Language 5 (MQL5) Economic Calendar dashboard by adding functionality for live updates, allowing us to maintain a continuously refreshed display of critical economic news events. In the previous part, we designed and implemented a dashboard panel to filter news based on currency, importance, and time, giving us a tailored view of relevant events. Now, we take it further by enabling real-time updates, ensuring that our calendar displays the latest data for timely decision-making. The topics we will cover include:
This enhancement will transform our dashboard into a dynamic, real-time tool continuously updated with the latest economic events. By implementing live refresh functionality, we will ensure that our calendar remains accurate and relevant, supporting timely trading decisions in MetaTrader 5 or the trading terminal.
Implementing Real-Time News Updates in MQL5
To implement live updates for our dashboard, we need to ensure that news events are stored and compared periodically to detect any changes. This requires maintaining arrays to hold current and previous event data, enabling us to identify updates and reflect them accurately on the dashboard. By doing so, we can ensure that the dashboard dynamically adjusts to display the most recent economic events in real-time. Below, we define the arrays that we will use for this purpose:
string current_eventNames_data[]; string previous_eventNames_data[];
Here, we define two string arrays, "current_eventNames_data" and "previous_eventNames_data", which we will use to manage and compare economic event data for live updates to the dashboard. The array "current_eventNames_data" will store the latest events retrieved from the economic calendar, while "previous_eventNames_data" will hold the data from the last update cycle. By comparing these two arrays, we can identify any changes or new additions to the events, allowing us to update the dashboard dynamically.
Using these arrays, we will need to get the current values on every event selected in the initialization section and store them in the current data holder array, and then later on copy them into the previous holder, which we will use to make the comparison on the next price tick.
ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1); current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name;
Here, we dynamically expand the "current_eventNames_data" array and add a new event name to it. We use the function ArrayResize to increase the size of the array by one, making space for a new entry. After resizing, we assign the event's name to the last index of the array using the expression "current_eventNames_data[ArraySize(current_eventNames_data)-1]". This process ensures that each new event name retrieved from the economic calendar is stored in the array, enabling us to maintain an up-to-date list of events for further processing and comparison.
However, before adding the events to the array, we need to ensure that we start fresh, meaning that we need an empty array.
ArrayFree(current_eventNames_data);
Here, we use the ArrayFree function to clear all elements from the "current_eventNames_data" array, effectively resetting it to an empty state. This is essential for ensuring that the array does not retain outdated data from previous iterations, thus preparing it to store a fresh set of event names during the processing cycle. After filling the array, we then need to copy it into the previous holder and use it later to make the comparison.
Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data)); ArrayPrint(current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (Before) = ",ArraySize(previous_eventNames_data)); ArrayCopy(previous_eventNames_data,current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (After) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data);
Here, we log and manage the transition of data between the "current_eventNames_data" and "previous_eventNames_data" arrays. First, we use the Print function to display the size of the "current_eventNames_data" array, providing visibility into the number of event names stored at that moment. We then call the ArrayPrint function to output the array's contents for further verification. Next, we log the size of the "previous_eventNames_data" array before copying, giving us a baseline for comparison.
Using the ArrayCopy function, we copy the contents of "current_eventNames_data" into "previous_eventNames_data", effectively transferring the latest event names for future comparisons. We then print the size of the "previous_eventNames_data" array after the copy operation to confirm the successful update. Finally, we call the ArrayPrint function to output the updated contents of "previous_eventNames_data", ensuring that the data transfer is accurate and complete. Those are the changes that we need on the OnInit event handler to store the initial events. Let us highlight them for clarity.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- ArrayFree(current_eventNames_data); //--- Loop through each calendar value up to the maximum defined total for (int i = 0; i < valuesTotal; i++){ //--- //--- Loop through calendar data columns for (int k=0; k<ArraySize(array_calendar); k++){ //--- //--- Prepare news data array with time, country, and other event details string news_data[ArraySize(array_calendar)]; news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time news_data[2] = country.currency; //--- Event country currency //--- Determine importance color based on event impact color importance_color = clrBlack; if (event.importance == CALENDAR_IMPORTANCE_LOW){importance_color=clrYellow;} else if (event.importance == CALENDAR_IMPORTANCE_MODERATE){importance_color=clrOrange;} else if (event.importance == CALENDAR_IMPORTANCE_HIGH){importance_color=clrRed;} //--- Set importance symbol for the event news_data[3] = ShortToString(0x25CF); //--- Set event name in the data array news_data[4] = event.name; //--- Populate actual, forecast, and previous values in the news data array news_data[5] = DoubleToString(value.GetActualValue(),3); news_data[6] = DoubleToString(value.GetForecastValue(),3); news_data[7] = DoubleToString(value.GetPreviousValue(),3); //--- } ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1); current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name; } Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data)); ArrayPrint(current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (Before) = ",ArraySize(previous_eventNames_data)); ArrayCopy(previous_eventNames_data,current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (After) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data); //Print("Final News = ",news_filter_count); updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)+" ||| Total News: "+ IntegerToString(news_filter_count)+"/"+IntegerToString(allValues)); //--- return(INIT_SUCCEEDED); }
Here, we just have made the changes that will help us get the initial event data. We have highlighted them in yellow color for clarity. Next, we just need to update the dashboard values when there are changes detected during comparison. For this, we will create a custom function that will contain all the logic for the event updates and dashboard recalculation, respectively.
//+------------------------------------------------------------------+ //| Function to update dashboard values | //+------------------------------------------------------------------+ void update_dashboard_values(){ //--- }
Here, we define the "update_dashboard_values" function, which we will use to handle the dynamic updating of the economic calendar dashboard. This function will contain the core logic for comparing stored news data, identifying any changes, and applying the necessary updates to the dashboard interface. By organizing this functionality into this dedicated function, we will ensure a clean and modular code structure, making future modifications or enhancements easier to manage. Next, we will call the function on the OnTick event handler as follows.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- update_dashboard_values(); }
Here, we just call the custom function on every tick to do the updates. Upon compilation and running of the program, we have the following results:
From the image, we can see that we gather all the news events into the "current" array, which we then copy into the fresh "previous" array for storage, which perfectly aligns with what we want. We can now proceed to use these copied data for further analysis. In the function, we just get the current events as follows:
//--- Declare variables for tracking news events and status int totalNews = 0; bool isNews = false; MqlCalendarValue values[]; //--- Array to store calendar values //--- Define start and end time for calendar event retrieval datetime startTime = TimeTradeServer() - PeriodSeconds(PERIOD_H12); datetime endTime = TimeTradeServer() + PeriodSeconds(PERIOD_H12); //--- Set a specific country code filter (e.g., "US" for USD) string country_code = "US"; string currency_base = SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE); //--- Retrieve historical calendar values within the specified time range int allValues = CalendarValueHistory(values,startTime,endTime,NULL,NULL); //--- Print the total number of values retrieved and the array size //Print("TOTAL VALUES = ",allValues," || Array size = ",ArraySize(values)); //--- Define time range for filtering news events based on daily period datetime timeRange = PeriodSeconds(PERIOD_D1); datetime timeBefore = TimeTradeServer() - timeRange; datetime timeAfter = TimeTradeServer() + timeRange; //--- Print the furthest time look-back and current server time //Print("FURTHEST TIME LOOK BACK = ",timeBefore," >>> CURRENT = ",TimeTradeServer()); //--- Limit the total number of values to display int valuesTotal = (allValues <= 11) ? allValues : 11; string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"}; int news_filter_count = 0; ArrayFree(current_eventNames_data); // Define the levels of importance to filter (low, moderate, high) ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH}; //--- Loop through each calendar value up to the maximum defined total for (int i = 0; i < valuesTotal; i++){ MqlCalendarEvent event; //--- Declare event structure CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID MqlCalendarCountry country; //--- Declare country structure CalendarCountryById(event.country_id,country); //--- Retrieve country details by event's country ID MqlCalendarValue value; //--- Declare calendar value structure CalendarValueById(values[i].id,value); //--- Retrieve actual, forecast, and previous values //--- Check if the event’s currency matches any in the filter array (if the filter is enabled) bool currencyMatch = false; if (enableCurrencyFilter) { for (int j = 0; j < ArraySize(curr_filter); j++) { if (country.currency == curr_filter[j]) { currencyMatch = true; break; } } //--- If no match found, skip to the next event if (!currencyMatch) { continue; } } //--- Check importance level if importance filter is enabled bool importanceMatch = false; if (enableImportanceFilter) { for (int k = 0; k < ArraySize(allowed_importance_levels); k++) { if (event.importance == allowed_importance_levels[k]) { importanceMatch = true; break; } } //--- If importance does not match the filter criteria, skip the event if (!importanceMatch) { continue; } } //--- Apply time filter and set timeMatch flag (if the filter is enabled) bool timeMatch = false; if (enableTimeFilter) { datetime eventTime = values[i].time; if (eventTime <= TimeTradeServer() && eventTime >= timeBefore) { timeMatch = true; //--- Event is already released } else if (eventTime >= TimeTradeServer() && eventTime <= timeAfter) { timeMatch = true; //--- Event is yet to be released } //--- Skip if the event doesn't match the time filter if (!timeMatch) { continue; } } //--- If we reach here, the currency matches the filter news_filter_count++; //--- Increment the count of filtered events //--- Set alternating colors for each data row holder color holder_color = (news_filter_count % 2 == 0) ? C'213,227,207' : clrWhite; //--- Loop through calendar data columns for (int k=0; k<ArraySize(array_calendar); k++){ //--- Print event details for debugging //Print("Name = ",event.name,", IMP = ",EnumToString(event.importance),", COUNTRY = ",country.name,", TIME = ",values[i].time); //--- Skip event if currency does not match the selected country code // if (StringFind(_Symbol,country.currency) < 0) continue; //--- Prepare news data array with time, country, and other event details string news_data[ArraySize(array_calendar)]; news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time news_data[2] = country.currency; //--- Event country currency //--- Determine importance color based on event impact color importance_color = clrBlack; if (event.importance == CALENDAR_IMPORTANCE_LOW){importance_color=clrYellow;} else if (event.importance == CALENDAR_IMPORTANCE_MODERATE){importance_color=clrOrange;} else if (event.importance == CALENDAR_IMPORTANCE_HIGH){importance_color=clrRed;} //--- Set importance symbol for the event news_data[3] = ShortToString(0x25CF); //--- Set event name in the data array news_data[4] = event.name; //--- Populate actual, forecast, and previous values in the news data array news_data[5] = DoubleToString(value.GetActualValue(),3); news_data[6] = DoubleToString(value.GetForecastValue(),3); news_data[7] = DoubleToString(value.GetPreviousValue(),3); } ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1); current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name; } //Print("Final News = ",news_filter_count); updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)+" ||| Total News: "+ IntegerToString(news_filter_count)+"/"+IntegerToString(allValues));
This is just the prior code snippet code that we used during the initialization section, but we need it on every tick to get the updates. We will however go via it briefly. We focus on dynamically updating the dashboard with the latest economic news events on every tick, and to achieve this, we streamline the logic by removing the creation of graphical objects and instead concentrate on managing event data efficiently. We begin by defining variables to track the total news count and filter criteria such as currency, importance, and time ranges. These filters ensure that only relevant events are considered for further processing.
We loop through the retrieved calendar values, applying the filters to identify events that match the specified conditions. For each matching event, we extract key details like the event name, time, currency, and importance level. These details are stored in the "current_eventNames_data" array, which is resized dynamically to accommodate new entries. This array is crucial for tracking the events and allows us to identify changes between ticks by comparing them with previous data. Finally, we update the dashboard label to reflect the total filtered events and current server time, ensuring the dashboard always reflects the latest event data without creating unnecessary objects. This approach efficiently captures and updates the economic news information in real-time.
Next, we need to track if there are changes in the storage arrays using the newly acquired data. To do this, we use a custom function.
//+------------------------------------------------------------------+ //| Function to compare two string arrays and detect changes | //+------------------------------------------------------------------+ bool isChangeInStringArrays(string &arr1[], string &arr2[]) { bool isChange = false; int size1 = ArraySize(arr1); // Get the size of the first array int size2 = ArraySize(arr2); // Get the size of the second array // Check if sizes are different if (size1 != size2) { Print("Arrays have different sizes. Size of Array 1: ", size1, ", Size of Array 2: ", size2); isChange = true; return (isChange); } // Loop through the arrays and compare corresponding elements for (int i = 0; i < size1; i++) { // Compare the strings at the same index in both arrays if (StringCompare(arr1[i], arr2[i]) != 0) { // If strings are different // Action when strings differ at the same index Print("Change detected at index ", i, ": '", arr1[i], "' vs '", arr2[i], "'"); isChange = true; return (isChange); } } // If no differences are found, you can also log this as no changes detected //Print("No changes detected between arrays."); return (isChange); }
Here, we define the boolean function "isChangeInStringArrays", which compares two string arrays, "arr1" and "arr2", to detect any changes between them. We begin by determining the sizes of both arrays using the ArraySize function and store these sizes in "size1" and "size2". If the sizes of the arrays differ, we print the respective sizes, set the "isChange" flag to true, and return true, indicating a change. If the sizes are the same, we proceed to compare the elements of the arrays using a for loop. For each index, we use the StringCompare function to check if the strings in both arrays are identical. If any strings differ, we print the details of the change and set "isChange" to true, returning true to signal the update. If no differences are found after the loop, the function returns false, indicating that there are no changes. This approach is essential for detecting updates, such as new or updated news events, which we need to reflect on the dashboard.
Armed with the function, we can then use it to update the events.
if (isChangeInStringArrays(previous_eventNames_data,current_eventNames_data)){ Print("CHANGES IN EVENT NAMES DETECTED. UPDATE THE DASHBOARD VALUES"); ObjectsDeleteAll(0,DATA_HOLDERS); ObjectsDeleteAll(0,ARRAY_NEWS); ArrayFree(current_eventNames_data); //--- }
Here, we check if there has been any change between the "previous_eventNames_data" and "current_eventNames_data" arrays by calling the "isChangeInStringArrays" function. If the function returns true, indicating that changes have been detected, we print a message "CHANGES IN EVENT NAMES DETECTED. UPDATE THE DASHBOARD VALUES". Following this, we delete all objects related to data holders and news arrays on the chart using the ObjectsDeleteAll function, specifying the identifiers "DATA_HOLDERS" and "ARRAY_NEWS" as the object prefixes. We do this to clear up any outdated information before updating the dashboard with the latest event data. Finally, we free the memory used by the "current_eventNames_data" array using the ArrayFree function, ensuring that the array is cleared in preparation for the next update. Following this, we update the events data as usual, but this time round, create the data holders and update the news events on the fresh dashboard. Here is the logic.
if (isChangeInStringArrays(previous_eventNames_data,current_eventNames_data)){ Print("CHANGES IN EVENT NAMES DETECTED. UPDATE THE DASHBOARD VALUES"); ObjectsDeleteAll(0,DATA_HOLDERS); ObjectsDeleteAll(0,ARRAY_NEWS); ArrayFree(current_eventNames_data); //--- Initialize starting y-coordinate for displaying news data int startY = 162; //--- Loop through each calendar value up to the maximum defined total for (int i = 0; i < valuesTotal; i++){ MqlCalendarEvent event; //--- Declare event structure CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID MqlCalendarCountry country; //--- Declare country structure CalendarCountryById(event.country_id,country); //--- Retrieve country details by event's country ID MqlCalendarValue value; //--- Declare calendar value structure CalendarValueById(values[i].id,value); //--- Retrieve actual, forecast, and previous values //--- Check if the event’s currency matches any in the filter array (if the filter is enabled) bool currencyMatch = false; if (enableCurrencyFilter) { for (int j = 0; j < ArraySize(curr_filter); j++) { if (country.currency == curr_filter[j]) { currencyMatch = true; break; } } //--- If no match found, skip to the next event if (!currencyMatch) { continue; } } //--- Check importance level if importance filter is enabled bool importanceMatch = false; if (enableImportanceFilter) { for (int k = 0; k < ArraySize(allowed_importance_levels); k++) { if (event.importance == allowed_importance_levels[k]) { importanceMatch = true; break; } } //--- If importance does not match the filter criteria, skip the event if (!importanceMatch) { continue; } } //--- Apply time filter and set timeMatch flag (if the filter is enabled) bool timeMatch = false; if (enableTimeFilter) { datetime eventTime = values[i].time; if (eventTime <= TimeTradeServer() && eventTime >= timeBefore) { timeMatch = true; //--- Event is already released } else if (eventTime >= TimeTradeServer() && eventTime <= timeAfter) { timeMatch = true; //--- Event is yet to be released } //--- Skip if the event doesn't match the time filter if (!timeMatch) { continue; } } //--- If we reach here, the currency matches the filter news_filter_count++; //--- Increment the count of filtered events //--- Set alternating colors for each data row holder color holder_color = (news_filter_count % 2 == 0) ? C'213,227,207' : clrWhite; //--- Create rectangle label for each data row holder createRecLabel(DATA_HOLDERS+string(news_filter_count),62,startY-1,716,26+1,holder_color,1,clrNONE); //--- Initialize starting x-coordinate for each data entry int startX = 65; //--- Loop through calendar data columns for (int k=0; k<ArraySize(array_calendar); k++){ //--- Print event details for debugging //Print("Name = ",event.name,", IMP = ",EnumToString(event.importance),", COUNTRY = ",country.name,", TIME = ",values[i].time); //--- Skip event if currency does not match the selected country code // if (StringFind(_Symbol,country.currency) < 0) continue; //--- Prepare news data array with time, country, and other event details string news_data[ArraySize(array_calendar)]; news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time news_data[2] = country.currency; //--- Event country currency //--- Determine importance color based on event impact color importance_color = clrBlack; if (event.importance == CALENDAR_IMPORTANCE_LOW){importance_color=clrYellow;} else if (event.importance == CALENDAR_IMPORTANCE_MODERATE){importance_color=clrOrange;} else if (event.importance == CALENDAR_IMPORTANCE_HIGH){importance_color=clrRed;} //--- Set importance symbol for the event news_data[3] = ShortToString(0x25CF); //--- Set event name in the data array news_data[4] = event.name; //--- Populate actual, forecast, and previous values in the news data array news_data[5] = DoubleToString(value.GetActualValue(),3); news_data[6] = DoubleToString(value.GetForecastValue(),3); news_data[7] = DoubleToString(value.GetPreviousValue(),3); //--- Create label for each news data item if (k == 3){ createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY-(22-12),news_data[k],importance_color,22,"Calibri"); } else { createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY,news_data[k],clrBlack,12,"Calibri"); } //--- Increment x-coordinate for the next column startX += buttons[k]+3; } ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1); current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name; //--- Increment y-coordinate for the next row of data startY += 25; //Print(startY); //--- Print current y-coordinate for debugging } Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data)); ArrayPrint(current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (Before) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data); ArrayFree(previous_eventNames_data); ArrayCopy(previous_eventNames_data,current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (After) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data); }
Here, we update the dashboard based on the newly acquired data to ensure there are live updates taking effect. Using copy logic, we log the data into the "previous" data holder so that we can use the current data on the next check. We have highlighted the logic that takes care of that, but let us have a deeper look at it.
Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data)); ArrayPrint(current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (Before) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data); ArrayFree(previous_eventNames_data); ArrayCopy(previous_eventNames_data,current_eventNames_data); Print("PREVIOUS EVENT NAMES DATA SIZE (After) = ",ArraySize(previous_eventNames_data)); ArrayPrint(previous_eventNames_data);
Here, we begin by printing the current size of the "current_eventNames_data" array using the ArraySize function and displaying its contents with the ArrayPrint function. This will help us inspect the current set of event names that we are tracking. Then, we print the size of the "previous_eventNames_data" array before it is updated, followed by printing its contents.
Next, we free the memory used by "previous_eventNames_data" with the ArrayFree function, ensuring any previous data stored in the array is cleared to avoid memory issues. After freeing the memory, we use the ArrayCopy function to copy the contents of the "current_eventNames_data" array into "previous_eventNames_data", effectively updating it with the latest event names.
Finally, we print the updated size of the "previous_eventNames_data" array and its contents to confirm that the array now holds the most recent event names. This ensures that the previous event names are correctly updated for future comparison. Upon running the program, we have the following outcome.
Time updates.
Events update.
Dashboard update.
From the image, we can see that the newly registered data is accurately updated on the dashboard. To reconfirm this, we can wait again for some time and try to see of we can keep track of this data, and with the one that logs the updated data. Here is the outcome.
Events data update.
Dashboard update.
From the image, we can see that once there are changes from the previously stored data, they are detected and updated according to the newly registered data and stored for further reference. The stored data is the used to update the dashboard interface in real time, displaying the current news events, and thus confirming the success of our objective.
Conclusion
In conclusion, we implemented a robust system to monitor and detect changes in MQL5 Economic News events by comparing previously stored event data with newly retrieved updates. This comparison mechanism ensures that any differences in event names or details are promptly identified, triggering our dashboard refresh to maintain accuracy and relevance. By filtering data based on currency, importance, and time, we further refined the process to focus on impactful events while dynamically updating the interface.
In the next parts of this series, we will build on this foundation by integrating economic news events into trading strategies, enabling practical applications of the data. Additionally, we aim to enhance the dashboard's functionality by introducing mobility and responsiveness, ensuring a more seamless and interactive experience for traders. Keep tuned.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Users will probably be interested to know that the same topics have been covered thoroughly in the algotrading book (and the site does not show this somehow in automatic suggestions), including tracking and saving event changes, filtering events by multiple conditions (with different logical operators) for extended set of fields, displaying customizable on-the-fly calendar subset in a panel on chart, and embedding for trading into EAs with support of transferring complete archive of the calendar into the tester.