[{"data":1,"prerenderedAt":1529},["ShallowReactive",2],{"\u002Fes\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot":3,"\u002Fes\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot-surround":1508,"\u002Fes\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot-langs":1519},{"id":4,"title":5,"authors":6,"badge":12,"body":14,"date":1496,"description":1497,"draft":1498,"extension":1499,"image":1500,"meta":1502,"navigation":219,"path":1503,"seo":1504,"stem":1505,"updated":1506,"__hash__":1507},"posts\u002Fes\u002F3.guides\u002F43.add-a-news-filter-to-a-ctrader-cbot.md","Cómo agregar un filtro de noticias a un cBot de cTrader (también en la nube)",[7],{"name":8,"to":9,"avatar":10},"ShowMyTrades Team","https:\u002F\u002Fshowmytrades.com",{"src":11},"\u002Ficon-512.png",{"label":13},"Automatización",{"type":15,"value":16,"toc":1487},"minimark",[17,39,52,57,67,152,163,167,174,1273,1296,1300,1334,1338,1355,1359,1389,1393,1399,1411,1431,1442,1446,1472,1475,1483],[18,19,20,21,25,26,29,30,34,35,38],"p",{},"Quien programa un filtro de noticias para un cBot choca con dos muros que en MetaTrader no existen. El primero: la API de cTrader Automate ",[22,23,24],"strong",{},"no tiene calendario económico",". La plataforma muestra uno, pero un cBot no puede leerlo. El segundo: la mayoría de la gente ejecuta sus cBots en ",[22,27,28],{},"cTrader Cloud",", donde un cBot no puede hacer ninguna solicitud HTTP: ",[31,32,33],"code",{},"Http.Get()"," devuelve el estado ",[31,36,37],{},"503",".",[18,40,41,42,45,46,51],{},"La nube sí permite una cosa: un ",[22,43,44],{},"WebSocket en el puerto 25345",". Por ahí llega a un cBot el ",[47,48,50],"a",{"href":49},"\u002Fdocs\u002Fapi\u002Feconomic-calendar","calendario económico de ShowMyTrades",", que es gratuito. En esta guía tienes todo: la idea, el protocolo y un cBot que puedes compilar tal cual.",[53,54,56],"h2",{"id":55},"cómo-funciona","🧭 Cómo funciona",[58,59,64],"pre",{"className":60,"code":62,"language":63},[61],"language-text","cBot (escritorio o nube)                     ShowMyTrades\n┌──────────────────────────┐                 ┌─────────────────────────┐\n│ cada 15 min:             │  ws:\u002F\u002F…:25345   │ el mismo calendario que │\n│  abre el socket, envía   │ ──────────────► │ \u002Fapi\u002Fpublic\u002Fcalendar\u002Fv1 │\n│  \"currency=EUR,USD&      │                 │ en caché, filtrado,     │\n│   impact=high&days=2&    │ ◄────────────── │ unos pocos kilobytes    │\n│   format=csv\"            │   texto CSV     └─────────────────────────┘\n│  analiza, cierra         │\n│ cada tick: IsNewsTime()  │\n└──────────────────────────┘\n","text",[31,65,62],{"__ignoreMap":66},"",[68,69,70,81],"table",{},[71,72,73],"thead",{},[74,75,76,79],"tr",{},[77,78],"th",{},[77,80],{},[82,83,84,95,116,124,136,144],"tbody",{},[74,85,86,90],{},[87,88,89],"td",{},"Dirección",[87,91,92],{},[31,93,94],{},"ws:\u002F\u002Fshowmytrades.com:25345\u002F",[74,96,97,100],{},[87,98,99],{},"Qué envías",[87,101,102,103,106,107,106,110,106,113],{},"Una línea de texto con los mismos parámetros que la API HTTP: ",[31,104,105],{},"currency",", ",[31,108,109],{},"impact",[31,111,112],{},"days",[31,114,115],{},"format",[74,117,118,121],{},[87,119,120],{},"Qué recibes",[87,122,123],{},"Un frame de texto con el cuerpo en CSV (o JSON)",[74,125,126,129],{},[87,127,128],{},"Permisos de acceso",[87,130,131,132,135],{},"Basta con ",[31,133,134],{},"AccessRights.None",", así que el cBot se puede publicar",[74,137,138,141],{},[87,139,140],{},"Clave o cuenta",[87,142,143],{},"Ninguna",[74,145,146,149],{},[87,147,148],{},"Plan B en el escritorio",[87,150,151],{},"Ahí el HTTP normal funciona; el código de abajo recurre a él si el socket falla",[18,153,154,155,158,159,162],{},"El socket usa ",[31,156,157],{},"ws:\u002F\u002F"," y no ",[31,160,161],{},"wss:\u002F\u002F"," porque es lo que acepta cTrader Cloud. Por él no viaja nada sobre ti ni sobre tu cuenta: solo los cuatro parámetros.",[53,164,166],{"id":165},"el-cbot","📄 El cBot",[18,168,169,170,173],{},"Crea un cBot nuevo en cTrader Automate, sustituye su código por este y compílalo. En el escritorio se compila contra el paquete ",[31,171,172],{},"cTrader.Automate"," de NuGet; en la nube funciona tal cual.",[58,175,180],{"className":176,"code":177,"filename":178,"language":179,"meta":66,"style":66},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Filtro de noticias para cTrader (escritorio y nube), datos de ShowMyTrades, gratis, sin clave.\n\u002F\u002F Documentación: https:\u002F\u002Fshowmytrades.com\u002Fdocs\u002Fapi\u002Feconomic-calendar\nusing System;\nusing System.Collections.Generic;\nusing cAlgo.API;\n\nnamespace cAlgo.Robots\n{\n    [Robot(AccessRights = AccessRights.None, TimeZone = TimeZones.UTC)]\n    public class SmtNewsFilterBot : Robot\n    {\n        [Parameter(\"News: currencies (comma-separated, empty = all)\", DefaultValue = \"EUR,USD\")]\n        public string NewsCurrencies { get; set; }\n\n        [Parameter(\"News: impact (high | high,medium | all)\", DefaultValue = \"high\")]\n        public string NewsImpact { get; set; }\n\n        [Parameter(\"News: minutes before\", DefaultValue = 30, MinValue = 0)]\n        public int MinutesBefore { get; set; }\n\n        [Parameter(\"News: minutes after\", DefaultValue = 30, MinValue = 0)]\n        public int MinutesAfter { get; set; }\n\n        private const string CalendarWs = \"ws:\u002F\u002Fshowmytrades.com:25345\u002F\";\n        private const string CalendarHttp = \"https:\u002F\u002Fshowmytrades.com\u002Fapi\u002Fpublic\u002Fcalendar\u002Fv1?\";\n        private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n\n        private readonly List\u003CKeyValuePair\u003CDateTime, string>> _events = new List\u003CKeyValuePair\u003CDateTime, string>>();\n        private WebSocketClient _ws;\n        private bool _received;\n\n        protected override void OnStart()\n        {\n            DownloadCalendar();\n            Timer.Start(TimeSpan.FromMinutes(15));\n        }\n\n        protected override void OnTimer()\n        {\n            DownloadCalendar();\n        }\n\n        protected override void OnTick()\n        {\n            string reason;\n            if (IsNewsTime(out reason))\n            {\n                \u002F\u002F Sin entradas nuevas durante la ventana de noticias. Gestiona las posiciones abiertas como prefieras.\n                return;\n            }\n\n            \u002F\u002F ... tu lógica de entrada ...\n        }\n\n        protected override void OnStop()\n        {\n            CloseSocket();\n        }\n\n        \u002F\u002F True mientras una publicación cae dentro de la ventana de pausa; reason indica cuál.\n        \u002F\u002F Server.TimeInUtc es el reloj UTC del propio cTrader: la plataforma conoce el desfase del bróker,\n        \u002F\u002F así que se puede comparar con el calendario sin problema (ParseCsv lo contrasta con el servidor).\n        public bool IsNewsTime(out string reason)\n        {\n            DateTime now = Server.TimeInUtc;\n            foreach (KeyValuePair\u003CDateTime, string> e in _events)\n            {\n                if (now >= e.Key.AddMinutes(-MinutesBefore) && now \u003C= e.Key.AddMinutes(MinutesAfter))\n                {\n                    reason = e.Value;\n                    return true;\n                }\n            }\n            reason = null;\n            return false;\n        }\n\n        private string Query()\n        {\n            string q = \"format=csv&days=2&impact=\" + NewsImpact;\n            if (!string.IsNullOrWhiteSpace(NewsCurrencies)) q += \"&currency=\" + NewsCurrencies.Replace(\" \", \"\");\n            return q;\n        }\n\n        private void DownloadCalendar()\n        {\n            if (RunningMode != RunningMode.RealTime) return; \u002F\u002F el backtester no tiene red\n\n            \u002F\u002F cTrader Cloud bloquea el HTTP de los cBots y solo permite un WebSocket en el puerto 25345,\n            \u002F\u002F así que el socket es la vía principal en todas partes; HTTP es el plan B en el escritorio.\n            try\n            {\n                CloseSocket();\n                _received = false;\n                _ws = new WebSocketClient(new WebSocketClientOptions { Timeout = TimeSpan.FromSeconds(15) });\n                _ws.Connected += OnSocketConnected;\n                _ws.TextReceived += OnSocketText;\n                _ws.Disconnected += OnSocketDisconnected;\n                _ws.Connect(new Uri(CalendarWs));\n            }\n            catch (Exception ex)\n            {\n                Print(\"[SMT] calendar socket failed: {0}\", ex.Message);\n                DownloadOverHttp();\n            }\n        }\n\n        private void OnSocketConnected(WebSocketClientConnectedEventArgs args)\n        {\n            try { _ws.Send(Query()); }\n            catch (Exception ex) { Print(\"[SMT] calendar send failed: {0}\", ex.Message); }\n        }\n\n        private void OnSocketText(WebSocketClientTextReceivedEventArgs args)\n        {\n            _received = true;\n            if (args.Text.StartsWith(\"Error:\")) Print(\"[SMT] calendar: {0}\", args.Text);\n            else ParseCsv(args.Text);\n            CloseSocket();\n        }\n\n        private void OnSocketDisconnected(WebSocketClientDisconnectEventArgs args)\n        {\n            if (!_received) DownloadOverHttp(); \u002F\u002F solo en el escritorio: la nube responde 503\n        }\n\n        private void DownloadOverHttp()\n        {\n            try\n            {\n                HttpResponse r = Http.Get(CalendarHttp + Query());\n                if (r.IsSuccessful) ParseCsv(r.Body);\n                else Print(\"[SMT] calendar HTTP failed: {0}\", r.StatusCode);\n            }\n            catch (Exception ex) { Print(\"[SMT] calendar HTTP failed: {0}\", ex.Message); }\n        }\n\n        private void CloseSocket()\n        {\n            WebSocketClient ws = _ws;\n            _ws = null;\n            if (ws == null) return;\n            try { ws.Connected -= OnSocketConnected; ws.TextReceived -= OnSocketText; ws.Disconnected -= OnSocketDisconnected; } catch (Exception) { }\n            try { if (ws.State == WebSocketClientState.Open) ws.Close(WebSocketClientCloseStatus.NormalClosure, \"done\"); } catch (Exception) { }\n            try { ws.Dispose(); } catch (Exception) { }\n        }\n\n        \u002F\u002F CSV: ts,time,currency,impact,forecast,previous,actual,title (title al final, puede ir entre comillas),\n        \u002F\u002F y una última línea \"# server_time=\u003Cunix> server_time_iso=\u003Ciso>\" con el reloj UTC del servidor.\n        private void ParseCsv(string csv)\n        {\n            List\u003CKeyValuePair\u003CDateTime, string>> list = new List\u003CKeyValuePair\u003CDateTime, string>>();\n            foreach (string raw in csv.Split('\\n'))\n            {\n                string line = raw.TrimEnd('\\r');\n                if (line.Length == 0 || line.StartsWith(\"ts,\")) continue;\n                if (line.StartsWith(\"# server_time=\"))\n                {\n                    long serverTime;\n                    if (long.TryParse(line.Substring(14, 10), out serverTime))\n                    {\n                        double skew = (Server.TimeInUtc - Epoch.AddSeconds(serverTime)).TotalSeconds;\n                        if (Math.Abs(skew) > 60) Print(\"[SMT] warning: cTrader's UTC clock is {0:F0} s away from the calendar server\", skew);\n                    }\n                    continue;\n                }\n                string[] f = line.Split(',');\n                if (f.Length \u003C 8) continue;\n                long ts;\n                if (!long.TryParse(f[0], out ts)) continue;\n                DateTime at = Epoch.AddSeconds(ts);\n                string title = AfterComma(line, 7).Replace(\"\\\"\\\"\", \"\\\"\");\n                if (title.Length >= 2 && title[0] == '\"') title = title.Substring(1, title.Length - 2);\n                list.Add(new KeyValuePair\u003CDateTime, string>(at, f[2] + \" \" + f[3] + \": \" + title));\n            }\n            _events.Clear();\n            _events.AddRange(list);\n            Print(\"[SMT] calendar: {0} events loaded\", _events.Count);\n        }\n\n        private static string AfterComma(string line, int n)\n        {\n            int pos = -1;\n            for (int i = 0; i \u003C n; i++)\n            {\n                pos = line.IndexOf(',', pos + 1);\n                if (pos \u003C 0) return \"\";\n            }\n            return line.Substring(pos + 1);\n        }\n    }\n}\n","SmtNewsFilterBot.cs","csharp",[31,181,182,190,196,202,208,214,221,227,233,239,245,251,257,263,268,274,280,285,291,297,302,308,314,319,325,331,337,342,348,354,360,365,371,377,383,389,395,400,406,411,416,421,426,432,437,443,449,455,461,467,473,478,484,489,494,500,505,511,516,521,527,533,539,545,550,556,562,567,573,579,585,591,597,602,608,614,619,624,630,635,641,647,653,658,663,669,674,680,685,691,697,703,708,714,720,726,732,738,744,750,755,761,766,772,778,783,788,793,799,804,810,816,821,826,832,837,843,849,855,860,865,870,876,881,887,892,897,903,908,913,918,924,930,936,941,947,952,957,963,968,974,980,986,992,998,1004,1009,1014,1020,1026,1032,1037,1043,1049,1054,1060,1066,1072,1077,1083,1089,1095,1101,1107,1113,1119,1124,1130,1136,1142,1148,1154,1160,1166,1172,1177,1183,1189,1195,1200,1205,1211,1216,1222,1228,1233,1239,1245,1250,1256,1261,1267],{"__ignoreMap":66},[183,184,187],"span",{"class":185,"line":186},"line",1,[183,188,189],{},"\u002F\u002F Filtro de noticias para cTrader (escritorio y nube), datos de ShowMyTrades, gratis, sin clave.\n",[183,191,193],{"class":185,"line":192},2,[183,194,195],{},"\u002F\u002F Documentación: https:\u002F\u002Fshowmytrades.com\u002Fdocs\u002Fapi\u002Feconomic-calendar\n",[183,197,199],{"class":185,"line":198},3,[183,200,201],{},"using System;\n",[183,203,205],{"class":185,"line":204},4,[183,206,207],{},"using System.Collections.Generic;\n",[183,209,211],{"class":185,"line":210},5,[183,212,213],{},"using cAlgo.API;\n",[183,215,217],{"class":185,"line":216},6,[183,218,220],{"emptyLinePlaceholder":219},true,"\n",[183,222,224],{"class":185,"line":223},7,[183,225,226],{},"namespace cAlgo.Robots\n",[183,228,230],{"class":185,"line":229},8,[183,231,232],{},"{\n",[183,234,236],{"class":185,"line":235},9,[183,237,238],{},"    [Robot(AccessRights = AccessRights.None, TimeZone = TimeZones.UTC)]\n",[183,240,242],{"class":185,"line":241},10,[183,243,244],{},"    public class SmtNewsFilterBot : Robot\n",[183,246,248],{"class":185,"line":247},11,[183,249,250],{},"    {\n",[183,252,254],{"class":185,"line":253},12,[183,255,256],{},"        [Parameter(\"News: currencies (comma-separated, empty = all)\", DefaultValue = \"EUR,USD\")]\n",[183,258,260],{"class":185,"line":259},13,[183,261,262],{},"        public string NewsCurrencies { get; set; }\n",[183,264,266],{"class":185,"line":265},14,[183,267,220],{"emptyLinePlaceholder":219},[183,269,271],{"class":185,"line":270},15,[183,272,273],{},"        [Parameter(\"News: impact (high | high,medium | all)\", DefaultValue = \"high\")]\n",[183,275,277],{"class":185,"line":276},16,[183,278,279],{},"        public string NewsImpact { get; set; }\n",[183,281,283],{"class":185,"line":282},17,[183,284,220],{"emptyLinePlaceholder":219},[183,286,288],{"class":185,"line":287},18,[183,289,290],{},"        [Parameter(\"News: minutes before\", DefaultValue = 30, MinValue = 0)]\n",[183,292,294],{"class":185,"line":293},19,[183,295,296],{},"        public int MinutesBefore { get; set; }\n",[183,298,300],{"class":185,"line":299},20,[183,301,220],{"emptyLinePlaceholder":219},[183,303,305],{"class":185,"line":304},21,[183,306,307],{},"        [Parameter(\"News: minutes after\", DefaultValue = 30, MinValue = 0)]\n",[183,309,311],{"class":185,"line":310},22,[183,312,313],{},"        public int MinutesAfter { get; set; }\n",[183,315,317],{"class":185,"line":316},23,[183,318,220],{"emptyLinePlaceholder":219},[183,320,322],{"class":185,"line":321},24,[183,323,324],{},"        private const string CalendarWs = \"ws:\u002F\u002Fshowmytrades.com:25345\u002F\";\n",[183,326,328],{"class":185,"line":327},25,[183,329,330],{},"        private const string CalendarHttp = \"https:\u002F\u002Fshowmytrades.com\u002Fapi\u002Fpublic\u002Fcalendar\u002Fv1?\";\n",[183,332,334],{"class":185,"line":333},26,[183,335,336],{},"        private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n",[183,338,340],{"class":185,"line":339},27,[183,341,220],{"emptyLinePlaceholder":219},[183,343,345],{"class":185,"line":344},28,[183,346,347],{},"        private readonly List\u003CKeyValuePair\u003CDateTime, string>> _events = new List\u003CKeyValuePair\u003CDateTime, string>>();\n",[183,349,351],{"class":185,"line":350},29,[183,352,353],{},"        private WebSocketClient _ws;\n",[183,355,357],{"class":185,"line":356},30,[183,358,359],{},"        private bool _received;\n",[183,361,363],{"class":185,"line":362},31,[183,364,220],{"emptyLinePlaceholder":219},[183,366,368],{"class":185,"line":367},32,[183,369,370],{},"        protected override void OnStart()\n",[183,372,374],{"class":185,"line":373},33,[183,375,376],{},"        {\n",[183,378,380],{"class":185,"line":379},34,[183,381,382],{},"            DownloadCalendar();\n",[183,384,386],{"class":185,"line":385},35,[183,387,388],{},"            Timer.Start(TimeSpan.FromMinutes(15));\n",[183,390,392],{"class":185,"line":391},36,[183,393,394],{},"        }\n",[183,396,398],{"class":185,"line":397},37,[183,399,220],{"emptyLinePlaceholder":219},[183,401,403],{"class":185,"line":402},38,[183,404,405],{},"        protected override void OnTimer()\n",[183,407,409],{"class":185,"line":408},39,[183,410,376],{},[183,412,414],{"class":185,"line":413},40,[183,415,382],{},[183,417,419],{"class":185,"line":418},41,[183,420,394],{},[183,422,424],{"class":185,"line":423},42,[183,425,220],{"emptyLinePlaceholder":219},[183,427,429],{"class":185,"line":428},43,[183,430,431],{},"        protected override void OnTick()\n",[183,433,435],{"class":185,"line":434},44,[183,436,376],{},[183,438,440],{"class":185,"line":439},45,[183,441,442],{},"            string reason;\n",[183,444,446],{"class":185,"line":445},46,[183,447,448],{},"            if (IsNewsTime(out reason))\n",[183,450,452],{"class":185,"line":451},47,[183,453,454],{},"            {\n",[183,456,458],{"class":185,"line":457},48,[183,459,460],{},"                \u002F\u002F Sin entradas nuevas durante la ventana de noticias. Gestiona las posiciones abiertas como prefieras.\n",[183,462,464],{"class":185,"line":463},49,[183,465,466],{},"                return;\n",[183,468,470],{"class":185,"line":469},50,[183,471,472],{},"            }\n",[183,474,476],{"class":185,"line":475},51,[183,477,220],{"emptyLinePlaceholder":219},[183,479,481],{"class":185,"line":480},52,[183,482,483],{},"            \u002F\u002F ... tu lógica de entrada ...\n",[183,485,487],{"class":185,"line":486},53,[183,488,394],{},[183,490,492],{"class":185,"line":491},54,[183,493,220],{"emptyLinePlaceholder":219},[183,495,497],{"class":185,"line":496},55,[183,498,499],{},"        protected override void OnStop()\n",[183,501,503],{"class":185,"line":502},56,[183,504,376],{},[183,506,508],{"class":185,"line":507},57,[183,509,510],{},"            CloseSocket();\n",[183,512,514],{"class":185,"line":513},58,[183,515,394],{},[183,517,519],{"class":185,"line":518},59,[183,520,220],{"emptyLinePlaceholder":219},[183,522,524],{"class":185,"line":523},60,[183,525,526],{},"        \u002F\u002F True mientras una publicación cae dentro de la ventana de pausa; reason indica cuál.\n",[183,528,530],{"class":185,"line":529},61,[183,531,532],{},"        \u002F\u002F Server.TimeInUtc es el reloj UTC del propio cTrader: la plataforma conoce el desfase del bróker,\n",[183,534,536],{"class":185,"line":535},62,[183,537,538],{},"        \u002F\u002F así que se puede comparar con el calendario sin problema (ParseCsv lo contrasta con el servidor).\n",[183,540,542],{"class":185,"line":541},63,[183,543,544],{},"        public bool IsNewsTime(out string reason)\n",[183,546,548],{"class":185,"line":547},64,[183,549,376],{},[183,551,553],{"class":185,"line":552},65,[183,554,555],{},"            DateTime now = Server.TimeInUtc;\n",[183,557,559],{"class":185,"line":558},66,[183,560,561],{},"            foreach (KeyValuePair\u003CDateTime, string> e in _events)\n",[183,563,565],{"class":185,"line":564},67,[183,566,454],{},[183,568,570],{"class":185,"line":569},68,[183,571,572],{},"                if (now >= e.Key.AddMinutes(-MinutesBefore) && now \u003C= e.Key.AddMinutes(MinutesAfter))\n",[183,574,576],{"class":185,"line":575},69,[183,577,578],{},"                {\n",[183,580,582],{"class":185,"line":581},70,[183,583,584],{},"                    reason = e.Value;\n",[183,586,588],{"class":185,"line":587},71,[183,589,590],{},"                    return true;\n",[183,592,594],{"class":185,"line":593},72,[183,595,596],{},"                }\n",[183,598,600],{"class":185,"line":599},73,[183,601,472],{},[183,603,605],{"class":185,"line":604},74,[183,606,607],{},"            reason = null;\n",[183,609,611],{"class":185,"line":610},75,[183,612,613],{},"            return false;\n",[183,615,617],{"class":185,"line":616},76,[183,618,394],{},[183,620,622],{"class":185,"line":621},77,[183,623,220],{"emptyLinePlaceholder":219},[183,625,627],{"class":185,"line":626},78,[183,628,629],{},"        private string Query()\n",[183,631,633],{"class":185,"line":632},79,[183,634,376],{},[183,636,638],{"class":185,"line":637},80,[183,639,640],{},"            string q = \"format=csv&days=2&impact=\" + NewsImpact;\n",[183,642,644],{"class":185,"line":643},81,[183,645,646],{},"            if (!string.IsNullOrWhiteSpace(NewsCurrencies)) q += \"&currency=\" + NewsCurrencies.Replace(\" \", \"\");\n",[183,648,650],{"class":185,"line":649},82,[183,651,652],{},"            return q;\n",[183,654,656],{"class":185,"line":655},83,[183,657,394],{},[183,659,661],{"class":185,"line":660},84,[183,662,220],{"emptyLinePlaceholder":219},[183,664,666],{"class":185,"line":665},85,[183,667,668],{},"        private void DownloadCalendar()\n",[183,670,672],{"class":185,"line":671},86,[183,673,376],{},[183,675,677],{"class":185,"line":676},87,[183,678,679],{},"            if (RunningMode != RunningMode.RealTime) return; \u002F\u002F el backtester no tiene red\n",[183,681,683],{"class":185,"line":682},88,[183,684,220],{"emptyLinePlaceholder":219},[183,686,688],{"class":185,"line":687},89,[183,689,690],{},"            \u002F\u002F cTrader Cloud bloquea el HTTP de los cBots y solo permite un WebSocket en el puerto 25345,\n",[183,692,694],{"class":185,"line":693},90,[183,695,696],{},"            \u002F\u002F así que el socket es la vía principal en todas partes; HTTP es el plan B en el escritorio.\n",[183,698,700],{"class":185,"line":699},91,[183,701,702],{},"            try\n",[183,704,706],{"class":185,"line":705},92,[183,707,454],{},[183,709,711],{"class":185,"line":710},93,[183,712,713],{},"                CloseSocket();\n",[183,715,717],{"class":185,"line":716},94,[183,718,719],{},"                _received = false;\n",[183,721,723],{"class":185,"line":722},95,[183,724,725],{},"                _ws = new WebSocketClient(new WebSocketClientOptions { Timeout = TimeSpan.FromSeconds(15) });\n",[183,727,729],{"class":185,"line":728},96,[183,730,731],{},"                _ws.Connected += OnSocketConnected;\n",[183,733,735],{"class":185,"line":734},97,[183,736,737],{},"                _ws.TextReceived += OnSocketText;\n",[183,739,741],{"class":185,"line":740},98,[183,742,743],{},"                _ws.Disconnected += OnSocketDisconnected;\n",[183,745,747],{"class":185,"line":746},99,[183,748,749],{},"                _ws.Connect(new Uri(CalendarWs));\n",[183,751,753],{"class":185,"line":752},100,[183,754,472],{},[183,756,758],{"class":185,"line":757},101,[183,759,760],{},"            catch (Exception ex)\n",[183,762,764],{"class":185,"line":763},102,[183,765,454],{},[183,767,769],{"class":185,"line":768},103,[183,770,771],{},"                Print(\"[SMT] calendar socket failed: {0}\", ex.Message);\n",[183,773,775],{"class":185,"line":774},104,[183,776,777],{},"                DownloadOverHttp();\n",[183,779,781],{"class":185,"line":780},105,[183,782,472],{},[183,784,786],{"class":185,"line":785},106,[183,787,394],{},[183,789,791],{"class":185,"line":790},107,[183,792,220],{"emptyLinePlaceholder":219},[183,794,796],{"class":185,"line":795},108,[183,797,798],{},"        private void OnSocketConnected(WebSocketClientConnectedEventArgs args)\n",[183,800,802],{"class":185,"line":801},109,[183,803,376],{},[183,805,807],{"class":185,"line":806},110,[183,808,809],{},"            try { _ws.Send(Query()); }\n",[183,811,813],{"class":185,"line":812},111,[183,814,815],{},"            catch (Exception ex) { Print(\"[SMT] calendar send failed: {0}\", ex.Message); }\n",[183,817,819],{"class":185,"line":818},112,[183,820,394],{},[183,822,824],{"class":185,"line":823},113,[183,825,220],{"emptyLinePlaceholder":219},[183,827,829],{"class":185,"line":828},114,[183,830,831],{},"        private void OnSocketText(WebSocketClientTextReceivedEventArgs args)\n",[183,833,835],{"class":185,"line":834},115,[183,836,376],{},[183,838,840],{"class":185,"line":839},116,[183,841,842],{},"            _received = true;\n",[183,844,846],{"class":185,"line":845},117,[183,847,848],{},"            if (args.Text.StartsWith(\"Error:\")) Print(\"[SMT] calendar: {0}\", args.Text);\n",[183,850,852],{"class":185,"line":851},118,[183,853,854],{},"            else ParseCsv(args.Text);\n",[183,856,858],{"class":185,"line":857},119,[183,859,510],{},[183,861,863],{"class":185,"line":862},120,[183,864,394],{},[183,866,868],{"class":185,"line":867},121,[183,869,220],{"emptyLinePlaceholder":219},[183,871,873],{"class":185,"line":872},122,[183,874,875],{},"        private void OnSocketDisconnected(WebSocketClientDisconnectEventArgs args)\n",[183,877,879],{"class":185,"line":878},123,[183,880,376],{},[183,882,884],{"class":185,"line":883},124,[183,885,886],{},"            if (!_received) DownloadOverHttp(); \u002F\u002F solo en el escritorio: la nube responde 503\n",[183,888,890],{"class":185,"line":889},125,[183,891,394],{},[183,893,895],{"class":185,"line":894},126,[183,896,220],{"emptyLinePlaceholder":219},[183,898,900],{"class":185,"line":899},127,[183,901,902],{},"        private void DownloadOverHttp()\n",[183,904,906],{"class":185,"line":905},128,[183,907,376],{},[183,909,911],{"class":185,"line":910},129,[183,912,702],{},[183,914,916],{"class":185,"line":915},130,[183,917,454],{},[183,919,921],{"class":185,"line":920},131,[183,922,923],{},"                HttpResponse r = Http.Get(CalendarHttp + Query());\n",[183,925,927],{"class":185,"line":926},132,[183,928,929],{},"                if (r.IsSuccessful) ParseCsv(r.Body);\n",[183,931,933],{"class":185,"line":932},133,[183,934,935],{},"                else Print(\"[SMT] calendar HTTP failed: {0}\", r.StatusCode);\n",[183,937,939],{"class":185,"line":938},134,[183,940,472],{},[183,942,944],{"class":185,"line":943},135,[183,945,946],{},"            catch (Exception ex) { Print(\"[SMT] calendar HTTP failed: {0}\", ex.Message); }\n",[183,948,950],{"class":185,"line":949},136,[183,951,394],{},[183,953,955],{"class":185,"line":954},137,[183,956,220],{"emptyLinePlaceholder":219},[183,958,960],{"class":185,"line":959},138,[183,961,962],{},"        private void CloseSocket()\n",[183,964,966],{"class":185,"line":965},139,[183,967,376],{},[183,969,971],{"class":185,"line":970},140,[183,972,973],{},"            WebSocketClient ws = _ws;\n",[183,975,977],{"class":185,"line":976},141,[183,978,979],{},"            _ws = null;\n",[183,981,983],{"class":185,"line":982},142,[183,984,985],{},"            if (ws == null) return;\n",[183,987,989],{"class":185,"line":988},143,[183,990,991],{},"            try { ws.Connected -= OnSocketConnected; ws.TextReceived -= OnSocketText; ws.Disconnected -= OnSocketDisconnected; } catch (Exception) { }\n",[183,993,995],{"class":185,"line":994},144,[183,996,997],{},"            try { if (ws.State == WebSocketClientState.Open) ws.Close(WebSocketClientCloseStatus.NormalClosure, \"done\"); } catch (Exception) { }\n",[183,999,1001],{"class":185,"line":1000},145,[183,1002,1003],{},"            try { ws.Dispose(); } catch (Exception) { }\n",[183,1005,1007],{"class":185,"line":1006},146,[183,1008,394],{},[183,1010,1012],{"class":185,"line":1011},147,[183,1013,220],{"emptyLinePlaceholder":219},[183,1015,1017],{"class":185,"line":1016},148,[183,1018,1019],{},"        \u002F\u002F CSV: ts,time,currency,impact,forecast,previous,actual,title (title al final, puede ir entre comillas),\n",[183,1021,1023],{"class":185,"line":1022},149,[183,1024,1025],{},"        \u002F\u002F y una última línea \"# server_time=\u003Cunix> server_time_iso=\u003Ciso>\" con el reloj UTC del servidor.\n",[183,1027,1029],{"class":185,"line":1028},150,[183,1030,1031],{},"        private void ParseCsv(string csv)\n",[183,1033,1035],{"class":185,"line":1034},151,[183,1036,376],{},[183,1038,1040],{"class":185,"line":1039},152,[183,1041,1042],{},"            List\u003CKeyValuePair\u003CDateTime, string>> list = new List\u003CKeyValuePair\u003CDateTime, string>>();\n",[183,1044,1046],{"class":185,"line":1045},153,[183,1047,1048],{},"            foreach (string raw in csv.Split('\\n'))\n",[183,1050,1052],{"class":185,"line":1051},154,[183,1053,454],{},[183,1055,1057],{"class":185,"line":1056},155,[183,1058,1059],{},"                string line = raw.TrimEnd('\\r');\n",[183,1061,1063],{"class":185,"line":1062},156,[183,1064,1065],{},"                if (line.Length == 0 || line.StartsWith(\"ts,\")) continue;\n",[183,1067,1069],{"class":185,"line":1068},157,[183,1070,1071],{},"                if (line.StartsWith(\"# server_time=\"))\n",[183,1073,1075],{"class":185,"line":1074},158,[183,1076,578],{},[183,1078,1080],{"class":185,"line":1079},159,[183,1081,1082],{},"                    long serverTime;\n",[183,1084,1086],{"class":185,"line":1085},160,[183,1087,1088],{},"                    if (long.TryParse(line.Substring(14, 10), out serverTime))\n",[183,1090,1092],{"class":185,"line":1091},161,[183,1093,1094],{},"                    {\n",[183,1096,1098],{"class":185,"line":1097},162,[183,1099,1100],{},"                        double skew = (Server.TimeInUtc - Epoch.AddSeconds(serverTime)).TotalSeconds;\n",[183,1102,1104],{"class":185,"line":1103},163,[183,1105,1106],{},"                        if (Math.Abs(skew) > 60) Print(\"[SMT] warning: cTrader's UTC clock is {0:F0} s away from the calendar server\", skew);\n",[183,1108,1110],{"class":185,"line":1109},164,[183,1111,1112],{},"                    }\n",[183,1114,1116],{"class":185,"line":1115},165,[183,1117,1118],{},"                    continue;\n",[183,1120,1122],{"class":185,"line":1121},166,[183,1123,596],{},[183,1125,1127],{"class":185,"line":1126},167,[183,1128,1129],{},"                string[] f = line.Split(',');\n",[183,1131,1133],{"class":185,"line":1132},168,[183,1134,1135],{},"                if (f.Length \u003C 8) continue;\n",[183,1137,1139],{"class":185,"line":1138},169,[183,1140,1141],{},"                long ts;\n",[183,1143,1145],{"class":185,"line":1144},170,[183,1146,1147],{},"                if (!long.TryParse(f[0], out ts)) continue;\n",[183,1149,1151],{"class":185,"line":1150},171,[183,1152,1153],{},"                DateTime at = Epoch.AddSeconds(ts);\n",[183,1155,1157],{"class":185,"line":1156},172,[183,1158,1159],{},"                string title = AfterComma(line, 7).Replace(\"\\\"\\\"\", \"\\\"\");\n",[183,1161,1163],{"class":185,"line":1162},173,[183,1164,1165],{},"                if (title.Length >= 2 && title[0] == '\"') title = title.Substring(1, title.Length - 2);\n",[183,1167,1169],{"class":185,"line":1168},174,[183,1170,1171],{},"                list.Add(new KeyValuePair\u003CDateTime, string>(at, f[2] + \" \" + f[3] + \": \" + title));\n",[183,1173,1175],{"class":185,"line":1174},175,[183,1176,472],{},[183,1178,1180],{"class":185,"line":1179},176,[183,1181,1182],{},"            _events.Clear();\n",[183,1184,1186],{"class":185,"line":1185},177,[183,1187,1188],{},"            _events.AddRange(list);\n",[183,1190,1192],{"class":185,"line":1191},178,[183,1193,1194],{},"            Print(\"[SMT] calendar: {0} events loaded\", _events.Count);\n",[183,1196,1198],{"class":185,"line":1197},179,[183,1199,394],{},[183,1201,1203],{"class":185,"line":1202},180,[183,1204,220],{"emptyLinePlaceholder":219},[183,1206,1208],{"class":185,"line":1207},181,[183,1209,1210],{},"        private static string AfterComma(string line, int n)\n",[183,1212,1214],{"class":185,"line":1213},182,[183,1215,376],{},[183,1217,1219],{"class":185,"line":1218},183,[183,1220,1221],{},"            int pos = -1;\n",[183,1223,1225],{"class":185,"line":1224},184,[183,1226,1227],{},"            for (int i = 0; i \u003C n; i++)\n",[183,1229,1231],{"class":185,"line":1230},185,[183,1232,454],{},[183,1234,1236],{"class":185,"line":1235},186,[183,1237,1238],{},"                pos = line.IndexOf(',', pos + 1);\n",[183,1240,1242],{"class":185,"line":1241},187,[183,1243,1244],{},"                if (pos \u003C 0) return \"\";\n",[183,1246,1248],{"class":185,"line":1247},188,[183,1249,472],{},[183,1251,1253],{"class":185,"line":1252},189,[183,1254,1255],{},"            return line.Substring(pos + 1);\n",[183,1257,1259],{"class":185,"line":1258},190,[183,1260,394],{},[183,1262,1264],{"class":185,"line":1263},191,[183,1265,1266],{},"    }\n",[183,1268,1270],{"class":185,"line":1269},192,[183,1271,1272],{},"}\n",[18,1274,1275,1276,1279,1280,1283,1284,1287,1288,1291,1292,1295],{},"Para incorporar el filtro a un cBot que ya tienes, copia en él todo lo que va de ",[31,1277,1278],{},"CalendarWs"," hasta ",[31,1281,1282],{},"AfterComma()",", llama a ",[31,1285,1286],{},"DownloadCalendar()"," desde ",[31,1289,1290],{},"OnStart()"," y desde un temporizador de 15 min, y comprueba ",[31,1293,1294],{},"IsNewsTime()"," antes de cada entrada.",[53,1297,1299],{"id":1298},"cómo-probarlo","🧪 Cómo probarlo",[1301,1302,1303,1311,1327],"ol",{},[1304,1305,1306,1307,1310],"li",{},"Compila el cBot y agrega una instancia a un gráfico (Add instance). En pocos segundos el registro debería mostrar ",[31,1308,1309],{},"[SMT] calendar: N events loaded",". Un cero en un fin de semana tranquilo es lo normal.",[1304,1312,1313,1314,1318,1319,1322,1323,1326],{},"Pon ",[1315,1316,1317],"em",{},"News: impact"," en ",[31,1320,1321],{},"all"," y ",[1315,1324,1325],{},"minutes before"," en un número alto para ver cómo actúa el filtro; después vuelve a los valores reales.",[1304,1328,1329,1330,1333],{},"Ejecuta la misma instancia ",[22,1331,1332],{},"en la nube",": la línea del registro es la misma. Para eso existe el socket.",[53,1335,1337],{"id":1336},"️-los-horarios-y-el-backtester","⏱️ Los horarios y el backtester",[18,1339,1340,1341,1344,1345,1348,1349,1351,1352,1354],{},"Las marcas de tiempo del calendario están en UTC y el código las compara con ",[31,1342,1343],{},"Server.TimeInUtc",", así que ni la zona horaria del bróker ni el horario de verano entran en juego. Además, cada descarga termina con el reloj UTC del propio servidor (",[31,1346,1347],{},"# server_time=…","): el cBot compara ",[31,1350,1343],{}," con ese valor y escribe un aviso si los dos difieren en más de un minuto, de modo que un reloj desajustado no pasa inadvertido. El backtester y el optimizador no tienen red: ahí ",[31,1353,1286],{}," sale antes de tiempo y el filtro no hace nada. Para hacer el backtest de un filtro de noticias hace falta un archivo de eventos pasados que viaje con el cBot, y eso es otro trabajo.",[53,1356,1358],{"id":1357},"️-ajuste-fino","🎛️ Ajuste fino",[1360,1361,1362,1372,1383],"ul",{},[1304,1363,1364,1365,1368,1369,1371],{},"Las ",[22,1366,1367],{},"posiciones abiertas"," no se tocan. Usa ",[31,1370,1294],{}," para decidir qué hacer con ellas: cerrarlas, mover el stop o dejarlas como están.",[1304,1373,1374,1375,1378,1379,1382],{},"El ",[22,1376,1377],{},"impacto medio"," (",[31,1380,1381],{},"high,medium",") triplica los eventos. Los scalpers suelen quererlo; los sistemas swing, rara vez.",[1304,1384,1385,1388],{},[22,1386,1387],{},"Varios cBots en una misma cuenta"," descargan cada uno por su lado: diez cBots son 40 solicitudes pequeñas por hora, muy por debajo del límite de 60 mensajes por minuto y por dirección IP en el socket.",[53,1390,1392],{"id":1391},"preguntas-frecuentes","❓ Preguntas frecuentes",[18,1394,1395,1398],{},[22,1396,1397],{},"¿Por qué un WebSocket y no HTTPS?","\nPorque cTrader Cloud no deja que un cBot haga solicitudes HTTP y solo permite conexiones WebSocket en el puerto 25345. En el escritorio el mismo cBot también funcionaría por HTTPS, y el código recurre a él si el socket no logra conectarse.",[18,1400,1401,1407,1408,38],{},[22,1402,1403,1404,1406],{},"¿Es seguro enviar por ",[31,1405,157],{}," sin cifrado?","\nLa solicitud lleva cuatro parámetros (divisas, impacto, días, formato) y nada sobre ti ni sobre tu cuenta; la respuesta es un calendario público. No hay nada que proteger. Los mismos datos están disponibles por HTTPS en el ",[47,1409,1410],{"href":49},"endpoint de la API",[18,1412,1413,1420,1421,1322,1424,1427,1428,1430],{},[22,1414,1415,1416,1419],{},"¿Necesita ",[31,1417,1418],{},"AccessRights.FullAccess","?","\nNo. ",[31,1422,1423],{},"WebSocketClient",[31,1425,1426],{},"Http"," están disponibles con ",[31,1429,134],{},", que además es lo que exige la cTrader Store.",[18,1432,1433,1436,1437,1441],{},[22,1434,1435],{},"¿Hace falta una cuenta de ShowMyTrades?","\nNo. Si además quieres publicar los resultados del cBot, ",[47,1438,1440],{"href":1439},"\u002Fguides\u002Fshow-ctrader-results-on-your-website","conectar una cuenta de cTrader"," es otra cosa y requiere una sola autorización OAuth.",[53,1443,1445],{"id":1444},"artículos-relacionados","🔗 Artículos relacionados",[1360,1447,1448,1454,1461,1466],{},[1304,1449,1450,1453],{},[47,1451,1452],{"href":49},"Referencia de la API del calendario económico",": el protocolo WebSocket, el endpoint HTTP y los formatos JSON y CSV",[1304,1455,1456,1460],{},[47,1457,1459],{"href":1458},"\u002Fguides\u002Fadd-a-news-filter-to-your-mt4-expert-advisor","Cómo agregar un filtro de noticias a tu Expert Advisor de MT4",": el mismo filtro en MQL",[1304,1462,1463],{},[47,1464,1465],{"href":1439},"Cómo mostrar tus resultados de cTrader en tu web",[1304,1467,1468],{},[47,1469,1471],{"href":1470},"\u002Fglossary\u002Fwhat-is-ctrader","Qué es cTrader",[1473,1474],"hr",{},[18,1476,1477,1478,1482],{},"El socket es la única parte fuera de lo común, y ya está escrita. El resto es tu estrategia, con una forma menos de perder dinero alrededor de las noticias. ",[47,1479,1481],{"href":1480},"\u002Fsignup","Haz el seguimiento gratuito de la cuenta"," y verás la diferencia en el gráfico de drawdown.",[1484,1485,1486],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":66,"searchDepth":192,"depth":192,"links":1488},[1489,1490,1491,1492,1493,1494,1495],{"id":55,"depth":192,"text":56},{"id":165,"depth":192,"text":166},{"id":1298,"depth":192,"text":1299},{"id":1336,"depth":192,"text":1337},{"id":1357,"depth":192,"text":1358},{"id":1391,"depth":192,"text":1392},{"id":1444,"depth":192,"text":1445},"2026-10-17T00:00:00.000Z","cTrader no ofrece a los cBots ninguna API de calendario, y cTrader Cloud bloquea las solicitudes HTTP. Aquí tienes un filtro de noticias que funciona en el escritorio y en la nube, con un calendario gratuito por WebSocket y el código C# completo.",false,"md",{"src":1501},"\u002Fimages\u002Fblog\u002Fadd-a-news-filter-to-a-ctrader-cbot-hero.svg",{},"\u002Fes\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"title":5,"description":1497},"es\u002F3.guides\u002F43.add-a-news-filter-to-a-ctrader-cbot",null,"YqPWXMjQ3gm1G1NRgQGnG1E2RH70apZ_5Zgct_VsL3k",[1509,1514],{"title":1510,"path":1511,"stem":1512,"description":1513,"children":-1},"Filtro de noticias en MT5: ¿calendario integrado o API?","\u002Fes\u002Fguides\u002Fnews-filter-mt5-expert-advisor-built-in-calendar-or-api","es\u002F3.guides\u002F42.news-filter-mt5-expert-advisor-built-in-calendar-or-api","MetaTrader 5 incluye un calendario económico que los Expert Advisors pueden leer sin descargar nada. Cuándo basta, cuándo no y cómo usar en su lugar una API de calendario gratuita, con código MQL5 para las dos opciones.",{"title":1515,"path":1516,"stem":1517,"description":1518,"children":-1},"Una API de calendario económico gratuita para bots de trading","\u002Fes\u002Fguides\u002Ffree-economic-calendar-api-for-trading-bots","es\u002F3.guides\u002F44.free-economic-calendar-api-for-trading-bots","De dónde puede sacar un robot de trading el calendario económico: las fuentes que usa la gente, qué falla en cada una y una API gratuita y sin clave pensada para consultas periódicas desde Expert Advisors, cBots, Python y JavaScript.",[1520,1522,1524,1525,1527],{"path":1521},"\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1523},"\u002Fde\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1503},{"path":1526},"\u002Fja\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1528},"\u002Fzh\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",1794154056500]