952 lines
No EOL
61 KiB
MQL5
952 lines
No EOL
61 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Article-15346-MQL5-Trade-Monitor-Push-Service.mq5 |
|
|
//| Copyright 2026, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property service
|
|
|
|
#define COUNTER_DELAY 1000 // Задержка счётчика в миллисекундах в рабочем цикле
|
|
#define REFRESH_ATTEMPTS 5 // Количество попыток получения корректных данных аккаунта
|
|
#define REFRESH_DELAY 500 // Задержка в миллисекундах перед очередной попыткой получения данных
|
|
#define TABLE_COLUMN_W 10 // Ширина колонки таблицы статистики для вывода в журнал
|
|
|
|
#include <Arrays\ArrayString.mqh> // Динамический массив переменных типа string для объекта списка символов
|
|
#include <Arrays\ArrayLong.mqh> // Динамический массив переменных типа long для объекта списка магиков
|
|
#include <Tools\DateTime.mqh> // Расширение структуры MqlDateTime
|
|
#include "Accounts.mqh" // Класс-коллекция объектов-аккаунтов
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Перечисления |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_USED_ACCOUNTS // Перечисление используемых аккаунтов в статистике
|
|
{
|
|
USED_ACCOUNT_CURRENT, // Current Account only
|
|
USED_ACCOUNTS_ALL, // All used accounts
|
|
};
|
|
|
|
enum ENUM_REPORT_RANGE // Перечисление диапазонов статистики
|
|
{
|
|
REPORT_RANGE_DAILY, // Сутки
|
|
REPORT_RANGE_WEEK_BEGIN, // С начала недели
|
|
REPORT_RANGE_MONTH_BEGIN, // С начала месяца
|
|
REPORT_RANGE_YEAR_BEGIN, // С начала года
|
|
REPORT_RANGE_NUM_DAYS, // Количество дней
|
|
REPORT_RANGE_NUM_MONTHS, // Количество месяцев
|
|
REPORT_RANGE_NUM_YEARS, // Количество лет
|
|
REPORT_RANGE_ALL, // Весь период
|
|
};
|
|
|
|
enum ENUM_REPORT_BY // Перечисление фильтров статистики
|
|
{
|
|
REPORT_BY_RANGE, // Диапазон дат
|
|
REPORT_BY_SYMBOLS, // По символам
|
|
REPORT_BY_MAGICS, // По магикам
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Входные параметры |
|
|
//+------------------------------------------------------------------+
|
|
input group "============== Report options =============="
|
|
input ENUM_USED_ACCOUNTS InpUsedAccounts = USED_ACCOUNT_CURRENT;// Accounts included in statistics
|
|
input bool InpReportBySymbols = true; // Reports by Symbol
|
|
input bool InpReportByMagics = true; // Reports by Magics
|
|
input bool InpCommissionsInclude= true; // Including Comissions
|
|
input bool InpSpreadInclude = true; // Including Spread
|
|
|
|
input group "========== Daily reports for daily periods =========="
|
|
input bool InpSendDReport = true; // Send daily report (per day and specified periods)
|
|
input uint InpSendDReportHour = 8; // Hour of sending the report (Local time)
|
|
input uint InpSendDReportMin = 0; // Minutes of sending the report (Local time)
|
|
|
|
input group "========= Daily reports for specified periods ========="
|
|
input bool InpSendSReportDays = true; // Send a report for the specified num days
|
|
input uint InpSendSReportDaysN = 7; // Number of days to report for the specified number of days
|
|
input bool InpSendSReportMonths = true; // Send a report for the specified num months
|
|
input uint InpSendSReportMonthsN= 3; // Number of months to report for the specified number of months
|
|
input bool InpSendSReportYears = true; // Send a report for the specified num years
|
|
input uint InpSendSReportYearN = 2; // Number of years to report for the specified number of years
|
|
|
|
input group "======== Weekly reports for all other periods ========"
|
|
input ENUM_DAY_OF_WEEK InpSendWReportDayWeek= SATURDAY; // Day of sending the reports (Local time)
|
|
input uint InpSendWReportHour = 8; // Hour of sending the reports (Local time)
|
|
input uint InpSendWReportMin = 0; // Minutes of sending the reports (Local time)
|
|
input bool InpSendWReport = true; // Send a report for the current week
|
|
input bool InpSendMReport = false; // Send a report for the current month
|
|
input bool InpSendYReport = false; // Send a report for the current year
|
|
input bool InpSendAReport = false; // Send a report for the entire trading period
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Глобальные переменные |
|
|
//+------------------------------------------------------------------+
|
|
CAccounts ExtAccounts; // Объект управления аккаунтами
|
|
long ExtLogin; // Логин текущего аккаунта
|
|
string ExtServer; // Сервер текущего аккаунта
|
|
bool ExtNotify; // Флаг разрешения Push-уведомлений
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Service program start function |
|
|
//+------------------------------------------------------------------+
|
|
void OnStart()
|
|
{
|
|
//---
|
|
CArrayObj *PositionsList = NULL; // Список закрытых позиций аккаунтов
|
|
long account_prev = 0; // Прошлый логин
|
|
double balance_prev = EMPTY_VALUE; // Прошлый баланс
|
|
bool Sent = false; // Флаг отправленного отчёта за не дневные периоды
|
|
int day_of_year_prev= WRONG_VALUE; // Прошлый номер дня в году
|
|
|
|
//--- Создаём списки торгуемых в истории символов и магиков и список сообщений для Push-уведомлений
|
|
CArrayString *SymbolsList = new CArrayString();
|
|
CArrayLong *MagicsList = new CArrayLong();
|
|
CArrayString *MessageList = new CArrayString();
|
|
if(SymbolsList==NULL || MagicsList==NULL || MessageList==NULL)
|
|
{
|
|
Print("Failed to create list CArrayObj");
|
|
return;
|
|
}
|
|
|
|
//--- Проверяем наличие MetaQuotes ID и разрешение отправки на него уведомлений
|
|
ExtNotify=CheckMQID();
|
|
if(ExtNotify)
|
|
Print(MQLInfoString(MQL_PROGRAM_NAME)+"-Service notifications OK");
|
|
|
|
//--- Основной цикл
|
|
int count=0;
|
|
while(!IsStopped())
|
|
{
|
|
//+------------------------------------------------------------------+
|
|
//| Задержка в цикле |
|
|
//+------------------------------------------------------------------+
|
|
//--- Увеличиваем счётчик цикла. Если счётчик не превысил заданного значения - повторяем
|
|
Sleep(16);
|
|
count+=10;
|
|
if(count<COUNTER_DELAY)
|
|
continue;
|
|
//--- Ожидание завершено. Сбрасываем счётчик цикла
|
|
count=0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Проверка настроек уведомлений |
|
|
//+------------------------------------------------------------------+
|
|
//--- Если флаг уведомлений не установлен - проверяем настройки уведомлений в терминале и, если активированы - сообщаем об этом
|
|
if(!ExtNotify && TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
|
|
{
|
|
Print("Now MetaQuotes ID is specified and sending notifications is allowed");
|
|
SendNotification("Now MetaQuotes ID is specified and sending notifications is allowed");
|
|
ExtNotify=true;
|
|
}
|
|
//--- Если флаг уведомлений установлен, но в терминале нет на них разрешения - сообщаем об этом
|
|
if(ExtNotify && (!TerminalInfoInteger(TERMINAL_MQID) || !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)))
|
|
{
|
|
string caption=MQLInfoString(MQL_PROGRAM_NAME);
|
|
string message="The terminal has a limitation on sending notifications. Please check your notification settings";
|
|
MessageBox(message, caption, MB_OK|MB_ICONWARNING);
|
|
ExtNotify=false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Смена аккаунта |
|
|
//+------------------------------------------------------------------+
|
|
//--- Если текущий логин не равен предыдущему
|
|
if(AccountInfoInteger(ACCOUNT_LOGIN)!=account_prev)
|
|
{
|
|
//--- если не дождались обновления данных аккаунта - повторим на следующей итерации цикла
|
|
if(!DataUpdateWait(balance_prev))
|
|
continue;
|
|
|
|
//--- Получены данные нового аккаунта
|
|
//--- Сохраним текущие логин и баланс как предыдущие для следующей проверки
|
|
account_prev=AccountInfoInteger(ACCOUNT_LOGIN);
|
|
balance_prev=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
//--- Сбросим флаг отправленного сообщения и вызовем обработчик смены аккаунта
|
|
Sent=false;
|
|
AccountChangeHandler();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Ежедневные отчёты |
|
|
//+------------------------------------------------------------------+
|
|
//--- Заполним структуру данными о локальном времени и дате
|
|
MqlDateTime tm={};
|
|
TimeLocal(tm);
|
|
|
|
//--- Очистим список сообщений, отправляемых на MQID
|
|
MessageList.Clear();
|
|
|
|
//--- Если текущий номер дня в году не равен прошлому - это начало нового дня
|
|
if(tm.day_of_year!=day_of_year_prev)
|
|
{
|
|
//--- Если часы/минуты достигли заданных значений для отправки статистики
|
|
if(tm.hour>=(int)InpSendDReportHour && tm.min>=(int)InpSendDReportMin)
|
|
{
|
|
//--- Если разрешена отправка ежедневной статистики
|
|
if(InpSendDReport)
|
|
{
|
|
//--- обновляем списки закрытых позиций за сутки на текущем аккаунте
|
|
ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
|
|
//--- если в настройках задано получение статистики со всех аккаунтов -
|
|
//--- получаем список закрытых позиций всех аккаунтов, бывших активными при работе сервиса
|
|
if(InpUsedAccounts==USED_ACCOUNTS_ALL)
|
|
PositionsList=ExtAccounts.GetCommonPositionsList();
|
|
//--- иначе - получаем список закрытых позиций только текущего на данный момент аккаунта
|
|
else
|
|
PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
|
|
|
|
//--- Создаём сообщения о торговой статистике за дневной диапазон времени,
|
|
//--- распечатываем созданные сообщения в журнал и отправляем их на MQID
|
|
SendReport(REPORT_RANGE_DAILY, 0, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за указанное количество дней,
|
|
//--- Создаём сообщения о торговой статистике за количество дней в InpSendSReportDaysN,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendSReportDays)
|
|
SendReport(REPORT_RANGE_NUM_DAYS, InpSendSReportDaysN, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за указанное количество месяцев,
|
|
//--- Создаём сообщения о торговой статистике за количество месяцев в InpSendSReportMonthsN,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendSReportMonths)
|
|
SendReport(REPORT_RANGE_NUM_MONTHS, InpSendSReportMonthsN, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за указанное количество лет,
|
|
//--- Создаём сообщения о торговой статистике за количество лет в InpSendSReportYearN,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendSReportYears)
|
|
SendReport(REPORT_RANGE_NUM_YEARS, InpSendSReportYearN, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
}
|
|
|
|
//--- Записываем текущий день как прошлый для последующей проверки
|
|
day_of_year_prev=tm.day_of_year;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Еженедельные отчёты |
|
|
//+------------------------------------------------------------------+
|
|
//--- Если день недели равен устанорвленному в настройках,
|
|
if(tm.day_of_week==InpSendWReportDayWeek)
|
|
{
|
|
//--- если сообщение ещё не отправлено и наступило время отправки сообщений
|
|
if(!Sent && tm.hour>=(int)InpSendWReportHour && tm.min>=(int)InpSendWReportMin)
|
|
{
|
|
//--- обновляем списки закрытых позиций на текущем аккаунте
|
|
ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
|
|
|
|
//--- если в настройках задано получение статистики со всех аккаунтов -
|
|
//--- получаем список закрытых позиций всех аккаунтов, бывших активными при работе сервиса
|
|
if(InpUsedAccounts==USED_ACCOUNTS_ALL)
|
|
PositionsList=ExtAccounts.GetCommonPositionsList();
|
|
//--- иначе -получаем список закрытых позиций только текущего на данный момент аккаунта
|
|
else
|
|
PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за неделю,
|
|
//--- Создаём сообщения о торговой статистике с начала текущей недели,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendWReport)
|
|
SendReport(REPORT_RANGE_WEEK_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за месяц,
|
|
//--- Создаём сообщения о торговой статистике с начала текущего месяца,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendMReport)
|
|
SendReport(REPORT_RANGE_MONTH_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за год,
|
|
//--- Создаём сообщения о торговой статистике с начала текущго года,
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendYReport)
|
|
SendReport(REPORT_RANGE_YEAR_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Если в настройках разрешена отправка торговой статистики за весь период,
|
|
//--- Создаём сообщения о торговой статистике с начала эпохи (01.01.1970 00:00),
|
|
//--- распечатываем созданные сообщения в журнал и добавляем их в список для отправки на MQID
|
|
if(InpSendAReport)
|
|
SendReport(REPORT_RANGE_ALL, 0, PositionsList, SymbolsList, MagicsList, MessageList);
|
|
|
|
//--- Устанавливаем флаг, что все сообщения со статистикой в журнал распечатаны
|
|
Sent=true;
|
|
}
|
|
}
|
|
//--- Если ещё не наступил указанный в настройках день недели для отправки статистики - сбрасываем флаг отправленных сообщений
|
|
else
|
|
Sent=false;
|
|
|
|
//--- Если список сообщений для отправки на MQID не пустой - вызываем функцию отправки уведомлений на смартфон
|
|
if(MessageList.Total()>0)
|
|
SendMessage(MessageList);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Завершение работы сервиса |
|
|
//+------------------------------------------------------------------+
|
|
//--- Очищаем и удаляем списки сообщений, символов и магиков
|
|
if(MessageList!=NULL)
|
|
{
|
|
MessageList.Clear();
|
|
delete MessageList;
|
|
}
|
|
if(SymbolsList!=NULL)
|
|
{
|
|
SymbolsList.Clear();
|
|
delete SymbolsList;
|
|
}
|
|
if(MagicsList!=NULL)
|
|
{
|
|
MagicsList.Clear();
|
|
delete MagicsList;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Проверяет наличие в терминале MetaQuotes ID |
|
|
//| и разрешение отправки уведомлений на мобильный терминал |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckMQID(void)
|
|
{
|
|
string caption=MQLInfoString(MQL_PROGRAM_NAME); // Заголовок окна сообщений
|
|
string message=caption+"-Service OK"; // Текст окна сообщений
|
|
int mb_id=IDOK; // Код возврата MessageBox()
|
|
|
|
//--- Если в настройках терминала не установлен MQID - сделаем запрос на его установку с пояснениями о порядке действий
|
|
if(!TerminalInfoInteger(TERMINAL_MQID))
|
|
{
|
|
message="The client terminal does not have a MetaQuotes ID for sending Push notifications.\n"+
|
|
"1. Install the mobile version of the MetaTrader 5 terminal from the App Store or Google Play.\n"+
|
|
"2. Go to the \"Messages\" section of your mobile terminal.\n"+
|
|
"3. Click \"MQID\".\n"+
|
|
"4. In the client terminal, in the \"Tools - Settings\" menu, in the \"Notifications\" tab, in the MetaQuotes ID field, enter the received code.";
|
|
mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONWARNING);
|
|
}
|
|
|
|
//--- Если нажата кнопка "Cancel" - сообщим об отказе от использования Push-уведомлений
|
|
if(mb_id==IDCANCEL)
|
|
{
|
|
message="You refused to enter your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal";
|
|
MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
|
|
}
|
|
|
|
//--- Если нажата кнопка "Retry" -
|
|
else
|
|
{
|
|
//--- Если в терминале установлен MetaQuotes ID для отправки Push-уведомлений
|
|
if(TerminalInfoInteger(TERMINAL_MQID))
|
|
{
|
|
//--- если в терминале отсутствует разрешение на отправку уведомлений на смартфон
|
|
if(!TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
|
|
{
|
|
//--- показываем сообщение с просьбой дать разрешение на отправку уведомлений в настройках
|
|
message="Please enable sending Push notifications in the terminal settings in the \"Notifications\" tab in the \"Tools - Settings\" menu.";
|
|
mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONEXCLAMATION);
|
|
|
|
//--- Если в ответ на сообщение нажата кнопка Cancel
|
|
if(mb_id==IDCANCEL)
|
|
{
|
|
//--- сообщаем об отказе от отправки уведомлений на смартфон
|
|
string message="You have opted out of sending Push notifications. The service will send notifications to the “Experts” tab of the terminal.";
|
|
MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
|
|
}
|
|
//--- Если в ответ на сообщение нажата кнопка Retry (ожидается, что сделано это будет после включения разрешения в настройках),
|
|
//--- но разрешения на отправку уведомлений в терминале так и нет,
|
|
if(mb_id==IDRETRY && !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
|
|
{
|
|
//--- сообщаем, что пользователь отказался от отправки уведомлений на смартфон, и сообщения будут только в журнале
|
|
string message="You have not allowed push notifications. The service will send notifications to the “Experts” tab of the terminal.";
|
|
MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
|
|
}
|
|
}
|
|
}
|
|
//--- Если в терминале не установлен MetaQuotes ID для отправки Push-уведомлений
|
|
else
|
|
{
|
|
//--- сообщаем, что в терминале не установлен MetaQuotes ID для отправки уведомлений на смартфон, и сообщения будут только в журнале
|
|
string message="You have not set your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal";
|
|
MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
|
|
}
|
|
}
|
|
//--- Возвращаем флаг, что MetaQuotes ID в терминале установлен и отправка уведомлений разрешена
|
|
return(TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Ожидает обновления данных аккаунта |
|
|
//+------------------------------------------------------------------+
|
|
bool DataUpdateWait(double &balance_prev)
|
|
{
|
|
int attempts=0; // Количество попыток
|
|
|
|
//--- До тех пор пока снят флаг остановки программы и пока количество попыток меньше установленного в REFRESH_ATTEMPTS
|
|
while(!IsStopped() && attempts<REFRESH_ATTEMPTS)
|
|
{
|
|
//--- Если баланс текущего аккаунта отличается от баланса ранее сохранённого значения баланса,
|
|
//--- считаем, что данные аккаунта получить удалось - возвращаем true
|
|
if(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)-balance_prev, 8)!=0)
|
|
return true;
|
|
|
|
//--- Ожидаем полсекунды для следующей попытки, увеличиваем количество попыток и
|
|
//--- выводим в журнал сообщение об ожидании получения данных и количестве попыток
|
|
Sleep(500);
|
|
attempts++;
|
|
PrintFormat("%s::%s: Waiting for account information to update. Attempt %d", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__, attempts);
|
|
}
|
|
|
|
//--- Если по истечении всех попыток получить данные нового аккаунта не удалось,
|
|
//--- сообщаем об этом в журнал, записываем в "прошлый баланс" пустое значение и возвращаем false
|
|
PrintFormat("%s::%s: Could not wait for updated account data... Try again", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__);
|
|
balance_prev=EMPTY_VALUE;
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает список с указанным диапазоном статистики |
|
|
//+------------------------------------------------------------------+
|
|
CArrayObj *GetListDataRange(ENUM_REPORT_RANGE range, CArrayObj *list, datetime &time_start, const int num_periods)
|
|
{
|
|
//--- Текущая дата
|
|
CDateTime current={};
|
|
current.Date(TimeLocal());
|
|
|
|
//--- Дата начала периода
|
|
CDateTime begin_range=current;
|
|
|
|
//--- Устанавливаем время начала периода в значение 00:00:00
|
|
begin_range.Hour(0);
|
|
begin_range.Min(0);
|
|
begin_range.Sec(0);
|
|
|
|
//--- В зависимости от указанного периода требуемой статистики, корректируем дату начала периода
|
|
switch(range)
|
|
{
|
|
//--- Сутки
|
|
case REPORT_RANGE_DAILY : // уменьшаем значение День на 1
|
|
begin_range.DayDec(1);
|
|
break;
|
|
|
|
//--- С начала недели
|
|
case REPORT_RANGE_WEEK_BEGIN : // уменьшаем значение День на (количество прошедших дней в неделе)-1
|
|
begin_range.DayDec(begin_range.day_of_week==SUNDAY ? 6 : begin_range.day_of_week-1);
|
|
break;
|
|
|
|
//--- С начала месяца
|
|
case REPORT_RANGE_MONTH_BEGIN : // устанавливаем в значение День первое число месяца
|
|
begin_range.Day(1);
|
|
break;
|
|
|
|
//--- С начала года
|
|
case REPORT_RANGE_YEAR_BEGIN : // устанавливаем в значение Месяц первый месяц в году, а в значение День первое число месяца
|
|
begin_range.Mon(1);
|
|
begin_range.Day(1);
|
|
break;
|
|
|
|
//--- Количество дней
|
|
case REPORT_RANGE_NUM_DAYS : // Уменьшаем значение День на указанное количество дней
|
|
begin_range.DayDec(fabs(num_periods));
|
|
break;
|
|
|
|
//--- Количество месяцев
|
|
case REPORT_RANGE_NUM_MONTHS : // Уменьшаем значение Месяц на указанное количество месяцев
|
|
begin_range.MonDec(fabs(num_periods));
|
|
break;
|
|
|
|
//--- Количество лет
|
|
case REPORT_RANGE_NUM_YEARS : // Уменьшаем значение Год на указанное количество лет
|
|
begin_range.YearDec(fabs(num_periods));
|
|
break;
|
|
|
|
//---REPORT_RANGE_ALL Весь период
|
|
default : // Устанавливаем дату 1970.01.01
|
|
begin_range.Year(1970);
|
|
begin_range.Mon(1);
|
|
begin_range.Day(1);
|
|
break;
|
|
}
|
|
|
|
//--- Записываем дату начала периода и возвращаем указатель на список позиций,
|
|
//--- время открытия которых больше, либо равно времени начала запрошенного периода
|
|
time_start=begin_range.DateTime();
|
|
return CSelect::ByPositionProperty(list,POSITION_PROP_TIME,time_start,EQUAL_OR_MORE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Обработчик смены аккаунта |
|
|
//+------------------------------------------------------------------+
|
|
void AccountChangeHandler(void)
|
|
{
|
|
//--- Записываем логин и сервер текущего аккаунта
|
|
long login = AccountInfoInteger(ACCOUNT_LOGIN);
|
|
string server = AccountInfoString(ACCOUNT_SERVER);
|
|
|
|
//--- Получаем указатель на объект-аккаунт по данным текущего аккаунта
|
|
CAccount *account = ExtAccounts.Get(login, server);
|
|
|
|
//--- Если объект пустой - создаём новый объект-аккаунт и получаем указатель на него
|
|
if(account==NULL && ExtAccounts.Create(login, server))
|
|
account=ExtAccounts.Get(login, server);
|
|
|
|
//--- Если в итоге объект-аккаунт не получен - сообщаем об этом и уходим
|
|
if(account==NULL)
|
|
{
|
|
PrintFormat("Error getting access to account object: %I64d (%s)", login, server);
|
|
return;
|
|
}
|
|
|
|
//--- Записываем текущие значения логина и сервера из данных объекта-аккаунта
|
|
ExtLogin =account.Login();
|
|
ExtServer=account.Server();
|
|
|
|
//--- Распечатываем данные аккаунта в журнал и выводим сообщение о начале создания списка закрытых позиций
|
|
account.Print();
|
|
Print("Beginning to create a list of closed positions...");
|
|
|
|
//--- Создаём список закрытых позиций и по завершении процесса сообщаем в журнал количество созданных позиций и затраченное время
|
|
ulong start=GetTickCount();
|
|
ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
|
|
PrintFormat("A list of %d positions was created in %I64u ms", account.PositionsTotal(), GetTickCount()-start);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Создаёт статистику за указанный диапазон времени |
|
|
//+------------------------------------------------------------------+
|
|
void SendReport(ENUM_REPORT_RANGE range, int num_periods, CArrayObj *list_common, CArrayString *list_symbols, CArrayLong *list_magics, CArrayString *list_msg)
|
|
{
|
|
string array_msg[2] = {NULL, NULL}; // Массив сообщений (0) для выводла в журнал, (1) для отправки на смартфон
|
|
datetime time_start = 0; // Здесь будем хранить время начала периода статистики
|
|
CArrayObj *list_tmp = NULL; // Временный список для фильтрации по символам и магикам
|
|
|
|
//--- Получаем список позиций за период range
|
|
CArrayObj *list_range=GetListDataRange(range, list_common, time_start, num_periods);
|
|
if(list_range==NULL)
|
|
return;
|
|
|
|
//--- Если список позиций пуст - сообщаем в журнал, что за данный период времени не было торговых транзакций
|
|
if(list_range.Total()==0)
|
|
{
|
|
PrintFormat("\"%s\" no trades",ReportRangeDescription(range, num_periods));
|
|
return;
|
|
}
|
|
|
|
//--- Предварительно обнулив, создаём списки символов и магиков позиций в полученном списке закрытых позиций за период времени
|
|
list_symbols.Clear();
|
|
list_magics.Clear();
|
|
CreateSymbolMagicLists(list_range, list_symbols, list_magics);
|
|
|
|
//--- Создаём статистику о закрытых позициях за указанный период,
|
|
//--- распечатываем в журнале созданную статистику из array_msg[0] и
|
|
//--- записываем в список сообщений для Push-уведомлений строку из array_msg[1]
|
|
if(CreateStatisticsMessage(range, num_periods, REPORT_BY_RANGE, MQLInfoString(MQL_PROGRAM_NAME),time_start, list_range, list_symbols, list_magics, 0, array_msg))
|
|
{
|
|
Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_RANGE, time_start)); // Заголовок статистики
|
|
Print(StatisticsTableHeader("Symbols ", InpCommissionsInclude, InpSpreadInclude)); // "Шапка" таблицы
|
|
Print(array_msg[0]); // Статистика за период времени
|
|
Print(""); // Отступ строки
|
|
list_msg.Add(array_msg[1]); // Сохраняем сообщение для Push-уведомлений в список для последующей отправки
|
|
}
|
|
|
|
//--- Если разрешена статистика раздельно по символам
|
|
if(InpReportBySymbols)
|
|
{
|
|
//--- Выводим в журнал заголовок статистики и "шапку" таблицы
|
|
Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_SYMBOLS, time_start));
|
|
Print(StatisticsTableHeader("Symbol ", InpCommissionsInclude, InpSpreadInclude));
|
|
|
|
//--- В цикле по списку символов
|
|
for(int i=0; i<list_symbols.Total(); i++)
|
|
{
|
|
//--- получаем наименование очередного символа
|
|
string symbol=list_symbols.At(i);
|
|
if(symbol=="")
|
|
continue;
|
|
//--- фильтруем список позиций, оставляя в нём только позиции с полученным символом
|
|
list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_SYMBOL, symbol, EQUAL);
|
|
|
|
//--- Создаём статистику о закрытых позициях за указанный период по текущему символу списка,
|
|
//--- распечатываем в журнале созданную статистику из array_msg[0] и
|
|
//--- записываем в список сообщений для Push-уведомлений строку из array_msg[1]
|
|
if(CreateStatisticsMessage(range, num_periods, REPORT_BY_SYMBOLS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg))
|
|
{
|
|
Print(array_msg[0]);
|
|
list_msg.Add(array_msg[1]);
|
|
}
|
|
}
|
|
//--- По окончании цикла по всем символам выводим в журнал разделительную строку
|
|
Print("");
|
|
}
|
|
|
|
//--- Если разрешена статистика раздельно по магикам
|
|
if(InpReportByMagics)
|
|
{
|
|
//--- Выводим в журнал заголовок статистики и "шапку" таблицы
|
|
Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_MAGICS, time_start));
|
|
Print(StatisticsTableHeader("Magic ", InpCommissionsInclude, InpSpreadInclude));
|
|
|
|
//--- В цикле по списку магиков
|
|
for(int i=0; i<list_magics.Total(); i++)
|
|
{
|
|
//--- получаем номер очередного магика
|
|
long magic=list_magics.At(i);
|
|
if(magic==LONG_MAX)
|
|
continue;
|
|
//--- фильтруем список позиций, оставляя в нём только позиции с полученным магиком
|
|
list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_MAGIC, magic, EQUAL);
|
|
|
|
//--- Создаём статистику о закрытых позициях за указанный период по текущему магику списка,
|
|
//--- распечатываем в журнале созданную статистику из array_msg[0] и
|
|
//--- записываем в список сообщений для Push-уведомлений строку из array_msg[1]
|
|
if(CreateStatisticsMessage(range, num_periods, REPORT_BY_MAGICS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg))
|
|
{
|
|
Print(array_msg[0]);
|
|
list_msg.Add(array_msg[1]);
|
|
}
|
|
}
|
|
//--- По окончании цикла по всем магикам выводим в журнал разделительную строку
|
|
Print("");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Создаёт и возвращает строку "шапки" таблицы |
|
|
//+------------------------------------------------------------------+
|
|
string StatisticsTableHeader(const string first, const bool commissions, const bool spreads)
|
|
{
|
|
//--- Объявим и инициализируем заголовки столбцов таблицы
|
|
string h_trades="Trades ";
|
|
string h_long="Long ";
|
|
string h_short="Short ";
|
|
string h_profit="Profit ";
|
|
string h_max="Max ";
|
|
string h_min="Min ";
|
|
string h_avg="Avg ";
|
|
string h_costs="Costs ";
|
|
//--- столбцы таблицы, отключаемые в настройках
|
|
string h_commiss=(commissions ? "Commiss " : "");
|
|
string h_swap=(commissions ? "Swap " : "");
|
|
string h_fee=(commissions ? "Fee " : "");
|
|
string h_spread=(spreads ? "Spread " : "");
|
|
//--- ширина столбцов таблицы
|
|
int w=TABLE_COLUMN_W;
|
|
int c=(commissions ? TABLE_COLUMN_W : 0);
|
|
|
|
//--- Разделители столбцов таблицы, отключаемых в настройках
|
|
string sep1=(commissions ? "|" : "");
|
|
string sep2=(spreads ? "|" : "");
|
|
|
|
//--- Создаём строку "шапки" таблицы
|
|
return StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s",
|
|
w,first,
|
|
w,h_trades,
|
|
w,h_long,
|
|
w,h_short,
|
|
w,h_profit,
|
|
w,h_max,
|
|
w,h_min,
|
|
w,h_avg,
|
|
w,h_costs,
|
|
c,h_commiss,sep1,
|
|
c,h_swap,sep1,
|
|
c,h_fee,sep1,
|
|
w,h_spread,sep2);
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает заголовок описания запрашиваемого периода статистики |
|
|
//+------------------------------------------------------------------+
|
|
string StatisticsRangeTitle(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const datetime time_start, const string symbol=NULL, const long magic=LONG_MAX)
|
|
{
|
|
string report_by_str=
|
|
(
|
|
report_by==REPORT_BY_SYMBOLS ? (symbol==NULL ? "by symbols " : "by "+symbol+" ") :
|
|
report_by==REPORT_BY_MAGICS ? (magic==LONG_MAX ? "by magics " : "by magic #"+(string)magic+" ") : ""
|
|
);
|
|
return StringFormat("Report %sfor the period \"%s\" from %s", report_by_str,ReportRangeDescription(range, num_periods), TimeToString(time_start, TIME_DATE));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает текст сообщения со статистикой |
|
|
//+------------------------------------------------------------------+
|
|
bool CreateStatisticsMessage(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const string header, const datetime time_start,
|
|
CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics, const int index, string &array_msg[])
|
|
{
|
|
//--- Получаем из переданных списков по индексу символ и магик
|
|
string symbol = list_symbols.At(index);
|
|
long magic = list_magics.At(index);
|
|
//--- Если переданные списки пусты, или не получены данные из них - возвращаем false
|
|
if(list==NULL || list.Total()==0 || (report_by==REPORT_BY_SYMBOLS && symbol=="") || (report_by==REPORT_BY_MAGICS && magic==LONG_MAX))
|
|
return false;
|
|
|
|
CPosition *pos_min = NULL; // Указатель на позицию с минимальным значением свойства
|
|
CPosition *pos_max = NULL; // Указатель на позицию с максимальным значением свойства
|
|
CArrayObj *list_tmp = NULL; // Указатель на временный список для фильтрации по свойствам
|
|
int index_min= WRONG_VALUE; // Индекс позиции в списке с минимальным значением свойства
|
|
int index_max= WRONG_VALUE; // Индекс позиции в списке с максимальным значением свойства
|
|
|
|
//--- Получаем из списка позиций суммы свойств позиций
|
|
double profit=PropertyValuesSum(list, POSITION_PROP_PROFIT); // Общий профит позиций в списке
|
|
double commissions=PropertyValuesSum(list,POSITION_PROP_COMMISSIONS); // Общая комиссия позиций в списке
|
|
double swap=PropertyValuesSum(list, POSITION_PROP_SWAP); // Общий своп позиций в списке
|
|
double fee=PropertyValuesSum(list, POSITION_PROP_FEE); // Общая оплата за проведение сделок позиций в списке
|
|
double costs=commissions+swap+fee; // Издержки: общая сумма значений всех комиссий
|
|
double spreads=PositionsCloseSpreadCostSum(list); // Общие затраты на спред всех позиций в списке
|
|
|
|
//--- Определяем текстовые описания всех полученных значений
|
|
string s_0=(report_by==REPORT_BY_SYMBOLS ? symbol : report_by==REPORT_BY_MAGICS ? (string)magic : (string)list_symbols.Total())+" ";
|
|
string s_trades=StringFormat("%d ", list.Total());
|
|
string s_profit=StringFormat("%+.2f ", profit);
|
|
string s_costs=StringFormat("%.2f ",costs);
|
|
string s_commiss=(InpCommissionsInclude ? StringFormat("%.2f ",commissions) : "");
|
|
string s_swap=(InpCommissionsInclude ? StringFormat("%.2f ",swap) : "");
|
|
string s_fee=(InpCommissionsInclude ? StringFormat("%.2f ",fee) : "");
|
|
string s_spread=(InpSpreadInclude ? StringFormat("%.2f ",spreads) : "");
|
|
|
|
//--- Получаем список только длинных позиций и создаём описание их количества
|
|
list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_BUY, EQUAL);
|
|
string s_long=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" ";
|
|
|
|
//--- Получаем список только коротких позиций и создаём описание их количества
|
|
list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_SELL, EQUAL);
|
|
string s_short=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" ";
|
|
|
|
//--- Получаем индекс позиции в списке с максимальным профитом и создаём описание полученного значения
|
|
index_max=CSelect::FindPositionMax(list, POSITION_PROP_PROFIT);
|
|
pos_max=list.At(index_max);
|
|
double profit_max=(pos_max!=NULL ? pos_max.Profit() : EMPTY_VALUE);
|
|
string s_max=(profit_max!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_max) : "No trades ");
|
|
|
|
//--- Получаем индекс позиции в списке с минимальным профитом и создаём описание полученного значения
|
|
index_min=CSelect::FindPositionMin(list, POSITION_PROP_PROFIT);
|
|
pos_min=list.At(index_min);
|
|
double profit_min=(pos_min!=NULL ? pos_min.Profit() : EMPTY_VALUE);
|
|
string s_min=(profit_min!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_min) : "No trades ");
|
|
|
|
//--- Создаём описание среднего значения профита всех позиций в списке
|
|
string s_avg=StringFormat("%.2f ", PropertyAverageValue(list, POSITION_PROP_PROFIT));
|
|
|
|
//--- Ширина столбцов таблицы
|
|
int w=TABLE_COLUMN_W;
|
|
int c=(InpCommissionsInclude ? TABLE_COLUMN_W : 0);
|
|
|
|
//--- Разделители отключаемых в настройках столбцов таблицы
|
|
string sep1=(InpCommissionsInclude ? "|" : "");
|
|
string sep2=(InpSpreadInclude ? "|" : "");
|
|
|
|
//--- Для вывода в журнал создаём строку со столбцами таблицы, внутри которых расположены полученных выше значения
|
|
array_msg[0]=StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s",
|
|
w,s_0,
|
|
w,s_trades,
|
|
w,s_long,
|
|
w,s_short,
|
|
w,s_profit,
|
|
w,s_max,
|
|
w,s_min,
|
|
w,s_avg,
|
|
w,s_costs,
|
|
c,s_commiss,sep1,
|
|
c,s_swap,sep1,
|
|
c,s_fee,sep1,
|
|
w,s_spread,sep2);
|
|
|
|
//--- Для отправки уведомления на MQID создаём строку со столбцами таблицы, внутри которых расположены полученных выше значения
|
|
array_msg[1]=StringFormat("%s:\nTrades: %s Long: %s Short: %s\nProfit: %s Max: %s Min: %s Avg: %s\n%s%s%s%s%s",
|
|
StatisticsRangeTitle(range, num_periods, report_by, time_start, (report_by==REPORT_BY_SYMBOLS ? symbol : NULL), (report_by==REPORT_BY_MAGICS ? magic : LONG_MAX)),
|
|
s_trades,
|
|
s_long,
|
|
s_short,
|
|
s_profit,
|
|
s_max,
|
|
s_min,
|
|
s_avg,
|
|
(costs!=0 ? "Costs: "+s_costs : ""),
|
|
(InpCommissionsInclude && commissions!=0 ? " Commiss: "+s_commiss : ""),
|
|
(InpCommissionsInclude && swap!=0 ? " Swap: "+s_swap : ""),
|
|
(InpCommissionsInclude && fee!=0 ? " Fee: "+s_fee : ""),
|
|
(InpSpreadInclude && spreads!=0 ? " Spreads: "+s_spread : ""));
|
|
//--- Всё успешно
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Отправляет сообщения из списка на MQID |
|
|
//+------------------------------------------------------------------+
|
|
void SendMessage(CArrayString *list_msg)
|
|
{
|
|
//--- Если не разрешена отправка Push-уведомлений или переданный список пуст - уходим
|
|
if(!ExtNotify || list_msg==NULL || list_msg.Total()==0)
|
|
return;
|
|
int total=list_msg.Total(); // Количество сообщений в списке
|
|
int left=60; // Счётчик количества секунд, остающееся до завершения минуты с начала отправки уведомлений
|
|
int count=0; // Счётчик количества отправленных уведомлений с начала минуты
|
|
|
|
//--- Выведем в журнал сообщение о начале отправки уведомлений
|
|
PrintFormat("Beginning of sending %d notifications to MQID",list_msg.Total());
|
|
|
|
//--- В цикле по количеству сообщений в списке
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
//--- отправляем на смартфон очередное уведомление
|
|
SendNotification(list_msg.At(i));
|
|
//--- Увеличиваем счётчик отправленных уведомлений и
|
|
//--- ждём полсекунды перед отправкой следующего (≈2 уведомления в секунду)
|
|
count++;
|
|
Sleep(500);
|
|
|
|
//--- если это не самое первое уведомление, и оно не чётное,
|
|
//--- уменьшаем оставшееся время до окончания минуты (в минуту не более 10 уведомлений)
|
|
if(i>0 && i %2!=0)
|
|
left-=1;
|
|
|
|
//--- Если отправлено 10 уведомлений с момента начала минуты
|
|
if(count==10)
|
|
{
|
|
//--- распечатаем в журнале предупреждающее сообщение о достижении лимита сообщений в минуту
|
|
PrintFormat("%d out of %d messages sent.\nNo more than 10 messages per minute! "+
|
|
"Message limit has been reached. Wait %d seconds until a minute is up.",
|
|
i+1, list_msg.Total(), left);
|
|
//--- подождём оставшееся время до истечения текущей минуты и переинициализируем счётчики
|
|
Sleep(left*1000);
|
|
count=0;
|
|
left=60;
|
|
}
|
|
}
|
|
|
|
//--- По завершении отправки уведомлений, сообщим об этом в журнале
|
|
PrintFormat("Sending %d notifications completed", list_msg.Total());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Заполняет списки магиков и символов позиций из переданного списка|
|
|
//+------------------------------------------------------------------+
|
|
void CreateSymbolMagicLists(CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics)
|
|
{
|
|
//--- Если передан невалидный указатель на список позиций, либо список пустой - уходим
|
|
if(list==NULL || list.Total()==0)
|
|
return;
|
|
|
|
int index=WRONG_VALUE; // Индекс искомого символа или магика в списке
|
|
|
|
//--- В цикле по списку позиций
|
|
for(int i=0; i<list.Total(); i++)
|
|
{
|
|
//--- получаем указатель на очередную позицию
|
|
CPosition *pos=list.At(i);
|
|
if(pos==NULL)
|
|
continue;
|
|
|
|
//--- Получаем символ позиции
|
|
string symbol=pos.Symbol();
|
|
|
|
//--- Списку символов устанавливаем флаг сортированн6ого списка и получаем индекс символа в списке символов
|
|
list_symbols.Sort();
|
|
index=list_symbols.Search(symbol);
|
|
|
|
//--- Если такого символа в списке нет - добавляем его в список
|
|
if(index==WRONG_VALUE)
|
|
list_symbols.Add(symbol);
|
|
|
|
//--- Получаем магик позиции
|
|
long magic=pos.Magic();
|
|
|
|
//--- Списку магиков устанавливаем флаг сортированного списка и получаем индекс магика в списке магиков
|
|
list_magics.Sort();
|
|
index=list_magics.Search(magic);
|
|
|
|
//--- Если такого магика в списке нет - добавляем его в список
|
|
if(index==WRONG_VALUE)
|
|
list_magics.Add(magic);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает сумму величин указанного |
|
|
//| целочисленного свойства всех позиций в списке |
|
|
//+------------------------------------------------------------------+
|
|
long PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property)
|
|
{
|
|
long res=0;
|
|
int total=list.Total();
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
CPosition *pos=list.At(i);
|
|
res+=(pos!=NULL ? pos.GetProperty(property) : 0);
|
|
}
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает сумму величин указанного |
|
|
//| вещественного свойства всех позиций в списке |
|
|
//+------------------------------------------------------------------+
|
|
double PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property)
|
|
{
|
|
double res=0;
|
|
int total=list.Total();
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
CPosition *pos=list.At(i);
|
|
res+=(pos!=NULL ? pos.GetProperty(property) : 0);
|
|
}
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает среднюю величину указанного |
|
|
//| целочисленного свойства всех позиций в списке |
|
|
//+------------------------------------------------------------------+
|
|
double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property)
|
|
{
|
|
long res=0;
|
|
int total=list.Total();
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
CPosition *pos=list.At(i);
|
|
res+=(pos!=NULL ? pos.GetProperty(property) : 0);
|
|
}
|
|
return(total>0 ? (double)res/(double)total : 0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает среднюю величину указанного |
|
|
//| вещественного свойства всех позиций в списке |
|
|
//+------------------------------------------------------------------+
|
|
double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property)
|
|
{
|
|
double res=0;
|
|
int total=list.Total();
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
CPosition *pos=list.At(i);
|
|
res+=(pos!=NULL ? pos.GetProperty(property) : 0);
|
|
}
|
|
return(total>0 ? res/(double)total : 0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает сумму стоимости спредов |
|
|
//| сделок закрытия всех позиций в списке |
|
|
//+------------------------------------------------------------------+
|
|
double PositionsCloseSpreadCostSum(CArrayObj *list)
|
|
{
|
|
double res=0;
|
|
if(list==NULL)
|
|
return 0;
|
|
int total=list.Total();
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
CPosition *pos=list.At(i);
|
|
res+=(pos!=NULL ? pos.SpreadOutCost() : 0);
|
|
}
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает описание периода отчёта |
|
|
//+------------------------------------------------------------------+
|
|
string ReportRangeDescription(ENUM_REPORT_RANGE range, const int num_period)
|
|
{
|
|
switch(range)
|
|
{
|
|
//--- Сутки
|
|
case REPORT_RANGE_DAILY : return("Daily");
|
|
//---С начала недели
|
|
case REPORT_RANGE_WEEK_BEGIN : return("Weekly");
|
|
//--- С начала месяца
|
|
case REPORT_RANGE_MONTH_BEGIN : return("Month-to-date");
|
|
//--- С начала года
|
|
case REPORT_RANGE_YEAR_BEGIN : return("Year-to-date");
|
|
//--- Количество дней
|
|
case REPORT_RANGE_NUM_DAYS : return StringFormat("%d days", num_period);
|
|
//--- Количество месяцев
|
|
case REPORT_RANGE_NUM_MONTHS : return StringFormat("%d months", num_period);
|
|
//--- Количество лет
|
|
case REPORT_RANGE_NUM_YEARS : return StringFormat("%d years", num_period);
|
|
//--- Весь период
|
|
case REPORT_RANGE_ALL : return("Entire period");
|
|
//--- any other
|
|
default : return("Unknown period: "+(string)range);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+ |