mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-27 18:57:44 +00:00
重构项目架构,新增 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>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""回测入口 — python -m run.backtest"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from core.utils import get_rates, initialize, shutdown
|
||||
from execution.backtest import BacktestEngine
|
||||
from config import SYMBOL, TIMEFRAME, BACKTEST_COUNT, BACKTEST_START_DATE, BACKTEST_END_DATE, USE_DATE_RANGE
|
||||
from logger import logger
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("MT5 智能交易系统 - 回测")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
initialize()
|
||||
if USE_DATE_RANGE:
|
||||
rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT,
|
||||
BACKTEST_START_DATE, BACKTEST_END_DATE)
|
||||
else:
|
||||
rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT)
|
||||
shutdown()
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
logger.error("未能获取历史数据,回测终止。")
|
||||
return
|
||||
|
||||
logger.info(f"获取数据: {len(rates)} 条")
|
||||
|
||||
engine = BacktestEngine(use_dynamic_weights=False)
|
||||
summary = engine.run(rates)
|
||||
print(f"\n回测完成!总盈亏: ${summary.get('total_profit_loss', 0):.2f}")
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"回测出错: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""优化器入口 — python -m run.optimize"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from execution.optimize import run_optimizer
|
||||
from logger import logger
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("MT5 智能交易系统 - 遗传算法参数优化")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
best_params, best_fitness = run_optimizer()
|
||||
print(f"\n优化完成!最佳适应度: {best_fitness:.2f}")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"优化器运行出错: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""实时交易入口 — python -m run.realtime"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from execution.realtime_trader import RealtimeTrader
|
||||
from core.data import LiveDataProvider, DryRunDataProvider
|
||||
from config import REALTIME_CONFIG, INITIAL_CAPITAL
|
||||
from logger import setup_logger
|
||||
|
||||
|
||||
def main():
|
||||
log_level = REALTIME_CONFIG.get('logging_level', 'INFO')
|
||||
setup_logger(log_level)
|
||||
|
||||
is_dry_run = REALTIME_CONFIG.get('dry_run', True)
|
||||
|
||||
print("=" * 60)
|
||||
print("MT5 智能交易系统 - 实时交易")
|
||||
print(f"启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"运行模式: {'模拟运行' if is_dry_run else '实盘交易'}")
|
||||
print("=" * 60)
|
||||
|
||||
if is_dry_run:
|
||||
data_provider = DryRunDataProvider(initial_equity=INITIAL_CAPITAL)
|
||||
else:
|
||||
print("WARNING: 即将启动实盘交易模式!")
|
||||
confirm = input("确认启动实盘交易?(输入 'YES' 继续): ")
|
||||
if confirm != 'YES':
|
||||
print("已取消启动。")
|
||||
return
|
||||
data_provider = LiveDataProvider()
|
||||
|
||||
trader = RealtimeTrader(data_provider, update_interval=REALTIME_CONFIG['update_interval'])
|
||||
print("\n正在启动交易系统... (按 Ctrl+C 可安全停止)")
|
||||
trader.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user