English Deutsch 日本語
preview
От новичка до эксперта: Создание подробных торговых отчетов с помощью советника Reporting EA

От новичка до эксперта: Создание подробных торговых отчетов с помощью советника Reporting EA

MetaTrader 5Трейдинг |
304 0
Clemence Benjamin
Clemence Benjamin

Содержание:


Введение

Сегодняшнее обсуждение посвящено решению проблем, связанных с предоставлением торговых отчетов в MetaTrader 5. В нашей предыдущей публикации мы описали общий рабочий процесс и требования, необходимые для эффективного функционирования системы. Мы представили советник  Reporting EA как инструмент, предназначенный для создания и предоставления торговых отчетов в формате PDF с периодичностью, которую может настроить пользователь.

В этом продолжении предыдущей статьи наша цель состоит в том, чтобы развить тот фундамент, повысив как глубину информации, содержащейся в отчетах, так и эффективность их представления. Такое улучшение стало возможным благодаря интеграции MQL5 с мощными библиотеками Python, что обеспечивает более богатый результат и более плавную автоматизацию. Попутно мы узнаем ценные уроки о том, как можно использовать межъязыковую интеграцию для создания надежных и бесперебойных торговых решений.

К этой теме стоит вернуться, как только вы поймете, насколько подробная отчетность может повлиять на мышление трейдера и процесс принятия решений. В предыдущей версии сгенерированный PDF—файл содержал только четыре точки данных - явное ограничение и убедительное напоминание о необходимости более полной отчетности. В дальнейшем наши отчеты будут включать в себя расширенные показатели, визуализации, графики и множество аналитических компонентов, описанных в пункте 2 предыдущей статьи об изучении функций отчетности.

В конечном счете, цель состоит в том, чтобы создать гибкий и богатый информацией инструмент отчетности, который каждый аналитик или трейдер сможет адаптировать к своим уникальным торговым стратегиям и рабочему процессу.

Reports snippet

Рисунок 1: Скриншот отчета из торгового отчета, подготовленного ранней версией советника Reporting EA

Изображение выше взято из отчета в формате PDF, сгенерированного первой версией нашего скрипта reports_processor.py. Соответствующий фрагмент кода приведен ниже. Сегодня мы сосредоточены на совершенствовании этого кода, чтобы он позволял создавать более подробные и содержательные отчеты.

В ходе настоящего обсуждения мы рассмотрим дополнительные возможности, которые Python может привнести в процесс создания отчетов, и продемонстрируем, как оптимизировать взаимодействие между MQL5 и Python для повышения эффективности и гибкости.

def generate_pdf(report, output_path):
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Arial", size=12)
    pdf.cell(0, 10, f"Report Date: {report['date']}", ln=True)
    pdf.cell(0, 10, f"Total Trades: {report['trade_count']}", ln=True)
    pdf.cell(0, 10, f"Net Profit:    ${report['net_profit']:.2f}", ln=True)
    pdf.cell(0, 10, f"Top Symbol:    {report['top_symbol']}", ln=True)
    pdf.output(output_path)

Когда мы сравниваем функции отчетности, показанные на рисунке 1 выше и рисунке 2 ниже, становится ясно, что все еще есть значительные возможности для улучшения. Наша цель состоит не в том, чтобы заново изобретать существующие решения, а в том, чтобы предоставить настраиваемую и гибкую альтернативу для создания торговых отчетов.

Изображение из раздела отчетов терминала MetaTrader 5 иллюстрирует текущие функции, которые мы будем использовать в качестве основы для планируемых к внедрению усовершенствований.

MQL5 reports

Рисунок 2: Особенности торговых отчетов на MQL5


Обзор обновления советника Reporting EA

На рисунке 3 (структурная схема) ниже представлен рабочий процесс обработки, реализуемый с помощью обновления советника Reporting EA. Советник экспортирует датированный CSV-файл торговой истории и запускает генератор отчетов на Python, обрабатывающий CSV-файл для расчета аналитики, создания графиков и записи окончательного отчета (PDF), а также компактного результата в формате JSON, содержащего статус pdf_path и email_sent. Советник запрашивает этот JSON—файл, считывает и анализирует его, проверяет указанные пути вывода и размеры файлов и — при успешной проверке - отправляет уведомления и, при необходимости, архивирует или открывает отчет для завершения цикла автоматической отчетности.

Reporting EA Update design

Рисунок 3: Структурная схема советника Reporting EA



Обновление кода советника Reporting EA

На данном этапе мы внедрим версию обновления на MQL5, чтобы обеспечить высокое качество вывода отчетов. Мы разобьем советник на логические разделы и объясним код, который напрямую поддерживает наши цели в области отчетности. Помните, что советники MQL5 не ограничиваются размещением сделок — они могут автоматизировать многие рутинные торговые задачи, и отчетность является прекрасным примером этого. Хотя в настоящее время мы ориентируемся на создание легкого специализированного советника с отчетностью, позже тот же модуль отчетности может быть интегрирован в более многофункциональный советник. Разработка специализированного, сфокусированного советника позволяет нам разложить большую проблему на управляемые части, создать и усовершенствовать функцию создания отчетов по отдельности, проверить ее с помощью дымовых тестов и целенаправленной отладки, а затем подключить усовершенствованный компонент к более сложным системам с минимальными недостатками.

1. Заголовок, импорт Windows и пользовательский ввод

В данном разделе объявляются метаданные советника, импортируются минимальные вызовы Windows API, которые нам требуются (для проверки атрибутов абсолютного файла и запуска cmd.exe), и предоставляются настраиваемые пользователем входные данные. Сохраняйте эти значения точными для вашей среды (путь к Python, путь к скрипту, выходной каталог), чтобы советник мог проверить их при запуске. Входные данные ReportFormats, EnableEmail и TestOnInit позволяют управлять поведением без изменения кода.

#property copyright "Clemence Benjamin"
#property version   "1.01"
#property description "Exports trading history in CSV and generates reports via Python"
#property strict

#define SW_HIDE                 0
#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF

#import "kernel32.dll"
uint   GetFileAttributesW(string lpFileName);
#import

#import "shell32.dll"
int    ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
#import

//--- User inputs
input string PythonPath   = "C:\\Users\\YOUR_COMPUTER_NAME\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
input string ScriptPath   = "C:\\Users\\YOUR_COMPUTER_NAME\\PATH_TO\\reports_processor.py";
input string ReportFormats= "html,pdf";
input string OutputDir    = "C:\\Users\\YOUR_COMPUTER_NAME\\PATH_TO_WHERE_YOUR_Reports_are_ saved";
input bool   EnableEmail  = true;
input bool   TestOnInit   = true;

