This commit is contained in:
lz_db
2025-11-17 19:36:28 +08:00
parent 42a0391eeb
commit da459da0f3
4 changed files with 324 additions and 188 deletions

View File

@@ -41,6 +41,14 @@ class mt5(mt5Parent):
"""监听所有数据(订单、持仓、钱包)"""
if not hasattr(self, 'token') or not self.token:
await self.get_token()
# 检查连接状态
try:
await self.check_connect()
except Exception as e:
if self.verbose:
print(f"连接检查失败,重新连接: {e}")
await self.get_token()
url = self.urls['api']['ws'] + '/OnOrderUpdate?id=' + self.token
message_hash = 'all_data'
@@ -153,40 +161,6 @@ class mt5(mt5Parent):
print(f"❌ 行情监听失败: {e}")
raise e
async def close_ticker(self, symbol: str, params={}):
"""取消行情订阅"""
await self.load_markets()
market = self.market(symbol)
if not hasattr(self, 'token') or not self.token:
await self.get_token()
# 取消订阅参数
request = {
'id': self.token,
'symbol': market['id'],
}
try:
# 发送取消订阅请求
result = await self.private_get('UnSubscribe', self.extend(request, params))
# 更新订阅状态
if hasattr(self, 'ticker_subscriptions'):
subscribe_hash = 'ticker:' + market['id']
if subscribe_hash in self.ticker_subscriptions:
self.ticker_subscriptions.remove(subscribe_hash)
if self.verbose:
print(f"✅ 取消行情订阅成功: {symbol}")
return result
except Exception as e:
if self.verbose:
print(f"❌ 取消行情订阅失败: {e}")
raise e
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Optional[int] = None, limit: Optional[int] = None, params={}):
await self.load_markets()
market = self.market(symbol)
@@ -238,6 +212,40 @@ class mt5(mt5Parent):
print(f"❌ K线监听失败: {e}")
raise e
async def close_ticker(self, symbol: str, params={}):
"""取消行情订阅"""
await self.load_markets()
market = self.market(symbol)
if not hasattr(self, 'token') or not self.token:
await self.get_token()
# 取消订阅参数
request = {
'id': self.token,
'symbol': market['id'],
}
try:
# 发送取消订阅请求
result = await self.private_get('UnSubscribe', self.extend(request, params))
# 更新订阅状态
if hasattr(self, 'ticker_subscriptions'):
subscribe_hash = 'ticker:' + market['id']
if subscribe_hash in self.ticker_subscriptions:
self.ticker_subscriptions.remove(subscribe_hash)
if self.verbose:
print(f"✅ 取消行情订阅成功: {symbol}")
return result
except Exception as e:
if self.verbose:
print(f"❌ 取消行情订阅失败: {e}")
raise e
async def close_ohlcv(self, symbol: Optional[str] = None, timeframe: Optional[str] = None, params={}):
"""取消K线订阅"""
if not hasattr(self, 'token') or not self.token:
@@ -291,29 +299,37 @@ class mt5(mt5Parent):
if self.verbose:
print(f"❌ 取消K线订阅失败: {e}")
raise e
async def send_ticker_subscription(self, request, params={}):
"""发送行情数据订阅请求"""
async def close(self):
"""关闭所有连接和清理缓存"""
try:
# 使用 private_get 发送订阅
response = await self.private_get('Subscribe', self.extend(request, params))
# 取消所有订阅
if hasattr(self, 'ohlcv_subscriptions') and self.ohlcv_subscriptions:
await self.close_ohlcv()
if hasattr(self, 'ticker_subscriptions') and self.ticker_subscriptions:
# 取消所有ticker订阅
for subscribe_hash in list(self.ticker_subscriptions):
symbol = subscribe_hash.replace('ticker:', '')
try:
await self.close_ticker(symbol)
except Exception:
pass
# 清理缓存
self.orders_list = []
self.positions_list = []
self.ohlcv_cache = {}
self.ticker_cache = {}
# 断开连接
await self.disconnect()
if self.verbose:
print(f"✅ 行情订阅响应: {response}")
# 检查订阅是否成功
if response in [True, 'OK', 'SUCCESS']:
return True
else:
raise ExchangeError(f"行情订阅失败: {response}")
except Exception as e:
if self.verbose:
print(f"private_get 失败尝试直接HTTP请求: {e}")
# 直接HTTP请求
return await self.send_direct_subscription('Subscribe', request, params)
print(f"关闭连接时出错: {e}")
await super().close()
def handle_message(self, client: Client, message):
"""处理WebSocket消息 - 根据类型分发"""
@@ -584,64 +600,75 @@ class mt5(mt5Parent):
if not order_data:
return None
symbol = self.safe_string(order_data, 'symbol')
if symbol and len(symbol) >= 6:
base = symbol[:3]
quote = symbol[3:]
symbol = base + '/' + quote
# 确定订单状态
state = self.safe_string(order_data, 'state')
status = self.parse_order_status(state)
# 确定订单是否已关闭
close_time = self.safe_string(order_data, 'closeTime')
is_closed = close_time and close_time != "0001-01-01T00:00:00"
timestamp = self.parse8601(self.safe_string(order_data, 'openTime'))
last_trade_timestamp = None
if is_closed:
last_trade_timestamp = self.parse8601(close_time)
amount = self.safe_number(order_data, 'lots')
filled = self.safe_number(order_data, 'closeLots')
remaining = amount - filled if amount is not None and filled is not None else None
return {
'id': self.safe_string(order_data, 'ticket'),
'clientOrderId': None,
'datetime': self.iso8601(timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': last_trade_timestamp,
'lastUpdateTimestamp': last_trade_timestamp,
'status': status,
'symbol': symbol,
'type': self.parse_order_type(self.safe_string(order_data, 'orderType')),
'timeInForce': None,
'postOnly': None,
'side': self.parse_order_side(self.safe_string(order_data, 'orderType')),
'price': self.safe_number(order_data, 'openPrice'),
'stopLossPrice': self.safe_number(order_data, 'stopLoss'),
'takeProfitPrice': self.safe_number(order_data, 'takeProfit'),
'reduceOnly':None,
'triggerPrice': None,
'amount': amount,
'filled': filled,
'remaining': remaining,
'cost': None,
'trades': None,
'fee': {
'cost': self.safe_number(order_data, 'commission', 0) + self.safe_number(order_data, 'fee', 0),
'currency': None,
},
'average': None,
'info': order_data,
}
try:
symbol = self.safe_string(order_data, 'symbol')
if symbol and len(symbol) >= 6:
base = symbol[:3]
quote = symbol[3:]
symbol = base + '/' + quote
# 确定订单状态
state = self.safe_string(order_data, 'state')
status = self.parse_order_status(state)
# 确定订单是否已关闭
close_time = self.safe_string(order_data, 'closeTime')
is_closed = close_time and close_time != "0001-01-01T00:00:00"
timestamp = self.parse8601(self.safe_string(order_data, 'openTime'))
if timestamp is None:
timestamp = self.milliseconds()
last_trade_timestamp = None
if is_closed:
last_trade_timestamp = self.parse8601(close_time)
amount = self.safe_number(order_data, 'lots', 0)
filled = self.safe_number(order_data, 'closeLots', 0)
remaining = max(amount - filled, 0) if amount is not None and filled is not None else None
return {
'id': self.safe_string(order_data, 'ticket'),
'clientOrderId': None,
'datetime': self.iso8601(timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': last_trade_timestamp,
'lastUpdateTimestamp': last_trade_timestamp,
'status': status,
'symbol': symbol,
'type': self.parse_order_type(self.safe_string(order_data, 'orderType')),
'timeInForce': None,
'postOnly': None,
'side': self.parse_order_side(self.safe_string(order_data, 'orderType')),
'price': self.safe_number(order_data, 'openPrice'),
'stopLossPrice': self.safe_number(order_data, 'stopLoss'),
'takeProfitPrice': self.safe_number(order_data, 'takeProfit'),
'reduceOnly': None,
'triggerPrice': None,
'amount': amount,
'filled': filled,
'remaining': remaining,
'cost': None,
'trades': None,
'fee': {
'cost': self.safe_number(order_data, 'commission', 0) + self.safe_number(order_data, 'fee', 0),
'currency': None,
},
'average': None,
'info': order_data,
}
except Exception as e:
if self.verbose:
print(f"解析订单数据失败: {e}")
return None
def parse_ws_positions_from_orders(self, orders_data):
"""从订单数据解析持仓信息"""
positions = []
if not orders_data:
return positions
if not isinstance(orders_data, list):
orders_data = [orders_data]
@@ -706,6 +733,10 @@ class mt5(mt5Parent):
"""解析WebSocket K线数据"""
timestamp = self.parse8601(self.safe_string(data, 'time'))
# 确保时间戳是毫秒
if timestamp and timestamp < 1000000000000:
timestamp *= 1000
return [
timestamp, # 时间戳
self.safe_number(data, 'open'), # 开盘价
@@ -797,6 +828,57 @@ class mt5(mt5Parent):
# 方法2直接HTTP请求
return await self.send_direct_subscription('SubscribeOhlc', request, params)
async def send_ticker_subscription(self, request, params={}):
"""发送行情数据订阅请求"""
try:
# 使用 private_get 发送订阅
response = await self.private_get('Subscribe', self.extend(request, params))
if self.verbose:
print(f"✅ 行情订阅响应: {response}")
# 检查订阅是否成功
if response in [True, 'OK', 'SUCCESS']:
return True
else:
raise ExchangeError(f"行情订阅失败: {response}")
except Exception as e:
if self.verbose:
print(f"private_get 失败尝试直接HTTP请求: {e}")
# 直接HTTP请求
return await self.send_direct_subscription('Subscribe', request, params)
async def send_direct_subscription(self, endpoint, request, params={}):
"""直接发送订阅HTTP请求"""
import aiohttp
# 构建完整的URL
base_url = self.urls['api']['private']
query_params = self.extend(request, params)
query_string = urllib.parse.urlencode(query_params)
url = f"{base_url}/{endpoint}?{query_string}"
if self.verbose:
print(f"🔧 直接订阅URL: {url}")
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
result = await response.text()
if self.verbose:
print(f"✅ 直接订阅成功: {result}")
return self.parse_json(result)
else:
error_text = await response.text()
raise ExchangeError(f"订阅请求失败 {response.status}: {error_text}")
except Exception as e:
raise ExchangeError(f"订阅请求错误: {e}")
def handle_error_message(self, client: Client, message):
"""处理错误消息"""
error_code = self.safe_string(message, 'errorCode')
@@ -820,7 +902,15 @@ class mt5(mt5Parent):
except Exception as e:
if self.verbose:
print(f"拒绝K线消息哈希失败 {ohlcv_hash}: {e}")
# 拒绝所有ticker消息哈希
if hasattr(client, 'futures') and client.futures:
ticker_hashes = [hash for hash in client.futures.keys() if isinstance(hash, str) and hash.startswith('ticker:')]
for ticker_hash in ticker_hashes:
try:
client.reject(error, ticker_hash)
except Exception:
pass
except Exception as e:
if self.verbose:
print(f"错误处理失败: {e}")