parameter_optimizer.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
参数迭代优化模块
自动遍历核心参数组合,每一组参数均做全样本历史回测,筛选最优参数
"""

import os
import logging
import pandas as pd
import json
from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

# 配置日志
logger = logging.getLogger(__name__)

class ParameterOptimizer:
    """参数迭代优化类"""
    
    def __init__(self):
        """初始化参数优化对象"""
        self.optimization_dir = "results/optimization"
        Path(self.optimization_dir).mkdir(parents=True, exist_ok=True)
        
        # 初始化回测模块
        from src.backtester import Backtester
        self.backtester = Backtester()
        
        # 初始化大模型模块
        from src.model_api import ModelAPI
        self.model_api = ModelAPI()
        
        # 参数池
        self.param_pool = self._build_parameter_pool()
    
    def _build_parameter_pool(self):
        """构建参数池"""
        return {
            "learning_window": ["60日", "90日", "120日"],
            "profit_threshold": ["2%", "3%", "4%", "5%"],
            "stock_count": ["5只", "10只", "15只"],
            "volume_ratio_threshold": ["1.2", "1.5", "2.0"]
        }
    
    def _generate_parameter_combinations(self):
        """生成参数组合"""
        from itertools import product
        
        combinations = []
        for params in product(
            self.param_pool["learning_window"],
            self.param_pool["profit_threshold"],
            self.param_pool["stock_count"],
            self.param_pool["volume_ratio_threshold"]
        ):
            combinations.append({
                "learning_window": params[0],
                "profit_threshold": params[1],
                "stock_count": params[2],
                "volume_ratio_threshold": params[3]
            })
        
        logger.info(f"参数组合总数:{len(combinations)}")
        return combinations
    
    def run(self, param_combinations=None):
        """执行参数优化"""
        try:
            logger.info("开始参数优化...")
            
            # 使用传入的参数组合或生成所有组合
            if not param_combinations:
                param_combinations = self._generate_parameter_combinations()
            
            logger.info(f"待测试参数组合数:{len(param_combinations)}")
            
            # 初始化优化结果
            optimization_results = []
            
            # 对每个参数组合进行回测
            for idx, params in enumerate(param_combinations):
                logger.info(f"测试参数组合 {idx+1}/{len(param_combinations)}:{params}")
                
                try:
                    # 执行回测
                    report = self.backtester.run(params)
                    
                    # 记录优化结果
                    optimization_results.append({
                        "params": params,
                        "win_rate": report['win_rate'],
                        "avg_return": report['avg_return'],
                        "total_return": report['total_return'],
                        "max_drawdown": report['max_drawdown'],
                        "profit_loss_ratio": report['profit_loss_ratio'],
                        "avg_daily_return": report['avg_daily_return']
                    })
                    
                    logger.info(f"参数组合 {idx+1} 回测完成")
                    logger.info(f"胜率:{report['win_rate']:.2%},收益率:{report['total_return']:.2%},最大回撤:{report['max_drawdown']:.2%}")
                
                except Exception as e:
                    logger.error(f"参数组合 {idx+1} 回测失败:{e}")
                    continue
            
            if not optimization_results:
                logger.warning("无有效优化结果")
                return self._get_default_parameters()
            
            # 筛选最优参数组合
            best_params = self._select_best_parameters(optimization_results)
            
            # 保存优化结果
            self._save_optimization_results(optimization_results)
            
            logger.info("参数优化完成")
            logger.info(f"最优参数组合:{best_params}")
            
            return best_params
            
        except Exception as e:
            logger.error(f"参数优化失败:{e}")
            return self._get_default_parameters()
    
    def _select_best_parameters(self, optimization_results):
        """筛选最优参数组合"""
        logger.info("开始筛选最优参数组合...")
        
        # 转换为DataFrame以便分析
        df_results = pd.DataFrame([
            {
                "learning_window": res['params']['learning_window'],
                "profit_threshold": res['params']['profit_threshold'],
                "stock_count": res['params']['stock_count'],
                "volume_ratio_threshold": res['params']['volume_ratio_threshold'],
                "win_rate": res['win_rate'],
                "avg_return": res['avg_return'],
                "total_return": res['total_return'],
                "max_drawdown": res['max_drawdown'],
                "profit_loss_ratio": res['profit_loss_ratio'],
                "avg_daily_return": res['avg_daily_return']
            }
            for res in optimization_results
        ])
        
        # 保存优化结果到CSV
        csv_path = Path(self.optimization_dir) / "parameter_optimization_log.csv"
        df_results.to_csv(csv_path, index=False, encoding='utf-8-sig')
        
        logger.info(f"优化结果已保存到:{csv_path}")
        
        # 筛选最优参数(多目标优化)
        # 优先考虑胜率≥50%,盈亏比≥2
        valid_results = df_results[
            (df_results['win_rate'] >= 0.5) &
            (df_results['profit_loss_ratio'] >= 2)
        ]
        
        if valid_results.empty:
            logger.warning("无符合要求的参数组合,使用默认参数")
            return self._get_default_parameters()
        
        # 综合评分(权重:胜率50%,盈亏比30%,收益率20%)
        valid_results['score'] = (
            valid_results['win_rate'] * 0.5 +
            valid_results['profit_loss_ratio'] * 0.3 +
            valid_results['total_return'] * 0.2
        )
        
        # 选择评分最高的参数组合
        best_idx = valid_results['score'].idxmax()
        best_result = valid_results.loc[best_idx]
        
        # 转换为字典格式
        best_params = {
            "learning_window": best_result['learning_window'],
            "profit_threshold": best_result['profit_threshold'],
            "stock_count": best_result['stock_count'],
            "volume_ratio_threshold": best_result['volume_ratio_threshold']
        }
        
        logger.info(f"最优参数组合筛选完成:{best_params}")
        return best_params
    
    def _save_optimization_results(self, optimization_results):
        """保存优化结果"""
        try:
            # 保存为JSON文件
            json_path = Path(self.optimization_dir) / "parameter_optimization_results.json"
            with open(json_path, 'w', encoding='utf-8') as f:
                json.dump(optimization_results, f, ensure_ascii=False, indent=2)
            
            logger.info(f"优化结果已保存到:{json_path}")
            
            # 保存优化日志
            log_path = Path(self.optimization_dir) / "optimization_log.txt"
            with open(log_path, 'a', encoding='utf-8') as f:
                f.write(f"优化时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"测试参数组合数:{len(optimization_results)}\n")
                f.write("-" * 80 + "\n")
            
            return True
            
        except Exception as e:
            logger.error(f"优化结果保存失败:{e}")
            return False
    
    def save_best_parameters(self, best_params):
        """保存最优参数到本地配置文件"""
        try:
            params_file = Path(self.optimization_dir) / "best_parameters.json"
            with open(params_file, 'w', encoding='utf-8') as f:
                json.dump(best_params, f, ensure_ascii=False, indent=2)
            
            logger.info(f"最优参数已保存到:{params_file}")
            return True
            
        except Exception as e:
            logger.error(f"最优参数保存失败:{e}")
            return False
    
    def load_best_parameters(self):
        """加载最优参数"""
        params_file = Path(self.optimization_dir) / "best_parameters.json"
        
        if params_file.exists():
            try:
                with open(params_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                logger.error(f"最优参数加载失败:{e}")
        
        return self._get_default_parameters()
    
    def _get_default_parameters(self):
        """获取默认参数"""
        return {
            "learning_window": "90日",
            "profit_threshold": "3%",
            "stock_count": "10只",
            "volume_ratio_threshold": "1.5"
        }
    
    def get_optimization_summary(self):
        """获取优化结果汇总"""
        try:
            csv_path = Path(self.optimization_dir) / "parameter_optimization_log.csv"
            
            if csv_path.exists():
                df_summary = pd.read_csv(csv_path)
                return df_summary
            else:
                logger.warning("优化结果文件不存在")
                return pd.DataFrame()
                
        except Exception as e:
            logger.error(f"优化结果汇总获取失败:{e}")
            return pd.DataFrame()
    
    def test_optimization(self):
        """测试参数优化功能"""
        logger.info("开始测试参数优化功能...")
        
        # 测试参数池构建
        logger.info(f"参数池大小:{len(self.param_pool)}")
        
        # 测试参数组合生成(只生成3个组合用于测试)
        test_combinations = self._generate_parameter_combinations()[:3]
        logger.info(f"测试参数组合数:{len(test_combinations)}")
        
        # 测试执行优化(使用简化日期范围)
        logger.info("开始执行参数优化测试...")
        self.backtester.start_date = "20241201"
        self.backtester.end_date = "20241231"
        
        test_results = []
        for idx, params in enumerate(test_combinations):
            logger.info(f"测试参数组合 {idx+1}/{len(test_combinations)}:{params}")
            
            try:
                report = self.backtester.run(params, "20241201", "20241231")
                test_results.append({
                    "params": params,
                    "win_rate": report['win_rate'],
                    "avg_return": report['avg_return'],
                    "total_return": report['total_return'],
                    "max_drawdown": report['max_drawdown'],
                    "profit_loss_ratio": report['profit_loss_ratio'],
                    "avg_daily_return": report['avg_daily_return']
                })
                
                logger.info(f"参数组合 {idx+1} 回测完成")
                logger.info(f"胜率:{report['win_rate']:.2%},收益率:{report['total_return']:.2%},最大回撤:{report['max_drawdown']:.2%}")
                
            except Exception as e:
                logger.error(f"参数组合 {idx+1} 回测失败:{e}")
                continue
        
        # 测试结果分析
        if test_results:
            best_params = self._select_best_parameters(test_results)
            logger.info(f"最优参数组合:{best_params}")
        
        logger.info("参数优化功能测试完成")

if __name__ == "__main__":
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    
    # 测试参数优化模块
    optimizer = ParameterOptimizer()
    optimizer.test_optimization()
    # best_params = optimizer.run()
    # optimizer.save_best_parameters(best_params)
    # logger.info(f"最优参数:{best_params}")