mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-28 03:07:48 +00:00
4cb4f4a15e
- 重构核心模块: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>
144 lines
3.7 KiB
Python
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)
|