#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 趋势质量评估器 - 参数优化测试 测试不同参数组合的回测表现,寻找最优配置 """ 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 from dataclasses import dataclass from typing import List, Tuple import itertools import warnings warnings.filterwarnings('ignore') print("="*70) print("趋势质量评估器 - 参数优化测试") 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()}") # 定义参数搜索空间 param_grid = { 'adx_threshold': [20, 25, 30], # ADX阈值 'adx_weight': [25, 30, 35], # ADX权重 'ma_slope_threshold': [1.001, 1.002, 1.003], # 均线斜率阈值 'ma_slope_weight': [20, 25, 30], # 均线斜率权重 'volatility_threshold': [0.7, 0.8, 0.9], # 波动率比率阈值 'volatility_weight': [15, 20, 25], # 波动率权重 'volume_threshold': [1.3, 1.5, 1.8], # 成交量阈值 'volume_weight': [8, 10, 12], # 成交量权重 } # 总权重必须是100 @dataclass class StrategyConfig: """策略配置""" adx_threshold: float adx_weight: int ma_slope_threshold: float ma_slope_weight: int volatility_threshold: float volatility_weight: int volume_threshold: float volume_weight: int timeframe_weight: int = 15 # 固定15分 @property def total_weight(self) -> int: return self.adx_weight + self.ma_slope_weight + self.volatility_weight + self.volume_weight + self.timeframe_weight @property def trade_threshold(self) -> int: return 60 # 固定60分阈值 def calculate_indicators(df: pd.DataFrame) -> pd.DataFrame: """预计算所有技术指标""" data = df.copy() # ADX high, low, close = data['high'], data['low'], data['close'] plus_dm = high.diff() minus_dm = low.diff().abs() plus_dm = plus_dm.where((plus_dm > minus_dm) & (plus_dm > 0), 0) minus_dm = minus_dm.where((minus_dm > plus_dm) & (minus_dm > 0), 0) tr1 = high - low tr2 = (high - close.shift()).abs() tr3 = (low - close.shift()).abs() tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) atr = tr.rolling(14).mean() plus_di = 100 * (plus_dm.rolling(14).mean() / atr) minus_di = 100 * (minus_dm.rolling(14).mean() / atr) dx = (abs(plus_di - minus_di) / (plus_di + minus_di + 1e-10)) * 100 data['adx'] = dx.rolling(14).mean() # MA20斜率 data['ma20'] = data['close'].rolling(20).mean() data['ma20_slope'] = data['ma20'] / data['ma20'].shift(5) # ATR比率 data['atr14'] = tr.rolling(14).mean() data['atr50'] = tr.rolling(50).mean() data['atr_ratio'] = data['atr14'] / data['atr50'] # 成交量 data['volume_ma20'] = data['volume'].rolling(20).mean() data['volume_ratio'] = data['volume'] / data['volume_ma20'] # 日线突破 data['price_above_ma20'] = data['close'] > data['ma20'] return data def evaluate_strategy(data: pd.DataFrame, config: StrategyConfig) -> dict: """ 评估策略配置 返回各项绩效指标 """ scores = [] # 从第60天开始(需要足够数据计算MA60) for i in range(60, len(data)): row = data.iloc[i] # 计算各因子得分 # 1. ADX得分 if row['adx'] >= config.adx_threshold: adx_score = config.adx_weight else: adx_score = config.adx_weight * (row['adx'] / config.adx_threshold) # 2. 均线斜率得分 if row['ma20_slope'] >= config.ma_slope_threshold * 1.5: ma_score = config.ma_slope_weight elif row['ma20_slope'] >= config.ma_slope_threshold: ma_score = config.ma_slope_weight * 0.7 elif row['ma20_slope'] >= 1.0: ma_score = config.ma_slope_weight * 0.3 else: ma_score = 0 # 3. 波动率得分 if row['atr_ratio'] <= config.volatility_threshold * 0.75: vol_score = config.volatility_weight elif row['atr_ratio'] <= config.volatility_threshold: vol_score = config.volatility_weight * 0.7 elif row['atr_ratio'] <= 1.0: vol_score = config.volatility_weight * 0.3 else: vol_score = 0 # 4. 成交量得分 if row['volume_ratio'] >= config.volume_threshold * 1.3: vol_score_trade = config.volume_weight elif row['volume_ratio'] >= config.volume_threshold: vol_score_trade = config.volume_weight * 0.7 elif row['volume_ratio'] >= 1.0: vol_score_trade = config.volume_weight * 0.3 else: vol_score_trade = 0 # 5. 时间框架得分(简化版:价格突破MA20) tf_score = config.timeframe_weight if row['price_above_ma20'] else 0 total_score = adx_score + ma_score + vol_score + vol_score_trade + tf_score is_tradeable = total_score >= config.trade_threshold scores.append({ 'date': data.index[i], 'close': row['close'], 'total_score': total_score, 'is_tradeable': is_tradeable, 'adx': row['adx'], 'ma20_slope': row['ma20_slope'], 'atr_ratio': row['atr_ratio'], 'volume_ratio': row['volume_ratio'] }) scores_df = pd.DataFrame(scores) scores_df = scores_df.set_index('date') # 计算未来收益 scores_df['future_5d_return'] = scores_df['close'].pct_change(5).shift(-5) * 100 scores_df['future_10d_return'] = scores_df['close'].pct_change(10).shift(-10) * 100 scores_df['future_20d_return'] = scores_df['close'].pct_change(20).shift(-20) * 100 # 计算绩效指标 t_mask = scores_df['is_tradeable'] total_days = len(scores_df) tradeable_days = t_mask.sum() if tradeable_days == 0: return None returns_5d = scores_df[t_mask]['future_5d_return'].dropna() returns_10d = scores_df[t_mask]['future_10d_return'].dropna() returns_20d = scores_df[t_mask]['future_20d_return'].dropna() results = { 'config': config, 'total_days': total_days, 'tradeable_days': tradeable_days, 'tradeable_pct': tradeable_days / total_days * 100, 'avg_score': scores_df['total_score'].mean(), 'return_5d': returns_5d.mean() if len(returns_5d) > 0 else 0, 'return_10d': returns_10d.mean() if len(returns_10d) > 0 else 0, 'return_20d': returns_20d.mean() if len(returns_20d) > 0 else 0, 'win_rate_20d': (returns_20d > 0).mean() * 100 if len(returns_20d) > 0 else 0, 'sharpe_20d': returns_20d.mean() / returns_20d.std() if len(returns_20d) > 0 and returns_20d.std() > 0 else 0, 'excess_return_20d': returns_20d.mean() - scores_df[~t_mask]['future_20d_return'].mean() if len(returns_20d) > 0 else 0, 'scores_df': scores_df } return results # 预计算指标 print("\n预计算技术指标...") data = calculate_indicators(df) print("✓ 指标计算完成") # 生成参数组合(只测试权重和为100的组合) print("\n生成参数组合...") valid_configs = [] for adx_t, adx_w, ma_t, ma_w, vol_t, vol_w, volu_t, volu_w in itertools.product( param_grid['adx_threshold'], param_grid['adx_weight'], param_grid['ma_slope_threshold'], param_grid['ma_slope_weight'], param_grid['volatility_threshold'], param_grid['volatility_weight'], param_grid['volume_threshold'], param_grid['volume_weight'] ): config = StrategyConfig( adx_threshold=adx_t, adx_weight=adx_w, ma_slope_threshold=ma_t, ma_slope_weight=ma_w, volatility_threshold=vol_t, volatility_weight=vol_w, volume_threshold=volu_t, volume_weight=volu_w ) if config.total_weight == 100: # 只保留权重和为100的组合 valid_configs.append(config) print(f"✓ 有效参数组合: {len(valid_configs)}个") # 运行回测 print(f"\n开始回测 ({len(valid_configs)}个组合)...") print("-"*70) all_results = [] for i, config in enumerate(valid_configs): result = evaluate_strategy(data, config) if result: all_results.append(result) if (i + 1) % 50 == 0: print(f" 进度: {i+1}/{len(valid_configs)} ({(i+1)/len(valid_configs)*100:.1f}%)") print(f"\n✓ 回测完成: {len(all_results)}个有效结果") # 排序并选择最佳配置 print("\n" + "="*70) print("参数优化结果") print("="*70) # 按不同目标排序 sort_by = [ ('20日收益', lambda r: r['return_20d'], True), ('超额收益', lambda r: r['excess_return_20d'], True), ('胜率', lambda r: r['win_rate_20d'], True), ('夏普比率', lambda r: r['sharpe_20d'], True), ] best_configs = {} for name, key_func, reverse in sort_by: sorted_results = sorted(all_results, key=key_func, reverse=reverse) best_configs[name] = sorted_results[0] print(f"\n【最佳配置 - {name}】") best = sorted_results[0] print(f" ADX阈值: {best['config'].adx_threshold}, 权重: {best['config'].adx_weight}") print(f" MA斜率阈值: {best['config'].ma_slope_threshold}, 权重: {best['config'].ma_slope_weight}") print(f" 波动率阈值: {best['config'].volatility_threshold}, 权重: {best['config'].volatility_weight}") print(f" 成交量阈值: {best['config'].volume_threshold}, 权重: {best['config'].volume_weight}") print(f" 时间框架权重: {best['config'].timeframe_weight}") print(f"\n 绩效指标:") print(f" 可交易比例: {best['tradeable_pct']:.1f}%") print(f" 5日收益: {best['return_5d']:+.2f}%") print(f" 10日收益: {best['return_10d']:+.2f}%") print(f" 20日收益: {best['return_20d']:+.2f}%") print(f" 超额收益: {best['excess_return_20d']:+.2f}%") print(f" 胜率(20日): {best['win_rate_20d']:.1f}%") print(f" 夏普比率: {best['sharpe_20d']:.2f}") # 综合评分最佳(平衡收益、胜率、夏普) print(f"\n【综合最佳配置】") print("-"*50) # 计算综合得分(标准化后加权) max_return = max(r['return_20d'] for r in all_results) max_winrate = max(r['win_rate_20d'] for r in all_results) max_sharpe = max(r['sharpe_20d'] for r in all_results) max_excess = max(r['excess_return_20d'] for r in all_results) for r in all_results: # 综合得分 = 收益*0.3 + 超额*0.3 + 胜率*0.2 + 夏普*0.2 r['composite_score'] = ( (r['return_20d'] / max_return if max_return > 0 else 0) * 0.3 + (r['excess_return_20d'] / max_excess if max_excess > 0 else 0) * 0.3 + (r['win_rate_20d'] / max_winrate if max_winrate > 0 else 0) * 0.2 + (r['sharpe_20d'] / max_sharpe if max_sharpe > 0 else 0) * 0.2 ) * 100 best_composite = max(all_results, key=lambda r: r['composite_score']) print(f" ADX阈值: {best_composite['config'].adx_threshold}, 权重: {best_composite['config'].adx_weight}") print(f" MA斜率阈值: {best_composite['config'].ma_slope_threshold}, 权重: {best_composite['config'].ma_slope_weight}") print(f" 波动率阈值: {best_composite['config'].volatility_threshold}, 权重: {best_composite['config'].volatility_weight}") print(f" 成交量阈值: {best_composite['config'].volume_threshold}, 权重: {best_composite['config'].volume_weight}") print(f"\n 绩效指标:") print(f" 可交易比例: {best_composite['tradeable_pct']:.1f}%") print(f" 5日收益: {best_composite['return_5d']:+.2f}%") print(f" 10日收益: {best_composite['return_10d']:+.2f}%") print(f" 20日收益: {best_composite['return_20d']:+.2f}%") print(f" 超额收益: {best_composite['excess_return_20d']:+.2f}%") print(f" 胜率(20日): {best_composite['win_rate_20d']:.1f}%") print(f" 夏普比率: {best_composite['sharpe_20d']:.2f}") print(f" 综合得分: {best_composite['composite_score']:.1f}") # 保存结果 print("\n" + "="*70) print("保存结果...") results_summary = [] for r in all_results: results_summary.append({ 'adx_threshold': r['config'].adx_threshold, 'adx_weight': r['config'].adx_weight, 'ma_slope_threshold': r['config'].ma_slope_threshold, 'ma_slope_weight': r['config'].ma_slope_weight, 'volatility_threshold': r['config'].volatility_threshold, 'volatility_weight': r['config'].volatility_weight, 'volume_threshold': r['config'].volume_threshold, 'volume_weight': r['config'].volume_weight, 'tradeable_pct': r['tradeable_pct'], 'return_5d': r['return_5d'], 'return_10d': r['return_10d'], 'return_20d': r['return_20d'], 'excess_return_20d': r['excess_return_20d'], 'win_rate_20d': r['win_rate_20d'], 'sharpe_20d': r['sharpe_20d'], 'composite_score': r['composite_score'] }) results_df = pd.DataFrame(results_summary) results_df = results_df.sort_values('composite_score', ascending=False) results_df.to_csv('/root/.openclaw/workspace/trend-quality-evaluator/optimization_results.csv', index=False) # 保存最佳配置详情 best_composite['scores_df'].to_csv('/root/.openclaw/workspace/trend-quality-evaluator/best_config_backtest.csv') print("✓ 优化结果已保存: optimization_results.csv") print("✓ 最佳配置回测详情: best_config_backtest.csv") print("\n" + "="*70) print("参数优化测试完成!") print("="*70)