English Русский 中文 Deutsch 日本語 Português
Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos

Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos

MetaTrader 5Ejemplos | 2 julio 2021, 17:27
772 0
Malik Arykov
Malik Arykov

Contenido


Introducción

El artículo se centrará en los parámetros que podemos restaurar tras reiniciar (cerrar) el terminal. Todos los ejemplos son en realidad trozos del código operativo del proyecto Cayman del propio autor.


Lugar de almacenamiento de los parámetros


Ejemplos de parámetros

  • Hora de la barra cero. Por ejemplo, para detectar un patrón de velas, resulta lógico evaluarlo una vez después de que surja una nueva barra en un periodo determinado.
  • Parámetros de nivel comercial. Por ejemplo, resaltamos un nivel comercial y establecemos con un script su tipo y el tamaño de la transacción en caso de ruptura. El script transmite los parámetros al asesor experto. El asesor crea un analizador de nivel. El analizador «se activa» solo después de que aparezca una nueva barra en el marco temporal indicado.
  • Preferencias del usuario. Por ejemplo, el color, las reglas comerciales, los métodos de dibujado, etcétera. Es lógico ajustarlos una vez, por ejemplo, en el archivo de configuración.
¿Dónde guardamos estos parámetros?
  • Variables globales del terminal
  • Objetos gráficos
  • Comentarios de las órdenes
  • Archivos de texto
Lugar de almacenamiento Tipo Ámbito Duración
Variables globales del terminal double Todos los gráficos 4 semanas después de la última llamada
Objetos gráficos Cualquiera. Líneas <= 63 caracteres Gráfico actual Vida útil del gráfico
Comentarios de las órdenes Longitud de línea <= 23 caracteres Todos los gráficos Vida útil del terminal
Archivos de texto Cualquiera. Sin límites Todos los gráficos Vida útil del archivo


Variables globales del terminal

Las variables globales del terminal están disponibles en cualquier gráfico. El ámbito se puede limitar incluyendo componentes como ChartId, Symbol, Period en el nombre de la variable. Sin embargo, lo que no podemos hacer es introducir cambios en el tipo de variable. No se puede guardar texto.

No obstante, hay un truco: empaquetar/desempaquetar valores enteros. Como ya sabemos, el tipo double ocupa 8 bytes (64 bits). Vamos a ver con un ejemplo cómo almacenar varios valores enteros en una variable. Lo más importante es determinar el tamaño de bits de sus valores máximos.

// -----------------------------------------------------------------------------
// Ejemplo de desempaquetado/empaquetado de valores enteros/de la variable global
// con ayuda de operaciones de bits                                             |
// -----------------------------------------------------------------------------
void OnStart() {
    
    int     value10 = 10; // max = 255 (8 bits)
    int     value20 = 300; // max = 65535 (16 bits)
    bool    value30 = true; // max = 1 (1 bit)
    
    // empaquetamos los valores en 25 bits (8+16+1)
    // quedan 39 bits libres (64-25)
    ulong packedValue = 
        (value10 << 17) + // reservamos espacio (16+1) para value20, value30
        (value20 << 1) + // reservamos espacio (1) para value30
        value30;
    
    // guardamos la variable global
    string nameGVar = "temp";
    GlobalVariableSet(nameGVar, packedValue);
    
    // leemos la variable global
    packedValue = (ulong)GlobalVariableGet(nameGVar);
    
    // desempaquetamos los valores
    // 0xFF, 0xFFFF, 0x1 - máscaras de bits de los valores máximos
    int value11 = (int)((packedValue >> 17) & 0xFF);
    int value21 = (int)((packedValue >> 1) & 0xFFFF);
    bool value31 = (bool)(packedValue & 0x1);
    
    // comparamos los valores
    if (value11 == value10 && value21 == value20 && value31 == value30) Print("OK");
    else PrintFormat("0x%X / 0x%X /0x%X / 0x%X", packedValue, value11, value21, value31);
}


Objetos gráficos

¿Es posible almacenar los parámetros de los scripts en los objetos gráficos? ¿Y por qué no? Establecemos la propiedad del objeto OBJPROP_PRICE = 0, luego el objeto se volverá visualmente "invisible", pero accesible de forma programática. Para mayor fiabilidad, podemos guardar dicho objeto en una plantilla de gráfico. La lógica de acceso a los parámetros funciona así: si hay un objeto, extraemos los parámetros; si no hay ninguno, establecemos los valores predeterminados.


