From 50ee1a5a0ab5b8247841be3664f119d957ce5cf7 Mon Sep 17 00:00:00 2001 From: liaozhaorun <1300336796@qq.com> Date: Sat, 10 Jan 2026 04:06:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0qmt=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A=E7=AB=AFqmt=E7=99=BB?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qmt/api_server.py | 123 ++++++---- qmt/dashboard.html | 227 +++++++++---------- qmt/qmt_engine.py | 544 +++++++++++++++++++++------------------------ qmt/qmt_test.py | 8 +- qmt/run.py | 68 +++--- qmt/start.bat | 12 +- 6 files changed, 487 insertions(+), 495 deletions(-) diff --git a/qmt/api_server.py b/qmt/api_server.py index 39ff4d1..4c33c61 100644 --- a/qmt/api_server.py +++ b/qmt/api_server.py @@ -8,37 +8,46 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from pydantic import BaseModel -from qmt_engine import QMTEngine, QMTStatus +# 导入新的管理器类 +from qmt_engine import MultiEngineManager, TerminalStatus # ================= Pydantic模型 ================= -class StatusResponse(BaseModel): - """状态响应模型""" - running: bool - qmt_connected: bool - start_time: str - last_loop_update: str - account_id: str +class TerminalStatusModel(BaseModel): + """单个终端状态模型""" + qmt_id: str + alias: str + account_id: str + is_connected: bool + last_heartbeat: str + +class StatusResponse(BaseModel): + """全局状态响应模型""" + running: bool + start_time: str + terminals: List[TerminalStatusModel] class PositionsResponse(BaseModel): """持仓响应模型""" - real_positions: List[Dict[str, Any]] + # 按 qmt_id 分组的实盘持仓 + real_positions: Dict[str, List[Dict[str, Any]]] + # 按策略名分组的虚拟持仓 virtual_positions: Dict[str, Dict[str, str]] - class LogsResponse(BaseModel): """日志响应模型""" logs: List[str] # ================= FastAPI应用 ================= + class QMTAPIServer: - """QMT API服务器""" + """多终端 QMT API服务器""" - def __init__(self, qmt_engine: QMTEngine): - self.app = FastAPI(title="QMT Monitor") - self.qmt_engine = qmt_engine + def __init__(self, manager: MultiEngineManager): + self.app = FastAPI(title="QMT Multi-Terminal Monitor") + self.manager = manager self._setup_middleware() self._setup_routes() @@ -61,41 +70,78 @@ class QMTAPIServer: return FileResponse("dashboard.html") return {"error": "Dashboard not found"} - @self.app.get("/api/status", response_model=StatusResponse, summary="获取系统状态") + @self.app.get("/api/status", response_model=StatusResponse, summary="获取所有终端状态") def get_status(): - """获取QMT连接状态和系统信息""" - status = self.qmt_engine.get_status() + """获取所有 QMT 终端的连接状态""" + terminal_data = self.manager.get_all_status() + + terminals = [ + TerminalStatusModel( + qmt_id=t.qmt_id, + alias=t.alias, + account_id=t.account_id, + is_connected=t.is_connected, + last_heartbeat=t.last_heartbeat + ) for t in terminal_data + ] + return StatusResponse( - running=status.is_running, - qmt_connected=status.is_connected, - start_time=status.start_time, - last_loop_update=status.last_heartbeat, - account_id=status.account_id + running=self.manager.is_running, + start_time=self.manager.start_time, + terminals=terminals ) @self.app.get("/api/positions", response_model=PositionsResponse, summary="获取持仓信息") def get_positions(): - """获取实盘和虚拟持仓信息""" - positions = self.qmt_engine.get_positions() + """汇总所有终端的实盘持仓和所有策略的虚拟持仓""" + real_pos_data = {} + virtual_pos_data = {} + + # 1. 遍历所有终端单元获取实盘持仓 + for qmt_id, unit in self.manager.units.items(): + positions = [] + if unit.callback and unit.callback.is_connected: + try: + xt_pos = unit.xt_trader.query_stock_positions(unit.acc_obj) + if xt_pos: + positions = [ + { + "code": p.stock_code, + "volume": p.volume, + "can_use": p.can_use_volume, + "market_value": round(p.market_value, 2) + } for p in xt_pos if p.volume > 0 + ] + except: + pass + real_pos_data[qmt_id] = positions + + # 2. 遍历所有策略获取虚拟持仓 + for s_name in self.manager.config.get('strategies', {}).keys(): + v_data = self.manager.pos_manager.get_all_virtual_positions(s_name) + virtual_pos_data[s_name] = v_data + return PositionsResponse( - real_positions=positions["real_positions"], - virtual_positions=positions["virtual_positions"] + real_positions=real_pos_data, + virtual_positions=virtual_pos_data ) - @self.app.get("/api/logs", response_model=LogsResponse, summary="获取日志") - def get_logs(lines: int = Query(50, ge=1, le=1000, description="返回日志行数")): - """获取最近的交易日志""" - logs = self.qmt_engine.get_logs(lines) + @self.app.get("/api/logs", response_model=LogsResponse, summary="获取系统日志") + def get_logs(lines: int = Query(50, ge=1, le=1000)): + """获取最近的系统运行日志""" + logs = self.manager.get_logs(lines) return LogsResponse(logs=logs) @self.app.get("/api/health", summary="健康检查") def health_check(): - """健康检查接口""" - status = self.qmt_engine.get_status() - if status.is_running and status.is_connected: + """健康检查:只要有一个终端在线即视为正常""" + terminal_data = self.manager.get_all_status() + any_connected = any(t.is_connected for t in terminal_data) + + if self.manager.is_running and any_connected: return {"status": "healthy", "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} else: - return {"status": "unhealthy", "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + return {"status": "unhealthy", "reason": "No terminals connected", "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} def get_app(self) -> FastAPI: """获取FastAPI应用实例""" @@ -103,7 +149,8 @@ class QMTAPIServer: # ================= 辅助函数 ================= -def create_api_server(qmt_engine: QMTEngine) -> FastAPI: - """创建API服务器""" - server = QMTAPIServer(qmt_engine) - return server.get_app() + +def create_api_server(manager: MultiEngineManager) -> FastAPI: + """创建API服务器入口""" + server = QMTAPIServer(manager) + return server.get_app() \ No newline at end of file diff --git a/qmt/dashboard.html b/qmt/dashboard.html index 49b14b0..bc82f29 100644 --- a/qmt/dashboard.html +++ b/qmt/dashboard.html @@ -3,7 +3,7 @@
-