Советы профессионального программиста (Часть II): Организация хранения и обмена параметров между экспертом, скриптами и внешними программами

6 мая 2021, 10:10
Malik Arykov
22
1 605

Содержание


Введение

В статье речь пойдет о параметрах, которые можно восстанавливать после перезапуска (закрытия) терминала. Все примеры — реально работающие куски кода из моего проекта Cayman.


Места хранения параметров


Примеры параметров

  • Время нулевого бара. Например, для обнаружения свечного паттерна логично оценивать его один раз после появления нового бара на заданном периоде.
  • Параметры торгового уровня. Например, выделили торговый уровень, скриптом установили его вид и размер сделки в случае пробоя. Скрипт передает параметры эксперту. Эксперт создает анализатор уровня. Анализатор «включается» только после появления нового бара на заданном таймфрейме.
  • Предпочтения пользователя. Например, цвет, правила торговли, методы отрисовки и т.д. Логично установить их один раз, например, в файле настроек.
Где хранить подобные параметры?
  • Глобальные переменные терминала
  • Графические объекты
  • Комментарии ордеров
  • Текстовые файлы
Места хранения Тип Область видимости Время жизни
Глобальные переменные терминала double Все графики 4 недели после последнего обращения
Графические объекты Любой. Строки <= 63 символов Текущий график Время жизни графика
Комментарии ордеров Строки длиной <= 23 символов Все графики Время жизни терминала
Текстовые файлы Любой. Без ограничений Все графики Время жизни файла


Глобальные переменные терминала

Глобальные переменные терминала доступны с любого графика. Область видимости можно ограничить за счет включения в имя переменной таких составляющих, как ChartId, Symbol, Period. Однако против типа переменной «не попрешь». Нельзя сохранить текст.

Однако есть один прием — упаковка/распаковка целых значений. Как известно, double занимает 8 байтов (64 бит). Покажу на примере, как хранить в одной переменной несколько целых значений. Самое главное — определить разрядность в битах их максимальных значений.

// -----------------------------------------------------------------------------
// Пример упаковки/распаковки целых значений в/из глобальной переменой         |
// с помощью битовых операций                                                  |
// -----------------------------------------------------------------------------
void OnStart() {
    
    int     value10 = 10; // max = 255 (8 разрядов)
    int     value20 = 300; // max = 65535 (16 разрядов)
    bool    value30 = true; // max = 1 (1 разряд)
    
    // упаковываем значения в 25 бит (8+16+1)
    // остаются свободными 39 бит (64-25)
    ulong packedValue = 
        (value10 << 17) + // резервируем место (16+1) для value20, value30
        (value20 << 1) + // резервируем место (1) для value30
        value30;
    
    // сохраняем глоб.переменную
    string nameGVar = "temp";
    GlobalVariableSet(nameGVar, packedValue);
    
    // считываем глоб.переменную
    packedValue = (ulong)GlobalVariableGet(nameGVar);
    
    // распаковываем значения
    // 0xFF, 0xFFFF, 0x1 - битовые маски максимальных значений
    int value11 = (int)((packedValue >> 17) & 0xFF);
    int value21 = (int)((packedValue >> 1) & 0xFFFF);
    bool value31 = (bool)(packedValue & 0x1);
    
    // сравниваем значения
    if (value11 == value10 && value21 == value20 && value31 == value30) Print("OK");
    else PrintFormat("0x%X / 0x%X /0x%X / 0x%X", packedValue, value11, value21, value31);
}


Графические объекты

Хм, в графических объектах можно хранить параметры скриптов? А почему нет. Устанавливаем свойство объекта OBJPROP_PRICE = 0, тогда объект станет визуально «не видимым», но программно доступным. Для надежности можно сохранить такой объект в шаблоне графика. Логика доступа к параметрам: есть объект — извлекаем параметры, нет объекта — устанавливаем значения по умолчанию.


Комментарии ордеров

