Сохранение важных текущих значений в программе на случай сбоя

 

Здравствуйте, коллеги-программисты. Заинтересовал меня следующий вопрос, прошу поделиться опытом.

Предположим, имеется программа, которая имеет ряд флагов состояний и требует знания информации о том, какие сделки и в каком статусе находились на предыдущем тике. Одним словом, часть важной информации сохраняется во внутренней памяти программы. Однако если есть требование, чтобы в случае технической неисправности (сбоя терминала, перезагрузки компьютера и т.д.) программу можно было запустить с того места, на котором она остановилась, нужно, чтобы все эти сведения хранились где-то в ином месте. И здесь возникает вопрос, что можно в таких случаях использовать. Конечно, самый простой способ - отправлять эти сведения в csv или ini файлы, однако когда это делаешь на каждом тике, скорость исполнения программы сильно снижается, так как нужно не просто записать сведения, но и открыть/закрыть файл на каждом тике, иначе в случае сбоя несохраненные сведения "улетят".

Мне приходит решение сохранять данные во внутренней памяти и перезаписывать сведения только если происходит изменение состояния (содержания внешнего файла).

Поделитесь опытом, кто как решает подобные проблемы?

 

Все сведения о сделках хранятся на сервере и они никуда не исчезают после сбоя клиента. Надо код строить так, чтобы состояния считывались из доступных источников, флаги - костыли. 

 
evillive:

Все сведения о сделках хранятся на сервере и они никуда не исчезают после сбоя клиента. Надо код строить так, чтобы состояния считывались из доступных источников, флаги - костыли. 

Оно конечно понятно, что нужно стремиться к минимуму флагов, но все-таки если флаги - костыли, то как бы Вы порекомендовали без них реализовать такую стратегию:

имеется перерисовывающийся индикатор, который рисует канал из трех линий - верхняя, средняя и нижняя. И мы должны выполнять стратегию А с того момента, как цена уходит вниз нижнего канала и до тех пор, пока не пробьет вверх среднюю линию, и, соответственно, стратегию Б с того момента, как цена уходит вверх верхнего канала до момента, как цена не пробьет вниз уровень средней линии. Подчеркну - советник перерисовывающийся. Реализовать такую стратегию без флагов я не представляю возможным. Поделитесь, пожалуйста, опытом, как это можно сделать без флагов?


И сам вопрос по сохранению значений флагов для меня остался актуальным.

 
Поскольку открытые сделки могут зарыться по стопу или тейку даже при выключенном терминале, то робот просто обязан перед началом работы убедиться какие сделки всё ещё находятся в рынке и из этого сделать вывод, в каком состоянии он должен находится. А флаги ещё можно в глобальные переменные терминала писать.
 
delfik71091:

Оно конечно понятно, что нужно стремиться к минимуму флагов, но все-таки если флаги - костыли, то как быВы порекомендовали без них реализовать такую стратегию:

имеетсяперерисовывающийся индикатор, который рисует канал из трех линий -верхняя, средняя и нижняя. И мы должны выполнять стратегию А с тогомомента, как цена уходит вниз нижнего канала и до тех пор, пока непробьет вверх среднюю линию, и, соответственно, стратегию Б с тогомомента, как цена уходит вверх верхнего канала до момента, как цена непробьет вниз уровень средней линии. Подчеркну - советникперерисовывающийся. Реализовать такую стратегию без флагов я не представляю возможным. Поделитесь, пожалуйста, опытом, как это можно сделать без флагов?


И сам вопрос по сохранению значений флагов для меня остался актуальным.

Я бы не связывался с перерисовывающим на каждом тике индикатором, каким бы он красивым ни казался, это всё обман. Но если очень надо именно такое, записывайте в файл критичную информацию, это меньше секунды времени занимает. Либо в глобальные переменные терминала, они тоже в файл скидываются по команде советника.
 
Я бы тоже не связывался, но какие есть ТЗ, по таким и работаю. Спасибо, Evilive, навели меня еще на одну мысль, как реализовать одну стратегию, над которой сейчас работаю и уйти от записи инфы в файл.
 
