| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 趋势质量评估器 - 历史回测 (2017-至今)
- 生成完整回测报告
- """
- import sys
- sys.path.insert(0, '/root/.openclaw/workspace/trend-quality-evaluator')
- import numpy as np
- import pandas as pd
- from trend_quality_evaluator import fetch_stock_data, TrendQualityEvaluator
- from datetime import datetime
- import warnings
- warnings.filterwarnings('ignore')
- print("="*70)
- print("趋势质量评估器 - 历史回测报告")
- print("回测区间: 2017-01-01 至 2026-03-06")
- print("="*70)
- # 获取数据
- df = fetch_stock_data("399673", "2017-01-01", "2026-03-06", "d")
- if df is None:
- print("数据获取失败")
- exit(1)
- print(f"\n✓ 数据获取成功: {len(df)}条")
- print(f" 日期范围: {df.index[0].date()} ~ {df.index[-1].date()}")
- # 初始化评估器
- evaluator = TrendQualityEvaluator()
- # 逐日评估
- print("\n正在进行历史回测...")
- results = []
- # 需要至少60天数据计算MA60
- start_idx = 60
- for i in range(start_idx, len(df)):
- current_df = df.iloc[:i+1]
-
- try:
- score = evaluator.evaluate(current_df)
- results.append({
- 'date': df.index[i],
- 'close': df['close'].iloc[i],
- 'total_score': score.total_score,
- '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,
- 'is_tradeable': score.is_tradeable,
- 'adx_value': score.adx_value,
- 'ma_slope': score.ma_slope,
- 'volatility_ratio': score.volatility_ratio,
- 'volume_ratio': score.volume_ratio
- })
- except Exception as e:
- pass
- if i % 100 == 0:
- print(f" 进度: {i}/{len(df)} ({i/len(df)*100:.1f}%)")
- results_df = pd.DataFrame(results)
- results_df = results_df.set_index('date')
- print(f"\n✓ 回测完成: {len(results_df)}个交易日")
- # 计算未来收益率(用于验证评分有效性)
- print("\n计算未来收益验证...")
- results_df['future_5d_return'] = results_df['close'].pct_change(5).shift(-5) * 100
- results_df['future_10d_return'] = results_df['close'].pct_change(10).shift(-10) * 100
- results_df['future_20d_return'] = results_df['close'].pct_change(20).shift(-20) * 100
- # ============================================
- # 生成报告
- # ============================================
- print("\n" + "="*70)
- print("回测报告")
- print("="*70)
- # 1. 评分分布统计
- print("\n【一、评分分布统计】")
- print("-"*50)
- total_days = len(results_df)
- tradeable_days = results_df['is_tradeable'].sum()
- print(f"总交易天数: {total_days}")
- print(f"可交易天数(≥60分): {tradeable_days} ({tradeable_days/total_days*100:.1f}%)")
- print(f"不可交易天数(<60分): {total_days-tradeable_days} ({(total_days-tradeable_days)/total_days*100:.1f}%)")
- # 分数段分布
- print(f"\n分数段分布:")
- bins = [0, 40, 60, 70, 80, 100]
- labels = ['0-40(混乱)', '40-60(较差)', '60-70(及格)', '70-80(良好)', '80-100(优秀)']
- results_df['score_bin'] = pd.cut(results_df['total_score'], bins=bins, labels=labels, include_lowest=True)
- score_dist = results_df['score_bin'].value_counts().sort_index()
- for label, count in score_dist.items():
- pct = count / total_days * 100
- bar = '█' * int(pct / 2)
- print(f" {label:15s}: {count:4d}天 ({pct:5.1f}%) {bar}")
- # 2. 各因子得分统计
- print("\n【二、各因子得分统计】")
- print("-"*50)
- factors = [
- ('adx_score', 'ADX趋势强度', 30),
- ('ma_slope_score', '均线斜率', 25),
- ('volatility_score', '波动率收缩', 20),
- ('timeframe_score', '时间框架共振', 15),
- ('volume_score', '成交量确认', 10)
- ]
- for col, name, max_score in factors:
- mean_score = results_df[col].mean()
- pct_of_max = mean_score / max_score * 100
- print(f"{name:15s}: 平均{mean_score:5.1f}/{max_score}分 ({pct_of_max:4.1f}%)")
- # 3. 评分有效性验证
- print("\n【三、评分有效性验证】")
- print("-"*50)
- print("不同评分区间的未来收益表现:")
- print(f"{'评分区间':<15} {'5日后收益':<12} {'10日后收益':<12} {'20日后收益':<12} {'样本数':<8}")
- print("-"*60)
- for label in labels:
- mask = results_df['score_bin'] == label
- if mask.sum() > 0:
- r5 = results_df[mask]['future_5d_return'].mean()
- r10 = results_df[mask]['future_10d_return'].mean()
- r20 = results_df[mask]['future_20d_return'].mean()
- count = mask.sum()
- print(f"{label:<15} {r5:>+10.2f}% {r10:>+10.2f}% {r20:>+10.2f}% {count:>6d}")
- # 4. 可交易 vs 不可交易对比
- print("\n【四、可交易性验证】")
- print("-"*50)
- tradeable_mask = results_df['is_tradeable']
- not_tradeable_mask = ~results_df['is_tradeable']
- print(f"{'指标':<20} {'可交易(≥60分)':<15} {'不可交易(<60分)':<15} {'差异':<10}")
- print("-"*60)
- for period, col in [('5日', 'future_5d_return'), ('10日', 'future_10d_return'), ('20日', 'future_20d_return')]:
- t_mean = results_df[tradeable_mask][col].mean()
- nt_mean = results_df[not_tradeable_mask][col].mean()
- diff = t_mean - nt_mean
- print(f"{period+'收益':<20} {t_mean:>+13.2f}% {nt_mean:>+14.2f}% {diff:>+9.2f}%")
- # 5. 年度最佳/最差月份
- print("\n【五、年度统计】")
- print("-"*50)
- results_df['year'] = results_df.index.year
- yearly_stats = results_df.groupby('year').agg({
- 'total_score': 'mean',
- 'is_tradeable': 'sum',
- 'future_20d_return': 'mean'
- }).round(2)
- yearly_stats['tradeable_pct'] = (yearly_stats['is_tradeable'] / results_df.groupby('year').size() * 100).round(1)
- print(f"{'年份':<8} {'平均评分':<10} {'可交易天数':<12} {'可交易比例':<12} {'20日收益':<10}")
- print("-"*60)
- for year, row in yearly_stats.iterrows():
- print(f"{year:<8} {row['total_score']:>8.1f} {int(row['is_tradeable']):>8}天 {row['tradeable_pct']:>8.1f}% {row['future_20d_return']:>+7.2f}%")
- # 6. 最新评分
- print("\n【六、最新评分】")
- print("-"*50)
- latest = results_df.iloc[-1]
- print(f"日期: {results_df.index[-1].strftime('%Y-%m-%d')}")
- print(f"收盘价: {latest['close']:.2f}")
- print(f"总分: {latest['total_score']:.1f}分")
- print(f"是否可交易: {'✅ 是' if latest['is_tradeable'] else '❌ 否'}")
- print(f"\n各因子得分:")
- for col, name, max_score in factors:
- print(f" {name:15s}: {latest[col]:.1f}/{max_score}分")
- # 7. 关键发现
- print("\n【七、关键发现】")
- print("-"*50)
- # 计算胜率
- tradeable_returns = results_df[tradeable_mask]['future_20d_return'].dropna()
- win_rate = (tradeable_returns > 0).mean() * 100
- print(f"1. 可交易信号胜率(20日): {win_rate:.1f}%")
- # 最佳评分日期
- best_day = results_df.loc[results_df['total_score'].idxmax()]
- print(f"2. 历史最高评分: {best_day['total_score']:.1f}分 ({results_df['total_score'].idxmax().strftime('%Y-%m-%d')})")
- # 评分与收益相关性
- corr_20d = results_df['total_score'].corr(results_df['future_20d_return'])
- print(f"3. 评分与20日收益相关性: {corr_20d:.3f}")
- # 连续可交易日统计
- results_df['tradeable_change'] = results_df['is_tradeable'].astype(int).diff()
- entry_points = results_df[results_df['tradeable_change'] == 1].index
- print(f"4. 历史可交易入场信号次数: {len(entry_points)}次")
- print("\n" + "="*70)
- print("回测报告生成完成")
- print("="*70)
- # 保存结果
- results_df.to_csv('/root/.openclaw/workspace/trend-quality-evaluator/backtest_results.csv')
- print("\n✓ 详细数据已保存: backtest_results.csv")
|