Simulación de mercado (Parte 12): Sockets (VI)
Introducción
En el artículo anterior, «Simulación de mercado (Parte 11): Sockets (V)», expliqué cómo crear una aplicación en Python para usar en Excel. El objetivo de dicha aplicación era mostrar cómo crear un servidor de eco en Python. La particularidad era que los datos relacionados con los eventos de conexión y cierre se mostraban en Excel.
De hecho, este servidor no es muy útil para nosotros, principalmente porque solo permite una conexión. En realidad, un servidor cuyo propósito es establecer solo una conexión no resulta muy útil. Sin embargo, quiero que tú, estimado lector, no te concentres en ese detalle. La intención era mostrar cómo un script escrito en Python puede ejecutarse en Excel de forma transparente. No obstante, para lo que necesitamos, será necesario que nuestro servidor sea un poco más elaborado. Para ello, tendremos que hacer algunas cosas más.
La idea aquí no es crear una aplicación finalizada. Como ya mencioné, y vuelvo a repetir, los sockets son un tema extremadamente denso que requiere mucho estudio y tiempo de investigación. No esperes poder crear algo realmente seguro y perfecto de la noche a la mañana. Cuando se trata de sockets, es necesario profundizar en muchos detalles, unos más simples y otros más complejos.
Sin embargo, dado que nuestra aplicación exige algo un poco más elaborado para lograr una comunicación entre Excel y MetaTrader 5, se decidió que sería necesario suavizar un poco la curva de desarrollo. Esto se debe a que el objetivo es mostrar a quien está comenzando a estudiar el tema de los sockets cómo pueden ocurrir realmente las cosas.
A los más experimentados, les pido disculpas si parece repetitivo o si el artículo no aporta nuevo conocimiento. Pero para quienes están empezando, el contenido aquí será muy útil, especialmente porque haremos todo en Python puro.
En este artículo no trabajaremos directamente con Excel ni con MQL5. Sin embargo, en el caso de MQL5 podremos hacer cierto uso de él. Más precisamente, utilizaremos algo que desarrollamos en esta misma serie de artículos. Para que puedas entender todo lo que será explicado, tal vez sea conveniente revisar también lo que se hizo en MQL5.
Si estás interesado, revisa los siguientes artículos:
Fue en esos dos artículos, específicamente, donde creamos lo que será el tema de este. En los artículos mencionados anteriormente desarrollamos un Mini Chat capaz de permitir que los usuarios de MetaTrader 5 intercambiaran información mediante mensajes de texto. El detalle es que el código del servidor, en aquel momento, fue desarrollado en C++. Aquí desarrollaremos un servidor similar, pero en Python.
Entendamos algunas cosas antes
La transformación de un servidor de eco en un servidor de minichat, a primera vista, no parece algo muy complicado. Y convertir un servidor de eco en el tipo que necesitamos para que Excel y MetaTrader 5 se comuniquen tampoco es una de las tareas más complejas. Eso, claro, para quien ya tiene las bases y el conocimiento adecuado para realizar tal tarea. Sin embargo, cuando se trata de sockets y, especialmente, del uso de sockets por varios clientes al mismo tiempo en el mismo servidor, la situación se vuelve un poco más compleja. Esto se debe a que muchos, sobre todo los principiantes, no tienen idea de lo que realmente se necesita hacer.
Entonces, volvamos al código en Python visto en el artículo anterior. Este puede verse justo a continuación, para que comprendas mejor la explicación.
01. import socket 02. import xlwings as xw 03. 04. def Echo(): 05. wb = xw.Book.caller() 06. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 07. server.bind(("127.0.0.1", 27015)) 08. wb.sheets[0]['A1'].value = "Waiting connection..." 09. server.listen(1) 10. client, addr = server.accept() 11. client.send("Wellcome to Server in Python\n\r".encode()) 12. wb.sheets[0]['A2'].value = str(f"Client connected by {addr}") 13. while True: 14. info = client.recv(512) 15. if not info: 16. break 17. client.send(b"Echo Server:" + info) 18. wb.sheets[0]['A3'].value = "Client disconnected" 19. client.close()
Servidor en Python
Aunque este código no sea funcional sin lo que se mostró en el artículo anterior, es posible hacerlo funcionar y, aun así, permitir que pueda ser usado más adelante como un módulo. Para realizar esos cambios será necesario eliminar algunas partes del código, considerando que no se utilizará Excel junto con Python. Entonces, el código quedaría como se muestra a continuación:
01. import socket 02. 03. def Echo(): 04. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 05. server.bind(("127.0.0.1", 27015)) 06. server.listen() 07. client, addr = server.accept() 08. client.send("Wellcome to Server in Python\n\r".encode()) 09. while True: 10. info = client.recv(512) 11. if not info: 12. break 13. client.send(b"Echo Server:" + info) 14. client.close() 15. 16. if __name__ == "__main__": 17. Echo()
Servidor en Python
Bien. Si ya tienes alguna base en Python, habrás notado lo que sucede aquí. Pero si no tienes conocimiento sobre Python, probablemente estarás pensando: «¿Qué locura es esta?». En realidad, no se trata de ninguna locura. Lo que ocurrió fue que el código anterior, que no podía ejecutarse directamente por el intérprete de Python, ahora sí puede hacerlo. Esto fue posible únicamente gracias a la adición de las líneas 16 y 17, pero el código puede seguir utilizándose dentro de un módulo precisamente por causa de esas líneas.
Sin embargo, ese no es nuestro foco aquí. El tema es cómo hacer que el código presentado arriba sea capaz de permitir que varios clientes se conecten a él, incluso cuando, en principio, eso no sea posible.
Bien. Estimado lector, si estudiaste el material de referencia que incluí en los artículos anteriores, ya debes entender algunos detalles y problemas presentes en este código. Esto se debe a que, en efecto, está diseñado para aceptar solo una única conexión a la vez. ¿Pero cómo es eso posible? Porque la función accept, presente en la línea siete, una vez ejecutada, no volverá a ejecutarse. Eso impide que más clientes se conecten. Incluso si el mismo cliente cierra la conexión, no podrá volver a conectarse.
El motivo es el mismo: la función accept no volverá a ejecutarse. Entonces, ¿podríamos añadir otro bucle a este código para que el contenido entre las líneas siete y 14 se ejecute nuevamente en caso de que el cliente decida regresar? Sí, podemos hacerlo. Y, de hecho, en algunos casos más simples, eso es precisamente lo que un programador haría. Sin embargo, pensemos un poco más allá, porque si implementamos esta solución, aun así solo tendríamos un único cliente conectado. Y para nuestro propósito de minichat, eso no sería suficiente.
Además de este problema, hay otro. Si realizaste las pruebas de lo que se presentó en los dos artículos anteriores, habrás notado que Excel se vuelve casi inaccesible cuando el servidor está en uso. Los artículos mencionados son los siguientes:
Esto es para quienes no los han leído. Ahora, la pregunta es: ¿por qué Excel se vuelve difícil de usar cuando el servidor de eco está en funcionamiento? Para responder a esa pregunta, es necesario entender lo que ocurre dentro del código del servidor. Recuerda que Excel llama, a través de VBA, al código en Python para que sea ejecutado. Cuando el código en Python se inicia, comienza a competir por el uso de la CPU junto con Excel.
Para el sistema operativo, el código en Python no es un proceso separado. En realidad, sería un tipo de thread de Excel. El término thread, de hecho, no sería el más adecuado para usar aquí, ya que una thread no competiría con el programa principal por el uso de la CPU. Sin embargo, como al cerrar Excel el código en Python también se termina, podemos usar la palabra "thread" aquí con la debida precaución. No obstante, hay que tener cuidado con este término, y el motivo será explicado un poco más adelante.
Sin embargo, es justo preguntarse por qué el código en Python compite por el uso de la CPU cuando Excel tiene una ventana de tiempo para utilizarla. El motivo se debe a dos puntos dentro del código. Volvamos entonces al código original del servidor de eco. Observa que, en la línea 10, hay una llamada a la función accept. Pues bien, esa función es bloqueante. Es decir, cuando se ejecuta, el código se detiene en ese punto y no continúa hasta que se produzca alguna conexión. Esta función representa el primer punto de cuello de botella, es decir, el primer momento en que el script en Python competirá con Excel por el uso de la CPU.
Muy bien, una vez establecida la conexión, tenemos un segundo problema. Este ocurre precisamente en la línea 14, donde el script en Python vuelve a competir con Excel. Esa competencia se repite en cada interacción con la línea 14. De esta forma, Excel recibe menos atención que el script en Python, lo que dificulta su uso mientras el servidor está en funcionamiento.
¿Pero existe alguna forma de resolver esto? Sí, existen algunas maneras de ofrecer una solución a esta situación. La primera consiste en hacer que el script en Python se convierta en una thread real de Excel. De esa forma, cuando Excel entre en su ventana de ejecución, tanto Excel como el script en Python dejarán de competir por el uso de la CPU. Esto ocurre porque el sistema operativo ajustará las tareas de modo que cada uno reciba una fracción del tiempo de procesamiento. En casos de CPU multinúcleo, el sistema operativo puede permitir que Excel utilice un núcleo y el script en Python otro. Para que eso ocurra realmente, sin embargo, es necesario que Excel informe al sistema operativo que el script en Python será una thread.
Pero, en fin, ¿qué es exactamente esta tal thread de la que estamos hablando? Para entenderlo, pasemos a un nuevo tema.
Entendamos qué es una thread
A grandes rasgos, una thread sería como otro programa que existe dentro de un programa principal. En efecto, esta es una forma sencilla de describir una thread. La thread existe únicamente mientras el programa principal esté en ejecución. Cuando el programa principal, en nuestro caso Excel, deja de existir, todo lo que está relacionado con él también muere.
El concepto de thread surgió junto con el procesamiento paralelo. En realidad, una thread sirve exactamente para eso. Ayuda al programa principal a ejecutar tareas que serían muy difíciles de programar directamente dentro del código principal. Para ello, colocas esa parte del código en un fragmento que será ejecutado en paralelo a lo que el programa principal esté realizando.
Tan pronto como la thread termina su trabajo, el programa principal puede utilizar los datos calculados o recuperados. Estos estarán disponibles en el área que hayas reservado. Es una forma bastante curiosa de programación, porque cuando usamos una thread es posible realizar una tarea mientras permite que el programa principal continúe funcionando, incluso si la tarea enviada a la thread tarda algunos minutos en completarse.
Esa característica resulta bastante interesante en Python, al igual que en otros lenguajes. Sin embargo, es necesario ver las cosas en funcionamiento para comprender lo que realmente sucede y cuál es la ventaja de usar una thread. Para no dejar el tema sin algún tipo de ejemplo práctico, veremos un caso de uso de threads en Python.
01. import socket as sock 02. import threading as thread 03. 04. def NewClientHandler(conn, addr): 05. print(f"Client [%s:%s] is online..." % addr) 06. while True: 07. msg = conn.recv(512).decode().rstrip(None) 08. if msg: 09. print(f"Message from [%s:%s]: {msg}" % addr) 10. if msg.lower() == "/see you later": 11. break 12. print(f"Client [%s:%s] is disconnected..." % addr) 13. conn.close() 14. 15. server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 16. server.bind(('localhost', 27015)) 17. server.listen() 18. print("Waiting connections...") 19. while True: 20. conn, addr = server.accept() 21. conn.send("Wellcome to Server.\n\r".encode()) 22. thread.Thread(target=NewClientHandler, args=(conn, addr)).start()
Servidor en Python
Este servidor, cuyo código aparece arriba, utiliza threads para manejar más de un cliente al mismo tiempo. Presta atención a algunos detalles aquí. Primero, este servidor no reenviará la información proporcionada por un cliente a los demás. Segundo, este servidor tiene únicamente un propósito didáctico, es decir, demostrar el uso de threads. No obstante, si comprendes lo que está ocurriendo, podrás crear muchas cosas interesantes simplemente modificando el código anterior.
Pero entendamos qué está pasando dentro de este código. Si ya sabes Python, te pido paciencia con los nuevos programadores. Recuerda que tú no naciste sabiendo: alguien te enseñó o aprendiste por medio de documentos escritos por otras personas. Así que, ten paciencia.
En la primera línea importamos el paquete de sockets. Observa que estoy declarando las cosas de una forma ligeramente diferente. En la línea dos importamos el paquete que nos permite crear threads. Al igual que en la primera línea, aquí también hay algo que puede parecer extraño, pero no te alarmes. Lo que estoy haciendo es crear un alias para hacer las cosas en Python más agradables y facilitar la comprensión del código.
Entre las líneas cuatro y 13 hay un procedimiento. Por ahora lo dejaremos de lado y avanzaremos directamente hasta la línea 15. Observa que, entre las líneas 15 y 18, el código es prácticamente igual al de los ejemplos anteriores de este mismo artículo. Esa parte se encarga de crear el socket en el servidor, por eso el código es idéntico. Un detalle: en estos ejemplos estamos utilizando el protocolo TCP. Sin embargo, recuerda que existen otros protocolos de red, por lo que el código puede variar un poco de un servidor a otro, principalmente debido a ciertos aspectos que veremos más adelante.
De cualquier forma, en la línea 18 indicamos que el servidor está listo para recibir conexiones. ¿Recuerdas el detalle que mencioné antes, sobre colocar un bucle antes de la llamada accept? Pues bien, aquí estamos haciendo exactamente eso. Así, incluso si el cliente se desconecta, podrá reconectarse al servidor, ya que el bucle permitirá más de una interacción con la llamada accept. Ahora recordemos un detalle: la llamada accept bloquea el código y lo mantiene a la espera de una conexión. Cuando esto ocurre, la ejecución continúa hacia la línea 21, que envía un mensaje básico al nuevo cliente, para que este sepa que la conexión se ha establecido. A continuación, en la línea 22, se crea la thread en Python.
Aquí es donde las cosas realmente se ponen interesantes, y en este punto es donde surge algo bastante importante. Si colocáramos el código entre la línea cuatro y la línea 13 en lugar de la línea 22, el comportamiento del servidor sería completamente diferente. Esto se debe a que el código dejaría de permitir más de una conexión y pasaría a aceptar solo una.
Espera un momento, ¿cómo es eso? Para entenderlo, es necesario comprender que la línea 22 no es una línea mágica. En realidad, lo que hace es llamar al procedimiento definido en la línea cuatro. Sin embargo, hay un detalle que marca toda la diferencia entre una llamada que se convierte en una thread y otra que no lo hace. Una vez que la llamada se convierte en una thread, como se muestra en este código, el sistema operativo hace lo siguiente: toma el código de la thread, lo asigna en memoria y divide el tiempo o la ventana de ejecución del código principal entre el propio código y la thread.
Tal vez esto resulte un poco confuso al principio. Pero piensa de la siguiente manera: el tiempo que el sistema operativo otorga a un programa sería como una barra de dulce. Cuando el programa no tiene threads, puede consumir toda la barra. Pero cuando empieza a tener threads, esa barra se divide en trozos cada vez más pequeños. De esta forma, el programa principal solo podrá consumir uno de esos trozos, mientras que los demás serán consumidos por las threads. Esta sería una explicación bastante simple si tu CPU tuviera un solo núcleo.
Volviendo a nuestro código, tan pronto como se ejecuta la línea 22, el programa se «divide» en dos. Una parte regresa inmediatamente a la línea 19, donde vuelve a esperar en la línea 20. Es importante recordar que la función accept bloqueará el código, impidiendo que continúe ejecutándose. La otra parte será dirigida a la línea 4.
Bien. Ahora sí que estoy realmente confundido. ¿Cómo puede funcionar el código de la línea 4 si el código está bloqueado en la línea 20? Esto me resulta muy confuso. Calma, mi estimado lector. No hay motivo para la confusión. Recuerda el ejemplo de la barra de dulce. En efecto, el código principal quedará bloqueado en la línea 20, esperando la conexión de un nuevo cliente. Sin embargo, también tendremos la ejecución del código entre las líneas cuatro y 13, independientemente de que un nuevo cliente se conecte o no. El detalle importante es que el servidor comenzará a escuchar la información del cliente conectado. Por esta razón, entramos en un nuevo bucle en la línea seis. Presta mucha atención a esto, porque si no entiendes esta parte, quedarás completamente desorientado respecto al funcionamiento de las threads.
Ese bucle, que comienza en la línea seis y termina en la línea once, existirá únicamente dentro de la thread. Y cada cliente, y esto es importante entenderlo, tendrá su propio bucle. Así, aunque dos o más clientes estén conectados, uno no interferirá en el bucle del otro. Ahora, piensa en esto como si cada cliente tuviera un servidor dedicado exclusivamente para él. Es decir, cada cliente conectado, o que llegue a conectarse, estará creando un servidor para sí mismo. Y ese servidor responderá únicamente a ese cliente.
Esa es la magia de la thread. Sin embargo, no todo son maravillas perfectas. Existen algunos problemas en el uso de las threads. Muchos de esos problemas surgen precisamente cuando tú, como programador, comienzas a hacer un uso indiscriminado de ellas. Siempre hay que tener en cuenta que una thread consumirá parte del tiempo del programa principal, incluso cuando parezca que la ejecución es paralela. Esto ocurre porque, por muchos núcleos que tenga tu CPU, no son infinitos. Por tanto, el uso excesivo de threads puede consumir todos los núcleos disponibles, haciendo que el tiempo de ejecución aumente y, en consecuencia, la ventaja de usar threads disminuya.
Ahora bien, puede que te estés preguntando otra cosa. Aunque este código sea capaz de manejar varios clientes, no permite que estos se comuniquen entre sí. ¿No sería posible transferir información entre clientes utilizando threads? Si pensaste de esa forma, eso significa que, en realidad, todavía no entiendes del todo cómo funcionan las threads. Pero no te sientas mal por ello. Al contrario, deberías sentirte satisfecho por habértelo cuestionado. Si, al mirar el código y leer el artículo, pensaste de esa manera, eso demuestra que realmente comprendiste el funcionamiento del servidor. Sin embargo, el hecho de que haya surgido la duda muestra que aún no dominas completamente el concepto de threads. Así que resolveremos esa duda. Para separar las ideas, veremos esto en el siguiente tema.
Transferencia de información entre clientes gestionados por thread
Para entender cómo sucede esto, es necesario haber comprendido lo que ocurre antes de este punto. Si no entendiste los temas anteriores, vuelve y léelos nuevamente. Es importante comprenderlos, de lo contrario te confundirás aún más en este apartado que, aunque relativamente corto, puede dejarte muy confundido y, peor aún, sin entender nada de lo que está ocurriendo.
Entonces, veamos el código.
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()
Servidor en Python
Pero, ¿qué es esto? Solo añadiste algunas líneas de código. ¿Estás seguro de que este código permite que los clientes se envíen información entre sí? Sí, te aseguro que este código no solo permite enviar información entre los clientes, sino también hace que el servidor muestre esos mismos mensajes. Esto puede observarse en la ventana del símbolo del sistema donde el servidor esté ejecutándose.
Sin embargo, hay un detalle en este código. Los mensajes más antiguos no estarán disponibles. Es decir, solo recibirán mensajes los clientes que estén realmente conectados al servidor en ese momento. Pero, ¿cómo ocurre eso exactamente? Estoy mirando el código y no logro entenderlo.
Para entender cómo este servidor logra transferir los mensajes enviados a otros clientes, es necesario comprender cómo funciona una thread. Esto fue explicado en el tema anterior. Si comprendiste el funcionamiento de una thread, ahora podemos entender cómo se comunican entre sí. Exactamente. Para que una thread pueda pasar los mensajes a otros clientes, es necesario que intercambien información. ¿Y cómo ocurre eso? En el caso más simple, sucede mediante una parte de la memoria compartida. Esa parte compartida se declara precisamente en la línea cuatro. Es decir, la lista declarada en la línea cuatro será visible para todas las threads.
Para que eso ocurra sin mayores inconvenientes, dentro de la thread, en la línea siete, indicamos que la lista es global. Esto evita que Python la cree localmente. Si Python la creara localmente, la memoria no sería compartida entre las threads, y necesitamos que sí lo sea. En la línea ocho añadimos a nuestra lista la conexión que creó la thread. Este paso es importante, ya que cada nueva conexión generará una nueva thread, agregando un nuevo valor a la lista.
Cuando se ejecuta la línea 11, esta se bloqueará mientras espera recibir algún dato de la conexión. Esto ocurrirá en todas las threads individualmente. Tan pronto como un cliente envíe algo al servidor, entraremos, en la línea 14, en un pequeño bucle. Este es el punto en el que ocurre la «magia». Observa que recorreremos la lista de conexiones, la cual fue creada por las threads de forma individual. Sin embargo, el bucle ignora eso, ya que la memoria está siendo compartida entre las threads. De esta manera, recorrerá todas las conexiones una por una.
Si la condición en la línea 15 es falsa (es decir, si la conexión corresponde exactamente a la conexión de la thread del bucle), no haremos nada. Sin embargo, si esta prueba es verdadera, se ejecutará la línea 16. Esta enviará a la conexión que está siendo observada por otra thread aquello que la conexión de esta thread ha recibido. Espero que esta parte haya quedado clara. Recuerda que cada thread corresponde a una conexión. Cuando una conexión recibe datos, hace que el bucle los retransmita a las demás conexiones. Sin embargo, esto no provoca que las otras threads se activen.
Pero espera un momento. ¿Cómo es eso? Si una thread envía datos a las demás conexiones, eso debería hacer que las demás threads se activaran. No, estimado lector. Pensar de esa forma es un error. No del todo, pero estás olvidando el siguiente hecho: el servidor no escucha lo que ocurre dentro de las threads. Escucha lo que ocurre en los clientes. Para cada cliente, el servidor parece estar conectado única y exclusivamente a un servidor dedicado. El cliente no tiene conciencia de que está dentro de una thread.
Por último, cuando el cliente se desconecta, eliminamos de la lista la identidad de la conexión que había sido añadida en la línea 21. De este modo, todas las demás threads dejan de ver esa conexión que acaba de cerrarse.
Consideraciones finales
Aunque pueda parecer muy confuso, el tema de las threads es tan denso como el de los sockets. Sin embargo, esta solución que utiliza threads es solo una entre muchas otras posibles. En el próximo artículo abordaré otra forma de resolver este problema, ya que esta solución basada en threads no resuelve la cuestión de Excel. Esto se debe a que aún tenemos el problema de la función accept, que bloquea el servidor, haciendo que la experiencia de uso de Python en Excel no sea tan agradable, al menos para nosotros que necesitamos los sockets.
| Archivo | Descripció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.mq5 | Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción) |
| Indicators\Market Replay.mq5 | Crea 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.mq5 | Permite 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.mq5 | Crea y mantiene el servicio de reproducción y simulación de mercado (archivo principal de todo el sistema) |
| Código VS C++\Server.cpp | Crea y mantiene un socket servidor desarrollado en C++ (versión MiniChat). |
| Code in Python\Server.py | Crea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e o Excel |
| Scripts\CheckSocket.mq5 | Permite realizar una prueba de conexión con un socket externo |
| Indicators\Mini Chat.mq5 | Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar) |
| Experts\Mini Chat.mq5 | Permite 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/12745
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
La estrategia de negociación de la brecha del valor razonable inverso (Inverse Fair Value Gap, IFVG)
Dominando los registros (Parte 4): Guardar registros en archivos
Optimización por herencia sanguínea — Blood inheritance optimization (BIO)
Simulación de mercado (Parte 11): Sockets (V)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso