313 lines
12 KiB
Python
313 lines
12 KiB
Python
|
|
import socket
|
||
|
|
import json
|
||
|
|
import threading
|
||
|
|
import argparse
|
||
|
|
from mcp.server.fastmcp import FastMCP
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
#| Args |
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
parser = argparse.ArgumentParser()
|
||
|
|
parser.add_argument("--host", type=str, default="127.0.0.1")
|
||
|
|
parser.add_argument("--port", type=int, default=9999)
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
HOST = args.host
|
||
|
|
PORT = args.port
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
#| General |
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
mcp = FastMCP("AiDataTaskRunerMT5 MCP")
|
||
|
|
|
||
|
|
# Conexion de MT5 (se llena cuando MT5 conecta)
|
||
|
|
mt5_conn = None
|
||
|
|
|
||
|
|
def esperar_mt5():
|
||
|
|
global mt5_conn
|
||
|
|
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
|
|
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
|
|
server_sock.bind((HOST, PORT))
|
||
|
|
server_sock.listen(1)
|
||
|
|
mt5_conn, _ = server_sock.accept() # espera a que MT5 conecte
|
||
|
|
|
||
|
|
# Arranca en hilo separado, no bloquea el MCP
|
||
|
|
# La idea esq ue exista mientras que se termine por fin de conectar con mt5
|
||
|
|
threading.Thread(target=esperar_mt5, daemon=True).start()
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
#| Send |
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
def send(name: str, payload: str) -> str:
|
||
|
|
if mt5_conn is None:
|
||
|
|
return json.dumps({"ok": False, "error": "mt5_no_conectado"})
|
||
|
|
msg = json.dumps({"name": name, "data": json.loads(payload)}) + "\n"
|
||
|
|
mt5_conn.sendall(msg.encode("utf-8"))
|
||
|
|
return mt5_conn.recv(4096).decode("utf-8").strip()
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
# Funciones
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_add_task(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Agrega una nueva tarea de backtest a la tabla de AiDataTaskRunner.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- symbol (string, requerido): Simbolo de trading. Ej: "EURUSD", "XAUUSD"
|
||
|
|
- start_date (string, requerido): Fecha inicio. Formato: "YYYY.MM.DD HH:MM"
|
||
|
|
- end_date (string, requerido): Fecha fin. Formato: "YYYY.MM.DD HH:MM"
|
||
|
|
- timeframe (string, opcional): Temporalidad MQL5. Ej: "PERIOD_H1", "PERIOD_M15". Default: "_Period"
|
||
|
|
- set_file (string, opcional): Ruta al archivo .set de parametros del EA. Default: ""
|
||
|
|
- symbol_folder (string, opcional): Subcarpeta para agrupar datos. Ej: "XAUUSD". Default: ""
|
||
|
|
- label (string, opcional): Nombre de la estrategia. Ej: "BosChoch". Default: ""
|
||
|
|
- label_id (string, opcional): ID variante del label. Ej: "0", "1". Default: "0"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se agrego correctamente
|
||
|
|
- result (string): mensaje de confirmacion o error
|
||
|
|
|
||
|
|
Ejemplo llamada: {"symbol": "EURUSD", "start_date": "2023.01.01 00:00", "end_date": "2024.01.01 00:00"}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "Task added successfully"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_add_task", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_get_task_total(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Retorna el numero total de tareas actualmente en la tabla.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si la consulta fue exitosa
|
||
|
|
- result (integer como string): numero total de tareas
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "5"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_get_task_total", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_get_task_by_index(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Retorna los detalles completos de una tarea segun su indice en la tabla.
|
||
|
|
Usar aidatataskrunner_get_task_total para conocer el total antes de llamar.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- index (integer, requerido): Indice base 0 de la tarea
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si el indice es valido
|
||
|
|
- result (string): datos de la tarea separados por | (timeframe|symbol|set_file|start|end|folder|label|label_id)
|
||
|
|
|
||
|
|
Ejemplo llamada: {"index": 0}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "16385|EURUSD||2023.01.01 00:00|2024.01.01 00:00|||0"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_get_task_by_index", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_get_task_status(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Retorna el estado actual de una tarea segun su indice en la tabla.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- index (integer, requerido): Indice base 0 de la tarea
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si el indice es valido
|
||
|
|
- result (integer como string):
|
||
|
|
"0" = Procesando
|
||
|
|
"1" = En cola
|
||
|
|
"2" = Pendiente
|
||
|
|
"3" = Listo
|
||
|
|
"4" = Fallo
|
||
|
|
|
||
|
|
Ejemplo llamada: {"index": 0}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "2"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_get_task_status", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_clean_all_tasks(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Elimina de la tabla todas las tareas que no esten en ejecucion ni en cola.
|
||
|
|
Las tareas con estado Procesando o En cola no se eliminan.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se limpio al menos una tarea
|
||
|
|
- result (string): mensaje de confirmacion o indicacion de que no habia nada que limpiar
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "All tasks cleaned"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_clean_all_tasks", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_execute_all_tasks(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Pone en cola de ejecucion todas las tareas con estado Pendiente.
|
||
|
|
Operacion fire-and-forget: retorna inmediatamente sin esperar resultado.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): siempre true
|
||
|
|
- result (string): "queued"
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "queued"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_execute_all_tasks", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_save_tasks_to_file(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Guarda las tareas de la tabla en un archivo CSV.
|
||
|
|
La ruta es relativa a la carpeta de trabajo de MT5.
|
||
|
|
Consultar aidatataskrunner_is_in_commonfolder para saber si es Common\\Files\\ o MQL5\\Files\\.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- file_name (string, requerido): Ruta relativa del archivo .csv destino
|
||
|
|
- only_unfinished (boolean, requerido): true = guardar solo pendientes/en cola/procesando | false = guardar todas
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se guardo correctamente
|
||
|
|
- result (string): mensaje de confirmacion o error
|
||
|
|
|
||
|
|
Ejemplo llamada: {"file_name": "tasks.csv", "only_unfinished": false}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "Successfully saving all tasks to file tasks.csv"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_save_tasks_to_file", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_load_tasks_from_file(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Carga tareas desde un archivo CSV y las agrega a la tabla.
|
||
|
|
La ruta es relativa a la carpeta de trabajo de MT5.
|
||
|
|
Consultar aidatataskrunner_is_in_commonfolder para saber si es Common\\Files\\ o MQL5\\Files\\.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- file_name (string, requerido): Ruta relativa del archivo .csv a cargar
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se cargo correctamente
|
||
|
|
- result (string): mensaje de confirmacion o error
|
||
|
|
|
||
|
|
Ejemplo llamada: {"file_name": "tasks.csv"}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "Tasks loaded successfully"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_load_tasks_from_file", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_load_config(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Carga la configuracion del tab de generacion de datos desde un archivo .txt.
|
||
|
|
La ruta es relativa a la carpeta de trabajo de MT5.
|
||
|
|
Consultar aidatataskrunner_is_in_commonfolder para saber si es Common\\Files\\ o MQL5\\Files\\.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- file_name (string, requerido): Ruta relativa del archivo .txt a cargar
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se cargo correctamente
|
||
|
|
- result (string): mensaje de confirmacion o error
|
||
|
|
|
||
|
|
Ejemplo llamada: {"file_name": "config.txt"}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "Config loaded successfully"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_load_config", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_save_config(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Guarda la configuracion actual del tab de generacion de datos en un archivo .txt.
|
||
|
|
La ruta es relativa a la carpeta de trabajo de MT5.
|
||
|
|
Consultar aidatataskrunner_is_in_commonfolder para saber si es Common\\Files\\ o MQL5\\Files\\.
|
||
|
|
|
||
|
|
Parametros (JSON):
|
||
|
|
- file_name (string, requerido): Ruta relativa del archivo .txt destino
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true si se guardo correctamente
|
||
|
|
- result (string): mensaje de confirmacion o error
|
||
|
|
|
||
|
|
Ejemplo llamada: {"file_name": "config.txt"}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "Config saved successfully"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_save_config", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_get_main_folder(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Retorna la ruta de la carpeta base principal usada por AiDataTaskRunner.
|
||
|
|
Relativa a Common\\Files\\ o MQL5\\Files\\ segun aidatataskrunner_is_in_commonfolder.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true siempre
|
||
|
|
- result (string): ruta de la carpeta principal
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "AiDataTaskRunerPro\\"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_get_main_folder", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_get_task_folder(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Retorna la ruta de la carpeta donde se almacenan los archivos de tareas.
|
||
|
|
Relativa a Common\\Files\\ o MQL5\\Files\\ segun aidatataskrunner_is_in_commonfolder.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true siempre
|
||
|
|
- result (string): ruta de la carpeta de tareas
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "AiDataTaskRunerPro\\Tasks\\"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_get_task_folder", payload)
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
@mcp.tool()
|
||
|
|
def aidatataskrunner_is_in_commonfolder(payload: str) -> str:
|
||
|
|
"""
|
||
|
|
Indica si AiDataTaskRunner esta operando sobre la carpeta comun de MT5 (Common\\Files\\)
|
||
|
|
o sobre la carpeta local del terminal (MQL5\\Files\\).
|
||
|
|
Util para construir rutas correctas antes de llamar a otras funciones.
|
||
|
|
|
||
|
|
Parametros: ninguno, pasar JSON vacio "{}"
|
||
|
|
|
||
|
|
Retorna (JSON):
|
||
|
|
- ok (boolean): true siempre
|
||
|
|
- result (string): "1" = usa Common\\Files\\ | "0" = usa MQL5\\Files\\
|
||
|
|
|
||
|
|
Ejemplo llamada: {}
|
||
|
|
Ejemplo retorno: {"ok": true, "result": "1"}
|
||
|
|
"""
|
||
|
|
return send("aidatataskrunner_is_in_commonfolder", payload)
|
||
|
|
|
||
|
|
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
#| Main |
|
||
|
|
#+------------------------------------------------------------------+
|
||
|
|
def main():
|
||
|
|
mcp.run()
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|