[{"data":1,"prerenderedAt":1538},["ShallowReactive",2],{"\u002Fja\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot":3,"\u002Fja\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot-surround":1517,"\u002Fja\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot-langs":1528},{"id":4,"title":5,"authors":6,"badge":12,"body":14,"date":1505,"description":1506,"draft":1507,"extension":1508,"image":1509,"meta":1511,"navigation":222,"path":1512,"seo":1513,"stem":1514,"updated":1515,"__hash__":1516},"posts\u002Fja\u002F3.guides\u002F43.add-a-news-filter-to-a-ctrader-cbot.md","cTrader cBotにニュースフィルターを組み込む方法（Cloud対応）",[7],{"name":8,"to":9,"avatar":10},"ShowMyTrades Team","https:\u002F\u002Fshowmytrades.com",{"src":11},"\u002Ficon-512.png",{"label":13},"自動売買",{"type":15,"value":16,"toc":1496},"minimark",[17,39,52,57,67,152,166,170,177,1276,1299,1303,1336,1340,1357,1361,1389,1393,1398,1401,1409,1416,1424,1438,1443,1451,1455,1481,1484,1492],[18,19,20,21,25,26,29,30,34,35,38],"p",{},"cBotにニュースフィルターを入れようとすると、MetaTraderの開発者なら出会わない壁に2つぶつかります。1つ目は、cTrader Automate APIに",[22,23,24],"strong",{},"経済指標カレンダーがない","ことです。プラットフォームの画面にはカレンダーが表示されるのに、cBotからは読み取れません。2つ目は、cBotを",[22,27,28],{},"cTrader Cloud","で動かす人が大半なのに、そこではcBotからHTTPリクエストを一切送れないことです。",[31,32,33],"code",{},"Http.Get()","を呼んでも、返ってくるのはステータス",[31,36,37],{},"503","です。",[18,40,41,42,45,46,51],{},"ただし、クラウドでも1つだけ許されている通信があります。",[22,43,44],{},"ポート25345のWebSocket","です。無料の",[47,48,50],"a",{"href":49},"\u002Fdocs\u002Fapi\u002Feconomic-calendar","ShowMyTradesの経済指標カレンダー","は、この経路でcBotにデータを届けます。このガイドでは、考え方、プロトコル、そのままコンパイルできるcBotまで、必要なものをすべてまとめました。",[53,54,56],"h2",{"id":55},"仕組み","🧭 仕組み",[58,59,64],"pre",{"className":60,"code":62,"language":63},[61],"language-text","cBot (desktop or cloud)                      ShowMyTrades\n┌──────────────────────────┐                 ┌─────────────────────────┐\n│ every 15 min:            │  ws:\u002F\u002F…:25345   │ same calendar as        │\n│  open socket, send       │ ──────────────► │ \u002Fapi\u002Fpublic\u002Fcalendar\u002Fv1 │\n│  \"currency=EUR,USD&      │                 │ cached, filtered,       │\n│   impact=high&days=2&    │ ◄────────────── │ a few kilobytes         │\n│   format=csv\"            │   CSV text      └─────────────────────────┘\n│  parse, close            │\n│ every 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,117,125,136,144],"tbody",{},[74,85,86,90],{},[87,88,89],"td",{},"接続先",[87,91,92],{},[31,93,94],{},"ws:\u002F\u002Fshowmytrades.com:25345\u002F",[74,96,97,100],{},[87,98,99],{},"送信するもの",[87,101,102,103,106,107,106,110,106,113,116],{},"HTTP APIと同じパラメーター（",[31,104,105],{},"currency","、",[31,108,109],{},"impact",[31,111,112],{},"days",[31,114,115],{},"format","）を書いた1行のテキスト",[74,118,119,122],{},[87,120,121],{},"返ってくるもの",[87,123,124],{},"CSV（またはJSON）の本文が入ったテキストフレーム1つ",[74,126,127,130],{},[87,128,129],{},"アクセス権限",[87,131,132,135],{},[31,133,134],{},"AccessRights.None","で足りるため、cBotを公開できます",[74,137,138,141],{},[87,139,140],{},"キー・会員登録",[87,142,143],{},"不要",[74,145,146,149],{},[87,147,148],{},"デスクトップでの代替経路",[87,150,151],{},"デスクトップでは通常のHTTPも使えます。以下のコードは、ソケットが失敗したときにHTTPへ切り替えます",[18,153,154,155,158,159,162,163,165],{},"ソケットは",[31,156,157],{},"wss:\u002F\u002F","ではなく",[31,160,161],{},"ws:\u002F\u002F","を使います。cTrader Cloudが接続に使うのが",[31,164,161],{},"だからです。この通信でやり取りされるのは4つのパラメーターだけで、利用者や口座に関する情報は一切含まれません。",[53,167,169],{"id":168},"cbot本体","📄 cBot本体",[18,171,172,173,176],{},"cTrader AutomateでcBotを新規作成し、コードを以下にまるごと置き換えてビルドします。デスクトップではNuGetの",[31,174,175],{},"cTrader.Automate","を参照してコンパイルされ、クラウドではそのまま動きます。",[58,178,183],{"className":179,"code":180,"filename":181,"language":182,"meta":66,"style":66},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F cTrader用ニュースフィルター（デスクトップ・クラウド対応）。データはShowMyTrades提供、無料、キー不要。\n\u002F\u002F ドキュメント：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 ニュースの時間帯は新規エントリーしない。保有ポジションの扱いはお好みで。\n                return;\n            }\n\n            \u002F\u002F ... エントリーのロジック ...\n        }\n\n        protected override void OnStop()\n        {\n            CloseSocket();\n        }\n\n        \u002F\u002F 指標発表が停止時間帯に入っている間はtrue。reasonにどの指標かが入る。\n        \u002F\u002F Server.TimeInUtcはcTrader自身のUTC時計。プラットフォームがブローカーの時差を把握しているので、\n        \u002F\u002F カレンダーとそのまま比較してよい（ParseCsvがサーバーの時刻と照合する）。\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 バックテスターではネットワークが使えない\n\n            \u002F\u002F cTrader CloudはcBotからのHTTPを遮断し、ポート25345のWebSocketしか許可しない。\n            \u002F\u002F そのため、どこでもソケットを第一の経路とし、デスクトップではHTTPを予備にする。\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 デスクトップのみ（クラウドは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は最後の列で、引用符付きの場合あり）。\n        \u002F\u002F 最終行は \"# server_time=\u003Cunix> server_time_iso=\u003Ciso>\" で、サーバーのUTC時計を示す。\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,184,185,193,199,205,211,217,224,230,236,242,248,254,260,266,271,277,283,288,294,300,305,311,317,322,328,334,340,345,351,357,363,368,374,380,386,392,398,403,409,414,419,424,429,435,440,446,452,458,464,470,476,481,487,492,497,503,508,514,519,524,530,536,542,548,553,559,565,570,576,582,588,594,600,605,611,617,622,627,633,638,644,650,656,661,666,672,677,683,688,694,700,706,711,717,723,729,735,741,747,753,758,764,769,775,781,786,791,796,802,807,813,819,824,829,835,840,846,852,858,863,868,873,879,884,890,895,900,906,911,916,921,927,933,939,944,950,955,960,966,971,977,983,989,995,1001,1007,1012,1017,1023,1029,1035,1040,1046,1052,1057,1063,1069,1075,1080,1086,1092,1098,1104,1110,1116,1122,1127,1133,1139,1145,1151,1157,1163,1169,1175,1180,1186,1192,1198,1203,1208,1214,1219,1225,1231,1236,1242,1248,1253,1259,1264,1270],{"__ignoreMap":66},[186,187,190],"span",{"class":188,"line":189},"line",1,[186,191,192],{},"\u002F\u002F cTrader用ニュースフィルター（デスクトップ・クラウド対応）。データはShowMyTrades提供、無料、キー不要。\n",[186,194,196],{"class":188,"line":195},2,[186,197,198],{},"\u002F\u002F ドキュメント：https:\u002F\u002Fshowmytrades.com\u002Fdocs\u002Fapi\u002Feconomic-calendar\n",[186,200,202],{"class":188,"line":201},3,[186,203,204],{},"using System;\n",[186,206,208],{"class":188,"line":207},4,[186,209,210],{},"using System.Collections.Generic;\n",[186,212,214],{"class":188,"line":213},5,[186,215,216],{},"using cAlgo.API;\n",[186,218,220],{"class":188,"line":219},6,[186,221,223],{"emptyLinePlaceholder":222},true,"\n",[186,225,227],{"class":188,"line":226},7,[186,228,229],{},"namespace cAlgo.Robots\n",[186,231,233],{"class":188,"line":232},8,[186,234,235],{},"{\n",[186,237,239],{"class":188,"line":238},9,[186,240,241],{},"    [Robot(AccessRights = AccessRights.None, TimeZone = TimeZones.UTC)]\n",[186,243,245],{"class":188,"line":244},10,[186,246,247],{},"    public class SmtNewsFilterBot : Robot\n",[186,249,251],{"class":188,"line":250},11,[186,252,253],{},"    {\n",[186,255,257],{"class":188,"line":256},12,[186,258,259],{},"        [Parameter(\"News: currencies (comma-separated, empty = all)\", DefaultValue = \"EUR,USD\")]\n",[186,261,263],{"class":188,"line":262},13,[186,264,265],{},"        public string NewsCurrencies { get; set; }\n",[186,267,269],{"class":188,"line":268},14,[186,270,223],{"emptyLinePlaceholder":222},[186,272,274],{"class":188,"line":273},15,[186,275,276],{},"        [Parameter(\"News: impact (high | high,medium | all)\", DefaultValue = \"high\")]\n",[186,278,280],{"class":188,"line":279},16,[186,281,282],{},"        public string NewsImpact { get; set; }\n",[186,284,286],{"class":188,"line":285},17,[186,287,223],{"emptyLinePlaceholder":222},[186,289,291],{"class":188,"line":290},18,[186,292,293],{},"        [Parameter(\"News: minutes before\", DefaultValue = 30, MinValue = 0)]\n",[186,295,297],{"class":188,"line":296},19,[186,298,299],{},"        public int MinutesBefore { get; set; }\n",[186,301,303],{"class":188,"line":302},20,[186,304,223],{"emptyLinePlaceholder":222},[186,306,308],{"class":188,"line":307},21,[186,309,310],{},"        [Parameter(\"News: minutes after\", DefaultValue = 30, MinValue = 0)]\n",[186,312,314],{"class":188,"line":313},22,[186,315,316],{},"        public int MinutesAfter { get; set; }\n",[186,318,320],{"class":188,"line":319},23,[186,321,223],{"emptyLinePlaceholder":222},[186,323,325],{"class":188,"line":324},24,[186,326,327],{},"        private const string CalendarWs = \"ws:\u002F\u002Fshowmytrades.com:25345\u002F\";\n",[186,329,331],{"class":188,"line":330},25,[186,332,333],{},"        private const string CalendarHttp = \"https:\u002F\u002Fshowmytrades.com\u002Fapi\u002Fpublic\u002Fcalendar\u002Fv1?\";\n",[186,335,337],{"class":188,"line":336},26,[186,338,339],{},"        private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n",[186,341,343],{"class":188,"line":342},27,[186,344,223],{"emptyLinePlaceholder":222},[186,346,348],{"class":188,"line":347},28,[186,349,350],{},"        private readonly List\u003CKeyValuePair\u003CDateTime, string>> _events = new List\u003CKeyValuePair\u003CDateTime, string>>();\n",[186,352,354],{"class":188,"line":353},29,[186,355,356],{},"        private WebSocketClient _ws;\n",[186,358,360],{"class":188,"line":359},30,[186,361,362],{},"        private bool _received;\n",[186,364,366],{"class":188,"line":365},31,[186,367,223],{"emptyLinePlaceholder":222},[186,369,371],{"class":188,"line":370},32,[186,372,373],{},"        protected override void OnStart()\n",[186,375,377],{"class":188,"line":376},33,[186,378,379],{},"        {\n",[186,381,383],{"class":188,"line":382},34,[186,384,385],{},"            DownloadCalendar();\n",[186,387,389],{"class":188,"line":388},35,[186,390,391],{},"            Timer.Start(TimeSpan.FromMinutes(15));\n",[186,393,395],{"class":188,"line":394},36,[186,396,397],{},"        }\n",[186,399,401],{"class":188,"line":400},37,[186,402,223],{"emptyLinePlaceholder":222},[186,404,406],{"class":188,"line":405},38,[186,407,408],{},"        protected override void OnTimer()\n",[186,410,412],{"class":188,"line":411},39,[186,413,379],{},[186,415,417],{"class":188,"line":416},40,[186,418,385],{},[186,420,422],{"class":188,"line":421},41,[186,423,397],{},[186,425,427],{"class":188,"line":426},42,[186,428,223],{"emptyLinePlaceholder":222},[186,430,432],{"class":188,"line":431},43,[186,433,434],{},"        protected override void OnTick()\n",[186,436,438],{"class":188,"line":437},44,[186,439,379],{},[186,441,443],{"class":188,"line":442},45,[186,444,445],{},"            string reason;\n",[186,447,449],{"class":188,"line":448},46,[186,450,451],{},"            if (IsNewsTime(out reason))\n",[186,453,455],{"class":188,"line":454},47,[186,456,457],{},"            {\n",[186,459,461],{"class":188,"line":460},48,[186,462,463],{},"                \u002F\u002F ニュースの時間帯は新規エントリーしない。保有ポジションの扱いはお好みで。\n",[186,465,467],{"class":188,"line":466},49,[186,468,469],{},"                return;\n",[186,471,473],{"class":188,"line":472},50,[186,474,475],{},"            }\n",[186,477,479],{"class":188,"line":478},51,[186,480,223],{"emptyLinePlaceholder":222},[186,482,484],{"class":188,"line":483},52,[186,485,486],{},"            \u002F\u002F ... エントリーのロジック ...\n",[186,488,490],{"class":188,"line":489},53,[186,491,397],{},[186,493,495],{"class":188,"line":494},54,[186,496,223],{"emptyLinePlaceholder":222},[186,498,500],{"class":188,"line":499},55,[186,501,502],{},"        protected override void OnStop()\n",[186,504,506],{"class":188,"line":505},56,[186,507,379],{},[186,509,511],{"class":188,"line":510},57,[186,512,513],{},"            CloseSocket();\n",[186,515,517],{"class":188,"line":516},58,[186,518,397],{},[186,520,522],{"class":188,"line":521},59,[186,523,223],{"emptyLinePlaceholder":222},[186,525,527],{"class":188,"line":526},60,[186,528,529],{},"        \u002F\u002F 指標発表が停止時間帯に入っている間はtrue。reasonにどの指標かが入る。\n",[186,531,533],{"class":188,"line":532},61,[186,534,535],{},"        \u002F\u002F Server.TimeInUtcはcTrader自身のUTC時計。プラットフォームがブローカーの時差を把握しているので、\n",[186,537,539],{"class":188,"line":538},62,[186,540,541],{},"        \u002F\u002F カレンダーとそのまま比較してよい（ParseCsvがサーバーの時刻と照合する）。\n",[186,543,545],{"class":188,"line":544},63,[186,546,547],{},"        public bool IsNewsTime(out string reason)\n",[186,549,551],{"class":188,"line":550},64,[186,552,379],{},[186,554,556],{"class":188,"line":555},65,[186,557,558],{},"            DateTime now = Server.TimeInUtc;\n",[186,560,562],{"class":188,"line":561},66,[186,563,564],{},"            foreach (KeyValuePair\u003CDateTime, string> e in _events)\n",[186,566,568],{"class":188,"line":567},67,[186,569,457],{},[186,571,573],{"class":188,"line":572},68,[186,574,575],{},"                if (now >= e.Key.AddMinutes(-MinutesBefore) && now \u003C= e.Key.AddMinutes(MinutesAfter))\n",[186,577,579],{"class":188,"line":578},69,[186,580,581],{},"                {\n",[186,583,585],{"class":188,"line":584},70,[186,586,587],{},"                    reason = e.Value;\n",[186,589,591],{"class":188,"line":590},71,[186,592,593],{},"                    return true;\n",[186,595,597],{"class":188,"line":596},72,[186,598,599],{},"                }\n",[186,601,603],{"class":188,"line":602},73,[186,604,475],{},[186,606,608],{"class":188,"line":607},74,[186,609,610],{},"            reason = null;\n",[186,612,614],{"class":188,"line":613},75,[186,615,616],{},"            return false;\n",[186,618,620],{"class":188,"line":619},76,[186,621,397],{},[186,623,625],{"class":188,"line":624},77,[186,626,223],{"emptyLinePlaceholder":222},[186,628,630],{"class":188,"line":629},78,[186,631,632],{},"        private string Query()\n",[186,634,636],{"class":188,"line":635},79,[186,637,379],{},[186,639,641],{"class":188,"line":640},80,[186,642,643],{},"            string q = \"format=csv&days=2&impact=\" + NewsImpact;\n",[186,645,647],{"class":188,"line":646},81,[186,648,649],{},"            if (!string.IsNullOrWhiteSpace(NewsCurrencies)) q += \"&currency=\" + NewsCurrencies.Replace(\" \", \"\");\n",[186,651,653],{"class":188,"line":652},82,[186,654,655],{},"            return q;\n",[186,657,659],{"class":188,"line":658},83,[186,660,397],{},[186,662,664],{"class":188,"line":663},84,[186,665,223],{"emptyLinePlaceholder":222},[186,667,669],{"class":188,"line":668},85,[186,670,671],{},"        private void DownloadCalendar()\n",[186,673,675],{"class":188,"line":674},86,[186,676,379],{},[186,678,680],{"class":188,"line":679},87,[186,681,682],{},"            if (RunningMode != RunningMode.RealTime) return; \u002F\u002F バックテスターではネットワークが使えない\n",[186,684,686],{"class":188,"line":685},88,[186,687,223],{"emptyLinePlaceholder":222},[186,689,691],{"class":188,"line":690},89,[186,692,693],{},"            \u002F\u002F cTrader CloudはcBotからのHTTPを遮断し、ポート25345のWebSocketしか許可しない。\n",[186,695,697],{"class":188,"line":696},90,[186,698,699],{},"            \u002F\u002F そのため、どこでもソケットを第一の経路とし、デスクトップではHTTPを予備にする。\n",[186,701,703],{"class":188,"line":702},91,[186,704,705],{},"            try\n",[186,707,709],{"class":188,"line":708},92,[186,710,457],{},[186,712,714],{"class":188,"line":713},93,[186,715,716],{},"                CloseSocket();\n",[186,718,720],{"class":188,"line":719},94,[186,721,722],{},"                _received = false;\n",[186,724,726],{"class":188,"line":725},95,[186,727,728],{},"                _ws = new WebSocketClient(new WebSocketClientOptions { Timeout = TimeSpan.FromSeconds(15) });\n",[186,730,732],{"class":188,"line":731},96,[186,733,734],{},"                _ws.Connected += OnSocketConnected;\n",[186,736,738],{"class":188,"line":737},97,[186,739,740],{},"                _ws.TextReceived += OnSocketText;\n",[186,742,744],{"class":188,"line":743},98,[186,745,746],{},"                _ws.Disconnected += OnSocketDisconnected;\n",[186,748,750],{"class":188,"line":749},99,[186,751,752],{},"                _ws.Connect(new Uri(CalendarWs));\n",[186,754,756],{"class":188,"line":755},100,[186,757,475],{},[186,759,761],{"class":188,"line":760},101,[186,762,763],{},"            catch (Exception ex)\n",[186,765,767],{"class":188,"line":766},102,[186,768,457],{},[186,770,772],{"class":188,"line":771},103,[186,773,774],{},"                Print(\"[SMT] calendar socket failed: {0}\", ex.Message);\n",[186,776,778],{"class":188,"line":777},104,[186,779,780],{},"                DownloadOverHttp();\n",[186,782,784],{"class":188,"line":783},105,[186,785,475],{},[186,787,789],{"class":188,"line":788},106,[186,790,397],{},[186,792,794],{"class":188,"line":793},107,[186,795,223],{"emptyLinePlaceholder":222},[186,797,799],{"class":188,"line":798},108,[186,800,801],{},"        private void OnSocketConnected(WebSocketClientConnectedEventArgs args)\n",[186,803,805],{"class":188,"line":804},109,[186,806,379],{},[186,808,810],{"class":188,"line":809},110,[186,811,812],{},"            try { _ws.Send(Query()); }\n",[186,814,816],{"class":188,"line":815},111,[186,817,818],{},"            catch (Exception ex) { Print(\"[SMT] calendar send failed: {0}\", ex.Message); }\n",[186,820,822],{"class":188,"line":821},112,[186,823,397],{},[186,825,827],{"class":188,"line":826},113,[186,828,223],{"emptyLinePlaceholder":222},[186,830,832],{"class":188,"line":831},114,[186,833,834],{},"        private void OnSocketText(WebSocketClientTextReceivedEventArgs args)\n",[186,836,838],{"class":188,"line":837},115,[186,839,379],{},[186,841,843],{"class":188,"line":842},116,[186,844,845],{},"            _received = true;\n",[186,847,849],{"class":188,"line":848},117,[186,850,851],{},"            if (args.Text.StartsWith(\"Error:\")) Print(\"[SMT] calendar: {0}\", args.Text);\n",[186,853,855],{"class":188,"line":854},118,[186,856,857],{},"            else ParseCsv(args.Text);\n",[186,859,861],{"class":188,"line":860},119,[186,862,513],{},[186,864,866],{"class":188,"line":865},120,[186,867,397],{},[186,869,871],{"class":188,"line":870},121,[186,872,223],{"emptyLinePlaceholder":222},[186,874,876],{"class":188,"line":875},122,[186,877,878],{},"        private void OnSocketDisconnected(WebSocketClientDisconnectEventArgs args)\n",[186,880,882],{"class":188,"line":881},123,[186,883,379],{},[186,885,887],{"class":188,"line":886},124,[186,888,889],{},"            if (!_received) DownloadOverHttp(); \u002F\u002F デスクトップのみ（クラウドは503を返す）\n",[186,891,893],{"class":188,"line":892},125,[186,894,397],{},[186,896,898],{"class":188,"line":897},126,[186,899,223],{"emptyLinePlaceholder":222},[186,901,903],{"class":188,"line":902},127,[186,904,905],{},"        private void DownloadOverHttp()\n",[186,907,909],{"class":188,"line":908},128,[186,910,379],{},[186,912,914],{"class":188,"line":913},129,[186,915,705],{},[186,917,919],{"class":188,"line":918},130,[186,920,457],{},[186,922,924],{"class":188,"line":923},131,[186,925,926],{},"                HttpResponse r = Http.Get(CalendarHttp + Query());\n",[186,928,930],{"class":188,"line":929},132,[186,931,932],{},"                if (r.IsSuccessful) ParseCsv(r.Body);\n",[186,934,936],{"class":188,"line":935},133,[186,937,938],{},"                else Print(\"[SMT] calendar HTTP failed: {0}\", r.StatusCode);\n",[186,940,942],{"class":188,"line":941},134,[186,943,475],{},[186,945,947],{"class":188,"line":946},135,[186,948,949],{},"            catch (Exception ex) { Print(\"[SMT] calendar HTTP failed: {0}\", ex.Message); }\n",[186,951,953],{"class":188,"line":952},136,[186,954,397],{},[186,956,958],{"class":188,"line":957},137,[186,959,223],{"emptyLinePlaceholder":222},[186,961,963],{"class":188,"line":962},138,[186,964,965],{},"        private void CloseSocket()\n",[186,967,969],{"class":188,"line":968},139,[186,970,379],{},[186,972,974],{"class":188,"line":973},140,[186,975,976],{},"            WebSocketClient ws = _ws;\n",[186,978,980],{"class":188,"line":979},141,[186,981,982],{},"            _ws = null;\n",[186,984,986],{"class":188,"line":985},142,[186,987,988],{},"            if (ws == null) return;\n",[186,990,992],{"class":188,"line":991},143,[186,993,994],{},"            try { ws.Connected -= OnSocketConnected; ws.TextReceived -= OnSocketText; ws.Disconnected -= OnSocketDisconnected; } catch (Exception) { }\n",[186,996,998],{"class":188,"line":997},144,[186,999,1000],{},"            try { if (ws.State == WebSocketClientState.Open) ws.Close(WebSocketClientCloseStatus.NormalClosure, \"done\"); } catch (Exception) { }\n",[186,1002,1004],{"class":188,"line":1003},145,[186,1005,1006],{},"            try { ws.Dispose(); } catch (Exception) { }\n",[186,1008,1010],{"class":188,"line":1009},146,[186,1011,397],{},[186,1013,1015],{"class":188,"line":1014},147,[186,1016,223],{"emptyLinePlaceholder":222},[186,1018,1020],{"class":188,"line":1019},148,[186,1021,1022],{},"        \u002F\u002F CSV：ts,time,currency,impact,forecast,previous,actual,title（titleは最後の列で、引用符付きの場合あり）。\n",[186,1024,1026],{"class":188,"line":1025},149,[186,1027,1028],{},"        \u002F\u002F 最終行は \"# server_time=\u003Cunix> server_time_iso=\u003Ciso>\" で、サーバーのUTC時計を示す。\n",[186,1030,1032],{"class":188,"line":1031},150,[186,1033,1034],{},"        private void ParseCsv(string csv)\n",[186,1036,1038],{"class":188,"line":1037},151,[186,1039,379],{},[186,1041,1043],{"class":188,"line":1042},152,[186,1044,1045],{},"            List\u003CKeyValuePair\u003CDateTime, string>> list = new List\u003CKeyValuePair\u003CDateTime, string>>();\n",[186,1047,1049],{"class":188,"line":1048},153,[186,1050,1051],{},"            foreach (string raw in csv.Split('\\n'))\n",[186,1053,1055],{"class":188,"line":1054},154,[186,1056,457],{},[186,1058,1060],{"class":188,"line":1059},155,[186,1061,1062],{},"                string line = raw.TrimEnd('\\r');\n",[186,1064,1066],{"class":188,"line":1065},156,[186,1067,1068],{},"                if (line.Length == 0 || line.StartsWith(\"ts,\")) continue;\n",[186,1070,1072],{"class":188,"line":1071},157,[186,1073,1074],{},"                if (line.StartsWith(\"# server_time=\"))\n",[186,1076,1078],{"class":188,"line":1077},158,[186,1079,581],{},[186,1081,1083],{"class":188,"line":1082},159,[186,1084,1085],{},"                    long serverTime;\n",[186,1087,1089],{"class":188,"line":1088},160,[186,1090,1091],{},"                    if (long.TryParse(line.Substring(14, 10), out serverTime))\n",[186,1093,1095],{"class":188,"line":1094},161,[186,1096,1097],{},"                    {\n",[186,1099,1101],{"class":188,"line":1100},162,[186,1102,1103],{},"                        double skew = (Server.TimeInUtc - Epoch.AddSeconds(serverTime)).TotalSeconds;\n",[186,1105,1107],{"class":188,"line":1106},163,[186,1108,1109],{},"                        if (Math.Abs(skew) > 60) Print(\"[SMT] warning: cTrader's UTC clock is {0:F0} s away from the calendar server\", skew);\n",[186,1111,1113],{"class":188,"line":1112},164,[186,1114,1115],{},"                    }\n",[186,1117,1119],{"class":188,"line":1118},165,[186,1120,1121],{},"                    continue;\n",[186,1123,1125],{"class":188,"line":1124},166,[186,1126,599],{},[186,1128,1130],{"class":188,"line":1129},167,[186,1131,1132],{},"                string[] f = line.Split(',');\n",[186,1134,1136],{"class":188,"line":1135},168,[186,1137,1138],{},"                if (f.Length \u003C 8) continue;\n",[186,1140,1142],{"class":188,"line":1141},169,[186,1143,1144],{},"                long ts;\n",[186,1146,1148],{"class":188,"line":1147},170,[186,1149,1150],{},"                if (!long.TryParse(f[0], out ts)) continue;\n",[186,1152,1154],{"class":188,"line":1153},171,[186,1155,1156],{},"                DateTime at = Epoch.AddSeconds(ts);\n",[186,1158,1160],{"class":188,"line":1159},172,[186,1161,1162],{},"                string title = AfterComma(line, 7).Replace(\"\\\"\\\"\", \"\\\"\");\n",[186,1164,1166],{"class":188,"line":1165},173,[186,1167,1168],{},"                if (title.Length >= 2 && title[0] == '\"') title = title.Substring(1, title.Length - 2);\n",[186,1170,1172],{"class":188,"line":1171},174,[186,1173,1174],{},"                list.Add(new KeyValuePair\u003CDateTime, string>(at, f[2] + \" \" + f[3] + \": \" + title));\n",[186,1176,1178],{"class":188,"line":1177},175,[186,1179,475],{},[186,1181,1183],{"class":188,"line":1182},176,[186,1184,1185],{},"            _events.Clear();\n",[186,1187,1189],{"class":188,"line":1188},177,[186,1190,1191],{},"            _events.AddRange(list);\n",[186,1193,1195],{"class":188,"line":1194},178,[186,1196,1197],{},"            Print(\"[SMT] calendar: {0} events loaded\", _events.Count);\n",[186,1199,1201],{"class":188,"line":1200},179,[186,1202,397],{},[186,1204,1206],{"class":188,"line":1205},180,[186,1207,223],{"emptyLinePlaceholder":222},[186,1209,1211],{"class":188,"line":1210},181,[186,1212,1213],{},"        private static string AfterComma(string line, int n)\n",[186,1215,1217],{"class":188,"line":1216},182,[186,1218,379],{},[186,1220,1222],{"class":188,"line":1221},183,[186,1223,1224],{},"            int pos = -1;\n",[186,1226,1228],{"class":188,"line":1227},184,[186,1229,1230],{},"            for (int i = 0; i \u003C n; i++)\n",[186,1232,1234],{"class":188,"line":1233},185,[186,1235,457],{},[186,1237,1239],{"class":188,"line":1238},186,[186,1240,1241],{},"                pos = line.IndexOf(',', pos + 1);\n",[186,1243,1245],{"class":188,"line":1244},187,[186,1246,1247],{},"                if (pos \u003C 0) return \"\";\n",[186,1249,1251],{"class":188,"line":1250},188,[186,1252,475],{},[186,1254,1256],{"class":188,"line":1255},189,[186,1257,1258],{},"            return line.Substring(pos + 1);\n",[186,1260,1262],{"class":188,"line":1261},190,[186,1263,397],{},[186,1265,1267],{"class":188,"line":1266},191,[186,1268,1269],{},"    }\n",[186,1271,1273],{"class":188,"line":1272},192,[186,1274,1275],{},"}\n",[18,1277,1278,1279,1282,1283,1286,1287,1290,1291,1294,1295,1298],{},"既存のcBotにフィルターを組み込むときは、",[31,1280,1281],{},"CalendarWs","から",[31,1284,1285],{},"AfterComma()","までをすべてコピーして貼り付けます。あとは",[31,1288,1289],{},"OnStart()","と15分ごとのタイマーから",[31,1292,1293],{},"DownloadCalendar()","を呼び出し、エントリーの前に毎回",[31,1296,1297],{},"IsNewsTime()","を確認するだけです。",[53,1300,1302],{"id":1301},"動作を確認する","🧪 動作を確認する",[1304,1305,1306,1314,1329],"ol",{},[1307,1308,1309,1310,1313],"li",{},"cBotをビルドし、チャートにインスタンスを追加します。数秒以内に、ログへ",[31,1311,1312],{},"[SMT] calendar: N events loaded","と表示されるはずです。指標の少ない週末なら0件でも正常です。",[1307,1315,1316,1320,1321,1324,1325,1328],{},[1317,1318,1319],"em",{},"News: impact","を",[31,1322,1323],{},"all","に、",[1317,1326,1327],{},"minutes before","を大きな数値にすると、フィルターが作動する様子を確認できます。確認が済んだら本来の値に戻してください。",[1307,1330,1331,1332,1335],{},"同じインスタンスを",[22,1333,1334],{},"クラウドで","動かします。ログの出力は同じです。ソケットを使う意味は、まさにここにあります。",[53,1337,1339],{"id":1338},"️-時刻の扱いとバックテスト","⏱️ 時刻の扱いとバックテスト",[18,1341,1342,1343,1346,1347,1350,1351,1353,1354,1356],{},"カレンダーのタイムスタンプはUTCで、コードはこれを",[31,1344,1345],{},"Server.TimeInUtc","と比べます。そのため、ブローカーのタイムゾーンや夏時間を考える必要はありません。ダウンロードしたデータの末尾には、毎回サーバー自身のUTC時刻（",[31,1348,1349],{},"# server_time=…","）も付いています。cBotは",[31,1352,1345],{},"をこの時刻と照合し、ずれが1分を超えると警告を出力します。時計が狂っていれば、必ず気付けます。バックテストと最適化ではネットワークが使えないため、",[31,1355,1293],{},"はすぐに処理を終え、フィルターは何もしません。ニュースフィルターをバックテストするには、過去の指標をまとめたファイルをcBotに同梱する必要があり、これは別の作業になります。",[53,1358,1360],{"id":1359},"️-調整のポイント","🎛️ 調整のポイント",[1362,1363,1364,1373,1383],"ul",{},[1307,1365,1366,1369,1370,1372],{},[22,1367,1368],{},"保有ポジション","には手を付けません。決済する、ストップを追従させる、そのままにする、といった扱いは",[31,1371,1297],{},"を使って自分で決めてください。",[1307,1374,1375,1378,1379,1382],{},[22,1376,1377],{},"重要度「中」","（",[31,1380,1381],{},"high,medium","）まで含めると、対象の指標は3倍になります。スキャルパーは含めたがることが多く、スイング系のシステムではあまり使いません。",[1307,1384,1385,1388],{},[22,1386,1387],{},"1つの口座で複数のcBotを動かす場合","、ダウンロードはcBotごとに行われます。10本動かしても1時間に40回の小さなリクエストで、IPアドレスごとに毎分60メッセージというソケットの上限を大きく下回ります。",[53,1390,1392],{"id":1391},"よくある質問","❓ よくある質問",[18,1394,1395],{},[22,1396,1397],{},"なぜHTTPSではなくWebSocketなのですか？",[18,1399,1400],{},"cTrader CloudではcBotからHTTPリクエストを送れず、WebSocket接続もポート25345でしか許可されていないからです。デスクトップなら同じcBotがHTTPSでも動きます。ソケットに接続できなかった場合、コードはHTTPSに切り替えます。",[18,1402,1403],{},[22,1404,1405,1406,1408],{},"暗号化のない",[31,1407,161],{},"で送っても安全ですか？",[18,1410,1411,1412,1415],{},"送るのは4つのパラメーター（通貨、重要度、日数、形式）だけで、利用者や口座の情報は含まれません。返ってくるのも公開されているカレンダーです。守るべきものは何もありません。同じデータは",[47,1413,1414],{"href":49},"APIエンドポイント","からHTTPSでも取得できます。",[18,1417,1418],{},[22,1419,1420,1423],{},[31,1421,1422],{},"AccessRights.FullAccess","は必要ですか？",[18,1425,1426,1427,1430,1431,1434,1435,1437],{},"不要です。",[31,1428,1429],{},"WebSocketClient","と",[31,1432,1433],{},"Http","は",[31,1436,134],{},"で使えます。cTrader Storeで求められるのも、この設定です。",[18,1439,1440],{},[22,1441,1442],{},"ShowMyTradesへの登録は必要ですか？",[18,1444,1445,1446,1450],{},"不要です。cBotの成績も公開したい場合は、別途",[47,1447,1449],{"href":1448},"\u002Fguides\u002Fshow-ctrader-results-on-your-website","cTrader口座の連携","が必要です。OAuthの認可を1回行うだけで済みます。",[53,1452,1454],{"id":1453},"関連ページ","🔗 関連ページ",[1362,1456,1457,1463,1470,1475],{},[1307,1458,1459,1462],{},[47,1460,1461],{"href":49},"経済指標カレンダーAPIリファレンス","：WebSocketのプロトコル、HTTPエンドポイント、JSONとCSVの形式",[1307,1464,1465,1469],{},[47,1466,1468],{"href":1467},"\u002Fguides\u002Fadd-a-news-filter-to-your-mt4-expert-advisor","MT4のEA（エキスパートアドバイザー）向けニュースフィルター","：同じフィルターのMQL版",[1307,1471,1472],{},[47,1473,1474],{"href":1448},"cTraderの成績を自分のウェブサイトに表示する",[1307,1476,1477],{},[47,1478,1480],{"href":1479},"\u002Fglossary\u002Fwhat-is-ctrader","cTraderとは",[1482,1483],"hr",{},[18,1485,1486,1487,1491],{},"特殊なのはソケットの部分だけで、そこはもう書き上がっています。残りは自分の手法です。ニュースの前後で損をする原因が、これで1つ減ります。",[47,1488,1490],{"href":1489},"\u002Fsignup","口座を無料で記録する","と、その違いがドローダウンのチャートに表れます。",[1493,1494,1495],"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":195,"depth":195,"links":1497},[1498,1499,1500,1501,1502,1503,1504],{"id":55,"depth":195,"text":56},{"id":168,"depth":195,"text":169},{"id":1301,"depth":195,"text":1302},{"id":1338,"depth":195,"text":1339},{"id":1359,"depth":195,"text":1360},{"id":1391,"depth":195,"text":1392},{"id":1453,"depth":195,"text":1454},"2026-10-17T00:00:00.000Z","cTraderにはcBotから読める経済指標カレンダーのAPIがなく、cTrader CloudではHTTPリクエストも送れません。デスクトップでもクラウドでも動くニュースフィルターを、WebSocketで取得する無料のカレンダーとC#の全コードつきで紹介します。",false,"md",{"src":1510},"\u002Fimages\u002Fblog\u002Fadd-a-news-filter-to-a-ctrader-cbot-hero.svg",{},"\u002Fja\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"title":5,"description":1506},"ja\u002F3.guides\u002F43.add-a-news-filter-to-a-ctrader-cbot",null,"mcTwDYN9G_KqOfAb8JrdgsA6SEkth0D-7HfVM7rHOfg",[1518,1523],{"title":1519,"path":1520,"stem":1521,"description":1522,"children":-1},"MT5のニュースフィルター：内蔵カレンダーとAPI、どちらを使う？","\u002Fja\u002Fguides\u002Fnews-filter-mt5-expert-advisor-built-in-calendar-or-api","ja\u002F3.guides\u002F42.news-filter-mt5-expert-advisor-built-in-calendar-or-api","MetaTrader 5には、EAがダウンロードなしで読み取れる経済指標カレンダーが内蔵されています。それで足りる場合と足りない場合、そして代わりに無料のカレンダーAPIを使う方法を、両方のMQL5コード付きで解説します。",{"title":1524,"path":1525,"stem":1526,"description":1527,"children":-1},"自動売買ボット向けの無料経済指標カレンダーAPI","\u002Fja\u002Fguides\u002Ffree-economic-calendar-api-for-trading-bots","ja\u002F3.guides\u002F44.free-economic-calendar-api-for-trading-bots","自動売買のロボットは、経済指標カレンダーをどこから取得すればよいのか。よく使われる取得元とそれぞれの弱点、そしてEA（エキスパートアドバイザー）、cBot、Python、JavaScriptからの定期取得を前提に作った、キー不要の無料APIを紹介します。",[1529,1531,1533,1535,1536],{"path":1530},"\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1532},"\u002Fde\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1534},"\u002Fes\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",{"path":1512},{"path":1537},"\u002Fzh\u002Fguides\u002Fadd-a-news-filter-to-a-ctrader-cbot",1794154046115]