Comentarios de las órdenes

La longitud máxima de los comentarios de una orden es de 23 símbolos. ¿Qué podemos almacenar allí? Por ejemplo, SOP/H1/SS/C2/Br/Br/Br. Donde (de izquierda a derecha)

  • SOP — emisor de la orden (SOP - script de comandos SendOrderByPlan)
  • H1  — periodo de formación de la orden (H1)
  • SS  — tipo de orden (SS - Sell Stop)
  • C2  — algoritmo de cierre de la orden
  • Br  — tendencia en D1 (Br - Bear)
  • Br  — tendencia en H4 (Br - Bear)
  • Br  — tendencia durante el periodo de formación de la orden (Br - Bear)

¿Para qué necesitamos esto? Por ejemplo, para analizar la historia de transacciones o al activar una orden pendiente, extraemos el valor del algoritmo de cierre y creamos el analizador de stops virtuales AnalyserVirtSL, que cerrará por sí mismo la transacción en ciertas condiciones.


Archivos de texto

Esta es quizás la forma más fiable y versátil de almacenar los parámetros de recuperación. Una vez que hemos depurado las clases de acceso, las usaremos siempre y en todas partes.


Ajustes de la aplicación

Parte del archivo de ajustes de la aplicación AppSettings.txt

# -------------------------------------------------------------------
# Ajustes del experto y los scripts
# Codificación del archivo = UCS-2 LE con BOM (imprescindible!!!) // es unicode
# -------------------------------------------------------------------
TimeEurWinter = 10:00 # horario invernal de comienzo de la sesión europea (hora del servidor)
TimeEurSummer = 09:00 # horario estival de comienzo de la sesión europea (hora del servidor)
ColorSessionEur = 224,255,255 # color de la sesión europea
ColorSessionUsd = 255,240,245 # color de la sesión americana
NumberColorDays = 10 # número de días (sesiones) resaltados


Clase AppSettings.mqh

#property copyright "Copyright 2020, Malik Arykov"
#property link      "malik.arykov@gmail.com"
#property strict

#include <Cayman/Params.mqh>

// nombres de los parámetros de la aplicación
#define APP_TIME_EUR_SUMMER "TimeEurSummer"
#define APP_TIME_EUR_WINTER "TimeEurWinter"
#define APP_TIME_TRADE_ASIA "TimeTradeAsia"
#define APP_COLOR_SESSION_EUR "ColorSessionEur"
#define APP_COLOR_SESSION_USD "ColorSessionUsd"
#define APP_NUMBER_COLOR_DAYS "NumberColorDays"

// -----------------------------------------------------------------------------
// Ajustes generales del Asesor y los Scripts                                  |
// -----------------------------------------------------------------------------
class AppSettings {
private:
    Params  *m_params;
public:
    // se establecen en el archivo AppSettings.txt
    string  TimeEurSummer; // horario estival de inicio de la sesión europea
    string  TimeEurWinter; // horario invernal de inicio de la sesión europea
    string  TimeTradeAsia; // hora de finalización del comercio del corredor asiático
    color   ColorSessionEur; // color de la sesión europea
    color   ColorSessionUsd; // color de la sesión americana
    int     NumberColorDays; // número de días resaltados

    // se establecen de forma programática
    string  PeriodTrends; // Periodos de cálculo de las tendencias (D1,H4)
    string  TradePlan; // Dirección del comercio (plan breve)
    bool    IsValid; // validez de los parámetros
    
    // métodos
    AppSettings();
    ~AppSettings() { delete m_params; };
    void Dump(string sender);
};

// -----------------------------------------------------------------------------
// Constructor                                                                 |
// -----------------------------------------------------------------------------
AppSettings::AppSettings() {

    IsValid = true;
    m_params = new Params();
    m_params.Load(PATH_APP_SETTINGS);
    if (m_params.Total() == 0) {
        PrintFormat("%s / ERROR: Archivo no válido / %s", __FUNCTION__, PATH_APP_SETTINGS);
        IsValid = false;
        return;
    }
        
    TimeEurWinter = m_params.GetValue(APP_TIME_EUR_WINTER);
    TimeEurSummer = m_params.GetValue(APP_TIME_EUR_SUMMER);
    TimeTradeAsia = m_params.GetValue(APP_TIME_TRADE_ASIA);
    ColorSessionEur = StringToColor(m_params.GetValue(APP_COLOR_SESSION_EUR));
    ColorSessionUsd = StringToColor(m_params.GetValue(APP_COLOR_SESSION_USD));
    NumberColorDays = (int)StringToInteger(m_params.GetValue(APP_NUMBER_COLOR_DAYS));
}

