""" A股智能体 - 主控制器 负责执行每日选股并发送通知 """ import asyncio from typing import Dict, Any, Optional from datetime import datetime, time from app.utils.logger import logger from app.config import get_settings from app.services.dingtalk_service import get_dingtalk_service from app.services.telegram_service import get_telegram_service from app.astock_agent.tushare_client import get_tushare_client from app.astock_agent.short_term_thematic_selector import get_thematic_selector class AStockAgent: """A股智能体""" _instance = None _initialized = False def __new__(cls, *args, **kwargs): """单例模式""" if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): """初始化智能体""" if AStockAgent._initialized: return AStockAgent._initialized = True self.settings = get_settings() # 初始化Tushare客户端 self.ts_client = get_tushare_client(self.settings.tushare_token) if not self.ts_client: logger.error("Tushare客户端初始化失败,请检查配置") raise Exception("Tushare客户端初始化失败") # 初始化选股器 self.selector = get_thematic_selector(self.ts_client) # 初始化通知服务 self.dingtalk = get_dingtalk_service() self.telegram = get_telegram_service() # 运行状态 self.running = False self._task = None logger.info("A股智能体初始化完成") async def run_once(self) -> Dict[str, Any]: """ 执行一次选股 Returns: 选股结果 """ try: logger.info("\n" + "=" * 60) logger.info("📊 开始执行短期题材选股") logger.info("=" * 60) # 执行选股 result = self.selector.select_stocks(max_stocks=10) # 输出日志 self._log_result(result) # 发送通知 await self._send_notifications(result) return result except Exception as e: logger.error(f"选股执行失败: {e}") import traceback logger.error(traceback.format_exc()) return {} def _log_result(self, result: Dict[str, Any]): """输出选股结果到日志""" if not result or result.get('total_stocks', 0) == 0: logger.info("\n📊 今日未选出符合条件的股票") return logger.info(f"\n📊 选股完成,共选出 {result['total_stocks']} 只股票") if result.get('summary'): summary = result['summary'] logger.info(f" - 总仓位: {summary.get('position_percent', 0):.1f}%") logger.info(f" - 涉及板块: {summary.get('sector_count', 0)} 个") for stock in result.get('stocks', []): logger.info(f" - {stock['name']}({stock['ts_code']}): {stock['close']:.2f}元, " f"仓位:{stock['position']*100:.1f}%, 评分:{stock['score']:.1f}分") async def _send_notifications(self, result: Dict[str, Any]): """发送选股通知""" try: # 格式化输出文本 text = self.selector.format_output_text(result) # 发送到钉钉 if self.settings.dingtalk_enabled: await self.dingtalk.send_markdown( "📊 短期题材选股结果", text ) logger.info("✅ 钉钉通知已发送") # 发送到Telegram if self.settings.telegram_enabled: await self.telegram.send_message(text) logger.info("✅ Telegram通知已发送") except Exception as e: logger.error(f"发送通知失败: {e}") async def run_daily(self, run_time: str = "15:30"): """ 每日定时运行 Args: run_time: 运行时间(HH:MM格式,24小时制) """ self.running = True logger.info("\n" + "=" * 60) logger.info("🚀 A股智能体已启动") logger.info(f"⏰ 运行时间: 每天 {run_time}(盘后)") logger.info("=" * 60) # 解析运行时间 hour, minute = map(int, run_time.split(':')) while self.running: try: # 计算下次运行时间 now = datetime.now() next_run = now.replace( hour=hour, minute=minute, second=0, microsecond=0 ) # 如果今天的运行时间已过,设置为明天 if now >= next_run: from datetime import timedelta next_run = next_run + timedelta(days=1) wait_seconds = (next_run - now).total_seconds() logger.info(f"⏳ 等待下次运行: {next_run.strftime('%Y-%m-%d %H:%M:%S')} " f"(等待 {wait_seconds/3600:.1f} 小时)") # 等待到运行时间 await asyncio.sleep(wait_seconds) # 执行选股 await self.run_once() except Exception as e: logger.error(f"定时运行出错: {e}") import traceback logger.error(traceback.format_exc()) # 等待1小时后重试 await asyncio.sleep(3600) def stop(self): """停止运行""" self.running = False logger.info("A股智能体已停止") # 全局单例 _astock_agent: Optional[AStockAgent] = None def get_astock_agent() -> AStockAgent: """获取A股智能体单例""" global _astock_agent if _astock_agent is None: _astock_agent = AStockAgent() return _astock_agent async def main(): """测试入口""" agent = get_astock_agent() # 执行一次选股 result = await agent.run_once() # 输出结果 print("\n" + "=" * 60) print(agent.selector.format_output_text(result)) print("=" * 60) if __name__ == "__main__": asyncio.run(main())