
Создание и публикация отчетов о результатах торговли, отправка SMS-сообщений
Введение
В данной статье описано как с помощью советника, индикатора или скрипта сформировать отчет о результатах торговли в виде html-файла и загрузить его по протоколу FTP на WWW-сервер; рассматривается вопрос отправки уведомлений о торговых событиях по электронной почте, которые могут поступать на мобильный телефон в виде SMS-сообщений.
Для лучшего понимания материала, описанного в этой статье, читателю рекомендуется быть знакомым с языком HTML (HyperText Markup Language).
Для осуществления возможности загрузки отчетов на WWW-сервер по FTP-протоколу необходимо наличие WWW-сервера (им может быть любой компьютер), данные на который можно загружать по протоколу FTP. Для осуществления возможности получения уведомлений о торговых событиях в виде SMS-сообщений, необходимо наличие шлюза EMAIL-SMS (эту услугу предоставляет большинство операторов мобильной связи и сторонние организации).
1. Создание отчета и его отправка по протоколу FTP
Напишем программу на языке MQL5, которая формирует отчет о торговле и отправляет его по FTP-протоколу. Для начала оформим ее в виде скрипта, в дальнейшем ее можно использовать как готовый блок, который можно вставлять в советники и индикаторы. В советниках, например, этот готовый модуль можно использовать в качестве обработчика событий Trade или Timer, запускать после выполнения торгового запроса или назначить одно из действий для события ChartEvent. В индикаторах можно аналогичным образом включать этот блок в обработчики событий Timer или ChartEvent.
Пример отчета, формируемого программой, можно увидеть на рис.1, рис.2 и рис.3, либо загрузив по ссылке, расположенной в конце статьи.
Рисунок 1. Пример отчета - таблица сделок и позиций
Рисунок 2. Пример отчета - график баланса
Рисунок 3. Пример отчета - график цены по текущему инструменту
В таблице сделок и позиций (рис.1.) все сделки для удобства разделены на позиции. В левой части таблицы показаны объем, время и цена входов в рынок (открытия позиции и добавлений), в правой - выходов из рынка (частичного или полного закрытия позиции). При перевороте сделка разбивается на две части - закрытие одной позиции и открытие следующей.
Под таблицей сделок и позиций выводится график изменения баланса (по оси абсцисс откладывается время), а в самом низу - график цены по текущему инструменту.
Программа создает файлы "report.html", "picture1.gif" и "picture2.gif" (html-файл отчета, рисунок с графиком баланса и рисунок с графиком цены) в директории каталог_установки_MetaTarder5\MQL5\Files, и, если в настройках торгового терминала разрешена отправка по протоколу FTP - отправляет эти три файла на сервер. Дополнительно потребуются еще два файла, содержащие картинки со стрелками, указывающими направление открытой позиции - покупка или продажа ("buy.gif" и "sell.gif"). Можно взять уже готовые картинки (ссылка на загрузку в конце статьи) или нарисовать в любом графическом редакторе. Эти два файла должны располагаться на WWW-сервере в том же директории, что и файл "report.html".
В качестве входных параметров в программу передается время начала и конца интервала, за который формируется отчет. В нашем примере концом отчетного интервала является текущее время, а пользователь выбирает один из вариантов отчетного периода: за весь период, за последний день, неделю, месяц, год.
Несколько слов о том, как происходит создание отчета. У торгового сервера запрашивается вся доступная история сделок, сделки из полученного списка обрабатываются по порядку. Массив deal_status[] хранит информацию о том, была ли обработана сделка или нет. Индексом элементов этого массива является номер сделки в полученном от торгового сервера списке сделок, а значения элементов интерпретируются следующим образом: 0 - сделка еще не обрабатывалась, 1 - сделка уже частично обработана (переворот), 127 - сделка уже полностью обработана (другие значения не используются и зарезервированы для будущего использования).
Массив symb_list[] содержит список названий финансовых инструментов, по которым велась торговля, а массив lots_list[] - объемы открытых позиций по каждому из инструментов на момент обработки данной сделки. Положительные значения объема соответствует длинным позициям, отрицательные - коротким. Значение объема равное нулю означает, что по этому инструменту нет открытых позиций. Если в процессе обработки сделок встречается финансовый инструмент, которого нет в списке инструментов (массиве symb_list[]) - он добавляется, а количество финансовых инструментов (переменная symb_total) увеличивается на единицу.
При обработке каждой сделки анализируются и последующие сделки по этому же финансовому инструменту до тех пор, пока позиция не будет закрыта, или пока не будет переворот позиции. Анализируются только те сделки, для которых значение массива deal_status[] меньше 127, после обработки сделки соответствующему элементу массива deal_status[] присваивается значение 127, а если эта сделка является переворотом позиции - значение 1. Если время, когда позиция была открыта, попадает в отчетный интервал (заданный переменными StartTime и EndTime) - записывается в отчет информация о данной позиции (все входы и выходы).
Кроме формирования таблицы сделок, открывается новый график по текущему финансовому инструменту, для него указываются необходимые свойства и происходит запись скриншота с помощью функции ChartScreenShot() - таким образом, получаем изображение с графиком цены по текущему инструменту. Далее на этом графике маскируется ценовой график и рисуется график изменения баланса, после чего создается еще один скриншот.
Когда две картинки с графиками созданы и html-файл с отчетом полностью сформированы, проверяется, разрешена ли отправка файлов по протоколу FTP, если разрешена - с помощью функции SendFTP() отправляются файлы "report.html", "picture1.gif" и "picture2.gif" в соответствии с настройками, которые указаны в торговом терминале MetaTrader 5.
Запускаем редактор MetaQuotes Language и начинаем создавать скрипт. Зададим константы - время, в течении которого происходит ожидание обновления графика (в секундах), ширина и высота ценового графика и максимальная ширина графика баланса. Период графика, на который будет выводиться кривая изменения баланса, выбирается в зависимости от длительности отчетного интервала и максимальной ширины графика. Ширина графика корректируется под размер, необходимый для графика баланса.
Высота графика вычисляется автоматически как половина ширины. Также, в константах укажем ширину вертикальной оси - то количество пикселей, на которое уменьшена графическая область по сравнению с шириной картинки из-за наличия оси ординат.
#define timeout 10 // время ожидания обновления графика #define Picture1_width 800 // максимальная ширина графика баланса в отчете #define Picture2_width 800 // ширина графика цены в отчете #define Picture2_height 600 // высота графика цены в отчете #define Axis_Width 59 // ширина оси ординат (в пикселях)
Укажем, что входные параметры будут запрашиваться у пользователя.
// запрашивать входные параметры #property script_show_inputs
Создадим перечисления отчетных интервалов.
// перечисление отчетных интервалов enum report_periods { All_periods, Last_day, Last_week, Last_month, Last_year };
Запросим у пользователя отчетный интервал (по умолчанию - весь период).
// запрос отчетного интервала input report_periods ReportPeriod=0;
Пишем тело функции OnStart().
void OnStart() {
Определяем начало и конец отчетного интервала.
datetime StartTime=0; // начало отчетного интервала datetime EndTime=TimeCurrent(); // конец отчетного интервала // вычисление начала отчетного интервала switch(ReportPeriod) { case 1: StartTime=EndTime-86400; // день break; case 2: StartTime=EndTime-604800; // неделя break; case 3: StartTime=EndTime-2592000; // месяц break; case 4: StartTime=EndTime-31536000; // год break; } // если ни один из вариантов не выполнен, StartTime=0 (весь период)
Объявим переменные, которые используются в программе. Назначение переменных описано в комментариях в тексте программы.
int total_deals_number; // количество сделок исторических данных int file_handle; // файловый указатель int i,j; // счетчики цикла int symb_total; // количество инструментов, по которым велась торговля int symb_pointer; // указатель на текущий инструмент char deal_status[]; // статус сделки (обработана/необработана) ulong ticket; // тикет сделки long hChart; // идентификатор графика double balance; // текущее значение баланса double balance_prev; // предыдущее значение баланса double lot_current; // объем текущей сделки double lots_list[]; // список открытых объемов по инструментам double current_swap; // своп текущей сделки double current_profit; // прибыль текущей сделки double max_val,min_val; // максимальное и минимальное значение string symb_list[]; // список инструментов, по которым велась торговля string in_table_volume; // объем входа в позицию string in_table_time; // время входа в позицию string in_table_price; // цена входа в позицию string out_table_volume; // объем выхода из позиции string out_table_time; // время выхода из позиции string out_table_price; // цена выхода из позиции string out_table_swap; // своп выхода из позиции string out_table_profit; // прибыль выхода из позиции bool symb_flag; // признак того, что инструмент есть в списке datetime time_prev; // предыдущее значение времени datetime time_curr; // текущее значение времени datetime position_StartTime; // время первого входа в позицию datetime position_EndTime; // время последнего выхода из позиции ENUM_TIMEFRAMES Picture1_period; // период графика баланса
Открываем новый график и устанавливаем его свойства - это будет ценовой график, выводящийся внизу отчета.
// открытие нового графика и указание его свойств hChart=ChartOpen(Symbol(),0); ChartSetInteger(hChart,CHART_MODE,CHART_BARS); // график в виде баров ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // автопрокрутка включена ChartSetInteger(hChart,CHART_COLOR_BACKGROUND,White); // фон белый ChartSetInteger(hChart,CHART_COLOR_FOREGROUND,Black); // оси и надписи черные ChartSetInteger(hChart,CHART_SHOW_OHLC,false); // OHLC не показывать ChartSetInteger(hChart,CHART_SHOW_BID_LINE,true); // линию BID показывать ChartSetInteger(hChart,CHART_SHOW_ASK_LINE,false); // линию ASK не показывать ChartSetInteger(hChart,CHART_SHOW_LAST_LINE,false); // линию LAST не показывать ChartSetInteger(hChart,CHART_SHOW_GRID,true); // сетку показывать ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,true); // разделители периодов показывать ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // сетка светло-серая ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,Black); // линии графика черные ChartSetInteger(hChart,CHART_COLOR_CHART_UP,Black); // восходящие бары черные ChartSetInteger(hChart,CHART_COLOR_CHART_DOWN,Black); // нисходящие бары черные ChartSetInteger(hChart,CHART_COLOR_BID,Gray); // линия BID серая ChartSetInteger(hChart,CHART_COLOR_VOLUME,Green); // объемы и уровни ордеров зеленые ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,Red); // уровни SL и TP красные ChartSetString(hChart,CHART_COMMENT,ChartSymbol(hChart)); // в комментарии - инструмент
Создаем скриншот графика и записываем его в файл "picture2.gif".
// запись картинки - графика цены ChartScreenShot(hChart,"picture2.gif",Picture2_width,Picture2_height);
Запросим историю сделок за все время сущестования счета.
// запрос истории сделок за весь период HistorySelect(0,TimeCurrent());
Открываем файл "report.html", в который будем записывать HTML страницу отчета (кодировка ANSI).
// открытие файла отчета file_handle=FileOpen("report.html",FILE_WRITE|FILE_ANSI);
Записываем начальную часть HTML-документа:
- начало html-документа (<html>),
- заголовок и надпись, которая будет в верхней части браузера (<head><title>Expert Trade Report</title></head>),
- начало основной части html-документа с указанием цвета фона (<body bgcolor=#EFEFEF>),
- выравнивание по центру (<center>),
- заголовок таблицы сделок и позиций (<h2>Trade Report</h2>),
- начало таблицы сделок и позиций с указанием выравнивания, толщины границы, цвета фона, цвета границы, расстояния между ячейками и расстояния между текстом и границей ячейки (<table align=center border=1 bgcolor=#FFFFFF bordercolor=#7F7FFF cellspacing=0 cellpadding=0>),
- "шапка" таблицы.
// запись в файл начала 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>");Получение количества сделок в списке.
// количество сделок на истории total_deals_number=HistoryDealsTotal();
Установка размеров массивов symb_list[], lots_list[] и deal_status[].
// установка размеров массивов списка инструментов, списка объемов, статусов сделок ArrayResize(symb_list,total_deals_number); ArrayResize(lots_list,total_deals_number); ArrayResize(deal_status,total_deals_number);
Иницилизация всех элементов массива deal_status[] значением 0 - все сделки не обработаны.
// присвоение всем элементам массива значения 0 - сделки не обработаны ArrayInitialize(deal_status,0);
Установка начальных значений баланса и переменной, используемой для хранения предыдущего значения баланса.
balance=0; // начальный баланс balance_prev=0; // предыдущий баланс
Установка начального значения переменной, используемой для хранения количества финансовых инструментов в списке.
// количество инструментов в списке symb_total=0;
Создаем цикл, в котором последовательно перебираем все сделки в списке сделок.
// перебор всех сделок на истории for(i=0;i<total_deals_number;i++) {
Выберем текущую сделку, получим ее тикет.
//выбор сделки, получение тикета ticket=HistoryDealGetTicket(i);
Изменение баланса на величину прибыли в текущей сделке.
// изменение баланса balance+=HistoryDealGetDouble(ticket,DEAL_PROFIT);
Получим время сделки - в дальнейшем оно будет часто использоваться.
// чтение времени сделки time_curr=HistoryDealGetInteger(ticket,DEAL_TIME);
Если это первая сделка в списке - корректировка границ отчетного интервала и выбор периода для графика изменения баланса в зависимости от длительности отчетного интервала и ширины области, в которой будет построение графика. Установка начальных значений максимального и минимального баланса (эти переменные потом будут использоваться для задания максимума и минимума графика).
// если это первая сделка if(i==0) { // если отчетный период начинается раньше первой сделки, // то начало отчетного периода будет с первой сделки if(StartTime<time_curr) StartTime=time_curr; // если отчетный период заканчивается позже текущего времени, // то конец отчетного периода соответствует текущему времени if(EndTime>TimeCurrent()) EndTime=TimeCurrent(); // начальные значения максимального и минимального баланса // равны текущему балансу max_val=balance; min_val=balance; // определение периода графика баланса в зависимости от продолжительности // отчетного интервала 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; // изменение периода открытого графика ChartSetSymbolPeriod(hChart,Symbol(),Picture1_period); }
Если эта сделка не первая - создание объекта "линия", при помощи которого строится график изменения баланса. Линия рисуется только в том случае, если хотя бы один ее конец попадает в отчетный интервал. Если попадают оба конца - линия будет "толстой". Указание цвета линии баланса (зеленый). Если значение баланса выходит за диапазон минимального и максимального баланса - корректировка диапазона.
else // если это не первая сделка { // рисование линии баланса, если сделка попадает в отчетный интервал // и указание свойств линии баланса 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(time_prev>=StartTime && time_curr<=EndTime) ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_WIDTH,2); } // если новое значение баланса выходит за диапазон // минимального и максимального значения, то их скорректировать if(balance<min_val) min_val=balance; if(balance>max_val) max_val=balance; }
Заносим предыдущее значение времени в соответствующую переменную.
// изменение предыдущего значения времени
time_prev=time_curr;
Если сделка еще не обработана - будем обрабатывать ее.
// если эта сделка еще не обработана if(deal_status[i]<127) {
Если эта сделка - начисление баланса и она попадает в отчетный интервал - выводится в отчет соответствующая строка. Сделка помечается как обработанная.
// если эта сделка - начисление баланса if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE) { // если она попадает в отчетный интервал - запись строки в файл 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>"); // пометка сделки как обработанной deal_status[i]=127; }
Если эта сделка - покупка или продажа, проверим, есть ли такой инструмент в списке инструментов (массиве symb_list[]); если нет - то занесем. Переменная symb_pointer указывает на элемент массива symb_list[], в котором находится название инструмента текущей сделки.
// если это сделка покупки или продажи if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY || HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // проверка, есть ли в списке инструментов инструмент этой сделки 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(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, которые используются для хранения начального и конечного времени жизни позиции.
// установка начального значения для времени начала сделки position_StartTime=time_curr; // установка начального значения для времени конца сделки 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 занесем также название финансового инструмента и ссылку на картинку, соответствующую направлению открытой позиции. Занесем во все эти переменные начальные значения.
// формирование строки в отчете - инструмент, направление позиции, начало таблицы для объемов для входов в рынок 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'>"); // формирование начала таблицы времени для входов в рынок in_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы цен для входов в рынок in_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы объемов для выходов из рынка out_table_volume="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы времени для выходов из рынка out_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы цен для выходов из рынка out_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы свопов для выходов из рынка out_table_swap="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // формирование начала таблицы прибыли для выходов из рынка out_table_profit="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>";
Перебираем все сделки начиная с текущей до тех пор, пока позиция не будет закрыта. Обрабатываем их, если они не обработаны ранее.
// перебор всех сделок для данной позиции начиная с текущей (пока позиция не будет закрыта) for(j=i;j<total_deals_number;j++) { // если сделка не обработана - обработать if(deal_status[j]<127) {
Выбираем сделку, получаем ее тикет.
// выбор сделки, получение тикета ticket=HistoryDealGetTicket(j);
Если сделка по тому же инструменту, что и открытая позиция - обрабатываем ее. Получаем время сделки. Если время сделки выходит на границы диапазона времени позиции - расширяем диапазон. Получаем объем сделки.
// если инструмент сделки совпадает с инструментом позиции, которая обрабатывается if(symb_list[symb_pointer]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { // получение времени сделки time_curr=HistoryDealGetInteger(ticket,DEAL_TIME); // если время сделки выходит за пределы диапазона времени позиции // - расширить время позиции if(time_curr<position_StartTime) position_StartTime=time_curr; if(time_curr>position_EndTime) position_EndTime=time_curr; // получение объема сделки lot_current=HistoryDealGetDouble(ticket,DEAL_VOLUME);
Сделки на покупку и на продажу обрабатываем раздельно. Начнем со сделок на покупку.
// если эта сделка - покупка if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) {
Если уже открыта позиция на продажу - то эта сделка на покупку будет выходом из рынка. А если объем сделки будет больше, чем объем открытой короткой позиции - то это будет переворот. Заносим в строковые переменные необходимые значения. Заносим в массив deal_status[] значение 127, если сделка полностью обработана, или 1, если это переворот и эту сделку нужно будет анализировать для другой позиции.
// если уже открыта позиция на продажу - то это будет выход из рынка if(NormalizeDouble(lots_list[symb_pointer],2)<0) { // если объем покупки больше, чем объем открытой короткой позиции - то это переворот if(NormalizeDouble(lot_current+lots_list[symb_pointer],2)>0) { // формирование таблицы объемов для выхода из рынка - указание только того объема, который был у открытой короткой позиции StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // пометка позиции как частично обработанной deal_status[j]=1; } else { // если объем покупки равен или меньше объема открытой короткой позиции - то это частичное или полное закрытие // формирование таблицы объемов для выхода из рынка StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // пометка сделки как обработанной deal_status[j]=127; } // формирование таблицы времени для выходов из рынка StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>", TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // формирование таблицы цен для выходов из рынка 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>"); // получение свопа текущей сделки current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // если своп равен нулю - формирование пустой строки таблицы свопов для выхода из рынка if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // иначе формирование строки со свопом в таблице свопов для выхода из рынка else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // получение профита текущей сделки current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // если прибыль отрицательная (убыток) - отображается в таблице прибыли для выхода из рынка красным цветом 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 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(deal_status[j]==1) { // формирование таблицы объемов входов в рынок (заносится объем, образовавшийся после переворота) StringConcatenate(in_table_volume,in_table_volume, "<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // компенсация изменения объема, которая будет произведена (объем этой сделки уже учтен ранее) lots_list[symb_pointer]-=lot_current; } // если эта сделка еще не обрабатывалась, формирование таблицы объемов для входов в рынок else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>", DoubleToString(lot_current,2),"</td></tr>"); // формирование таблицы времени входов в рынок StringConcatenate(in_table_time,in_table_time,"<tr><td align center>", TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // формирование таблицы цен входов в рынок 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>"); // пометка сделки как обработанной deal_status[j]=127; }
Изменяем объем позиции на объем текущей сделки. Если позиция закрыта (объем равен нулю) - прекращаем заниматься этой позицией (выходим из цикла с переменной j) и ищем следующую необработанную сделку (в цикле с переменной i).
// изменение объема позиции по текущему инструменту с учетом объема текущей сделки lots_list[symb_pointer]+=lot_current; // если объем открытой позиции по текущему инструменту стал равен нулю - позиция закрыта if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; }
Аналогичным образом обрабатываются и сделки на продажу, после чего выходим из цикла с переменной j.
// если эта сделка - продажа if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // если уже открыта позиция на покупку - то это будет выход из рынка if(NormalizeDouble(lots_list[symb_pointer],2)>0) { // если объем продажи больше, чем объем открытой длинной позиции - то это переворот if(NormalizeDouble(lot_current-lots_list[symb_pointer],2)>0) { // формирование таблицы объемов для выхода из рынка - указание только того объема, который был у открытой длинной позиции StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>", DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // пометка позиции как частично обработанной deal_status[j]=1; } else { // если объем продажи равен или меньше объема открытой длинной позиции - то это частичное или полное закрытие // формирование таблицы объемов для выхода из рынка StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // пометка сделки как обработанной deal_status[j]=127; } // формирование таблицы времени для выходов из рынка StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>", TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // формирование таблицы цен для выходов из рынка 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>"); // получение свопа текущей сделки current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // если своп равен нулю - формирование пустой строки таблицы свопов для выхода из рынка if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // иначе формирование строки со свопом в таблице свопов для выхода из рынка else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // получение профита текущей сделки current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // если прибыль отрицательная (убыток) - отображается в таблице прибыли для выхода из рынка красным цветом 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 StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); } else // если уже открыта позиция на продажу - это будет вход в рынок { // если эта сделка уже частично обработана (был переворот) if(deal_status[j]==1) { // формирование таблицы объемов входов в рынок (заносится объем, образовавшийся после переворота) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>", DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // компенсация изменения объема, которое будет произведено (объем этой сделки уже учтен ранее) lots_list[symb_pointer]+=lot_current; } // если эта сделка еще не обрабатывалась, формирование таблицы объемов для входов в рынок else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>", DoubleToString(lot_current,2),"</td></tr>"); // формирование таблицы времени входов в рынок StringConcatenate(in_table_time,in_table_time,"<tr><td align='center'>", TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // формирование таблицы цен входов в рынок 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>"); // пометка сделки как обработанной deal_status[j]=127; } // изменение объема позиции по текущему инструменту с учетом объема текущей сделки lots_list[symb_pointer]-=lot_current; // если объем открытой позиции по текущему инструменту стал равен нулю - позиция закрыта if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; } } } }
Если время, когда позиция была открыта, попадает в отчетный интервал (или хотя бы частично накладывается на него) - соответствующая запись выводится в файл "report.html".
// если интервал времени позиции накладывается на отчетный интервал - позиция выводится в отчет 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.
}
// изменение баланса
balance_prev=balance;
}
Записываем конец html-файла (ссылки на картинки, конец выравнивания по центру, конец основной части, конец html-документа). Закрываем файл "report.html".
// формирование конца html-файла 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>"); // закрытие файла FileClose(file_handle);Ожидание обновления графика не дольше, чем время, указанное в константе timeout.
// получение текущего времени time_curr=TimeCurrent(); // ожидание обновления графика while(SeriesInfoInteger(Symbol(),Picture1_period,SERIES_BARS_COUNT)==0 && TimeCurrent()-time_curr<timeout) Sleep(1000);
Установка фиксированного максимума и минимума графика.
// указание максимального и минимального значения для графика баланса (10% отступ от верхней и нижней границы) ChartSetDouble(hChart,CHART_FIXED_MAX,max_val+(max_val-min_val)/10); ChartSetDouble(hChart,CHART_FIXED_MIN,min_val-(max_val-min_val)/10);
Установка свойств графика баланса.
// установка свойств графика баланса ChartSetInteger(hChart,CHART_MODE,CHART_LINE); // график в виде линии ChartSetInteger(hChart,CHART_FOREGROUND,false); // график на переднем плане ChartSetInteger(hChart,CHART_SHOW_BID_LINE,false); // линию BID не показывать ChartSetInteger(hChart,CHART_COLOR_VOLUME,White); // объемы и уровни ордеров белые ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,White); // уровни SL и TP белые ChartSetInteger(hChart,CHART_SHOW_GRID,true); // сетку показывать ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // сетка светло-серая ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,false); // разделители периодов не показывать ChartSetInteger(hChart,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); // объемы не показывать ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,White); // график белый ChartSetInteger(hChart,CHART_SCALE,0); // масштаб минимальный ChartSetInteger(hChart,CHART_SCALEFIX,true); // фиксированный масштаб по вертикали ChartSetInteger(hChart,CHART_SHIFT,false); // смещения графика нет ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // автопрокрутка включена ChartSetString(hChart,CHART_COMMENT,"BALANCE"); // комментарий на графике
Перерисовка графика баланса.
// перерисовка графика баланса ChartRedraw(hChart); Sleep(8000);
Создание скриншота графика (запись картинки "picture1.gif"). Ширина графика подстраивается под ширину отчетного интервала (но из-за наличия выходных дней часто вносится погрешность и график получается шире, чем кривая изменения баланса), высота вычисляется как половина ширины.
// запись картинки - графика баланса ChartScreenShot(hChart,"picture1.gif",(int)(EndTime-StartTime)/PeriodSeconds(Picture1_period), (int)(EndTime-StartTime)/PeriodSeconds(Picture1_period)/2,ALIGN_RIGHT);
Удаление всех объектов с графика и его закрытие.
// удаление всех объектов с графика баланса ObjectsDeleteAll(hChart); // закрытие графика ChartClose(hChart);
Если разрешена отправка файлов по протоколу FTP, отправляются три файла: "report.html", picture1.gif" и "picture2.gif".
// если разрешена публикация отчета - отправка по FTP-протоколу // html-файла и двух картинок - графика цены и баланса if(TerminalInfoInteger(TERMINAL_FTP_ENABLED)) { SendFTP("report.html"); SendFTP("picture1.gif"); SendFTP("picture2.gif"); } }
На этом написание программы завершено. Для того, чтобы работала отправка файлов по FTP-протоколу, необходимо настроить параметры в торговом терминале MetaTrader 5. Для этого нужно зайти в меню Сервис, раздел Настройки и открыть вкладку Публикация (Рис.4).
Рисунок 4. Настройки публикации отчета по FTP-протоколу
В настройках нужно поставить птицу возле пункта "Разрешить", указать номер счета, адрес FTP сервера, директорий на нем, логин и пароль для доступа. Частота обновления и тип отчета не имеют значения (они используются для отправки стандартного отчета, формируемого автоматически MetaTrader 5).
Теперь можно запускать скрипт. После запуска на экране на несколько секунд появляется и изчезает график изменения баланса. В журнале можно посмотреть, есть ли ошибки, удалось ли отправить на сервер файлы по протоколу FTP. Если все работает нормально, то на сервере в указанном в настройках директории появятся три новых файла. Если там разместить еще два файла с картинками-стрелочками, WWW-сервер настроен и работает - можно при помощи браузера зайти и увидеть отчет, подобный тому, который находится в приложенных к статье файлах или изображен в начале статьи.
2. Отправка уведомлений в виде SMS-сообщений на мобильный телефон
Бывают случаи, когда вы находитесь вдали от компьютера и других электронных устройств, а под рукой есть только мобильный телефон. Но вы хотите контролировать ход торговли на вашем счете или следить за котировками по финансовому инструменту. В таком случае можно настроить отправку уведомлений в виде SMS-сообщений на мобильный телефон. Многие операторы мобильной связи (и сторонние организации) предоставляют услугу EMAIL-SMS, что позволяет получать на телефон сообщения, отправленные в виде писем на определенный электронный адрес.
Для работы уведомлений необходимо наличие ящика электронной почты (точнее, SMTP сервера). Необходимо внести настройки в торговом терминале MetaTrader 5. Для этого войдите в меню Сервис, раздел Настройки и откройте вкладку Почта (Рис.5).
Рисунок 5. Настройки отправки сообщений по электронной почте
В настройках нужно поставить галочку возле пункта "Разрешить", указать адрес SMTP сервера, логин и пароль для доступа, адрес отправителя (адрес вашего ящика электронной почты) и адрес получателя - адрес электронной почты, при отправке сообщений на который, они пересылаются в виде SMS-сообщений (уточните у своего оператора мобильной связи). Если все указано правильно, при нажатии на кнопку "Тест" будет отправлено проверочное сообщение (дополнительную информацию можно посмотреть в журнале).
Самый простой способ сделать уведомление при достижении ценой определенного уровня - создать сигнал (Alert). Это можно сделать, открыв соответствующую вкладку в окне "Инструменты", нажав правую кнопку мыши и выбрав "Создать" (Рис.6).
Рисунок 6. Создание сигнала.
В появившемся окне поставьте птицу возле пункта "Разрешить", укажите действие - "Письмо", выберите финансовый инструмент, условие, введите значение для условия и напишите текст сообщения. В поле "Максимум повторений" поставьте 1, если не хотите, чтобы сообщение приходило многократно. Когда все поля заполнены, нажмите "OK".
Если мы будем отправлять сообщение из программы, написанной на языке MQL5, у нас появится больше возможностей. Будем использовать функцию SendMail(). Она имеет два параметра. Первый - заголовок письма, второй - тело письма.
Функцию SendMail() можно вызывать после торгового запроса (функции OrderSend()) или в обработчике события Trade - таким образом мы будем иметь уведомления о торговых событиях - входах в рынок, установке ордеров, закрытии позиций. А можно SendMail() поставить внутри функции OnTimer() - будем получать периодические уведомления о текущих котировках. Можно сделать отправку уведомлений при появлении некоторых торговых сигналов - пересечении линий индикаторов, достижения ценой линий и уровней и т.п.
Рассмотрим примеры.
Если в советнике или скрипте заменить строчку
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() запустить таймер с помощью функции 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-файл и файлы с изображениями, как эти файлы загрузить на WWW-сервер по протоколу FTP. Показано, как настроить отправку уведомлений на мобильный телефон в виде SMS-сообщений.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Нет рисунков в начале, только одни названия.
Нет рисунков в начале, только одни названия.