---
phase: 11-predictv3
plan: 04
type: execute
wave: 1
depends_on: []
files_modified:
  - application/admin/model/History.php
  - application/admin/controller/History.php
autonomous: true
requirements:
  - PRED-04
must_haves:
  truths:
    - "用户可以通过接口获取最优权重配置"
    - "系统返回基于历史回测的权重优化结果"
    - "优化结果包含各权重配置的命中率、NDCG评估"
    - "网格搜索有超时保护机制"
  artifacts:
    - path: "application/admin/model/History.php"
      provides: "权重网格搜索优化方法"
      contains: "_optimizeWeightsGridSearch"
    - path: "application/admin/controller/History.php"
      provides: "权重优化接口入口"
      contains: "optimizeWeights"
  key_links:
    - from: "optimizeWeights controller"
      to: "_optimizeWeightsGridSearch model"
      via: "method call"
---

# Phase 11 - Plan 04: 权重网格搜索优化

## Objective

实现权重网格搜索优化功能，通过预定义权重组合批量回测，找出最优权重配置，提升算法预测准确性。

**Purpose:** 当前权重为手动配置，缺乏数据驱动优化。网格搜索是一种成熟的参数优化方法，能基于历史回测数据找到更优权重组合。

**Output:** `History.php` 新增 `_optimizeWeightsGridSearch` 方法，`History.php` controller 新增 `optimizeWeights` 接口入口。

## Tasks

### Task 1: 实现权重网格搜索方法（含5种具体配置和超时保护）

<read_first>
- D:\code\php\amlhc\application\admin\model\History.php (line 2094-2113, 默认权重配置)
- D:\code\php\amlhc\application\admin\model\History.php (line 3495-3556, _runBacktestV3 方法)
</read_first>

<action>
在 `History.php` 类末尾新增权重网格搜索优化方法：

