| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """生成最近60天详细数据邮件"""
- 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 warnings
- warnings.filterwarnings('ignore')
- # 获取数据
- df = fetch_cyb50_data('2024-01-01', '2026-03-06')
- if df is None:
- 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]
- # 获取最近60天
- last_60 = df_aligned.tail(60).copy()
- last_60['date'] = last_60.index.strftime('%m-%d')
- last_60['change'] = last_60['close'].pct_change() * 100
- state_names = ['震荡', '趋势', '反转']
- colors = ['#2196F3', '#4CAF50', '#FF5722']
- # 生成HTML
- html_rows = ""
- for idx, row in last_60.iterrows():
- state = int(row['state'])
- state_name = state_names[state]
- color = colors[state]
- change = row['change'] if not pd.isna(row['change']) else 0
- change_str = f"{change:+.2f}%" if change != 0 else "-"
- change_color = "green" if change > 0 else "red" if change < 0 else "gray"
-
- html_rows += f"""
- <tr>
- <td>{idx.strftime('%Y-%m-%d')}</td>
- <td>{row['close']:.2f}</td>
- <td style="color: {color}; font-weight: bold;">{state_name}</td>
- <td>{row['prob_ranging']:.1%}</td>
- <td>{row['prob_trend']:.1%}</td>
- <td>{row['prob_reversal']:.1%}</td>
- <td style="color: {change_color};">{change_str}</td>
- </tr>
- """
- # 计算统计
- summary = f"""
- <div class="summary">
- <h2>📊 最近60天统计</h2>
- <p><strong>统计区间:</strong> {last_60.index[0].date()} ~ {last_60.index[-1].date()}</p>
- <p><strong>起始价格:</strong> {last_60['close'].iloc[0]:.2f}</p>
- <p><strong>结束价格:</strong> {last_60['close'].iloc[-1]:.2f}</p>
- <p><strong>区间涨跌:</strong> {(last_60['close'].iloc[-1]/last_60['close'].iloc[0]-1)*100:+.2f}%</p>
- <p><strong>最高价:</strong> {last_60['close'].max():.2f} ({last_60['close'].idxmax().strftime('%m-%d')})</p>
- <p><strong>最低价:</strong> {last_60['close'].min():.2f} ({last_60['close'].idxmin().strftime('%m-%d')})</p>
- <br>
- <p><strong>状态分布:</strong></p>
- <p>🟦 震荡: {(last_60['state']==0).sum()}天 ({(last_60['state']==0).sum()/60*100:.1f}%)</p>
- <p>🟩 趋势: {(last_60['state']==1).sum()}天 ({(last_60['state']==1).sum()/60*100:.1f}%)</p>
- <p>🟧 反转: {(last_60['state']==2).sum()}天 ({(last_60['state']==2).sum()/60*100:.1f}%)</p>
- </div>
- """
- 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; }}
- h2 {{ color: #555; margin-top: 20px; border-left: 4px solid #4CAF50; padding-left: 10px; font-size: 14px; }}
- .summary {{ background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0; }}
- .summary p {{ margin: 5px 0; }}
- table {{ width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 11px; }}
- th {{ background: #2196F3; color: white; padding: 8px; text-align: center; position: sticky; top: 0; }}
- td {{ padding: 6px 8px; border-bottom: 1px solid #ddd; text-align: center; }}
- tr:nth-child(even) {{ background: #f8f9fa; }}
- tr:hover {{ background: #e3f2fd; }}
- .table-container {{ max-height: 500px; overflow-y: auto; }}
- </style>
- </head>
- <body>
- <h1>📊 创业板50最近60天详细数据 (2026-01-06 ~ 2026-03-06)</h1>
- {summary}
-
- <h2>📋 每日详细数据</h2>
- <div class="table-container">
- <table>
- <thead>
- <tr>
- <th>日期</th>
- <th>收盘价</th>
- <th>状态</th>
- <th>震荡概率</th>
- <th>趋势概率</th>
- <th>反转概率</th>
- <th>日涨跌</th>
- </tr>
- </thead>
- <tbody>
- {html_rows}
- </tbody>
- </table>
- </div>
-
- <hr>
- <p style="color: #666; font-size: 11px;">
- 生成时间: 2026-03-06 19:10<br>
- 数据更新至: 2026-03-06<br>
- 模型准确率: 72.10%
- </p>
- </body>
- </html>
- """
- # 保存HTML
- with open('/root/.openclaw/workspace/market-regime-identifier/last_60_days_report.html', 'w', encoding='utf-8') as f:
- f.write(html)
- print("✓ HTML报告已生成")
- print(f"最近60天: {last_60.index[0].date()} ~ {last_60.index[-1].date()}")
- print(f"\n状态分布:")
- print(f" 震荡: {(last_60['state']==0).sum()}天")
- print(f" 趋势: {(last_60['state']==1).sum()}天")
- print(f" 反转: {(last_60['state']==2).sum()}天")
|