// -----------------------------------------------------------------------------
// Imprimir los parámetros de los ajustes                                      | 
// -----------------------------------------------------------------------------
void AppSettings::Dump(string sender) {
    PrintFormat("sender=%s / %s", sender, PATH_APP_SETTINGS);
    PrintFormat("%s = %s", APP_TIME_EUR_WINTER, TimeEurWinter);
    PrintFormat("%s = %s", APP_TIME_EUR_SUMMER, TimeEurSummer);
    PrintFormat("%s = %s", APP_TIME_TRADE_ASIA, TimeTradeAsia);
    PrintFormat("%s = %s / %s", APP_COLOR_SESSION_EUR, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
    PrintFormat("%s = %s / %s", APP_COLOR_SESSION_USD, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
    PrintFormat("%s = %i", APP_NUMBER_COLOR_DAYS, NumberColorDays);
}



Características

Hemos colocado la declaración de la clase AppSettings en el archivo Uterminal.mqh, que se conecta a través de #include al asesor experto y a cualquier script.

extern AppSettings  *gAppSettings; // ajustes de la aplicación

Esta solución permite:

  • Inicializar gAppSettings una vez en cualquier lugar
  • Usar gAppSettings en una instancia de cualquier clase (en lugar de transmitirla como parámtro)


Parámetros de los analizadores

El asesor Cayman gestiona diferentes analizadores, por ejemplo AnalyserTrend, AnalyserLevel, AnalyserVirtSL. Cada analizador está vinculado a un marco temporal determinado, es decir, el análisis de inicia solo cuando aparece una nueva barra en el periodo indicado. Los parámetros del analizador se almacenan en un archivo de texto con las línea Key = Value. Por ejemplo, el analizador de nivel comercial en H4 almacena sus parámetros en el archivo Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt

  • Cayman — nombre del proyecto
  • Params  — subdirectorio con los parámetros del analizador
  • 128968168864101576  — ID del gráfico // IntergerToString(ChartID())
  • exp_05_Lev607A160E_H4.txt  — nombre del archivo con los parámetros del analizador —
    • exp  — prefijo
    • 05  — tipo de analizador
    • Lev607A160E  — nombre del analizador (nivel comercial)
    • H4  — periodo de seguimiento.

Vamos a mostrar el contenido del archivo con comentarios (el archivo real no tiene comentarios)

// parámetros del nivel comercial
nameObj=Lev607A160E // nombre del nivel comercial
kindLevel=1 // tipo de nivel (1 - resistencia)
riskValue=1.00 // volumen de la transacción al romper el nivel (1)
riskUnit=1 // unidad de medida del volumen de la transacción (1 - % fondos para el margen)
algClose=2 // algoritmo de cierre de la transacción (2 – dos barras de corrección)
ticketNew=0 // ticket de la transacción al romper el nivel
ticketOld=0 // ticket para cerrar una transacción al romper el nivel
profits=0 // beneficio planeado en puntos
losses=0 // pérdidas planeadas en puntos
// parámetros del analizador
symbol=EURUSD // nombre del símbolo
period=16388 // periodo del analizador (H4)
time0Bar=1618603200 // hora de la barra cero (seg)
typeAnalyser=5 // tipo de analizador
colorAnalyser=16711935 // color de los resultados del analizador
resultAnalyser=Lev607A160E, H4, 20:00, RS // resultado del analizador

Tenemos la clase básica Analyser que puede guardar y restaurar los parámetros de cualquier analizador. Al reiniciar el asesor (por ejemplo, al cambiar el marco temporal), los analizadores restauran sus parámetros desde los archivos de texto correspondientes. Al mismo tiempo, el análisis no se reiniciará si no ha llegado el momento de una nueva barra. Los resultados del analizador (reseultAnalyser, colorAnalyser) calculados en la barra anterior se muestran en los comentarios del experto.


Transmisión de los parámetros del script al asesor experto

El script SetTradeLevel nos permite establecer los parámetros del nivel comercial. En el gráfico se destaca un objeto (línea recta, línea de tendencia o rectángulo). El script SetTradeLevel encuentra el objeto (nivel comercial) destacado y establece sus parámetros.

Parámetros del script SetTradeLevel

A continuación, el script guarda los parámetros en el archivo Files Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt y envía el comando y la ruta al archivo a través de la función SendCommand.

// -----------------------------------------------------------------------------
// Enviar los parámetros del nivel del experto                                 |
// -----------------------------------------------------------------------------
NCommand SendCommand() {

    // cargando los parámetros del nivel (si hay)
    Params *params = new Params();
    string speriod = UConvert::PeriodToStr(_Period);
    params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod);

    // determinamos el comando
    NCommand cmd = 
        (gKindLevel == levUnknown) ? cmdDelete :
        (params.Total() > 0) ? cmdUpdate :
        cmdCreate;

    // guardamos los parámetros
    params.Clear();
    params.Add(PARAM_NAME_OBJ, gNameLev);
    params.Add(PARAM_TYPE_ANALYSER, IntegerToString(anaLevel));
    params.Add(PARAM_PERIOD, IntegerToString(_Period));
    params.Add(PARAM_KIND_LEVEL, IntegerToString(gKindLevel));
    params.Add(PARAM_RISK_VALUE, DoubleToString(gRiskValue, 2));
    params.Add(PARAM_RISK_UNIT, IntegerToString(gRiskUnit));
    params.Add(PARAM_ALG_CLOSE, IntegerToString(gAlgClose));
    params.Add(PARAM_TICKET_OLD, IntegerToString(gTicketOld));
    params.Add(PARAM_PROFITS, IntegerToString(gProfits));
    params.Add(PARAM_LOSSES, IntegerToString(gLosses));
    params.Save();
    
    // enviamos el comando al experto
    params.SendCommand(cmd);
    delete params;
    
    return cmd;
}


La función params.SendCommand(cmd) tiene el aspecto siguiente

// -----------------------------------------------------------------------------
// Enviar el comando al experto                                                |
// -----------------------------------------------------------------------------
void Params::SendCommand(NCommand cmd) {
    string nameObj = NAME_OBJECT_CMD;
    ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0);
    ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path);
    ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd);
    ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0);
}

