English Русский Deutsch 日本語
preview
De novato a experto: Dominando la generación de informes detallados de trading con Reporting EA

De novato a experto: Dominando la generación de informes detallados de trading con Reporting EA

MetaTrader 5Trading |
30 0
Clemence Benjamin
Clemence Benjamin

Contenido:


Introducción

El debate de hoy se centra en abordar los retos relacionados con la entrega de informes de trading en MetaTrader 5. En nuestra anterior publicación, describimos el flujo de trabajo general y los requisitos necesarios para que el sistema funcione de manera eficaz. Presentamos el Reporting EA como una herramienta diseñada para generar y enviar informes de operaciones en formato PDF, con una periodicidad que el usuario puede personalizar.

En esta nueva edición, nuestro objetivo es aprovechar esa base para mejorar tanto la profundidad de la información que contienen los informes como la eficiencia en su difusión. Esta mejora es posible gracias a la integración de MQL5 con potentes bibliotecas de Python, lo que permite obtener resultados más completos y una automatización más fluida. A lo largo del camino, descubriremos valiosas lecciones sobre cómo se puede aprovechar la integración entre lenguajes para crear soluciones de negociación sólidas y fluidas.

Vale la pena profundizar en este tema una vez que se comprenda el gran impacto que pueden tener los informes detallados en la mentalidad y el proceso de toma de decisiones de un operador. En la versión anterior, el PDF generado solo incluía cuatro datos, lo que constituía una clara limitación y ponía de manifiesto la necesidad de elaborar informes más completos. En adelante, nuestros informes incluirán métricas ampliadas, visualizaciones, gráficos y múltiples componentes analíticos, tal y como se describe en el párrafo 2 del artículo anterior sobre cómo entender las funciones de generación de informes.

En definitiva, el objetivo es ofrecer una herramienta de informes flexible y rica en información que cada analista o operador pueda adaptar a sus estrategias de trading y flujo de trabajo particulares.

Fragmento de informes

Figura 1: Captura de pantalla del informe de operaciones generado por la versión inicial de Reporting EA

La imagen anterior procede del informe en PDF generado por la primera versión de nuestro script «reports_processor.py». A continuación se muestra el fragmento de código correspondiente. Hoy nos centraremos en perfeccionar este código para que genere informes más detallados y reveladores.

A lo largo de esta explicación, analizaremos las posibilidades adicionales que Python puede aportar al proceso de generación de informes y mostraremos cómo optimizar la interacción entre MQL5 y Python para lograr una mayor eficiencia y flexibilidad.

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)

Al comparar las funciones de generación de informes que se muestran en la figura 1, arriba, y en la figura 2, abajo, queda claro que aún hay un margen de mejora considerable. Nuestro objetivo no es reinventar las soluciones existentes, sino ofrecer una alternativa personalizable y flexible para la elaboración de informes de operaciones.

La imagen de la sección de informes de la plataforma MetaTrader 5 muestra las funciones actuales, que utilizaremos como punto de partida para las mejoras que tenemos previsto implementar.

Informes de MQL5

Figura 2: Funciones de los informes de operaciones de MQL5


Resumen de la actualización de Reporting EA

La figura 3 (diagrama de flujo) que se muestra a continuación presenta el flujo de trabajo que se implementará con la actualización de Reporting EA. El EA exporta un CSV del historial de operaciones con fecha, y ejecuta el generador de informes de Python, que procesa el CSV para calcular indicadores analíticos, crear gráficos y generar el informe final (PDF), además de un resultado JSON compacto que incluye los estados pdf_path y email_sent. El EA sondea la presencia de ese archivo JSON, lo lee y lo analiza, valida las rutas de salida y los tamaños de archivo indicados y, si la validación se realiza correctamente, envía notificaciones y, opcionalmente, archiva o abre el informe para completar el ciclo de generación de informes automatizado.

Reporting EA: Actualización del

Figura 3: Diagrama de flujo del proceso para Reporting EA



Actualización del código de Reporting EA