2. Инициализация и проверка среды (OnInit)

OnInit() запускает советник, запускает RNG, очищает настроенные пути, регистрирует каталог терминальных файлов, проверяет наличие исполняемого файла Python и выходного каталога и подтверждает права на запись в MQL5\Files. Когда функция TestOnInit включена, запускается тестовый запуск — дымовой тест, позволяющий убедиться в правильности подключения сквозной цепи.

int OnInit()
{
   Print(">> Reporting EA initializing…");
   MathSrand((uint)TimeLocal());

   string cleanPythonPath = CleanPath(PythonPath);
   string cleanScriptPath = CleanPath(ScriptPath);
   string cleanOutputDir = CleanPath(OutputDir);
   Print("PythonPath set to: ", cleanPythonPath);
   Print("ScriptPath set to: ", cleanScriptPath);
   Print("OutputDir set to: ", cleanOutputDir);

   string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   Print("Terminal Files directory: ", filesDir);

   if(GetFileAttributesW(cleanPythonPath) == INVALID_FILE_ATTRIBUTES)
      Print("!! Python executable not found at: ", cleanPythonPath);
   else
      Print("✔ Found Python at: ", cleanPythonPath);

   // Write-permission test (MQL5\\Files)
   int h = FileOpen("test_perm.txt", FILE_WRITE|FILE_TXT);
   if(h == INVALID_HANDLE)
      Print("!! Cannot write to MQL5\\Files directory! Error: ", GetLastError());
   else
   {
      FileWrite(h, "OK");
      FileFlush(h);
      FileClose(h);
      FileDelete("test_perm.txt");
      Print("✔ Write permission confirmed.");
   }

   if(TestOnInit)
   {
      Print(">> Test mode: running initial export.");
      RunDailyExport();
      lastRunTime = TimeCurrent();
   }

   return(INIT_SUCCEEDED);
}

3. Утилиты Path и основные помощники

Чистые, нормализованные пути и надежная проверка файлов позволяют избежать целого класса ошибок во время выполнения. CleanPath() нормализует косые черты и предупреждает о пробелах; FileExists() оборачивает GetFileAttributesW для абсолютных путей и возвращается к FileOpen для файлов, находящихся на терминале. Держите этих помощников в центре внимания и повторно используйте их в работе советника.

string CleanPath(string path)
{
   string cleaned = path;
   StringReplace(cleaned, "/", "\\");
   while(StringFind(cleaned,"\\\\")>=0) StringReplace(cleaned, "\\\\", "\\");
   StringTrimLeft(cleaned);
   StringTrimRight(cleaned);
   return cleaned;
}

bool FileExists(string path)
{
   if(StringFind(path, "\\", 0) >= 0)
   {
      uint attrs = GetFileAttributesW(path);
      return(attrs != INVALID_FILE_ATTRIBUTES);
   }
   else
   {
      int fh = FileOpen(path, FILE_READ|FILE_TXT);
      if(fh == INVALID_HANDLE) return false;
      FileClose(fh);
      return true;
   }
}

4. Логика создания и экспорта CSV-файлов

Функция ExportHistoryToCSV() обрабатывает выбор истории и записывает CSV-файл с заголовком и рядами для каждой сделки. Она использует MakeUniqueFilename(), чтобы избежать коллизий (полезно при повторных запусках), и возвращает выбранное имя файла в глобальном LastExportedCSV. Вам надо, чтобы ваш CSV-файл был детерминированным и включал столбцы, ожидаемые сценарием Python.

string MakeUniqueFilename(string baseName)
{
   int dot = StringFind(baseName, ".", 0);
   string name = dot >= 0 ? StringSubstr(baseName, 0, dot) : baseName;
   string ext  = dot >= 0 ? StringSubstr(baseName, dot) : "";
   string ts = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
   StringReplace(ts, ".", ""); StringReplace(ts, " ", "_"); StringReplace(ts, ":", "");
   int suffix = MathAbs(MathRand()) % 9000 + 1000;
   return name + "_" + ts + "_" + IntegerToString(suffix) + ext;
}

bool ExportHistoryToCSV(string filename)
{
   datetime end = TimeCurrent();
   datetime start = end - 7776000; // 90 days
   if(!HistorySelect(start, end)) { Print("!! HistorySelect failed."); return false; }
   int total = HistoryDealsTotal();
   if(total == 0) { Print("!! No trading history found."); return false; }

   int attempts = 3;
   string tryName = filename;
   int fh = INVALID_HANDLE;
   for(int a=0; a<attempts; a++)
   {
      fh = FileOpen(tryName, FILE_WRITE|FILE_CSV|FILE_ANSI, ",");
      if(fh != INVALID_HANDLE) break;
      tryName = MakeUniqueFilename(filename);
      Sleep(200);
   }
   if(fh == INVALID_HANDLE) { Print("!! FileOpen failed after retries."); return false; }

   FileWrite(fh, "Ticket,Time,Type,Symbol,Volume,Price,Profit,Commission,Swap,Balance,Equity");

   // ... iterate deals and FileWrite rows ...
   FileFlush(fh); FileClose(fh);
   LastExportedCSV = tryName;
   return true;
}

5. Вызов и управление Python (RunDailyExport)

RunDailyExport() - это центр управления: он создает путь к CSV-файлу, запускает Python с помощью cmd.exe и перенаправляет stdout/stderr в python_run.log в MQL5\Files, чтобы советник мог просматривать выходные данные скрипта. Затем метод запрашивает предпочтительный report_result_YYYY-MM-DD.json (созданный на Python) и возвращается к опросу в формате PDF, если JSON недоступен. В этом дизайне приоритет отдается структурированному обмену данными в формате JSON, чтобы советник мог надежно подтвердить успех и вложения.

void RunDailyExport()
{
   string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   string dateStr = TimeToString(TimeCurrent(), TIME_DATE); StringReplace(dateStr, ".", "");
   string csvBase = "History_" + dateStr + ".csv";

   LastExportedCSV = "";
   if(!ExportHistoryToCSV(csvBase)) { Print("!! CSV export failed"); return; }
   string csvFull = filesDir + LastExportedCSV;

   string cleanOutputDir  = CleanPath(OutputDir);
   string pyLogName = "python_run.log";
   string pyLogFull = filesDir + pyLogName;

   string pythonCmd = StringFormat("\"%s\" \"%s\" \"%s\" --formats %s --outdir \"%s\" %s > \"%s\" 2>&1",
      CleanPath(PythonPath), CleanPath(ScriptPath), csvFull, "pdf", cleanOutputDir, EnableEmail ? "--email" : "", pyLogFull);

   string fullCmd = "/c " + pythonCmd;
   PrintFormat("→ Launching: cmd.exe %s", fullCmd);
   int result = ShellExecute(fullCmd);
   PrintFormat("← ShellExecute returned: %d", result);

   // Then poll for JSON or fallback to PDF; see next section for JSON handling...
}

