| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- Trend-Max: 三大策略综合分析
- 汇总 Trend-Mix, TQE, Regime 的结果
- 工作日15:30定时推送
- 发件人: trend-max
- """
- import sys
- sys.path.insert(0, '/root/.openclaw/workspace/trend-mix')
- sys.path.insert(0, '/root/.openclaw/workspace/trend-quality-evaluator')
- sys.path.insert(0, '/root/.openclaw/workspace/market-regime-identifier')
- import numpy as np
- import pandas as pd
- from trend_mix_strategy import TrendMixStrategy
- from trend_quality_evaluator import fetch_stock_data as tqe_fetch, TrendQualityEvaluator
- from cyb50_market_classifier import fetch_cyb50_data, calculate_features, define_market_regime
- from sklearn.ensemble import RandomForestClassifier
- from datetime import datetime
- import smtplib
- from email.mime.text import MIMEText
- from email.mime.multipart import MIMEMultipart
- from email.header import Header
- import warnings
- warnings.filterwarnings('ignore')
- print("="*70)
- print(f"Trend-Max 三大策略综合分析 - {datetime.now().strftime('%Y-%m-%d %H:%M')}")
- print("="*70)
- # ============================================
- # 1. Trend-Mix 分析
- # ============================================
- print("\n[1/3] 运行 Trend-Mix 分析...")
- strategy_mix = TrendMixStrategy()
- df_mix = strategy_mix.fetch_data("399673", "2024-01-01", "2026-12-31")
- if df_mix is not None:
- # 计算综合状态
- composite_df = strategy_mix.calc_composite_state()
- latest_mix = composite_df.iloc[-1]
-
- mix_result = {
- 'state': latest_mix['Composite_State'],
- 'adx': latest_mix['ADX'],
- 'adx_state': latest_mix['ADX_State'],
- 'vr_state': latest_mix['VR_State'],
- 'hurst_state': latest_mix['Hurst_State'],
- 'bb_state': latest_mix['BB_State'],
- 'deviation': latest_mix['Deviation']
- }
- print(f" ✓ Trend-Mix: {mix_result['state']}")
- else:
- mix_result = None
- print(" ✗ Trend-Mix 失败")
- # ============================================
- # 2. TQE 分析
- # ============================================
- print("\n[2/3] 运行 TQE 分析...")
- df_tqe = tqe_fetch("399673", "2024-01-01", "2026-12-31", "d")
- if df_tqe is not None:
- evaluator = TrendQualityEvaluator()
- score = evaluator.evaluate(df_tqe)
-
- tqe_result = {
- 'total_score': score.total_score,
- 'is_tradeable': score.is_tradeable,
- 'adx_score': score.adx_score,
- 'ma_slope_score': score.ma_slope_score,
- 'volatility_score': score.volatility_score,
- 'timeframe_score': score.timeframe_score,
- 'volume_score': score.volume_score,
- 'adx_value': score.adx_value,
- 'ma_slope': score.ma_slope,
- 'volatility_ratio': score.volatility_ratio,
- 'volume_ratio': score.volume_ratio
- }
- print(f" ✓ TQE: {score.total_score:.1f}分 {'✅可交易' if score.is_tradeable else '❌观望'}")
- else:
- tqe_result = None
- print(" ✗ TQE 失败")
- # ============================================
- # 3. Regime 分析
- # ============================================
- print("\n[3/3] 运行 Regime 分析...")
- df_regime = fetch_cyb50_data("2024-01-01", "2026-12-31")
- if df_regime is not None:
- features = calculate_features(df_regime)
- labels = define_market_regime(df_regime, 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)
-
- # 预测
- latest_features = features.iloc[-1:]
- pred = clf.predict(latest_features)[0]
- proba = clf.predict_proba(latest_features)[0]
-
- state_names = ['震荡', '趋势', '反转']
- regime_result = {
- 'state': state_names[pred],
- 'confidence': proba[pred],
- 'prob_ranging': proba[0],
- 'prob_trend': proba[1],
- 'prob_reversal': proba[2]
- }
- print(f" ✓ Regime: {regime_result['state']} (置信度{regime_result['confidence']:.1%})")
- else:
- regime_result = None
- print(" ✗ Regime 失败")
- # ============================================
- # 4. 综合分析
- # ============================================
- print("\n" + "="*70)
- print("综合分析")
- print("="*70)
- state_names_cn = ['震荡', '趋势', '反转']
- final_recommendation = "观望"
- final_confidence = 0.0
- if mix_result and tqe_result and regime_result:
- # 统计各策略的状态
- states = [mix_result['state'], '趋势' if tqe_result['is_tradeable'] else '震荡', regime_result['state']]
-
- # 交易建议映射
- trade_advice = {
- '强趋势': '重仓',
- '趋势': '持仓/加仓',
- '潜在爆发': '建仓',
- '反转': '止盈/减仓',
- '震荡': '观望'
- }
-
- mix_advice = trade_advice.get(mix_result['state'], '观望')
- tqe_advice = '交易' if tqe_result['is_tradeable'] else '观望'
- regime_advice = regime_result['state']
-
- # 综合判断
- trend_count = sum([s in ['强趋势', '趋势'] for s in states])
- ranging_count = sum([s == '震荡' for s in states])
- reversal_count = sum([s == '反转' for s in states])
-
- if trend_count >= 2 and tqe_result['is_tradeable']:
- final_recommendation = "✅ 交易 (趋势确认)"
- final_confidence = 0.8
- elif ranging_count >= 2 or not tqe_result['is_tradeable']:
- final_recommendation = "❌ 观望 (震荡整理)"
- final_confidence = 0.7
- elif reversal_count >= 2:
- final_recommendation = "⚠️ 谨慎 (可能反转)"
- final_confidence = 0.6
- else:
- final_recommendation = "❓ 分歧 (信号不一)"
- final_confidence = 0.4
-
- print(f"\nTrend-Mix: {mix_result['state']} → {mix_advice}")
- print(f"TQE: {tqe_result['total_score']:.0f}分 → {tqe_advice}")
- print(f"Regime: {regime_result['state']} → {regime_advice}")
- print(f"\n综合建议: {final_recommendation}")
- # ============================================
- # 5. 生成邮件
- # ============================================
- print("\n生成邮件...")
- today = datetime.now().strftime('%Y-%m-%d')
- 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 #673AB7; padding-bottom: 10px; font-size: 20px; }}
- h2 {{ color: #555; margin-top: 25px; border-left: 4px solid #2196F3; padding-left: 10px; font-size: 15px; }}
- h3 {{ color: #666; font-size: 14px; }}
- .summary {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0; }}
- .strategy {{ background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #4CAF50; }}
- .trade-yes {{ background: #e8f5e9; border-left: 4px solid #4CAF50; }}
- .trade-no {{ background: #ffebee; border-left: 4px solid #f44336; }}
- .trade-warn {{ background: #fff3cd; border-left: 4px solid #FFC107; }}
- table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 12px; }}
- th {{ background: #673AB7; color: white; padding: 10px; text-align: center; }}
- td {{ padding: 8px; border-bottom: 1px solid #ddd; text-align: center; }}
- tr:nth-child(even) {{ background: #f8f9fa; }}
- .score-high {{ color: #4CAF50; font-size: 28px; font-weight: bold; }}
- .score-mid {{ color: #FF9800; font-size: 28px; font-weight: bold; }}
- .score-low {{ color: #f44336; font-size: 28px; font-weight: bold; }}
- .final-rec {{ font-size: 18px; font-weight: bold; text-align: center; padding: 15px; border-radius: 8px; margin: 20px 0; }}
- </style>
- </head>
- <body>
- <h1>📊 Trend-Max 三大策略综合分析</h1>
-
- <div class="summary">
- <h2 style="color: white; border-left: none;">🎯 综合建议</h2>
- <p style="font-size: 16px;"><strong>日期:</strong> {today} | <strong>标的:</strong> 创业板50 (399673)</p>
- <div class="final-rec {'trade-yes' if '✅' in final_recommendation else 'trade-no' if '❌' in final_recommendation else 'trade-warn'}">
- {final_recommendation}
- </div>
- <p><strong>置信度:</strong> {final_confidence*100:.0f}% | <strong>三策略一致率:</strong> {max(trend_count, ranging_count, reversal_count)}/3</p>
- </div>
-
- <h2>📈 各策略详细分析</h2>
-
- <!-- Trend-Mix -->
- <div class="strategy">
- <h3>1️⃣ Trend-Mix (6种方法综合)</h3>
- <p><strong>状态:</strong> {mix_result['state'] if mix_result else 'N/A'}</p>
- <table>
- <tr><th>指标</th><th>数值</th><th>状态</th></tr>
- <tr><td>ADX(14)</td><td>{mix_result['adx']:.2f}</td><td>{mix_result['adx_state']}</td></tr>
- <tr><td>价格偏离MA20</td><td>{mix_result['deviation']:.2f}%</td><td>-</td></tr>
- <tr><td>方差比检验</td><td>-</td><td>{mix_result['vr_state']}</td></tr>
- <tr><td>Hurst指数</td><td>-</td><td>{mix_result['hurst_state']}</td></tr>
- <tr><td>布林带</td><td>-</td><td>{mix_result['bb_state']}</td></tr>
- </table>
- <p><strong>建议:</strong> {trade_advice.get(mix_result['state'], '观望') if mix_result else 'N/A'}</p>
- </div>
-
- <!-- TQE -->
- <div class="strategy">
- <h3>2️⃣ TQE 趋势质量评估 (0-100分)</h3>
- <p><strong>总分:</strong> <span class="{'score-high' if tqe_result['total_score'] >= 80 else 'score-mid' if tqe_result['total_score'] >= 60 else 'score-low'}">{tqe_result['total_score']:.1f}分</span></p>
- <p><strong>是否可交易:</strong> {'✅ 是 (≥60分)' if tqe_result['is_tradeable'] else '❌ 否 (<60分)'}</p>
-
- <table>
- <tr><th>因子</th><th>得分</th><th>原始值</th></tr>
- <tr><td>ADX趋势强度 (30分)</td><td>{tqe_result['adx_score']:.1f}</td><td>ADX={tqe_result['adx_value']:.1f}</td></tr>
- <tr><td>均线斜率 (25分)</td><td>{tqe_result['ma_slope_score']:.1f}</td><td>斜率={tqe_result['ma_slope']:.4f}</td></tr>
- <tr><td>波动率收缩 (20分)</td><td>{tqe_result['volatility_score']:.1f}</td><td>ATR比={tqe_result['volatility_ratio']:.3f}</td></tr>
- <tr><td>时间框架共振 (15分)</td><td>{tqe_result['timeframe_score']:.1f}</td><td>-</td></tr>
- <tr><td>成交量确认 (10分)</td><td>{tqe_result['volume_score']:.1f}</td><td>量比={tqe_result['volume_ratio']:.2f}x</td></tr>
- </table>
- <p><strong>建议:</strong> {'交易' if tqe_result['is_tradeable'] else '观望'}</p>
- </div>
-
- <!-- Regime -->
- <div class="strategy">
- <h3>3️⃣ Regime 市场状态分类</h3>
- <p><strong>预测状态:</strong> {regime_result['state']} (置信度 {regime_result['confidence']:.1%})</p>
-
- <table>
- <tr><th>状态</th><th>概率</th><th>可视化</th></tr>
- <tr><td>震荡</td><td>{regime_result['prob_ranging']:.1%}</td><td>{'█' * int(regime_result['prob_ranging'] * 20)}</td></tr>
- <tr><td>趋势</td><td>{regime_result['prob_trend']:.1%}</td><td>{'█' * int(regime_result['prob_trend'] * 20)}</td></tr>
- <tr><td>反转</td><td>{regime_result['prob_reversal']:.1%}</td><td>{'█' * int(regime_result['prob_reversal'] * 20)}</td></tr>
- </table>
- <p><strong>建议:</strong> {regime_result['state']}期 {'持仓' if regime_result['state'] == '趋势' else '观望' if regime_result['state'] == '震荡' else '减仓'}</p>
- </div>
-
- <h2>📊 策略一致性分析</h2>
-
- <table>
- <tr><th>策略</th><th>判断</th><th>建议</th></tr>
- <tr><td>Trend-Mix</td><td>{mix_result['state']}</td><td>{trade_advice.get(mix_result['state'], '观望')}</td></tr>
- <tr><td>TQE</td><td>{tqe_result['total_score']:.0f}分 {'✅' if tqe_result['is_tradeable'] else '❌'}</td><td>{'交易' if tqe_result['is_tradeable'] else '观望'}</td></tr>
- <tr><td>Regime</td><td>{regime_result['state']}</td><td>{regime_result['state']}期操作</td></tr>
- </table>
-
- <h2>💡 关键洞察</h2>
-
- <ul>
- <li><strong>ADX指标:</strong> 三策略均显示高ADX值({tqe_result['adx_value']:.1f}),表明趋势强度较高</li>
- <li><strong>MA方向:</strong> 但MA斜率({tqe_result['ma_slope']:.4f})向下,价格低于MA20,短期偏弱</li>
- <li><strong>成交量:</strong> 量比仅{tqe_result['volume_ratio']:.2f}x,缩量整理,缺乏资金流入</li>
- <li><strong>结论:</strong> 高ADX但价格下跌,处于下跌趋势延续中,建议观望等待明确信号</li>
- </ul>
-
- <hr>
- <p style="color: #666; font-size: 11px;">
- 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}<br>
- 分析模块: Trend-Mix + TQE + Regime<br>
- 发件人: trend-max | 推送时间: 工作日 15:30
- </p>
- </body>
- </html>
- """
- # 发送邮件
- EMAIL_CONFIG = {
- "smtp_server": "localhost",
- "smtp_port": 25,
- "sender_email": "trend-max@erwin.wang",
- "receiver_email": "380880504@qq.com"
- }
- msg = MIMEMultipart('related')
- msg['Subject'] = Header(f"📊 Trend-Max综合分析 [{today}] {final_recommendation}", '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"\n✅ 邮件发送成功! 发件人: trend-max")
- except Exception as e:
- print(f"\n❌ 邮件发送失败: {e}")
- print("="*70)
|