Русский Português
preview
Simulación de mercado (Parte 13): Sockets (VII)

Simulación de mercado (Parte 13): Sockets (VII)

MetaTrader 5Probador |
124 1
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, «Simulación de mercado (Parte 12): Sockets (VI)», creamos un servidor en Python capaz de responder a varios clientes. Este funcionaba gracias al uso de threads. Creo que quedó clara la idea básica de cómo funciona un thread. Pero, sobre todo, espero que hayas logrado entender cómo un servidor consigue, en cierto modo, hacer funcionar un pequeño chat. La finalidad de estas explicaciones sobre los sockets no es enseñarlo todo sobre ellos. Lo que queremos y necesitamos es que comprendas cómo funcionan los sockets. Este conocimiento será necesario más adelante, en el sistema de repetición/simulador.


Volvemos al problema en Excel

Pero, aunque el servidor en Python esté funcionando para que varios clientes puedan utilizarlo, no está diseñado para usarlo en Excel. Es decir, si usas xlwings para integrar este servidor en Excel, tendrás problemas de interacción con Excel. Pero ¿por qué, si estamos usando threads para no bloquear la ejecución del código?

Esta es una cuestión un poco complicada de observar y notar. Bueno, al menos para quien tiene menos experiencia en la forma en que un programa usa otro que no forma parte de él. Tal vez esta frase haya quedado un poco confusa, pero intentemos entenderla. Cuando usamos un script en VBA dentro de Excel, en realidad estamos ejecutando un programa. Si este script llama a alguna otra aplicación, Excel suele esperar a que esa aplicación termine. Pero esto no siempre ocurre. Para evitar que Excel tenga que esperar a que la aplicación finalice, puedes usar una aplicación que no esté realmente vinculada a Excel. Es decir, tendrás Excel abierto y, al mismo tiempo, otra aplicación abierta por VBA. Ambas pueden coexistir sin competir por la CPU. Sería más o menos como pedirle a Excel que abra Word. No importa si Word se queda detenido, no interferirá con Excel.

Sin embargo, esto no se aplica a lo que se propone, que es precisamente hacer que MetaTrader 5 y Excel intercambien información. Aunque MetaTrader 5 y Excel no compitan por la CPU, no ocurre lo mismo con el script en Python que crea un socket para que se produzca esta comunicación. Esto se debe a los modelos de servidores que se han mostrado hasta ahora. Para simplificar la explicación, tomemos el último script visto en el artículo anterior. Puede verse a continuación:

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()

Código en Python

Aunque este script no se pueda usar directamente en xlwings para crear el servidor, podemos hacer algunos pequeños cambios y pasará a poder utilizarse. Entonces, el código que se muestra arriba pasará a ser el que se muestra justo a continuación:

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)

Código en Python

Muy bien. Como puedes ver, hemos añadido muy poco código, pero esto nos permite usar el script directamente en Python o ejecutarlo desde el VBA de Excel con xlwings. Así, en VBA puedes usar el siguiente comando para activar el servidor desde Excel:

Public Sub ExecuteServer()
    RunPython ("import Server_Thread; Server_Thread.InitServer('127.0.0.1', 27014)")
End Sub

Código VBA

Veamos qué ocurre aquí. Esto es para quien no ha seguido esta secuencia o no sabe trabajar en VBA. Este pequeño script en VBA debe ser llamado por algún control que deberás agregar en Excel. Es una tarea muy sencilla. Agregas un control, por ejemplo, un botón, y en las propiedades de ese botón indicas que debe ejecutar el script mostrado arriba.

Pero, ¿qué está haciendo este script en VBA? Bien, le dice a xlwings que ejecute el script en Python que hemos visto antes. Observa que en el script en Python es posible indicar cuál será el puerto y el host donde estará el servidor. Entonces, los argumentos que vemos en Server_Thread.InitServer indican precisamente esto: cuál es el host, o mejor dicho, la dirección donde el servidor espera las conexiones, así como el puerto. Fíjate que estoy definiendo un puerto diferente del usado en el script en Python; esto es para dejar realmente claro que el servidor se definirá y llamará por Excel.

