문자 알림과 트레이드 리포트 생성 및 발간
들어가며
본 문서는 매매 결과 리포트를 (Expert Advisor, 인디케이터 혹은 스크립트를 사용하여) HTML 파일로 생성하여 FTP를 통해 WWW 서버에 업로드하는 법을 다룹니다. 또한 문자를 통해 핸드폰에 매매 알림을 보내는 것 또한 다루겠습니다.
본 문서에서 다뤄지는 내용을 쉽게 받아들일 수 있도록 독자 여러분이 HTML (HyperText Markup Language)에 먼저 익숙해지시는 것을 추천드립니다.
업로드 보고서를 구현하려면 FTP를 통해 데이터를 수신할 수 있는 WWW 서버(아무 컴퓨터나 가능)가 필요합니다. 문자 메세지로 거래 이벤트에 대한 알림을 받을 수 있게 구현하기 위해서는 EMAIL-SMS 게이트웨이가 필요합니다(이 서비스는 대부분의 이동 통신사와 타사 조직에서 제공함).
1. 리포트 생성 및 FTP로 업로드하기
무역 보고서를 생성하여 FTP 프로토콜을 통해 전송하는 MQL5 프로그램을 만들어 봅시다. 우선 스크립트로 만들겠습니다. 향후에는 Expert Advisors 및 Indicators에 삽입할 수 있는 완성된 블록으로 사용할 수 있습니다. 예를들어 Expert Adviser 안에서는 블록을 Trade 혹은 Timer 이벤트 핸들러로 활용할 수 있으며, 매매 요청 후에 이 블록을 실행하거나 ChartEvent 이벤트에 대응하여 액션을 설정할 수 있습니다. 인디케이터 안에 Timer 혹은 ChartEvent 이벤트 핸들러를 이용하여 삽입할 수 있습니다.
프로그램에 의해 만들어진 보고서 예시는 1,2,3번 그림을 참조하시기 바랍니다 또는 문서 끝에 있는 링크를 통해 이 보고서를 다운로드할 수 있습니다.
1번 그림. 리포트 예시 - 거래 테이블 및 포지션.
2번 그림. 리포트 예시 - 균형 차트.
3번 그림. 리포트 예시 - 현 금융상품의 매매가 차트.
매매 및 포지션 표(1번 그림)에서 편의를 위해 모든 거래는 포지션으로 나뉩니다. 표의 왼쪽에는 시장에 진입할 수 있는 볼륨, 시간 및 가격이 표시됩니다(오픈 포지션 및 추가). 표의 오른쪽에는 마찬가지로 시장에서 퇴장할 때의 패러미터가 표시됩니다(부분 혹은 완전 청산). 인/아웃 매매는 두 부분, 즉, 한 포지션의 청산과 다음 포지션의 오픈으로 나뉩니다.
거래 및 위치 표 아래에는 현재 상품의 밸런스 차트(수평 축 - 시간)가 표시되어 있습니다
이 프로그램을 통해 "report.html", "picture1.gif" 및 "picture2.gif" 파일 (리포트의 html 파일, 밸런스 차트및 매매가 차트의 그림 파일) 이MetaTarder5_istall_dir\MQL5\Files 폴더 안에 생성됩니다. 또한 FTP 게시는 터미널 설정에서 활성화되며, 이 세 개의 파일을 지정된 서버로 보냅니다. 또한 오픈 포지션의 방향을 가리키는 화살표가 있는 이미지 - 매수 및 매도 용으로 ("buy.gif" 및 "sell.gif") 두 파일이 더 필요합니다. 이미지를 가져다 쓰거나(문서 끝에 있는 다운로드 링크) 아무 그래픽 편집기로 직접 그릴 수 있습니다. 이 두 파일은 WWW 서버 상에 "report.html" 파일과 같은 폴더에 놓여야 합니다.
입력 패러미터로서 프로그램은 보고서가 생성되는 기간의 시작 및 종료 시간을 받습니다. 우리가 쓰던 예시에서는 보고 기간의 종료 시간은 지금 시간이고, 사용자는 보고 기간 여러가지 중 하나(전체 기간, 마지막 날, 마지막 주, 지난 달 또는 지난해)를 선택합니다.
어떻게 리포트가 생성되는지에 대해 조금 더 부연하겠습니다. 사용 가능한 모든 거래 내역에 대해 매매 서버를 요청합니다. 획득된 거래는 하나씩 처리됩니다. deal_status[] 어레이는 해당 거래가 처리 되었는지 아닌지에 대한 정보를 담습니다. 이 어레이의 엘리먼트 인덱스는 매매 서버 거래 리스트에서 받아온 거래 숫자입니다. 엘리먼트 값의 해석은 다음과 같습니다: 0 - 처리되지 않은 거래, 1 - 일부 처리된 거래 (인/아웃), 127 - 처리완료된 거래 (다른 값은 쓰이지 않으며 미래에 활용되기 위해 따로 빼내어집니다).
symb_list[] 어레이에는 거래가 이루어진 금융상품명의 리스트가 보존되며, lots_list[] 어레이에는 - 각 거래가 처리되었을 당시의 상품의 오픈 포지션 볼륨이 보존됩니다. 볼륨이 양수값을 지니고 있다면 이는 롱 포지션을 의미하며, 음수는 숏 포지션을 의미합니다. 만약 볼륨이 0이라면 이는 해당 툴에 오픈 포지션이 없다는 의미입니다. 만약 거래를 처리하는 금융상품명이 리스트 (symb_list[] 어레이)에 없을 경우, 추가 되며, 금융 상품의 숫자 (symb_total 변수)가 1 올라갑니다.
각 거래에서 모든 후속 거래는 동일한 금융상품으로 분석되며, 직급이 마감될 때까지 또는 입출금까지 분석됩니다. 이러한 거래만 분석되며, 이 경우 deal_status[] 어레이의 값은 127보다 작습니다. 거래 처리 후, deal_status[] 어레이의 해당 엘리먼트의 값은 127이 되며, 딜이 포지션 인/아웃일 경우에는 값이 1이 됩니다. 포지션이 열린 시간이 보고 기간(StartTime 및 EndTime 변수로 정의되는)과 일치할 경우 해당 포지션은 리포트에 기록됩니다(입출력값 양쪽 모두) .
거래 표 외에도 현재의 재정 상품의 새로운 차트가 열려 있습니다. 이 차트 용으로 필요한 속성이 모두 제공 되며, ChartScreenShot() 함수를 통해 현 상품을 위한 가격 차트를 이미지파일로 만들 수 있도록 스크린샷도 제공됩니다. 다음으로 이 차트에서는 가격 차트를 마스킹하고 밸런스 변화 차트를 그린 다음 다른 스크린샷을 만듭니다.
차트 이미지 두 개와 리포트용 HTML 파일이 완성되면 FTP를 통해 전송할 수 있는지 확인됩니다. 만약 허가되어 있다면 "report.html", "picture1.gif" 및 "picture2.gif" 파일들은 MetaTrader 5 설정값에 명시된대로 SendFTP()함수를 통해 전송됩니다.
MetaQuotes 언어 편집기 실행 후 스크립트 만들기. 상수 정의 - 차트 새로 고침 시간 초과(초), 가격 차트의 너비 및 높이, 밸런스 차트의 최대 너비. 밸런스 변화 곡선을 표시하는 차트의 기간은 보고 기간, 그리고 차트의 최대 너비에 따라 선택됩니다. 차트 너비는 밸런스 차트에 필요한 크기로 조정됩니다.
차트의 높이는 너비의 절반으로 자동 계산됩니다. 또한 수직축 너비를 상수로 지정하겠습니다. 수직축으로 인해 그래픽 영역이 그림 너비에 비해 축소되는 픽셀 수입니다.
#define timeout 10 // chart refresh timeout #define Picture1_width 800 // max width of chart in report #define Picture2_width 800 // width of price chart in report #define Picture2_height 600 // height of price chart in report #define Axis_Width 59 // width of vertical axis (in pixels)
사용자에게 요청할 입력 패러미터를 명시하십시오.
// request input parameters #property script_show_inputs
보고 기간의 목록을 생성하십시오.
// enumeration of report periods enum report_periods { All_periods, Last_day, Last_week, Last_month, Last_year };
사용자에게 보고 기간을 어찌할지 물어보십시오 (기본값은 전체 기간입니다).
// ask for report period input report_periods ReportPeriod=0;
OnStart() 함수의 메인을 쓰십시오.
void OnStart() {
보고기간의 시작과 끝을 판단하십시오.
datetime StartTime=0; // beginning of report period datetime EndTime=TimeCurrent(); // end of report period // calculating the beginning of report period switch(ReportPeriod) { case 1: StartTime=EndTime-86400; // day break; case 2: StartTime=EndTime-604800; // week break; case 3: StartTime=EndTime-2592000; // month break; case 4: StartTime=EndTime-31536000; // year break; } // if none of the options is executed, then StartTime=0 (entire period)
프로그램에서 쓰일 변수를 선언하십시오. 각 변수의 목적은 코멘트에 설명되어있습니다.
int total_deals_number; // number of deals for history data int file_handle; // file handle int i,j; // loop counters int symb_total; // number of instruments, that were traded int symb_pointer; // pointer to current instrument char deal_status[]; // state of deal (processed/not processed) ulong ticket; // ticket of deal long hChart; // chart id double balance; // current balance value double balance_prev; // previous balance value double lot_current; // volume of current deal double lots_list[]; // list of open volumes by instruments double current_swap; // swap of current deal double current_profit; // profit of current deal double max_val,min_val; // maximal and minimal value string symb_list[]; // list of instruments, that were traded string in_table_volume; // volume of entering position string in_table_time; // time of entering position string in_table_price; // price of entering position string out_table_volume; // volume of exiting position string out_table_time; // time of exiting position string out_table_price; // price of exiting position string out_table_swap; // swap of exiting position string out_table_profit; // profit of exiting position bool symb_flag; // flag that instrument is in the list datetime time_prev; // previous value of time datetime time_curr; // current value of time datetime position_StartTime; // time of first enter to position datetime position_EndTime; // time of last exit from position ENUM_TIMEFRAMES Picture1_period; // period of balance chart
새 차트를 열고 속성값을 정하십시오 - 가격 차트이며, 보고서 하단에 출력값이 적힐 것입니다.
// open a new chart and set its properties hChart=ChartOpen(Symbol(),0); ChartSetInteger(hChart,CHART_MODE,CHART_BARS); // bars chart ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // autoscroll enabled ChartSetInteger(hChart,CHART_COLOR_BACKGROUND,White); // white background ChartSetInteger(hChart,CHART_COLOR_FOREGROUND,Black); // axes and labels are black ChartSetInteger(hChart,CHART_SHOW_OHLC,false); // OHLC are not shown ChartSetInteger(hChart,CHART_SHOW_BID_LINE,true); // show BID line ChartSetInteger(hChart,CHART_SHOW_ASK_LINE,false); // hide ASK line ChartSetInteger(hChart,CHART_SHOW_LAST_LINE,false); // hide LAST line ChartSetInteger(hChart,CHART_SHOW_GRID,true); // show grid ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,true); // show period separators ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // grid is light-gray ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,Black); // chart lines are black ChartSetInteger(hChart,CHART_COLOR_CHART_UP,Black); // up bars are black ChartSetInteger(hChart,CHART_COLOR_CHART_DOWN,Black); // down bars are black ChartSetInteger(hChart,CHART_COLOR_BID,Gray); // BID line is gray ChartSetInteger(hChart,CHART_COLOR_VOLUME,Green); // volumes and orders levels are green ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,Red); // SL and TP levels are red ChartSetString(hChart,CHART_COMMENT,ChartSymbol(hChart)); // comment contains instrument <end segm
차트의 스크린샷을 찍고 "picture2.gif"로 저장하십시오.
// save chart as image file ChartScreenShot(hChart,"picture2.gif",Picture2_width,Picture2_height);
계정 존속기간에 대한 거래 기록을 요청하십시오.
// request deals history for entire period HistorySelect(0,TimeCurrent());
리포트(ANSI 인코딩)와 HTML 페이지를 쓸 "report.html" 파일을 여십시오.
// open report file file_handle=FileOpen("report.html",FILE_WRITE|FILE_ANSI);
HTML 문서의 도입부를 작성하십시오:
- HTML문서의 시작 (<html>)
- 브라우저 창의 최상단에 표기될 표제 (<head><title>Expert 매매 보고서</title></head>)
- 배경색으로 HTML 문서의 메인 파트 시작 (<body bgcolor='#EFEFEF'>)
- 중앙 정렬 (<center>)
- 거래 및 포지션 테이블의 표제 (<h2>매매 보고서</h2>)
- 정렬, 테두리 너비, 배경색, 테두리 색, 셀 간격 및 셀 패딩이 있는 거래 및 위치 테이블의 시작 (<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>)
- 테이블 표제
// write the beginning of HTML FileWrite(file_handle,"<html>"+ "<head>"+ "<title>Expert Trade Report</title>"+ "</head>"+ "<body bgcolor='#EFEFEF'>"+ "<center>"+ "<h2>Trade Report</h2>"+ "<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>"+ "<tr>"+ "<th rowspan=2>SYMBOL</th>"+ "<th rowspan=2>Direction</th>"+ "<th colspan=3>Open</th>"+ "<th colspan=3>Close</th>"+ "<th rowspan=2>Swap</th>"+ "<th rowspan=2>Profit</th>"+ "</tr>"+ "<tr>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "</tr>");
리스트의 거래 수를 가져오는 중.
// number of deals in history total_deals_number=HistoryDealsTotal();
symb_list[], lots_list[], 및 deal_status[] 어레이의 디멘션을 정하기.
// setting dimensions for the instruments list, the volumes list and the deals state arrays ArrayResize(symb_list,total_deals_number); ArrayResize(lots_list,total_deals_number); ArrayResize(deal_status,total_deals_number);
deal_status[] 어레이의 전 요소 값을 0으로 초기화하는 중 - 모든 거래가 처리되지않았다는 의미.
// setting all elements of array with value 0 - deals are not processed ArrayInitialize(deal_status,0);
이전 밸런스 값을 저장하는 데 사용되는 밸런스 값 및 변수의 초기 값 설정.
balance=0; // initial balance balance_prev=0; // previous balance
리스트 내의 금융 상품의 수를 저장하는 데에 쓰이는 변수의 초기 값 세팅하기.
// number of instruments in the list symb_total=0;
리스트 내의 거래를 순서대로 처리할 루프를 만드십시오.
// processing all deals in history for(i=0;i<total_deals_number;i++) {
현재 거래를 선택하고 해당 티켓을 확보하십시오.
//select deal, get ticket ticket=HistoryDealGetTicket(i);
밸런스를 현재 거래의 수익량만큼 변화시키기.
// changing balance balance+=HistoryDealGetDouble(ticket,DEAL_PROFIT);
거래 시간 받아오기 - 앞으로 자주 쓰입니다.
// reading the time of deal time_curr=HistoryDealGetInteger(ticket,DEAL_TIME);
리스트의 첫 거래일 경우 - 보고기간과 도표가 표시될 지역 폭에 따라 보고 기간 경계를 조정하고 밸런스 차트용 기간을 선택해야 합니다. 최대 및 최소 밸런스의 초기 값 설정(이러한 변수는 차트의 최대값과 최소값을 설정하는 데 사용됩니다.).
// if this is the first deal if(i==0) { // if the report period starts before the first deal, // then the report period will start from the first deal if(StartTime<time_curr) StartTime=time_curr; // if report period ends before the current time, // then the end of report period corresponds to the current time if(EndTime>TimeCurrent()) EndTime=TimeCurrent(); // initial values of maximal and minimal balances // are equal to the current balance max_val=balance; min_val=balance; // calculating the period of balance chart depending on the duration of // report period Picture1_period=PERIOD_M1; if(EndTime-StartTime>(Picture1_width-Axis_Width)) Picture1_period=PERIOD_M2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*120) Picture1_period=PERIOD_M3; if(EndTime-StartTime>(Picture1_width-Axis_Width)*180) Picture1_period=PERIOD_M4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*240) Picture1_period=PERIOD_M5; if(EndTime-StartTime>(Picture1_width-Axis_Width)*300) Picture1_period=PERIOD_M6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*360) Picture1_period=PERIOD_M10; if(EndTime-StartTime>(Picture1_width-Axis_Width)*600) Picture1_period=PERIOD_M12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*720) Picture1_period=PERIOD_M15; if(EndTime-StartTime>(Picture1_width-Axis_Width)*900) Picture1_period=PERIOD_M20; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1200) Picture1_period=PERIOD_M30; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1800) Picture1_period=PERIOD_H1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*3600) Picture1_period=PERIOD_H2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*7200) Picture1_period=PERIOD_H3; if(EndTime-StartTime>(Picture1_width-Axis_Width)*10800) Picture1_period=PERIOD_H4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*14400) Picture1_period=PERIOD_H6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*21600) Picture1_period=PERIOD_H8; if(EndTime-StartTime>(Picture1_width-Axis_Width)*28800) Picture1_period=PERIOD_H12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*43200) Picture1_period=PERIOD_D1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*86400) Picture1_period=PERIOD_W1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*604800) Picture1_period=PERIOD_MN1; // changing the period of opened chart ChartSetSymbolPeriod(hChart,Symbol(),Picture1_period); }
이 거래가 첫 번째 거래가 아닌 경우 - 밸런스 변화 차트를 사용하여 "line" 객체를 만듭니다. 이 선은 최소한 한 쪽 끝이 보고 기간 사이에 있어야 그려집니다. 만약 양쪽 끝이 모두 보고 기간 사이에 있다면 라인이 "굵어집니다". 밸런스 선은 녹색입니다. 밸런스가 최소 및 최대 밸런스 범위를 벗어나는 경우 이 범위가 조정됩니다.
else // if this is not the first deal { // plotting the balance line, if the deal is in the report period, // and setting properties of the balance line if(time_curr>=StartTime && time_prev<=EndTime) { ObjectCreate(hChart,IntegerToString(i),OBJ_TREND,0,time_prev,balance_prev,time_curr,balance); ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_COLOR,Green); // if both ends of line are in the report period, // it will be "thick" if(time_prev>=StartTime && time_curr<=EndTime) ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_WIDTH,2); } // if new value of balance exceeds the range // of minimal and maximal values, it must be adjusted if(balance<min_val) min_val=balance; if(balance>max_val) max_val=balance; }
해당 변수에 이전 시간 값을 할당합니다.
// changing the previous time value
time_prev=time_curr;
거래가 아직 처리되지 않았다면 처리하십시오.
// if the deal has not been processed yet if(deal_status[i]<127) {
이 거래가 밸런스 변화이고 보고 기간 사이에 있는 경우 해당 스트링이 보고서에 기록됩니다. 이 거래는 이제 처리된 것으로 표기됩니다.
// If this deal is balance charge if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE) { // if it's in the report period - write the corresponding string to report. if(time_curr>=StartTime && time_curr<=EndTime) FileWrite(file_handle,"<tr><td colspan='9'>Balance:</td><td align='right'>",HistoryDealGetDouble(ticket,DEAL_PROFIT), "</td></tr>"); // mark deal as processed deal_status[i]=127; }
만약 이 거래가 매수나 매도라면 이 상품이 리스트 (symb_list[] 어레이) 안에 있는지 확인합니다. 만약 없으면 그 안에 넣습니다. symb_pointer 변수는 symb_list[] 어레이 내에서 현재 거래의 상품명을 담고있는 요소를 가리키게 됩니다.
// if this deal is buy or sell if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY || HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // check if there is instrument of this deal in the list symb_flag=false; for(j=0;j<symb_total;j++) { if(symb_list[j]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { symb_flag=true; symb_pointer=j; } } // if there is no instrument of this deal in the list if(symb_flag==false) { symb_list[symb_total]=HistoryDealGetString(ticket,DEAL_SYMBOL); lots_list[symb_total]=0; symb_pointer=symb_total; symb_total++; }
포지션 수명의 처음 그리고 마지막을 담고있는 position_StartTime 및 position_EndTime 변수의 초기 값을 설정합니다.
// set the initial value for the beginning time of deal position_StartTime=time_curr; // set the initial value for the end time of deal position_EndTime=time_curr;
in_table_volume, in_table_time, in_table_price, out_table_volume, out_table_time, out_table_price, out_table_swap 그리고 out_table_profit 변수들에는 볼륨, 시장 진입 시작 및 진입가, 시간, 단가, 스왑 및 이익실현량 등 더 큰 테이블의 셀 안에 들어갈 테이블이 담길 것입니다. in_table_volume 변수에는 금융상품명과 오픈 포지션의 방향에 대응되는 이미지로의 링크를 같이 저장할 것입니다. 모든 변수에 초기값을 부여합니다.
// creating the string in report - instrument, position direction, beginning of table for volumes to enter the market if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) StringConcatenate(in_table_volume,"<tr><td align='left'>",symb_list[symb_pointer], "</td><td align='center'><img src='buy.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) StringConcatenate(in_table_volume,"<tr><td align='left'>",symb_list[symb_pointer], "</td><td align='center'><img src='sell.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); // creating the beginning of time table to enter the market in_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of price table to enter the market in_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of volume table to exit the market out_table_volume="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of time table to exit the market out_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of price table to exit the market out_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of swap table to exit the market out_table_swap="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of profit table to exit the market out_table_profit="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>";
현재에서 시작하여 포지션이 닫힐 때까지 모든 거래를 처리합니다. 이전에 처리하지 않았다면 모두 처리하십시오.
// process all deals for this position starting with the current(until position is closed) for(j=i;j<total_deals_number;j++) { // if the deal has not been processed yet - process it if(deal_status[j]<127) {
거래를 선택하고 티켓을 가져옵니다.
// select deal, get ticket ticket=HistoryDealGetTicket(j);
거래가 오픈 포지션과 동일한 상품에 있는 경우, 거래를 처리한다. 거래 시각을 받아오십시오. 거래 시간이 위치 시간 범위를 초과하는 경우 - 범위를 확장합니다. 거래 볼륨을 받아오십시오.
// if the instrument of deal matches the instrument of position, that is processed if(symb_list[symb_pointer]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { // get the deal time time_curr=HistoryDealGetInteger(ticket,DEAL_TIME); // If the deal time goes beyond the range of position time // - extend position time if(time_curr<position_StartTime) position_StartTime=time_curr; if(time_curr>position_EndTime) position_EndTime=time_curr; // get the volume of deal lot_current=HistoryDealGetDouble(ticket,DEAL_VOLUME);
매매거래는 별도로 처리됩니다. 매수 거래로 시작합니다.
// if this deal is buy if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) {
이미 매도 포지션을 오픈했다면 매수 거래는 시장에서 종료됩니다. 그리고 거래량이 열린 숏 포지션의 볼륨보다 클 경우, 이것이 인/아웃이 됩니다. 필수 값을 가진 스트링 변수를 할당하십시오. 만약 거래가 완전 처리 되었다면 deal_status[] 어레이 값으로 127을, 인/아웃 상태라면 1을, 그리고 이 거래는 다른 포지션들을 위해 분석되어야합니다.
// if position is opened for sell - this will be exit from market if(NormalizeDouble(lots_list[symb_pointer],2)<0) { // if buy volume is greater than volume of opened short position - then this is in/out if(NormalizeDouble(lot_current+lots_list[symb_pointer],2)>0) { // creating table of volumes to exit the market - indicate only volume of opened short position StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // mark position as partially processed deal_status[j]=1; } else { // if buy volume is equal or less than volume of opened short position - then this is partial or full close // creating the volume table to exit the market StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // creating the time table to exit the market StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table to exit the market StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // get the swap of current deal current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // if swap is equal to zero - create empty string of the swap table to exit the market if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // else create the swap string in the swap table to exit the market else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // get the profit of current deal current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // if profit is negative (loss) - it is displayed as red in the profit table to exit the market if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align=right><SPAN style='COLOR: #EF0000'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); // else - it is displayed as green else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); }
이미 롱 포지션을 오픈한 경우, 이 거래에서 매수는 시장에 진입하는 첫 번째 또는 추가 구매가 됩니다. 만약 이 거래에 대응되는 deal_status[] 어레이 엘리먼트가 -1을 값으로 가진다면 이는 인/아웃이 성립했다는 의미입니다. 필요한 값을 가진 스트링 변수를 할당하고 딜을 처리된 것으로 표시합니다(deal_status[] 어레이 안에서 저 거래에 해당되는 요소에 127을 값으로 부여).
else // if position is opened for buy - this will be the enter to the market { // if this deal has been already partially processed (in/out) if(deal_status[j]==1) { // create the volume table of entering the market (volume, formed after in/out, is put here) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // indemnity of volume change, which will be produced (the volume of this deal is already taken into account) lots_list[symb_pointer]-=lot_current; } // if this deal has not been processed yet, create the volume table to enter the market else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // creating the time table of entering the market StringConcatenate(in_table_time,in_table_time,"<tr><td align center>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table of entering the market StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // mark deal as processed deal_status[j]=127; }
포지션의 볼륨을 현재 거래의 볼륨으로 변경하십시오. 포지션이 닫힌 경우(볼륨이 0임) - 이 포지션의 처리를 중지하고(j 변수와 함께 루프에서 이탈) 처리되지 않은 다음 거래(루프에서 i변수)를 찾습니다.
// change of position volume by the current instrument, taking into account the volume of current deal lots_list[symb_pointer]+=lot_current; // if the volume of opened position by the current instrument became equal to zero - position is closed if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; }
매매거래는 비슷하게 처리되고, 그 다음에는 j 변수와 함께 루프를 빠져나갑니다.
// if this deal is sell if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // if position has been already opened for buy - this will be the exit from market if(NormalizeDouble(lots_list[symb_pointer],2)>0) { // if sell volume is greater than volume of opened long position - then this is in/out if(NormalizeDouble(lot_current-lots_list[symb_pointer],2)>0) { // creating table of volumes to exit the market - indicate only volume of opened long position StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // mark position as partially processed deal_status[j]=1; } else { // if sell volume is equal or greater than volume of opened short position - then this is partial or full close // creating the volume table to exit the market StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // creating the time table to exit the market StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table to exit the market StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // get the swap of current deal current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // if swap is equal to zero - create empty string of the swap table to exit the market if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // else create the swap string in the swap table to exit the market else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // get the profit of current deal current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // if profit is negative (loss) - it is displayed as red in the profit table to exit the market if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'> <SPAN style='COLOR: #EF0000'>",DoubleToString(current_profit,2),"</SPAN></td></tr>"); // else - it is displayed as green else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); } else // if position is opened for sell - this will be the enter to the market { // if this deal has been already partially processed (in/out) if(deal_status[j]==1) { // create the volume table of entering the market (volume, formed after in/out, is put here) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // indemnity of volume change, which will be produced (the volume of this deal is already taken into account) lots_list[symb_pointer]+=lot_current; } // if this deal has not been processed yet, create the volume table to enter the market else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // creating the time table of entering the market StringConcatenate(in_table_time,in_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table of entering the market StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // change of position volume by the current instrument, taking into account the volume of current deal lots_list[symb_pointer]-=lot_current; // if the volume of opened position by the current instrument became equal to zero - position is closed if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; } } } }
포지션이 열린 시간이 보고 기간 (일부라도) 내에 있는 경우 해당 항목은 "report.html" 파일로 출력됩니다.
// if the position period is in the the report period - the position is printed to report if(position_EndTime>=StartTime && position_StartTime<=EndTime) FileWrite(file_handle, in_table_volume,"</table></td>", in_table_time,"</table></td>", in_table_price,"</table></td>", out_table_volume,"</table></td>", out_table_time,"</table></td>", out_table_price,"</table></td>", out_table_swap,"</table></td>", out_table_profit,"</table></td></tr>");
balance_prev 변수에 밸런스 값을 부여하십시오. i 변수를 가지고 루프를 이탈합니다.
}
// changing balance
balance_prev=balance;
}
HTML 파일의 끝(이미지 링크, 중앙 정렬의 끝, 주요 부분의 끝, HTML 문서 끝)을 작성하십시오. "report.html" file.
// create the end of html-file FileWrite(file_handle, "</table><br><br>"+ "<h2>Balance Chart</h2><img src='picture1.gif'><br><br><br>"+ "<h2>Price Chart</h2><img src='picture2.gif'>"+ "</center>"+ "</body>"+ "</html>"); // close file FileClose(file_handle);
차트 업데이트를 기다리는 시간이 시간 초과 상수에 지정된 시간보다 짧습니다.
// get current time time_curr=TimeCurrent(); // waiting for chart update while(SeriesInfoInteger(Symbol(),Picture1_period,SERIES_BARS_COUNT)==0 && TimeCurrent()-time_curr<timeout) Sleep(1000);
차트의 고정 최대 및 최소값 설정하기.
// setting maximal and minimal values for the balance chart (10% indent from upper and lower boundaries) ChartSetDouble(hChart,CHART_FIXED_MAX,max_val+(max_val-min_val)/10); ChartSetDouble(hChart,CHART_FIXED_MIN,min_val-(max_val-min_val)/10);
밸런스 차트의 속성 설정하기.
// setting properties of the balance chart ChartSetInteger(hChart,CHART_MODE,CHART_LINE); // chart as line ChartSetInteger(hChart,CHART_FOREGROUND,false); // chart on foreground ChartSetInteger(hChart,CHART_SHOW_BID_LINE,false); // hide BID line ChartSetInteger(hChart,CHART_COLOR_VOLUME,White); // volumes and orders levels are white ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,White); // SL and TP levels are white ChartSetInteger(hChart,CHART_SHOW_GRID,true); // show grid ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // grid is light-gray ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,false); // hide period separators ChartSetInteger(hChart,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); // hide volumes ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,White); // chart is white ChartSetInteger(hChart,CHART_SCALE,0); // minimal scale ChartSetInteger(hChart,CHART_SCALEFIX,true); // fixed scale on vertical axis ChartSetInteger(hChart,CHART_SHIFT,false); // no chart shift ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // autoscroll enabled ChartSetString(hChart,CHART_COMMENT,"BALANCE"); // comment on chart
밸런스 차트 다시 그리기.
// redraw the balance chart ChartRedraw(hChart); Sleep(8000);
차트 스크린샷 찍기 ("picture1.gif" 이미지 저장). 차트의 너비는 보고 기간의 너비에 맞춰 조정되지만(휴일 때문에 부정확한 경우가 자주 발생하고 차트가 밸런스 변화 곡선보다 넓어짐) 높이는 너비의 절반으로 계산됩니다.
// screen shooting the balance chart ChartScreenShot(hChart,"picture1.gif",(int)(EndTime-StartTime)/PeriodSeconds(Picture1_period), (int)(EndTime-StartTime)/PeriodSeconds(Picture1_period)/2,ALIGN_RIGHT);
차트의 모든 객체를 삭제하고 닫으십시오.
// delete all objects from the balance chart ObjectsDeleteAll(hChart); // close chart ChartClose(hChart);
FTP 전송이 허가되어 있다면 "report.html", picture1.gif "그리고" picture2.gif " 이렇게 세 파일을 전송하십시오.
// if report publication is enabled - send via FTP // HTML-file and two images - price chart and balance chart if(TerminalInfoInteger(TERMINAL_FTP_ENABLED)) { SendFTP("report.html"); SendFTP("picture1.gif"); SendFTP("picture2.gif"); } }
일단 프로그램 설명은 끝났습니다. FTP전송을 하려면 MetaTrader 5 설정을 변경할 필요가 있습니다 - Tools 메뉴로 가서 Options를 누르고 Publisher 탭을 여십시오 (4번 그림).
4번 그림. FTP를 통해 리포트를 게시하는 옵션.
Options 다이얼로그 박스에서 "Enable"옵션을 체크하고, 계좌 번호, FTP 주소, 패스, 액세스용 로그인 및 비밀번호를 적어야합니다. 갱신 주기는 아무래도 좋습니다.
이제 스크립트를 실행시킬 수 있습니다. 실행시키면 밸런스 차트가 몇초동안 보이다 사라집니다. Journal에서는 잠재적 에러를 확인하고 혹시 파일이 FTP로 전송된 것인지 확인할 수 있습니다. 만약 모든 것이제대로 되었다면 세 파일이 서버 상의 지정된 폴더에 나타날 것입니다. 이제 화살표 이미지 파일 두개를 넣고, WWW 서버가 설정되었고 기동된 후라면 웹 브라우저를 통해 리포트를 열 수 있게됩니다.
2. 문자메세지를 통해 핸드폰에 알림 보내기
때때로 컴퓨터나 다른 전자기기에서 멀리 떨어져서 손엔 핸드폰밖에 없는 경우가 있습니다. 그러나 그런 상황에서도 계좌에 대한 거래를 통제하거나 금융상품의 견적을 모니터하기를 원할 것니다. 이 경우 문자를 통해 휴대폰으로 보내는 알림을 설정할 수 있습니다. 많은 모바일 운영자(및 제3자)는 특정 전자 메일 주소로 보낸 문자 메시지를 수신할 수 있는 EMAIL-SMS 서비스를 제공합니다.
이를 위해서는 이메일(특히 사용할 SMTP 서버를 알아야 함)이 있어야 합니다. MetaTrader 5 세팅을 수정하십시오 - Tools 메뉴, Options로 가서 Email 탭을 여십시오 (5번 그림).
5번 그림. 이메일 알림 설정하기
"Enable" 옵션을 선택하고 SMTP 서버 주소, 로그인 및 암호, 보낸 사람 주소(전자 메일) 및 받는 사람 주소 - 문자메세지(모바일 운영자에게 확인)로 메시지를 보내는 데 사용되는 전자 메일 주소를 지정합니다. 모든 것이 올바른 경우, "Test" 버튼을 클릭하면 확인 메시지가 발송됩니다(저널의 추가 정보 참조).
가격이 일정 수준에 도달했을 때 연락 받는 가장 쉬운 방법은 경보를 만드는 것입니다. 이를 위해서는 "Toolbox" 탭을 누르고, 우클릭 후 "Create" 를 누르십시오 (6번 그림).
6번 그림. 경보 만들기.
이 창에서 "Enable" 을 체크하고 "Mail" 을 선택한 후 금융상품, 조건을 선택하고 조건 값을 입력한 후 메시지 텍스트를 작성합니다. 메시지가 여러번 오지 않도록 하려면 "Maximum iterations"에 1을 입력합니다. 모든 필드를 작성 완료하고 OK를 누르십시오.
만약 MQL5 프로그램을 통해 메세지를 보내게되면 더욱 많은 가능성이 열립니다. SendMail() 함수를 사용합니다. 패러미터가 두개 있습니다. 첫번째 - 표제, 두번째 - 메세지 본문.
거래 리퀘스트 (OrderSend() 함수) 후에서나 Trade 이벤트 핸들러 안에서 SendMail() 함수를 호출할 수 있습니다. 이제 시장 진입, 주문, 포지션 청산 등 거래 이벤트에 대한 연락을 받게 됩니다. 혹은 SendMail()를 OnTimer() 함수 안에 두어서 현재 견적에 대한 주기적인 연락이 오게끔 할 수도 있습니다. 특정 매매 신호(인디케이터 선 교차, 가격이 특정 라인이나 레벨에 도달, 등.,)가 나타나면 연락이 오게할 수 있습니다.
몇가지 예시를 살펴보겠습니다.
Expert Advisor 나 Script에서는 이하로 대체합니다.
OrderSend(request,result};
다음 파트를 참조하십시오
string msg_subj,msg_text; if(OrderSend(request,result)) { switch(request.action) { case TRADE_ACTION_DEAL: switch(request.type) { case ORDER_TYPE_BUY: StringConcatenate(msg_text,"Buy ",result.volume," ",request.symbol," at price ",result.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL: StringConcatenate(msg_text,"Sell ",result.volume," ",request.symbol," at price ",result.price,", SL=",request.sl,", TP=",request.tp); break; } break; case TRADE_ACTION_PENDING: switch(request.type) { case ORDER_TYPE_BUY_LIMIT: StringConcatenate(msg_text,"Set BuyLimit ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_LIMIT: StringConcatenate(msg_text,"Set SellLimit ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_BUY_STOP: StringConcatenate(msg_text,"Set BuyStop ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_STOP: StringConcatenate(msg_text,"Set SellStop ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_BUY_STOP_LIMIT: StringConcatenate(msg_text,"Set BuyStopLimit ",result.volume," ",request.symbol," at price ",request.price,", stoplimit=",request.stoplimit, ", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_STOP_LIMIT: StringConcatenate(msg_text,"Set SellStop ",result.volume," ",request.symbol," at price ",request.price,", stoplimit=",request.stoplimit, ", SL=",request.sl,", TP=",request.tp); break; } break; case TRADE_ACTION_SLTP: StringConcatenate(msg_text,"Modify SL&TP. SL=",request.sl,", TP=",request.tp); break; case TRADE_ACTION_MODIFY: StringConcatenate(msg_text,"Modify Order",result.price,", SL=",request.sl,", TP=",request.tp); break; case TRADE_ACTION_REMOVE: msg_text="Delete Order"; break; } } else msg_text="Error!"; StringConcatenate(msg_subj,AccountInfoInteger(ACCOUNT_LOGIN),"-",AccountInfoString(ACCOUNT_COMPANY)); SendMail(msg_subj,msg_text);
매매 리퀘스트 후에 OrderSend() 함수는 SendMail() 함수를 이용하여 메세지를 보내게 됩니다. 여기에는 다음과 같은 매매 구좌 번호, 브로커 이름 및 조치(매입, 판매, 보류 주문, 주문 수정 또는 삭제)에 대한 정보가 포함됩니다.
59181-MetaQuotes Software Corp. Buy 0.1 EURUSD at price 1.23809, SL=1.2345, TP=1.2415
OnInit() 바디 안에 Expert Advisor나 인디케이터가 있는 경우 타이머는 EventSetTimer() 함수를 통해 시작됩니다 (패러미터는 타이머 기간을 초단위로 표기하는 단 하나):
void OnInit() { EventSetTimer(3600); }
OnDeinit() 안에서 EventKillTimer()를 통해 끄는 것을 잊지 마십시오:
void OnDeinit(const int reason) { EventKillTimer(); }
OnTimer() 안에서 SendMail()를 이용하여 메세지를 보냅니다:
void OnTimer() { SendMail(Symbol(),DoubleToString(SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits)); }
그러면 지정된 기간의 현재 금융상품 가격에 대한 메시지를 받게 됩니다.
마치며
지금까지 이 문서에서는 MQL5 프로그램을 사용하여 HTML과 이미지 파일을 만드는 방법과 FTP를 통해 WWW-서버에 업로드하는 방법에 대해 설명했습니다 또한 문자 메세지로 핸드폰에 연락을 보내도록 설정하는 방법 또한 다뤄보았습니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/61