En esta fase, implementaremos la parte de MQL5 de la actualización para garantizar la generación de informes de alta calidad. Dividiremos el EA en secciones lógicas y explicaremos el código que respalda directamente nuestros objetivos de generación de informes. Recuerda que los asesores expertos de MQL5 no se limitan a ejecutar operaciones: pueden automatizar muchas tareas rutinarias de trading, y la generación de informes es un ejemplo perfecto. Aunque actualmente nos centramos en un EA ligero y específico para la generación de informes, ese mismo módulo de informes se puede integrar más adelante en un asesor con más funciones. Diseñar un asesor experto (EA) especializado nos permite desglosar un problema de gran envergadura en partes manejables, desarrollar y perfeccionar la función de generación de informes de forma aislada, validarla mediante pruebas de funcionamiento básico y una depuración específica, y, a continuación, integrar el componente perfeccionado en sistemas más sofisticados con las mínimas dificultades.

1. Encabezado, importaciones de Windows y entradas del usuario

En esta sección se definen los metadatos de EA, se importan las llamadas mínimas a la API de Windows que necesitamos (para comprobar los atributos de los archivos absolutos y para ejecutar cmd.exe) y se exponen los datos de entrada configurables por el usuario. Asegúrate de que estos valores sean correctos para tu entorno (ruta de Python, ruta de los scripts, directorio de salida) para que el EA pueda validarlos al iniciarse. Las entradas «ReportFormats», «EnableEmail» y «TestOnInit» te permiten controlar el comportamiento sin necesidad de modificar el código.

#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. Inicialización y validación del entorno (OnInit)

La función OnInit() inicia el EA, inicializa el generador de números aleatorios (RNG), limpia las rutas configuradas, registra el directorio de archivos del terminal, comprueba que el ejecutable de Python y el directorio de salida existan, y confirma que se dispone de permisos de escritura en MQL5\Files. Cuando se activa TestOnInit, se inicia una ejecución de prueba, una útil comprobación básica para confirmar que la cadena de extremo a extremo está correctamente conectada.

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. Utilidades de rutas y funciones auxiliares básicas

Las rutas limpias y normalizadas, junto con comprobaciones de archivos fiables, evitan toda una serie de errores en tiempo de ejecución. CleanPath() normaliza las barras inclinadas y avisa sobre los espacios; FileExists() utiliza GetFileAttributesW para las rutas absolutas y recurre a FileOpen para los archivos locales del terminal. Mantén estos ayudantes en un lugar central y reutilízalos en toda la EA.

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. Lógica de creación y exportación de archivos CSV

La función ExportHistoryToCSV() se encarga de seleccionar el historial y genera un archivo CSV con un encabezado y filas para cada operación. Utiliza MakeUniqueFilename() para evitar conflictos de nombres (útil en ejecuciones repetidas) y devuelve el nombre de archivo elegido en la variable global LastExportedCSV. Quieres que tu generador de archivos CSV sea determinista e incluya las columnas que espera el script de 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. Ejecución y coordinación de Python (RunDailyExport)

RunDailyExport() es el centro de coordinación: genera la ruta del archivo CSV, ejecuta Python mediante cmd.exe y redirige stdout/stderr al archivo python_run.log, ubicado en MQL5\Files, para que el EA pueda seguir supervisando la salida del script. A continuación, el método busca el archivo report_result_YYYY-MM-DD.json (generado por Python) y, si este no está disponible, recurre a la búsqueda del PDF. Este diseño da prioridad a un protocolo de intercambio de datos JSON estructurado, de modo que la EA pueda confirmar de forma fiable el éxito de la operación y los archivos adjuntos.

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. Gestión de JSON y estrategia de lectura alternativa

Para evitar depender exclusivamente de registros legibles por el ser humano, la EA prefiere un resultado en formato JSON legible por máquina. La función `ReadFileText()` intenta leer rutas JSON absolutas directamente; cuando MetaTrader 5 rechaza la apertura de archivos absolutos con `FileOpen`, la función `CopyToFilesAndRead()` utiliza un comando «cmd copy» para copiar el archivo JSON en la carpeta `MQL5\Files` y, a continuación, lo lee con `FileOpen`. JsonExtractString y JsonExtractBool son extractores pequeños y prácticos, suficientes para el contrato JSON básico (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. Verificación, notificación y mecanismo de respaldo

Tras analizar el JSON, el EA comprueba los valores de «pdf_path» y «html_path» mediante la función FileExists() y, opcionalmente, avisa al operador mediante SendNotification(). Si falla la lectura o la validación del JSON, el EA recurre de forma controlada a revisar el archivo python_run.log y a comprobar si existe el archivo Report_YYYY-MM-DD.pdf esperado (tamaño > 0). Este enfoque por capas combina la fiabilidad (JSON) con la solidez (PDF/alternativa de registro).

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. Funciones auxiliares y cierre

Las rutinas de utilidad como GetFileSizeByOpen() (comprobación segura del tamaño de los archivos), ReadTerminalLogFile() (lectura de archivos de la carpeta MQL5\Files) y la envoltura ShellExecute() son pequeñas pero importantes. OnDeinit() muestra un mensaje al finalizar. Procura que sean concisos y estén bien documentados; así se simplifica la depuración y se reduce la duplicación.

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);
}


