English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Создание и публикация отчетов о результатах торговли, отправка SMS-сообщений

Создание и публикация отчетов о результатах торговли, отправка SMS-сообщений

MetaTrader 5Примеры | 2 июня 2010, 13:56
6 659 3
Denis Zyatkevich
Denis Zyatkevich

Введение

В данной статье описано как с помощью советника, индикатора или скрипта сформировать отчет о результатах торговли в виде 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. Пример отчета - таблица сделок и позиций.

Рисунок 1. Пример отчета - таблица сделок и позиций

Рис.2. Пример отчета - график баланса.

Рисунок 2. Пример отчета - график баланса

 Рис.3. Пример отчета - график цены по текущему инструменту.

Рисунок 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-протоколу.

Рисунок 4. Настройки публикации отчета по FTP-протоколу

В настройках нужно поставить птицу возле пункта "Разрешить", указать номер счета, адрес FTP сервера, директорий на нем, логин и пароль для доступа. Частота обновления и тип отчета не имеют значения (они используются для отправки стандартного отчета, формируемого автоматически MetaTrader 5).

Теперь можно запускать скрипт. После запуска на экране на несколько секунд появляется и изчезает график изменения баланса. В журнале можно посмотреть, есть ли ошибки, удалось ли отправить на сервер файлы по протоколу FTP. Если все работает нормально, то на сервере в указанном в настройках директории появятся три новых файла. Если там разместить еще два файла с картинками-стрелочками, WWW-сервер настроен и работает - можно при помощи браузера зайти и увидеть отчет, подобный тому, который находится в приложенных к статье файлах или изображен в начале статьи.

2. Отправка уведомлений в виде SMS-сообщений на мобильный телефон

Бывают случаи, когда вы находитесь вдали от компьютера и других электронных устройств, а под рукой есть только мобильный телефон. Но вы хотите контролировать ход торговли на вашем счете или следить за котировками по финансовому инструменту. В таком случае можно настроить отправку уведомлений в виде SMS-сообщений на мобильный телефон. Многие операторы мобильной связи (и сторонние организации) предоставляют услугу EMAIL-SMS, что позволяет получать на телефон сообщения, отправленные в виде писем на определенный электронный адрес.

Для работы уведомлений необходимо наличие ящика электронной почты (точнее, SMTP сервера). Необходимо внести настройки в торговом терминале MetaTrader 5. Для этого войдите в меню Сервис, раздел Настройки и откройте вкладку Почта (Рис.5).

Рисунок 5. Настройки отправки сообщений по электронной почте

Рисунок 5. Настройки отправки сообщений по электронной почте

В настройках нужно поставить галочку возле пункта "Разрешить", указать адрес SMTP сервера, логин и пароль для доступа, адрес отправителя (адрес вашего ящика электронной почты) и адрес получателя - адрес электронной почты, при отправке сообщений на который, они пересылаются в виде SMS-сообщений (уточните у своего оператора мобильной связи). Если все указано правильно, при нажатии на кнопку "Тест"  будет отправлено проверочное сообщение (дополнительную информацию можно посмотреть в журнале).

Самый простой способ сделать уведомление при достижении ценой определенного уровня - создать сигнал (Alert). Это можно сделать, открыв соответствующую вкладку в окне "Инструменты", нажав правую кнопку мыши и выбрав "Создать" (Рис.6).

Рис.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-сообщений.

Прикрепленные файлы |
report.zip (33.08 KB)
sendreport.mq5 (32.38 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Iurii Tokman
Iurii Tokman | 2 июн. 2010 в 14:43
Нет рисунков в начале, только одни названия.

Rashid Umarov
Rashid Umarov | 2 июн. 2010 в 15:27
satop:
Нет рисунков в начале, только одни названия.

Приложите скриншот, пожалуйста. И сообщите версию браузера. У меня все видно.
Denis Zyatkevich
Denis Zyatkevich | 2 июн. 2010 в 15:46
satop:
Нет рисунков в начале, только одни названия.

Попробуйте обновить страницу. Виден хоть один рисунок из статьи? Видны ли рисунки в других статьях?
Пошаговое руководство по написанию MQL5-советников для начинающих Пошаговое руководство по написанию MQL5-советников для начинающих
Написание советников на MQL5 проще чем кажется, вы легко можете этому научиться. В этом руководстве вы познакомитесь с основными моментами, необходимыми для написания простого советника на основе конкретной торговой стратегии. Рассмотрена структура советника, использование встроенных технических индикаторов и торговых функций, вопросы отладки и тестирования советника на исторических данных.
Обработка торговых событий в эксперте при помощи функции OnTrade() Обработка торговых событий в эксперте при помощи функции OnTrade()
В 5-ой версии языка MQL появилась масса нововведений, в том числе работа с событиями различных типов (события таймера, торговые события, пользовательские и т.д.). Возможность обработки событий позволяет создавать совершенно новый тип программ для автоматического и полуавтоматического трейдинга. В этой статье мы рассмотрим торговые события и напишем для функции OnTrade() код, который будет обрабатывать событие Trade.
Как заказать торгового робота на MQL5 и MQL4 Как заказать торгового робота на MQL5 и MQL4
Сервис "Фриланс" является самой большой биржей для заказа торговых роботов и технических индикаторов. Сотни профессиональных разработчиков готовы написать торговое приложение для терминала MetaTrader 4/5.
Новые возможности с MetaTrader 5 Новые возможности с MetaTrader 5
MetaTrader 4 завоевал популярность у трейдеров по всему миру, и казалось бы, нельзя желать большего. Высокая производительность и стабильность, широкие возможности по написанию индикаторов, экспертов и торгово-информационных систем, возможность выбора любого из нескольких сотен брокеров - вот те основные преимущества, которые выделяют этот терминал на фоне всех остальных. Но время не стоит на месте, и вот мы уже стоим перед выбором - MetaTrader 4 или MetaTrader 5. В этой статье мы опишем основные отличия терминала 5-го поколения от нынешнего фаворита.