Моделирование рынка (Часть 13): Сокеты (VII)
Введение
В предыдущей статье, "Моделирование рынка (Часть 12): Сокеты (VI)", мы создали сервер Python, способный отвечать нескольким клиентам. Он работал благодаря использованию потоков. Я думаю, что основная идея функционирования потоков ясна. Но прежде всего, я надеюсь, что вам удалось понять, как серверу удается в некотором роде заставить работать небольшой чат. Цель данных пояснений к сокетами не в том, чтобы рассказать о них всё. Мы хотим, чтобы вы поняли, как работают сокеты. Эти знания понадобятся позже в системе репликации/моделирования.
Вернемся к проблеме в Excel
Однако, хотя сервер Python работает так, что его могут использовать несколько клиентов, он не предназначен для использования в Excel. То есть, если мы используем xlwings для интеграции этого сервера в Excel, у вас возникнут проблемы с взаимодействием с Excel. Но зачем, если мы используем потоки, чтобы не блокировать выполнение кода?
Это несколько сложный вопрос. По крайней мере, для тех, кто не очень хорошо разбирается в том, как программа использует другую программу, которая не является её частью. Возможно, это предложение было немного запутанным, но давайте постараемся его понять. Когда мы используем скрипт VBA внутри Excel, мы фактически запускаем программу. Если этот скрипт вызывает другое приложение, Excel обычно ждет, пока оно завершит работу. Но это не всегда так. Чтобы Excel не приходилось ждать завершения работы приложения, можно использовать приложение, которое не связано с Excel. То есть у нас будет открыт Excel и одновременно другое приложение для работы с VBA. Оба могут сосуществовать без конкуренции за процессор. Это примерно то же самое, что попросить Excel открыть Word. Неважно, если Word зависнет, это не помешает работе Excel.
Однако это не относится к тому, что предлагается, а именно - сделать так, чтобы MetaTrader 5 и Excel обменивались данными. Если MetaTrader 5 и Excel не конкурируют за процессор, то скрипт Python, который создает сокет для этого обмена данными, - нет. Это связано с моделями серверов, которые показывали до этого момента. Чтобы сделать объяснение проще, возьмем последний скрипт, рассмотренный в предыдущей статье. Его можно увидеть ниже:
01. import socket as sock 02. import threading as thread 03. 04. CONN_LIST = [] 05. 06. def NewClientHandler(conn, addr): 07. global CONN_LIST 08. CONN_LIST.append(conn) 09. print(f"Client [%s:%s] is online..." % addr) 10. while True: 11. msg = conn.recv(512).decode().rstrip(None) 12. if msg: 13. print(f"Message from [%s:%s]: {msg}" % addr) 14. for slave in CONN_LIST: 15. if slave != conn: 16. slave.send(f"[{addr[0]}]: {msg}\n\r".encode()) 17. if msg.lower() == "/see you later": 18. break 19. print(f"Client [%s:%s] is disconnecting..." % addr) 20. conn.close() 21. CONN_LIST.remove(conn) 22. 23. server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 24. server.bind(('0.0.0.0', 27015)) 25. server.listen() 26. print("Waiting connections...") 27. while True: 28. conn, addr = server.accept() 29. conn.send("Wellcome to Server.\n\r".encode()) 30. thread.Thread(target=NewClientHandler, args=(conn, addr)).start()
Код Python
Хотя данный скрипт нельзя использовать непосредственно в xlwings для создания сервера, мы можем внести некоторые небольшие изменения, и он будет пригоден для использования. Код, который показываем выше, превратится в этот код:
01. import socket as sock 02. import threading as thread 03. 04. CONN_LIST = [] 05. 06. def NewClientHandler(conn, addr): 07. global CONN_LIST 08. CONN_LIST.append(conn) 09. print(f"Client [%s:%s] is online..." % addr) 10. while True: 11. msg = conn.recv(512).decode().rstrip(None) 12. if msg: 13. print(f"Message from [%s:%s]: {msg}" % addr) 14. for slave in CONN_LIST: 15. if slave != conn: 16. slave.send(f"[{addr[0]}]: {msg}\n\r".encode()) 17. if msg.lower() == "good bye": 18. break 19. print(f"Client [%s:%s] is disconnecting..." % addr) 20. conn.close() 21. CONN_LIST.remove(conn) 22. 23. def InitServer(HOST, PORT): 24. server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 25. server.bind((HOST, PORT)) 26. server.listen() 27. print("Waiting connections...") 28. while True: 29. conn, addr = server.accept() 30. conn.send("Wellcome to Server.\n\r".encode()) 31. thread.Thread(target=NewClientHandler, args=(conn, addr)).start() 32. 33. if __name__ == '__main__': 34. InitServer('localhost', 27015)
Код Python
Хорошо. Как видите, мы добавили совсем немного кода, но это позволяет нам использовать скрипт непосредственно в Python или запускать его из VBA из Excel с помощью xlwings. В VBA можно использовать следующую команду для активации сервера из Excel:
Public Sub ExecuteServer()
RunPython ("import Server_Thread; Server_Thread.InitServer('127.0.0.1', 27014)")
End Sub
Код VBA
Посмотрим, что из этого выйдет. Это для тех, кто не придерживался данной последовательности или не знает, как работать в VBA. Этот небольшой скрипт VBA должен вызываться каким-либо элементом управления, который мы должны добавить в Excel. На самом деле это очень простая задача. Мы добавляем элемент управления, например, кнопку и в свойствах этой кнопки указываем, что она должна выполнять скрипт, показанный выше.
Но что делает данный скрипт в VBA? Он указывает xlwings запустить скрипт Python, который мы видели ранее. Прошу заметить, что в скрипте на Python можно указать порт и хост, на котором будет находиться сервер. Тогда аргументы, которые мы видим в Server_Thread.InitServer, указывают именно на хост, а точнее, на адрес, по которому сервер ожидает соединений, а также порт. Обратите внимание, что мы определяем порт, который отличается от того, который используется в скрипте на Python; это сделано для того, чтобы было понятно, что сервер будет определяться и вызываться с помощью Excel.
Если вы правильно выполнили все шаги, то заметите, что Excel открывает Python и сервер становится доступным. Таким образом, он будет работать так же, как если бы мы запускали его непосредственно в Python. Но есть один важный момент: Excel будет вести себя странно, и выполнять действия в нем будет сложно. Это связано с тем, что сервер Python будет конкурировать с Excel за использование процессора. «Но почему это происходит, неужели наш сервер не использует потоки?» Да, сервер использует потоки, но не в масштабах всего сервера; есть часть кода, которая блокируется. Именно эта часть находится в строке 29 или 28 из оригинального кода. Если посмотреть на него, то можно увидеть, что в этой строке находится вызов функции accept. Именно данная функция и блокирует и именно она конкурирует с Excel за использование процессора.
А есть ли способ решить эту проблему? Да, есть. Важным моментом является то, сколько мы собираемся вложить в решение. Я говорю так, потому что вы можете создать поток внутри VBA, чтобы скрипт Python выполнялся непосредственно в потоке. Или вы можете поискать другое решение этой же проблемы.
В любом случае, нужно что-то сделать, чтобы функция accept не продолжала конкурировать с Excel за использование процессора.
Начнем использовать функцию SELECT
Одно из самых простых решений, не требующее особых программных манипуляций, - это использование функции select в коде сервера. Данная функция уже встречалась в другом коде, здесь, в этой части о сокетах. Можно проверить это, посмотрев на строку 69 кода сервера мини-чата, выполненного на C++. Этот код можно найти в статье: Моделирование рынка (Часть 09): Сокеты (III)
Как видите, это не такое уж сложное решение. Однако, если вы хотите сделать нечто подобное на Python, код будет несколько отличаться от C++. Если использовать код, идентичный коду C++, то программа аварийно завершит работу при вызове select вместо accept. Чтобы было понятнее, давайте посмотрим, как выполняется серверный код на Python. Это можно увидеть в скрипте ниже:
01. import socket as sock 02. import threading as thread 03. import select 04. import xlwings as xl 05. import time 06. 07. CONN_LIST = [] 08. INPUTS = [] 09. WB = None 10. LINE = 1 11. 12. def WriteMessageInExcel(msg): 13. global WB 14. global LINE 15. if WB == None: 16. WB = xl.Book.caller() 17. WB.sheets[0].cells(LINE, 1).value = msg 18. LINE = LINE + 1 19. 20. def NewClientHandler(conn, addr): 21. global CONN_LIST 22. CONN_LIST.append(conn) 23. print(f"Client [%s:%s] is online..." % addr) 24. while True: 25. msg = conn.recv(512).decode().rstrip(None) 26. if msg: 27. print(f"Message from [%s:%s]: {msg}" % addr) 28. for slave in CONN_LIST: 29. if slave != conn: 30. slave.send(f"[{addr[0]}]: {msg}\n\r".encode()) 31. if msg.lower() == "good bye": 32. break 33. print(f"Client [%s:%s] is disconnecting..." % addr) 34. conn.close() 35. CONN_LIST.remove(conn) 36. 37. def InitServer(HOST, PORT): 38. global INPUTS 39. server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 40. server.bind((HOST, PORT)) 41. server.listen() 42. INPUTS.append(server) 43. WriteMessageInExcel("Waiting connections...") 44. while True: 45. read, write, err = select.select(INPUTS, [], [], 1) 46. for slave in read: 47. if slave is server: 48. conn, addr = slave.accept() 49. conn.send("Wellcome to Server in Python.\n\r".encode()) 50. thread.Thread(target=NewClientHandler, args=(conn, addr)).start() 51. WriteMessageInExcel(f"Client [%s:%s] is online..." % addr) 52. WriteMessageInExcel("Ping...") 53. time.sleep(1) 54. 55. if __name__ == '__main__': 56. InitServer('localhost', 27015)
Код Python
Прошу заметить, что были внесены лишь незначительные изменения. Мы это делаем шаг за шагом, чтобы вы могли понять, что именно изменяется. Обратите внимание, что теперь у нас есть несколько дополнительных элементов между строками 08 и 09. Они служат поддержкой для select и Excel через xlwings.
Чтобы облегчить написание кода, отладку и отображение информации, в строке 12 мы разместили небольшую процедуру. Её назначение - вводить данные в Excel. Прошу заметить, что в строке 15 мы проверяем, была ли инициализирована переменная; поскольку при первом вызове она не будет инициализирована, строка 16 будет выполнена, но только один раз. Затем в строке 17 выводится сообщение на текущую строку листа, а в строке 18 увеличивается, чтобы отобразить следующий вызов на следующей строке.
Я знаю, что этот код не очень привлекателен, но эстетика здесь не главное, главное - функциональность. Самое главное - чтобы это работало, а не чтобы было красиво или приятно использовать из VBA.
Теперь я хочу обратить ваше внимание на строку 45 данного скрипта. Прошу заметить, что здесь мы делаем вызов select. В отличие от кода на C++, о котором говорилось ранее, здесь у нас есть дополнительный параметр. Данный параметр имеет точное значение равное единице. Он указывает, как долго select будет ждать, пока что-то произойдет. Это время выражается в секундах, поэтому select подождет одну секунду, прежде чем продолжить. Можно использовать меньшее значение, и установить число с плавающей точкой. Но если вы не знаете, как это сделать, или сомневаетесь, не волнуйтесь, потому что мы объясним вам это позже. Однако важно отметить следующее: если мы не укажем это значение, select будет блокироваться до тех пор, пока на сокете сервера не произойдет какое-либо событие.
Чтобы лучше понять это, обратите внимание, что в строке 52 мы печатаем сообщение в Excel, а в строке 53 ждем еще одну секунду, создавая таким образом двухсекундную задержку в цикле. Для наших целей, этого более чем достаточно. Вот что произойдет: если строка 45 блокирует выполнение в ожидании события на сокете, пинг не будет отправляться каждые две секунды; если такой блокировки нет, пинг будет отправлен. При запуске этого скрипта с помощью VBA мы получим следующее:

Хорошо, но зачем нам нужен цикл for в строке 46? Он нужен для того, чтобы проверить, какое событие произошло в сокете, учитывая, что мы ещё не вошли в поток. Таким образом, если пользователь попытается подключиться, данный цикл будет использован для выяснения того, что произошло. Таким образом, всё остальное остается прежним. Один важный момент: без этого цикла for сервер зависнет в ожидании пока что-то произойдет в функции accept, которая находится на строке 48. Но с помощью проверки в строке 47 внутри цикла мы можем обнаружить, что клиент пытается подключиться. Хотя этот сервер ещё не позволяет запускать Excel без конкуренции с Python, мы уже значительно улучшили систему.
Но проблема возникает в строке 44. Тот факт, что в этой строке мы входим в бесконечный цикл скрипта на Python, заставляет скрипт конкурировать с Excel за использование процессора. Однако мы не можем удалить данный цикл, потому что, если мы сделаем это так, как реализовано в скрипте, сервер выключится. Поэтому мы должны внести некоторые изменения, чтобы избежать этого сценария. То есть нам нужно входить и выходить из скрипта без того, чтобы сервер закрывал свои соединения. Но перед этим давайте посмотрим на другой промежуточный код, который нам нужно создать. Чтобы немного разбавить обстановку, давайте перейдем к новой теме.
Сервер мини-чата в классе
Хотя многие не любят разрабатывать или внедрять объектно-ориентированный код, в нем есть явные преимущества. Прежде всего, можно сделать сервер, реализованный на Python, более простым в эксплуатации, по крайней мере, с точки зрения программирования. Это связано с тем, что мы можем очень легко удалить поток из версии, которая была показана в предыдущей теме. Таким образом, мы добьемся функционирования, которое очень похоже на реализацию в C++. Для этого создадим новый код, но он будет полностью совместим с тем, что мы видели до этого момента. Конечно, на этом раннем этапе мы не будем запускать его из Excel. Мы будем использовать Python в чистом виде. Новый скрипт, использующий ООП, показан ниже:
01. import socket as sock 02. import select 03. import time 04. 05. class MiniChat: 06. def __init__(self, HOST : str = 'localhost', PORT : int = 27015) -> None: 07. self.server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 08. self.server.setblocking(0) 09. self.server.bind((HOST, PORT)) 10. self.server.listen() 11. self.CONN_LIST = [self.server] 12. print(f"Waiting connections in {PORT}...") 13. 14. def BroadCastData(self, conn, info) -> None: 15. for sock in self.CONN_LIST: 16. if sock != self.server and sock != conn: 17. sock.send((info + "\n\r").encode()) 18. 19. def ClientDisconnect(self, conn) -> None: 20. msg = "Client " + str(conn.getpeername()) + " is disconnected..." 21. self.BroadCastData(conn, msg) 22. print(msg) 23. conn.close() 24. self.CONN_LIST.remove(conn) 25. 26. def Shutdown(self) -> None: 27. while self.CONN_LIST: 28. for conn in self.CONN_LIST: 29. self.CONN_LIST.remove(conn) 30. 31. def LoopMessage(self) -> None: 32. while self.CONN_LIST: 33. read, write, err = select.select(self.CONN_LIST, [], [], 1) 34. for sock in read: 35. if sock is self.server: 36. conn, addr = sock.accept() 37. conn.setblocking(0) 38. self.CONN_LIST.append(conn) 39. conn.send("Wellcome to Server Chat in Python.\n\r".encode()) 40. self.BroadCastData(conn, "[%s:%s] entered room." % addr) 41. print("Client [%s:%s] connecting..." % addr) 42. else: 43. try: 44. data = sock.recv(512).decode().rstrip(None) 45. if data: 46. if data.lower() == '/shutdown': 47. self.Shutdown() 48. self.BroadCastData(sock, str(sock.getpeername()) + f":{data}") 49. else: 50. self.ClientDisconnect(sock) 51. except: 52. self.ClientDisconnect(sock) 53. time.sleep(0.5) 54. 55. if __name__ == '__main__': 56. MyChat = MiniChat() 57. MyChat.LoopMessage() 58. print("Server Shutdown...")
Код Python
Если у вас возникли вопросы по этому коду, вы можете обратиться на документацию на Python. Но я верю, что вы не будете испытывать особых трудностей в понимании скрипта. Хотя на первый взгляд это может показаться странным, всё дело в том, как реализован Python. Обратитесь к документации, чтобы понять, как всё работает. Но давайте сделаем небольшой обзор, для большей ясности.
Класс начинается со строки 05 и заканчивается строкой 53. В строке 56 мы используем конструктор класса, то есть процедуру __init__, расположенную в строке 06. Данная процедура фактически создаст сервер, хотя он ещё не будет прослушивать соединения. Однако, когда init завершается, вызов происходит в строке 57. Он направит выполнение на строку 31. Прошу заметить, что в этой процедуре в строке 31 мы удалили поток. То есть теперь сервер будет использовать тот же тип управления, что и в сервере C++. Обратите внимание на то, что мы всё ещё находимся в цикле в строке 32. Однако я хочу, чтобы вы поняли следующее: этот цикл может находиться как внутри класса, так и вне его, и сервер всё равно сможет выполнить свою работу.
Почему это так важно для нас? Причина в том, что если мы поместим цикл за пределы класса, нам понадобится способ проверить, может ли сервер быть активным. С циклом внутри класса для создания сервера нам понадобятся только строки 56 и 57. Это позволяет нам упаковать данный сервер для импорта в другие скрипты Python.
Теперь самое интересное: Если не обращать внимания на то, что у нас есть цикл в строке 32, этот же сервер можно упаковать и включить в Excel, чтобы xlwings мог использовать его не так, как было показано до настоящего момента. Однако такая реализация была выполнена так намеренно. Таким образом, мы сможем легко понять изменения между версиями сервера Python. Таким образом, мы можем легко увидеть, как мы можем остановить сервер Python, конкурирующий с Excel за использование процессора. Теперь, предполагая, что вы действительно поняли код, мы изменим его. Так что его можно уже легко упаковать и использовать в любом скрипте.
01. import socket as sock 02. import select as sel 03. 04. class MiniChat: 05. def __init__(self, HOST : str = 'localhost', PORT : int = 27015) -> None: 06. self.server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 07. self.server.bind((HOST, PORT)) 08. self.server.listen() 09. self.CONN_LIST = [self.server] 10. print(f"Waiting connections in {PORT}...") 11. 12. def __BroadCastData(self, conn, info) -> None: 13. for sock in self.CONN_LIST: 14. if sock != self.server and sock != conn: 15. sock.send((info + "\n\r").encode()) 16. 17. def __ClientDisconnect(self, conn) -> None: 18. msg = "Client " + str(conn.getpeername()) + " is disconnected..." 19. self.BroadCastData(conn, msg) 20. print(msg) 21. conn.close() 22. self.CONN_LIST.remove(conn) 23. 24. def ExistConnection(self) -> bool: 25. if self.CONN_LIST: 26. return True 27. return False 28. 29. def CheckMessage(self, sleep) -> str: 30. read, write, err = sel.select(self.CONN_LIST, [], [], sleep) 31. for sock in read: 32. if sock is self.server: 33. conn, addr = sock.accept() 34. self.CONN_LIST.append(conn) 35. conn.send("Wellcome to Server Chat in Python.\n\r".encode()) 36. self.__BroadCastData(conn, "[%s:%s] entered room." % addr) 37. print("Client [%s:%s] connecting..." % addr) 38. else: 39. try: 40. data = sock.recv(512).decode().rstrip(None) 41. if data: 42. if data.lower() == '/shutdown': 43. self.CONN_LIST.clear() 44. self.__BroadCastData(sock, str(sock.getpeername()) + f":{data}") 45. return str(sock.getpeername()) + f"> {data}" 46. else: 47. self.__ClientDisconnect(sock) 48. except: 49. self.__ClientDisconnect(sock) 50. return "" 51. 52. if __name__ == '__main__': 53. MyChat = MiniChat() 54. while MyChat.ExistConnection(0.2): 55. info = MyChat.CheckMessage() 56. if info: 57. print(f"{info}") 58. print("Server Shutdown...")
Код Python
Хорошо. Хорошо. В скрипте, показанном выше, мы внесли некоторые улучшения в код, чтобы он мог делать то, о чем говорилось ранее, то есть его можно было бы упаковать и использовать в других приложениях. Но давайте посмотрим, что произойдет теперь, ведь данный код больше не будет находиться в ожидании событий в сети.
Во-первых, процедуры BroadCastData и ClientDisconnect являются приватными в классе, то есть к ним нельзя получить доступ непосредственно из тела класса. Хотя существуют способы получить доступ к этим приватным процедурам, я не буду объяснять, как это сделать, чтобы у вас не возникло соблазна сделать это в других случаях. Прошу заметить, что в Python можно определять приватные элементы и не пытаться получить доступ к процедурам или функциям, которые считаются приватными. Если мы попробуем это сделать, Python сообщит об ошибке. Опять же, в Python есть способ получить доступ к приватным (частным) процедурам, но я оставлю это на ваше усмотрение. Однако я вам настоятельно не рекомендую использовать эту практику, поскольку она нарушает одну из предпосылок инкапсуляции кода. Если процедура или функция является приватной, на это есть причина.
Остаются только две публичные процедуры: ExistConnection и CheckMessage. ExistConnection возвращает булево значение, как видно из строки 24. Это возвращаемое значение предназначено для того, чтобы вне тела класса мы могли проверить, активен ли сервер или нет, т.е. есть ли активное соединение.
Процедура CheckMessage, расположенная в строке 29, возвращает сообщение, отправленное каким-то клиентом. Но не только это. Прошу заметить, что в строке 43 сервер выключается по запросу клиента. Это может показаться немного радикальным, так как в принципе любой клиент не должен иметь возможности сделать что-то подобное. Но, поскольку этот код предназначен только для образовательных целей, я не вижу никаких проблем в том, чтобы сделать его таким образом.
В любом случае, в строке 45 мы вернем то, что написал клиент. Мы увидим, что в качестве результата функции мы используем как имя клиента, так и набранное сообщение. Это важно для анализа и экспериментов.
Но что нас действительно интересует (и здесь начинается самая интересная часть этого кода) так это содержимое строк с 53 по 58. Обратите внимание, что цикл внутри класса в предыдущем коде был удален и перенесен в эту точку. Если точнее, то сейчас она находится на 54-й строке. В строке 55 мы перехватываем всё, что клиент отправил на сокет, и, если значение релевантно, то есть содержит какие-либо данные, выводим полученную информацию на консоль с помощью Python; это делается в строке 57. Таким образом, всё, что отправляет любой клиент, будет видно остальным и, кроме того, может отслеживаться непосредственно на терминале, где запущен сервер.
Теперь один момент, который может вас заинтересовать: как долго этот сервер будет находиться в режиме ожидания, если на сокете, за которым он наблюдает, ничего не происходит? Помните, в предыдущем коде мы упоминали, что функция select будет ждать одну секунду? Это было связано с определенным значением.
Здесь мы будем ждать меньше времени. Поскольку мы не хотим внести изменения в код класса, мы передадим значение непосредственно в класс. Это делается в строке 54. Прошу заметить, что здесь мы определяем значение менее единицы. В данном случае устанавливаемое значение равно 0,2. Это время в секундах, которое он будет ждать, то есть менее одной секунды, прежде чем функция select прервется и продолжит выполнение кода.
В зависимости от того, какую работу необходимо выполнить с информацией, отправленной в сокет, и со временем обработки, можно ещё больше сократить данное время. Помните, что, в отличие от модели, в которой мы использовали потоки, здесь сервер будет наблюдать только за тем, что делает каждое из соединений. В той модели, где мы использовали потоки, каждое соединение было независимым. Это делает обработку данной модели чуть медленнее, чем при использовании потоков для чтения и записи в сокет.
Теперь обратите внимание на следующие детали. Данный сервер, который можно увидеть в приведенном выше коде, прекрасно работает при запуске в промпте. Даже с тем ограничением, что при большом количестве клиентов он будет считывать соединения последовательно.
Но для двунаправленной передачи данных между Excel и MetaTrader 5 этот скрипт на Python пока не вполне подходит. Несмотря на то, что сейчас он находится на гораздо более продвинутом уровне, всё, что мы видели до сих пор, работает, но не так, чтобы скрипт на Python мог работать, не конкурируя с Excel за использование процессора.
Даже этот последний скрипт не позволяет нам добиться этого, хотя и дает хорошее представление о том, как это сделать. Если вы не уверены, не волнуйтесь, на самом деле, это не так просто заметить; вам нужен некоторый опыт в параллельном программировании.
Вы, наверное, заметили, что в большинстве последних скриптов мы не использовали xlwings. Почему? Он не подходил для этого? В действительности, всё, что вы используете за пределами основ Python, не обеспечит хорошего опыта при использовании Excel в сочетании с Python. Позвольте мне пояснить сказанное, чтобы избежать неправильного толкования.
Когда мы разрабатываем что-то в xlwings или в любом другом пакете, позволяющем читать и писать непосредственно в Excel, мы должны заметить, что все программы, функции или процедуры выполняются, а затем завершают свою задачу. Они не остаются внутри цикла, и как бы вы ни старались сделать всё по-другому, такие решения никогда не приведут к тому, что нам действительно нужно. Проблема заключается в том, что серверный скрипт всегда должен находиться в замкнутом цикле, независимо от того, ожидает он чего-то на сокете или нет.
Дело в том, что скрипт будет выполняться в цикле. В случае с клиентом подход может быть несколько другим. Клиент подключается к серверу, и как только это происходит, сервер отправит информацию, которую должен получить клиент. Когда клиент получает его, он может закрыть соединение, что завершает работу скрипта и делает данные доступными для корректной обработки в Excel. Данная обработка может быть выполнена как из Python, так и из VBA.
Но на самом деле это не самый важный момент. Хочу заметить, что если бы вместо сервера использовался клиент, то код был бы совершенно другим. Это связано с тем, что мы можем использовать таймер, созданный в VBA, для запуска клиента в Python без каких-либо проблем касаемо конкуренции процессора с Excel. Поэтому я утверждаю, что пакетов, которые многие используют на постоянной основе, будет недостаточно. Нам нужно другое решение.
Заключительные идеи
В сегодняшней статье мы рассмотрели, как создать Python-сервер, который не заблокируется в ожидании событий на сокете. Однако ни один из представленных кодов не достигает поставленной задачи. Как программисты, мы должны выходить из своей зоны комфорта. Программирование - заключается в поиске решения проблемы, что сильно отличается от того, что многие себе представляют, это не простое copy-paste. Программирование - это нечто более художественное, и требует выхода за рамки, изучения и поиска решений проблем.
Как мы уже говорили в конце предыдущей темы, использование клиента, разработанного на Python, вместе с Excel гарантирует, что Excel не будет вести себя странно до такой степени, что станет непригодным для использования. Однако я хочу вам показать, как сделать так, чтобы сервер Python работал внутри Excel, и при этом Excel можно было бы использовать в обычном режиме. Многие могут подумать: «Но зачем держать Excel открытым? Выполните вычисления, обновите всё с помощью Pandas, xlwings или любого другого пакета, позволяющего работать с файлами Excel, и всё».
Это было бы самым простым решением. Но я хочу показать вам, что это возможно сделать так, как мы себе это представляли. Чтобы понять эту мотивацию, представьте на мгновение обычного пользователя. Каким бы красивым и простым ни был интерфейс, созданный на Python, чтобы пользователю не требовался Excel для анализа данных, многие из них будут сомневаться в нашем решении. Они с полным основанием будут искать программиста, который сможет решить проблему так, чтобы она адаптировалась к их рабочему процессу.
Дело в том, что тот же пользователь захочет использовать Excel для управления тем, что будет выполняться в MetaTrader 5. И вы, как программист и начинающий профессионал, должны понимать, что если решения ещё не существует, то создавать его придется вам. Поэтому привычка копировать и вставлять не подойдет.
В следующей статье мы рассмотрим, как это сделать. Поэтому хорошо изучите весь этот материал, он понадобится вам для понимания того, что будет сделано в следующей части.
| Файл | Описание |
|---|---|
| Experts\Expert Advisor.mq5 | Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study). |
| Indicators\Chart Trade.mq5 | Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study). |
| Indicators\Market Replay.mq5 | Создайте элементы управления для взаимодействия с сервисом репликации/моделирования (для взаимодействия требуется Mouse Study). |
| Indicators\Mouse Study.mq5 | Позволяет взаимодействовать между графическими элементами управления и пользователем (необходимо как для воспроизведения, так и для торговли на реальном рынке). |
| Servicios\Market Replay.mq5 | Создает и поддерживает сервис репликации/моделирования рынка (основной файл всей системы). |
| Код VS C++ Server.cpp | Создает и поддерживает сокет-сервер, разработанный на C++ (версия мини-чат). |
| Код на Python Server.py | Создание и поддержка сокета Python для связи между MetaTrader 5 и Excel. |
| ScriptsCheckSocket.mq5 | Позволяет выполнить тест соединения с внешним сокетом |
| Indicators\Mini Chat.mq5 | Позволяет реализовать мини-чат с помощью индикатора (для работы требуется использование сервера) |
| Experts\Mini Chat.mq5 | Позволяет реализовать мини-чат через советник (для работы требуется использование сервера). |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12790
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Инженерия признаков с Python и MQL5 (Часть III): Угол наклона цены (2) Полярные координаты
Введение в MQL5 (Часть 12): Руководство для начинающих по созданию пользовательских индикаторов
Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Моделирование рынка (часть 13): Сокеты (VII):
Автор: Даниэль Хосе