Actualización del script de Python «reports_processor»

El EA funciona en estrecha colaboración con el script de Python «reports_processor.py», por lo que, ahora que hemos actualizado el EA, debemos adaptar el código de Python para guardar y generar los resultados de forma que el EA pueda procesarlos de manera eficiente. En esta sección explicaremos el código del script en detalle y mostraremos exactamente cómo se integra cada parte con el EA —desde la importación y el análisis de archivos CSV hasta la generación de archivos PDF y la emisión de resultados en formato JSON— para garantizar un proceso de generación de informes fiable y automatizable. 

Antes de entrar en detalle con el análisis del código de nuestro script, primero debemos configurar el entorno de Python para asegurarnos de que el sistema pueda admitir plenamente todas las funciones del script. En los siguientes pasos, te guiaremos a través del proceso de configuración, empezando por la instalación. Una vez completado este paso fundamental, tenemos la seguridad de que el entorno está correctamente configurado, lo que nos permite realizar pruebas sin problemas durante el desarrollo del script «reports_processor».

Guía paso a paso: Configuración de Python en Windows para el script «reports_processor»

1. Instala Python 3.9 o una versión posterior desde python.org y asegúrate de que Python esté incluido en la variable PATH.

2. Abre PowerShell y crea y activa un entorno virtual (venv):

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

3. Actualizar pip:

python -m pip install --upgrade pip

4. Instala los paquetes básicos (fpdf es el motor de PDF predeterminado; añade otros si lo deseas):

pip install pandas numpy jinja2 matplotlib fpdf yagmail

Backends opcionales:

pip install pdfkit weasyprint

Si utilizas wkhtmltopdf, descarga su instalador para Windows y añade el archivo ejecutable a la variable PATH o configura la variable de entorno WKHTMLTOPDF_PATH.

