Beobachten
1
1
Fork
Du hast bereits einen Fork von mt5-manager erstellt
1
mt5-manager/docs/articles/part-05/publish/article5-site-20261102.html
257 Zeilen
116 KiB
HTML
<p><b>Содержание</b></p> <ul> <li><a href="/ru/articles/23709/#intro">Введение</a></li> <li><a href="/ru/articles/23709/#plan">Намечаем путь</a></li> <li><a href="/ru/articles/23709/#initial_setup">Первоначальная установка</a></li> <li><a href="/ru/articles/23709/#terminal_copy">Получаем новую копию терминала</a></li> <li><a href="/ru/articles/23709/#broker_connection">Подключаемся к торговому серверу</a></li> <li><a href="/ru/articles/23709/#account_settings">Сохраняем параметры счёта</a></li> <li><a href="/ru/articles/23709/#source_files">Подготавливаем исходные файлы</a></li> <li><a href="/ru/articles/23709/#configuration">Сохраняем конфигурацию</a></li> <li><a href="/ru/articles/23709/#create_instance">Создаём экземпляр</a></li> <li><a href="/ru/articles/23709/#account_login">Выбираем счёт при запуске</a></li> <li><a href="/ru/articles/23709/#web_interface">Добавляем веб-интерфейс</a></li> <li><a href="/ru/articles/23709/#update_project">Обновляем проект и сервис</a></li> <li><a href="/ru/articles/23709/#testing">Тестирование</a></li> <li><a href="/ru/articles/23709/#conclusion">Заключение</a></li> </ul> <p>&nbsp;</p> <h3 id="intro">Введение</h3> <p>В <a href="/ru/articles/20150" target="_blank">предыдущей части</a> мы изменили способ получения информации о терминалах: теперь в каждом экземпляре работает сервис на MQL5, который записывает состояние терминала и торгового счёта в общую базу SQLite, а веб-приложение читает из неё последние данные. Это позволило отказаться от последовательного подключения к терминалам через библиотеку <i>MetaTrader5</i> для Python, причём карточки на странице менеджера продолжили работать практически без изменений.</p> <p>Однако сами экземпляры мы пока подготавливали вручную. Создание нового терминала требуется не так уж часто: обычно после первоначальной настройки мы большую часть времени просто наблюдаем за уже работающими экземплярами. Поэтому автоматизация этой операции вряд ли даст заметную экономию усилий, но, поскольку терминалы предполагается размещать на удалённом сервере, было бы удобно и эту работу выполнять через веб-интерфейс, не подключаясь к серверу через удалённый рабочий стол. Особенно если в дальнейшем мы будем использовать несколько серверов и объединим управление всеми экземплярами в одном окне браузера.</p> <p>Поэтому в этой части займёмся созданием новых экземпляров терминала, то есть отдельных копий MetaTrader 5, каждая из которых размещается в своей папке и имеет собственные настройки и программы. Добавим страницу настроек, через которую можно передать необходимые файлы на сервер, научимся подготавливать папку для новой копии и запускать в ней
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>При первом запуске терминал сам создаёт недостающие каталоги и стандартные файлы. Параметр <i>/portable</i> здесь существенен. В таком режиме данные хранятся в самой папке установленной копии. Более подробно про этот режим можно почитать в <a href="https://www.metatrader5.com/ru/terminal/help/start_advanced/start" title="https://www.metatrader5.com/ru/terminal/help/start_advanced/start" target="_blank">справке по запуску платформы</a>.</p> <p>&nbsp;</p> <h3 id="broker_connection">Подключаемся к торговому серверу</h3> <p>С созданием папки мы разобрались, однако первый опыт с подключением к счёту другого брокера оказался менее успешным. Для проверки мы создали небольшой скрипт на Python, в котором разместили вызов функции <i>mt5.initialize()</i>, передав ей в качестве параметров путь к исполняемому файлу новой копии терминала, логин, пароль и имя торгового сервера. После запуска скрипта терминал открылся, начал первоначальную подготовку и показывал номер счёта в заголовке, но подключения не было. Вызов <i>mt5.initialize()</i> завершился с сообщением:</p> <div class="code-box"><pre class="code" data-enhanced="true">initialize() failed: (-10005, 'IPC timeout')
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Согласно <a href="/ru/docs/python_metatrader5/mt5lasterror_py" target="_blank">справочнику ошибок Python API</a>, это тайм-аут внутреннего взаимодействия с терминалом. Для ошибки авторизации предусмотрен другой код, поэтому по одному этому сообщению нельзя было заключить, что неверен пароль. Кроме того, при первом запуске терминал создаёт в папке данных стандартные примеры советников, индикаторов и других MQL5-программ, после чего автоматически запускается их компиляция. Эта подготовка занимает некоторое время и заметно нагружает компьютер, поэтому сначала было неясно, связана ли задержка подключения с компиляцией примеров или у неё есть другая причина.</p> <p>Терминал, который мы использовали, был взят с сайта MetaQuotes, и изначально в его списке был только сервер MetaQuotes. Поэтому во время ожидания мы открыли диалог создания счёта и выполнили поиск нужной брокерской компании, чтобы добавить сведения о её торговых серверах. Сам порядок поиска описан в <a href="https://www.metatrader5.com/ru/terminal/help/startworking/acc_open" title="https://www.metatrader5.com/ru/terminal/help/startworking/acc_open" target="_blank">справке по открытию счетов</a>. После того как компания появилась в списке, её серверы стали доступны для выбора при подключении к существующему счёту. Вскоре терминал подключился, и повторный вызов Python-скрипта завершился успешно, однако оставалось неясно, помог ли поиск брокера или просто прошло достаточно времени.</p> <p>Чтобы проверить, не было ли дело только во времени ожидания, мы повторили опыт с чистой копией терминала и на этот раз ждали длительное время, не выполняя поиск брокера. Однако самостоятельно найти нужный сервер и подключиться к нему терминал так и не смог. Поэтому в нашем случае предварительное заполнение списка серверов оказалось обязательным. В связи с этим возник вопрос: можно ли сделать это другим способом, чтобы не искать брокера вручную в каждом новом терминале?</p> <p>Оказалось, что сведения для подключения можно перенести в другую копию файлом <i>config/servers.dat</i>. В <a href="https://www.metatrader5.com/ru/terminal/help/start_advanced/structure" title="https://www.metatrader5.com/ru/terminal/help/start_advanced/structure" target="_blank">описании структуры каталогов</a> он как раз указан как файл нас
KeepPrivate=<span class="number">1</span>
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Затем повторили Python-авторизацию на новой чистой копии, штатно закрыли терминал и запустили его обычным способом. В этом варианте он опять показал окно ввода пароля, хотя настройка оставалась в файле. Значит, надеяться, что добавление <i>KeepPrivate=1</i> само по себе решит вопрос после первого подключения к торговому счёту через вызов Python API, не приходится.</p> <p>Тогда мы попробовали другой способ. Вместо Python-функции мы передали реквизиты самому терминалу через отдельный конфигурационный файл:</p> <div class="code-box"><pre class="code" data-enhanced="true">[Common]
Login=<span class="number">12345678</span>
Server=Broker-Demo
Password=example-password
KeepPrivate=<span class="number">1</span>
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Для этого понадобилось в команде запуска терминала добавить параметр <i>/config</i>, вместе с нашим обязательным <i>/portable</i>:</p> <div class="code-box"><pre class="code" data-enhanced="true">terminal64.exe /portable /config:C:\MT5\MetaTrader5.<span class="number">2</span>\first-login.ini
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Это штатный способ задания параметров, описанный в <a href="https://www.metatrader5.com/ru/terminal/help/start_advanced/start" title="https://www.metatrader5.com/ru/terminal/help/start_advanced/start" target="_blank">справке по запуску с собственной конфигурацией</a>. Во время работы такой файл используется в режиме чтения, то есть изменения из интерфейса не переписывают переданный INI. В командной строке при этом указывается только путь к файлу, а реквизиты находятся внутри него.</p> <p>В результате такого запуска терминал успешно подключился к счёту, затем мы штатно закрыли его, удалили временный INI и повторно запустили только с <i>/portable</i>. На этот раз пароль не потребовался, а журнал подтвердил новую авторизацию и синхронизацию счёта. Значит, такую последовательность действий и будем использовать в менеджере терминалов. Python-модуль <i>MetaTrader5</i>, с которого начиналась проверка, оказался пока не нужен.</p> <p>Обратим внимание на штатное закрытие между двумя запусками. При таком завершении терминал может сохранить настройки и корректно закончить работу, поэтому здесь оно предпочтительнее принудительной остановки через <i>terminate()</i>. Для этого добавим функцию <i>graceful_close()</i>, которая запрашивает закрытие терминала и ожидает завершения процесса. Если на этом этапе штатное закрытие не сработало, менеджер завершает процесс принудительно и сообщает об ошибке настройки счёта, поскольку сохранение параметров в таком случае не подтверждено.</p> <p>&nbsp;</p> <h3 id="source_files">Подготавливаем исходные файлы</h3> <p>Теперь вернёмся к пользователю за домашним компьютером. У него уже может быть установлено несколько терминалов MetaTrader 5 от разных брокерских компаний, с заранее подготовленными списками торговых серверов. Если нужный сервер уже есть в одном из таких терминалов, достаточно взять файл <i>servers.dat</i> из его папки данных, и выполнять поиск компании вообще не придётся. Если же нужного сервера в списке нет, найти брокера можно в локальном терминале, после чего выбрать подготовленный файл при создании экземпляра через браузер.</p> <p>Для получения обоих EXE укажем на сервере одну исходную установку MT5:</p> <div class="code-box"><pre class="code" data-enhanced="true">source-mt5/
&nbsp;&nbsp;&nbsp;&nbsp;terminal64.exe
&nbsp;&nbsp;&nbsp;&nbsp;MetaEditor64.exe
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Её расположение сохраним в параметре <i>terminal_template</i>. Это единственный источник исполняемых файлов: если они уже находятся в выбранной папке, дополнительных действий не потребуется. Если какого-то EXE нет, в настройках появится возможность загрузить его с компьютера пользователя прямо в эту папку. Так можно подготовить исходные файлы и на сервере, где ещё не было установлено ни одного терминала.</p> <p>А вот общий <i>servers.dat</i> хранить не будем. При создании экземпляра пользователь при необходимости выберет файл из своего локального терминала, и менеджер запишет его в <i>config/servers.dat</i> только новой копии. Для разных брокеров можно использовать разные файлы; существующие экземпляры при этом не затрагиваются. Выбранный файл должен содержать сведения о торговом сервере, к которому собираемся подключиться. Если нужные серверы уже доступны терминалу, дополнительная загрузка не требуется; можно также создать экземпляр без реквизитов и настроить подключение позднее.</p> <p>Скомпилированный <i>ServiceStateWriter.ex5</i> менеджер подготовит из исходного кода текущего проекта. Для этого в <i>deploy_service.py</i> выделим функцию <i>service_build_paths()</i>, которая определяет пути к MetaEditor, исходнику и результату сборки, и функцию <i>compile_service()</i>, выполняющую компиляцию. Компилятор берём из той же исходной установки. При размещении проекта внутри <i>MQL5/Shared Projects</i> результат, как и прежде, попадёт в каталог сервисов терминала, а в отдельной папке проекта EX5 будет создан рядом с исходником.</p> <p>Если EX5 уже не старее исходника, повторная компиляция не потребуется. Полученный файл скопируем в новый терминал, поэтому клонировать туда репозиторий и компилировать один и тот же исходник заново не придётся. Если MetaEditor не смог скомпилировать сервис, создание остановится с сообщением об ошибке, а причину можно будет посмотреть в журнале компиляции.</p> <p>Загрузку недостающих EXE реализуем через маршрут <i>PUT /api/files/{kind}</i>. По значению <i>kind</i> менеджер определяет имя файла, а тело запроса читает порциями во временный файл. Загруженный EXE сохраняется в выбранную исходную установку; уже существующий файл таким
&nbsp;&nbsp;<span class="string">"terminal_template"</span>: <span class="string">"terminal-template"</span>,
&nbsp;&nbsp;<span class="string">"startup_timeout_sec"</span>: <span class="number">180</span>,
&nbsp;&nbsp;<span class="string">"disk_warning_mb"</span>: <span class="number">1024</span>
}
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Относительные пути считаем от папки конфигурационного файла. Запас места задаётся в МиБ, а время ожидания — в секундах. Значение 1024 МиБ здесь служит изменяемым порогом предупреждения, а не оценкой того, сколько обязательно требуется каждому терминалу.</p> <p>Чтобы не оставить обрезанный JSON при неудачной записи, мы сначала полностью записываем новый документ во временный файл рядом с исходным, сбрасываем буферы и только затем заменяем основной файл:</p> <div class="code-box"><pre class="code" data-enhanced="true">out.flush()
os.fsync(out.fileno())
os.replace(temporary, self.path)
self.data = validated
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Рабочая конфигурация в памяти обновляется после успешной замены. Переименования файла недостаточно для общей транзакции со всеми папками и процессами, но оно защищает нас от частично записанного JSON. Для более длинной операции создания экземпляра понадобится отдельный откат.</p> <p>Отдельный путь для каждого экземпляра сохранять не будем: поскольку все терминалы находятся внутри одной базовой папки, достаточно соединить <i>mt5_folder</i> с именем экземпляра, которое служит ключом в словаре <i>terminals</i>. Например, при <i>mt5_folder</i>, равном <i>C:\MT5</i>, экземпляр <i>MetaTrader5.2</i> всегда будем искать в <i>C:\MT5\MetaTrader5.2</i>. При изменении базовой папки через интерфейс сначала выполним подтверждённый перенос экземпляров и лишь после его завершения запишем новое расположение в конфигурацию. Если пользователь отменит перенос, прежние настройки сохранятся. Старое поле <i>path</i>, если оно осталось в конфигурации, при чтении игнорируем, а при следующем сохранении убираем.</p> <p>Пароль, который пользователь задаёт менеджеру, тоже хранится в <i>config.json</i>, как мы и договорились для текущей реализации. При этом выдавать его обратно браузеру для заполнения формы не требуется. В ответе настроек заменяем его признаком наличия:</p> <div class="code-box"><pre class="code" data-enhanced="true"><span class="keyword">for</span> record <span class="keyword">in</span> data[<span class="string">'terminals'</span>].values():
&nbsp;&nbsp;&nbsp;&nbsp;record[<span class="string">'password_set'</span>] = <span class="built_in">bool</span>(record.pop(<span class="string">'password'</span>, <span class="string">''</span>))
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>В словари карточек пароли вообще не включаются. Это стало особенно существенным после появления записи настроек: прежний отладочный вывод полного словаря экземпляров мы убрали.</p> <p>&nbsp;</p> <h3 id="create_instance">Создаём экземпляр</h3> <p>Чтение состояния и поиск процессов остаются в <i>MT5_Control</i>, а последовательность подготовки вынесем в новый класс <i>TerminalManager</i>. Ему нужны конфигурация, работа с файлами и возможность дождаться результата запуска, поэтому помещать всю эту работу в обработчик HTTP-запроса было бы неудобно.</p> <p>Начнём с имени. Предложим первое свободное значение вида <i>MetaTrader5.5</i>, но оставим пользователю возможность его изменить. Имя экземпляра является именем папки и ключом связи с сервисом, поэтому допускаем только один корректный компонент пути Windows. Учтём также, что Windows не различает регистр: занятая папка <i>MetaTrader5.5</i> должна мешать созданию <i>metatrader5.5</i>.</p> <p>После проверки исходных файлов и свободного места создадим папку. Здесь намеренно не используем <i>exist_ok=True</i>: новая операция должна владеть именно созданной ею папкой, иначе при последующем откате можно было бы удалить чужие данные.</p> <div class="code-box"><pre class="code" data-enhanced="true">target.mkdir()
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Копируем оба EXE из исходной установки и добавляем сервис. Если пользователь выбрал <i>servers.dat</i>, сохраняем его в папке <i>config</i> новой копии; без загрузки этого файла данный шаг пропускаем. Для сервиса сохраняем путь из четвёртой части:</p> <div class="code-box"><pre class="code" data-enhanced="true">MQL5/Services/Shared Projects/mt5-manager/ServiceStateWriter.ex5
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Затем создаём <i>config/services.ini</i> по нашему образцу, подставляя имя базы из настройки <i>database</i>. Как и прежде, MQL5-сервис открывает файл в общей папке <i>Common/Files</i>, поэтому путь, который использует Python, должен указывать на этот же файл того же пользователя Windows. Сам механизм запуска через <i>services.ini</i> подробно разобран в прошлой статье.</p> <p>Когда подготовка завершена, добавляем экземпляр в конфигурацию и обновляем объект управления в памяти:</p> <div class="code-box"><pre class="code" data-enhanced="true">updated[<span class="string">'terminals'</span>][folder] = {
&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">'name'</span>: record.get(<span class="string">'name'</span>) <span class="keyword">or</span> folder,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">'login'</span>: login,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">'server'</span>: server,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">'password'</span>: password,
}</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Теперь можно запускать процесс, однако успешный <i>Popen()</i> ещё не означает, что всё готово. Дождёмся свежей записи сервиса, причём проверим не только время, но и путь терминала. Это важно при повторном использовании имени: в базе могли остаться сведения о ранее удалённой копии. Если требуется подключение к конкретному счёту, дополнительно проверим флаг соединения, логин и сервер.</p> <p>При неудаче нового создания завершаем принадлежащие этому экземпляру процессы, удаляем созданную папку и убираем запись из конфигурации. Если сам откат не удался, сообщаем об этом отдельно. Удаление применяется именно к новой папке, созданной текущей операцией; ошибка смены счёта у существующего экземпляра такого отката не вызывает.</p> <p>&nbsp;</p> <h3 id="account_login">Выбираем счёт при запуске</h3> <p>Пустые реквизиты означают, что менеджер не задаёт счёт. Тогда запустим терминал с <i>/portable</i>: он использует собственные сохранённые настройки либо работает без авторизации. Для создания экземпляра в таком режиме достаточно появления свежих данных сервиса, а отсутствие подключения к брокеру отображается отдельно.</p> <p>Если счёт указан, номер сравниваем вместе с именем сервера. Один и тот же номер может встречаться у разных брокеров, поэтому совпадения только <i>login</i> недостаточно.</p> <p>Перед подключением подготовим и основную конфигурацию экземпляра: в секции <i>[Common]</i> файла <i>config/common.ini</i> сохраним <i>Login</i>, <i>Server</i> и <i>KeepPrivate=1</i>. Это делает функция <i>prepare_account_defaults()</i>. Она сохраняет остальные настройки и не записывает туда пароль: он будет передан отдельным временным файлом. Так мы явно задаём выбор счёта и для последующего запуска без <i>/config</i>, не рассчитывая, что все параметры временной конфигурации перенесутся в основную сами.</p> <p>Полные реквизиты позволяют выполнить первую настройку через временный INI. В <i>start()</i> соответствующий фрагмент выглядит так:</p> <div class="code-box"><pre class="code" data-enhanced="true"><span class="keyword">with</span> tempfile.NamedTemporaryFile(mode=<span class="string">'w'</span>, encoding=<span class="string">'utf-16'</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="built_in">dir</span>=directory, prefix=<span class="string">'manager-login-'</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; suffix=<span class="string">'.ini'</span>, delete=<span class="literal">False</span>) <span class="keyword">as</span> out:
&nbsp;&nbsp;&nbsp;&nbsp;temporary = Path(out.name)
&nbsp;&nbsp;&nbsp;&nbsp;out.write(<span class="string">f'[Common]\nLogin=<span class="subst">{account[<span class="number">0</span>]}</span>\nServer=<span class="subst">{account[<span class="number">1</span>]}</span>\n'</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">f'Password=<span class="subst">{account[<span class="number">2</span>]}</span>\nKeepPrivate=1\n'</span>)
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Сам запуск выполняет небольшой метод, которому достаточно знать имя экземпляра и необязательный путь к INI:</p> <div class="code-box"><pre class="code" data-enhanced="true"><span class="keyword">def</span> launch(self, folder, config=<span class="literal">None</span>):
&nbsp;&nbsp;&nbsp;&nbsp;args = [self.control.instance_path(folder), <span class="string">'/portable'</span>]
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> config:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># Кавычки окружают значение ключа, а не весь аргумент.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;command = subprocess.list2cmdline(args) + <span class="string">f' /config:"<span class="subst">{config}</span>"'</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> subprocess.Popen(command, cwd=<span class="built_in">str</span>(Path(args[<span class="number">0</span>]).parent))
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> subprocess.Popen(args, cwd=<span class="built_in">str</span>(Path(args[<span class="number">0</span>]).parent))
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>При проверке в папке с пробелами обнаружилась ещё одна особенность: передача <i>/config:путь</i> обычным элементом списка аргументов приводила к кавычкам вокруг всего ключа, и терминал сообщал, что не может загрузить INI. Поэтому для этого запуска формируем строку с кавычками именно вокруг значения: <i>/config:"C:\папка с пробелами\first-login.ini"</i>. Оболочку командной строки при этом не вызываем: <i>Popen()</i> по-прежнему запускает сам терминал.</p> <p>После подтверждения счёта штатно закрываем терминал, удаляем временный файл и повторяем запуск без <i>/config</i>. Ещё раз ждём подключение к тому же счёту. Только теперь считаем настройку завершённой.</p> <p>Есть небольшая особенность с изменением пароля. Сервис сообщает логин и сервер, но по ним нельзя определить, соответствует ли сохранённый пароль тому, который только что ввёл пользователь. Поэтому в памяти менеджера запоминаем успешно применённый набор реквизитов. Если пароль изменился, повторяем настройку, даже когда номер счёта остался прежним. После перезапуска самого менеджера полные реквизиты также один раз применяются заново: подтверждение предыдущего процесса мы в памяти уже не найдём.</p> <p>Старые конфигурации могут содержать логин и сервер без пароля. Если терминал самостоятельно подключился к нужному счёту, этого достаточно. Если же нужна перенастройка, попросим указать пароль. Для нового экземпляра с заданным счётом требуем полный набор реквизитов сразу, поскольку использовать сохранённый пароль в ещё не созданной копии неоткуда.</p> <p>Для существующего экземпляра настройка счёта выполняется в его прежней папке. Его историю и программы сохраняем, а при ошибке показываем результат пользователю. Автоматически перезапускать терминал до бесконечности не будем: неверные реквизиты от этого вряд ли станут правильными.</p> <p>&nbsp;</p> <h3 id="web_interface">Добавляем веб-интерфейс</h3> <p>На странице «Настройки» появились общие параметры размещения терминалов (рис. 1). Здесь зададим две папки: базовую для всех управляемых экземпляров и папку исходной установки MT5, откуда будем брать оба EXE. Эти папки могут находиться в ра<D180>
python run.py
</pre><button type="button" class="copy-code-btn c-b8amxo" title="Скопировать в буфер обмена"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4.5H9
C9.39782 4.5 9.77936 4.65804 10.0607 4.93934
C10.342 5.22064 10.5 5.60218 10.5 6V14
C10.5 14.3978 10.342 14.7794 10.0607 15.0607
C9.77936 15.342 9.39782 15.5 9 15.5H4
C3.60218 15.5 3.22064 15.342 2.93934 15.0607
C2.65804 14.7794 2.5 14.3978 2.5 14V6
C2.5 5.60218 2.65804 5.22064 2.93934 4.93934
C3.22064 4.65804 3.60218 4.5 4 4.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.5H11L14.5 5V11
C14.5 11.3978 14.342 11.7794 14.0607 12.0607
C13.7794 12.342 13.3978 12.5 13 12.5H7
C6.60218 12.5 6.22064 12.342 5.93934 12.0607
C5.65804 11.7794 5.5 11.3978 5.5 11V3
C5.5 2.60218 5.65804 2.22064 5.93934 1.93934
C6.22064 1.65804 6.60218 1.5 7 1.5Z" fill="#fff" stroke="#A1A1A1"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M11 1.5V3.5C11 3.89782 11.158 4.27936 11.4393 4.56066C11.7206 4.84196 12.1022 5 12.5 5H14.5L11 1.5Z" fill="#fff" stroke="#A1A1A1" stroke-linejoin="bevel"></path></svg><!----></button></div> <p>Модуль <i>MetaTrader5</i> остаётся полезен для отдельных экспериментов, но рабочий сценарий этой части от него уже не зависит: состояние поступает от сервиса, а параметры первого подключения передаются самому терминалу.</p> <p>Сначала укажем папку исходной установки и проверим наличие обоих EXE. Затем в форме создания выберем <i>servers.dat</i> и создадим экземпляр без счёта. В конфигурации должна появиться запись с именем экземпляра и пустыми строками для реквизитов, в базовой папке — новая подпапка с файлами терминала, сервисом и выбранным списком серверов, а на главной странице — новая карточка. После первой записи сервиса менеджер видит работающий процесс, хотя соединения с торговым сервером может ещё не быть.</p> <p>Затем создадим экземпляр с существующим счётом. В ходе операции должны последовательно пройти первое подключение, штатное закрытие и повторный запуск без файла авторизации. После завершения в папке экземпляра не должно остаться файлов <i>manager-login-*.ini</i>, а данные сервиса должны подтверждать нужный логин и сервер.</p> <p>Отдельно проверим остановку и запуск уже созданной копии, а затем изменение её настроек. Сохранение имени или реквизитов не должно запускать терминал или переподключать его к счёту. Явную проверку подключения выполним и для работавшего, и для остановленного экземпляра, проверяя в обоих случаях восстановление прежнего состояния процесса после успеха и ошибки. Папку работающего терминала переименовывать нельзя, а изменение отображаемого имени от его состояния не зависит.</p> <p>Ошибка авторизации у существующего экземпляра должна оставлять его папку на месте. Ошибка при создании новой копии, напротив, должна приводить к удалению её папки и записи конфигурации. Занятое имя нужно отклонять до копирования, сохраняя прежнее содержимое каталога. Удаление уже добавленного экземпляра проверим отдельно: отмена подтверждения ничего не меняет, работающий терминал удалить нельзя, а успешное удаление остановленного экземпляра убирает только его папку и запись из конфигурации.</p> <p>Эти случаи включены в автоматические проверки