""" 智能盯盘 - 主引擎 整合DeepSeek AI决策、数据获取、交易执行、通知等功能 """ import logging import time from typing import Dict, List, Optional from datetime import datetime import threading from smart_monitor_deepseek import SmartMonitorDeepSeek from smart_monitor_data import SmartMonitorDataFetcher from smart_monitor_qmt import SmartMonitorQMT, SmartMonitorQMTSimulator from smart_monitor_db import SmartMonitorDB from notification_service import notification_service # 复用主程序的通知服务 from config_manager import config_manager # 复用主程序的配置管理器 class SmartMonitorEngine: """智能盯盘引擎""" def __init__(self, deepseek_api_key: str = None, qmt_account_id: str = None, use_simulator: bool = None): """ 初始化智能盯盘引擎 Args: deepseek_api_key: DeepSeek API密钥(可选,从配置读取) qmt_account_id: miniQMT账户ID(可选,从配置读取) use_simulator: 是否使用模拟交易(可选,从配置读取) """ self.logger = logging.getLogger(__name__) # 从配置管理器读取配置 config = config_manager.read_env() # DeepSeek API if deepseek_api_key is None: deepseek_api_key = config.get('DEEPSEEK_API_KEY', '') # MiniQMT配置 if qmt_account_id is None: qmt_account_id = config.get('MINIQMT_ACCOUNT_ID', '') if use_simulator is None: # 如果MINIQMT_ENABLED=false,则使用模拟器 miniqmt_enabled = config.get('MINIQMT_ENABLED', 'false').lower() == 'true' use_simulator = not miniqmt_enabled # 初始化各个模块 self.deepseek = SmartMonitorDeepSeek(deepseek_api_key) self.data_fetcher = SmartMonitorDataFetcher() self.db = SmartMonitorDB() self.notification = notification_service # 使用主程序的通知服务 # 初始化交易接口 if use_simulator: self.qmt = SmartMonitorQMTSimulator() self.qmt.connect(qmt_account_id or "simulator") self.logger.info("使用模拟交易模式") else: self.qmt = SmartMonitorQMT() if qmt_account_id: success = self.qmt.connect(qmt_account_id) if success: self.logger.info(f"已连接miniQMT账户: {qmt_account_id}") else: self.logger.warning(f"连接miniQMT失败,切换到模拟模式") self.qmt = SmartMonitorQMTSimulator() self.qmt.connect("simulator") else: self.logger.warning("未配置miniQMT账户,使用模拟模式") self.qmt = SmartMonitorQMTSimulator() self.qmt.connect("simulator") # 监控线程控制 self.monitoring_threads = {} self.stop_flags = {} self.logger.info("智能盯盘引擎初始化完成") def analyze_stock(self, stock_code: str, auto_trade: bool = False, notify: bool = True, has_position: bool = False, position_cost: float = 0, position_quantity: int = 0, trading_hours_only: bool = True) -> Dict: """ 分析单只股票并做出决策 Args: stock_code: 股票代码 auto_trade: 是否自动交易 notify: 是否发送通知 has_position: 是否已持仓(可选) position_cost: 持仓成本(可选) position_quantity: 持仓数量(可选) trading_hours_only: 是否仅在交易时段分析(可选,默认True) Returns: 分析结果 """ try: self.logger.info(f"[{stock_code}] 开始分析...") # 1. 检查交易时段 session_info = self.deepseek.get_trading_session() self.logger.info(f"[{stock_code}] 当前时段: {session_info['session']}") # 如果启用了仅交易时段分析,且当前不在交易时段,则跳过分析 if trading_hours_only and not session_info.get('can_trade', False): self.logger.info(f"[{stock_code}] 非交易时段,跳过分析") return { 'success': False, 'error': f"非交易时段({session_info['session']}),跳过分析", 'session_info': session_info, 'skipped': True } # 2. 获取市场数据 market_data = self.data_fetcher.get_comprehensive_data(stock_code) if not market_data: return { 'success': False, 'error': '获取市场数据失败' } # 3. 获取账户信息 account_info = self.qmt.get_account_info() # 4. 检查是否已持有该股票 # 优先使用传入的持仓信息,否则从QMT获取 if has_position and position_cost > 0 and position_quantity > 0: # 使用用户设置的持仓信息 self.logger.info(f"[{stock_code}] 使用监控任务设置的持仓: {position_quantity}股 @ {position_cost:.2f}元") else: # 从QMT获取持仓 position = self.qmt.get_position(stock_code) has_position = position is not None if has_position: position_cost = position.get('cost_price', 0) position_quantity = position.get('quantity', 0) account_info['current_position'] = position self.logger.info(f"[{stock_code}] 从QMT获取持仓: {position_quantity}股, " f"成本价: {position_cost:.2f}, " f"浮动盈亏: {position.get('profit_loss_pct', 0):+.2f}%") # 5. 调用DeepSeek AI决策 ai_result = self.deepseek.analyze_stock_and_decide( stock_code=stock_code, market_data=market_data, account_info=account_info, has_position=has_position, position_cost=position_cost, position_quantity=position_quantity ) if not ai_result['success']: return { 'success': False, 'error': 'AI决策失败', 'details': ai_result } decision = ai_result['decision'] self.logger.info(f"[{stock_code}] AI决策: {decision['action']} " f"(信心度: {decision['confidence']}%)") self.logger.info(f"[{stock_code}] 决策理由: {decision['reasoning'][:100]}...") # 6. 保存AI决策到数据库 decision_id = self.db.save_ai_decision({ 'stock_code': stock_code, 'stock_name': market_data.get('name'), 'trading_session': session_info['session'], 'action': decision['action'], 'confidence': decision['confidence'], 'reasoning': decision['reasoning'], 'position_size_pct': decision.get('position_size_pct'), 'stop_loss_pct': decision.get('stop_loss_pct'), 'take_profit_pct': decision.get('take_profit_pct'), 'risk_level': decision.get('risk_level'), 'key_price_levels': decision.get('key_price_levels', {}), 'market_data': market_data, 'account_info': account_info }) # 7. 执行交易(如果开启自动交易) execution_result = None if auto_trade and session_info['can_trade']: execution_result = self._execute_decision( stock_code=stock_code, decision=decision, market_data=market_data, has_position=has_position ) # 更新决策执行状态 self.db.update_decision_execution( decision_id=decision_id, executed=execution_result.get('success', False), result=str(execution_result) ) # 8. 发送通知 if notify: self._send_notification( stock_code=stock_code, stock_name=market_data.get('name'), decision=decision, execution_result=execution_result, market_data=market_data ) return { 'success': True, 'stock_code': stock_code, 'stock_name': market_data.get('name'), 'session_info': session_info, 'market_data': market_data, 'decision': decision, 'decision_id': decision_id, 'execution_result': execution_result } except Exception as e: self.logger.error(f"[{stock_code}] 分析失败: {e}") import traceback traceback.print_exc() return { 'success': False, 'error': str(e) } def _execute_decision(self, stock_code: str, decision: Dict, market_data: Dict, has_position: bool) -> Dict: """ 执行AI决策 Args: stock_code: 股票代码 decision: AI决策 market_data: 市场数据 has_position: 是否已持有 Returns: 执行结果 """ action = decision['action'] try: if action == 'BUY' and not has_position: # 买入逻辑 return self._execute_buy(stock_code, decision, market_data) elif action == 'SELL' and has_position: # 卖出逻辑 return self._execute_sell(stock_code, decision, market_data) elif action == 'HOLD': # 持有,不操作 return { 'success': True, 'action': 'HOLD', 'message': 'AI建议持有,未执行交易' } else: return { 'success': False, 'error': f'无效操作: {action}' } except Exception as e: self.logger.error(f"[{stock_code}] 执行交易失败: {e}") return { 'success': False, 'error': str(e) } def _execute_buy(self, stock_code: str, decision: Dict, market_data: Dict) -> Dict: """执行买入""" try: # 获取账户信息 account_info = self.qmt.get_account_info() available_cash = account_info['available_cash'] # 计算买入金额 position_size_pct = decision.get('position_size_pct', 20) buy_amount = available_cash * (position_size_pct / 100) # 计算买入数量(必须是100的整数倍) current_price = market_data['current_price'] quantity = int(buy_amount / current_price / 100) * 100 if quantity < 100: return { 'success': False, 'error': f'资金不足,最少需要买入100股(约{current_price * 100:.2f}元)' } # 执行买入 result = self.qmt.buy_stock( stock_code=stock_code, quantity=quantity, price=current_price, order_type='market' ) if result['success']: # 保存交易记录 self.db.save_trade_record({ 'stock_code': stock_code, 'stock_name': market_data.get('name'), 'trade_type': 'BUY', 'quantity': quantity, 'price': current_price, 'amount': quantity * current_price, 'order_id': result.get('order_id'), 'order_status': '已提交' }) # 保存持仓监控 self.db.save_position({ 'stock_code': stock_code, 'stock_name': market_data.get('name'), 'quantity': quantity, 'cost_price': current_price, 'current_price': current_price, 'profit_loss': 0, 'profit_loss_pct': 0, 'holding_days': 0, 'buy_date': datetime.now().strftime('%Y-%m-%d'), 'stop_loss_price': current_price * (1 - decision.get('stop_loss_pct', 5) / 100), 'take_profit_price': current_price * (1 + decision.get('take_profit_pct', 10) / 100) }) self.logger.info(f"[{stock_code}] 买入成功: {quantity}股 @ {current_price:.2f}元") return result except Exception as e: self.logger.error(f"[{stock_code}] 买入失败: {e}") return { 'success': False, 'error': str(e) } def _execute_sell(self, stock_code: str, decision: Dict, market_data: Dict) -> Dict: """执行卖出""" try: # 获取持仓 position = self.qmt.get_position(stock_code) if not position: return { 'success': False, 'error': '未持有该股票' } # 可卖数量(考虑T+1限制) can_sell = position['can_sell'] if can_sell <= 0: return { 'success': False, 'error': 'T+1限制,今天买入的股票明天才能卖出' } # 执行卖出 current_price = market_data['current_price'] result = self.qmt.sell_stock( stock_code=stock_code, quantity=can_sell, price=current_price, order_type='market' ) if result['success']: # 计算盈亏 profit_loss = (current_price - position['cost_price']) * can_sell # 保存交易记录 self.db.save_trade_record({ 'stock_code': stock_code, 'stock_name': market_data.get('name'), 'trade_type': 'SELL', 'quantity': can_sell, 'price': current_price, 'amount': can_sell * current_price, 'order_id': result.get('order_id'), 'order_status': '已提交', 'profit_loss': profit_loss }) # 更新或关闭持仓记录 if can_sell >= position['quantity']: self.db.close_position(stock_code) self.logger.info(f"[{stock_code}] 卖出成功: {can_sell}股 @ {current_price:.2f}元, " f"盈亏: {profit_loss:+.2f}元") return result except Exception as e: self.logger.error(f"[{stock_code}] 卖出失败: {e}") return { 'success': False, 'error': str(e) } def _send_notification(self, stock_code: str, stock_name: str, decision: Dict, execution_result: Optional[Dict], market_data: Dict): """ 发送通知(使用主程序的通知服务) 优化策略:仅在买入或卖出信号时发送通知,持有信号不发送 """ try: action = decision['action'].upper() # 仅在买入或卖出时发送通知,持有信号不发送 if action not in ['BUY', 'SELL']: self.logger.info(f"[{stock_code}] 决策为{action},不发送通知") return # 构建通知内容 action_text = { 'BUY': '🟢 买入', 'SELL': '🔴 卖出' }.get(action, action) message = f"{action_text}信号 - {stock_name}({stock_code})" # 简化的AI决策内容(提取核心信息) reasoning_summary = decision['reasoning'][:150] + '...' if len(decision['reasoning']) > 150 else decision['reasoning'] # 提取关键价位信息 key_levels = decision.get('key_price_levels', {}) support = key_levels.get('support', 'N/A') resistance = key_levels.get('resistance', 'N/A') # 构建简化的详细内容 content = f""" 【{action_text}信号】{stock_name}({stock_code}) 📊 市场信息 • 当前价: ¥{market_data.get('current_price', 0):.2f} • 涨跌幅: {market_data.get('change_pct', 0):+.2f}% • 成交量: {market_data.get('volume', 0):,.0f}手 🤖 AI决策 • 操作: {action_text} • 信心度: {decision['confidence']}% • 风险: {decision.get('risk_level', '中')} 💡 核心理由 {reasoning_summary} 📈 关键价位 • 支撑位: {support} • 阻力位: {resistance} • 止盈: {decision.get('take_profit_pct', 'N/A')}% • 止损: {decision.get('stop_loss_pct', 'N/A')}% 📉 技术指标 • MA5: {market_data.get('ma5', 0):.2f} / MA20: {market_data.get('ma20', 0):.2f} • RSI(6): {market_data.get('rsi6', 0):.1f} • MACD: {market_data.get('macd', 0):.4f} """ if execution_result: if execution_result.get('success'): content += f"\n✅ 操作已自动执行" else: content += f"\n⚠️ 执行失败: {execution_result.get('error')}" content += f"\n\n⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" # 使用主程序的通知服务格式 notification_data = { 'symbol': stock_code, 'name': stock_name, 'type': '智能盯盘', 'message': message, 'details': content, 'triggered_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), # 新增实时市场数据 'current_price': market_data.get('current_price', 'N/A'), 'change_pct': f"{market_data.get('change_pct', 0):+.2f}" if market_data.get('change_pct') else 'N/A', 'change_amount': f"{market_data.get('change_amount', 0):+.2f}" if market_data.get('change_amount') else 'N/A', 'volume': market_data.get('volume', 'N/A'), 'turnover_rate': f"{market_data.get('turnover_rate', 0):.2f}" if market_data.get('turnover_rate') else 'N/A', # 持仓信息 'position_status': '已持仓' if has_position else '未持仓', 'position_cost': f"{position_cost:.2f}" if has_position and position_cost else 'N/A', 'profit_loss_pct': f"{((market_data.get('current_price', 0) - position_cost) / position_cost * 100):+.2f}" if has_position and position_cost else 'N/A', # 交易时段信息 'trading_session': session_info.get('session', '未知') } # 直接调用主程序的通知服务发送 success = self.notification.send_notification(notification_data) if success: self.logger.info(f"[{stock_code}] {action_text}通知已发送") else: self.logger.warning(f"[{stock_code}] 通知发送失败") # 同时保存到智能盯盘的数据库 self.db.save_notification({ 'stock_code': stock_code, 'notify_type': 'decision', 'subject': f"智能盯盘 - {message}", 'content': content, 'status': 'sent' if success else 'failed' }) except Exception as e: self.logger.error(f"[{stock_code}] 发送通知失败: {e}") import traceback traceback.print_exc() def start_monitor(self, stock_code: str, check_interval: int = 300, auto_trade: bool = False, notify: bool = True, has_position: bool = False, position_cost: float = 0, position_quantity: int = 0, trading_hours_only: bool = True): """ 启动股票监控(在独立线程中运行) Args: stock_code: 股票代码 check_interval: 检查间隔(秒) auto_trade: 是否自动交易 notify: 是否发送通知 has_position: 是否已持仓 position_cost: 持仓成本 position_quantity: 持仓数量 trading_hours_only: 是否仅在交易时段监控(默认True) """ if stock_code in self.monitoring_threads: self.logger.warning(f"[{stock_code}] 监控已在运行中") return # 创建停止标志 stop_flag = threading.Event() self.stop_flags[stock_code] = stop_flag # 创建监控线程 thread = threading.Thread( target=self._monitor_loop, args=(stock_code, check_interval, auto_trade, notify, stop_flag, has_position, position_cost, position_quantity, trading_hours_only), daemon=True ) self.monitoring_threads[stock_code] = thread thread.start() position_info = f"(持仓: {position_quantity}股 @ {position_cost:.2f}元)" if has_position else "" trading_info = "(仅交易时段)" if trading_hours_only else "(全时段)" self.logger.info(f"[{stock_code}] 监控已启动{trading_info},间隔: {check_interval}秒 {position_info}") def stop_monitor(self, stock_code: str): """停止股票监控""" if stock_code not in self.monitoring_threads: self.logger.warning(f"[{stock_code}] 监控未运行") return # 设置停止标志 self.stop_flags[stock_code].set() # 等待线程结束 self.monitoring_threads[stock_code].join(timeout=5) # 清理 del self.monitoring_threads[stock_code] del self.stop_flags[stock_code] self.logger.info(f"[{stock_code}] 监控已停止") def _monitor_loop(self, stock_code: str, check_interval: int, auto_trade: bool, notify: bool, stop_flag: threading.Event, has_position: bool = False, position_cost: float = 0, position_quantity: int = 0, trading_hours_only: bool = True): """监控循环(在独立线程中运行)""" self.logger.info(f"[{stock_code}] 监控线程已启动") while not stop_flag.is_set(): try: # 执行分析 result = self.analyze_stock( stock_code=stock_code, auto_trade=auto_trade, notify=notify, has_position=has_position, position_cost=position_cost, position_quantity=position_quantity, trading_hours_only=trading_hours_only ) if result.get('skipped'): # 非交易时段跳过,不算错误 self.logger.debug(f"[{stock_code}] {result.get('error')}") elif result['success']: self.logger.info(f"[{stock_code}] 分析完成: {result['decision']['action']}") else: self.logger.error(f"[{stock_code}] 分析失败: {result.get('error')}") except Exception as e: self.logger.error(f"[{stock_code}] 监控循环异常: {e}") # 等待下一次检查 stop_flag.wait(check_interval) self.logger.info(f"[{stock_code}] 监控线程已退出") if __name__ == '__main__': # 测试代码 import os from dotenv import load_dotenv load_dotenv() logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) # 使用模拟模式测试 engine = SmartMonitorEngine( deepseek_api_key=os.getenv('DEEPSEEK_API_KEY'), use_simulator=True ) # 测试分析贵州茅台 print("\n测试分析贵州茅台(600519)...") result = engine.analyze_stock('600519', auto_trade=False, notify=False) if result['success']: print(f"\n分析成功!") print(f" 决策: {result['decision']['action']}") print(f" 信心度: {result['decision']['confidence']}%") print(f" 理由: {result['decision']['reasoning'][:100]}...") else: print(f"\n分析失败: {result.get('error')}")