```php
/**
 * 权重网格搜索优化
 * 
 * 优化目标定义:
 * - 综合评估得分 = hit_rate * 0.6 + ndcg_5 * 100 * 0.4
 * - 命中率权重60%，NDCG权重40%
 * - 返回综合得分最高的权重配置
 * 
 * 5种预定义权重配置:
 * - 配置1: 遗漏优先型 - omit_regression权重最高(0.25)
 * - 配置2: 转移概率优先型 - transition_prob权重最高(0.25)
 * - 配置3: 走势方向优先型 - trend_direction权重最高(0.25)
 * - 配置4: 平衡型 - 各维度权重较均衡
 * - 配置5: 组合特征优先型 - combination权重最高(0.20)
 * 
 * @param int $periods 统计期数，范围50-500
 * @param int $backtestCount 回测期数，范围10-100
 * @param int $timeoutSeconds 超时限制秒数，默认60秒
 * @return array {best_weights: [], best_hit_rate: float, best_ndcg: float, all_results: [], timed_out: bool}
 */
private function _optimizeWeightsGridSearch($periods = 200, $backtestCount = 50, $timeoutSeconds = 60)
{
    // 超时保护：记录开始时间
    $startTime = microtime(true);
    $timedOut = false;
    
    // 5种预定义权重配置（具体权重值明确）
    $weightConfigs = [
        // 配置1: 遗漏优先型 - 遗漏回归权重最高
        [
            'omit_regression' => 0.25,    // 遗漏回归权重25%
            'freq_regression' => 0.12,    // 频率回归权重12%
            'transition_prob' => 0.15,    // 转移概率权重15%
            'trend_direction' => 0.12,    // 走势方向权重12%
            'oddeven_balance' => 0.08,    // 单双平衡权重8%
            'bigsmall_balance' => 0.08,   // 大小平衡权重8%
            'zone_balance' => 0.05,       // 区域平衡权重5%
            'color_balance' => 0.05,      // 波色平衡权重5%
            'combination' => 0.10         // 组合特征权重10%
        ],
        // 配置2: 转移概率优先型 - 转移概率权重最高
        [
            'omit_regression' => 0.15,
            'freq_regression' => 0.10,
            'transition_prob' => 0.25,    // 转移概率权重25%（最高）
            'trend_direction' => 0.12,
            'oddeven_balance' => 0.08,
            'bigsmall_balance' => 0.08,
            'zone_balance' => 0.04,
            'color_balance' => 0.04,
            'combination' => 0.14
        ],
        // 配置3: 走势方向优先型 - 走势方向权重最高
        [
            'omit_regression' => 0.12,
            'freq_regression' => 0.10,
            'transition_prob' => 0.15,
            'trend_direction' => 0.25,    // 走势方向权重25%（最高）
            'oddeven_balance' => 0.08,
            'bigsmall_balance' => 0.08,
            'zone_balance' => 0.04,
            'color_balance' => 0.04,
            'combination' => 0.12
        ],
        // 配置4: 平衡型（默认配置）- 各维度权重较均衡
        [
            'omit_regression' => 0.18,
            'freq_regression' => 0.12,
            'transition_prob' => 0.18,
            'trend_direction' => 0.14,
            'oddeven_balance' => 0.08,
            'bigsmall_balance' => 0.08,
            'zone_balance' => 0.04,
            'color_balance' => 0.04,
            'combination' => 0.10
        ],
        // 配置5: 组合特征优先型 - 组合特征权重最高
        [
            'omit_regression' => 0.15,
            'freq_regression' => 0.10,
            'transition_prob' => 0.15,
            'trend_direction' => 0.12,
            'oddeven_balance' => 0.06,
            'bigsmall_balance' => 0.06,
            'zone_balance' => 0.03,
            'color_balance' => 0.03,
            'combination' => 0.20     // 组合特征权重20%（最高）
        ]
    ];
    
    $bestWeights = [];
    $bestHitRate = 0;
    $bestNdcg = 0;
    $bestCombinedScore = 0;
    $allResults = [];
    
    // 执行每种配置的回测（添加超时检查）
    foreach ($weightConfigs as $configIdx => $weights) {
        // 超时检查：超过限制时间则停止
        $elapsedTime = microtime(true) - $startTime;
        if ($elapsedTime > $timeoutSeconds) {
            $timedOut = true;
            break;
        }
        
        // 执行回测
        $backtest = $this->_runBacktestV3($periods, $weights, $backtestCount);
        
        $hitRate = $backtest['hit_rate'] ?? 0;
        $ndcg = $backtest['ndcg_5'] ?? 0;
        $avgRank = $backtest['avg_rank'] ?? 0;
        $mrr = $backtest['mrr'] ?? 0;
        
        // 综合评估得分：命中率60% + NDCG40%
        $combinedScore = $hitRate * 0.6 + $ndcg * 100 * 0.4;
        
        $result = [
            'config_name' => $configIdx + 1,
            'config_type' => ['遗漏优先型', '转移概率优先型', '走势方向优先型', '平衡型', '组合特征优先型'][$configIdx],
            'weights' => $weights,
            'hit_rate' => $hitRate,
            'avg_rank' => $avgRank,
            'ndcg_5' => $ndcg,
            'mrr' => $mrr,
            'combined_score' => round($combinedScore, 2),
            'total_hits' => $backtest['total_hits'] ?? 0
        ];
        
        $allResults[] = $result;
        
        // 更新最优配置
        if ($combinedScore > $bestCombinedScore) {
            $bestCombinedScore = $combinedScore;
            $bestHitRate = $hitRate;
            $bestNdcg = $ndcg;
            $bestWeights = $weights;
        }
    }
    
    // 按综合得分降序排序结果
    usort($allResults, function($a, $b) {
        return $b['combined_score'] - $a['combined_score'];
    });
    
    return [
        'best_weights' => $bestWeights,
        'best_hit_rate' => $bestHitRate,
        'best_ndcg' => $bestNdcg,
        'best_combined_score' => round($bestCombinedScore, 2),
        'all_results' => $allResults,
        'periods' => $periods,
        'backtest_count' => $backtestCount,
        'timeout_seconds' => $timeoutSeconds,
        'timed_out' => $timedOut,
        'elapsed_time' => round(microtime(true) - $startTime, 2)
    ];
}
```

实现要点：
- **5种具体权重配置**：每种配置的权重值在代码中明确列出
- **优化目标明确**：综合得分 = hit_rate*0.6 + ndcg_5*100*0.4
- **超时保护**：添加 $timeoutSeconds 参数，默认60秒，超时后停止剩余配置测试
- **配置类型命名**：遗漏优先型、转移概率优先型、走势方向优先型、平衡型、组合特征优先型
- 返回 timed_out 标志和 elapsed_time 供前端判断
</action>

<acceptance_criteria>
- grep 正则匹配: `_optimizeWeightsGridSearch\s*\(` 在 History.php 中存在
- grep 匹配: `$weightConfigs` 数组包含5个配置项
- grep 匹配: `$timeoutSeconds` 参数在方法签名中存在
- grep 匹配: `$timedOut` 变量在方法中存在
- grep 匹配: `combined_score` 在返回结果中存在
- grep 匹配: `config_type` 在结果中存在
- 方法调用 `_runBacktestV3` 进行回测
- 方法包含函数级注释说明优化目标和配置类型
</acceptance_criteria>

