mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-08-01 21:27:44 +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:
+42
-34
@@ -1,16 +1,24 @@
|
||||
import MetaTrader5 as mt5
|
||||
# 惰性导入 — ARM 环境不装 MetaTrader5
|
||||
_mt5 = None
|
||||
|
||||
def _get_mt5():
|
||||
global _mt5
|
||||
if _mt5 is None:
|
||||
import MetaTrader5 as _mt5_module
|
||||
return _mt5
|
||||
|
||||
import pandas as pd
|
||||
from logger import setup_logger
|
||||
logger = setup_logger()
|
||||
|
||||
def initialize():
|
||||
if not mt5.initialize():
|
||||
logger.error("MT5初始化失败,错误代码:%d", mt5.last_error())
|
||||
if not _get_mt5().initialize():
|
||||
logger.error("MT5初始化失败,错误代码:%d", _get_mt5().last_error())
|
||||
return False
|
||||
return True
|
||||
|
||||
def shutdown():
|
||||
mt5.shutdown()
|
||||
_get_mt5().shutdown()
|
||||
|
||||
def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||
"""
|
||||
@@ -23,21 +31,21 @@ def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||
- end_date: 结束日期 (格式: "YYYY-MM-DD" 或 datetime对象)
|
||||
"""
|
||||
# 检查MT5连接状态
|
||||
if not mt5.terminal_info():
|
||||
if not _get_mt5().terminal_info():
|
||||
logger.warning("MT5终端未连接,尝试重新连接...")
|
||||
if not initialize():
|
||||
logger.error("MT5重新连接失败")
|
||||
return None
|
||||
|
||||
# 检查交易品种是否可用
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
symbol_info = _get_mt5().symbol_info(symbol)
|
||||
if symbol_info is None:
|
||||
logger.error(f"交易品种 {symbol} 不可用")
|
||||
return None
|
||||
|
||||
if not symbol_info.visible:
|
||||
logger.info(f"交易品种 {symbol} 不可见,尝试启用...")
|
||||
if not mt5.symbol_select(symbol, True):
|
||||
if not _get_mt5().symbol_select(symbol, True):
|
||||
logger.error(f"无法启用交易品种 {symbol}")
|
||||
return None
|
||||
if start_date is not None and end_date is not None:
|
||||
@@ -58,7 +66,7 @@ def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||
end_utc = end_date.replace(hour=23, minute=59, second=59, tzinfo=utc_timezone)
|
||||
|
||||
try:
|
||||
rates = mt5.copy_rates_range(symbol, timeframe, start_utc, end_utc)
|
||||
rates = _get_mt5().copy_rates_range(symbol, timeframe, start_utc, end_utc)
|
||||
if rates is None:
|
||||
logger.info(f"获取{symbol}从{start_date.date()}到{end_date.date()}的历史数据失败")
|
||||
return None
|
||||
@@ -66,14 +74,14 @@ def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||
except Exception as e:
|
||||
logger.error(f"使用日期范围获取数据失败: {e}")
|
||||
logger.info("回退到使用数据量获取数据")
|
||||
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
rates = _get_mt5().copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
if rates is None:
|
||||
logger.info(f"获取{symbol}历史数据失败")
|
||||
return None
|
||||
logger.debug(f"成功获取{symbol}历史数据,共{len(rates)}条")
|
||||
else:
|
||||
# 使用数据量获取数据
|
||||
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
rates = _get_mt5().copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
if rates is None:
|
||||
logger.info(f"获取{symbol}历史数据失败")
|
||||
return None
|
||||
@@ -82,39 +90,39 @@ def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||
return rates
|
||||
|
||||
def has_open_position(symbol):
|
||||
positions = mt5.positions_get(symbol=symbol)
|
||||
positions = _get_mt5().positions_get(symbol=symbol)
|
||||
return positions is not None and len(positions) > 0
|
||||
|
||||
def close_all(symbol):
|
||||
positions = mt5.positions_get(symbol=symbol)
|
||||
positions = _get_mt5().positions_get(symbol=symbol)
|
||||
if positions is None:
|
||||
return
|
||||
for pos in positions:
|
||||
request = {
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"action": _get_mt5().TRADE_ACTION_DEAL,
|
||||
"position": pos.ticket,
|
||||
"symbol": symbol,
|
||||
"volume": pos.volume,
|
||||
"type": mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY,
|
||||
"price": mt5.symbol_info_tick(symbol).bid if pos.type == 0 else mt5.symbol_info_tick(symbol).ask,
|
||||
"type": _get_mt5().ORDER_TYPE_SELL if pos.type == 0 else _get_mt5().ORDER_TYPE_BUY,
|
||||
"price": _get_mt5().symbol_info_tick(symbol).bid if pos.type == 0 else _get_mt5().symbol_info_tick(symbol).ask,
|
||||
"deviation": 20,
|
||||
"magic": 234000,
|
||||
"comment": "Close position",
|
||||
"type_filling": mt5.ORDER_FILLING_RETURN,
|
||||
"type_filling": _get_mt5().ORDER_FILLING_RETURN,
|
||||
}
|
||||
mt5.order_send(request)
|
||||
_get_mt5().order_send(request)
|
||||
|
||||
def send_order(symbol, order_type, volume=0.01):
|
||||
symbol_info_tick = mt5.symbol_info_tick(symbol)
|
||||
symbol_info_tick = _get_mt5().symbol_info_tick(symbol)
|
||||
if symbol_info_tick is None:
|
||||
logger.error(f"无法获取{symbol}行情")
|
||||
return None
|
||||
|
||||
price = symbol_info_tick.ask if order_type == "buy" else symbol_info_tick.bid
|
||||
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,
|
||||
@@ -122,11 +130,11 @@ def send_order(symbol, order_type, volume=0.01):
|
||||
"deviation": 20,
|
||||
"magic": 234000,
|
||||
"comment": f"{order_type} order",
|
||||
"type_filling": mt5.ORDER_FILLING_RETURN,
|
||||
"type_filling": _get_mt5().ORDER_FILLING_RETURN,
|
||||
}
|
||||
|
||||
result = mt5.order_send(request)
|
||||
if result.retcode != mt5.TRADE_RETCODE_DONE:
|
||||
result = _get_mt5().order_send(request)
|
||||
if result.retcode != _get_mt5().TRADE_RETCODE_DONE:
|
||||
logger.error(f"下单失败,retcode={result.retcode}")
|
||||
return None
|
||||
else:
|
||||
@@ -137,7 +145,7 @@ def close_position(ticket, symbol, volume):
|
||||
"""根据ticket平掉一个特定的仓位"""
|
||||
# In MT5, you close a position by creating an opposite order.
|
||||
# We need to get the position details first.
|
||||
positions = mt5.positions_get(ticket=ticket)
|
||||
positions = _get_mt5().positions_get(ticket=ticket)
|
||||
if not positions:
|
||||
logger.error(f"无法找到ticket为 {ticket} 的持仓")
|
||||
return False
|
||||
@@ -145,19 +153,19 @@ def close_position(ticket, symbol, volume):
|
||||
pos = positions[0] # positions_get returns a tuple of objects
|
||||
|
||||
request = {
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"action": _get_mt5().TRADE_ACTION_DEAL,
|
||||
"position": pos.ticket,
|
||||
"symbol": symbol,
|
||||
"volume": volume,
|
||||
"type": mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY, # pos.type == 0 is a BUY position
|
||||
"price": mt5.symbol_info_tick(symbol).bid if pos.type == 0 else mt5.symbol_info_tick(symbol).ask,
|
||||
"type": _get_mt5().ORDER_TYPE_SELL if pos.type == 0 else _get_mt5().ORDER_TYPE_BUY, # pos.type == 0 is a BUY position
|
||||
"price": _get_mt5().symbol_info_tick(symbol).bid if pos.type == 0 else _get_mt5().symbol_info_tick(symbol).ask,
|
||||
"deviation": 20,
|
||||
"magic": 234000,
|
||||
"comment": f"Close position {ticket}",
|
||||
"type_filling": mt5.ORDER_FILLING_RETURN,
|
||||
"type_filling": _get_mt5().ORDER_FILLING_RETURN,
|
||||
}
|
||||
result = mt5.order_send(request)
|
||||
if result.retcode != mt5.TRADE_RETCODE_DONE:
|
||||
result = _get_mt5().order_send(request)
|
||||
if result.retcode != _get_mt5().TRADE_RETCODE_DONE:
|
||||
logger.error(f"平仓失败 ticket {ticket}, retcode={result.retcode}")
|
||||
return False
|
||||
else:
|
||||
@@ -168,25 +176,25 @@ def get_current_price(symbol):
|
||||
"""获取当前价格"""
|
||||
try:
|
||||
# 检查MT5连接状态
|
||||
if not mt5.terminal_info():
|
||||
if not _get_mt5().terminal_info():
|
||||
logger.warning("MT5终端未连接,尝试重新连接...")
|
||||
if not initialize():
|
||||
logger.error("MT5重新连接失败")
|
||||
return None
|
||||
|
||||
# 检查交易品种是否可用
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
symbol_info = _get_mt5().symbol_info(symbol)
|
||||
if symbol_info is None:
|
||||
logger.error(f"交易品种 {symbol} 不可用")
|
||||
return None
|
||||
|
||||
if not symbol_info.visible:
|
||||
logger.info(f"交易品种 {symbol} 不可见,尝试启用...")
|
||||
if not mt5.symbol_select(symbol, True):
|
||||
if not _get_mt5().symbol_select(symbol, True):
|
||||
logger.error(f"无法启用交易品种 {symbol}")
|
||||
return None
|
||||
|
||||
tick = mt5.symbol_info_tick(symbol)
|
||||
tick = _get_mt5().symbol_info_tick(symbol)
|
||||
if tick is None:
|
||||
logger.error(f"无法获取 {symbol} 的价格信息")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user