6. Стратегия обработки и резервного чтения в формате JSON

Чтобы избежать хрупкой зависимости от удобочитаемых для человека логов, советник предпочитает машиночитаемый результат в формате JSON. Функция ReadFileText() пытается напрямую считывать абсолютные пути к файлам в формате JSON; когда MetaTrader 5 отказывает в абсолютном FileOpen, функция CopyToFilesAndRead() использует копию cmd для копирования JSON в MQL5\Files, а затем считывает его с помощью FileOpen. JsonExtractString / JsonExtractBool - это небольшие, прагматичные средства извлечения, достаточные для простого JSON-контракта (pdf_path, email_sent).

string ReadFileText(string fullpath)
{

   if(StringFind(fullpath, "\\", 0) >= 0)
   {
      uint attrs = GetFileAttributesW(fullpath);
      if(attrs == INVALID_FILE_ATTRIBUTES) return("");
      int fh = FileOpen(fullpath, FILE_READ|FILE_TXT);
      if(fh == INVALID_HANDLE) return("");
      string txt = "";
      while(!FileIsEnding(fh)) { txt += FileReadString(fh); if(!FileIsEnding(fh)) txt += "\n"; }
      FileClose(fh);
      return txt;
   }

   else
   {
      int fh = FileOpen(fullpath, FILE_READ|FILE_TXT);
      if(fh == INVALID_HANDLE) return("");

      // ... read and return ...
   }
}

string CopyToFilesAndRead(string absPath, string filesName)
{
   string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   string destAbs = filesDir + filesName;
   string cmd = "/c copy /Y \"" + absPath + "\" \"" + destAbs + "\" > nul 2>&1";
   int r = ShellExecute(cmd);

   // wait briefly for copy then read local file
   return ReadTerminalLogFile(filesName);
}

string JsonExtractString(string json, string key) { /* tiny extractor: locate "key": "value" */ }
bool   JsonExtractBool(string json, string key)   { /* tiny extractor: locate "key": true/false */ }

7. Проверка, уведомление и корректный возврат

После анализа JSON-файла советник проверяет значения pdf_path и html_path с помощью FileExists() и, при необходимости, уведомляет трейдера с помощью SendNotification(). Если чтение JSON или проверка завершаются неудачей, советник корректно возвращается к завершению работы с python_run.log и запрашивает ожидаемый Report_YYYY-MM-DD.pdf (размер > 0). Этот многоуровневый подход обеспечивает баланс между надежностью (JSON) и безотказностью (PDF/log резервирование).

if(StringLen(pdfp) > 0)
{
   if(FileExists(pdfp))
   {
      PrintFormat("✔ PDF exists at (from JSON): %s", pdfp);
      SendNotification("Report PDF created: " + pdfp);
   }
   else
      Print("!! JSON says PDF path but file not found: ", pdfp);
}

// fallback: if JSON unavailable -> poll expectedPdf in OutputDir and check size

8. Поддержка помощников и демонтаж

Служебные утилиты, такие как GetFileSizeByOpen() (безопасная проверка размера файла), ReadTerminalLogFile() (чтение файлов из MQL5\Files) и обертка ShellExecute(), невелики, но важны. OnDeinit() выводит сообщение об очистке. Сохраняйте их компактными и хорошо документированными; они упрощают отладку и сокращают дублирование.

long GetFileSizeByOpen(string filename)

{

   int fh = FileOpen(filename, FILE_READ|FILE_BIN);
   if(fh == INVALID_HANDLE) return -1;
   int pos = FileSeek(fh, 0, SEEK_END);
   FileClose(fh);
   return (pos < 0) ? -1 : (long)pos;
}

string ReadTerminalLogFile(string filename)

{
   int fh = FileOpen(filename, FILE_READ | FILE_TXT);
   if(fh == INVALID_HANDLE) return("");
   string content = "";
   while(!FileIsEnding(fh)) { content += FileReadString(fh); if(!FileIsEnding(fh)) content += "\n"; }
   FileClose(fh);
   return content;
}
int ShellExecute(string command)
{
   return ShellExecuteW(0, "open", "cmd.exe", command, NULL, SW_HIDE);
}


Обновление Python-скрипта reports_processor

Советник работает рука об руку со скриптом на Python reports_processor.py. Поэтому теперь, когда мы обновили советник, необходимо адаптировать его к Python для сохранения результатов и представления отчетов таким образом, чтобы советник мог эффективно их использовать. В настоящем разделе мы подробно объясним код скрипта и покажем, как именно каждая часть интегрируется с советником — от обработки CSV и аналитики до рендеринга в формате PDF и выдачи результатов в формате JSON — для обеспечения надежного и автоматизируемого процесса создания отчетов. 

Прежде чем приступить к разбивке кода нашего скрипта, нам сначала нужно настроить среду Python, чтобы убедиться, что система может полностью поддерживать функциональность скрипта. На следующих этапах мы рассмотрим процесс настройки, начиная с установки. Выполнение этого критически важного шага дает нам уверенность в том, что среда настроена должным образом, что позволяет нам беспрепятственно выполнять тесты во время разработки скрипта reports_processor.

Пошаговое руководство: Настройка Python в Windows для скрипта reports_processor

1. Установите Python 3.9+ с python.org и убедитесь, что python указан в PATH.

2. Откройте PowerShell и создайте + активируйте venv:

python -m venv venv
.\venv\Scripts\Activate.ps1

3. Обновите пипс:

python -m pip install --upgrade pip

4. Установите основные пакеты (fpdf - это бэкенды PDF по умолчанию; при желании добавьте другие):

pip install pandas numpy jinja2 matplotlib fpdf yagmail

Дополнительные бэкенды:

pip install pdfkit weasyprint

Если вы используете wkhtmltopdf, загрузите его установщик Windows и добавьте исполняемый файл в PATH или задайте WKHTMLTOPDF_PATH env var.

5. Установите переменные среды для электронной почты (например, задайте в свойствах системы → Переменные среды или PowerShell):

