Conselhos de um programador profissional (Parte II): armazenamento e troca de parâmetros entre um EA, scripts e programas externos

Malik Arykov | 19 julho, 2021

Sumário


Introdução

Este artigo se concentrará nos parâmetros que podem ser restaurados após reiniciar (fechar) o terminal. Na verdade, todos os exemplos apresentados são partes funcionais do código do meu projeto Cayman.


Locais de armazenamento de parâmetros


Exemplos de parâmetros

Onde armazenar esses parâmetros?
Locais de armazenamento Tipo Área de visibilidade Tempo de vida
Variáveis globais do terminal double Todos os gráficos 4 semanas após o último acesso
Objetos gráficos Qualquer. String <= 63 caracteres Gráfico atual Vida útil do gráfico
Comentários de ordens Strings <= 23 caracteres Todos os gráficos Vida útil do terminal
Arquivos de texto Qualquer. Sem restrições Todos os gráficos Tempo de vida do arquivo


Variáveis globais do terminal

As variáveis globais do terminal estão disponíveis em qualquer gráfico. O escopo pode ser limitado incluindo componentes como ChartId, Symbol, Period no nome da variável. No entanto, você não pode "argumentar" contra o tipo de variável. O texto não pode ser salvo.

No entanto, há um truque - compactar/descompactar valores inteiros. Como é sabido, o double ocupa 8 bytes (64 bits). Vou mostrar com um exemplo como armazenar vários valores inteiros numa variável. O mais importante é determinar a profundidade de bits de seus valores máximos.

