Economic calendar API

Free economic calendar for Expert Advisors, cBots and any trading bot: upcoming news for the 9 major currencies as JSON or CSV, filtered by currency, impact and days. No API key. With ready-made news filter code for MT4, MT5 and cTrader (cloud included).
GET https://showmytrades.com/api/public/calendar/v1

Scheduled macroeconomic releases, central-bank decisions and speeches, and market holidays for USD, EUR, GBP, JPY, AUD, NZD, CAD, CHF and CNY, with forecast, previous and actual values. The same data as the economic calendar page, in a format made for code: Unix timestamps, lowercase impact levels, JSON or CSV.

It is free and needs no API key. The typical use is a news filter: a trading robot that pauses before and after high-impact news. This page has the endpoint, then working code for MetaTrader 4, MetaTrader 5 and cTrader that you can paste as it is.

One request every 15 minutes is all a robot needs. The calendar changes a few times a day, responses are cached for 15 minutes on our side, and a request with If-None-Match costs nothing while the data is unchanged. The limit is 600 requests per minute per IP address, so dozens of terminals on one VPS can start at the same time without being refused.

Query parameters

currency
string
Comma-separated ISO codes among USD, EUR, GBP, JPY, AUD, NZD, CAD, CHF, CNY. Example: currency=EUR,USD. Omit it for all nine.
impact
string
Comma-separated levels among high, medium, low, holiday, or all. Defaults to high. impact=high,medium is the usual choice for a cautious filter.
days
integer
How many days ahead, from 1 to 30. Defaults to 1. The window always starts 6 hours in the past, so a filter that pauses after a release still sees the release.
format
string
json (default) or csv. CSV is easier to parse in MQL.

Anything outside those lists is a 400 with a message that says what is allowed, so a misconfigured robot notices on its first run instead of trading through the news.

Example

Today's high-impact news for the euro and the dollar:

curl "https://showmytrades.com/api/public/calendar/v1?currency=EUR,USD&impact=high&days=1"
Response
{
  "generated_at": "2026-10-17T07:12:40Z",
  "from": "2026-10-17T01:12:40Z",
  "to": "2026-10-18T07:12:40Z",
  "count": 2,
  "events": [
    {
      "ts": 1760702400,
      "time": "2026-10-17T12:00:00Z",
      "currency": "EUR",
      "impact": "high",
      "title": "ECB President Lagarde Speech",
      "forecast": "",
      "previous": "",
      "actual": ""
    },
    {
      "ts": 1760704200,
      "time": "2026-10-17T12:30:00Z",
      "currency": "USD",
      "impact": "high",
      "title": "Building Permits MoM",
      "forecast": "1.33M",
      "previous": "1.31M",
      "actual": ""
    }
  ],
  "attribution": { "required": false, "text": "Economic calendar by ShowMyTrades", "url": "https://showmytrades.com/tools/economic-calendar" },
  "docs": "https://showmytrades.com/docs/api/economic-calendar"
}
ts
integer
The release time as Unix time in seconds, UTC. In MQL it is a datetime as it is; compare it with TimeGMT(), never with the broker's TimeCurrent().
time
string
The same instant in ISO 8601 UTC, for humans and for languages with a date parser.
impact
string
high, medium, low or holiday. A holiday is a closed market, not a release: its time is the start of the day.
forecast / previous / actual
string
Strings exactly as published: "0.3%", "$70.5B", "1.33M". Empty when not available. actual is empty until the release and is filled in minutes after it.

The same request with format=csv:

ts,time,currency,impact,forecast,previous,actual,title
1760702400,2026-10-17T12:00:00Z,EUR,high,,,,ECB President Lagarde Speech
1760704200,2026-10-17T12:30:00Z,USD,high,1.33M,1.31M,,Building Permits MoM

The title is the last column because it is the only one that can contain a comma (it is then wrapped in double quotes, RFC 4180). Read the first seven columns with a split on commas and take everything after the seventh comma as the title.

MetaTrader 4 and 5: a news filter in one include file

The same file compiles in MQL4 and MQL5. Save it as SMT_NewsFilter.mqh in MQL4\Include (or MQL5\Include), then include it in your Expert Advisor.

Before it can download anything, allow the address in the terminal: Tools → Options → Expert Advisors → tick Allow WebRequest for listed URL and add https://showmytrades.com. Without that line WebRequest() returns -1 and error 4060; the file prints a reminder.

SMT_NewsFilter.mqh
//+------------------------------------------------------------------+
//| SMT_NewsFilter.mqh - news filter for MT4 and MT5                 |
//| Data: https://showmytrades.com/api/public/calendar/v1 (free)     |
//| Docs: https://showmytrades.com/docs/api/economic-calendar        |
//| Setup: Tools > Options > Expert Advisors > Allow WebRequest for  |
//|        https://showmytrades.com                                  |
//+------------------------------------------------------------------+
#property strict