Максимальная длина комментария ордера 23 символа. Что там можно хранить? Например, SOP/H1/SS/C2/Br/Br/Br. Где (слева на право)

  • SOP — отправитель ордера (SOP – скрипт SendOrderByPlan)
  • H1  — период формирования ордера (H1)
  • SS  — тип ордера (SS – Sell Stop)
  • C2  — алгоритм закрытия ордера
  • Br  — тренд на D1 (Br – Bear)
  • Br  — тренд на H4 (Br – Bear)
  • Br  — тренд на периоде формирования ордера (Br – Bear)

Зачем это нужно? Например, для анализа истории сделок или при срабатывании отложенного ордера я извлекаю значение алгоритма закрытия и создаю анализатор виртуального стопа AnalyserVirtSL, который сам закроет сделку при определенных условиях.


Текстовые файлы

Это, пожалуй, самый надежный и универсальный способ хранения параметров для восстановления. Один раз отладил классы доступа — пользуешься ими везде и всегда.


Настройки приложения

Часть файла настроек приложения AppSettings.txt

# -------------------------------------------------------------------
# Настройки эксперта и скриптов
# Кодировка файла = UCS-2 LE с BOM (обязательна!!!) // это юникод
# -------------------------------------------------------------------
TimeEurWinter = 10:00 # зимнее время начала Европейской сессии (время сервера)
TimeEurSummer = 09:00 # летнее время начала Европейской сессии (время сервера)
ColorSessionEur = 224,255,255 # цвет Европейской сессии
ColorSessionUsd = 255,240,245 # цвет Американской сессии
NumberColorDays = 10 # кол-во подсвеченных дней (сессий)


Класс AppSettings.mqh

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

#include <Cayman/Params.mqh>

// имена параметров приложения
#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"

// -----------------------------------------------------------------------------
// Общие настройки Советника и Скриптов                                        |
// -----------------------------------------------------------------------------
class AppSettings {
private:
    Params  *m_params;
public:
    // устанавливаются в файле AppSettings.txt
    string  TimeEurSummer; // летнее время начала Европейской сессии
    string  TimeEurWinter; // зимнее время начала Европейской сессии
    string  TimeTradeAsia; // время окончания торговли коридора Азии
    color   ColorSessionEur; // цвет Европейской сессии
    color   ColorSessionUsd; // цвет Американской сессии
    int     NumberColorDays; // кол-во подсвеченных дней

    // устанавливаются программно
    string  PeriodTrends; // Периоды расчета трендов (D1,H4)
    string  TradePlan; // Направление торговли (краткий план)
    bool    IsValid; // допустимость параметров
    
    // методы
    AppSettings();
    ~AppSettings() { delete m_params; };
    void Dump(string sender);
};

// -----------------------------------------------------------------------------
// Конструктор                                                                 |
// -----------------------------------------------------------------------------
AppSettings::AppSettings() {

    IsValid = true;
    m_params = new Params();
    m_params.Load(PATH_APP_SETTINGS);
    if (m_params.Total() == 0) {
        PrintFormat("%s / ERROR: Не допустимый файл / %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));
}

// -----------------------------------------------------------------------------
// Распечатать параметры настроек                                              | 
// -----------------------------------------------------------------------------
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);
}


Особености

Объявление класса AppSettings я разместил в файле Uterminal.mqh, который подключается через #include к эксперту и любому скрипту.

extern AppSettings  *gAppSettings; // настройки приложения

Такое решение позволяет:

  • Инициализировать gAppSettings один раз в любом месте
  • Использовать gAppSettings в экземпляре любого класса (вместо передачи в качестве параметра)


Параметры анализаторов

Эксперт Cayman управляет различными анализаторами, например AnalyserTrend, AnalyserLevel, AnalyserVirtSL. Каждый анализатор привязан к определенному таймфрейму. Т.е. анализ запускается только в момент появления нового бара на заданном периоде. Параметры анализатора хранятся в текстовом файле со строками Key = Value. Например, анализатор торгового уровня на H4 хранит свои параметры в файле Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt

  • Cayman — имя проекта
  • Params  — подкаталог с параметрами анализаторов
  • 128968168864101576  — ID графика // IntergerToString(ChartID())
  • exp_05_Lev607A160E_H4.txt  — имя файла с параметрами анализатора —
    • exp  — префикс
    • 05  — тип анализатора
    • Lev607A160E  — имя анализатора (торгового уровня)
    • H4  — отслеживаемый период.

