class TelemetryMetric
Структура для хранения метрики телеметрии с именем и счётчиком.
Сообщите нам, если считаете, что материал размещён без разрешения.
Обращение по товару «eatelemetry.mqh» отправится на рассмотрение. Укажите контакт для ответа и опишите причину.
EATelemetry.mqh — библиотека телеметрии для торговых советников MetaTrader 4. Она обеспечивает сбор и сохранение данных о торговых операциях, состоянии корзин сделок и диагностике торговых условий. Библиотека работает с файлами CSV для ведения статистики сделок, истории корзин и диагностических снимков.
Основные возможности включают регистрацию открытия и закрытия сделок, отслеживание максимальной прибыли и просадки корзины, управление списком тикетов в корзине, а также подсчёт количества попыток и триггеров торговых условий. Данные сохраняются в файлы с префиксом, формируемым из имени советника, символа и таймфрейма.
Библиотека поддерживает восстановление состояния корзин и открытых сделок после перезапуска терминала, миграцию устаревших CSV-файлов и очистку финализированных записей. Все операции записи осуществляются в CSV-файлы, расположенные в общей директории данных терминала.
Классов: 3 · методов: 0 · конструкторов: 0 · деструкторов: 0 · отдельных функций: 44
class TelemetryMetric
Структура для хранения метрики телеметрии с именем и счётчиком.
class TradeTelemetry
Структура для хранения телеметрии отдельной сделки.
class TelemetryBasket
Структура для хранения телеметрии корзины сделок.
int FindOrAddMetric(TelemetryMetric &arr[], string name)
Находит существующую метрику в массиве по имени или добавляет новую.
int TelemetryFindTradeIndex(int ticket)
Находит индекс сделки в массиве телеметрии по номеру тикета.
void TelemetryRemoveTrade(int idx)
Удаляет запись о сделке из массива телеметрии по индексу.
int TelemetryFindBasketIndex(string basketId)
Находит индекс корзины в массиве истории по идентификатору.
void TelemetryRemoveBasketFromHistory(int idx)
Удаляет корзину из массива истории по индексу.
void TelemetryStoreBasketToHistory(TelemetryBasket &basket)
Сохраняет указанную корзину в массив истории, если она ещё не записана.
void TelemetryStoreCurrentBasketToHistory()
Сохраняет текущую корзину в историю, если она неактивна.
bool TelemetryGetBasketData(string basketId, TelemetryBasket &outBasket)
Возвращает данные корзины по идентификатору из текущей или истории.
bool TelemetryBasketHasOpenTrades(string basketId)
Проверяет, есть ли открытые сделки, принадлежащие указанной корзине.
void TelemetryCleanupFinalizedBasket(string basketId)
Заглушка для очистки финализированной корзины; фактически не удаляет данные.
string TelemetryTimeframe()
Возвращает текстовое обозначение текущего таймфрейма.
string TelemetryCleanFileName(string text)
Заменяет в строке недопустимые символы для имени файла.
string TelemetryGenerateBasketId(string symbol, int magic)
Генерирует уникальный идентификатор корзины из символа и магического номера.
string TelemetryOrderComment(int ticket)
Возвращает комментарий ордера по номеру тикета из активных или истории.
string TelemetryTicketListToString(int &tickets[])
Преобразует массив тикетов в разделённую строку.
void TelemetryParseTicketList(string str, int &tickets[])
Парсит строку с тикетами в массив целых чисел.
string TelemetryBasketToString(TelemetryBasket &basket)
Сериализует данные корзины в разделённую строку полного состояния.
void TelemetryPersistBasket()
Сохраняет текущую корзину в файл состояния, обновляя существующую запись.
void TelemetryRemoveBasketFromFile(string basketId)
Удаляет запись о корзине из файла состояния по идентификатору.
void TelemetryRecoverBaskets(int magic)
Восстанавливает корзины из файла состояния с фильтром по магическому номеру.
void TelemetryMigrateLegacyCSV()
Мигрирует данные из устаревшего CSV-формата в новый формат.
void TelemetryInit(string eaName, int magic)
Инициализирует телеметрию: формирует имена файлов, проверяет миграцию, восстанавливает данные.
void TelemetryHeartbeat()
Обновляет MFE и MAE для открытых сделок при каждом тике.
void TelemetryTradeOpened(int ticket, string symbol, int magic, string type, double lots, double price, string strategy)
Регистрирует открытие сделки в массиве телеметрии.
void TelemetryRecoverOpenTrades(int magic, string strategy)
Восстанавливает открытые сделки из истории ордеров после перезапуска.
bool TelemetryBasketContainsTicketInBasket(TelemetryBasket &basket, int ticket)
Проверяет, содержит ли указанная корзина данный тикет.
void TelemetryTradeClosed(int ticket, string symbol, int magic, string type, double price, double profit, int duration, string reason, string strategy)
Регистрирует закрытие сделки и записывает данные в CSV-файл статистики.
void TelemetryEvent(string eventName, string symbol, int magic, int ticket, string type, double price, double profit, int duration, string strategy)
Заглушка для события телеметрии; не записывает данные.
void TelemetryAttempt(string metricName)
Увеличивает счётчик попыток для указанной метрики.
void TelemetryTrigger(string metricName)
Увеличивает счётчик выполнений для указанного триггера.
void TelemetryValue(string symbol, int magic, int ticket, string metric, double value, string strategy)
Заглушка для значения телеметрии; не записывает данные.
void TelemetryError(string symbol, int magic, int ticket, string type, double price, double profit, string errorDesc)
Заглушка для ошибки телеметрии; не записывает данные.
void TelemetryBasketAddTicket(int ticket)
Добавляет тикет в список тикетов текущей корзины.
bool TelemetryBasketContainsTicket(int ticket)
Проверяет, принадлежит ли тикет текущей корзине.
void TelemetryBasketUpdate(double currentProfit, int currentLayers, double currentLots)
Обновляет пиковую прибыль, просадку, количество слоёв и лотов корзины.
void TelemetryBasketTradeAdded(int ticket, int layers)
Регистрирует добавление сделки в корзину, обновляя статистику и сохраняя состояние.
void TelemetryWriteBasketClose(double finalProfit, string exitReason)
Заглушка для записи закрытия корзины; не выполняет действий.
void TelemetryBasketClose(double finalProfit, string exitReason)
Фиксирует финальное состояние текущей корзины как закрытую.
void TelemetryBasketCloseFromHistory(string exitReason)
Закрывает корзину, рассчитывая реализованную прибыль из истории ордеров.
string TelemetryGetBasketId()
Возвращает идентификатор текущей активной корзины.
bool TelemetryIsBasketActive()
Возвращает true, если текущая корзина активна.
int TelemetryGetBasketTradeCount()
Возвращает количество сделок в текущей корзине.
void TelemetrySummary()
Записывает итоговую сводку по метрикам и триггерам в CSV-файл.
void TelemetryShutdown()
Завершает работу телеметрии, записывая итоговую сводку в файл.
#property strict
#ifndef EATELEMETRY_MQH
#define EATELEMETRY_MQH
struct TelemetryMetric {
string name;
int count;
};
struct TradeTelemetry
{
int ticket;
string symbol;
int magic;
string type;
double lots;
datetime entryTime;
double entryPrice;
double highestPrice;
double lowestPrice;
string strategy;
string comment;
string basketId;
};
struct TelemetryBasket
{
string basketId;
string symbol;
int magic;
datetime startTime;
double peakProfit;
double maxDrawdown;
double finalProfit;
int ticketList[];
int ticketCount;
int tradeCount;
int maxLayers;
double maxLots;
double entryStrength;
double entrySecondStrength;
double entryStrengthGap;
double entryROC;
double entryStrengthDelta1;
double entryStrengthDelta3;
double entryStrengthDelta5;
double entryADX;
double entryATR;
double entryAvgATR;
double entryATRRatio;
double entryEMAFast;
double entryEMASlow;
double entrySpread;
string entryState;
string exitReason;
bool active;
bool finalized;
};
TelemetryMetric g_attempts[];
TelemetryMetric g_triggers[];
TradeTelemetry g_tradeTelemetry[];
int g_totalTrades = 0;
TelemetryBasket g_telemetryBasket;
TelemetryBasket g_telemetryBaskets[];
bool g_telemetryTickReceived = false;
bool g_telemetryWarningShown = false;
int g_telemetryTickCount = 0;
uint g_telemetryStartTime = 0;
string g_statsFile;
string g_summaryFile;
string g_diagnosticFile;
string g_basketStateFile;
int FindOrAddMetric(TelemetryMetric &arr[], string name)
{
for(int i=0; i<ArraySize(arr); i++) {
if(arr[i].name == name) return i;
}
int s = ArraySize(arr);
ArrayResize(arr, s + 1);
arr[s].name = name;
arr[s].count = 0;
return s;
}
int TelemetryFindTradeIndex(int ticket)
{
for(int i = 0; i < ArraySize(g_tradeTelemetry); i++)
{
if(g_tradeTelemetry[i].ticket == ticket)
return i;
}
return -1;
}
void TelemetryRemoveTrade(int idx)
{
int size = ArraySize(g_tradeTelemetry);
for(int i = idx; i < size - 1; i++)
{
g_tradeTelemetry[i] = g_tradeTelemetry[i + 1];
}
if(size > 0)
ArrayResize(g_tradeTelemetry, size - 1);
}
int TelemetryFindBasketIndex(string basketId)
{
for(int i = 0; i < ArraySize(g_telemetryBaskets); i++)
{
if(g_telemetryBaskets[i].basketId == basketId)
return i;
}
return -1;
}
void TelemetryRemoveBasketFromHistory(int idx)
{
int size = ArraySize(g_telemetryBaskets);
for(int i = idx; i < size - 1; i++)
{
g_telemetryBaskets[i] = g_telemetryBaskets[i + 1];
}
if(size > 0)
ArrayResize(g_telemetryBaskets, size - 1);
}
void TelemetryStoreBasketToHistory(TelemetryBasket &basket)
{
if(basket.basketId == "")
return;
if(TelemetryFindBasketIndex(basket.basketId) != -1)
return;
int idx = ArraySize(g_telemetryBaskets);
ArrayResize(g_telemetryBaskets, idx + 1);
g_telemetryBaskets[idx] = basket;
}
void TelemetryStoreCurrentBasketToHistory()
{
if(g_telemetryBasket.basketId == "")
return;
if(g_telemetryBasket.active)
return;
TelemetryStoreBasketToHistory(g_telemetryBasket);
}
bool TelemetryGetBasketData(string basketId, TelemetryBasket &outBasket)
{
if(g_telemetryBasket.basketId == basketId)
{
outBasket = g_telemetryBasket;
return true;
}
int idx = TelemetryFindBasketIndex(basketId);
if(idx != -1)
{
outBasket = g_telemetryBaskets[idx];
return true;
}
return false;
}
bool TelemetryBasketHasOpenTrades(string basketId)
{
for(int i = 0; i < ArraySize(g_tradeTelemetry); i++)
{
if(g_tradeTelemetry[i].basketId == basketId)
return true;
}
return false;
}
void TelemetryCleanupFinalizedBasket(string basketId)
{
}
string TelemetryTimeframe()
{
switch(Period())
{
case PERIOD_M1: return "M1";
case PERIOD_M5: return "M5";
case PERIOD_M15: return "M15";
case PERIOD_M30: return "M30";
case PERIOD_H1: return "H1";
case PERIOD_H4: return "H4";
case PERIOD_D1: return "D1";
case PERIOD_W1: return "W1";
case PERIOD_MN1: return "MN1";
}
return IntegerToString(Period());
}
string TelemetryCleanFileName(string text)
{
StringReplace(text, " ", "_");
StringReplace(text, ".", "_");
StringReplace(text, "/", "_");
StringReplace(text, "\\", "_");
StringReplace(text, ":", "_");
return text;
}
string TelemetryGenerateBasketId(string symbol, int magic)
{
return IntegerToString(magic) + "_" + symbol + "_" +
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS);
}
string TelemetryOrderComment(int ticket)
{
if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
return OrderComment();
if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_HISTORY))
return OrderComment();
return "";
}
string TelemetryTicketListToString(int &tickets[])
{
string result = "";
for(int i = 0; i < ArraySize(tickets); i++)
{
if(i > 0) result += ";";
result += IntegerToString(tickets[i]);
}
return result;
}
void TelemetryParseTicketList(string str, int &tickets[])
{
ArrayResize(tickets, 0);
if(str == "") return;
string parts[];
int count = StringSplit(str, ';', parts);
if(count <= 0) return;
ArrayResize(tickets, count);
for(int i = 0; i < count; i++)
{
tickets[i] = (int)StringToInteger(parts[i]);
}
}
string TelemetryBasketToString(TelemetryBasket &basket)
{
string tickets = TelemetryTicketListToString(basket.ticketList);
return StringFormat(
"%s|%s|%d|%d|%.10g|%.10g|%.10g|%d|%.10g|%d|%d|%d|%s|"
"%.10g|%.10g|%.10g|%.10g|%.10g|%.10g|%.10g|%.10g|%.10g|%.10g|"
"%.10g|%.10g|%.10g|%s|%s",
basket.basketId,
basket.symbol,
basket.magic,
(int)basket.startTime,
basket.peakProfit,
basket.maxDrawdown,
basket.finalProfit,
basket.maxLayers,
basket.maxLots,
basket.ticketCount,
basket.active ? 1 : 0,
basket.finalized ? 1 : 0,
tickets,
basket.entryStrength,
basket.entrySecondStrength,
basket.entryStrengthGap,
basket.entryROC,
basket.entryStrengthDelta1,
basket.entryStrengthDelta3,
basket.entryStrengthDelta5,
basket.entryADX,
basket.entryATR,
basket.entryAvgATR,
basket.entryATRRatio,
basket.entryEMAFast,
basket.entryEMASlow,
basket.entrySpread,
basket.entryState,
basket.exitReason);
}
void TelemetryPersistBasket()
{
if(g_telemetryBasket.basketId == "")
return;
string lines[];
int lineCount = 0;
bool found = false;
int handle = FileOpen(g_basketStateFile, FILE_TXT|FILE_READ|FILE_SHARE_READ);
if(handle != INVALID_HANDLE)
{
while(!FileIsEnding(handle))
{
string line = FileReadString(handle);
if(StringFind(line, g_telemetryBasket.basketId + "|") == 0)
{
line = TelemetryBasketToString(g_telemetryBasket);
found = true;
}
ArrayResize(lines, lineCount + 1);
lines[lineCount] = line;
lineCount++;
}
FileClose(handle);
}
if(!found)
{
ArrayResize(lines, lineCount + 1);
lines[lineCount] = TelemetryBasketToString(g_telemetryBasket);
lineCount++;
}
handle = FileOpen(g_basketStateFile, FILE_TXT|FILE_WRITE|FILE_SHARE_READ);
if(handle != INVALID_HANDLE)
{
for(int i = 0; i < lineCount; i++)
FileWrite(handle, lines[i]);
FileClose(handle);
}
}
void TelemetryRemoveBasketFromFile(string basketId)
{
if(basketId == "")
return;
string lines[];
int lineCount = 0;
int handle = FileOpen(g_basketStateFile, FILE_TXT|FILE_READ|FILE_SHARE_READ);
if(handle != INVALID_HANDLE)
{
while(!FileIsEnding(handle))
{
string line = FileReadString(handle);
if(StringFind(line, basketId + "|") != 0)
{
ArrayResize(lines, lineCount + 1);
lines[lineCount] = line;
lineCount++;
}
}
FileClose(handle);
}
handle = FileOpen(g_basketStateFile, FILE_TXT|FILE_WRITE|FILE_SHARE_READ);
if(handle != INVALID_HANDLE)
{
for(int i = 0; i < lineCount; i++)
FileWrite(handle, lines[i]);
FileClose(handle);
}
}
void TelemetryRecoverBaskets(int magic = 0)
{
int handle = FileOpen(g_basketStateFile, FILE_TXT|FILE_READ|FILE_SHARE_READ);
if(handle == INVALID_HANDLE)
return;
while(!FileIsEnding(handle))
{
string line = FileReadString(handle);
if(line == "") continue;
string parts[];
if(StringSplit(line, '|', parts) < 29) continue;
TelemetryBasket basket;
basket.basketId = parts[0];
basket.symbol = parts[1];
basket.magic = (int)StringToInteger(parts[2]);
basket.startTime = (datetime)StringToInteger(parts[3]);
basket.peakProfit = StringToDouble(parts[4]);
basket.maxDrawdown = StringToDouble(parts[5]);
basket.finalProfit = StringToDouble(parts[6]);
basket.maxLayers = (int)StringToInteger(parts[7]);
basket.maxLots = StringToDouble(parts[8]);
basket.ticketCount = (int)StringToInteger(parts[9]);
basket.active = ((int)StringToInteger(parts[10]) != 0);
basket.finalized = ((int)StringToInteger(parts[11]) != 0);
TelemetryParseTicketList(parts[12], basket.ticketList);
basket.ticketCount = ArraySize(basket.ticketList);
basket.entryStrength = StringToDouble(parts[13]);
basket.entrySecondStrength = StringToDouble(parts[14]);
basket.entryStrengthGap = StringToDouble(parts[15]);
basket.entryROC = StringToDouble(parts[16]);
basket.entryStrengthDelta1 = StringToDouble(parts[17]);
basket.entryStrengthDelta3 = StringToDouble(parts[18]);
basket.entryStrengthDelta5 = StringToDouble(parts[19]);
basket.entryADX = StringToDouble(parts[20]);
basket.entryATR = StringToDouble(parts[21]);
basket.entryAvgATR = StringToDouble(parts[22]);
basket.entryATRRatio = StringToDouble(parts[23]);
basket.entryEMAFast = StringToDouble(parts[24]);
basket.entryEMASlow = StringToDouble(parts[25]);
basket.entrySpread = StringToDouble(parts[26]);
basket.entryState = parts[27];
basket.exitReason = parts[28];
if(magic != 0 && basket.magic != magic) continue;
if(basket.active)
g_telemetryBasket = basket;
else if(basket.finalized)
TelemetryStoreBasketToHistory(basket);
}
FileClose(handle);
}
void TelemetryMigrateLegacyCSV()
{
string lines[];
int lineCount = 0;
int oldHandle = FileOpen(g_statsFile, FILE_TXT|FILE_READ|FILE_SHARE_READ);
if(oldHandle == INVALID_HANDLE) return;
if(!FileIsEnding(oldHandle))
FileReadString(oldHandle);
while(!FileIsEnding(oldHandle))
{
string line = FileReadString(oldHandle);
if(line != "")
{
ArrayResize(lines, lineCount + 1);
lines[lineCount] = line;
lineCount++;
}
}
FileClose(oldHandle);
if(lineCount == 0) return;
string openTickets[];
string openData[];
int openCount = 0;
string closeData[];
int closeCount = 0;
for(int i = 0; i < lineCount; i++)
{
string fields[];
int fieldCount = StringSplit(lines[i], ',', fields);
if(fieldCount < 5) continue;
string event = fields[4];
if(event == "TRADE_OPEN")
{
string ticket = fields[3];
int foundIdx = -1;
for(int j = 0; j < openCount; j++)
{
if(openTickets[j] == ticket)
{
foundIdx = j;
break;
}
}
if(foundIdx == -1)
{
ArrayResize(openTickets, openCount + 1);
ArrayResize(openData, openCount + 1);
openTickets[openCount] = ticket;
openData[openCount] = lines[i];
openCount++;
}
else
{
openData[foundIdx] = lines[i];
}
}
else if(event != "BASKET_START" &&
event != "BASKET_CLOSE" &&
event != "VALUE" &&
event != "ERROR" &&
fields[3] != "0")
{
ArrayResize(closeData, closeCount + 1);
closeData[closeCount] = lines[i];
closeCount++;
}
}
string legacyFile = StringSubstr(g_statsFile, 0, StringLen(g_statsFile) - 4) + "_legacy.csv";
FileMove(g_statsFile, 0, legacyFile, FILE_REWRITE|FILE_COMMON);
int newHandle = FileOpen(g_statsFile, FILE_CSV|FILE_READ|FILE_WRITE|FILE_SHARE_READ, ',');
if(newHandle == INVALID_HANDLE) return;
FileWrite(newHandle,
"EntryTime", "ExitTime", "Symbol", "Magic", "Ticket", "Type", "Lots",
"EntryPrice", "ExitPrice", "Profit", "Duration", "Strategy", "Comment",
"MFE", "MAE", "MFE_Points", "MAE_Points", "BasketID", "BasketPeakProfit",
"BasketMaxDrawdown", "ProfitCapture", "BasketMaxLayers", "BasketMaxLots", "ExitReason");
for(int i = 0; i < closeCount; i++)
{
string closeFields[];
StringSplit(closeData[i], ',', closeFields);
if(ArraySize(closeFields) < 7) continue;
string ticket = closeFields[3];
string exitTime = closeFields[0];
string symbol = closeFields[1];
int magic = (int)StringToInteger(closeFields[2]);
string type = closeFields[5];
string exitPrice = closeFields[6];
string profit = (ArraySize(closeFields) > 7) ? closeFields[7] : "0.00";
string duration = (ArraySize(closeFields) > 8) ? closeFields[8] : "0";
string strategy = (ArraySize(closeFields) > 9) ? closeFields[9] : "";
string comment = (ArraySize(closeFields) > 10) ? closeFields[10] : "";
string mfe = (ArraySize(closeFields) > 11) ? closeFields[11] : "";
string mae = (ArraySize(closeFields) > 12) ? closeFields[12] : "";
string mfePoints = (ArraySize(closeFields) > 13) ? closeFields[13] : "";
string maePoints = (ArraySize(closeFields) > 14) ? closeFields[14] : "";
string basketId = (ArraySize(closeFields) > 15) ? closeFields[15] : "";
string peakProfit = (ArraySize(closeFields) > 16) ? closeFields[16] : "";
string maxDrawdown = (ArraySize(closeFields) > 17) ? closeFields[17] : "";
string profitCap = (ArraySize(closeFields) > 18) ? closeFields[18] : "";
string maxLayers = (ArraySize(closeFields) > 19) ? closeFields[19] : "";
string maxLots = (ArraySize(closeFields) > 20) ? closeFields[20] : "";
string exitReason = closeFields[4];
string entryTime = "";
string entryPrice = "";
for(int j = 0; j < openCount; j++)
{
if(openTickets[j] == ticket)
{
string openFields[];
StringSplit(openData[j], ',', openFields);
if(ArraySize(openFields) >= 7)
{
entryTime = openFields[0];
entryPrice = openFields[6];
}
break;
}
}
FileWrite(newHandle,
entryTime, exitTime, symbol, magic, ticket, type, "",
entryPrice, exitPrice, profit, (int)StringToInteger(duration),
strategy, comment, mfe, mae, mfePoints, maePoints,
basketId, peakProfit, maxDrawdown, profitCap,
(int)StringToInteger(maxLayers), maxLots, exitReason);
}
FileClose(newHandle);
}
void TelemetryInit(string eaName = "", int magic = 0)
{
if(eaName == "") eaName = WindowExpertName();
eaName = TelemetryCleanFileName(eaName);
string prefix =
eaName + "_"
+ Symbol() + "_"
+ TelemetryTimeframe();
g_statsFile = prefix + "_Stats.csv";
g_summaryFile = prefix + "_Summary.csv";
g_diagnosticFile = prefix + "_Diagnostics.csv";
g_basketStateFile = prefix + "_BasketState.csv";
bool headerWritten = false;
int checkHandle = FileOpen(g_statsFile, FILE_TXT|FILE_READ|FILE_SHARE_READ);
if(checkHandle != INVALID_HANDLE)
{
if(FileSize(checkHandle) > 0)
{
string firstLine = FileReadString(checkHandle);
if(StringFind(firstLine, "Event") != -1 && StringFind(Показано первые 50% кода (символов: 25406)
Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.
На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.
Зачем нужен email? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.