Cada tick (OnTick), el asesor experto comprueba la existencia del objeto llamado NAME_OBJECT_CMD a través de la función CheckExpernalCommand(). Si existe, se leerán el comando y la ruta al archivo con los parámetros del analizador, y el objeto se eliminará de inmediato. A continuación, el experto buscará un analizador que funcione según el nombre del archivo. Si cmd == cmdDelete, se eliminará el analizador. Si cmd == cmdUpdate, se actualizarán los parámetros del analizador del archivo. Si cmd == cmdNew, se creará un nuevo analizador con los parámetros del archivo.

Aquí tenemos el texto completo de la clase Params, que encapsula la lógica necesaria para trabajar con los archivos de los parámetros (líneas Key=Value).

#property copyright "Copyright 2020, Malik Arykov"
#property link      "malik.arykov@gmail.com"

#include <Arrays/ArrayString.mqh>
#include <Cayman/UConvert.mqh>
#include <Cayman/UFile.mqh>

// -----------------------------------------------------------------------------
// Clase de los parámetros (líneas key=value con comentarios #)                |
// -----------------------------------------------------------------------------
class Params {
private:
    string  m_path; // ruta al archivo de parámetros
    NCommand m_cmd; // comando para el experto
    CArrayString *m_items; // matriz de parejas {key=value}
    int Find(string key);
public:
    Params();
    ~Params() { delete m_items; };
    void Clear() { m_items.Clear(); };
    int Total() { return m_items.Total(); };
    string Path() { return m_path; };
    CArrayString *Items() { return m_items; };
    void Add(string line) { m_items.Add(line); };
    bool Add(string key, string value);
    string GetValue(string key);
    void Load(string prefix, int typeAnalyser, string nameObj, string speriod);
    void Load(string path);
    void Save();
    void SendCommand(NCommand cmd);
    NCommand TakeCommand();
    void Dump(string sender);
};

