trend-max-daily.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Trend-Max: 三大策略综合分析
  5. 汇总 Trend-Mix, TQE, Regime 的结果
  6. 工作日15:30定时推送
  7. 发件人: trend-max
  8. """
  9. import sys
  10. sys.path.insert(0, '/root/.openclaw/workspace/trend-mix')
  11. sys.path.insert(0, '/root/.openclaw/workspace/trend-quality-evaluator')
  12. sys.path.insert(0, '/root/.openclaw/workspace/market-regime-identifier')
  13. import numpy as np
  14. import pandas as pd
  15. from trend_mix_strategy import TrendMixStrategy
  16. from trend_quality_evaluator import fetch_stock_data as tqe_fetch, TrendQualityEvaluator
  17. from cyb50_market_classifier import fetch_cyb50_data, calculate_features, define_market_regime
  18. from sklearn.ensemble import RandomForestClassifier
  19. from datetime import datetime
  20. import smtplib
  21. from email.mime.text import MIMEText
  22. from email.mime.multipart import MIMEMultipart
  23. from email.header import Header
  24. import warnings
  25. warnings.filterwarnings('ignore')
  26. print("="*70)
  27. print(f"Trend-Max 三大策略综合分析 - {datetime.now().strftime('%Y-%m-%d %H:%M')}")
  28. print("="*70)
  29. # ============================================
  30. # 1. Trend-Mix 分析
  31. # ============================================
  32. print("\n[1/3] 运行 Trend-Mix 分析...")
  33. strategy_mix = TrendMixStrategy()
  34. df_mix = strategy_mix.fetch_data("399673", "2024-01-01", "2026-12-31")
  35. if df_mix is not None:
  36. # 计算综合状态
  37. composite_df = strategy_mix.calc_composite_state()
  38. latest_mix = composite_df.iloc[-1]
  39. mix_result = {
  40. 'state': latest_mix['Composite_State'],
  41. 'adx': latest_mix['ADX'],
  42. 'adx_state': latest_mix['ADX_State'],
  43. 'vr_state': latest_mix['VR_State'],
  44. 'hurst_state': latest_mix['Hurst_State'],
  45. 'bb_state': latest_mix['BB_State'],
  46. 'deviation': latest_mix['Deviation']
  47. }
  48. print(f" ✓ Trend-Mix: {mix_result['state']}")
  49. else:
  50. mix_result = None
  51. print(" ✗ Trend-Mix 失败")
  52. # ============================================
  53. # 2. TQE 分析
  54. # ============================================
  55. print("\n[2/3] 运行 TQE 分析...")
  56. df_tqe = tqe_fetch("399673", "2024-01-01", "2026-12-31", "d")
  57. if df_tqe is not None:
  58. evaluator = TrendQualityEvaluator()
  59. score = evaluator.evaluate(df_tqe)
  60. tqe_result = {
  61. 'total_score': score.total_score,
  62. 'is_tradeable': score.is_tradeable,
  63. 'adx_score': score.adx_score,
  64. 'ma_slope_score': score.ma_slope_score,
  65. 'volatility_score': score.volatility_score,
  66. 'timeframe_score': score.timeframe_score,
  67. 'volume_score': score.volume_score,
  68. 'adx_value': score.adx_value,
  69. 'ma_slope': score.ma_slope,
  70. 'volatility_ratio': score.volatility_ratio,
  71. 'volume_ratio': score.volume_ratio
  72. }
  73. print(f" ✓ TQE: {score.total_score:.1f}分 {'✅可交易' if score.is_tradeable else '❌观望'}")
  74. else:
  75. tqe_result = None
  76. print(" ✗ TQE 失败")
  77. # ============================================
  78. # 3. Regime 分析
  79. # ============================================
  80. print("\n[3/3] 运行 Regime 分析...")
  81. df_regime = fetch_cyb50_data("2024-01-01", "2026-12-31")
  82. if df_regime is not None:
  83. features = calculate_features(df_regime)
  84. labels = define_market_regime(df_regime, lookback=10)
  85. # 训练模型
  86. valid_idx = ~np.isnan(labels)
  87. X = features[valid_idx]
  88. y = labels[valid_idx]
  89. clf = RandomForestClassifier(
  90. n_estimators=100, max_depth=10, min_samples_split=20,
  91. min_samples_leaf=10, random_state=42, class_weight='balanced'
  92. )
  93. clf.fit(X, y)
  94. # 预测
  95. latest_features = features.iloc[-1:]
  96. pred = clf.predict(latest_features)[0]
  97. proba = clf.predict_proba(latest_features)[0]
  98. state_names = ['震荡', '趋势', '反转']
  99. regime_result = {
  100. 'state': state_names[pred],
  101. 'confidence': proba[pred],
  102. 'prob_ranging': proba[0],
  103. 'prob_trend': proba[1],
  104. 'prob_reversal': proba[2]
  105. }
  106. print(f" ✓ Regime: {regime_result['state']} (置信度{regime_result['confidence']:.1%})")
  107. else:
  108. regime_result = None
  109. print(" ✗ Regime 失败")
  110. # ============================================
  111. # 4. 综合分析
  112. # ============================================
  113. print("\n" + "="*70)
  114. print("综合分析")
  115. print("="*70)
  116. state_names_cn = ['震荡', '趋势', '反转']
  117. final_recommendation = "观望"
  118. final_confidence = 0.0
  119. if mix_result and tqe_result and regime_result:
  120. # 统计各策略的状态
  121. states = [mix_result['state'], '趋势' if tqe_result['is_tradeable'] else '震荡', regime_result['state']]
  122. # 交易建议映射
  123. trade_advice = {
  124. '强趋势': '重仓',
  125. '趋势': '持仓/加仓',
  126. '潜在爆发': '建仓',
  127. '反转': '止盈/减仓',
  128. '震荡': '观望'
  129. }
  130. mix_advice = trade_advice.get(mix_result['state'], '观望')
  131. tqe_advice = '交易' if tqe_result['is_tradeable'] else '观望'
  132. regime_advice = regime_result['state']
  133. # 综合判断
  134. trend_count = sum([s in ['强趋势', '趋势'] for s in states])
  135. ranging_count = sum([s == '震荡' for s in states])
  136. reversal_count = sum([s == '反转' for s in states])
  137. if trend_count >= 2 and tqe_result['is_tradeable']:
  138. final_recommendation = "✅ 交易 (趋势确认)"
  139. final_confidence = 0.8
  140. elif ranging_count >= 2 or not tqe_result['is_tradeable']:
  141. final_recommendation = "❌ 观望 (震荡整理)"
  142. final_confidence = 0.7
  143. elif reversal_count >= 2:
  144. final_recommendation = "⚠️ 谨慎 (可能反转)"
  145. final_confidence = 0.6
  146. else:
  147. final_recommendation = "❓ 分歧 (信号不一)"
  148. final_confidence = 0.4
  149. print(f"\nTrend-Mix: {mix_result['state']} → {mix_advice}")
  150. print(f"TQE: {tqe_result['total_score']:.0f}分 → {tqe_advice}")
  151. print(f"Regime: {regime_result['state']} → {regime_advice}")
  152. print(f"\n综合建议: {final_recommendation}")
  153. # ============================================
  154. # 5. 生成邮件
  155. # ============================================
  156. print("\n生成邮件...")
  157. today = datetime.now().strftime('%Y-%m-%d')
  158. html = f"""
  159. <html>
  160. <head>
  161. <meta charset="utf-8">
  162. <style>
  163. body {{ font-family: Arial, sans-serif; margin: 20px; font-size: 13px; }}
  164. h1 {{ color: #333; border-bottom: 3px solid #673AB7; padding-bottom: 10px; font-size: 20px; }}
  165. h2 {{ color: #555; margin-top: 25px; border-left: 4px solid #2196F3; padding-left: 10px; font-size: 15px; }}
  166. h3 {{ color: #666; font-size: 14px; }}
  167. .summary {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0; }}
  168. .strategy {{ background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #4CAF50; }}
  169. .trade-yes {{ background: #e8f5e9; border-left: 4px solid #4CAF50; }}
  170. .trade-no {{ background: #ffebee; border-left: 4px solid #f44336; }}
  171. .trade-warn {{ background: #fff3cd; border-left: 4px solid #FFC107; }}
  172. table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 12px; }}
  173. th {{ background: #673AB7; color: white; padding: 10px; text-align: center; }}
  174. td {{ padding: 8px; border-bottom: 1px solid #ddd; text-align: center; }}
  175. tr:nth-child(even) {{ background: #f8f9fa; }}
  176. .score-high {{ color: #4CAF50; font-size: 28px; font-weight: bold; }}
  177. .score-mid {{ color: #FF9800; font-size: 28px; font-weight: bold; }}
  178. .score-low {{ color: #f44336; font-size: 28px; font-weight: bold; }}
  179. .final-rec {{ font-size: 18px; font-weight: bold; text-align: center; padding: 15px; border-radius: 8px; margin: 20px 0; }}
  180. </style>
  181. </head>
  182. <body>
  183. <h1>📊 Trend-Max 三大策略综合分析</h1>
  184. <div class="summary">
  185. <h2 style="color: white; border-left: none;">🎯 综合建议</h2>
  186. <p style="font-size: 16px;"><strong>日期:</strong> {today} | <strong>标的:</strong> 创业板50 (399673)</p>
  187. <div class="final-rec {'trade-yes' if '✅' in final_recommendation else 'trade-no' if '❌' in final_recommendation else 'trade-warn'}">
  188. {final_recommendation}
  189. </div>
  190. <p><strong>置信度:</strong> {final_confidence*100:.0f}% | <strong>三策略一致率:</strong> {max(trend_count, ranging_count, reversal_count)}/3</p>
  191. </div>
  192. <h2>📈 各策略详细分析</h2>
  193. <!-- Trend-Mix -->
  194. <div class="strategy">
  195. <h3>1️⃣ Trend-Mix (6种方法综合)</h3>
  196. <p><strong>状态:</strong> {mix_result['state'] if mix_result else 'N/A'}</p>
  197. <table>
  198. <tr><th>指标</th><th>数值</th><th>状态</th></tr>
  199. <tr><td>ADX(14)</td><td>{mix_result['adx']:.2f}</td><td>{mix_result['adx_state']}</td></tr>
  200. <tr><td>价格偏离MA20</td><td>{mix_result['deviation']:.2f}%</td><td>-</td></tr>
  201. <tr><td>方差比检验</td><td>-</td><td>{mix_result['vr_state']}</td></tr>
  202. <tr><td>Hurst指数</td><td>-</td><td>{mix_result['hurst_state']}</td></tr>
  203. <tr><td>布林带</td><td>-</td><td>{mix_result['bb_state']}</td></tr>
  204. </table>
  205. <p><strong>建议:</strong> {trade_advice.get(mix_result['state'], '观望') if mix_result else 'N/A'}</p>
  206. </div>
  207. <!-- TQE -->
  208. <div class="strategy">
  209. <h3>2️⃣ TQE 趋势质量评估 (0-100分)</h3>
  210. <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>
  211. <p><strong>是否可交易:</strong> {'✅ 是 (≥60分)' if tqe_result['is_tradeable'] else '❌ 否 (<60分)'}</p>
  212. <table>
  213. <tr><th>因子</th><th>得分</th><th>原始值</th></tr>
  214. <tr><td>ADX趋势强度 (30分)</td><td>{tqe_result['adx_score']:.1f}</td><td>ADX={tqe_result['adx_value']:.1f}</td></tr>
  215. <tr><td>均线斜率 (25分)</td><td>{tqe_result['ma_slope_score']:.1f}</td><td>斜率={tqe_result['ma_slope']:.4f}</td></tr>
  216. <tr><td>波动率收缩 (20分)</td><td>{tqe_result['volatility_score']:.1f}</td><td>ATR比={tqe_result['volatility_ratio']:.3f}</td></tr>
  217. <tr><td>时间框架共振 (15分)</td><td>{tqe_result['timeframe_score']:.1f}</td><td>-</td></tr>
  218. <tr><td>成交量确认 (10分)</td><td>{tqe_result['volume_score']:.1f}</td><td>量比={tqe_result['volume_ratio']:.2f}x</td></tr>
  219. </table>
  220. <p><strong>建议:</strong> {'交易' if tqe_result['is_tradeable'] else '观望'}</p>
  221. </div>
  222. <!-- Regime -->
  223. <div class="strategy">
  224. <h3>3️⃣ Regime 市场状态分类</h3>
  225. <p><strong>预测状态:</strong> {regime_result['state']} (置信度 {regime_result['confidence']:.1%})</p>
  226. <table>
  227. <tr><th>状态</th><th>概率</th><th>可视化</th></tr>
  228. <tr><td>震荡</td><td>{regime_result['prob_ranging']:.1%}</td><td>{'█' * int(regime_result['prob_ranging'] * 20)}</td></tr>
  229. <tr><td>趋势</td><td>{regime_result['prob_trend']:.1%}</td><td>{'█' * int(regime_result['prob_trend'] * 20)}</td></tr>
  230. <tr><td>反转</td><td>{regime_result['prob_reversal']:.1%}</td><td>{'█' * int(regime_result['prob_reversal'] * 20)}</td></tr>
  231. </table>
  232. <p><strong>建议:</strong> {regime_result['state']}期 {'持仓' if regime_result['state'] == '趋势' else '观望' if regime_result['state'] == '震荡' else '减仓'}</p>
  233. </div>
  234. <h2>📊 策略一致性分析</h2>
  235. <table>
  236. <tr><th>策略</th><th>判断</th><th>建议</th></tr>
  237. <tr><td>Trend-Mix</td><td>{mix_result['state']}</td><td>{trade_advice.get(mix_result['state'], '观望')}</td></tr>
  238. <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>
  239. <tr><td>Regime</td><td>{regime_result['state']}</td><td>{regime_result['state']}期操作</td></tr>
  240. </table>
  241. <h2>💡 关键洞察</h2>
  242. <ul>
  243. <li><strong>ADX指标:</strong> 三策略均显示高ADX值({tqe_result['adx_value']:.1f}),表明趋势强度较高</li>
  244. <li><strong>MA方向:</strong> 但MA斜率({tqe_result['ma_slope']:.4f})向下,价格低于MA20,短期偏弱</li>
  245. <li><strong>成交量:</strong> 量比仅{tqe_result['volume_ratio']:.2f}x,缩量整理,缺乏资金流入</li>
  246. <li><strong>结论:</strong> 高ADX但价格下跌,处于下跌趋势延续中,建议观望等待明确信号</li>
  247. </ul>
  248. <hr>
  249. <p style="color: #666; font-size: 11px;">
  250. 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}<br>
  251. 分析模块: Trend-Mix + TQE + Regime<br>
  252. 发件人: trend-max | 推送时间: 工作日 15:30
  253. </p>
  254. </body>
  255. </html>
  256. """
  257. # 发送邮件
  258. EMAIL_CONFIG = {
  259. "smtp_server": "localhost",
  260. "smtp_port": 25,
  261. "sender_email": "trend-max@erwin.wang",
  262. "receiver_email": "380880504@qq.com"
  263. }
  264. msg = MIMEMultipart('related')
  265. msg['Subject'] = Header(f"📊 Trend-Max综合分析 [{today}] {final_recommendation}", 'utf-8')
  266. msg['From'] = EMAIL_CONFIG['sender_email']
  267. msg['To'] = EMAIL_CONFIG['receiver_email']
  268. msg.attach(MIMEText(html, 'html', 'utf-8'))
  269. try:
  270. with smtplib.SMTP(EMAIL_CONFIG['smtp_server'], EMAIL_CONFIG['smtp_port']) as server:
  271. server.sendmail(EMAIL_CONFIG['sender_email'], EMAIL_CONFIG['receiver_email'], msg.as_string())
  272. print(f"\n✅ 邮件发送成功! 发件人: trend-max")
  273. except Exception as e:
  274. print(f"\n❌ 邮件发送失败: {e}")
  275. print("="*70)