Bien, si has seguido todos los pasos correctamente, verás que Excel abre Python y el servidor estará disponible. De este modo, funcionará igual que cuando lo ejecutamos directamente en Python. Pero hay un detalle: Excel se comportará de forma bastante extraña y será difícil realizar acciones en él. Esto se debe a que el servidor en Python competirá con Excel por el uso de la CPU. Pero ¿por qué sucede esto? ¿Acaso nuestro servidor no está usando threads? Sí, el servidor está usando threads, pero no en todo el servidor. Hay una parte del código que bloquea. Esa parte es precisamente la línea 29 o 28 del código original. Si la observas, verás que en esa línea hay una llamada a la función accept. Esta función es bloqueante y es justamente la que compite con Excel por el uso de la CPU.

Pero ¿existe alguna forma de resolver esto? Sí, de hecho, existen medios para solucionar este punto. El gran detalle es cuánto pretendes invertir en la solución. Digo esto porque puedes crear un thread dentro de VBA con el fin de que el script en Python se ejecute directamente dentro de un thread. O también puedes buscar otra solución para este mismo problema.

De cualquier forma, será necesario hacer algo para impedir que la función accept siga compitiendo con Excel por el uso de la CPU.


Comenzamos a usar la función SELECT

Una de las soluciones más simples, y que al mismo tiempo no nos obliga a realizar un verdadero malabarismo en términos de programación, es usar la función select en el código del servidor. Esta función ya se vio en otro código, aquí en esta parte sobre sockets. Puedes comprobarlo observando la línea 69 del código del servidor del Mini Chat hecho en C++. Este código se encuentra en el artículo: Simulación de mercado (Parte 09): Sockets (III)

Como verás, no es una solución tan complicada. Sin embargo, si quieres hacer algo similar en Python, el código será ligeramente distinto al de C++. Si usas un código idéntico al de C++, el programa se bloqueará en la llamada a select en lugar de en accept. Para que quede más claro, veamos cómo se hace el código del servidor en Python. Esto se puede ver en el script que aparece justo abajo:

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)

Código en Python

Observa que se hicieron solo algunos pequeños cambios. Estoy avanzando poco a poco para que tú, estimado lector, que no tengas experiencia en el uso de sockets en Python, puedas entender lo que está siendo modificado. Nota que ahora tenemos algunos elementos adicionales entre las líneas ocho y diez. Estos sirven como soporte para el select y para Excel a través de xlwings.

Muy bien, para facilitar la codificación, depurar y mostrar información, tenemos un pequeño procedimiento en la línea 12. Su objetivo es introducir datos en Excel. Observa que en la línea 15 verifico si la variable ha sido inicializada; como en la primera llamada no lo estará, se ejecutará la línea 16, pero solo una vez. A continuación, en la línea 17, se muestra un mensaje en la línea actual de la hoja y, en la línea 18, se incrementa para poder mostrar la siguiente llamada en la línea siguiente.

Sé que este código no es nada bonito, pero la estética no es lo importante aquí, sino la funcionalidad. Lo importante es que funcione, no que sea bonito o agradable de usar desde VBA.

Ahora quiero que prestes atención a la línea 45 de este script. Observa que allí hacemos una llamada a select. A diferencia del código en C++ que mencioné antes, aquí tenemos un parámetro adicional. Dicho parámetro es precisamente el valor uno. Indica cuánto tiempo select permanecerá esperando a que ocurra algo. Ese tiempo se expresa en segundos, por lo que select esperará un segundo antes de continuar. Puedes usar un valor menor; para ello, basta con emplear un número en punto flotante. Pero, si no sabes cómo hacerlo o tienes dudas, no te preocupes, porque más adelante te lo explicaré. Sin embargo, es importante tener en cuenta lo siguiente: si no especificas este valor, select se bloqueará hasta que ocurra algún evento en el socket del servidor.

Para entenderlo mejor, fíjate en que en la línea 52 imprimimos un mensaje en Excel y en la línea 53 esperamos un segundo más, creando así un retardo de dos segundos en el bucle. Para lo que queremos, es más que suficiente. Entonces, esto es lo que sucede: si la línea 45 bloquea la ejecución mientras espera un evento en el socket, no se enviará un ping cada dos segundos; en cambio, si no existe ese bloqueo, sí se enviará el ping. Al ejecutar este script mediante VBA, obtendrás lo siguiente:

Muy bien, pero ¿por qué necesitamos el bucle for de la línea 46? Bueno, este bucle es necesario para poder verificar qué tipo de evento ocurrió en el socket, teniendo en cuenta que aún no hemos entrado en el thread. Así, si algún usuario intenta conectarse, este bucle servirá para saber qué ocurrió. De esta manera, todo lo demás permanece igual que antes. Un detalle importante: sin este bucle for, el servidor se bloqueará esperando que ocurra algo en la función accept, que está en la línea 48. Pero, con esta comprobación en la línea 47 dentro del bucle, podemos detectar que un cliente está intentando conectarse. Aunque este servidor aún no permite que Excel funcione sin competir con Python, ya hemos mejorado considerablemente el sistema.

Pero, el problema está en la línea 44. El hecho de que entremos en el bucle infinito del script en Python en esa línea es lo que hace que el script compita con Excel por el uso de la CPU. Sin embargo, no podemos eliminar ese bucle, ya que, si lo hacemos tal y como está implementado el script, el servidor se cerrará. Por lo tanto, debemos realizar algunos cambios para evitarlo. Es decir, necesitamos entrar y salir del script sin que el servidor cierre sus conexiones. Antes de hacerlo, veamos otro código intermedio respecto al que realmente necesitamos crear. Para separar un poco las cosas, pasemos a un nuevo tema.


Servidor de Mini Chat en una clase

Aunque a muchos no les guste desarrollar o implementar códigos orientados a objetos, existen grandes ventajas en hacerlo. En primer lugar, podemos lograr que el servidor implementado en Python tenga un funcionamiento más sencillo, al menos en términos de programación. Esto se debe a que podemos eliminar muy fácilmente el thead de la versión vista en el tema anterior. Así, tendremos un funcionamiento muy parecido al de la implementación en C++. Para ello, surgirá un nuevo código, pero será completamente compatible con lo que hemos visto hasta ahora. Por supuesto, en este primer momento no lo ejecutaremos desde Excel. Usaremos Python de forma pura y simple. Entonces, el nuevo script, que utiliza programación orientada a objetos, puede verse a continuación:

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...")

Código en Python

Si tienes alguna duda sobre este código, puedes consultar la documentación de Python. Pero creo que tú, estimado lector, que has estado siguiendo las explicaciones, no tendrás demasiada dificultad para entender el script. Aunque pueda parecer extraño a primera vista, todo esto se debe a la forma en que está implementado Python. Así que consulta la documentación para entender realmente cómo funcionan las cosas. Pero hagamos un repaso rápido, solo para ayudarte un poco.

La clase comienza en la línea cinco y termina en la 53. En la línea 56, hacemos uso del constructor de la clase, es decir, del procedimiento __init__, que se encuentra en la línea seis. Este procedimiento creará efectivamente el servidor, aunque aún no estará escuchando ninguna conexión. Sin embargo, cuando init finaliza, se produce la llamada en la línea 57. Esta dirigirá la ejecución hacia la línea 31. Observa que en este procedimiento de la línea 31 lo que hicimos fue eliminar el thread. Es decir, ahora el servidor utilizará el mismo tipo de control que en el servidor en C++. Presta atención al hecho de que seguimos dentro de un bucle en la línea 32. No obstante, quiero que intentes entender lo siguiente: este bucle puede estar tanto dentro como fuera de la clase y, aun así, el servidor podrá cumplir su función.

¿Por qué es esto importante para nosotros? El motivo es que, si colocas el bucle fuera de la clase, necesitarás una forma de verificar si el servidor aún puede estar activo. Con el bucle dentro de la clase, todo lo que necesitamos para crear el servidor son las líneas 56 y 57. Esto nos permite empaquetar este servidor para ser importado dentro de otros scripts en Python.

Ahora viene la parte interesante. Si ignoramos el hecho de que tenemos un bucle en la línea 32, este mismo servidor podría empaquetarse e incorporarse a Excel para que xlwings pudiera usarlo de una forma diferente a la vista hasta ahora. Sin embargo, esta implementación se hizo así de forma intencionada. Así podrás comprender fácilmente los cambios entre cada una de las versiones del servidor en Python. Así, verás fácilmente cómo lograremos que el servidor de Python deje de competir con Excel por el uso de la CPU. Así que, suponiendo que hayas entendido realmente el código, lo modificaremos. Ahora podrá empaquetarse sin inconvenientes y utilizarse en cualquier script.

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...")