// -----------------------------------------------------------------------------
// Constructor por defecto                                                     |
// -----------------------------------------------------------------------------
Params::Params() {
    m_items = new CArrayString();
}

// -----------------------------------------------------------------------------
// Añadir pareja clave=valor                                                   | 
// -----------------------------------------------------------------------------
bool Params::Add(string key, string value) {

    int j = Find(key);
    string line = key + "=" + value;
    if (j >= 0) { // actualizamos
        m_items.Update(j, line);
        return false;
    }
    else { // añadimos
        m_items.Add(line);
        return true;
    }
}

// -----------------------------------------------------------------------------
// Obtener valor del parámetro según la clave                                  |
// -----------------------------------------------------------------------------
string Params::GetValue(string key) {

    // buscamos la clave
    int j = Find(key);
    if (j < 0) return NULL; // no hay clave
    
    // comprobamos el separador
    string line = m_items.At(j);
    j = StringFind(line, "=");
    if (j < 0) { // no =
        PrintFormat("%s / ERROR: línea no válida %s", __FUNCTION__, line);
        return NULL;
    }
    
    // retornamos el valor
    return UConvert::Trim(StringSubstr(line, j + 1));
}

// -----------------------------------------------------------------------------
// Encontrar el valor del parámetro según la clave                             |
// -----------------------------------------------------------------------------
int Params::Find(string key) {

    int index = -1;
    for (int j = 0; j < m_items.Total(); j++) {
        if (StringFind(m_items.At(j), key) == 0) {
            index = j;
            break;
        }
    }
    return index;
}

// -----------------------------------------------------------------------------
// Cargar parámetros                                                           |
// -----------------------------------------------------------------------------
void Params::Load(string prefix, int typeAnalyser, string nameObj, string speriod) {
    string nameFile = StringFormat("%s%02i_%s_%s.txt", prefix, typeAnalyser, nameObj, speriod);
    m_path = StringFormat("%s%s/%s", PATH_PARAMS, IntegerToString(ChartID()), nameFile);
    if (FileIsExist(m_path)) Load(m_path);
}

// -----------------------------------------------------------------------------
// Cargar parámetros                                                           |
// -----------------------------------------------------------------------------
void Params::Load(string path) {
  
    m_path = path;
    if (!FileIsExist(m_path)) return;

    //PrintFormat("%s / %s", __FUNCTION__, m_path);
    string text = UFile::LoadText(m_path);
    if (text == NULL) return;
    
    // dividimos el texto en líneas
    string line, lines[];
    int numLines = StringSplit(text, DLM_LINE, lines);
    for (int j = 0; j < numLines; j++) {
        line = lines[j];
        // eliminamos los comentarios
        int k = StringFind(line, "#");
        if (k == 0) continue; // la línea completa es un comentario
        if (k > 0) line = StringSubstr(line, 0, k);
        // añadimos una línea no vacía
        if (line != "") m_items.Add(line);
    }
}

// -----------------------------------------------------------------------------
// Guardar parámetros                                                          |
// -----------------------------------------------------------------------------
void Params::Save() {

    string text = "";
    for (int j = 0; j < m_items.Total(); j++) {
        text += m_items.At(j) + "\n";
    }
    // reescribir archivo existente
    UFile::SaveText(text, m_path, true);
}

// -----------------------------------------------------------------------------
// Enviar el comando al experto                                                | 
// -----------------------------------------------------------------------------
void Params::SendCommand(NCommand cmd) {
    string nameObj = NAME_OBJECT_CMD;
    ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0);
    ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path);
    ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd);
    ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0);
}

// -----------------------------------------------------------------------------
// Recibir un comando del script                                               |
// -----------------------------------------------------------------------------
NCommand Params::TakeCommand() {
    string nameObj = NAME_OBJECT_CMD;
    if (ObjectFind(0, nameObj) < 0) return cmdUnknown;
    
    m_path = ObjectGetString(0, nameObj, OBJPROP_TEXT);
    m_cmd = (NCommand)ObjectGetInteger(0, nameObj, OBJPROP_ZORDER);
    ObjectDelete(0, nameObj);
    Load(m_path);
    
    return m_cmd;
}

