Files
aiagents-stock/sector_strategy_scheduler.py
T
2025-10-12 20:53:30 +08:00

437 lines
16 KiB
Python

"""
智策定时分析服务
支持定时运行板块策略分析并发送邮件通知
"""
import schedule
import threading
import time
from datetime import datetime
from sector_strategy_data import SectorStrategyDataFetcher
from sector_strategy_engine import SectorStrategyEngine
from notification_service import notification_service
import json
class SectorStrategyScheduler:
"""智策定时分析调度器"""
def __init__(self):
self.running = False
self.thread = None
self.schedule_time = "09:00" # 默认上午9点
self.enabled = False
self.last_run_time = None
self.last_result = None
print("[智策定时] 调度器初始化完成")
def start(self, schedule_time="09:00"):
"""
启动定时任务
Args:
schedule_time: 定时时间,格式 "HH:MM"
"""
if self.running:
print("[智策定时] 调度器已在运行中")
return False
self.schedule_time = schedule_time
self.enabled = True
self.running = True
# 清除之前的任务
schedule.clear('sector_strategy')
# 设置定时任务
schedule.every().day.at(schedule_time).do(self._run_analysis).tag('sector_strategy')
# 启动后台线程
self.thread = threading.Thread(target=self._schedule_loop, daemon=True)
self.thread.start()
print(f"[智策定时] ✓ 定时任务已启动,每天 {schedule_time} 运行")
return True
def stop(self):
"""停止定时任务"""
if not self.running:
print("[智策定时] 调度器未运行")
return False
self.running = False
self.enabled = False
schedule.clear('sector_strategy')
print("[智策定时] ✓ 定时任务已停止")
return True
def _schedule_loop(self):
"""定时任务循环"""
print("[智策定时] 后台线程已启动")
while self.running:
try:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次
except Exception as e:
print(f"[智策定时] ✗ 调度循环出错: {e}")
time.sleep(60)
def _run_analysis(self):
"""运行智策分析"""
print("\n" + "="*60)
print(f"[智策定时] 开始定时分析 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*60)
try:
# 1. 获取数据
print("[智策定时] [1/3] 获取市场数据...")
fetcher = SectorStrategyDataFetcher()
data = fetcher.get_all_sector_data()
if not data.get("success"):
print("[智策定时] ✗ 数据获取失败")
self._send_error_notification("数据获取失败")
return
print("[智策定时] ✓ 数据获取成功")
# 2. 运行AI分析
print("[智策定时] [2/3] AI智能体分析中...")
engine = SectorStrategyEngine(model="deepseek-chat")
result = engine.run_comprehensive_analysis(data)
if not result.get("success"):
print("[智策定时] ✗ 分析失败")
self._send_error_notification("AI分析失败")
return
print("[智策定时] ✓ 分析完成")
# 3. 发送邮件通知
print("[智策定时] [3/3] 发送邮件通知...")
self._send_analysis_notification(result)
# 保存最后运行结果
self.last_run_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.last_result = result
print("="*60)
print("[智策定时] ✓ 定时分析完成!")
print("="*60 + "\n")
except Exception as e:
print(f"[智策定时] ✗ 分析过程出错: {e}")
import traceback
traceback.print_exc()
self._send_error_notification(f"分析异常: {str(e)}")
def _send_analysis_notification(self, result):
"""发送分析结果邮件"""
try:
# 检查邮件配置
config = notification_service.config
if not config.get('email_enabled') or not all([
config.get('smtp_server'),
config.get('email_from'),
config.get('email_password'),
config.get('email_to')
]):
print("[智策定时] ⚠️ 邮件配置不完整,跳过发送")
return
predictions = result.get("final_predictions", {})
timestamp = result.get("timestamp", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# 构建邮件内容
subject = f"智策板块分析报告 - {timestamp}"
body = self._format_email_body(predictions, timestamp)
# 直接发送邮件
success = self._send_email_direct(subject, body)
if success:
print("[智策定时] ✓ 邮件发送成功")
else:
print("[智策定时] ✗ 邮件发送失败")
except Exception as e:
print(f"[智策定时] ✗ 邮件发送异常: {e}")
import traceback
traceback.print_exc()
def _send_error_notification(self, error_msg):
"""发送错误通知邮件"""
try:
subject = f"智策定时分析失败 - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
body = f"""
智策定时分析任务失败
时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
错误: {error_msg}
请检查系统日志获取详细信息。
"""
self._send_email_direct(subject, body)
except:
pass
def _send_email_direct(self, subject, body):
"""直接发送邮件(参考notification_service的实现)"""
try:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
config = notification_service.config
# 创建邮件
msg = MIMEMultipart()
msg['From'] = config['email_from']
msg['To'] = config['email_to']
msg['Subject'] = subject
# 添加正文(纯文本)
msg.attach(MIMEText(body, 'plain', 'utf-8'))
print(f"[智策定时] 📧 正在发送邮件...")
print(f"[智策定时] - 收件人: {config['email_to']}")
print(f"[智策定时] - 主题: {subject}")
# 根据端口选择连接方式
if config['smtp_port'] == 465:
print(f"[智策定时] - 使用 SMTP_SSL 连接 {config['smtp_server']}:{config['smtp_port']}")
server = smtplib.SMTP_SSL(config['smtp_server'], config['smtp_port'], timeout=15)
else:
print(f"[智策定时] - 使用 SMTP+TLS 连接 {config['smtp_server']}:{config['smtp_port']}")
server = smtplib.SMTP(config['smtp_server'], config['smtp_port'], timeout=15)
server.starttls()
print(f"[智策定时] - 正在登录...")
server.login(config['email_from'], config['email_password'])
print(f"[智策定时] - 正在发送...")
server.send_message(msg)
server.quit()
print(f"[智策定时] ✓ 邮件发送成功")
return True
except Exception as e:
print(f"[智策定时] ✗ 邮件发送失败: {e}")
import traceback
traceback.print_exc()
return False
def _format_email_body(self, predictions, timestamp):
"""格式化邮件正文"""
if not predictions or predictions.get("prediction_text"):
# 文本格式
return f"""
智策板块策略分析报告
分析时间: {timestamp}
{predictions.get('prediction_text', '暂无预测')}
---
本邮件由智策系统自动发送
"""
# JSON格式预测
body_parts = []
# 标题
body_parts.append("="*60)
body_parts.append("智策板块策略分析报告")
body_parts.append("="*60)
body_parts.append(f"分析时间: {timestamp}")
body_parts.append(f"AI模型: DeepSeek Multi-Agent System")
body_parts.append("")
# 1. 板块多空
long_short = predictions.get("long_short", {})
if long_short:
body_parts.append("="*60)
body_parts.append("一、板块多空预测")
body_parts.append("="*60)
body_parts.append("")
# 看多板块
bullish = long_short.get("bullish", [])
if bullish:
body_parts.append("【看多板块】")
body_parts.append("")
for idx, item in enumerate(bullish, 1):
body_parts.append(f"{idx}. {item.get('sector', 'N/A')} (信心度: {item.get('confidence', 0)}/10)")
body_parts.append(f" 理由: {item.get('reason', 'N/A')}")
body_parts.append(f" 风险: {item.get('risk', 'N/A')}")
body_parts.append("")
# 看空板块
bearish = long_short.get("bearish", [])
if bearish:
body_parts.append("【看空板块】")
body_parts.append("")
for idx, item in enumerate(bearish, 1):
body_parts.append(f"{idx}. {item.get('sector', 'N/A')} (信心度: {item.get('confidence', 0)}/10)")
body_parts.append(f" 理由: {item.get('reason', 'N/A')}")
body_parts.append(f" 风险: {item.get('risk', 'N/A')}")
body_parts.append("")
# 2. 板块轮动
rotation = predictions.get("rotation", {})
if rotation:
body_parts.append("="*60)
body_parts.append("二、板块轮动预测")
body_parts.append("="*60)
body_parts.append("")
# 当前强势
current_strong = rotation.get("current_strong", [])
if current_strong:
body_parts.append("【当前强势板块】")
body_parts.append("")
for item in current_strong:
body_parts.append(f"• {item.get('sector', 'N/A')}")
body_parts.append(f" 轮动逻辑: {item.get('logic', 'N/A')[:100]}...")
body_parts.append(f" 时间窗口: {item.get('time_window', 'N/A')}")
body_parts.append(f" 操作建议: {item.get('advice', 'N/A')}")
body_parts.append("")
# 潜力接力
potential = rotation.get("potential", [])
if potential:
body_parts.append("【潜力接力板块】⭐ 重点关注")
body_parts.append("")
for item in potential:
body_parts.append(f"• {item.get('sector', 'N/A')}")
body_parts.append(f" 轮动逻辑: {item.get('logic', 'N/A')[:100]}...")
body_parts.append(f" 时间窗口: {item.get('time_window', 'N/A')}")
body_parts.append(f" 操作建议: {item.get('advice', 'N/A')}")
body_parts.append("")
# 3. 板块热度
heat = predictions.get("heat", {})
if heat:
body_parts.append("="*60)
body_parts.append("三、板块热度排行")
body_parts.append("="*60)
body_parts.append("")
# 最热板块
hottest = heat.get("hottest", [])
if hottest:
body_parts.append("【最热板块 TOP5】")
body_parts.append("")
for idx, item in enumerate(hottest, 1):
body_parts.append(f"{idx}. {item.get('sector', 'N/A')} - 热度: {item.get('score', 0)}分 ({item.get('trend', 'N/A')})")
body_parts.append("")
# 升温板块
heating = heat.get("heating", [])
if heating:
body_parts.append("【升温板块】")
body_parts.append("")
for idx, item in enumerate(heating, 1):
body_parts.append(f"{idx}. {item.get('sector', 'N/A')} - 热度: {item.get('score', 0)}分 ↗")
body_parts.append("")
# 4. 策略总结
summary = predictions.get("summary", {})
if summary:
body_parts.append("="*60)
body_parts.append("四、策略总结")
body_parts.append("="*60)
body_parts.append("")
if summary.get('market_view'):
body_parts.append("【市场观点】")
body_parts.append(summary['market_view'])
body_parts.append("")
if summary.get('key_opportunity'):
body_parts.append("【核心机会】⭐")
body_parts.append(summary['key_opportunity'])
body_parts.append("")
if summary.get('major_risk'):
body_parts.append("【主要风险】⚠️")
body_parts.append(summary['major_risk'])
body_parts.append("")
if summary.get('strategy'):
body_parts.append("【整体策略】")
body_parts.append(summary['strategy'])
body_parts.append("")
# 结束语
body_parts.append("="*60)
body_parts.append("本报告由智策AI系统自动生成并发送")
body_parts.append("仅供参考,不构成投资建议")
body_parts.append("="*60)
return "\n".join(body_parts)
def manual_run(self):
"""手动触发一次分析"""
print("[智策定时] 手动触发分析...")
self._run_analysis()
def get_status(self):
"""获取调度器状态"""
return {
"running": self.running,
"enabled": self.enabled,
"schedule_time": self.schedule_time,
"last_run_time": self.last_run_time,
"next_run_time": self._get_next_run_time()
}
def _get_next_run_time(self):
"""获取下次运行时间"""
if not self.running:
return None
try:
jobs = schedule.get_jobs('sector_strategy')
if jobs:
next_run = jobs[0].next_run
if next_run:
return next_run.strftime('%Y-%m-%d %H:%M:%S')
except:
pass
return None
# 创建全局实例
sector_strategy_scheduler = SectorStrategyScheduler()
# 测试函数
if __name__ == "__main__":
print("智策定时分析服务测试")
print("="*60)
# 启动定时任务(测试用,设置为当前时间后1分钟)
from datetime import datetime, timedelta
test_time = (datetime.now() + timedelta(minutes=1)).strftime("%H:%M")
print(f"设置测试时间: {test_time}")
sector_strategy_scheduler.start(test_time)
# 保持运行
try:
while True:
status = sector_strategy_scheduler.get_status()
print(f"\n状态: {status}")
time.sleep(30)
except KeyboardInterrupt:
print("\n停止测试...")
sector_strategy_scheduler.stop()