input string SMT_Currencies     = "EUR,USD"; // News: currencies (comma-separated, empty = all)
input string SMT_Impact         = "high";    // News: impact (high | high,medium | all)
input int    SMT_MinutesBefore  = 30;        // News: pause before a release (minutes)
input int    SMT_MinutesAfter   = 30;        // News: pause after a release (minutes)
input int    SMT_RefreshMinutes = 15;        // News: download interval (minutes, min 15)

datetime smt_eventTime[];
string   smt_eventTitle[];
datetime smt_nextDownload = 0;

//--- everything after the n-th comma (the CSV title, which may itself contain commas)
string SMT_AfterComma(const string line, const int n)
{
   int pos = -1;
   for(int i = 0; i < n; i++)
   {
      pos = StringFind(line, ",", pos + 1);
      if(pos < 0) return "";
   }
   string rest = StringSubstr(line, pos + 1);
   StringReplace(rest, "\"\"", "\"");
   if(StringLen(rest) >= 2 && StringGetCharacter(rest, 0) == '"')
      rest = StringSubstr(rest, 1, StringLen(rest) - 2);
   return rest;
}

//--- downloads the next 2 days (the window also covers the last 6 hours)
bool SMT_Download()
{
   string url = "https://showmytrades.com/api/public/calendar/v1?format=csv&days=2&impact=" + SMT_Impact;
   if(StringLen(SMT_Currencies) > 0) url += "&currency=" + SMT_Currencies;

   char   post[], result[];
   string headers;
   ResetLastError();
   int status = WebRequest("GET", url, "", NULL, 10000, post, 0, result, headers);
   if(status != 200)
   {
      Print("[SMT] calendar download failed: HTTP ", status, ", error ", GetLastError(),
            ". Is https://showmytrades.com in Tools > Options > Expert Advisors > Allow WebRequest?");
      smt_nextDownload = TimeCurrent() + 60; // retry in a minute
      return false;
   }

   string body = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
   string lines[];
   int n = StringSplit(body, '\n', lines);

   ArrayResize(smt_eventTime, 0);
   ArrayResize(smt_eventTitle, 0);
   for(int i = 1; i < n; i++) // line 0 is the header
   {
      string f[];
      if(StringSplit(lines[i], ',', f) < 8) continue;
      int k = ArraySize(smt_eventTime);
      ArrayResize(smt_eventTime, k + 1);
      ArrayResize(smt_eventTitle, k + 1);
      smt_eventTime[k]  = (datetime)StringToInteger(f[0]);      // Unix seconds, UTC
      smt_eventTitle[k] = f[2] + " " + f[3] + ": " + SMT_AfterComma(lines[i], 7);
   }
   smt_nextDownload = TimeCurrent() + MathMax(SMT_RefreshMinutes, 15) * 60;
   Print("[SMT] calendar: ", ArraySize(smt_eventTime), " events loaded");
   return true;
}

//--- true while a release is within the pause window; reason tells which one
bool SMT_IsNewsTime(string &reason)
{
   reason = "";
   if(MQLInfoInteger(MQL_TESTER)) return false;   // no network in the tester
   if(TimeCurrent() >= smt_nextDownload) SMT_Download();

   datetime now = TimeGMT();
   for(int i = 0; i < ArraySize(smt_eventTime); i++)
   {
      if(now >= smt_eventTime[i] - SMT_MinutesBefore * 60 && now <= smt_eventTime[i] + SMT_MinutesAfter * 60)
      {
         reason = smt_eventTitle[i];
         return true;
      }
   }
   return false;
}

Using it in an Expert Advisor:

YourEA.mq4 / .mq5
#include <SMT_NewsFilter.mqh>

void OnTick()
{
   string news;
   if(SMT_IsNewsTime(news))
   {
      Comment("News filter active: ", news);
      return;                       // no new entries during the news window
   }
   Comment("");
   // ... your entry logic ...
}

A few things worth knowing:

  • Times. ts is UTC. The file compares it with TimeGMT(), which is the computer's clock in UTC: keep the VPS clock synchronised. Broker time (TimeCurrent()) is only used to space the downloads.
  • The tester. WebRequest() does not work in the strategy tester, so the filter returns "no news" there. Backtesting a news filter needs historical data stored locally; that is a different job.
  • Several charts. Each chart downloads on its own. Ten EAs on one terminal are ten requests every 15 minutes, well inside the limits.
  • MT5 has a built-in calendar. The Calendar* functions of MQL5 work without any download on most brokers, and that is a fine choice. Use this API when the broker does not provide the built-in calendar, when you want one file for MT4 and MT5, or when you want the same data on cTrader.