setx YAGMAIL_USERNAME "you@example.com"
setx YAGMAIL_PASSWORD "your-email-password"
setx WKHTMLTOPDF_PATH "C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"  # if using wkhtmltopdf

6. Тестовый запуск (из папки проекта, с активным venv):

python reports_generator.py "C:\path\to\MQL5\Files\History_20250821.csv" --formats pdf --outdir "C:\path\to\Reports" --email

7. Проверьте выходные данные в C:\path\to\Reports:

  • Report_YYYY-MM-DD.pdf (основной)
  • equity_curve_YYYY-MM-DD.png (график)
  • report_result_YYYY-MM-DD.json (подтверждение советника)

Reports_processor.py

1. Запуск, импорт и обнаружение бэкендов

При запуске скрипт загружает стандартные библиотеки Python (os, sys, logging, datetime, argparse, glob) и основные библиотеки данных (pandas, numpy). Затем он пытается импортировать дополнительные цепочки инструментов — WeasyPrint, pdfkit (wkhtmltopdf), FPDF и yagmail — и записывает флаги доступности для каждого бэкенда. Эти флаги позволяют сценарию выбирать подходящий способ создания PDF-файлов или отправки по электронной почте во время выполнения на основе того, что установлено, и при этом работать в средах, где отсутствуют некоторые дополнительные пакеты. Ведение журнала настраивается заранее, поэтому скрипт выдает информационные сообщения и сообщения об ошибках с временными метками, которые советник может ввести в python_run.log.

#!/usr/bin/env python
# reports_processor.py
__version__ = "1.0.1" 
import sys
import os
import traceback
import argparse
import glob
from datetime import datetime, timedelta
import logging

# core data libs
import pandas as pd
import numpy as np

# templating & plotting
from jinja2 import Environment, FileSystemLoader
import matplotlib.pyplot as plt

# optional libraries: import safely
WEASY_AVAILABLE = False
PDFKIT_AVAILABLE = False
FPDF_AVAILABLE = False
YAGMAIL_AVAILABLE = False

try:
    from weasyprint import HTML          # may fail if native libs aren't installed
    WEASY_AVAILABLE = True
except Exception:
    WEASY_AVAILABLE = False

try:
    import pdfkit                       # wrapper for wkhtmltopdf
    PDFKIT_AVAILABLE = True
except Exception:
    PDFKIT_AVAILABLE = False

try:
    from fpdf import FPDF               # pure-python PDF writer (no native deps)
    FPDF_AVAILABLE = True
except Exception:
    FPDF_AVAILABLE = False

try:
    import yagmail
    YAGMAIL_AVAILABLE = True
except Exception:
    YAGMAIL_AVAILABLE = False

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

2. Синтаксический анализ аргументов и командный контракт

Скрипт предоставляет небольшой интерфейс командной строки: его позиционный csv_path принимает либо путь к файлу CSV, либо папку для поиска; --formats определяет, какие выходные данные создавать (HTML, PDF или оба варианта); --outdir указывает, куда записывать артефакты; --email переключает отправку отчетов; и --pdf-backend выбирает, какой PDF-путь использовать. Этот контракт соответствует паттерну вызова советника: советник предоставляет путь в формате CSV, выходной каталог и опциональный флажок электронной почты, а затем отслеживает выходной каталог на предмет получения результата в формате JSON.

def parse_args():
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(description="Generate trading reports (HTML/PDF) from CSV.")
    parser.add_argument("csv_path", help="Path to the trades CSV file or a folder to search for History_*.csv")
    parser.add_argument("-f", "--formats", nargs="+", choices=["html", "pdf"], default=["pdf"],
                        help="Report formats to generate (html, pdf, or both)")
    parser.add_argument("-o", "--outdir", default=".", help="Directory to write report files")
    parser.add_argument("--email", action="store_true", help="Send report via email")
    parser.add_argument("--pdf-backend", choices=["weasyprint","wkhtmltopdf","fpdf"],
                        default="fpdf",
                        help="PDF backend to use. 'weasyprint', 'wkhtmltopdf', or 'fpdf' (no native deps). Default: fpdf")
    return parser.parse_args()

3. Разрешение в формате CSV (поиск входного файла)

Когда задается папка или гибкий csv_path, скрипт определяет фактический CSV для обработки. Если csv_path - это каталог, он выполняет поиск файлов History_*.csv (возвращаясь к любому .csv, если ни один из них не совпадает) и выбирает самый последний измененный файл. Если указан полный путь к файлу CSV, он принимается как есть. Это делает скрипт толерантным и простым в вызове как из советника (который передает определенный CSV-файл), так и для ручного запуска.

def resolve_csv_path(csv_path):
    """
    Ensure csv_path points to an existing file. If not, try to locate the
    most recent History_*.csv in the same directory or the provided folder.
    Returns resolved path or raises FileNotFoundError.
    """
    # If csv_path is a directory -> look there
    if os.path.isdir(csv_path):
        search_dir = csv_path
    else:
        # If path exists as file -> return
        if os.path.isfile(csv_path):
            return csv_path
        # otherwise, take parent directory to search
        search_dir = os.path.dirname(csv_path) or "."

    pattern = os.path.join(search_dir, "History_*.csv")
    candidates = glob.glob(pattern)
    if not candidates:
        # also allow any .csv if no History_*.csv found
        candidates = glob.glob(os.path.join(search_dir, "*.csv"))
    if not candidates:
        raise FileNotFoundError(f"No CSV files found in {search_dir} (tried {pattern})")

    # pick the newest file by modification time
    candidates.sort(key=os.path.getmtime, reverse=True)
    chosen = candidates[0]
    logging.info(f"Resolved CSV path: {chosen} (searched: {search_dir})")
    return chosen

4. Загрузка и нормализация торговых данных

Загрузчик считывает CSV с помощью pandas, пытаясь преобразовать столбец времени в datetimes. Он обеспечивает наличие необходимых столбцов (например, прибыль, символ, баланс и эквити), заполняя значения по умолчанию или вычисляя значения, когда это возможно (например, совокупные суммы для баланса/эквити, когда это необходимо). После нормализации DataFrame сортируется по времени, создавая согласованный набор данных, упорядоченный по времени, для аналитики и построения графиков.

