""" 智能盯盘 - miniQMT交易接口适配器 封装miniQMT的交易功能,支持A股T+1交易 使用主程序的配置管理系统 """ import logging import os from typing import Dict, List, Optional from datetime import datetime class SmartMonitorQMT: """miniQMT交易接口""" def __init__(self, mini_qmt_path: str = None): """ 初始化miniQMT接口 Args: mini_qmt_path: miniQMT安装路径 """ self.logger = logging.getLogger(__name__) self.xt_trader = None self.account = None self.connected = False # 尝试导入miniQMT try: from xtquant import xttrader, xtdata self.xttrader = xttrader self.xtdata = xtdata self.logger.info("miniQMT模块加载成功") except ImportError as e: self.logger.warning(f"miniQMT模块未安装: {e}") self.logger.warning("将使用模拟模式(不实际下单)") def connect(self, account_id: str = None) -> bool: """ 连接miniQMT Args: account_id: 交易账户ID(可选,从环境变量读取) Returns: 是否连接成功 """ if not self.xttrader: self.logger.warning("miniQMT未安装,使用模拟模式") self.connected = False return False # 从配置读取账户ID if account_id is None: account_id = os.getenv('MINIQMT_ACCOUNT_ID', '') if not account_id: self.logger.error("未配置miniQMT账户ID,请在环境配置中设置") self.connected = False return False try: # 创建交易对象 self.xt_trader = self.xttrader.XtQuantTrader() # 连接 self.xt_trader.start() # 连接账户 self.account = self.xttrader.StockAccount(account_id) connect_result = self.xt_trader.connect() if connect_result == 0: self.connected = True self.logger.info(f"miniQMT连接成功,账户: {account_id}") return True else: self.logger.error(f"miniQMT连接失败,错误码: {connect_result}") return False except Exception as e: self.logger.error(f"连接miniQMT失败: {e}") return False def disconnect(self): """断开连接""" if self.xt_trader: try: self.xt_trader.stop() self.connected = False self.logger.info("miniQMT已断开连接") except Exception as e: self.logger.error(f"断开连接失败: {e}") def get_account_info(self) -> Dict: """ 获取账户信息 Returns: 账户信息字典 """ if not self.connected or not self.account: return { 'available_cash': 0, 'total_value': 0, 'positions_count': 0, 'total_profit_loss': 0 } try: # 获取资金信息 asset = self.xt_trader.query_stock_asset(self.account) # 获取持仓信息 positions = self.xt_trader.query_stock_positions(self.account) # 计算总浮动盈亏 total_profit_loss = 0 if positions: for pos in positions: total_profit_loss += pos.unrealized_profit return { 'available_cash': asset.cash, # 可用资金 'total_value': asset.total_asset, # 总资产 'positions_count': len(positions) if positions else 0, 'total_profit_loss': total_profit_loss, 'frozen_cash': asset.frozen_cash, # 冻结资金 'market_value': asset.market_value # 持仓市值 } except Exception as e: self.logger.error(f"获取账户信息失败: {e}") return { 'available_cash': 0, 'total_value': 0, 'positions_count': 0, 'total_profit_loss': 0 } def get_position(self, stock_code: str) -> Optional[Dict]: """ 获取指定股票的持仓信息 Args: stock_code: 股票代码 Returns: 持仓信息,如果未持有则返回None """ if not self.connected or not self.account: return None try: positions = self.xt_trader.query_stock_positions(self.account) if not positions: return None # 查找指定股票 for pos in positions: if pos.stock_code == stock_code: # 计算持仓天数 holding_days = 0 if hasattr(pos, 'open_date'): try: open_date = datetime.strptime(str(pos.open_date), '%Y%m%d') holding_days = (datetime.now() - open_date).days except: pass return { 'stock_code': pos.stock_code, 'stock_name': getattr(pos, 'stock_name', ''), 'quantity': pos.volume, # 持仓数量 'can_sell': pos.can_use_volume, # 可用数量(考虑T+1) 'cost_price': pos.avg_price, # 成本价 'current_price': pos.last_price, # 最新价 'market_value': pos.market_value, # 市值 'profit_loss': pos.unrealized_profit, # 浮动盈亏 'profit_loss_pct': (pos.last_price - pos.avg_price) / pos.avg_price * 100 if pos.avg_price > 0 else 0, 'holding_days': holding_days, 'buy_date': getattr(pos, 'open_date', '') } return None except Exception as e: self.logger.error(f"获取持仓信息失败 {stock_code}: {e}") return None def get_all_positions(self) -> List[Dict]: """ 获取所有持仓 Returns: 持仓列表 """ if not self.connected or not self.account: return [] try: positions = self.xt_trader.query_stock_positions(self.account) if not positions: return [] result = [] for pos in positions: holding_days = 0 if hasattr(pos, 'open_date'): try: open_date = datetime.strptime(str(pos.open_date), '%Y%m%d') holding_days = (datetime.now() - open_date).days except: pass result.append({ 'stock_code': pos.stock_code, 'stock_name': getattr(pos, 'stock_name', ''), 'quantity': pos.volume, 'can_sell': pos.can_use_volume, 'cost_price': pos.avg_price, 'current_price': pos.last_price, 'market_value': pos.market_value, 'profit_loss': pos.unrealized_profit, 'profit_loss_pct': (pos.last_price - pos.avg_price) / pos.avg_price * 100 if pos.avg_price > 0 else 0, 'holding_days': holding_days }) return result except Exception as e: self.logger.error(f"获取所有持仓失败: {e}") return [] def buy_stock(self, stock_code: str, quantity: int, price: float = 0, order_type: str = 'market') -> Dict: """ 买入股票 Args: stock_code: 股票代码(如:600519.SH) quantity: 数量(股,必须是100的整数倍) price: 价格(限价单时使用,市价单可为0) order_type: 订单类型 ('market': 市价, 'limit': 限价) Returns: 订单结果 """ if not self.connected: return { 'success': False, 'error': 'miniQMT未连接', 'message': '模拟模式:买入订单已记录但未实际执行' } # 检查数量是否是100的整数倍 if quantity % 100 != 0: return { 'success': False, 'error': 'A股买入数量必须是100的整数倍(1手=100股)' } try: # 构造完整股票代码(带市场后缀) full_code = self._format_stock_code(stock_code) # 根据订单类型选择价格类型 if order_type == 'market': # 市价单 price_type = self.xttrader.XTP_PRICE_MARKET_OR_CANCEL # 市价剩余转限价 else: # 限价单 price_type = self.xttrader.XTP_PRICE_LIMIT # 下单 order_id = self.xt_trader.order_stock( account=self.account, stock_code=full_code, order_type=self.xttrader.XTP_SIDE_BUY, # 买入 order_volume=quantity, price_type=price_type, price=price ) if order_id > 0: self.logger.info(f"买入订单已提交: {stock_code}, 数量: {quantity}, 订单号: {order_id}") return { 'success': True, 'order_id': order_id, 'stock_code': stock_code, 'quantity': quantity, 'price': price, 'order_type': order_type, 'message': '买入订单已提交' } else: return { 'success': False, 'error': f'下单失败,订单号: {order_id}' } except Exception as e: self.logger.error(f"买入股票失败 {stock_code}: {e}") return { 'success': False, 'error': str(e) } def sell_stock(self, stock_code: str, quantity: int, price: float = 0, order_type: str = 'market') -> Dict: """ 卖出股票 Args: stock_code: 股票代码 quantity: 数量(股) price: 价格(限价单时使用) order_type: 订单类型 ('market': 市价, 'limit': 限价) Returns: 订单结果 """ if not self.connected: return { 'success': False, 'error': 'miniQMT未连接', 'message': '模拟模式:卖出订单已记录但未实际执行' } try: # 检查是否有持仓 position = self.get_position(stock_code) if not position: return { 'success': False, 'error': f'未持有股票 {stock_code}' } # 检查可卖数量(T+1限制) if quantity > position['can_sell']: return { 'success': False, 'error': f'可卖数量不足(可卖: {position["can_sell"]}股,T+1限制)' } # 构造完整股票代码 full_code = self._format_stock_code(stock_code) # 根据订单类型选择价格类型 if order_type == 'market': price_type = self.xttrader.XTP_PRICE_MARKET_OR_CANCEL else: price_type = self.xttrader.XTP_PRICE_LIMIT # 下单 order_id = self.xt_trader.order_stock( account=self.account, stock_code=full_code, order_type=self.xttrader.XTP_SIDE_SELL, # 卖出 order_volume=quantity, price_type=price_type, price=price ) if order_id > 0: self.logger.info(f"卖出订单已提交: {stock_code}, 数量: {quantity}, 订单号: {order_id}") return { 'success': True, 'order_id': order_id, 'stock_code': stock_code, 'quantity': quantity, 'price': price, 'order_type': order_type, 'message': '卖出订单已提交' } else: return { 'success': False, 'error': f'下单失败,订单号: {order_id}' } except Exception as e: self.logger.error(f"卖出股票失败 {stock_code}: {e}") return { 'success': False, 'error': str(e) } def cancel_order(self, order_id: int) -> bool: """ 撤销订单 Args: order_id: 订单ID Returns: 是否撤销成功 """ if not self.connected: return False try: result = self.xt_trader.cancel_order_stock(self.account, order_id) if result == 0: self.logger.info(f"订单已撤销: {order_id}") return True else: self.logger.error(f"撤销订单失败: {order_id}, 错误码: {result}") return False except Exception as e: self.logger.error(f"撤销订单失败: {e}") return False def get_orders(self, stock_code: str = None) -> List[Dict]: """ 获取当日委托订单 Args: stock_code: 股票代码(可选,不传则返回所有订单) Returns: 订单列表 """ if not self.connected: return [] try: orders = self.xt_trader.query_stock_orders(self.account) if not orders: return [] result = [] for order in orders: # 如果指定了股票代码,则过滤 if stock_code and order.stock_code != self._format_stock_code(stock_code): continue result.append({ 'order_id': order.order_id, 'stock_code': order.stock_code, 'stock_name': getattr(order, 'stock_name', ''), 'order_type': '买入' if order.order_type == self.xttrader.XTP_SIDE_BUY else '卖出', 'price': order.price, 'quantity': order.order_volume, 'traded_quantity': order.traded_volume, 'status': self._format_order_status(order.order_status), 'order_time': getattr(order, 'insert_time', '') }) return result except Exception as e: self.logger.error(f"获取订单失败: {e}") return [] def _format_stock_code(self, stock_code: str) -> str: """ 格式化股票代码(添加市场后缀) Args: stock_code: 股票代码(如:600519) Returns: 完整代码(如:600519.SH) """ # 如果已经包含市场后缀,直接返回 if '.' in stock_code: return stock_code # 沪市:6开头 if stock_code.startswith('6'): return f"{stock_code}.SH" # 深市:0、3开头 elif stock_code.startswith(('0', '3')): return f"{stock_code}.SZ" else: return stock_code def _format_order_status(self, status: int) -> str: """格式化订单状态""" status_map = { 0: '未报', 1: '待报', 2: '已报', 3: '已报待撤', 4: '部分待撤', 5: '部成待撤', 6: '部撤', 7: '已撤', 8: '部成', 9: '已成', 10: '废单' } return status_map.get(status, f'未知({status})') # 模拟交易类(当miniQMT不可用时使用) class SmartMonitorQMTSimulator: """模拟交易(用于测试)""" def __init__(self): self.logger = logging.getLogger(__name__) self.connected = True self.cash = 100000 # 模拟资金10万 self.positions = {} # 模拟持仓 self.orders = [] # 模拟订单 self.logger.info("使用模拟交易模式") def connect(self, account_id: str) -> bool: self.logger.info(f"模拟连接成功: {account_id}") return True def disconnect(self): self.logger.info("模拟断开连接") def get_account_info(self) -> Dict: total_value = self.cash for pos in self.positions.values(): total_value += pos['market_value'] return { 'available_cash': self.cash, 'total_value': total_value, 'positions_count': len(self.positions), 'total_profit_loss': sum(pos['profit_loss'] for pos in self.positions.values()) } def get_position(self, stock_code: str) -> Optional[Dict]: return self.positions.get(stock_code) def get_all_positions(self) -> List[Dict]: """获取所有持仓""" return list(self.positions.values()) def buy_stock(self, stock_code: str, quantity: int, price: float = 0, order_type: str = 'market') -> Dict: cost = quantity * price if price > 0 else quantity * 10 # 假设价格 if cost > self.cash: return {'success': False, 'error': '资金不足'} self.cash -= cost # 获取股票名称(简化版,实际应该从数据源获取) stock_name = f"股票{stock_code}" self.positions[stock_code] = { 'stock_code': stock_code, 'stock_name': stock_name, 'quantity': quantity, 'can_sell': 0, # T+1,今天买入不能卖 'cost_price': price if price > 0 else 10, 'current_price': price if price > 0 else 10, 'market_value': cost, 'profit_loss': 0, 'profit_loss_pct': 0, 'holding_days': 0, 'buy_date': datetime.now().strftime('%Y%m%d') } # 记录订单 self.orders.append({ 'order_id': len(self.orders) + 1, 'stock_code': stock_code, 'order_type': 'BUY', 'quantity': quantity, 'price': price, 'status': '已成交', 'order_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) self.logger.info(f"[模拟] 买入 {stock_code} {quantity}股 @ {price:.2f}元") return {'success': True, 'order_id': len(self.orders), 'message': '模拟买入成功'} def sell_stock(self, stock_code: str, quantity: int, price: float = 0, order_type: str = 'market') -> Dict: if stock_code not in self.positions: return {'success': False, 'error': '未持有该股票'} pos = self.positions[stock_code] if quantity > pos['can_sell']: return {'success': False, 'error': 'T+1限制,今天买入不能卖'} sell_price = price if price > 0 else pos['current_price'] revenue = quantity * sell_price self.cash += revenue # 计算盈亏 profit_loss = (sell_price - pos['cost_price']) * quantity # 记录订单 self.orders.append({ 'order_id': len(self.orders) + 1, 'stock_code': stock_code, 'order_type': 'SELL', 'quantity': quantity, 'price': sell_price, 'status': '已成交', 'order_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) # 更新或删除持仓 if quantity >= pos['quantity']: del self.positions[stock_code] else: pos['quantity'] -= quantity pos['can_sell'] -= quantity pos['market_value'] = pos['quantity'] * pos['current_price'] self.logger.info(f"[模拟] 卖出 {stock_code} {quantity}股 @ {sell_price:.2f}元, 盈亏: {profit_loss:+.2f}元") return { 'success': True, 'order_id': len(self.orders), 'profit_loss': profit_loss, 'message': '模拟卖出成功' } def cancel_order(self, order_id: int) -> bool: """撤销订单(模拟)""" self.logger.info(f"[模拟] 撤销订单 {order_id}") return True def get_orders(self, stock_code: str = None) -> List[Dict]: """获取订单列表(模拟)""" return self.orders