こんにちは、トレーダーの皆さん!今日は、クルードオイルとブレント用のエキスパートアドバイザー(EA)を開発している私が、ForexFactoryから「クルードオイル在庫」レポートの正確な日付と時間を取得する方法をお伝えします。このレポートは通常、アメリカ東部時間の水曜日午前10時30分に発表されますが、祝日などがあると発表日が変更されることがあります。私のEAには非常に重要な情報なので、オンラインサービスをチェックして発表日を確認する必要があります。
まず最初に、OPTIONS | EXPERT ADVISORタブにWebRequestを行うためのサイトを追加します。使用するサイトは「http://www.forexfactory.com/」です(画像1参照)。

次に、コード内でイベントを格納するための構造体を定義します。これは、コードの上部に配置して、「DailyEvents」をグローバル変数として宣言し、格納するイベントの最大数は「MaxDailyEvents」変数で定義します。
// イベント構造体を定義します struct EVENTS { string time; string title; string currency; bool displayed; }; #define MaxDailyEvents 20 // もし20以上の高インパクトイベントがある場合は、この数を増やしてください。 EVENTS DailyEvents[MaxDailyEvents];
次に、ForexFactoryからHTMLコードを取得して解析する必要があります。HTMLコードがよくわからない方もご安心ください。ステップバイステップで説明しますね!
まず、WebRequestのURLを構築します。今日はデフォルト(全週)ではなく、その日のカレンダーが欲しいので、リクエストの「day」パラメーターを今日の日付に設定してリクエストを送信します。
string url="http://www.forexfactory.com/calendar.php?day="; url += MthName(Month()) + DoubleToStr(Day(), 0) + "." + DoubleToStr(Year(), 0);
次にリクエストを送信し、エラーコードをチェックします(あれば)。戻り値の文字配列を文字列に変換します。これにより、HTMLコードを解析しやすくなります。
// Webリクエストを送信します ResetLastError(); res = WebRequest("GET", url, cookie, NULL, timeout, post, 0, result, headers); // エラーをチェックします if(res == -1) { Print("WebRequestでエラー。エラーコード = ", GetLastError()); MessageBox("アドレス 'http://forexfactory.com/' を 許可されたURLリストに追加してください" + "タブ 'エキスパートアドバイザー'", "エラー", MB_ICONINFORMATION); return(false); }
エラーがない場合、キャラクター配列「result」を文字列に変換して、解析がしやすくなります。
// キャラクター配列を文字列に変換します HTML = CharArrayToString(result);
これで文字列ができたので、「StringFind」関数を使用してHTML要素を探すことができます。最初に戻ってきたHTMLが本当に今日の日付のものであることを確認し、このHTMLタグの前の部分を切り取ります。関数「GetHTMLElement」を使ってHTMLコードを解析し、指定されたHTMLタグの間の値を返します。以下にその定義を示します。
// カレンダーが読み込まれたら、今日の日付か確認します int i = StringFind(HTML, "<span class=\"date\">"); if(i == -1) return(false); HTML = StringSubstr(HTML, i); string date = GetHTMLElement(HTML, "<span>", "</span>"); if(date != MthName(Month()) + " " + DoubleToStr(Day(), 0)) return(false);
「GetHTMLElement」関数の定義は以下の通りです。
//+------------------------------------------------------------------+ //| HTML要素を抽出します //+------------------------------------------------------------------+ string GetHTMLElement(string HTML, string ElementStart, string ElementEnd) { string data = NULL; // 要素の開始位置と終了位置を見つけます int s = StringFind(HTML, ElementStart) + StringLen(ElementStart); int e = StringFind(StringSubstr(HTML, s), ElementEnd); // 要素の内容を返します if(e != 0) data = StringSubstr(HTML, s, e); return(data); }
さて、カレンダーが今日の日付であることを確認したので、各テーブル行を解析し、必要な要素(イベントの時間、通貨、インパクト、タイトル)を抽出します。これをHTMLコードのテーブル行がなくなるまで、またはMaxDailyEventsに達するまで繰り返します。
各行のデータを抽出したら、それをDailyEvents構造体に追加します。
// 各イベントのテーブル行を取得します lasttime = NULL; cnrt = 0; date = DoubleToStr(Year(), 0) + "." + DoubleToStr(Month(), 0) + "." + DoubleToStr(Day(), 0) + " "; do { // イベント情報を取得します time = GetHTMLElement(HTML, "<td class=\"calendar__cell calendar__time time\">", "</td>"); if(StringFind(time, "<a name=\"upnext\"") == 0) time = GetHTMLElement(time, "class=\"upnext\">", "</span>"); if(StringLen(time) != 0) lasttime = time; if(StringLen(time) == 0) time = lasttime; time = date + time; currency = GetHTMLElement(HTML, "<td class=\"calendar__cell calendar__currency currency\">", "</td>"); impact = GetHTMLElement(HTML, "<span title=\"", "\" class=\""); i = StringFind(impact, " Impact"); if(i != -1) impact = StringSubstr(impact, 0, i); title = GetHTMLElement(HTML, "\"calendar__event-title\">", "</span>"); // これは高インパクトイベントですか? if(StringFind(Symbol(), currency) != -1 && impact == "High") { // 日々のイベント構造体に追加します DailyEvents[cntr].displayed = false; DailyEvents[cntr].time = time; DailyEvents[cntr].title = title; DailyEvents[cntr++].currency = currency; } // HTML文字列を次のテーブル行に切り取ります i = StringFind(HTML, "</tbody> </table> </td> </tr> "); if(i != -1) HTML = StringSubstr(HTML, i+30); if(StringFind(HTML, "</table> <div class=\"foot\">") == 0) i = -1; } while(i != -1 || cntr == MaxDailyEvents);
すべてのテーブル行を解析し、カレンダーの終わりに達したら、イベントをチャートに表示する必要があります。イベントが未来の場合は垂直線を表示し、過去の場合は何も表示しません。
// 高インパクトイベントを表示します(あれば) lasttime = NULL; for(cntr = 0; cntr < MaxDailyEvents; cntr++) { if(StringLen(DailyEvents[cntr].time) == 0) break; // 最後のマーケットと時間が同じでない場合、チャートにイベントマーカーを作成します if(lasttime != DailyEvents[cntr].time) { res = cntr; // もし文字列に「pm」があれば、時間に12時間を加えます if(StringFind(DailyEvents[cntr].time, "pm") != -1) DailyEvents[cntr].time = TimeToStr(StrToTime(DailyEvents[cntr].time) + 43200); if(ObjectCreate(0, Event + cntr, OBJ_EVENT, 0, StrToTime(DailyEvents[cntr].time), 0)) { ObjectSetString(0, Event + cntr, OBJPROP_TEXT, DailyEvents[cntr].title + " (" + DailyEvents[cntr].currency + ")"); ObjectSetInteger(0, Event + cntr, OBJPROP_COLOR, Red); ObjectSetInteger(0, Event + cntr, OBJPROP_WIDTH, 2); ObjectSetInteger(0, Event + cntr, OBJPROP_BACK, true); ObjectSetInteger(0, Event + cntr, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, Event + cntr, OBJPROP_SELECTED, false); ObjectSetInteger(0, Event + cntr, OBJPROP_HIDDEN, true); ObjectSetString(0, Event + cntr, OBJPROP_TOOLTIP, DailyEvents[cntr].title + " (" + DailyEvents[cntr].currency + ")"); } // イベントが未来の場合、垂直線を作成します if(TimeCurrent() < TimeOffset(DailyEvents[cntr].time, 0)) { if(ObjectCreate(0, VLine + cntr, OBJ_VLINE, 0, TimeOffset(DailyEvents[cntr].time, 0), 0)) { ObjectSetInteger(0, VLine + cntr, OBJPROP_COLOR, Red); ObjectSetInteger(0, VLine + cntr, OBJPROP_WIDTH, 1); ObjectSetInteger(0, VLine + cntr, OBJPROP_BACK, true); ObjectSetInteger(0, VLine + cntr, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, VLine + cntr, OBJPROP_SELECTED, false); ObjectSetInteger(0, VLine + cntr, OBJPROP_HIDDEN, true); ObjectSetString(0, VLine + cntr, OBJPROP_TOOLTIP, DailyEvents[cntr].title + " (" + DailyEvents[cntr].currency + ")"); } } else DailyEvents[cntr].displayed = true; } else { title = ObjectGetString(0, Event + res, OBJPROP_TOOLTIP); title += "\n" + DailyEvents[cntr].title + " (" + DailyEvents[cntr].currency + ")"; ObjectSetString(0, Event + res, OBJPROP_TOOLTIP, title); if(TimeCurrent() < TimeOffset(DailyEvents[cntr].time, 0)) ObjectSetString(0, VLine + res, OBJPROP_TOOLTIP, title); } lasttime = DailyEvents[cntr].time; }
未来のイベントについては、イベントまでの5分以内にユーザーに通知し、垂直線を削除したいと思います。これは、EAまたはインジケーターの「start()」関数にいくつかのコードを追加することで実現できます。
//+------------------------------------------------------------------+ //| エキスパートのスタート関数 | //+------------------------------------------------------------------+ void start() { string event = NULL; // 次の5分以内に高インパクトイベントはありますか? for(int i = 0; i < MaxDailyEvents; i++) { if(StringLen(DailyEvents[i].time) == 0) break; if(TimeCurrent() >= StrToTime(DailyEvents[i].time) - 300 && TimeCurrent() < StrToTime(DailyEvents[i].time) && !DailyEvents[i].displayed) { // イベントまで5分... event += DailyEvents[i].title + " (" + DailyEvents[i].currency + "), "; DailyEvents[i].displayed = true; // イベントに関連する垂直線を削除します if(ObjectFind("VLine" + DoubleToStr(i, 0)) >= 0) ObjectDelete("VLine" + DoubleToStr(i, 0)); } } // 表示するものはありますか? if(StringLen(event) != 0) { event += "5分以内です。"; Alert(event); } }
最後に、一日の日々のイベントを取得する必要があります。これは、OnInit()関数に行を追加することで実現できます。
//+------------------------------------------------------------------+ //| エキスパートの初期化関数 | //+------------------------------------------------------------------+ int OnInit() { // 今日のイベントを取得します GetHighImpactEvents(); return(INIT_SUCCEEDED); }
シンプルで簡単ですね。もちろん、通貨ペアのすべてのイベントを表示するようにコードを修正したり、インジケーターやEAに影響を表示するための入力パラメータを追加したり、真夜中のターンアラウンドを取得して新しい日々のイベントのリストを得るためのチェックを追加したりすることもできますが、その辺は皆さんにお任せします!
それでは、トレード頑張ってください!
– クロード