5. Configura las variables de entorno para el correo electrónico (por ejemplo: en «Propiedades del sistema» → «Variables de entorno» o en 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. Prueba de ejecución (desde la carpeta del proyecto, con venv activo):

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

7. Comprueba los resultados en C:\ruta\a\Informes:

  • Report_YYYY-MM-DD.pdf (primario)
  • equity_curve_YYYY-MM-DD.png (gráfico)
  • report_result_YYYY-MM-DD.json (señal de confirmación para el EA)

Reports_processor.py

1. Inicio, importaciones y detección del backend

Al iniciarse, el script carga las bibliotecas estándar de Python (os, sys, logging, datetime, argparse, glob) y las bibliotecas de datos principales (pandas, numpy). A continuación, intenta importar las herramientas opcionales —WeasyPrint, pdfkit (wkhtmltopdf), FPDF y yagmail— y registra los indicadores de disponibilidad de cada backend. Estas opciones permiten que el script elija la ruta adecuada para la generación de PDF o el envío por correo electrónico en tiempo de ejecución, en función de lo que esté instalado, sin dejar de funcionar en entornos en los que falten algunos paquetes opcionales. El registro se configura desde el principio, de modo que el script genera mensajes informativos y de error con marca de tiempo que el EA puede capturar en el archivo 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. Análisis de argumentos e interfaz de línea de comandos

El script ofrece una pequeña interfaz de línea de comandos: su parámetro posicional `csv_path` admite tanto la ruta de un archivo CSV como una carpeta en la que buscar; `--formats` controla qué formatos de salida se deben generar (HTML, PDF o ambos); `--outdir` establece dónde guardar los archivos resultantes; `--email` activa o desactiva el envío de informes; y `--pdf-backend` selecciona qué ruta de PDF se debe utilizar. Este contrato sigue el patrón de invocación de EA: el EA proporciona la ruta del archivo CSV, el directorio de salida y un indicador de correo electrónico opcional, y a continuación supervisa el directorio de salida en busca de un resultado en formato 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. Resolución de CSV (búsqueda del archivo de entrada)

Cuando se le proporciona una carpeta o una ruta CSV flexible, el script determina el archivo CSV concreto que se va a procesar. Si csv_path es un directorio, busca archivos con el nombre History_*.csv (y, si no encuentra ninguno que coincida, recurre a cualquier archivo .csv) y selecciona el que se haya modificado más recientemente. Si se proporciona una ruta CSV completa, se acepta tal cual. Esto hace que el script sea flexible y fácil de ejecutar tanto desde el EA (que le pasa un archivo CSV concreto) como en ejecuciones manuales.

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. Carga y normalización de datos comerciales

El cargador lee el archivo CSV utilizando pandas, intentando convertir la columna de hora en objetos de tipo «datetime». Garantiza que existan las columnas necesarias (por ejemplo, beneficios, símbolo, saldo y capital) rellenando los valores por defecto o calculando los valores cuando sea posible (por ejemplo, sumas acumuladas para el saldo y el capital cuando sea necesario). Tras la normalización, el DataFrame se ordena cronológicamente, lo que da como resultado un conjunto de datos coherente y ordenado por tiempo para su análisis y representación gráfica.

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. Análisis y cálculo de métricas

El paso «compute_stats» calcula las métricas clave del informe que interesan tanto al EA como al usuario: una cadena de caracteres con la fecha del informe, el beneficio neto, el número total de operaciones, el símbolo con mejor rendimiento (por beneficio agregado), la caída máxima calculada a partir de la curva de capital y una estimación sencilla de un ratio similar al de Sharpe. Estos datos se recopilan en un diccionario de estadísticas y, posteriormente, se integran tanto en plantillas HTML como en páginas PDF para ofrecer un resumen ejecutivo rápido.

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. Gráficos: curva de capital y de saldo

El script genera una curva de capital en formato PNG utilizando matplotlib. Prefiere representar gráficamente el tiempo frente al saldo y el capital cuando hay marcas de tiempo; en caso contrario, representa gráficamente el índice frente al saldo o el capital. El archivo PNG generado se guarda en la carpeta --outdir y, posteriormente, se incluye en la plantilla HTML o se integra en el PDF, lo que ofrece a los lectores un resumen visual del rendimiento de la cuenta durante el periodo del informe.

# 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. Generación de HTML con Jinja2

Si se solicita una salida en HTML (o se genera como un producto intermedio), el script utiliza Jinja2 para generar el archivo report_template.html. La plantilla recibe el diccionario de estadísticas, los totales de beneficios por símbolo y el nombre del archivo de la imagen de la curva. El código HTML generado se guarda en la carpeta de salida y se registra en el registro. Este código HTML puede utilizarse como producto final o como material de partida para los convertidores de HTML a 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. Generación de PDF: múltiples motores

El script admite tres métodos de generación de PDF y selecciona uno en función del argumento --pdf-backend y de la disponibilidad detectada:

  • WeasyPrint: convierte el código HTML generado directamente a PDF (HTML → PDF) utilizando WeasyPrint, si está disponible.
  • wkhtmltopdf (pdfkit): del mismo modo, convierte HTML a PDF mediante el binario wkhtmltopdf cuando se configura.
  • FPDF: una biblioteca escrita íntegramente en Python que permite generar archivos PDF mediante programación e insertar gráficos y tablas de datos sin depender de la representación HTML/CSS. Esta ruta genera un archivo PDF de varias páginas en formato A4 que incluye una portada, cuadros con estadísticas clave, el gráfico de capital, tablas de beneficios por símbolo y una muestra de operaciones recientes.

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}")

Cada backend guarda el archivo final Report_YYYY-MM-DD.pdf en el directorio de salida configurado e informa de su finalización mediante el registro de eventos.

9. Envío de correos electrónicos 