Приведу содержимое файла с комментариями (реальный файл — без комментариев)

// параметры торгового уровня
nameObj=Lev607A160E // имя торгового уровня
kindLevel=1 // вид уровня (1 - сопротивление)
riskValue=1.00 // объем сделки при пробое уровня (1)
riskUnit=1 // единица измерения объема сделки (1 - % средств для залога)
algClose=2 // алгоритм закрытия сделки (2 – два коррекционных бара)
ticketNew=0 // тикет сделки открытый при пробое уровня
ticketOld=0 // тикет для закрытия сделки при пробое уровня
profits=0 // планируемый профит в пунктах
losses=0 // планируемый убыток в пунктах
// параметры анализатора
symbol=EURUSD // имя символа
period=16388 // период анализатора (H4)
time0Bar=1618603200 // время нулевого бара (сек)
typeAnalyser=5 // тип анализатора
colorAnalyser=16711935 // цвет результатов анализатора
resultAnalyser=Lev607A160E, H4, 20:00, RS // результат анализатора

Есть базовый класс Analyser, который умеет сохранять и восстанавливать параметры любого анализатора. При перезапуске эксперта (например, при переключении таймфрейма) анализаторы восстанавливают свои параметры из соответствующих текстовых файлов. При этом, если не наступило время нового бара, анализ повторно не запускается. Результаты анализаторов (reseultAnalyser, colorAnalyser), рассчитанные на предыдущем баре, отображаются в комментариях эксперта.


Передача параметров скрипта эксперту

Скрипт SetTradeLevel позволяет установить параметры торгового уровня. На графике выделяется один объект (прямая или трендовая линия, или прямоугольник). Скрипт SetTradeLevel находит выделенный объект (торговый уровень) и устанавливает его параметры.

Параметры скрипта SetTradeLevel

Далее, скрипт сохраняет параметры в файл Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt и оправляет команду и путь к файлу через функцию SendCommand

// -----------------------------------------------------------------------------
// Отправить параметры уровня эксперту                                         |
// -----------------------------------------------------------------------------
NCommand SendCommand() {

    // загружаем параметры уровня (если есть)
    Params *params = new Params();
    string speriod = UConvert::PeriodToStr(_Period);
    params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod);

    // определяем команду
    NCommand cmd = 
        (gKindLevel == levUnknown) ? cmdDelete :
        (params.Total() > 0) ? cmdUpdate :
        cmdCreate;

    // сохраняем параметры
    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();
    
    // отправляем команду эксперту
    params.SendCommand(cmd);
    delete params;
    
    return cmd;
}


Функция params.SendCommand(cmd) имеет вид

// -----------------------------------------------------------------------------
// Послать команду эксперту                                                    |
// -----------------------------------------------------------------------------
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);
}

Эксперт каждый тик (OnTick) через функцию CheckExpernalCommand() проверяет наличие объекта с именем NAME_OBJECT_CMD. При его наличии считывается команда и путь к файлу с параметрами анализатора, и объект сразу же удаляется. Далее эксперт ищет работающий анализатор по имени файла. Если cmd == cmdDelete, то анализатор удаляется. Если cmd == cmdUpdate, то обновляются параметры анализатора из файла. Если cmd == cmdNew, то создается новый анализатор с параметрами из файла.

