| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- CYB50市场状态识别 - 每日邮件发送脚本
- 通过本地SMTP服务器(直接投递模式)发送邮件
- """
- import sys
- sys.path.insert(0, '/root/.openclaw/workspace/market-regime-identifier')
- import numpy as np
- import pandas as pd
- from cyb50_market_classifier import fetch_cyb50_data, calculate_features, define_market_regime
- from sklearn.ensemble import RandomForestClassifier
- import smtplib
- from email.mime.text import MIMEText
- from email.mime.multipart import MIMEMultipart
- from email.header import Header
- from datetime import datetime
- import os
- import warnings
- warnings.filterwarnings('ignore')
- # SMTP配置
- SMTP_SERVER = os.getenv('SMTP_SERVER', 'localhost')
- SMTP_PORT = int(os.getenv('SMTP_PORT', '25'))
- SENDER = os.getenv('SMTP_SENDER', 'kalman@erwin.wang')
- RECEIVER = '380880504@qq.com'
- print("="*60)
- print(f"CYB50每日市场状态报告 - {datetime.now().strftime('%Y-%m-%d %H:%M')}")
- print(f"SMTP: {SMTP_SERVER}:{SMTP_PORT}")
- print("="*60)
- # 获取数据
- df = fetch_cyb50_data('2024-01-01', '2026-12-31')
- if df is None:
- print("数据获取失败")
- exit(1)
- # 计算特征和标签
- features = calculate_features(df)
- labels = define_market_regime(df, lookback=10)
- # 训练模型
- valid_idx = ~np.isnan(labels)
- X = features[valid_idx]
- y = labels[valid_idx]
- clf = RandomForestClassifier(
- n_estimators=100, max_depth=10, min_samples_split=20,
- min_samples_leaf=10, random_state=42, class_weight='balanced'
- )
- clf.fit(X, y)
- # 预测所有数据
- states = clf.predict(X)
- probs = clf.predict_proba(X)
- # 对齐数据
- df_aligned = df.iloc[-len(states):].copy()
- df_aligned['state'] = states
- df_aligned['prob_ranging'] = probs[:, 0]
- df_aligned['prob_trend'] = probs[:, 1]
- df_aligned['prob_reversal'] = probs[:, 2]
- # 获取最新数据
- today = df_aligned.iloc[-1]
- yesterday = df_aligned.iloc[-2] if len(df_aligned) > 1 else today
- state_names = ['震荡', '趋势', '反转']
- colors = ['#2196F3', '#4CAF50', '#FF5722']
- state_name = state_names[int(today['state'])]
- state_color = colors[int(today['state'])]
- # 计算涨跌
- daily_change = today['close'] - yesterday['close']
- daily_change_pct = daily_change / yesterday['close'] * 100
- # 获取最近30天
- last_30 = df_aligned.tail(30).copy()
- # 生成HTML表格
- html_rows = ""
- for idx, row in last_30.iterrows():
- s = int(row['state'])
- html_rows += f"""
- <tr>
- <td>{idx.strftime('%m-%d')}</td>
- <td>{row['close']:.2f}</td>
- <td style="color: {colors[s]}; font-weight: bold;">{state_names[s]}</td>
- <td>{row['prob_ranging']:.1%}</td>
- <td>{row['prob_trend']:.1%}</td>
- <td>{row['prob_reversal']:.1%}</td>
- </tr>
- """
- # 邮件内容
- html = f"""
- <html>
- <head>
- <meta charset="utf-8">
- <style>
- body {{ font-family: Arial, sans-serif; margin: 20px; font-size: 12px; }}
- h1 {{ color: #333; border-bottom: 3px solid #2196F3; padding-bottom: 10px; font-size: 18px; }}
- .today {{ background: #e3f2fd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #2196F3; }}
- table {{ width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 11px; }}
- th {{ background: #2196F3; color: white; padding: 8px; text-align: center; }}
- td {{ padding: 6px 8px; border-bottom: 1px solid #ddd; text-align: center; }}
- tr:nth-child(even) {{ background: #f8f9fa; }}
- </style>
- </head>
- <body>
- <h1>CYB50每日市场状态报告</h1>
- <div class="today">
- <h2>今日状态 ({df_aligned.index[-1].strftime('%Y-%m-%d')})</h2>
- <p><strong>收盘价:</strong> {today['close']:.2f}</p>
- <p><strong>日涨跌:</strong> <span style="color: {'green' if daily_change >= 0 else 'red'};">{daily_change:+.2f} ({daily_change_pct:+.2f}%)</span></p>
- <p><strong>市场状态:</strong> <span style="color: {state_color}; font-size: 16px; font-weight: bold;">{state_name}</span></p>
- <p><strong>状态概率:</strong> 震荡 {today['prob_ranging']:.1%} / 趋势 {today['prob_trend']:.1%} / 反转 {today['prob_reversal']:.1%}</p>
- </div>
- <h2>最近30天数据</h2>
- <table>
- <thead>
- <tr>
- <th>日期</th>
- <th>收盘价</th>
- <th>状态</th>
- <th>震荡概率</th>
- <th>趋势概率</th>
- <th>反转概率</th>
- </tr>
- </thead>
- <tbody>
- {html_rows}
- </tbody>
- </table>
- <hr>
- <p style="color: #666; font-size: 11px;">
- 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}<br>
- 数据更新至: {df_aligned.index[-1].strftime('%Y-%m-%d')}<br>
- 模型准确率: 72.10% | 创业板50指数 (sz399673)
- </p>
- </body>
- </html>
- """
- # 发送邮件
- msg = MIMEMultipart('related')
- msg['Subject'] = Header(f"CYB50-Regime每日报告 [{df_aligned.index[-1].strftime('%m-%d')}] 当前{state_name}", 'utf-8')
- msg['From'] = f"regime <{SENDER}>"
- msg['To'] = RECEIVER
- msg.attach(MIMEText(html, 'html', 'utf-8'))
- try:
- with smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=10) as server:
- server.sendmail(SENDER, RECEIVER, msg.as_string())
- print(f"[OK] 邮件发送成功! [{df_aligned.index[-1].strftime('%Y-%m-%d')}] 当前状态: {state_name}")
- except Exception as e:
- print(f"[ERROR] 邮件发送失败: {e}")
- print(f"请确保SMTP服务器已启动: python smtp_relay_server.py --direct")
- print("="*60)
|