Código en Python

Muy bien. Muy bien. En el script que se muestra arriba, realicé algunas mejoras en el código para que pudiera hacer lo que mencioné antes, es decir, que se pudiera empaquetar y usar sin problemas en otras aplicaciones. Pero veamos qué ocurre ahora, ya que este código ya no se quedará realmente detenido esperando a que ocurra algún evento en la red.

En primer lugar, los procedimientos BroadCastData y ClientDisconnect son privados dentro de la clase. Es decir, no podrán accederse directamente desde fuera del cuerpo de la clase. Aunque existen formas de acceder a estos procedimientos privados, no te explicaré cómo hacerlo para que no te veas tentado a hacerlo en otras ocasiones. Ten en cuenta que es posible definir elementos privados en Python y no intentes acceder a procedimientos o funciones que se consideran privados. Si lo intentas, Python te notificará esta acción como un error. De nuevo, existe una manera de acceder a procedimientos privados dentro de Python, pero te dejo a ti investigar cómo hacerlo. Sin embargo, sinceramente desaconsejo esta práctica, ya que rompe una de las premisas del encapsulamiento de código. Si un procedimiento o una función son privados, es por algo.

Bien, pues con esto tenemos solo dos procedimientos públicos: ExistConnection y CheckMessage. ExistConnection devuelve un valor booleano, como se puede ver en la línea 24. Este valor devuelto tiene como finalidad permitir que, fuera del cuerpo de la clase, podamos verificar si el servidor está activo o no, es decir, si hay alguna conexión activa.

El procedimiento CheckMessage, que se encuentra en la línea 29, devuelve el mensaje enviado por algún cliente. Pero no solo eso. Observa que, en la línea 43, el servidor se cierra a petición del cliente. Puede parecer un poco drástico, ya que, en principio, cualquier cliente no debería poder hacer algo así. Sin embargo, dado que este código tiene únicamente fines didácticos, no veo problema en hacerlo de esta manera.

En cualquier caso, en la línea 45 devolveremos lo que haya escrito algún cliente. Verás que usamos tanto el nombre del cliente como el mensaje digitado como resultado de la función. Esto es importante para fines de análisis y experimentación.

Pero lo que realmente nos interesa, y aquí viene la parte interesante de este código, es el contenido de las líneas 53 a 58. Observa que el bucle que había dentro de la clase en el código anterior se ha eliminado y se ha trasladado a este punto. Para ser más exactos, ahora se encuentra en la línea 54. En la línea 55, capturamos lo que haya enviado algún cliente al socket y, si el valor es relevante, es decir, si contiene algún dato, imprimiremos la información recibida en la consola que utilice Python; esto se hace en la línea 57. De este modo, todo lo que envíe cualquier cliente será visto por los demás y, además, podrá ser monitorizado directamente en la terminal donde se esté ejecutando el servidor.

Ahora, un detalle sobre el que quizás tengas curiosidad: ¿cuánto tiempo permanecerá este servidor en modo de espera si no ocurre nada en el socket que está observando? Es una pregunta muy pertinente. ¿Recuerdas que en el código anterior mencioné que la función select esperaría un segundo? Esto se debía al valor definido allí.

Pues bien, aquí esperará menos tiempo. Como el objetivo es no tener que modificar el código de la clase, pasaremos el valor directamente dentro de ella. Esto se hace en la línea 54. Observa que allí estamos definiendo un valor inferior a uno. En este caso, el valor definido es 0.2. Este es el tiempo, en segundos, que permanecerá en espera, es decir, menos de un segundo, antes de que la función select se interrumpa y continúe con la ejecución del código.

En función del trabajo que deba realizarse con la información enviada al socket y del tiempo de procesamiento, es posible reducir aún más este tiempo. Recordemos que, a diferencia del modelo en el que usábamos threads, aquí el servidor solo observará lo que esté haciendo cada una de las conexiones. En el modelo en el que usábamos threads, cada conexión era independiente. Esto hace que el procesamiento de este modelo sea un poco más lento que cuando se usan threads para leer y escribir en el socket.