// -----------------------------------------------------------------------------
// Exemplo de compactação/descompactação de valores inteiros de/para           |
// uma variável global usando operações bit a bit                              |
// -----------------------------------------------------------------------------
void OnStart() {
    
    int     value10 = 10; // max = 255 (8 casas decimais)
    int     value20 = 300; // max = 65535 (16 casas decimais)
    bool    value30 = true; // max = 1 (1 casas decimais)
    
    // empacotamos os valores em 25 bits (8+16+1)
    // 39 bits permanecem livres (64-25)
    ulong packedValue = 
        (value10 << 17) + // reservamos local (16+1) para value20, value30
        (value20 << 1) + // reservamos local (1) para value30
        value30;
    
    // salvamos a variável global
    string nameGVar = "temp";
    GlobalVariableSet(nameGVar, packedValue);
    
    // lemos a variável global
    packedValue = (ulong)GlobalVariableGet(nameGVar);
    
    // descompactamos os valores
    // 0xFF, 0xFFFF, 0x1 - máscaras de bits de valores máximos
    int value11 = (int)((packedValue >> 17) & 0xFF);
    int value21 = (int)((packedValue >> 1) & 0xFFFF);
    bool value31 = (bool)(packedValue & 0x1);
    
    // comparamos os 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

Hmm, será que podemos armazenar parâmetros de script em objetos gráficos? Por que não? Definimos a propriedade do objeto OBJPROP_PRICE = 0, portanto, o objeto se tornará "invisível", mas acessível programaticamente. Para maior confiabilidade, podemos salvar tal objeto no modelo do gráfico. Lógica de acesso ao parâmetro: há um objeto - extraímos os parâmetros, não há objeto - definimos os valores padrão.


Comentários de ordens

O comprimento máximo do comentário da ordem é de 23 caracteres. O que pode ser armazenado lá? Por exemplo, SOP/H1/SS/C2/Br/Br/Br. Onde (da esquerda para a direita)

Por que isso é necessário? Por exemplo, quando o histórico de trades é analisado ou quando uma ordem pendente é acionada, extraio o valor do algoritmo de fechamento e crio um analisador de stop virtual AnalyzerVirtSL que fechará o próprio trade sob certas condições.


Arquivos de texto

Esta é talvez a maneira mais confiável e versátil de armazenar parâmetros de recuperação. Uma vez que depuramos as classes de acesso, +podemos usá-las em qualquer lugar e sempre.


Configurações do aplicativo

Parte do arquivo de configurações do aplicativo AppSettings.txt

# -------------------------------------------------------------------
# Configurações de EA e script
#Codificação de arquivo = UCS-2 LE com BOM (obrigatório!!!) // isto é unicode
# -------------------------------------------------------------------
TimeEurWinter = 10:00 # horário de inverno do início da sessão europeia (horário do servidor)
TimeEurSummer = 09:00 # horário de verão do início da sessão europeia (horário do servidor)
ColorSessionEur = 224,255,255 # cor da sessão europeia
ColorSessionUsd = 255,240,245 # cor da sessão americana
NumberColorDays =10 # número de dias destacados (sessões)


Classe AppSettings.mqh

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

#include <Cayman/Params.mqh>

// nomes dos parâmetros do aplicativo
#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"

// -----------------------------------------------------------------------------
// Configurações gerais do Expert Advisor e dos Scripts                                        |
// -----------------------------------------------------------------------------
class AppSettings {
private:
    Params  *m_params;
public:
    // são instalados no arquivo AppSettings.txt
    string  TimeEurSummer; // horário de verão do início da sessão europeia
    string  TimeEurWinter; // horário de inverno do início da sessão europeia
    string  TimeTradeAsia; // hora do fim da negociação do corredor asiático
    color   ColorSessionEur; // cor da sessão europeia
    color   ColorSessionUsd; // cor da sessão americana
    int     NumberColorDays; // número de dias destacados

    // são instalados programaticamente
    string  PeriodTrends; // período de cálculo das tendências (D1,H4)
    string  TradePlan; // direção do trading (plano curto)
    bool    IsValid; // parâmetros válidos
    
    // métodos
    AppSettings();
    ~AppSettings() { delete m_params; };
    void Dump(string sender);
};

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

    IsValid = true;
    m_params = new Params();
    m_params.Load(PATH_APP_SETTINGS);
    if (m_params.Total() == 0) {
        PrintFormat("%s / ERROR: Arquivo invá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 os parâmetros das configuraçõ                                      | 
// -----------------------------------------------------------------------------
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

Coloquei a declaração da classe AppSettings no arquivo Uterminal.mqh, que é anexado via #include ao Expert Advisor e a qualquer script.

extern AppSettings  *gAppSettings; // configurações do aplicativo

Esta solução permite:


Parâmetros dos analisadores

O Expert Cayman gerencia vários analisadores, como AnalyzerTrend, AnalyserLevel, AnalyserVirtSL. Cada analisador está vinculado a um período gráfico específico. Isto quer dizer que a análise é iniciada apenas no momento em que uma nova barra aparece no período especificado. Os parâmetros do analisador são armazenados num arquivo de texto com as strings Key = Value. Por exemplo, o analisador de nível de negociação em H4 armazena seus parâmetros no arquivo Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt

Darei o conteúdo do arquivo com comentários (arquivo real — sem comentários)

// parâmetros do nível de negociação
nameObj=Lev607A160E // nome do nível de negociação
kindLevel=1 // tipo de nível (1 - resistência)
riskValue=1.00 // volume do trade durante o rompimento de nível (1)
riskUnit =1 // unidade de medida do volume do trade (1 -% dos fundos para garantia)
algClose =2 // algoritmo de fechamento de trade (2 - duas barras de correção)
ticketNew=0 // ticket do trade aberto durante o rompimento de nível
ticketOld=0 // ticket para fechamento de trade durante o rompimento de nível
profits=0// lucro planejado em pontos
losses=0 // perda planejada em pontos
// parâmetros do analisador
symbol=EURUSD // nome do símbolo
period=16388 // período do analisador (H4)
time0Bar=1618603200 // tempo de barra zero (seg.)
typeAnalyser=5 // tipo de analisador
colorAnalyser=16711935 // cor dos resultados do analisador
resultAnalyser=Lev607A160E, H4, 20:00, RS // resultado do analisador

Existe uma Analyser Analyser que pode salvar e restaurar os parâmetros de qualquer analisador. Quando o EA é reiniciado (por exemplo, ao alternar o período gráfico), os analisadores restauram seus parâmetros a partir dos devidos arquivos de texto. Além disso, se não chegar a hora de uma nova barra, a análise não é reiniciada. Os resultados do analisador (reseultAnalyser, colorAnalyser) calculados na barra anterior são exibidos nos comentários do Expert Advisor.


Transmissão parâmetros de script para um Expert Advisor

O script SetTradeLevel permite definir os parâmetros do nível de negociação. Um único objeto (linha reta, linha de tendência ou retângulo) é destacado no gráfico. O script SetTradeLevel encontra o objeto selecionado (nível de negociação) e define seus parâmetros.

Parâmetros do script SetTradeLevel

Em seguida, o script salva os parâmetros no arquivo Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt e envia o comando e o caminho para o arquivo através da função SendCommand

// -----------------------------------------------------------------------------
// Enviar os parâmetros de nível ao EA                                         |
// -----------------------------------------------------------------------------
NCommand SendCommand() {

    // carregamos os parâmetros de nível (se houver)
    Params *params = new Params();
    string speriod = UConvert::PeriodToStr(_Period);
    params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod);

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

    // salvamos os 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 um comando para EA
    params.SendCommand(cmd);
    delete params;
    
    return cmd;
}


A função params.SendCommand(cmd) é

// -----------------------------------------------------------------------------
// Enviar um comando ao EA                                                     |
// -----------------------------------------------------------------------------
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);
}