// -----------------------------------------------------------------------------
// |Volcar parámetros                                                          |                                                             
// -----------------------------------------------------------------------------
void Params::Dump(string sender) {
    for (int j = 0; j < m_items.Total(); j++) {
        PrintFormat("%s / %s", sender, m_items.At(j));
    }
}

Para los fanáticos de MQL5: al cambiar el tipo m_items a CHashMap, el código de las funciones Add, GetValue, Find se reducirá significativamente, pero la clase Params también se utiliza en MQL4. Además, la velocidad de acceso a los parámetros no resulta importante en este caso, ya que los parámetros se leen una vez para inicializar las variables locales. ¿Por qué no hemos rehecho la clase según CHashMap para MQL5? Probablemente porque el propio autor trabajó durante mucho tiempo en un banco. Los desarrolladores de software financiero tienen un principio muy importante: ¡Si funciona, no lo toques! ;-)


/es/articles/9327#topic10

La unidad de intercambio entre diferentes sistemas es de hecho un archivo json. Antes era un archivo xml. Principales ventajas de los archivos json:

  • Son fáciles de crear (dar forma/formatear)
  • Ofrecen un excelente soporte en todos los lenguajes de alto nivel
  • Su legibilidad

Por ejemplo, tenemos la clase Bar con los campos m_time, m_open, m_high, m_low, m_close, m_body. Donde m_body es el color de la vela: blanco, negro o doji. La clase Bar tiene un método ToJson(), que genera una línea json

string Bar::ToJson() {
        return "{" +
        "\n\t\"symbol\":\"" + _Symbol + "\"," +
        "\n\t\"period\":" + IntegerToString(_Period) + "," +
        "\n\t\"digits\":" + IntegerToString(_Digits) + "," +
        "\n\t\"timeBar\":\"" + TimeToStr(m_time) + "\"," +
        "\n\t\"open\":" + DoubleToString(m_open, _Digits) + "," +
        "\n\t\"high\":" + DoubleToString(m_high, _Digits) + "," +
        "\n\t\"low\":" + DoubleToString(m_low, _Digits) + "," +
        "\n\t\"close\":" + DoubleToString(m_close, _Digits) + "," +
        "\n\t\"body\":" + IntegerToString(m_body) + "," +
        "\n}";
}


Podemos utilizar StringFormat, pero tendremos dificultades para reorganizar o eliminar los valores. Es posible quitar el formateo “\n\t”, ya que existen bastantes servicios de formato json en línea. Uno de ellos es JSON Parser. Basta con depurar una vez la obtención de un json válido y usar la función bar.ToJson() sin dudarlo.

Un programa externo, por ejemplo en C#, puede convertir un archivo json de cualquier complejidad en un objeto. ¿Y cómo transferimos un archivo json desde MQL? Es muy simple. Pasamos (guardamos) el archivo json, por ejemplo, al directorio del terminal Files/Json. El programa externo supervisa este directorio en busca de nuevos archivos. Tras encontrar el archivo, lo lee, lo convierte en un objeto e inmediatamente lo borra (para que no ocupe lugar) o lo mueve al archivo (para las estadísticas).


Recepción de los parámetros desde programas externos

Conectar una biblioteca json (Dios nos libre de inventar de nuevo esa rueda) a nuestros programas MQL es una molestia adicional. Resulta más simple transferir archivos de texto con líneas Key=Value. Para procesar los archivos, podemos usar la clase Params (mire más arriba). El experto y el indicador son los candidatos para recibir los parámetros de los programas o scripts externos. Por ejemplo, en el manejador OnTick, debemos llamar a la función CheckExternalCommand(), que comprobará la presencia de archivos en el directorio Files/ExtCmd. Si se encuentra un archivo, este será leído, procesado (se aceptarán los parámetros) y eliminado.

Por consiguiente, hemos analizado las formas de obtención y transmisión de parámetros entre MQL y los programas externos. Ahora, toca pensar sobre lo siguiente: ¿Por qué necesitamos DLL para los programas MQL?. Además, el Mercado MQL no acepta este tipo de programas. Solo hay una razón: la seguridad, ya que desde una DLL se puede entrar en cualquier parte.


Transmisión de los parámetros a un smartphone