def load_data(csv_path):
    """Load and validate CSV data, computing missing columns if necessary."""
    try:
        if not os.path.isfile(csv_path):
            logging.error(f"CSV file not found: {csv_path}")
            raise FileNotFoundError(f"CSV file not found: {csv_path}")
        # try parsing Time; if fails, read without parse and try to coerce later
        try:
            df = pd.read_csv(csv_path, parse_dates=["Time"])
        except Exception:
            df = pd.read_csv(csv_path)
            if "Time" in df.columns:
                df["Time"] = pd.to_datetime(df["Time"], errors="coerce")
        # Provide minimal defaults if columns missing
        if "Profit" not in df.columns:
            df["Profit"] = 0.0
        if "Symbol" not in df.columns:
            df["Symbol"] = "N/A"
        required_cols = ["Time", "Symbol", "Profit", "Balance", "Equity"]
        for col in required_cols:
            if col not in df.columns:
                logging.warning(f"Missing column: {col}. Attempting to compute.")
                if col == "Balance":
                    # create cumsum of profit + commission + swap if possible
                    df["Commission"] = df.get("Commission", 0)
                    df["Swap"] = df.get("Swap", 0)
                    df["Balance"] = (df["Profit"] + df["Commission"] + df["Swap"]).cumsum()
                elif col == "Equity":
                    df["Equity"] = df.get("Balance", df["Profit"].cumsum())
        # ensure Time column exists and is sorted
        if "Time" in df.columns:
            df = df.sort_values(by="Time")
        return df
    except Exception as e:
        logging.error(f"Error loading CSV: {e}")
        raise

5. Вычислительная аналитика и показатели

На шаге compute_stats выводятся ключевые показатели отчета, которые важны советнику и пользователю: строка даты отчета, чистая прибыль, общее количество сделок, наиболее эффективный инструмент (по совокупной прибыли), максимальная просадка, рассчитанная по кривой эквити, и простая оценка коэффициента Шарпа. Эти значения упаковываются в статистический словарь, а затем встраиваются как в HTML-шаблоны, так и в PDF-страницы для представления краткой сводки.

def compute_stats(df):
    """Compute trading statistics from the DataFrame."""
    try:
        top_symbol = None
        try:
            top_symbol = df.groupby("Symbol")["Profit"].sum().idxmax()
        except Exception:
            top_symbol = "N/A"
        eq = df.get("Equity")
        if eq is None:
            eq = df.get("Balance", df["Profit"].cumsum())
        max_dd = float((eq.cummax() - eq).max()) if len(eq) > 0 else 0.0
        sharpe = 0.0
        if len(df) > 1 and "Profit" in df.columns:
            dif = df["Profit"].diff().dropna()
            if dif.std() != 0:
                sharpe = float((dif.mean() / dif.std()) * np.sqrt(252))
        stats = {
            "date": datetime.now().strftime("%Y-%m-%d"),
            "net_profit": float(df["Profit"].sum()) if "Profit" in df.columns else 0.0,
            "trade_count": int(len(df)),
            "top_symbol": top_symbol,
            "max_drawdown": max_dd,
            "sharpe_ratio": sharpe
        }
        return stats
    except Exception as e:
        logging.error(f"Error computing stats: {e}")
        raise

6. Построение графика —кривая собственного капитала / баланса

Скрипт создает кривую эквити в формате PNG с помощью matplotlib. Он предпочитает отображать время в зависимости от баланса и эквити, когда присутствуют временные метки; в противном случае он отображает индекс в зависимости от баланса /эквити. Сгенерированный PNG-файл сохраняется в --outdir, а затем на него ссылаются в HTML-шаблоне или встраивают в PDF-файл, предоставляя читателям визуальную сводку о работе аккаунта в окне отчетов.

# Generate equity curve plot
curve_image = os.path.join(args.outdir, f"equity_curve_{stats['date']}.png")
plt.figure()
# handle possibility of NaT in Time
if "Time" in df.columns and not df["Time"].isnull().all():
    plt.plot(df["Time"], df["Balance"], label="Balance")
    plt.plot(df["Time"], df["Equity"], label="Equity")
else:
    # fallback: plot index vs balance/equity
    plt.plot(df.index, df["Balance"], label="Balance")
    plt.plot(df.index, df["Equity"], label="Equity")
plt.title("Balance & Equity Curve")
plt.legend()
plt.tight_layout()
plt.savefig(curve_image)
plt.close()
logging.info(f"Equity curve saved: {curve_image}")

7. Рендеринг HTML с помощью Jinja2

Если запрашивается вывод HTML (или создается как промежуточный артефакт), скрипт использует Jinja2 для рендеринга report_template.html. Шаблон получает словарь статистики, сводные данные о прибыли по каждому символу и имя файла с изображением кривой. Отрисованный HTML-код записывается в выходную папку и регистрируется в логе. Этот HTML-код можно использовать как конечный результат или как исходные данные для конвертеров HTML в PDF.

def render_html(df, stats, outpath, curve_image):
    """Render the HTML report using a Jinja2 template."""
    try:
        env = Environment(loader=FileSystemLoader(os.path.dirname(__file__)))
        tmpl_path = os.path.join(os.path.dirname(__file__), "report_template.html")
        if not os.path.isfile(tmpl_path):
            logging.error(f"Template not found: {tmpl_path}")
            raise FileNotFoundError(f"Missing template: {tmpl_path}")
        tmpl = env.get_template("report_template.html")
        html = tmpl.render(stats=stats, symbol_profits=df.groupby("Symbol")["Profit"].sum().to_dict(), curve_image=curve_image)
        os.makedirs(os.path.dirname(outpath), exist_ok=True)
        with open(outpath, "w", encoding="utf-8") as f:
            f.write(html)
        logging.info(f"HTML written: {outpath}")
    except Exception as e:
        logging.error(f"Error rendering HTML: {e}")
        raise

8. Генерация PDF—файлов - множество бэкендов

Скрипт поддерживает три стратегии создания PDF-файлов и выбирает одну из них на основе аргумента --pdf-backend и обнаруженной доступности:

  • WeasyPrint: преобразует отрисованный HTML непосредственно в PDF (HTML → PDF) с помощью WeasyPrint, если он присутствует.
  • wkhtmltopdf (pdfkit): аналогично преобразует HTML в PDF с помощью двоичного файла wkhtmltopdf при настройке.
  • FPDF: путь на чистом Python, который программно создает PDF-файл и вставляет диаграммы и таблицы данных, не полагаясь на рендеринг HTML/CSS. Этот путь позволяет создать многостраничный PDF-файл формата А4 с титульным листом, блоками ключевых характеристик, графиком эквити, таблицами прибыли по символам и выборкой последних сделок.

def convert_pdf_weasy(html_path, pdf_path):
    if not WEASY_AVAILABLE:
        raise RuntimeError("WeasyPrint backend requested but weasyprint is not available.")
    os.makedirs(os.path.dirname(pdf_path), exist_ok=True)
    HTML(html_path).write_pdf(pdf_path)
    logging.info(f"PDF written: {pdf_path}")