O EA cada tick (OnTick) por meio da função CheckExpernalComman() verifica a existência de um objeto denominado NAME_OBJECT_CMD. Se estiver presente, o comando e o caminho para o arquivo com os parâmetros do analisador são lidos e o objeto é imediatamente excluído. Em seguida, o Expert Advisor procura um analisador funcional pelo nome do arquivo. Se cmd == cmdDelete, o analisador é removido. Se cmd == cmdUpdate, são atualizados os parâmetros do analisador desde o arquivo. Se cmd == cmdNew, novo analisador é criado com parâmetros desde o arquivo.

Texto completo da classe Params, que encapsula a lógica para usar arquivos de parâmetros (strings 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>

// -----------------------------------------------------------------------------
// Classe de parâmetros (strings key=value com comentários #)                  |
// -----------------------------------------------------------------------------
class Params {
private:
    string  m_path; // caminho ao arquivo de parâmetros
    NCommand m_cmd; // comando para o EA
    CArrayString *m_items; // matriz de pares {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);
};

// -----------------------------------------------------------------------------
// Construtor padrão                                                           |
// -----------------------------------------------------------------------------
Params::Params() {
    m_items = new CArrayString();
}

// -----------------------------------------------------------------------------
// Adicionar o par chave=valor                                                 | 
// -----------------------------------------------------------------------------
bool Params::Add(string key, string value) {

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

// -----------------------------------------------------------------------------
// Obter o valor do parâmetro segundo a chave                                  |
// -----------------------------------------------------------------------------
string Params::GetValue(string key) {

    // buscamos a chave
    int j = Find(key);
    if (j < 0) return NULL; // não há chave
    
    // verificamos o separador
    string line = m_items.At(j);
    j = StringFind(line, "=");
    if (j < 0) { // não =
        PrintFormat("%s / ERROR: string inválida %s", __FUNCTION__, line);
        return NULL;
    }
    
    // retornamos o valor
    return UConvert::Trim(StringSubstr(line, j + 1));
}

// -----------------------------------------------------------------------------
// Encontrar o valor do parâmetro segundo a chave                                           |
// -----------------------------------------------------------------------------
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;
}

// -----------------------------------------------------------------------------
// Carregar os 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);
}

// -----------------------------------------------------------------------------
// Carregar os 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 o texto em strings
    string line, lines[];
    int numLines = StringSplit(text, DLM_LINE, lines);
    for (int j = 0; j < numLines; j++) {
        line = lines[j];
        // removemos comentários
        int k = StringFind(line, "#");
        if (k == 0) continue; // toda a string é um comentário
        if (k > 0) line = StringSubstr(line, 0, k);
        // adicionamos uma string não vazia
        if (line != "") m_items.Add(line);
    }
}

// -----------------------------------------------------------------------------
// Salvar parâmetros                                                           |
// -----------------------------------------------------------------------------
void Params::Save() {

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

// -----------------------------------------------------------------------------
// Enviar um comando ao EA                                                    | 
// -----------------------------------------------------------------------------
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);
}

// -----------------------------------------------------------------------------
// Receber o comando desde o 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;
}

// -----------------------------------------------------------------------------
// |Despejo de 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 fãs de MQL5: ao alterar o tipo m_items para CHashMap, o código das funções Add, GetValue, Find será reduzido significativamente. Mas a classe Params também é usada em MQL4. Além disso, a velocidade de acesso aos parâmetros não é importante neste caso. Porque os parâmetros são lidos uma vez para inicializar as variáveis locais. Por que não refiz a classe para CHashMap para MQL5? Provavelmente por ter trabalhado muito tempo num banco. Os desenvolvedores de software financeiro têm uma frase: se funciona, não toque nele! ;-)


