안녕하세요, 트레이더 여러분! 요즘 원유와 브렌트에 대한 EA(Expert Advisor)를 개발하고 있는데요, 이 과정에서 ForexFactory.com에서 '원유 재고' 보고서의 정확한 날짜와 시간을 가져오는 방법을 공유하려고 합니다. 이 보고서는 보통 수요일 오전 10시 30분에 발표되지만, 휴일이 있으면 발표일이 달라질 수 있습니다. 제 EA에 중요한 보고서이기 때문에, 정확한 발표일을 확인하기 위해 온라인 서비스를 체크할 수밖에 없었습니다.
1. 웹사이트 설정하기
먼저, OPTIONS | EXPERT ADVISOR 탭에서 WebRequest를 사용할 웹사이트를 추가해야 합니다. 사용할 주소는 'http://www.forexfactory.com/'입니다. 아래 이미지를 참고하세요.

2. 이벤트 구조 정의하기
다음으로, 코드 상단에 이벤트를 저장할 구조체를 정의해야 합니다. 여기서 'DailyEvents'를 전역 변수로 선언하고, 저장할 최대 이벤트 수를 'MaxDailyEvents' 변수로 정합니다.
// 이벤트 구조체 정의 struct EVENTS { string time; string title; string currency; bool displayed; }; #define MaxDailyEvents 20 // 20개 이상의 고강도 이벤트가 있을 것으로 예상되면 이 숫자를 늘리세요. EVENTS DailyEvents[MaxDailyEvents];
3. HTML 코드 가져오기 및 파싱하기
이제 ForexFactory.com에서 HTML 코드를 가져와야 합니다. HTML 코드에 익숙하지 않더라도 걱정하지 마세요, 제가 차근차근 설명해 드릴게요 :)
먼저, WebRequest를 위한 URL을 구성해야 합니다. 오늘 날짜의 캘린더만 필요하므로 'day' 파라미터를 오늘 날짜로 설정하고 요청을 보냅니다.
string url="http://www.forexfactory.com/calendar.php?day="; url += MthName(Month()) + DoubleToStr(Day(), 0) + "." + DoubleToStr(Year(), 0);
그 다음 요청을 보내고, 오류 코드를 확인합니다(만약 있다면). 반환된 문자 배열을 문자열로 변환하여 HTML 코드를 더 쉽게 파싱할 수 있습니다.
// 웹 요청 보내기 ResetLastError(); res = WebRequest("GET", url, cookie, NULL, timeout, post, 0, result, headers); // 오류 확인 if(res == -1) { Print("웹 요청에서 오류 발생. 오류 코드 = ", GetLastError()); MessageBox("'http://forexfactory.com/' 주소를 허용된 URL 목록에 추가하세요." , "오류", MB_ICONINFORMATION); return(false); }
4. 이벤트 데이터 파싱하기
오류가 없다면, 문자 배열 'result'를 문자열로 변환해 파싱을 시작합니다. HTML에서 오늘 날짜에 해당하는 데이터를 찾고, 필요한 HTML 태그의 값을 가져오는 'GetHTMLElement' 함수를 사용합니다.
// 캘린더가 오늘 날짜인지 확인 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);
5. 이벤트 출력하기
이제 모든 테이블 행을 파싱하고, 필요한 이벤트 정보를 'DailyEvents' 구조체에 추가합니다. 마지막으로 차트에 이벤트를 표시합니다. 만약 이벤트가 미래에 있다면, 수직선을 표시하고, 과거라면 표시하지 않습니다.
// 고강도 이벤트 표시하기 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; }
6. 알림 설정하기
미래의 이벤트가 있을 경우, 현재 시간으로부터 5분 이내에 다가오는 이벤트에 대해 사용자에게 알림을 보내고 수직선을 제거하는 코드를 'start()' 함수에 추가해야 합니다.
//+------------------------------------------------------------------+ //| EA 시작 함수 | //+------------------------------------------------------------------+ 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); } }
7. 초기화 함수 추가하기
마지막으로, 매일 이벤트를 가져오는 코드를 OnInit() 함수에 추가해야 합니다.
//+------------------------------------------------------------------+ //| EA 초기화 함수 | //+------------------------------------------------------------------+ int OnInit() { // 오늘의 이벤트 가져오기 GetHighImpactEvents(); return(INIT_SUCCEEDED); }
이렇게 간단하게 설정할 수 있습니다. 물론 이 코드를 수정하여 모든 이벤트를 표시하거나, 표시할 영향을 지정하는 입력 파라미터를 추가할 수도 있습니다. 자정을 체크하여 새로운 일일 이벤트 목록을 가져오는 체크도 추가할 수 있는데, 그 부분은 여러분이 직접 실험해보세요 :)
감사합니다!
- 클로드.