Полный текст класса Params, который инкапсулирует логику работы с файлами параметров (строки 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>

// -----------------------------------------------------------------------------
// Класс параметров (строки key=value с коментами #)                           |
// -----------------------------------------------------------------------------
class Params {
private:
    string  m_path; // путь к файлу параметров
    NCommand m_cmd; // команда для эксперта
    CArrayString *m_items; // массив пар {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);
};

// -----------------------------------------------------------------------------
// Конструктор по умолчанию                                                    |
// -----------------------------------------------------------------------------
Params::Params() {
    m_items = new CArrayString();
}

// -----------------------------------------------------------------------------
// Добавить пару ключ=значение                                                 | 
// -----------------------------------------------------------------------------
bool Params::Add(string key, string value) {

    int j = Find(key);
    string line = key + "=" + value;
    if (j >= 0) { // обновляем
        m_items.Update(j, line);
        return false;
    }
    else { // добавляем
        m_items.Add(line);
        return true;
    }
}

// -----------------------------------------------------------------------------
// Получить значение параметра по ключу                                        |
// -----------------------------------------------------------------------------
string Params::GetValue(string key) {

    // ищем ключ
    int j = Find(key);
    if (j < 0) return NULL; // нет ключа
    
    // проверяем разделитель
    string line = m_items.At(j);
    j = StringFind(line, "=");
    if (j < 0) { // нет =
        PrintFormat("%s / ERROR: не допустимая строка %s", __FUNCTION__, line);
        return NULL;
    }
    
    // возвращаем значение
    return UConvert::Trim(StringSubstr(line, j + 1));
}

// -----------------------------------------------------------------------------
// Найти значение параметра по ключу                                           |
// -----------------------------------------------------------------------------
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;
}

// -----------------------------------------------------------------------------
// Загрузить параметры                                                         |
// -----------------------------------------------------------------------------
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);
}

// -----------------------------------------------------------------------------
// Загрузить параметры                                                         |
// -----------------------------------------------------------------------------
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;
    
    // разбиваем текст на строки
    string line, lines[];
    int numLines = StringSplit(text, DLM_LINE, lines);
    for (int j = 0; j < numLines; j++) {
        line = lines[j];
        // удаляем коментарий
        int k = StringFind(line, "#");
        if (k == 0) continue; // вся строка - коммент
        if (k > 0) line = StringSubstr(line, 0, k);
        // добавляем не пустую строку
        if (line != "") m_items.Add(line);
    }
}

// -----------------------------------------------------------------------------
// Сохранить параметры                                                         |
// -----------------------------------------------------------------------------
void Params::Save() {

    string text = "";
    for (int j = 0; j < m_items.Total(); j++) {
        text += m_items.At(j) + "\n";
    }
    // перезаписываем существующий файл
    UFile::SaveText(text, m_path, true);
}

// -----------------------------------------------------------------------------
// Послать команду эксперту                                                    | 
// -----------------------------------------------------------------------------
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);
}

// -----------------------------------------------------------------------------
// Принять команду от скрипта                                                  |
// -----------------------------------------------------------------------------
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;
}

// -----------------------------------------------------------------------------
// |Дамп параметров                                                            |                                                             
// -----------------------------------------------------------------------------
void Params::Dump(string sender) {
    for (int j = 0; j < m_items.Total(); j++) {
        PrintFormat("%s / %s", sender, m_items.At(j));
    }
}

Фанатам MQL5: при изменении типа m_items на CHashMap код функций Add, GetValue, Find  существенно сократится. Но класс Params используется и в MQL4. Кроме того, скорость доступа к параметрам в данном случае не важна. Поскольку параметры считываются один раз для инициализации локальных переменных. Почему же я не переделал класс под CHashMap для MQL5. Наверное из-за того, что долгое время работал в банке. У разработчиков финансового софта есть очень важный принцип: Работает — не трогай! ;-)


Передача параметров внешним программам

Единицей обмена между различными системами, де-факто является json-файл. Раньше был xml-файл. Основными преимуществами json-файлов являются:

  • Легкость создания (формирования / форматирования)
  • Отличная поддержка на всех языках высокого уровня
  • Читабельность

Например, есть класс Bar с полями m_time, m_open, m_high, m_low, m_close, m_body. Где m_body — цвет свечи: белая, черная или дожи. Класс Bar имеет метод ToJson(), который формирует 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}";
}