def convert_pdf_wkhtml(html_path, pdf_path, wk_path=None):
    if not PDFKIT_AVAILABLE:
        raise RuntimeError("wkhtmltopdf/pdfkit backend requested but pdfkit is not available.")
    config = None
    if wk_path and os.path.isfile(wk_path):
        config = pdfkit.configuration(wkhtmltopdf=wk_path)
    pdfkit.from_file(html_path, pdf_path, configuration=config)
    logging.info(f"PDF written: {pdf_path}")

def convert_pdf_with_fpdf(df, stats, curve_image_path, pdf_path):
    if not FPDF_AVAILABLE:
        raise RuntimeError("FPDF backend requested but fpdf is not installed.")
    pdf = FPDF(orientation='P', unit='mm', format='A4')
    pdf.set_auto_page_break(auto=True, margin=15)
    pdf.add_page()
    pdf.set_font("Arial", "B", 16)
    pdf.cell(0, 8, f"Trading Report - {stats.get('date','')}", ln=True, align='C')
    # ... add stats, insert curve image and tables ...
    if curve_image_path and os.path.isfile(curve_image_path):
        try:
            pdf.image(curve_image_path, x=10, y=None, w=190)
        except Exception as e:
            logging.warning(f"Could not insert curve image into PDF: {e}")
    pdf.output(pdf_path)
    logging.info(f"PDF written: {pdf_path}")

Каждый бэкенд записывает окончательный файл Report_YYYY-MM-DD.pdf в настроенный выходной каталог и сообщает о его завершении посредством ведения лога.

9. Отправка Email 

Если установлен флаг --email, скрипт использует вспомогательную подпрограмму электронной почты для отправки сгенерированных файлов отчетов настроенному получателю с использованием доступной библиотеки электронной почты. Процедура регистрирует, была ли отправка успешной, и результат фиксируется позже при обмене данными в формате JSON, чтобы советник мог определить, было ли отправлено электронное письмо во время этого прогона.

def send_email(files, stats):
    """Send the generated reports via email."""
    try:
        if not YAGMAIL_AVAILABLE:
            logging.error("yagmail not available; cannot send email. Install yagmail or remove --email flag.")
            return
        user = os.getenv("Your YAGMAIL ACCOUNT")
        pw = os.getenv("Your Email password")
        if not user or not pw:
            logging.error("Email credentials not set in environment variables (YAGMAIL_USERNAME, YAGMAIL_PASSWORD).")
            return
        yag = yagmail.SMTP(user, pw)
        subject = f"Trading Report {stats['date']}"
        contents = [f"See attached report for {stats['date']}"] + files
        yag.send(to=user, subject=subject, contents=contents)
        logging.info("Email sent.")
    except Exception as e:
        logging.error(f"Error sending email: {e}")

10. Результат взаимодействия JSON writer — советник

После генерации выходных данных скрипт компилирует компактный, машиночитаемый JSON-файл, содержащий pdf_path, html_path, email_sent, временную метку и exit_code. JSON записывается атомарно (через временный файл + переименование) в --outdir как report_result_YYYY-MM-DD.json. Поскольку запись является атомарной, советник может безопасно проверить наличие файла JSON в качестве надежного сигнала о завершении работы на стороне Python, а затем прочитать его содержимое, чтобы узнать точные места вывода и о том, была ли отправлена электронная почта.

def write_result_json(outdir, pdf_path=None, html_path=None, email_sent=False, exit_code=0):
    """Write a small JSON file with the report result so MQL5 can poll/verify."""
    import json, tempfile
    try:
        result = {
            "pdf_path": pdf_path or "",
            "html_path": html_path or "",
            "email_sent": bool(email_sent),
            "timestamp": datetime.now().isoformat(),
            "exit_code": int(exit_code)
        }
        os.makedirs(outdir, exist_ok=True)
        fname = os.path.join(outdir, f"report_result_{datetime.now().strftime('%Y-%m-%d')}.json")
        # atomic write
        fd, tmp = tempfile.mkstemp(prefix="._report_result_", dir=outdir, text=True)
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(result, f, indent=2)
        os.replace(tmp, fname)
        logging.info(f"Result JSON written: {fname}")
        return fname
    except Exception as e:
        logging.error(f"Failed to write result JSON: {e}")
        return None

11. Основной поток

Функция main() объединяет все в единое целое: она анализирует аргументы, преобразует CSV, загружает и нормализует данные, вычисляет статистику, генерирует график эквити, отображает HTML (по запросу), генерирует PDF(ы) с помощью выбранного бэкенда, при необходимости отправляет электронную почту, очищает старые отчеты в выходном каталоге, записывает результат в формате JSON и завершает работу с кодом success или failure. Последовательность действий и ведение лога гарантируют, что советник, запустивший скрипт, сможет отслеживать ход выполнения (через файл python_run.log) и реагировать на итоговый отчет_result_...json.

def main():
    """Main function to generate reports."""
    args = parse_args()
    try:
        # Ensure output directory exists
        os.makedirs(args.outdir, exist_ok=True)

        # Resolve csv_path in case a folder or variable-name was provided
        try:
            csv_path_resolved = resolve_csv_path(args.csv_path)
        except FileNotFoundError as e:
            logging.error(str(e))
            return 1

        # Load data and compute stats
        df = load_data(csv_path_resolved)
        stats = compute_stats(df)

        # Generate equity curve plot
        curve_image = os.path.join(args.outdir, f"equity_curve_{stats['date']}.png")
        # ... plotting code ...

        base = os.path.join(args.outdir, f"Report_{stats['date']}")
        files_out = []

        # Generate HTML report (if requested or needed by PDF backend)
        html_path = base + ".html"
        if "html" in args.formats:
            render_html(df, stats, html_path, os.path.basename(curve_image))
            files_out.append(html_path)

        # Decide PDF backend and generate PDF
        backend = args.pdf_backend.lower() if hasattr(args, "pdf_backend") else "fpdf"
        if "pdf" in args.formats:
            pdf_path = base + ".pdf"
            if backend == "weasyprint":
                convert_pdf_weasy(html_path, pdf_path)
            elif backend == "wkhtmltopdf":
                convert_pdf_wkhtml(html_path, pdf_path, wk_path=os.getenv("WKHTMLTOPDF_PATH"))
            elif backend == "fpdf":
                convert_pdf_with_fpdf(df, stats, curve_image, pdf_path)
            files_out.append(pdf_path)

        # Send email if enabled
        if args.email:
            send_email(files_out, stats)

        # write result JSON for MQL5
        pdf_file = next((f for f in files_out if f.endswith(".pdf")), "")
        html_file = next((f for f in files_out if f.endswith(".html")), "")
        write_result_json(args.outdir, pdf_path=pdf_file, html_path=html_file,
                          email_sent=args.email, exit_code=0)

        return 0

    except Exception as e:
        logging.error(f"Main execution error: {e}")
        traceback.print_exc()
        try:
            write_result_json(args.outdir, exit_code=1)
        except:
            pass
        return 1

