| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 趋势质量评估器 - 交易日15:06定时推送
- 检查是否为交易日,如果是则发送报告
- """
- import sys
- sys.path.insert(0, '/root/.openclaw/workspace/trend-quality-evaluator')
- import numpy as np
- import pandas as pd
- from trend_quality_evaluator import fetch_stock_data, TrendQualityEvaluator
- from datetime import datetime
- import smtplib
- from email.mime.text import MIMEText
- from email.mime.multipart import MIMEMultipart
- from email.mime.base import MIMEBase
- from email.header import Header
- from email import encoders
- import warnings
- warnings.filterwarnings('ignore')
- print("="*60)
- print(f"TQE交易日定时推送 - {datetime.now().strftime('%Y-%m-%d %H:%M')}")
- print("="*60)
- # 获取数据
- df = fetch_stock_data("399673", "2025-01-01", "2026-12-31", "d")
- if df is None:
- print("❌ 数据获取失败,跳过")
- exit(0)
- # 检查今天是否有数据(是否为交易日)
- today = datetime.now().strftime('%Y-%m-%d')
- if today not in df.index.strftime('%Y-%m-%d').values:
- print(f"📅 {today} 非交易日,跳过推送")
- exit(0)
- print(f"✅ {today} 是交易日,生成报告...")
- # 获取最近365天数据用于统计
- last_365 = df.tail(365).copy()
- # 评估今日趋势质量
- evaluator = TrendQualityEvaluator()
- score = evaluator.evaluate(df)
- # 获取最新数据
- latest = df.iloc[-1]
- prev = df.iloc[-2] if len(df) > 1 else latest
- # 计算涨跌
- daily_change = latest['close'] - prev['close']
- daily_change_pct = daily_change / prev['close'] * 100
- # 计算近期趋势
- ret_5d = (latest['close'] / df['close'].iloc[-6] - 1) * 100 if len(df) >= 6 else 0
- ret_20d = (latest['close'] / df['close'].iloc[-21] - 1) * 100 if len(df) >= 21 else 0
- # 生成邮件内容
- state_name = ['震荡', '趋势', '反转'][0] # TQE不输出状态,只输出评分
- is_tradeable = score.is_tradeable
- html = f"""
- <html>
- <head>
- <meta charset="utf-8">
- <style>
- body {{ font-family: Arial, sans-serif; margin: 20px; font-size: 13px; }}
- h1 {{ color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; font-size: 18px; }}
- h2 {{ color: #555; margin-top: 20px; border-left: 4px solid #2196F3; padding-left: 10px; font-size: 14px; }}
- .summary {{ background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0; }}
- .today {{ background: #e3f2fd; padding: 15px; border-radius: 5px; margin: 15px 0; border-left: 4px solid #2196F3; }}
- .tradeable {{ background: #e8f5e9; border-left: 4px solid #4CAF50; }}
- .not-tradeable {{ background: #ffebee; border-left: 4px solid #f44336; }}
- table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 12px; }}
- th {{ background: #4CAF50; color: white; padding: 10px; text-align: center; }}
- td {{ padding: 8px; border-bottom: 1px solid #ddd; text-align: center; }}
- tr:nth-child(even) {{ background: #f8f9fa; }}
- .positive {{ color: #4CAF50; font-weight: bold; }}
- .negative {{ color: #f44336; font-weight: bold; }}
- .score-high {{ color: #4CAF50; font-size: 24px; font-weight: bold; }}
- .score-low {{ color: #f44336; font-size: 24px; font-weight: bold; }}
- .score-mid {{ color: #FF9800; font-size: 24px; font-weight: bold; }}
- </style>
- </head>
- <body>
- <h1>📊 Trend-Quality-Evaluator 每日质量报告</h1>
-
- <div class="today {'tradeable' if is_tradeable else 'not-tradeable'}">
- <h2>📈 今日评估 ({today})</h2>
- <p><strong>收盘价:</strong> {latest['close']:.2f}</p>
- <p><strong>日涨跌:</strong> <span class="{'positive' if daily_change >= 0 else 'negative'}">{daily_change:+.2f} ({daily_change_pct:+.2f}%)</span></p>
-
- <p><strong>趋势质量评分:</strong> <span class="{'score-high' if score.total_score >= 80 else 'score-mid' if score.total_score >= 60 else 'score-low'}">{score.total_score:.1f}分</span></p>
-
- <p><strong>交易建议:</strong> {'✅ 可交易 (≥60分)' if is_tradeable else '❌ 观望 (<60分)'}</p>
- </div>
-
- <div class="summary">
- <h2>📊 各因子得分详情</h2>
- <table>
- <tr><th>因子</th><th>得分</th><th>满分</th><th>原始指标</th><th>阈值</th></tr>
- <tr><td>ADX趋势强度</td><td>{score.adx_score:.1f}</td><td>30</td><td>ADX={score.adx_value:.2f}</td><td>>25</td></tr>
- <tr><td>均线斜率</td><td>{score.ma_slope_score:.1f}</td><td>25</td><td>斜率={score.ma_slope:.4f}</td><td>>1.002</td></tr>
- <tr><td>波动率收缩</td><td>{score.volatility_score:.1f}</td><td>20</td><td>ATR比={score.volatility_ratio:.3f}</td><td><0.8</td></tr>
- <tr><td>时间框架共振</td><td>{score.timeframe_score:.1f}</td><td>15</td><td>日周共振</td><td>-</td></tr>
- <tr><td>成交量确认</td><td>{score.volume_score:.1f}</td><td>10</td><td>量比={score.volume_ratio:.2f}x</td><td>>1.5</td></tr>
- </table>
- </div>
-
- <div class="summary">
- <h2>📈 近期趋势</h2>
- <p><strong>5日涨跌:</strong> <span class="{'positive' if ret_5d >= 0 else 'negative'}">{ret_5d:+.2f}%</span></p>
- <p><strong>20日涨跌:</strong> <span class="{'positive' if ret_20d >= 0 else 'negative'}">{ret_20d:+.2f}%</span></p>
- <p><strong>365天最高:</strong> {last_365['close'].max():.2f} ({last_365['close'].idxmax().strftime('%y-%m-%d')})</p>
- <p><strong>365天最低:</strong> {last_365['close'].min():.2f} ({last_365['close'].idxmin().strftime('%y-%m-%d')})</p>
- </div>
-
- <h2>💡 交易建议说明</h2>
- <ul>
- <li><strong>≥80分:</strong> 优秀趋势,建议重仓</li>
- <li><strong>70-79分:</strong> 良好趋势,建议中等仓位</li>
- <li><strong>60-69分:</strong> 及格趋势,建议轻仓试探</li>
- <li><strong>40-59分:</strong> 趋势较弱,建议观望</li>
- <li><strong><40分:</strong> 趋势混乱,避免交易</li>
- </ul>
-
- <hr>
- <p style="color: #666; font-size: 11px;">
- 推送时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}<br>
- 评估标的: 创业板50指数 (sz399673)<br>
- 模型版本: Trend-Quality-Evaluator v1.0 (参数已优化)
- </p>
- </body>
- </html>
- """
- # 发送邮件
- EMAIL_CONFIG = {
- "smtp_server": "localhost",
- "smtp_port": 25,
- "sender_email": "regime@erwin.wang",
- "receiver_email": "380880504@qq.com"
- }
- msg = MIMEMultipart('related')
- msg['Subject'] = Header(f"📊 TQE每日质量报告 [{today}] {'✅可交易' if is_tradeable else '❌观望'} {score.total_score:.0f}分", 'utf-8')
- msg['From'] = EMAIL_CONFIG['sender_email']
- msg['To'] = EMAIL_CONFIG['receiver_email']
- msg.attach(MIMEText(html, 'html', 'utf-8'))
- try:
- with smtplib.SMTP(EMAIL_CONFIG['smtp_server'], EMAIL_CONFIG['smtp_port']) as server:
- server.sendmail(EMAIL_CONFIG['sender_email'], EMAIL_CONFIG['receiver_email'], msg.as_string())
- print(f"✅ 邮件发送成功! [{today}] 评分: {score.total_score:.1f}分 {'可交易' if is_tradeable else '观望'}")
- except Exception as e:
- print(f"❌ 邮件发送失败: {e}")
- print("="*60)
|