Si se activa el parámetro --email, el script utiliza una rutina auxiliar de correo electrónico para enviar los archivos de informe generados al destinatario configurado mediante la biblioteca de correo electrónico disponible. La rutina registra si el envío se ha realizado correctamente, y el resultado se recoge posteriormente en el intercambio de datos JSON para que el EA pueda determinar si se envió un correo electrónico durante esa ejecución.

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. Generador de resultados en formato JSON: El mecanismo de confirmación de la EA

Tras generar los resultados, el script compila un archivo JSON compacto y legible por máquina que contiene pdf_path, html_path, email_sent, timestamp y un exit_code. El archivo JSON se guarda de forma atómica (mediante un archivo temporal y un cambio de nombre) en el directorio --outdir con el nombre report_result_YYYY-MM-DD.json. Dado que la escritura es atómica, el EA puede comprobar de forma segura si el archivo JSON está presente como indicio fiable de que el proceso en Python ha finalizado y, a continuación, leer su contenido para conocer las ubicaciones exactas de los resultados y si se ha enviado el correo electrónico.

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. Flujo principal

La función `main()` se encarga de coordinar todas las tareas: analiza los argumentos, procesa el archivo CSV, carga y normaliza los datos, calcula las estadísticas, genera el gráfico de capital, genera el código HTML (si se solicita), genera archivos PDF a través del backend seleccionado, envía correos electrónicos de forma opcional, elimina los informes antiguos del directorio de salida, escribe el JSON con los resultados y finaliza con un código de éxito o de error. La secuencia y el registro garantizan que la EA —que ha iniciado el script— pueda seguir el progreso de la ejecución (a través del archivo python_run.log) y reaccionar ante el archivo final report_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. Gestión de errores y JSON garantizado en caso de fallo

A lo largo de la función main(), las excepciones se capturan en el nivel superior, de modo que, si se produce algún error no gestionado, el script registra el seguimiento de errores y sigue generando un archivo report_result_YYYY-MM-DD.json con un código de salida que indica un fallo. Esto garantiza que el EA reciba siempre un objeto JSON determinista para analizar (ya sea de éxito o de error), lo que simplifica la lógica de verificación y de plan de contingencia del EA.

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. Política de limpieza y retención

Al finalizar una ejecución correcta, el script realiza una limpieza de retención del directorio de salida, eliminando los archivos «Report_» más antiguos que superen un plazo configurable (el script actual utiliza 30 días). Esto mantiene ordenada la carpeta de salida y evita que el espacio en disco aumente sin control en los sistemas en los que se ejecutan informes con frecuencia.

# 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}")

Cómo se integran el EA y el script de Python en tiempo de ejecución

El proceso de ejecución es sencillo: el EA exporta un archivo CSV y ejecuta el script (pasándole la ruta del CSV, --outdir y cualquier otro parámetro). El script de Python procesa el archivo CSV, guarda los archivos resultantes (PNG, PDF) en la carpeta de salida y, por último, genera el archivo report_result_YYYY-MM-DD.json. El EA busca ese archivo JSON; una vez localizado, lo lee, comprueba la validez de las rutas y los tamaños de archivo indicados y lleva a cabo las acciones posteriores (notificaciones, archivo, confirmación por correo electrónico). El registro (python_run.log) y el archivo JSON, en conjunto, proporcionan un contrato sólido y procesable por máquinas entre los dos componentes.


Pruebas 

Nuestras pruebas se llevan a cabo implementando el EA en la plataforma MetaTrader 5, donde el script de Python se inicia y se ejecuta en segundo plano. Los resultados se pueden supervisar a través del registro “Experts” de la terminal, así como comprobando los directorios de salida designados para los archivos generados. En mi prueba, localicé el informe de salida denominado Report_2025-08-21. El contenido de este archivo es mucho más amplio que el de las versiones anteriores. El informe incluye ahora tablas bien estructuradas, curvas de capital y múltiples métricas calculadas, todo ello presentado en un formato claro que ofrece información valiosa para los operadores. A continuación se muestran capturas de pantalla en las que se pueden ver los archivos de informe generados y, en parte, el contenido del PDF.

Figura 4: Archivos de salida visualizados en el Explorador de Windows

Contenido del PDF del archivo generado

Figura 5: Captura de pantalla animada del PDF de los informes generado

Curva de capital

Figura 6: Curva de capital generada en los informes



Conclusión