Transmissão de parâmetros para programas externos

A unidade de intercâmbio entre sistemas diferentes é um arquivo json. Costumava ser um arquivo xml. As principais vantagens dos arquivos json são:

Por exemplo, existe uma classe Bar com os campos m_time, m_open, m_high, m_low, m_close, m_body. Onde m_body é a cor do candle: branco, preto ou doji. A classe Bar tem um método ToJson() que gera uma string 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}";
}


Isso pode ser feito por meio de StringFormat, mas haverá dificuldades com a reorganização ou exclusão de valores. É possível remover a formatação “\n\t”, uma vez que existem muitos serviços de formatação json online. Um deles é JSON Parser. Basta depurar a obtenção de um json válido uma vez e usar a função bar.ToJson() sem hesitação.

Um programa externo, por exemplo em C#, pode converter qualquer arquivo json num objeto. Como transferir um arquivo json desde MQL? É muito simples. Solte (salve) o arquivo json, por exemplo, no diretório do terminal Files/Json. Um programa externo monitora este diretório em busca de novos arquivos. Após encontrar um arquivo, ele o lê, converte-o num objeto e imediatamente exclui o arquivo (para que ele não fique a fazer nada) ou o move para o arquivo (para estatísticas).


Recepção de parâmetros vindos de programas externos

É desnecessário anexar a biblioteca json (Deus me livre de fazer esse bicho de sete cabeças) aos programas MQL. É mais fácil transferir arquivos de texto com strings Key=Value. Para processar arquivos, você pode usar a classe Params (veja acima). O Expert Advisor e o indicador são candidatos a receber parâmetros de programas ou scripts externos. Por exemplo, no manipulador OnTick, precisamos chamar a função CheckExternalCommand(), que verificará a presença de arquivos no diretório Files/ExtCmd. Se um arquivo for encontrado, lê, processa (aceita os parâmetros) e exclui o arquivo.

Assim, já ficam estudadas as formas de receber e passar parâmetros entre MQL e programas externos. Agora pensemos por que DLLs são necessárias para programas MQL?. Além disso, o Mercado MQL não aceita esse tipo de programas. Há apenas um motivo, e é a segurança, já que se pode obter acesso total a partir de uma DLL.


Transmissão de parâmetros para um smartphone

Preste atenção ao aplicativo Android WirePusher. Tiro o chapéu para os desenvolvedores por esse serviço (gratuito e sem anúncios). Não sei se existe algo semelhante no iPhone. Para fãs do iPhone, sigam a discussão neste artigo. 

Para usar o serviço, primeiro devemos:

Em seguida, executar o script, após substituir os asteriscos em id = "********" pelo ID

void OnStart() {
    string id = "**********"; // id do nosso smartphone no WirePusher
    WirePusher("Lucro $1000", "Negócio", "Fechado", id);
}

// ------------------------------------------------------------------------------------------------
// Enviar notificação para smartphone via serviço da web WirePusher
// Adicionar https://wirepusher.com a Terminal/Service/Settings/Expert Advisors/Allow WebRequest
// message - texto da notificação
// title - título da notificação (por exemplo, Atenção / Sinal / Transação)
// type - tipo de notificação (por exemplo, ordem pendente ativada / nível de lucro / fechado)
// id - número exclusivo do smartphone no aplicativo Android WirePusher
// ------------------------------------------------------------------------------------------------
bool WirePusher(string message, string title, string type, string id) {

    char data[]; // matriz de dados do corpo da mensagem HTTP
    char result[]; // matriz com dados de resposta do serviço da web
    string answer; // cabeçalhos de resposta do serviço da 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;
}

No EA do Cayman, a função WirePusher é chamada em AnalyzerTrade quando:

  • Ativada uma ordem pendente
  • Quebra do nível de negociação
  • Fechado um trade

No aplicativo WirePusher, podemos anexar som a cada tipo de notificação. Eu costumava definir o som "vai nessa" ao fechar um trade com lucro e "explosão" quando fechava com prejuízo. Depois me cansei de explosões ;-)


Conclusão

O local de armazenamento mais confiável e versátil para parâmetros são os arquivos de texto. Além disso, as operações de arquivo em qualquer sistema operacional (aplicativo) estão bem desenvolvidas (em cache).