# -*- coding: utf-8 -*- from ccxt.async_support.mt5 import mt5 as mt5Parent from ccxt.base.errors import ExchangeError, ArgumentsRequired from ccxt.async_support.base.ws.client import Client import asyncio from typing import Optional, Dict, Any, List class mt5(mt5Parent): def describe(self): return self.deep_extend(super(mt5, self).describe(), { 'platinum': True, 'hostname': '43.167.188.220:5000', 'has': { # 专业版特有功能 'watchPosition': True, 'watchOrder': True, 'watchLeverage': True, 'watchMargin': True, 'advancedAPI': True, 'batchOrders': True, 'modifyOrder': True, 'closePosition': True, 'setLeverage': True, 'setMargin': True, }, 'urls': { 'api': { 'ws': 'ws://{hostname}', }, }, 'options': { 'defaultType': 'spot', 'watchOrder': { 'symbol': None, 'orderId': None, }, 'watchPosition': { 'symbol': None, }, }, }) # 专业版特有方法 async def watch_order(self, params={}): """ 监听特定订单的更新(专业版特有) """ if not self.token: await self.get_token() try: await self.load_markets() except Exception as e: print(f"错误: {e}") # await self.fetch_markets() # return request: dict = { 'id': self.token, } endpoint = self.api['wsEndpoint']['order'] url = self.implode_hostname(self.urls['api']['ws']) + '/' + endpoint url += '?' + self.urlencode(request) return await self.watch(url,'order_position') def handle_message(self, client: Client, message): # print(message) errorCode = self.safe_string(message, 'errorCode') if errorCode is not None: self.handle_error_message(client, message) return # 处理 MT5 特定的消息 message_type = self.safe_string(message, 'type') if message_type == 'OpenedOrders': print("111111111111111") self.handle_opened_orders(client, message) elif message_type == self.api['wsEndpoint']['orderbook']: self.handle_orderbook(client, message) elif message_type == self.api['wsEndpoint']['quote']: self.handle_ticker(client, message) def handle_opened_orders(self, client, message): """ 处理 OpenedOrders 消息 """ orders_data = self.safe_value(message, 'data', []) message_id = self.safe_string(message, 'id') timestamp = self.safe_integer(message, 'timestampUTC') parsed_orders = [] for order_data in orders_data: # 解析每个订单 order = self.parse_ws_order(order_data) parsed_orders.append(order) # 分发到不同的消息流 order_id = order['id'] symbol = order['symbol'] # 更新特定订单 client.resolve(order, 'order:' + order_id) # 更新符号特定的订单列表 client.resolve([order], 'orders:' + symbol) print('-------------parsed_orders:',parsed_orders) # 更新所有订单列表 client.resolve(parsed_orders, 'orders') # 如果有请求ID,也解析到该请求 if message_id: client.resolve(parsed_orders, 'request:' + message_id) def handle_orderbook(self, client, message): """处理深度数据""" orderbook = self.parse_ws_order_book(message) symbol = orderbook['symbol'] message_hash = 'orderbook:' + symbol client.resolve(orderbook, message_hash) def parse_ws_order(self, order, market=None): """ 解析 MT5 WebSocket 订单数据为 CCXT 标准格式 """ # 提取订单基本信息 ticket = self.safe_integer(order, 'ticket') symbol = self.safe_string(order, 'symbol') order_type = self.safe_string(order, 'orderType') deal_type = self.safe_string(order, 'dealType') state = self.safe_string(order, 'state') lots = self.safe_number(order, 'lots') contract_size = self.safe_number(order, 'contractSize', 1.0) open_price = self.safe_number(order, 'openPrice') close_price = self.safe_number(order, 'closePrice') profit = self.safe_number(order, 'profit') print("ggeggegge========") # 获取市场信息 market = self.market(symbol) if market is None else market # 解析时间戳 open_time_str = self.safe_string(order, 'openTime') open_timestamp = self.safe_integer(order, 'openTimestampUTC') # 如果 openTimestampUTC 不存在,尝试解析 openTime 字符串 if open_timestamp is None and open_time_str: try: # 解析格式: "2025-11-15T04:06:06.994" open_timestamp = self.parse8601(open_time_str) except: open_timestamp = None # 解析订单状态 status = self.parse_order_status(state) # 解析订单方向 side = self.parse_order_side(order_type, deal_type) # 解析订单类型 order_type_parsed = self.parse_order_type(order_type) # 计算数量 (lots * contractSize) amount = lots * contract_size if (lots is not None and contract_size is not None) else None # 解析成交数量 volume = self.safe_integer(order, 'volume', 0) close_volume = self.safe_integer(order, 'closeVolume', 0) # 对于已成交订单,filled 应该是 volume filled = volume remaining = 0 # MT5 中订单要么完全成交,要么没有 # 解析止盈止损 stop_loss = self.safe_number(order, 'stopLoss') take_profit = self.safe_number(order, 'takeProfit') # 构建标准订单对象 result = { 'id': str(ticket), 'clientOrderId': None, 'datetime': self.iso8601(open_timestamp) if open_timestamp else None, 'timestamp': open_timestamp, 'lastTradeTimestamp': None, 'status': status, 'symbol': market['symbol'] if market else symbol, 'type': order_type_parsed, 'side': side, 'price': open_price, 'amount': amount, 'filled': filled, 'remaining': remaining, 'cost': None, # 可以计算: amount * price 'average': None, 'fee': { 'currency': market['quote'] if market else 'USD', 'cost': self.safe_number(order, 'fee', 0), }, 'trades': None, 'info': order, } # 计算成本 if amount is not None and open_price is not None: result['cost'] = amount * open_price # 添加 MT5 特定字段 result['stopLoss'] = stop_loss result['takeProfit'] = take_profit result['profit'] = profit result['commission'] = self.safe_number(order, 'commission', 0) result['swap'] = self.safe_number(order, 'swap', 0) result['comment'] = self.safe_string(order, 'comment', '') print("-----------result:",result) return result def parse_order_status(self, status): """ 解析 MT5 订单状态 """ statuses = { 'Filled': 'closed', # 已成交 'PartialFilled': 'open', # 部分成交 'Pending': 'open', # 挂单中 'Cancelled': 'canceled', # 已取消 'Rejected': 'rejected', # 已拒绝 'Expired': 'expired', # 已过期 # 根据您的数据添加更多状态映射 'Active': 'open', 'Closed': 'closed', } return self.safe_string(statuses, status, status.lower() if status else 'unknown') def parse_order_side(self, order_type, deal_type): """ 解析订单方向 """ # 优先使用 deal_type,因为它更准确 if deal_type: if 'Buy' in deal_type: return 'buy' elif 'Sell' in deal_type: return 'sell' # 其次使用 order_type if order_type: if order_type == 'Buy': return 'buy' elif order_type == 'Sell': return 'sell' elif 'Buy' in order_type: return 'buy' elif 'Sell' in order_type: return 'sell' return 'unknown' def parse_order_type(self, order_type): """ 解析订单类型 """ if not order_type: return 'market' # 默认类型 order_type_lower = order_type.lower() if 'limit' in order_type_lower: return 'limit' elif 'stop' in order_type_lower: return 'stop' elif 'market' in order_type_lower: return 'market' else: # 根据 dealType 判断 return 'market' # 默认为市价单 def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): """签名请求 URL 构建""" endpoint = '/' + self.implode_params(path, params) url = self.implode_hostname(self.urls['api'][api]) + endpoint headers = headers if (headers is not None) else {} # 对于 GET 请求,将参数添加到查询字符串 if method == 'GET' and params: # 特殊处理数组参数 query_params = {} for key, value in params.items(): if isinstance(value, list): # 对于数组参数,可能需要特殊编码 query_params[key] = ','.join(value) else: query_params[key] = value url += '?' + self.urlencode(query_params) elif method == 'GET' and params: url += '?' + self.urlencode(params) # print(f"🔧 Debug: Final URL: {url}") return { 'url': url, 'method': method, 'body': body, 'headers': headers }