Echemos un vistazo a la aplicación de Android WirePusher. El servicio de estos desarrolladores (gratis y sin anuncios) es para quitarse el sombrero. Dudamos que haya algo similar en iPhone. Los fanáticos de iPhone pueden escribir en los comentarios al artículo. 

Para trabajar con el servicio, primero debemos:

  • Instalar WirePusher en el smartphone
  • Iniciar la aplicacion. En la pantalla de inicio veremos nuestra id.
  • Añadir https://wirepusher.com a Terminal/Servicio/Ajustes/Asesores Expertos/Permitir WebRequest

A continuación, ejecutamos el script, reemplazando previamente los asteriscos en id = "********" con nuestra id.

void OnStart() {
    string id = "**********"; // id de su smartphone en WirePusher
    WirePusher("Beneficio $1000", "Transacción", "Cerrada", id);
}

// ------------------------------------------------------------------------------------------------
// Enviar notificación al smartphone a través del servicio web WirePusher
// Añadir https://wirepusher.com en Terminal/Servicio/Ajustes/Asesor/Permitir WebRequest
// message - texto de la notificación
// title - encabezado de la notificación (Ejemplo, Atención / Señal / Transacción)
// type - tipo de notificación (ejemplo, Activación de orden pendiente / Nivel roto / Cerrada)
// id - número único de smartphone en la aplicación de android WirePusher
// ------------------------------------------------------------------------------------------------
bool WirePusher(string message, string title, string type, string id) {

    char data[]; // matriz de datos del cuerpo del mensaje HTTP
    char result[]; // matriz con los datos de la respuesta del servicio web
    string answer; // encabezado de la respuesta del servicio web
    string url = "https://wirepusher.com/send?id={id}&title={title}&message={message}&type={type}";
    
    StringReplace(url, "{id}", id);
    StringReplace(url, "{type}", type);
    StringReplace(url, "{title}", title);
    StringReplace(url, "{message}", message);
    
    ResetLastError();
    int rcode = WebRequest("GET", url, NULL, 3000, data, result, answer);
    if (rcode != 200) {
        PrintFormat("%s / error=%i / url=%s / answer=%s / %s", 
            __FUNCTION__, GetLastError(), url, answer, CharArrayToString(result));
        return false;
    }
    PrintFormat("%s / %s / %s", __FUNCTION__, title, message);
    return true;
}

En Cayman Expert Advisor, la función WirePusher se llama en AnalyzerTrade cuando:

  • Se activa una orden pendiente
  • El precio rompe a través de un nivel comercial
  • Se cierra una transacción

En la aplicación WirePusher, podemos adjuntar a cada tipo de notificación nuestro propio tipo de sonido. Antes teníamos el sonido "ta-da" para el cierre de transacciones con beneficios, y una "explosión" para las transacciones con pérdidas. Pero uno terminaba cansado de tanta explosión ;-)


Conclusión

Los archivos de texto son el lugar más fiable y adecuado para almacenar los parámetros. Tanto más que las operaciones de archivos en cualquier SO (aplicación) se procesan/almacenan en la caché bastante bien.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9327

Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección
En el presente artículo, finalizaremos el trabajo con las clases de los objetos de gráfico y sus colecciones. Implementaremos el seguimiento automático del cambio de las propiedades de los gráficos y sus ventanas, y también el almacenamiento de los parámetros en las propiedades del objeto. Estas mejoras nos permitirán en el futuro crear una funcionalidad de eventos para la colección de gráficos al completo.
Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico
En el presente artículo, crearemos la funcionalidad necesaria para monitorear algunos eventos de los objetos del gráfico: añadir y eliminar gráficos de símbolos, añadir y eliminar subventanas en el gráfico, y también añadir/eliminar/cambiar indicadores en las ventanas del gráfico.
Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico
En el presente artículo, iniciaremos un nuevo apartado del trabajo con gráficos. En esta ocasión, vamos a crear el objeto de estado del ratón, el objeto básico de todos los elementos gráficos y la clase de objeto de formulario de los elementos gráficos de la biblioteca.
Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico
En este artículo, ampliaremos la funcionalidad de los objetos de gráfico, organizaremos la navegación por los gráficos, crearemos capturas de pantalla, y también guardaremos plantillas y las aplicaremos a los gráficos. Asimismo, implementaremos la actualización automática de la colección de objetos de gráfico, sus ventanas y los indicadores en ellas.