|
|
@@ -0,0 +1,317 @@
|
|
|
+#!/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@openclaw.local",
|
|
|
+ "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)
|