Files
mt5_python_ea_suite/run/server.py
T
silencesdg 4cb4f4a15e 重构项目架构,新增 MT5 代理服务
- 重构核心模块:DataProvider 依赖注入、RiskController 门面、信号注册表
- 新增 FastAPI 代理服务 (run/server.py),支持局域网远程调用 MT5
- 新增 RemoteDataProvider + AttrDict,远端无缝替代 LiveDataProvider
- 新增序列化模块,MT5 对象转 JSON 兼容格式
- 重构入口点至 run/ 包,支持 python -m run.realtime/server/backtest/optimize
- 更新 CLAUDE.md 文档

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-05-11 12:00:45 +08:00

144 lines
3.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""MT5 代理服务 — python -m run.server"""
import sys
import os
import threading
from contextlib import asynccontextmanager
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from core.data import LiveDataProvider
from core.data.serializers import (
serialize_account_info,
serialize_position,
serialize_symbol_info,
serialize_order_result,
serialize_rates,
)
from config import SERVER_HOST, SERVER_PORT
from logger import setup_logger, logger
setup_logger("INFO")
# 全局 MT5 连接和线程锁
_provider = LiveDataProvider()
_lock = threading.Lock()
@asynccontextmanager
async def lifespan(app: FastAPI):
with _lock:
if not _provider.initialize():
logger.error("MT5 初始化失败,服务启动中止")
sys.exit(1)
logger.info(f"MT5 代理服务启动于 {SERVER_HOST}:{SERVER_PORT}")
yield
with _lock:
_provider.shutdown()
logger.info("MT5 代理服务已关闭")
app = FastAPI(title="MT5 Proxy API", lifespan=lifespan)
# ── 请求模型 ──
class OrderRequest(BaseModel):
symbol: str
order_type: str
volume: float
class CloseRequest(BaseModel):
ticket: int
symbol: str
volume: float
# ── 端点 ──
@app.post("/api/initialize")
def api_initialize():
with _lock:
return {"success": _provider.initialize()}
@app.post("/api/shutdown")
def api_shutdown():
with _lock:
_provider.shutdown()
return {"success": True}
@app.get("/api/price/{symbol}")
def api_price(symbol: str):
with _lock:
data = _provider.get_current_price(symbol)
if data is None:
return JSONResponse(status_code=503, content={"error": "无法获取价格"})
# time 转为 Unix 时间戳
data['time'] = int(data['time'].timestamp()) if hasattr(data['time'], 'timestamp') else int(data['time'])
return data
@app.get("/api/historical/{symbol}/{timeframe}/{count}")
def api_historical(symbol: str, timeframe: int, count: int):
with _lock:
rates = _provider.get_historical_data(symbol, timeframe, count)
if rates is None:
return JSONResponse(status_code=503, content={"error": "无法获取历史数据"})
return {"rates": serialize_rates(rates)}
@app.get("/api/account")
def api_account():
with _lock:
info = _provider.get_account_info()
if info is None:
return JSONResponse(status_code=503, content={"error": "无法获取账户信息"})
return serialize_account_info(info)
@app.get("/api/positions/{symbol}")
def api_positions(symbol: str):
with _lock:
positions = _provider.get_positions(symbol)
if positions is None:
return {"positions": []}
return {"positions": [serialize_position(p) for p in positions]}
@app.get("/api/symbol/{symbol}")
def api_symbol(symbol: str):
with _lock:
info = _provider.get_symbol_info(symbol)
if info is None:
return JSONResponse(status_code=503, content={"error": "无法获取品种信息"})
return serialize_symbol_info(info)
@app.post("/api/order")
def api_order(req: OrderRequest):
with _lock:
result = _provider.send_order(req.symbol, req.order_type, req.volume)
if result is None:
return JSONResponse(status_code=503, content={"error": "下单失败"})
return serialize_order_result(result)
@app.post("/api/close")
def api_close(req: CloseRequest):
with _lock:
success = _provider.close_position(req.ticket, req.symbol, req.volume)
return {"success": success}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host=SERVER_HOST, port=SERVER_PORT)