Можно сделать через StringFormat, но возникнут трудности с перестановкой или удалением значений. Можно убрать форматирование “\n\t”, поскольку есть достаточно много онлайн сервисов форматирования json. Один из них JSON Parser. Достаточно один раз отладить получение валидного json и пользоваться функцией bar.ToJson() не задумываясь.

Внешняя программа, например на C#, может преобразовать json файл любой сложности в объект. Как же передать json файл из MQL? Да очень просто. Скидываете (сохраняете) json файл, например, в каталог терминала Files/Json. Внешняя программа следит за этим каталогом на предмет появления новых файлов. Обнаружив файл, считывает его, преобразовает в объект и сразу удаляет файл (чтобы не болтался) или перемещает в архив (для статистики).


Прием параметров от внешних программ

Подключать json библиотеку (не дай бог самому делать такой «велосипед») к своим MQL программам — лишние хлопоты. Проще передавать текстовые файлы со строками Key=Value. Для обработки файлов можно воспользоваться классом Params (см. выше). Кандидатами на прием параметров от внешних программ или скриптов являются Эксперт и Индикатор. Например, в обработчике OnTick нужно вызывать функцию CheckExternalCommand(), которая будет проверять наличие файлов в каталоге Files/ExtCmd. При обнаружении файла считать, обработать(принять параметры) и удалить файл.

Итак, рассмотрены способы приема и передачи параметров между MQL и внешними программами. Теперь задумайтесь над вопросом: Зачем для MQL программ нужны DLL?. Тем более MQL-маркет не принимает такие программы. Причина одна — безопасность, поскольку из DLL можно залезть куда угодно.


Передача параметров на смартфон

Обратите внимание на Android приложение WirePusher. Снимаю шляпу перед разработчикам за такой сервис (бесплатный и без рекламы). Не знаю есть ли подобное на iPhone. Фанаты iPhone отпишитесь в обсуждении статьи. 

Для работы с сервисом предварительно нужно:

  • Установить WirePusher на свой смартфон
  • Запустить приложение. На главном экране увидите свой id
  • Добавить https://wirepusher.com в Терминал/Сервис/Настройки/Советники/Разрешить WebRequest

Далее запустить скрипт, предварительно заменив звездочки в id = “********” на свой id

void OnStart() {
    string id = "**********"; // id вашего смартфона в WirePusher
    WirePusher("Профит $1000", "Сделка", "Закрылась", id);
}

// ------------------------------------------------------------------------------------------------
// Отправить уведомление на смартфон через веб-сервис WirePusher
// Добавить https://wirepusher.com в Терминал/Сервис/Настройки/Советники/Разрешить WebRequest
// message - текст уведомления
// title - заголовок уведомления (например, Внимание / Сигнал / Сделка)
// type - тип уведомления (например, Сработала отложка / Пробит уровень / Закрылась)
// id - уникальный номер смартфона в андроид-приложении WirePusher
// ------------------------------------------------------------------------------------------------
bool WirePusher(string message, string title, string type, string id) {

    char data[]; // массив данных тела HTTP-сообщения
    char result[]; // массив с данными ответа веб-сервиса
    string answer; // заголовки ответа веб-сервиса
    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;
}

В эксперте Cayman, функция WirePusher вызывается в AnalyserTrade при:

  • Срабатывании отложки
  • Пробое торгового уровня
  • Закрытии сделки

В приложении WirePusher к каждому типу уведомления можно привязать свой звук. Раньше у меня при закрытии сделки с профитом звучало «та-да», для сделки с убытком — «взрыв». Но потом я устал от взрывов ;-)


Заключение

Самым надежным и универсальным местом хранения параметров являются текстовые файлы. Тем более, что файловые операции в любой ОС (приложении) хорошо отработаны (кешируются).


Последние комментарии | Перейти к обсуждению на форуме трейдеров (22)
Vladimir Simakov
Vladimir Simakov | 18 май 2021 в 20:30
Malik Arykov:

Все примеры из моих статей, у меня прекрасно работают (в моем эксперте и скриптах)

Мой пост не о том, что не работает, а о том, что не обеспечивается безопасность и целостность данных. При использовании всех перечисленных методов передачи данных, не обеспечивается 100 надежность механизма. Причем, связано это не с возможными багами сторонних систем (Терминал, ОС, аппаратные баги), а с самим методом: коллизия имен или действия пользователя в случае глобальных переменных терминала, то же самое + функции удаления для графических объектов, отсутствие синхронизации для операций чтения и записи в файл из разных потоков.

Да, вероятность этого можно свести к очень низким значениям, но не к 0. Вот об этом и надо не забывать)

Malik Arykov
Malik Arykov | 18 май 2021 в 22:18
Vladimir Simakov:

Мой пост не о том, что не работает, а о том, что не обеспечивается безопасность и целостность данных. При использовании всех перечисленных методов передачи данных, не обеспечивается 100 надежность механизма. Причем, связано это не с возможными багами сторонних систем (Терминал, ОС, аппаратные баги), а с самим методом: коллизия имен или действия пользователя в случае глобальных переменных терминала, то же самое + функции удаления для графических объектов, отсутствие синхронизации для операций чтения и записи в файл из разных потоков.

Да, вероятность этого можно свести к очень низким значениям, но не к 0. Вот об этом и надо не забывать)

Хранение параметров в файлах обеспечивают 100% надежность. Все ваши "коллизии" решаются с помощью префиксов имен объектов, в т.ч. глобальных переменных.

Например, параметры уровня хранятся в Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt. Где 128968168864101576 - ChartID. Никто кроме анализатора именно этого уровня об этом файле не знает. Возьмите любой кусок кода из статьи и попробуйте "сломать" ;-) Если сломаете, то я покажу, где вы ошиблись ;-)

Vladimir Simakov
Vladimir Simakov | 19 май 2021 в 06:40
void OnStart()
  {
   while(!IsStopped())
      for(int i=GlobalVariablesTotal();i>=0;--i)
         GlobalVariableSet(GlobalVariableName(i),MathRand());
  }

Что-нибудь подобное такому скрипту в любом советнике/индикаторе/скрипте/сервисе, запущенному любопытным юзером, и у Вас веселые недели поиска невоспроизводимого бага, которого нет)))

Malik Arykov
Malik Arykov | 19 май 2021 в 11:53
Vladimir Simakov:

Что-нибудь подобное такому скрипту в любом советнике/индикаторе/скрипте/сервисе, запущенному любопытным юзером, и у Вас веселые недели поиска невоспроизводимого бага, которого нет)))

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

Aleksey Mavrin
Aleksey Mavrin | 20 май 2021 в 10:31
Malik Arykov:

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

Очевидно имелось ввиду случайное изменение вашей переменной другой программой - любой, даже демкой с маркета)

Ну если модератор похвалил, тогда ок...  ))

Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции
В статье завершим работу над классами объектов-чартов и их коллекцией. Сделаем автоматическое отслеживание изменения свойств чартов и их окон, а также сохранение новых параметров в свойства объекта. Такая доработка позволит в будущем сделать событийный функционал для всей коллекции чартов.
Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов
В статье создадим функционал отслеживания некоторых событий объектов-чартов — добавление и удаление графиков символов, добавление и удаление подокон на график, а также добавление/удаление/изменение индикаторов в окнах чартов.
Паттерны с примерами (Часть I): Кратная вершина Паттерны с примерами (Часть I): Кратная вершина
Статья начинает цикл рассмотрения разворотных паттернов в рамках алготрейдинга. Мы начнем мысль, исследуя первое и самое интересное семейство данных паттернов, которые берут начало из паттерна "Двойная вершина" и "Двойное дно".
Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов
В статье расширим функционал объектов-чартов, организуем навигацию по графикам, создание скриншотов, сохранение и применение шаблонов к графикам. Также сделаем автоматическое обновление коллекции объектов-чартов, их окон и индикаторов в них.