Ahora, presta mucha atención a los siguientes detalles. Este servidor, que se puede ver en el código anterior, funciona perfectamente cuando se ejecuta en un prompt. Incluso con la limitación de que, si hay muchos clientes, leerá las conexiones de forma secuencial.

Sin embargo, para lograr transferir información entre Excel y MetaTrader 5 de forma bidireccional, este script en Python aún no es del todo adecuado. Aunque ya se encuentra en un nivel mucho más avanzado, todo lo que se ha visto hasta el momento funciona, pero no de una manera que permita que el script de Python trabaje sin competir con Excel por el uso de la CPU.

Ni siquiera este último script nos permite lograrlo, aunque ya nos da una buena idea de cómo hacerlo. Si no lo tienes claro, no te preocupes. En realidad, no es algo que se note fácilmente; se necesita cierta experiencia en programación paralela.

Habrás notado que en casi todos estos últimos scripts no he utilizado xlwings. ¿Por qué? ¿Acaso no era adecuado para ello? En realidad, cualquier cosa que utilices, más allá de lo básico de Python, no ofrecerá una buena experiencia al usar Excel junto con Python. Permíteme aclarar lo que acabo de decir para evitar malinterpretaciones.

Cuando tú desarrollas algo, ya sea en xlwings o en cualquier otro paquete que nos permita leer y escribir directamente en Excel, en realidad deberías notar que todos los programas, funciones o procedimientos se ejecutan y luego finalizan su tarea. No se quedan dentro de un bucle y, por más que intentes hacer las cosas de otra manera, tales soluciones nunca alcanzarán lo que realmente necesitamos hacer. El problema radica en que el script de un servidor debe mantenerse siempre en un bucle cerrado, ya sea esperando o no a que algo se coloque en el socket.

El hecho es que el script se ejecutará en un bucle. En el caso de un cliente, el enfoque puede ser un poco diferente. El cliente se conecta al servidor y, en cuanto eso ocurre, el servidor envía la información que el cliente debe recibir. Cuando el cliente la reciba, podrá cerrar la conexión, lo que finalizará el script y dejará los datos disponibles para que Excel los procese adecuadamente. Este procesamiento se puede realizar tanto desde Python como desde VBA.

En realidad, este punto no es lo más importante. Lo que quiero destacar, querido lector, es que si en lugar de un servidor se utilizara un cliente, el código sería completamente distinto. Esto se debe a que podríamos usar un temporizador creado en VBA para ejecutar el cliente en Python sin que haya problemas de competencia por la CPU con Excel. Por este motivo, afirmo que los paquetes que muchos utilizan habitualmente no serán suficientes. Necesitamos una solución diferente.


Consideraciones finales

En este artículo, te mostré cómo crear un servidor en Python que no se bloqueara mientras esperaba que ocurriera algo en el socket. Sin embargo, ninguno de los códigos presentados logra cumplir realmente nuestro objetivo. Como programador, debes salir de tu zona de confort. Programar consiste en buscar una solución a un problema, algo muy distinto de lo que muchos imaginan, que se reduce a copiar y pegar. La programación es algo más artístico que exige ir más allá, explorar y buscar soluciones a los problemas.

Pues bien, como mencioné al final del tema anterior, utilizar un cliente desarrollado en Python junto con Excel garantiza que Excel no se comporte de forma extraña hasta el punto de volverse inutilizable. No obstante, quiero mostrarte cómo hacer que un servidor en Python se ejecute dentro de Excel, de manera que Excel pueda seguir utilizándose con normalidad. Muchos podrían decir: «¿Pero por qué mantener Excel abierto? Haz los cálculos, actualiza todo con Pandas, xlwings o cualquier otro paquete que permita manipular archivos de Excel y listo».

Bueno, esa sería la solución más sencilla. Pero quiero mostrarte que es posible hacerlo tal y como se imaginó. Para que entiendas esta motivación, imagina por un momento a un usuario promedio. Por muy agradable y sencilla que sea la interfaz que construyas con Python para que el usuario no necesite Excel para analizar los datos, muchos de ellos pondrían en duda tu solución. Buscarían, con toda la razón, a un programador que pudiera resolver el problema de una manera que se adaptara a su flujo de trabajo.

