stock-ai-agent/backend/app/astock_agent/astock_agent.py
2026-03-11 00:01:18 +08:00

207 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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())