cTrader: a news filter for a cBot, cloud included

cTrader Cloud does not let a cBot make HTTP requests: Http.Get() fails with status 503 there. The only network it allows is a WebSocket on port 25345. So the calendar is also served on that port:

ws://showmytrades.com:25345/

Open the socket, send one line of text with the same parameters as the HTTP query, and read one text frame back with the same JSON or CSV body:

→ currency=EUR,USD&impact=high&days=2&format=csv
← ts,time,currency,impact,forecast,previous,actual,title
  1760704200,2026-10-17T12:30:00Z,USD,high,1.33M,1.31M,,Building Permits MoM
  ...

ping answers pong, an invalid request answers a line starting with Error:, and the connection closes after a minute of silence. The same port also answers a plain HTTP GET with the same path, which is convenient for a quick test from a terminal:

curl "http://showmytrades.com:25345/api/public/calendar/v1?currency=USD&impact=high"

The cBot below works unchanged on the desktop and in the cloud, with AccessRights.None, so it can be published. It downloads the calendar at start and every 15 minutes, and IsNewsTime() tells you whether to stay out.

SmtNewsFilterBot.cs
// News filter for cTrader (desktop and cloud), data by ShowMyTrades, free, no key.
// Docs: https://showmytrades.com/docs/api/economic-calendar
using System;
using System.Collections.Generic;
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None, TimeZone = TimeZones.UTC)]
    public class SmtNewsFilterBot : Robot
    {
        [Parameter("News: currencies (comma-separated, empty = all)", DefaultValue = "EUR,USD")]
        public string NewsCurrencies { get; set; }

        [Parameter("News: impact (high | high,medium | all)", DefaultValue = "high")]
        public string NewsImpact { get; set; }

        [Parameter("News: minutes before", DefaultValue = 30, MinValue = 0)]
        public int MinutesBefore { get; set; }

        [Parameter("News: minutes after", DefaultValue = 30, MinValue = 0)]
        public int MinutesAfter { get; set; }

        private const string CalendarWs = "ws://showmytrades.com:25345/";
        private const string CalendarHttp = "https://showmytrades.com/api/public/calendar/v1?";

        private readonly List<KeyValuePair<DateTime, string>> _events = new List<KeyValuePair<DateTime, string>>();
        private WebSocketClient _ws;
        private bool _received;

        protected override void OnStart()
        {
            DownloadCalendar();
            Timer.Start(TimeSpan.FromMinutes(15));
        }

        protected override void OnTimer()
        {
            DownloadCalendar();
        }

        protected override void OnTick()
        {
            string reason;
            if (IsNewsTime(out reason))
            {
                // No new entries during the news window. Manage open positions as you prefer.
                return;
            }

            // ... your entry logic ...
        }

        protected override void OnStop()
        {
            CloseSocket();
        }

        // True while a release is inside the pause window; reason says which one.
        public bool IsNewsTime(out string reason)
        {
            DateTime now = Server.TimeInUtc;
            foreach (KeyValuePair<DateTime, string> e in _events)
            {
                if (now >= e.Key.AddMinutes(-MinutesBefore) && now <= e.Key.AddMinutes(MinutesAfter))
                {
                    reason = e.Value;
                    return true;
                }
            }
            reason = null;
            return false;
        }

        private string Query()
        {
            string q = "format=csv&days=2&impact=" + NewsImpact;
            if (!string.IsNullOrWhiteSpace(NewsCurrencies)) q += "&currency=" + NewsCurrencies.Replace(" ", "");
            return q;
        }

        private void DownloadCalendar()
        {
            if (RunningMode != RunningMode.RealTime) return; // no network in the backtester

            // cTrader Cloud blocks HTTP from cBots and allows only a WebSocket on port 25345,
            // so the socket is the primary path everywhere; HTTP is the fallback on the desktop.
            try
            {
                CloseSocket();
                _received = false;
                _ws = new WebSocketClient(new WebSocketClientOptions { Timeout = TimeSpan.FromSeconds(15) });
                _ws.Connected += OnSocketConnected;
                _ws.TextReceived += OnSocketText;
                _ws.Disconnected += OnSocketDisconnected;
                _ws.Connect(new Uri(CalendarWs));
            }
            catch (Exception ex)
            {
                Print("[SMT] calendar socket failed: {0}", ex.Message);
                DownloadOverHttp();
            }
        }

        private void OnSocketConnected(WebSocketClientConnectedEventArgs args)
        {
            try { _ws.Send(Query()); }
            catch (Exception ex) { Print("[SMT] calendar send failed: {0}", ex.Message); }
        }

        private void OnSocketText(WebSocketClientTextReceivedEventArgs args)
        {
            _received = true;
            if (args.Text.StartsWith("Error:")) Print("[SMT] calendar: {0}", args.Text);
            else ParseCsv(args.Text);
            CloseSocket();
        }

        private void OnSocketDisconnected(WebSocketClientDisconnectEventArgs args)
        {
            if (!_received) DownloadOverHttp(); // desktop only: the cloud answers 503
        }

        private void DownloadOverHttp()
        {
            try
            {
                HttpResponse r = Http.Get(CalendarHttp + Query());
                if (r.IsSuccessful) ParseCsv(r.Body);
                else Print("[SMT] calendar HTTP failed: {0}", r.StatusCode);
            }
            catch (Exception ex) { Print("[SMT] calendar HTTP failed: {0}", ex.Message); }
        }

        private void CloseSocket()
        {
            WebSocketClient ws = _ws;
            _ws = null;
            if (ws == null) return;
            try { ws.Connected -= OnSocketConnected; ws.TextReceived -= OnSocketText; ws.Disconnected -= OnSocketDisconnected; } catch (Exception) { }
            try { if (ws.State == WebSocketClientState.Open) ws.Close(WebSocketClientCloseStatus.NormalClosure, "done"); } catch (Exception) { }
            try { ws.Dispose(); } catch (Exception) { }
        }

        // CSV: ts,time,currency,impact,forecast,previous,actual,title (title last, may be quoted)
        private void ParseCsv(string csv)
        {
            List<KeyValuePair<DateTime, string>> list = new List<KeyValuePair<DateTime, string>>();
            foreach (string raw in csv.Split('\n'))
            {
                string line = raw.TrimEnd('\r');
                if (line.Length == 0 || line.StartsWith("ts,")) continue;
                string[] f = line.Split(',');
                if (f.Length < 8) continue;
                long ts;
                if (!long.TryParse(f[0], out ts)) continue;
                DateTime at = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ts);
                string title = AfterComma(line, 7).Replace("\"\"", "\"");
                if (title.Length >= 2 && title[0] == '"') title = title.Substring(1, title.Length - 2);
                list.Add(new KeyValuePair<DateTime, string>(at, f[2] + " " + f[3] + ": " + title));
            }
            _events.Clear();
            _events.AddRange(list);
            Print("[SMT] calendar: {0} events loaded", _events.Count);
        }

        private static string AfterComma(string line, int n)
        {
            int pos = -1;
            for (int i = 0; i < n; i++)
            {
                pos = line.IndexOf(',', pos + 1);
                if (pos < 0) return "";
            }
            return line.Substring(pos + 1);
        }
    }
}
  • Server.TimeInUtc is the comparison clock, so the broker's time zone never matters.
  • The backtester has no network: the filter is silent there, like in MetaTrader.
  • The socket has no TLS (ws://, not wss://): that is what cTrader Cloud connects with. The calendar is public and the request carries nothing about you or your account.

Python, JavaScript and everything else

Any HTTP client works. Python:

import requests

events = requests.get(
    "https://showmytrades.com/api/public/calendar/v1",
    params={"currency": "EUR,USD", "impact": "high,medium", "days": 7},
    timeout=10,
).json()["events"]

for e in events:
    print(e["time"], e["currency"], e["impact"], e["title"], e["forecast"], e["actual"])

JavaScript, in a browser too (CORS is open):

const res = await fetch('https://showmytrades.com/api/public/calendar/v1?currency=USD&impact=high&days=1')
const { events } = await res.json()

Polite polling with the ETag: keep the value from the last response and send it back; a 304 means nothing changed and has no body.

curl -i "https://showmytrades.com/api/public/calendar/v1?impact=high" \
  -H 'If-None-Match: "a1b2c3d4e5f6a7b8c9d0"'

For AI assistants

The same data is a tool of the MCP server, get_economic_calendar, with the same three parameters and no key. An assistant asked to "add a news filter to my EA" or "what news is out today" can call it directly, and should point the user to the code on this page for the robot itself.

Limits and terms

PriceFree, no account, no key
CurrenciesUSD, EUR, GBP, JPY, AUD, NZD, CAD, CHF, CNY
WindowFrom 6 hours ago to 30 days ahead at most
Rate limit600 requests per minute per IP (HTTP), 60 messages per minute per IP (WebSocket)
Caching15 minutes, with ETag
CreditAppreciated, not required: Economic calendar by ShowMyTrades, linked to the calendar page

The calendar is assembled from public sources and served as-is, with no guarantee of completeness or timing; schedules change and some releases are moved or cancelled. It is information, not advice: whether to trade around a release is your decision.