cTrader cBotにニュースフィルターを組み込む方法(Cloud対応)
cBotにニュースフィルターを入れようとすると、MetaTraderの開発者なら出会わない壁に2つぶつかります。1つ目は、cTrader Automate APIに経済指標カレンダーがないことです。プラットフォームの画面にはカレンダーが表示されるのに、cBotからは読み取れません。2つ目は、cBotをcTrader Cloudで動かす人が大半なのに、そこではcBotからHTTPリクエストを一切送れないことです。Http.Get()を呼んでも、返ってくるのはステータス503です。
ただし、クラウドでも1つだけ許されている通信があります。ポート25345のWebSocketです。無料のShowMyTradesの経済指標カレンダーは、この経路でcBotにデータを届けます。このガイドでは、考え方、プロトコル、そのままコンパイルできるcBotまで、必要なものをすべてまとめました。
🧭 仕組み
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() │
└──────────────────────────┘
| 接続先 | ws://showmytrades.com:25345/ |
| 送信するもの | HTTP APIと同じパラメーター(currency、impact、days、format)を書いた1行のテキスト |
| 返ってくるもの | CSV(またはJSON)の本文が入ったテキストフレーム1つ |
| アクセス権限 | AccessRights.Noneで足りるため、cBotを公開できます |
| キー・会員登録 | 不要 |
| デスクトップでの代替経路 | デスクトップでは通常のHTTPも使えます。以下のコードは、ソケットが失敗したときにHTTPへ切り替えます |
ソケットはwss://ではなくws://を使います。cTrader Cloudが接続に使うのがws://だからです。この通信でやり取りされるのは4つのパラメーターだけで、利用者や口座に関する情報は一切含まれません。
📄 cBot本体
cTrader AutomateでcBotを新規作成し、コードを以下にまるごと置き換えてビルドします。デスクトップではNuGetのcTrader.Automateを参照してコンパイルされ、クラウドではそのまま動きます。
// cTrader用ニュースフィルター(デスクトップ・クラウド対応)。データはShowMyTrades提供、無料、キー不要。
// ドキュメント: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 static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
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))
{
// ニュースの時間帯は新規エントリーしない。保有ポジションの扱いはお好みで。
return;
}
// ... エントリーのロジック ...
}
protected override void OnStop()
{
CloseSocket();
}
// 指標発表が停止時間帯に入っている間はtrue。reasonにどの指標かが入る。
// Server.TimeInUtcはcTrader自身のUTC時計。プラットフォームがブローカーの時差を把握しているので、
// カレンダーとそのまま比較してよい(ParseCsvがサーバーの時刻と照合する)。
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 += "¤cy=" + NewsCurrencies.Replace(" ", "");
return q;
}
private void DownloadCalendar()
{
if (RunningMode != RunningMode.RealTime) return; // バックテスターではネットワークが使えない
// cTrader CloudはcBotからのHTTPを遮断し、ポート25345のWebSocketしか許可しない。
// そのため、どこでもソケットを第一の経路とし、デスクトップではHTTPを予備にする。
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(); // デスクトップのみ(クラウドは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は最後の列で、引用符付きの場合あり)。
// 最終行は "# server_time=<unix> server_time_iso=<iso>" で、サーバーのUTC時計を示す。
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;
if (line.StartsWith("# server_time="))
{
long serverTime;
if (long.TryParse(line.Substring(14, 10), out serverTime))
{
double skew = (Server.TimeInUtc - Epoch.AddSeconds(serverTime)).TotalSeconds;
if (Math.Abs(skew) > 60) Print("[SMT] warning: cTrader's UTC clock is {0:F0} s away from the calendar server", skew);
}
continue;
}
string[] f = line.Split(',');
if (f.Length < 8) continue;
long ts;
if (!long.TryParse(f[0], out ts)) continue;
DateTime at = Epoch.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);
}
}
}
既存のcBotにフィルターを組み込むときは、CalendarWsからAfterComma()までをすべてコピーして貼り付けます。あとはOnStart()と15分ごとのタイマーからDownloadCalendar()を呼び出し、エントリーの前に毎回IsNewsTime()を確認するだけです。
🧪 動作を確認する
- cBotをビルドし、チャートにインスタンスを追加します。数秒以内に、ログへ
[SMT] calendar: N events loadedと表示されるはずです。指標の少ない週末なら0件でも正常です。 - News: impactを
allに、minutes beforeを大きな数値にすると、フィルターが作動する様子を確認できます。確認が済んだら本来の値に戻してください。 - 同じインスタンスをクラウドで動かします。ログの出力は同じです。ソケットを使う意味は、まさにここにあります。
⏱️ 時刻の扱いとバックテスト
カレンダーのタイムスタンプはUTCで、コードはこれをServer.TimeInUtcと比べます。そのため、ブローカーのタイムゾーンや夏時間を考える必要はありません。ダウンロードしたデータの末尾には、毎回サーバー自身のUTC時刻(# server_time=…)も付いています。cBotはServer.TimeInUtcをこの時刻と照合し、ずれが1分を超えると警告を出力します。時計が狂っていれば、必ず気付けます。バックテストと最適化ではネットワークが使えないため、DownloadCalendar()はすぐに処理を終え、フィルターは何もしません。ニュースフィルターをバックテストするには、過去の指標をまとめたファイルをcBotに同梱する必要があり、これは別の作業になります。
🎛️ 調整のポイント
- 保有ポジションには手を付けません。決済する、ストップを追従させる、そのままにする、といった扱いは
IsNewsTime()を使って自分で決めてください。 - 重要度「中」(
high,medium)まで含めると、対象の指標は3倍になります。スキャルパーは含めたがることが多く、スイング系のシステムではあまり使いません。 - 1つの口座で複数のcBotを動かす場合、ダウンロードはcBotごとに行われます。10本動かしても1時間に40回の小さなリクエストで、IPアドレスごとに毎分60メッセージというソケットの上限を大きく下回ります。
❓ よくある質問
なぜHTTPSではなくWebSocketなのですか?
cTrader CloudではcBotからHTTPリクエストを送れず、WebSocket接続もポート25345でしか許可されていないからです。デスクトップなら同じcBotがHTTPSでも動きます。ソケットに接続できなかった場合、コードはHTTPSに切り替えます。
暗号化のないws://で送っても安全ですか?
送るのは4つのパラメーター(通貨、重要度、日数、形式)だけで、利用者や口座の情報は含まれません。返ってくるのも公開されているカレンダーです。守るべきものは何もありません。同じデータはAPIエンドポイントからHTTPSでも取得できます。
AccessRights.FullAccessは必要ですか?
不要です。WebSocketClientとHttpはAccessRights.Noneで使えます。cTrader Storeで求められるのも、この設定です。
ShowMyTradesへの登録は必要ですか?
不要です。cBotの成績も公開したい場合は、別途cTrader口座の連携が必要です。OAuthの認可を1回行うだけで済みます。
🔗 関連ページ
- 経済指標カレンダーAPIリファレンス:WebSocketのプロトコル、HTTPエンドポイント、JSONとCSVの形式
- MT4のEA(エキスパートアドバイザー)向けニュースフィルター:同じフィルターのMQL版
- cTraderの成績を自分のウェブサイトに表示する
- cTraderとは
特殊なのはソケットの部分だけで、そこはもう書き上がっています。残りは自分の手法です。ニュースの前後で損をする原因が、これで1つ減ります。口座を無料で記録すると、その違いがドローダウンのチャートに表れます。