if __name__ == "__main__":
    sys.exit(main())

12. Обработка ошибок и гарантированный JSON в случае сбоя

В функции main() исключения перехватываются на верхнем уровне, так что, если возникает какая-либо необработанная ошибка, скрипт отслеживает снизу вверх и по-прежнему записывает report_result_YYYY-MM-DD.json с exit_code, указывая на сбой. Это гарантирует, что советник всегда получает детерминированный объект JSON для проверки (success или failure), что упрощает логику проверки советника и резервного копирования.

except Exception as e:
    logging.error(f"Main execution error: {e}")
    traceback.print_exc()
    # ensure a JSON is still written so MQL5 knows it failed
    try:
        write_result_json(args.outdir, exit_code=1)
    except:
        pass
    return 1

13. Политика очистки и хранения

В конце успешного запуска скрипт выполняет проверку сохранности выходного каталога, удаляя старые файлы Report_ за пределы настраиваемого окна (текущий скрипт использует 30 дней). Это позволяет поддерживать порядок в выходной папке и предотвращает неограниченный рост объема диска в системах, которые часто запускают отчеты.

# Clean up old reports (older than 30 days)
cutoff = datetime.now() - timedelta(days=30)
for f in os.listdir(args.outdir):
    if f.startswith("Report_") and f.endswith(tuple(args.formats)):
        full = os.path.join(args.outdir, f)
        if datetime.fromtimestamp(os.path.getmtime(full)) < cutoff:
            os.remove(full)
            logging.info(f"Deleted old file: {full}")

Как советник и скрипт на Python интегрируются во время выполнения

Обмен данными во время выполнения прост: советник экспортирует CSV-файл и запускает скрипт (передавая путь к CSV-файлу, --outdir и любые флаги). Скрипт на Python обрабатывает CSV и записывает артефакты (PNG, PDF) в выходную папку и, наконец, записывает report_result_YYYY-MM-DD.json. Советник запрашивает этот JSON-файл; после его получения он считывает JSON-файл, проверяет указанные пути и размеры файлов и выполняет последующие действия (уведомления, архивирование, подтверждение электронной почты). Ведение лога (python_run.log) и JSON вместе обеспечивают надежный, совместимый с компьютером контракт между двумя компонентами.


Тестирование 

Наше тестирование проводится путем развертывания советника на платформе MetaTrader 5, где скрипт на Python запускается и выполняется в фоновом режиме. Результаты можно отслеживать через журнал экспертов в терминале, а также путем проверки указанных выходных каталогов на наличие сгенерированных файлов. В своем тесте я нашел выходной отчет с именем Report_2025-08-21. Содержимое этого файла гораздо более обширно, чем в предыдущих версиях. Отчет теперь включает в себя хорошо структурированные таблицы, кривые эквити и множество рассчитанных показателей, все это представлено в понятном формате, который дает ценную информацию трейдерам. Ниже приведены скриншоты, демонстрирующие сгенерированные файлы отчетов и частично содержимое PDF.

Рисунок 4: Выходные файлы, просматриваемые в Windows Explorer

PDF contents of the generated file

Рисунок 5: Анимированный скриншот из сгенерированных отчетов в формате PDF

Equity Curve produce

Рисунок 6: Кривая эквити, представленная в отчетах



Заключение

Мы успешно продемонстрировали интеграцию MQL5 и Python для создания многофункциональных торговых отчетов, которые выходят далеко за рамки базовых результатов, предоставляемых предыдущей версией нашего советника Reporting EA. Одной из отличительных особенностей является автоматическая кривая эквити, которая визуально отслеживает баланс и тенденции изменения эквити с течением времени, предлагая трейдерам мгновенное представление о динамике показателей. Система доказывает возможность подключения советника к внешнему механизму создания отчетов на Python, а также демонстрирует, как это сотрудничество может быть усовершенствовано и расширено в дальнейшем.

Постоянно совершенствуя код советника и используя мощные библиотеки Python, такие как Pandas, Matplotlib и FPDF, мы можем рассчитывать расширенную статистику, создавать содержательные таблицы и встраивать четкие визуализации в отчеты. Добавление упрощенного обмена данными в формате JSON между советником и скриптом на Python обеспечивает надежное подтверждение выходных данных, делая рабочий процесс плавным и прозрачным. Кроме того, дополнительные функции, такие как автоматическая доставка по электронной почте и гибкие бэкенды PDF, демонстрируют адаптивность платформы к различным средам.

Эта разработка закладывает прочную основу для долгосрочных решений в области отчетности. Полученные в результате документы могут служить комплексными инструментами для анализа эффективности торговли, помогая как трейдерам, так и инвесторам выявлять спотовые тренды, измерять риски, такие как просадки, и более четко оценивать стратегии. В конечном счете, советник Reporting EA в сочетании со скриптом reports_processor.py показывает, как межъязыковая интеграция может повысить уровень автоматизации, улучшить процесс принятия решений и открыть двери для еще более сложной аналитики в будущем.


Основные уроки

