#!/usr/bin/env python3 # -*- coding: utf-8 -*- import asyncio import json import logging from datetime import datetime from ccxt.pro import mt5 from ccxt.base.errors import ExchangeError, AuthenticationError # 设置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('MT5-WebSocket-Test') class MT5WebSocketOrderTest: def __init__(self, config): self.exchange = mt5({ 'apiKey': config.get('apiKey', ''), 'secret': config.get('secret', ''), 'password': config.get('password', ''), 'user': config.get('user', 62333850), 'host': config.get('host', '78.140.180.198'), 'port': config.get('port', 443), 'sandbox': True, 'verbose': True, }) # 存储数据 self.balance = None self.orders = [] self.open_orders = [] self.closed_orders = [] # 统计信息 self.stats = { 'order_updates': 0, 'balance_updates': 0, 'trade_updates': 0, 'errors': 0 } logger.info("MT5 WebSocket 订单测试初始化完成") async def watch_orders(self): """监听订单更新""" logger.info("开始监听订单更新...") while True: try: orders = await self.exchange.watch_orders() for order in orders: await self.handle_order_update(order) except Exception as e: logger.error(f"监听订单时发生错误: {e}") self.stats['errors'] += 1 await asyncio.sleep(5) # 等待后重试 async def watch_balance(self): """监听余额更新""" logger.info("开始监听余额更新...") while True: try: balance = await self.exchange.watch_balance() await self.handle_balance_update(balance) except Exception as e: logger.error(f"监听余额时发生错误: {e}") self.stats['errors'] += 1 await asyncio.sleep(5) async def watch_my_trades(self): """监听交易更新""" logger.info("开始监听交易更新...") while True: try: trades = await self.exchange.watch_my_trades() for trade in trades: await self.handle_trade_update(trade) except Exception as e: logger.error(f"监听交易时发生错误: {e}") self.stats['errors'] += 1 await asyncio.sleep(5) async def handle_order_update(self, order): """处理订单更新""" self.stats['order_updates'] += 1 order_id = order['id'] symbol = order['symbol'] status = order['status'] side = order['side'] order_type = order['type'] price = order.get('price') amount = order.get('amount') filled = order.get('filled') # 检查是否是新的订单 existing_order = next((o for o in self.orders if o['id'] == order_id), None) if existing_order: # 更新现有订单 old_status = existing_order['status'] if old_status != status: logger.info(f"🔁 订单状态更新: {order_id} {symbol} {side} {old_status} -> {status}") # 更新订单信息 existing_order.update(order) else: # 新订单 logger.info(f"🆕 新订单: {order_id} {symbol} {side} {order_type} {status}") self.orders.append(order.copy()) # 更新开单/平单列表 self.update_order_lists(order) # 打印订单详情 self.print_order_details(order) async def handle_balance_update(self, balance): """处理余额更新""" self.stats['balance_updates'] += 1 self.balance = balance # 提取主要余额信息 total_balance = 0 free_balance = 0 used_balance = 0 for currency, info in balance['total'].items(): if info is not None: total_balance += info for currency, info in balance['free'].items(): if info is not None: free_balance += info for currency, info in balance['used'].items(): if info is not None: used_balance += info logger.info(f"💰 余额更新 - 总余额: {total_balance:.2f}, 可用: {free_balance:.2f}, 占用: {used_balance:.2f}") # 打印详细余额信息 self.print_balance_details(balance) async def handle_trade_update(self, trade): """处理交易更新""" self.stats['trade_updates'] += 1 trade_id = trade['id'] order_id = trade['order'] symbol = trade['symbol'] side = trade['side'] price = trade['price'] amount = trade['amount'] cost = trade.get('cost') fee = trade.get('fee', {}) logger.info(f"💸 交易执行: {trade_id} | 订单: {order_id} | {symbol} {side} {amount} @ {price}") if cost: logger.info(f" 交易成本: {cost:.2f}") if fee and fee.get('cost'): logger.info(f" 手续费: {fee['cost']} {fee.get('currency', '')}") def update_order_lists(self, order): """更新开单和平单列表""" order_id = order['id'] status = order['status'] # 从开单列表中移除已关闭的订单 self.open_orders = [o for o in self.open_orders if o['id'] != order_id] # 如果是开单状态,添加到开单列表 if status in ['open', 'pending']: self.open_orders.append(order.copy()) # 如果是关闭状态,添加到平单列表 elif status in ['closed', 'canceled', 'expired', 'rejected']: # 检查是否已经在平单列表中 if not any(o['id'] == order_id for o in self.closed_orders): self.closed_orders.append(order.copy()) def print_order_details(self, order): """打印订单详情""" order_id = order['id'] symbol = order['symbol'] status = order['status'] side = order['side'] order_type = order['type'] price = order.get('price', 'N/A') amount = order.get('amount', 'N/A') filled = order.get('filled', 'N/A') remaining = order.get('remaining', 'N/A') cost = order.get('cost', 'N/A') details = [ f"订单ID: {order_id}", f"交易对: {symbol}", f"方向: {side}", f"类型: {order_type}", f"状态: {status}", f"价格: {price}", f"数量: {amount}", f"已成交: {filled}", f"未成交: {remaining}", f"成本: {cost}" ] logger.debug("📋 订单详情: " + " | ".join(details)) def print_balance_details(self, balance): """打印余额详情""" if not balance: return logger.debug("💳 详细余额信息:") for currency in ['USD', 'EUR', 'GBP', 'JPY']: if currency in balance['total'] and balance['total'][currency] is not None: total = balance['total'][currency] free = balance['free'].get(currency, 0) used = balance['used'].get(currency, 0) logger.debug(f" {currency}: 总额={total:.2f}, 可用={free:.2f}, 占用={used:.2f}") async def place_test_orders(self): """放置测试订单""" logger.info("开始放置测试订单...") test_orders = [ { 'symbol': 'EUR/USD', 'type': 'limit', 'side': 'buy', 'amount': 0.01, 'price': 1.0800 }, { 'symbol': 'EUR/USD', 'type': 'limit', 'side': 'sell', 'amount': 0.01, 'price': 1.0900 }, { 'symbol': 'GBP/USD', 'type': 'market', 'side': 'buy', 'amount': 0.01 } ] created_orders = [] for order_params in test_orders: try: logger.info(f"尝试下单: {order_params}") order = await self.exchange.create_order( order_params['symbol'], order_params['type'], order_params['side'], order_params['amount'], order_params.get('price') ) created_orders.append(order) logger.info(f"✅ 下单成功: {order['id']}") await asyncio.sleep(2) # 间隔2秒 except Exception as e: logger.error(f"❌ 下单失败: {e}") return created_orders async def cancel_test_orders(self, orders): """取消测试订单""" logger.info("开始取消测试订单...") for order in orders: try: if order['status'] in ['open', 'pending']: await self.exchange.cancel_order(order['id'], order['symbol']) logger.info(f"✅ 取消订单成功: {order['id']}") await asyncio.sleep(1) except Exception as e: logger.error(f"❌ 取消订单失败 {order['id']}: {e}") async def get_current_orders(self): """获取当前订单状态""" try: open_orders = await self.exchange.fetch_open_orders() closed_orders = await self.exchange.fetch_closed_orders() logger.info(f"当前开单数量: {len(open_orders)}") logger.info(f"历史订单数量: {len(closed_orders)}") return open_orders, closed_orders except Exception as e: logger.error(f"获取订单状态失败: {e}") return [], [] def print_statistics(self): """打印统计信息""" logger.info("📊 WebSocket 测试统计:") logger.info(f" 订单更新次数: {self.stats['order_updates']}") logger.info(f" 余额更新次数: {self.stats['balance_updates']}") logger.info(f" 交易更新次数: {self.stats['trade_updates']}") logger.info(f" 错误次数: {self.stats['errors']}") logger.info(f" 总订单数: {len(self.orders)}") logger.info(f" 当前开单: {len(self.open_orders)}") logger.info(f" 已关闭订单: {len(self.closed_orders)}") if self.balance: total = sum([v for v in self.balance['total'].values() if v is not None]) logger.info(f" 当前总余额: {total:.2f}") async def run_test(self, duration=300, place_test_orders=True): """运行测试""" logger.info(f"🚀 开始 MT5 WebSocket 订单测试,持续时间: {duration}秒") start_time = datetime.now() try: # 启动监听任务 watch_tasks = [ asyncio.create_task(self.watch_orders()), asyncio.create_task(self.watch_balance()), asyncio.create_task(self.watch_my_trades()), ] # 等待连接建立 await asyncio.sleep(5) # 放置测试订单 test_orders = [] if place_test_orders: test_orders = await self.place_test_orders() # 等待订单处理 await asyncio.sleep(10) # 获取当前订单状态 await self.get_current_orders() # 等待一段时间后取消测试订单 await asyncio.sleep(30) await self.cancel_test_orders(test_orders) # 主测试循环 test_task = asyncio.create_task(self.test_loop(duration)) await test_task # 取消监听任务 for task in watch_tasks: task.cancel() # 等待任务结束 await asyncio.gather(*watch_tasks, return_exceptions=True) except Exception as e: logger.error(f"测试运行错误: {e}") finally: # 关闭连接 await self.exchange.close() end_time = datetime.now() duration_actual = (end_time - start_time).total_seconds() logger.info(f"✅ 测试完成,实际运行时间: {duration_actual:.2f}秒") self.print_statistics() async def test_loop(self, duration): """测试主循环""" start_time = asyncio.get_event_loop().time() while True: current_time = asyncio.get_event_loop().time() elapsed = current_time - start_time if elapsed >= duration: break # 每分钟打印一次状态 if int(elapsed) % 60 == 0: logger.info(f"⏰ 测试运行中... 已运行: {int(elapsed)}秒") self.print_statistics() await asyncio.sleep(1) async def real_time_monitoring(self): """实时监控模式""" logger.info("🔍 启动实时监控模式...") try: await asyncio.gather( self.watch_orders(), self.watch_balance(), self.watch_my_trades(), ) except KeyboardInterrupt: logger.info("👋 用户中断监控") finally: await self.exchange.close() self.print_statistics() # 测试配置 TEST_CONFIG = { 'user': 62333850, # 演示账户 'password': 'tecimil4', 'host': '78.140.180.198', 'port': 443, } async def main(): """主函数""" print("=" * 60) print("MT5 WebSocket 订单信息测试") print("=" * 60) # 创建测试实例 tester = MT5WebSocketOrderTest(TEST_CONFIG) try: # 选择测试模式 print("\n选择测试模式:") print("1. 完整测试 (包含测试订单)") print("2. 实时监控模式") print("3. 仅测试连接") choice = input("请输入选择 (1-3, 默认1): ").strip() if choice == "2": # 实时监控模式 await tester.real_time_monitoring() elif choice == "3": # 仅测试连接 await tester.get_current_orders() await tester.exchange.close() else: # 完整测试 duration = input("输入测试持续时间(秒, 默认300): ").strip() duration = int(duration) if duration.isdigit() else 300 place_orders = input("是否放置测试订单? (y/n, 默认y): ").strip().lower() place_orders = place_orders != 'n' await tester.run_test(duration=duration, place_test_orders=place_orders) except KeyboardInterrupt: logger.info("👋 用户中断测试") except Exception as e: logger.error(f"测试执行错误: {e}") finally: print("\n" + "=" * 60) print("测试结束") print("=" * 60) if __name__ == "__main__": # 运行测试 asyncio.run(main())