Automationยท

How to Add a News Filter to a cTrader cBot (Cloud Included)

cTrader has no calendar API for cBots, and cTrader Cloud blocks HTTP requests. Here is a news filter that works on the desktop and in the cloud, with a free calendar over WebSocket and the full C# code.

A news filter in a cBot runs into two walls that MetaTrader developers never see. First, the cTrader Automate API has no economic calendar: the platform shows one, a cBot cannot read it. Second, most people run their cBots in cTrader Cloud, where a cBot cannot make HTTP requests at all: Http.Get() comes back with status 503.

The cloud does allow one thing: a WebSocket on port 25345. So that is how the free ShowMyTrades economic calendar reaches a cBot. This guide gives you the whole thing: the idea, the protocol, and a cBot you can compile as it is.

๐Ÿงญ How It Works

cBot (desktop or cloud)                      ShowMyTrades
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ every 15 min:            โ”‚  ws://โ€ฆ:25345   โ”‚ same calendar as        โ”‚
โ”‚  open socket, send       โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ /api/public/calendar/v1 โ”‚
โ”‚  "currency=EUR,USD&      โ”‚                 โ”‚ cached, filtered,       โ”‚
โ”‚   impact=high&days=2&    โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ a few kilobytes         โ”‚
โ”‚   format=csv"            โ”‚   CSV text      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚  parse, close            โ”‚
โ”‚ every tick: IsNewsTime() โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Addressws://showmytrades.com:25345/
You sendOne line of text with the same parameters as the HTTP API: currency, impact, days, format
You getOne text frame with the CSV (or JSON) body
Access rightsAccessRights.None is enough, so the cBot can be published
Key or accountNone
Desktop fallbackPlain HTTP works there; the code below falls back to it if the socket fails

The socket uses ws://, not wss://, because that is what cTrader Cloud connects with. Nothing about you or your account travels on it: only the four parameters.

๐Ÿ“„ The cBot

Create a new cBot in cTrader Automate, replace its code with this, and build. On the desktop it compiles against cTrader.Automate from NuGet; in the cloud it runs as it is.

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);
        }
    }
}

To put the filter inside an existing cBot, copy everything from CalendarWs down to AfterComma() into it, call DownloadCalendar() from OnStart() and from a 15-minute timer, and check IsNewsTime() before each entry.

๐Ÿงช Trying It

  1. Build the cBot and add an instance to a chart. The log should show [SMT] calendar: N events loaded within a few seconds. Zero on a quiet weekend is correct.
  2. Set News: impact to all and minutes before to a large number to see the filter engage, then put the real values back.
  3. Run the same instance in the cloud: the log line is the same. That is the whole point of the socket.

โฑ๏ธ Times and the Backtester

The calendar's timestamps are UTC and the code compares them with Server.TimeInUtc, so the broker's time zone and daylight saving never enter the picture. The backtester and the optimizer have no network: DownloadCalendar() returns early there and the filter is silent. Backtesting a news filter needs a file of past events shipped with the cBot, a separate exercise.

๐ŸŽ›๏ธ Tuning

  • Open positions are not touched. Use IsNewsTime() to decide what to do with them: close, trail, or leave alone.
  • Medium impact (high,medium) triples the events. Scalpers often want it, swing systems rarely.
  • Several cBots on one account each download on their own: ten of them are 40 small requests an hour, far below the limit of 60 socket messages per minute per IP address.

โ“ FAQ

Why a WebSocket and not HTTPS? Because cTrader Cloud does not let a cBot make HTTP requests, and it allows WebSocket connections only on port 25345. On the desktop the same cBot would also work over HTTPS, and the code falls back to it if the socket cannot connect.

Is it safe to send on ws:// without encryption? The request contains four parameters (currencies, impact, days, format) and nothing about you or your account; the response is a public calendar. There is nothing to protect. The same data is available over HTTPS from the API endpoint.

Does it need AccessRights.FullAccess? No. WebSocketClient and Http are available with AccessRights.None, which is also what the cTrader Store requires.

Does it need a ShowMyTrades account? No. If you also want to publish the results of the cBot, connecting a cTrader account is a separate thing and takes one OAuth authorisation.


The socket is the only unusual part, and it is already written. The rest is your strategy, with one fewer way to lose money around the news. Track the account free to see the difference on the drawdown chart.