Основной урок Описание:
Проверьте внешние зависимости при запуске. Всегда проверяйте расположение внешних инструментов и папок (исполняемый файл Python, путь к скрипту, выходной каталог) во время OnInit, используя проверки атрибутов файлов. Ранняя проверка с помощью GetFileAttributesW предотвращает непонятные сбои во время выполнения.
Предпочитайте машиночитаемое подтверждение (JSON) Используйте небольшой структурированный файл результатов (JSON) в качестве канонического сигнала от Python к MQL5 вместо синтаксического анализа логов произвольной формы. Это делает проверку детерминированной и программируемой.
Записывайте файлы результатов атомарно Записывайте JSON-файл с временным именем файла, а затем переименуйте/замените его атомарно. Это позволяет избежать скачков частичного чтения, когда советник запрашивает завершение.
Учитывайте ограничения абсолютного пути на MQL5 MQL5 FileOpen часто не может прочитать произвольные абсолютные пути. Предусмотрите резервный вариант: скопируйте файл в MQL5\\Files или прочитайте с помощью WinAPI ReadFile, чтобы советник мог постоянно получать доступ к выходным данным.
Используйте надежные проверки наличия файлов и их размера Не думайте, что присутствие подразумевает полноту. Проверьте атрибуты файла и его размер (открыть + поиск до конца), чтобы убедиться, что отчет или двоичный файл полностью записан, прежде чем приступать к работе с ним.
Используйте уникальные имена файлов, чтобы избежать коллизий Генерируйте CSV-файлы и имена файлов отчетов, содержащие временные метки и случайные суффиксы, чтобы при повторном запуске результаты не перезаписывались, а исторические артефакты сохранялись для аудита.
Последовательный экспорт CSV-файлов для Python Создавайте столбцы CSV (заголовки, форматы, стиль даты/времени) в соответствии с ожиданиями скрипта Python. Согласованный экспортный контракт устраняет двусмысленность при анализе и уменьшает количество ошибок при обработке данных.
Централизация вспомогательных утилит Сохраняйте нормализацию пути, средства поддержки существования файлов, средства извлечения JSON и небольшие программы-обёртки для чтения / записи в одном месте, чтобы каждая часть советника повторно использовала одну и ту же надежную логику (CleanPath, FileExists, ReadTerminalLogFile).
Реализуйте опрос с разумными таймаутами При ожидании внешнего процесса используйте цикл с коротким интервалом опроса и общим таймаутом. Это уравновешивает скорость отклика с загрузкой процессора и позволяет избежать бесконечных ожиданий.
Обеспечьте резервные варианты и многоуровневую проверку Разработайте многоуровневые проверки: предпочитайте подтверждение на основе JSON; если оно отсутствует, вернитесь к завершению python_run.log; если это не удается, выполните опрос в поисках ожидаемого PDF-файла. Наличие нескольких путей проверки повышает надежность.
Обильно регистрируйте и проводите поверхностную диагностику Делайте информативные записи в логе и выводите завершенные записи логов Python в журнал экспертов. Четкая диагностика ускоряет устранение неполадок при запуске в рабочей среде.
Никогда не вводите жесткие учетные данные Храните секреты вдалеке от источника: используйте переменные среды для учетных данных электронной почты и задокументируйте, какие переменные должны быть установлены. Это повышает безопасность и мобильность.
Поддерживайте нескольких бэкендов PDF и определяйте доступность Сделайте скрипт на Python независимым от бэкенда: определите путь к weasyprint, wkhtmltopdf/pdfkit или путь к fpdf на чистом Python и выберите наилучший доступный вариант во время выполнения.
Используйте виртуальные среды и настройку документов Документируйте воспроизводимую настройку Windows: Версия Python, создание virtualenv, список установок pip и собственные условия для дополнительных бэкендов, чтобы развертывание было предсказуемым.
Дымовой тест сквозного потока Проведите быстрый сквозной “дымовой тест”: разверните советник, пусть он экспортирует CSV-файл, запустите Python и проверьте журнал эксперта и выходную папку на наличие PDF/JSON. Дымовые тесты дают быструю уверенность перед более глубоким контролем качества.
Гарантируйте детерминированную отчетность о сбоях При исключениях всегда записывайте результат в формате JSON с exit_code и диагностическими полями. Это гарантирует, что советник сможет обнаруживать сбои и применять резервную логику, а не зависать на неопределенный срок.


Вложения

Имя файла Версия Описание
Reporting_EA.mq5 1.01 Советник, который экспортирует торговую историю в файлы формата CSV с уникальными именами, запускает процессор отчетов на Python и проверяет результаты. Функции включают в себя нормализацию пути, проверку зависимостей, генерацию атомарного имени файла, обмен данными в формате JSON с помощью Python, резервное копирование для чтения по абсолютному пути, резервный опрос в формате PDF, подробное ведение журнала эксперта и дополнительные push-уведомления.
Reports_processor.py 1.0.1 Механизм составления отчетов на Python, который использует CSV-файл советника, вычисляет аналитику (чистая прибыль, просадка, коэффициент Шарпа, агрегаты по каждому символу), создает графики, отрисовывает HTML и/или PDF (несколько вариантов бэкенда: fpdf, wkhtmltopdf, WeasyPrint), при необходимости отправляет электронное письмо и записывает атомарный report_result_YYYY-MM-DD.json для подтверждения установления связи с советником. Включает в себя очистку хранилища и надежную обработку ошибок с гарантированным использованием JSON в случае сбоя.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19006

Прикрепленные файлы |
Reporting_EA.mq5 (50.56 KB)
Нейросети в трейдинге: Адаптивное восприятие рыночной динамики (Энкодер) Нейросети в трейдинге: Адаптивное восприятие рыночной динамики (Энкодер)
В статье представлена комплексная архитектура Энкодера STE-FlowNet, объединяющая стековую память, рекуррентную обработку и корреляционный механизм для извлечения скрытых рыночных зависимостей. Показано, как эти модули последовательно интегрируются в единую вычислительную цепочку, способную осуществлять разносторонний анализ временных рядов.
Разработка инструментария для анализа движения цен (Часть 12): Внешние библиотеки (III) TrendMap Разработка инструментария для анализа движения цен (Часть 12): Внешние библиотеки (III) TrendMap
Движение рынка определяется силами быков и медведей. Существуют определенные уровни, которые рынок соблюдает из-за действующих на них сил. Уровни Фибоначчи и VWAP особенно сильно влияют на поведение рынка. В этой статье мы рассмотрим стратегию, основанную на VWAP и уровнях Фибоначчи для генерации сигналов.
Самоорганизующиеся карты Кохонена в советнике MQL5 Самоорганизующиеся карты Кохонена в советнике MQL5
Самоорганизующиеся карты Кохонена превращают хаос рыночных данных в упорядоченную двумерную карту, где похожие паттерны группируются вместе. Эта статья показывает полную реализацию SOM в торговом советнике MQL5 с четырехстами нейронами и непрерывным обучением. Разбираем алгоритм поиска Best Matching Unit, обновление весов с гауссовой функцией соседства, интеграцию с квантовыми эффектами и создание торговых сигналов. Код открыт, математика понятна, результаты проверяемы.
Математические модели в сеточных стратегиях Математические модели в сеточных стратегиях
В этой статье мы рассмотрим применение математики к сеточным стратегиям. Мы разберем основные принципы работы стратегии, её преимущества и недостатки. Вы узнаете, как построить торговую сетку, задавать оптимальные параметры и эффективно управлять рисками.