### Task 2: 新增权重优化接口入口（含参数验证和超时警告）

<read_first>
- D:\code\php\amlhc\application\admin\controller\History.php (line 25, noNeedRight 数组)
- D:\code\php\amlhc\application\admin\controller\History.php (line 460-489, predictV3 方法)
</read_first>

<action>
在 `History.php` controller 中：

1. 将 `optimizeWeights` 添加到 `noNeedRight` 数组（约 line 25）：

找到：
```php
protected $noNeedRight = ['missingNum', 'trendData', ..., 'predictV3'];
```

在 `predictV3` 后添加 `'optimizeWeights'`：

```php
protected $noNeedRight = ['missingNum', 'trendData', ..., 'predictV3', 'optimizeWeights'];
```

2. 在 `predictV3` 方法后（约 line 489 之后）新增 `optimizeWeights` 方法：

```php
/**
 * 权重网格搜索优化接口
 * 执行多权重配置回测，返回最优权重组合
 * 
 * 参数说明:
 * - periods: 统计期数，范围50-500，默认200
 * - backtest: 回测期数，范围10-100，默认30
 * - timeout: 超时秒数，范围10-120，默认60
 * 
 * 返回说明:
 * - best_weights: 最优权重配置
 * - best_hit_rate: 最优配置命中率
 * - all_results: 所有配置测试结果
 * - timed_out: 是否超时中断
 */
public function optimizeWeights()
{
    if ($this->request->isAjax()) {
        // 参数验证
        $periods = $this->request->get('periods', 200, 'intval');
        if ($periods < 50 || $periods > 500) {
            $this->error('期数范围必须在 50-500 之间');
        }
        
        $backtestCount = $this->request->get('backtest', 30, 'intval');
        if ($backtestCount < 10 || $backtestCount > 100) {
            $backtestCount = 30; // 使用默认值而非报错
        }
        
        $timeoutSeconds = $this->request->get('timeout', 60, 'intval');
        if ($timeoutSeconds < 10 || $timeoutSeconds > 120) {
            $timeoutSeconds = 60; // 使用默认值
        }
        
        // 执行优化
        $result = $this->model->_optimizeWeightsGridSearch($periods, $backtestCount, $timeoutSeconds);
        
        // 超时警告提示
        $message = '优化完成';
        if ($result['timed_out']) {
            $message = '优化超时中断，已完成' . count($result['all_results']) . '种配置测试';
        }
        
        $this->success($message, null, $result);
    }
}
```

实现要点：
- 接口参数：periods（统计期数）、backtest（回测期数）、timeout（超时秒数）
- 参数范围验证，超出范围时使用默认值或报错
- 返回最优权重配置及所有测试结果
- 超时时返回警告消息和已完成的测试数量
- 需添加到 noNeedRight 允许无权限访问
</action>

<acceptance_criteria>
- grep 匹配: `optimizeWeights` 在 noNeedRight 数组中存在
- grep 正则匹配: `public function optimizeWeights\s*\(` 在 controller 中存在
- grep 匹配: `$timeoutSeconds` 在方法中存在
- grep 匹配: `$result['timed_out']` 在方法中存在
- 方法调用 `$this->model->_optimizeWeightsGridSearch`
- 方法包含函数级注释
</acceptance_criteria>

## Verification

执行权重优化接口验证返回结果：

```bash
curl -s "http://127.0.0.1:8000/admin/history/optimizeWeights?periods=200&backtest=20&timeout=60" | grep -E "best_weights|best_hit_rate|best_ndcg|all_results|timed_out|config_type"
```

预期结果：返回 JSON 中包含 best_weights、best_hit_rate、best_ndcg、all_results、timed_out、config_type 字段。

## Success Criteria

1. `_optimizeWeightsGridSearch` 方法已实现，包含 5 种预定义权重配置（权重值明确）
2. 优化目标明确：综合得分 = hit_rate*0.6 + ndcg_5*100*0.4
3. 超时保护机制已添加，默认60秒
4. `optimizeWeights` controller 接口已实现，含参数验证
5. 接口能正常返回最优权重配置及测试结果
6. 超时时返回警告消息
7. 所有新增方法包含函数级注释

## Output

完成后创建 `.planning/phases/11-predictv3/11-04-SUMMARY.md`