Esto se debe a que ese mismo usuario querrá usar Excel para controlar algo que será ejecutado en MetaTrader 5. Y tú, como programador y aspirante a profesional, debes entender que, si la solución aún no existe, te corresponderá crearla. Por lo tanto, ese hábito de copiar y pegar no será suficiente.

En el próximo artículo te mostraré cómo se hará esto. Así que estudia bien todo este material, porque lo necesitarás para comprender lo que se hará en la siguiente entrega.

ArchivoDescripción
Experts\Expert Advisor.mq5
Demuestra la interacción entre Chart Trade y el Asesor Experto (es necesario el Mouse Study para la interacción)
Indicators\Chart Trade.mq5Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción)
Indicators\Market Replay.mq5Crea los controles para la interacción con el servicio de reproducción/simulador (es necesario el Mouse Study para la interacción)
Indicators\Mouse Study.mq5Permite la interacción entre los controles gráficos y el usuario (Necesario tanto para operar el sistema de repetición como en el mercado real)
Servicios\Market Replay.mq5Crea y mantiene el servicio de reproducción y simulación de mercado (archivo principal de todo el sistema)
Código VS C++\Server.cppCrea y mantiene un socket servidor desarrollado en C++ (versión MiniChat).
Code in Python\Server.pyCrea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e o Excel
Scripts\CheckSocket.mq5Permite realizar una prueba de conexión con un socket externo
Indicators\Mini Chat.mq5Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar)
Experts\Mini Chat.mq5Permite implementar un mini chat mediante un Asesor Experto (requiere el uso de un servidor para funcionar)

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12790

Archivos adjuntos |
Anexo.zip (560.03 KB)
Hebert wilfredo Herrera Ortiz
Hebert wilfredo Herrera Ortiz | 7 nov 2025 en 08:37
MetaQuotes:

Artículo publicado Simulación de mercado (Parte 13): Sockets (VII):

Autor: Daniel Jose

Gracias 
Mecanismos de compuertas en el aprendizaje en conjuntos Mecanismos de compuertas en el aprendizaje en conjuntos
En este artículo, continuamos nuestra exploración de los modelos ensamblados analizando el concepto de compuertas, concretamente cómo pueden ser útiles para combinar los resultados de los modelos con el fin de mejorar la precisión de las predicciones o la generalización de los modelos.
Optimización por herencia sanguínea — Blood inheritance optimization (BIO) Optimización por herencia sanguínea — Blood inheritance optimization (BIO)
Les presento mi nuevo algoritmo basado en la población, el BIO (Blood Inheritance Optimization), inspirado en el sistema de herencia del grupo sanguíneo humano. En este algoritmo, cada solución tiene un "grupo sanguíneo" distinto que determina su forma de evolucionar. Al igual que en la naturaleza, el grupo sanguíneo de un niño se hereda según reglas específicas, en el BIO las nuevas soluciones obtienen sus características mediante un sistema de herencia y mutaciones.
Redes neuronales en el trading: Modelos híbridos de secuencias de grafos (GSM++) Redes neuronales en el trading: Modelos híbridos de secuencias de grafos (GSM++)
Los modelos híbridos de secuencias de grafos (GSM++) combinan los puntos fuertes de distintas arquitecturas para posibilitar un análisis de datos de gran precisión y optimizar los costes computacionales. Estos modelos se adaptan eficazmente a los datos dinámicos del mercado, mejorando la presentación y el procesamiento de la información financiera.
La estrategia de negociación de la brecha del valor razonable inverso (Inverse Fair Value Gap, IFVG) La estrategia de negociación de la brecha del valor razonable inverso (Inverse Fair Value Gap, IFVG)
Una brecha inversa del valor razonable (Inverse Fair Value Gap, IFVG) se produce cuando el precio vuelve a una brecha del valor razonable identificada previamente y, en lugar de mostrar la reacción de apoyo o resistencia esperada, no la respeta. Este comportamiento puede indicar un posible cambio en la dirección del mercado y ofrecer una ventaja comercial contraria. En este artículo, voy a presentar mi enfoque, desarrollado por mí mismo, para cuantificar y utilizar la brecha inversa del valor razonable como estrategia para los asesores expertos de MetaTrader 5.