Hemos demostrado con éxito la integración de MQL5 y Python para ofrecer informes de trading con numerosas funciones que van mucho más allá de los resultados básicos que ofrecía la versión anterior de nuestro Reporting EA. Una de las características más destacadas es la curva de capital automatizada, que muestra gráficamente la evolución del saldo y el capital a lo largo del tiempo, lo que permite a los operadores comprender de inmediato la dinámica del rendimiento. El sistema demuestra que es posible conectar un asesor experto con un proceso externo de generación de informes en Python y, además, muestra cómo se puede perfeccionar y ampliar aún más esta integración.

Al perfeccionar continuamente el código de EA y aprovechar potentes bibliotecas de Python como Pandas, Matplotlib y FPDF, podemos calcular estadísticas avanzadas, generar tablas informativas e incorporar visualizaciones claras en los informes. La incorporación de un intercambio de datos JSON ligero entre el EA y el script de Python garantiza una confirmación fiable de los resultados, lo que hace que el flujo de trabajo sea fluido y transparente. Además, funciones opcionales como el envío automático de correos electrónicos y los backends de PDF flexibles ponen de manifiesto la adaptabilidad del marco a diferentes entornos.

Este avance sienta unas bases sólidas para soluciones de presentación de informes a largo plazo. Los documentos resultantes pueden servir como herramientas completas para el análisis del rendimiento de las operaciones, ayudando tanto a los operadores como a los inversores a detectar tendencias, medir riesgos como los drawdowns y evaluar estrategias con mayor claridad. En definitiva, Reporting EA, junto con el script «reports_processor.py», demuestra cómo la integración entre distintos lenguajes puede potenciar la automatización, mejorar la toma de decisiones y allanar el camino hacia análisis aún más sofisticados en el futuro.


Lecciones clave

Lección clave Descripción:
Validar las dependencias externas al iniciar Siempre verifique la ubicación de las herramientas y carpetas externas (ejecutable de Python, ruta del script, directorio de salida) durante OnInit mediante comprobaciones de atributos de archivo. La validación temprana con GetFileAttributesW evita fallos de ejecución difíciles de detectar.
Preferir un mecanismo legible por máquina (JSON) Utilice un archivo de resultados pequeño y estructurado (JSON) como señal canónica de Python a MQL5 en lugar de analizar registros de formato libre. Esto hace que la validación sea determinista y programática.
Escribir archivos de resultados de forma atómica Guarda el archivo JSON con un nombre provisional y, a continuación, cámbiale el nombre o sustitúyelo de forma atómica. Esto evita las carreras de lectura parcial cuando el EA sondea para comprobar la finalización.
Tenga en cuenta las limitaciones de ruta absoluta de MQL5. MQL5 FileOpen a menudo no puede leer rutas absolutas arbitrarias. Proporcione una alternativa: copie el archivo en MQL5\\Files o léalo mediante un enfoque de lectura de archivos de WinAPI para que el EA pueda acceder a las salidas de forma consistente.
Utilice comprobaciones rigurosas de existencia y tamaño de archivos No asuma que la presencia implica completitud Compruebe los atributos y el tamaño del archivo (abrir y buscar hasta el final) para asegurarse de que un informe o archivo binario se haya escrito por completo antes de actuar sobre él.
Utilice nombres de archivo únicos para evitar conflictos Genera archivos CSV e informes con nombres que incluyan marcas de tiempo y sufijos aleatorios para que las ejecuciones repetidas no sobrescriban los resultados y se conserven los registros históricos para fines de auditoría.
Exportar archivos CSV de forma consistente para Python Diseña las columnas CSV (encabezados, formatos, estilo de fecha/hora) que espera el script de Python. Un contrato de exportación coherente elimina la ambigüedad en el análisis sintáctico y reduce los errores de manipulación de datos.
Centralizar las utilidades auxiliares Mantenga la normalización de rutas, las funciones auxiliares de existencia de archivos, los extractores JSON y los pequeños envoltorios de lectura/escritura en un solo lugar para que cada parte del EA reutilice la misma lógica robusta (CleanPath, FileExists, ReadTerminalLogFile).
Implementar sondeos con tiempos de espera razonables Cuando espere a que finalice un proceso externo, utilice un bucle con un intervalo de sondeo corto y un tiempo de espera general. Esto equilibra la capacidad de respuesta con el uso de la CPU y evita esperas infinitas.
Proporcionar alternativas y verificación por capas Diseñar comprobaciones por capas: preferir la confirmación basada en JSON; si no está presente, recurrir al seguimiento de python_run.log; si eso falla, consultar el PDF esperado. La existencia de múltiples vías de verificación aumenta la robustez.
Registre abundantemente y mostrar diagnósticos claramente Escribe entradas de registro informativas e imprime el final de los registros de Python en el Registro de expertos. Un diagnóstico claro acelera la resolución de problemas cuando las ejecuciones fallan en producción.
Nunca codifique las credenciales directamente en el código Mantenga los secretos fuera del código fuente: utilice variables de entorno para las credenciales de correo electrónico y documente qué variables deben configurarse. Esto mejora la seguridad y la portabilidad.
Admite múltiples sistemas de gestión de PDF y detecta la disponibilidad Haz que el script de Python sea independiente del backend: detecta weasyprint, wkhtmltopdf/pdfkit o una ruta fpdf puramente de Python y elige la mejor opción disponible en tiempo de ejecución.
Utilice entornos virtuales y configuración de documentos Documentar una configuración reproducible de Windows: versión de Python, creación de virtualenv, lista de instalaciones de pip y requisitos previos nativos para backends opcionales, de modo que las implementaciones sean predecibles.
Realizar una prueba de funcionamiento del flujo de principio a fin Realiza una «prueba de funcionamiento» rápida de principio a fin: implementa el EA, haz que exporte un archivo CSV, ejecuta Python y comprueba el registro “Experts” y la carpeta de salida en busca de archivos PDF o JSON. Las pruebas de funcionamiento básico permiten obtener una primera impresión rápida antes de pasar a un control de calidad más exhaustivo.
Garantizar la notificación determinista de fallos En caso de excepción, escribe siempre un JSON de resultado que incluya exit_code y campos de diagnóstico. Esto garantiza que el EA pueda detectar fallos y aplicar una lógica alternativa, en lugar de quedarse bloqueado indefinidamente.