delfik71091:

...Поделитесь опытом, кто как решает подобные проблемы?

Насколько проблемы подобны, не знаю. Я ловлю изменение комплекта последних тиков по 25 парам с опросом каждые 16 миллисекунд, сейчас работает такой код выдачи строки изменений S в файл (iErr извлекает последнюю ошибку и сбрасывает соответствующую системную переменную в ноль), работало это и для 60 терминалов без ощутимых последствий для процессоров и дисков, реально ведь идет обмен с памятью (не с магнитными пластинами):

 k=StringLen(S);
  if (k!=0) {
    FileWriteString(fiRes, S, k); k = iErr();
    // FileFlush(fiRes); 25.05.2014 узнал, что FileFlush работает дольше 25 мс. А Close,Open,Seek исполняются 0.1 мс
    FileClose(fiRes); k1=iErr(); // Закрываем файл тиков, данные ушли в буфер контроллера диска
    for (kFi=0;kFi<=10;kFi++) { // пытаемся открыть 10 раз с шагом 0.05 сек
      fiRes = FileOpen(ResFName, FILE_BIN|FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ,0,CP_ACP); // если не было, создаст
      k1=iErr();
      if (fiRes>=0) break; else Sleep (50);
      }
    if (fiRes<1) {ToProt(StringConcatenate ("^99 Не открывается файл ",ResFName," для записи, err=",k1)); return;}
    
    FileSeek(fiRes,0,SEEK_END); k1=iErr(); // В конец файла
    // Новые тики записаны, размер файла изменился, он снова открыт    .......
 

Влад, это опять-таки идет запись в файл.

А что касается флагов - ведь по сути, любая переменная класса памяти static или переменная, объявленная на глобальном уровне mql-программы является флагом, так как сохраняет в себя то, что было раньше и передает на новую итерацию.

И при этом без статической переменной невозможно корректно обнаружить новый бар (по крайней мере, я таких функций не встречал), но ведь если во время текущего бара произойдет сбой и программа перезапустится, она вновь обнаружит ""новый" бар. Таким образом получается, что для надежной работы программы нам необходимо все static переменные и переменные, объявленные на глобальном уровне программы куда-то скидывать, либо писать такой код, чтобы абсолютно каждую переменную на каждом тике можно было вычислить "с нуля". Но как этого можно достичь?

 
delfik71091:

Влад, это опять-таки идет запись в файл.

А что касается флагов - ведь по сути, любая переменная класса памяти static или переменная, объявленная на глобальном уровне mql-программы является флагом, так как сохраняет в себя то, что было раньше и передает на новую итерацию.

И при этом без статической переменной невозможно корректно обнаружить новый бар (по крайней мере, я таких функций не встречал), но ведь если во время текущего бара произойдет сбой и программа перезапустится, она вновь обнаружит ""новый" бар. Таким образом получается, что для надежной работы программы нам необходимо все static переменные и переменные, объявленные на глобальном уровне программы куда-то скидывать, либо писать такой код, чтобы абсолютно каждую переменную на каждом тике можно было вычислить "с нуля". Но как этого можно достичь?

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

Насколько я догадываюсь, в Ваших ТЗ нет требования обеспечить взрывозащищенную работу советника. Защищать надо не от взрыва газового баллона в соседней комнате, а от отказа электропитания. В первую очередь установкой устройства бесперебойного питания. И, например, запуском скидывания всего нужного на диск по сигналу от этого устройства о том, что емкость аккумулятор скоро будет исчерпана. Или жестче, как Вы пишете, записью на диск на каждый тик. Даже в отсутствие бесперебойника данные, переданные в буфер контроллера диска, успеют при отказе питания записаться на магнитные пластины (для этого используются накопленные механическая энергия вращения и электрическая энергия на обкладках конденсаторов).

Общий подход к ответственному хранению данных хорошо развит в универсальных системах управления базами данных (СУБД). Непрерывно ведется журнал транзакций (на диске). И вообще любая операция считается исполненной после ее отражения на диске, не в памяти, поэтому и быстродействие меряется лишь сотнями-тысячами транзакций в секунду. По этому журналу можно восстановить состояние БД в любой предыдущий момент, начиная с создания БД. Важность этого дела приводит к тому, что СУБД при разворачивании отнимает под себя у диска нужную часть с секторами, идущими подряд, а затем внутри этой области ведет свою файловую систему. Ничего более подходящего предложить не могу. Диск - место энергонезависимого хранения данных, а гораздо более быстрая по сравнению с ним память, к сожалению, забывает данные сразу с отключением питания.

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

 
Спасибо, Влад! Чувствуется ответ компетентного человека. За время обдумывания вопроса в эти дни, да и Ваш ответ подтвердил, что это правильное решение, - придумал следующий вариант: из последовательности переменных составляется текстовая строка через определенный разделитель, далее мы отслеживаем,  не изменилась ли эта текстовая строка. Как только замечено отличие хотя бы на 1 знак, сбрасываем строку на жесткий диск вместо старой записи (до полноценной БД, скорее всего, в таких программах нет смысла разрастаться, кроме того, в случае, если с момента сбоя прошло значительное время, даже последние восстановленные данные, скорее всего, уже будут не актуальны, а вот ). В OnInit'e делаем проверку на наличие такой строки на жестком диске. Если есть, то все значения переменных восстанавливаются из нее и советник продолжает работу с последней точки. Тогда мы и данные сохраняем, и не особо нагружаем систему.
 
delfik71091:
Спасибо, Влад! Чувствуется ответ компетентного человека. За время обдумывания вопроса в эти дни, да и Ваш ответ подтвердил, что это правильное решение, - придумал следующий вариант: из последовательности переменных составляется текстовая строка через определенный разделитель, далее мы отслеживаем,  не изменилась ли эта текстовая строка. Как только замечено отличие хотя бы на 1 знак, сбрасываем строку на жесткий диск вместо старой записи (до полноценной БД, скорее всего, в таких программах нет смысла разрастаться, кроме того, в случае, если с момента сбоя прошло значительное время, даже последние восстановленные данные, скорее всего, уже будут не актуальны, а вот ). В OnInit'e делаем проверку на наличие такой строки на жестком диске. Если есть, то все значения переменных восстанавливаются из нее и советник продолжает работу с последней точки. Тогда мы и данные сохраняем, и не особо нагружаем систему.

Если это подходит, хорошо. Особо нагрузить систему очень сложно, чтобы убедиться, просто замеряйте время исполнения операций, исполняя их, например, по 100 тысяч раз. Но вот складывать все в одну строку для проверки, по-моему, лишнее. И сами преобразования значений в строки, и, особенно, сложение подстрок мне кажутся лишними операциями. Лучше завести структуру, в которой содержатся только элементы, которые надо сравнивать. Один экземпляр со значениями, с которыми надо сравнивать (последними записанными на диск), другой с текущими. Одна функция их сравнит (LastV.a != OperV.a || LastV.b != OperV.b || LastV.c != OperV.c || ...) и вернет необходимость записи на диск. Логические выражения в MQL подсчитываются по короткой схеме, и в случае первого же обнаруженного отличия остальные делаться не будут.

Структура того, что лучше писать на диск - посмотрите файл \config\terminal.ini, ведь он предназначен тоже для возобновления работы после перерыва. А также справку к терминалу "Конфигурация при старте". С разделителями трудно разбираться, лучше дать имя каждому из записываемых значений. Можно будет наглядно просматривать редактором текстов. И считывать проще, особенно с учетом последующего изменения состава хранимых на диске значений. И, в целях отладки, лучше было бы опустошать файл, куда пишете, не каждый раз, а, например, после достижения размера в 10 Мб или 1 Гб. В этих же целях добавил бы в записываемые сведения локальное время. Собственно, время дополнит проверку наличия на диске, ведь прошлогодние сведения вряд ли нужны.

Причина обращения: