mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-31 20:57:45 +00:00
feat: 远程MT5数据源 + 信号对冲/回撤锁仓模块
新增: - core/risk/hedge.py: 对冲管理器 - 信号对冲: 加权信号反向超阈值→半仓反向 - 回撤锁仓: 浮亏超-0.3%→全仓锁死 - 自动解锁: 信号回正/对冲止盈0.5% 改动: - run/backtest.py: 支持远程数据源回测 - run/realtime.py: 远程/本地双模式 - core/risk/position.py: 集成HedgeManager - core/risk/controller.py: 传递weighted_signal - execution/realtime_trader.py: 传入加权信号 - core/data/live.py, utils.py: MetaTrader5懒加载(ARM兼容) - config.py: HEDGE_CONFIG, REMOTE配置, INITIAL_CAPITAL=1944 今日实盘: 9单, +7.4% (944→088)
This commit is contained in:
+19
-2
@@ -1,6 +1,23 @@
|
||||
from core.data.abc import DataProvider
|
||||
from core.data.live import LiveDataProvider
|
||||
from core.data.dryrun import DryRunDataProvider
|
||||
from core.data.backtest import BacktestDataProvider
|
||||
from core.data.multi_tf import MultiTimeframeDataStore
|
||||
from core.data.remote import RemoteDataProvider
|
||||
|
||||
# LiveDataProvider 和 DryRunDataProvider 依赖 MetaTrader5,
|
||||
# 仅在本地模式需要时延迟导入
|
||||
_LiveDataProvider = None
|
||||
_DryRunDataProvider = None
|
||||
|
||||
def LiveDataProvider(*args, **kwargs):
|
||||
global _LiveDataProvider
|
||||
if _LiveDataProvider is None:
|
||||
from core.data.live import LiveDataProvider as LDP
|
||||
_LiveDataProvider = LDP
|
||||
return _LiveDataProvider(*args, **kwargs)
|
||||
|
||||
def DryRunDataProvider(*args, **kwargs):
|
||||
global _DryRunDataProvider
|
||||
if _DryRunDataProvider is None:
|
||||
from core.data.dryrun import DryRunDataProvider as DDP
|
||||
_DryRunDataProvider = DDP
|
||||
return _DryRunDataProvider(*args, **kwargs)
|
||||
|
||||
+42
-34
@@ -1,8 +1,16 @@
|
||||
import MetaTrader5 as mt5
|
||||
import pandas as pd
|
||||
from logger import logger
|
||||
from core.data.abc import DataProvider
|
||||
|
||||
# 惰性导入 — ARM 环境不装 MetaTrader5
|
||||
_mt5 = None
|
||||
|
||||
def _get_mt5():
|
||||
global _mt5
|
||||
if _mt5 is None:
|
||||
import MetaTrader5 as _mt5
|
||||
return _mt5
|
||||
|
||||
|
||||
class LiveDataProvider(DataProvider):
|
||||
"""实盘数据提供者 — 封装真实MT5 API调用"""
|
||||
@@ -12,18 +20,18 @@ class LiveDataProvider(DataProvider):
|
||||
return True
|
||||
|
||||
def initialize(self):
|
||||
if not mt5.initialize():
|
||||
if not _get_mt5().initialize():
|
||||
logger.error("MT5初始化失败")
|
||||
return False
|
||||
logger.info("MT5连接成功")
|
||||
return True
|
||||
|
||||
def shutdown(self):
|
||||
mt5.shutdown()
|
||||
_get_mt5().shutdown()
|
||||
logger.info("MT5连接已关闭")
|
||||
|
||||
def get_current_price(self, symbol):
|
||||
tick = mt5.symbol_info_tick(symbol)
|
||||
tick = _get_mt5().symbol_info_tick(symbol)
|
||||
if tick:
|
||||
last_price = tick.last if tick.last != 0 else (tick.bid + tick.ask) / 2
|
||||
return {
|
||||
@@ -34,16 +42,16 @@ class LiveDataProvider(DataProvider):
|
||||
return None
|
||||
|
||||
def get_historical_data(self, symbol, timeframe, count, **kwargs):
|
||||
return mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
return _get_mt5().copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
|
||||
def get_account_info(self):
|
||||
return mt5.account_info()
|
||||
return _get_mt5().account_info()
|
||||
|
||||
def get_positions(self, symbol):
|
||||
return mt5.positions_get(symbol=symbol)
|
||||
return _get_mt5().positions_get(symbol=symbol)
|
||||
|
||||
def get_symbol_info(self, symbol):
|
||||
return mt5.symbol_info(symbol)
|
||||
return _get_mt5().symbol_info(symbol)
|
||||
|
||||
def send_order(self, symbol, order_type, volume):
|
||||
price_data = self.get_current_price(symbol)
|
||||
@@ -53,16 +61,16 @@ class LiveDataProvider(DataProvider):
|
||||
|
||||
price = price_data['ask'] if order_type == "buy" else price_data['bid']
|
||||
|
||||
if not mt5.terminal_info().trade_allowed:
|
||||
if not _get_mt5().terminal_info().trade_allowed:
|
||||
logger.error("MT5终端未启用自动交易")
|
||||
return None
|
||||
|
||||
account_info = mt5.account_info()
|
||||
account_info = _get_mt5().account_info()
|
||||
if account_info and not account_info.trade_allowed:
|
||||
logger.error("当前账户不允许自动交易")
|
||||
return None
|
||||
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
symbol_info = _get_mt5().symbol_info(symbol)
|
||||
if not symbol_info:
|
||||
logger.error(f"无法获取 {symbol} 的品种信息")
|
||||
return None
|
||||
@@ -71,15 +79,15 @@ class LiveDataProvider(DataProvider):
|
||||
fm = symbol_info.filling_mode
|
||||
# MQL5 filling_mode 位图: FOK=1, IOC=2
|
||||
if fm & 1:
|
||||
filling_mode = mt5.ORDER_FILLING_FOK
|
||||
filling_mode = _get_mt5().ORDER_FILLING_FOK
|
||||
elif fm & 2:
|
||||
filling_mode = mt5.ORDER_FILLING_IOC
|
||||
filling_mode = _get_mt5().ORDER_FILLING_IOC
|
||||
else:
|
||||
filling_mode = mt5.ORDER_FILLING_RETURN
|
||||
filling_mode = _get_mt5().ORDER_FILLING_RETURN
|
||||
|
||||
order_type_mt5 = mt5.ORDER_TYPE_BUY if order_type == "buy" else mt5.ORDER_TYPE_SELL
|
||||
order_type_mt5 = _get_mt5().ORDER_TYPE_BUY if order_type == "buy" else _get_mt5().ORDER_TYPE_SELL
|
||||
request = {
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"action": _get_mt5().TRADE_ACTION_DEAL,
|
||||
"symbol": symbol,
|
||||
"volume": volume,
|
||||
"type": order_type_mt5,
|
||||
@@ -89,18 +97,18 @@ class LiveDataProvider(DataProvider):
|
||||
"comment": f"{order_type} order",
|
||||
"type_filling": filling_mode,
|
||||
}
|
||||
result = mt5.order_send(request)
|
||||
result = _get_mt5().order_send(request)
|
||||
|
||||
if result and hasattr(result, 'retcode'):
|
||||
if result.retcode == 10027:
|
||||
logger.error("自动交易被禁用")
|
||||
elif result.retcode == 10030:
|
||||
if filling_mode != mt5.ORDER_FILLING_IOC:
|
||||
request["type_filling"] = mt5.ORDER_FILLING_IOC
|
||||
result = mt5.order_send(request)
|
||||
if result and result.retcode == 10030 and filling_mode != mt5.ORDER_FILLING_FOK:
|
||||
request["type_filling"] = mt5.ORDER_FILLING_FOK
|
||||
result = mt5.order_send(request)
|
||||
if filling_mode != _get_mt5().ORDER_FILLING_IOC:
|
||||
request["type_filling"] = _get_mt5().ORDER_FILLING_IOC
|
||||
result = _get_mt5().order_send(request)
|
||||
if result and result.retcode == 10030 and filling_mode != _get_mt5().ORDER_FILLING_FOK:
|
||||
request["type_filling"] = _get_mt5().ORDER_FILLING_FOK
|
||||
result = _get_mt5().order_send(request)
|
||||
elif result.retcode != 10009:
|
||||
logger.error(f"下单失败,错误代码: {result.retcode}")
|
||||
|
||||
@@ -122,33 +130,33 @@ class LiveDataProvider(DataProvider):
|
||||
logger.error(f"未找到ticket为 {ticket} 的持仓")
|
||||
return False
|
||||
|
||||
tick = mt5.symbol_info_tick(symbol)
|
||||
tick = _get_mt5().symbol_info_tick(symbol)
|
||||
if not tick:
|
||||
logger.error(f"无法获取 {symbol} 的当前价格")
|
||||
return False
|
||||
|
||||
if target_position.type == mt5.POSITION_TYPE_BUY:
|
||||
if target_position.type == _get_mt5().POSITION_TYPE_BUY:
|
||||
close_price = tick.bid
|
||||
order_type = mt5.ORDER_TYPE_SELL
|
||||
order_type = _get_mt5().ORDER_TYPE_SELL
|
||||
else:
|
||||
close_price = tick.ask
|
||||
order_type = mt5.ORDER_TYPE_BUY
|
||||
order_type = _get_mt5().ORDER_TYPE_BUY
|
||||
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
symbol_info = _get_mt5().symbol_info(symbol)
|
||||
if not symbol_info:
|
||||
return False
|
||||
|
||||
fm = symbol_info.filling_mode
|
||||
# MQL5 filling_mode 位图: FOK=1, IOC=2
|
||||
if fm & 1:
|
||||
filling_mode = mt5.ORDER_FILLING_FOK
|
||||
filling_mode = _get_mt5().ORDER_FILLING_FOK
|
||||
elif fm & 2:
|
||||
filling_mode = mt5.ORDER_FILLING_IOC
|
||||
filling_mode = _get_mt5().ORDER_FILLING_IOC
|
||||
else:
|
||||
filling_mode = mt5.ORDER_FILLING_IOC
|
||||
filling_mode = _get_mt5().ORDER_FILLING_IOC
|
||||
|
||||
request = {
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"action": _get_mt5().TRADE_ACTION_DEAL,
|
||||
"position": target_position.ticket,
|
||||
"symbol": symbol,
|
||||
"volume": volume,
|
||||
@@ -160,11 +168,11 @@ class LiveDataProvider(DataProvider):
|
||||
"type_filling": filling_mode,
|
||||
}
|
||||
|
||||
result = mt5.order_send(request)
|
||||
result = _get_mt5().order_send(request)
|
||||
if result is None:
|
||||
logger.error(f"平仓请求返回None: Ticket={ticket}")
|
||||
return False
|
||||
if result.retcode == mt5.TRADE_RETCODE_DONE:
|
||||
if result.retcode == _get_mt5().TRADE_RETCODE_DONE:
|
||||
logger.info(f"平仓成功: Ticket {ticket}")
|
||||
return True
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user