Archivos adjuntos

Nombre del archivo Versión Descripción
Reporting_EA.mq5 1.01 Asesor experto que exporta el historial de operaciones a archivos CSV con nombres únicos, inicia el procesador de informes de Python y verifica los resultados. Entre sus características se incluyen la normalización de rutas, comprobaciones de dependencias, generación atómica de nombres de archivo, protocolo de enlace «JSON-first» con Python, alternativa de copia para lecturas de rutas absolutas, alternativa de sondeo de PDF, registro detallado en Experts y notificaciones push opcionales.
Reports_processor.py 1.0.1 Motor de generación de informes en Python que lee el archivo CSV del EA, calcula los datos analíticos (beneficio neto, caída máxima, índice de Sharpe, totales por símbolo), genera gráficos, crea archivos HTML y/o PDF (varias opciones de backend: fpdf, wkhtmltopdf, WeasyPrint), envía correos electrónicos de forma opcional y escribe un archivo «report_result_AAAA-MM-DD.json» para el protocolo de intercambio con el EA. Incluye limpieza de la retención y un sólido manejo de errores con JSON garantizado en caso de fallo.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19006

Archivos adjuntos |
Reporting_EA.mq5 (50.56 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Programación gráfica para principiantes (Parte II): Dominando la Interfaz, POO y Persistencia Programación gráfica para principiantes (Parte II): Dominando la Interfaz, POO y Persistencia
El artículo transforma un minijuego en un sistema UI profesional orientado a EAs y paneles. Introduce una clase de botón con detección AABB y estados hover/pressed, resuelve el rebote de clic mediante transición 0→1 y ajusta colores con manipulación ARGB. Los datos persisten con FileReadStruct/FileWriteStruct en .bin. El resultado es una base compilable y reutilizable para interfaces robustas.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Introducción a MQL5 (Parte 20): Introducción a los patrones armónicos Introducción a MQL5 (Parte 20): Introducción a los patrones armónicos
En este artículo, analizamos los fundamentos de los patrones armónicos, sus estructuras y cómo se aplican en el trading. Aprenderás sobre los retrocesos y las extensiones de Fibonacci, así como a implementar la detección de patrones armónicos en MQL5, sentando así las bases para crear herramientas de trading avanzadas y asesores expertos.