437 lines
16 KiB
Python
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()
|
|
|