01-研究工程与治理

预计学习时间:4-6 小时

难度:⭐⭐⭐⭐⭐

核心问题:你发现了一个”能赚钱”的策略——但你确定它真的能赚钱吗?


从一个真实场景出发

你是一个量化研究员,花了两周时间构建了一个新的动量因子。回测结果令人兴奋:

  • 年化收益 35%
  • Sharpe 2.8
  • 最大回撤 8%
  • 回撤恢复期从未超过 3 个月

你兴冲冲地写了一份报告,提交给投资委员会。但 CRO(首席风险官)只问了三个问题:

  1. 你用的数据包含退市股票吗?
  2. 你的交易成本模型是怎么设的?
  3. 这个因子的月均换手率是多少?容量是多少?

你突然意识到:你从来没有考虑过这三个问题。

更残酷的是,当你回去仔细检查之后发现:

  • 你的数据只包含当前存活的股票 → 幸存者偏差:把退市的垃圾股排除了,当然”低价值股”表现好
  • 你的回测用的是 5bp 的交易成本 → 低估成本:实际上这个因子月换手率 200%,以真实滑点计算,收益几乎为零
  • 你的因子容量只有 5000 万 → 无法规模化:即使真的有效,对基金的整体贡献也微乎其微

这不是虚构的故事,而是几乎每个量化研究员都经历过的”假 Alpha”教训。

这章讲的就是:如何通过系统化的治理流程,防止这种”假 Alpha”进入实盘。


一、命题治理(Idea Governance)

1.1 核心思想:从”灵感”到”命题”需要形式化

个人研究者最常见的习惯是:想到一个想法 → 直接写代码回测 → 看结果决定要不要继续。

机构研究不是这么做的。在机构里,一个想法要变成”可研究的命题”,需要经过形式化。

想法 → 命题的转化过程:

  灵感阶段                      命题阶段
  ┌────────────────────┐       ┌────────────────────────────┐
  │ "我觉得低波动股票    │       │ 命题:在控制市值和行业暴露后,│
  │  长期表现更好"      │  ──→  │ 低波动率股票的超额收益是否     │
  │                    │       │ 显著为正?                   │
  │ 模糊的直觉          │       │                            │
  │ 不可验证            │       │ 明确的假设                  │
  │ 没有边界条件        │       │ 可检验的表述                │
  └────────────────────┘       │ 有明确的适用范围             │
                                └────────────────────────────┘

一个”好命题”需要回答四个问题:

问题好的答案不好的答案
为什么有效?低波动率股票因为杠杆约束和机构基准竞争被系统性低估”回测数据就是这样”
在什么市场有效?成熟市场,流动性足够的中大盘股”所有股票都行”
可能失效的条件?当大量资金涌入低波动策略导致拥挤时”不会失效”
容量大约多大?根据市场流动性和持仓集中度估算没想过

1.2 命题评审标准

机构对命题有明确的评审标准,通常从三个维度打分:

维度一:经济学逻辑(权重 40%)

评分标准
A有清晰的理论基础(风险补偿、行为偏差、市场摩擦),有学术文献支撑
B有合理的经济学故事,但理论支撑不够强
C纯数据驱动,没有经济学解释
D甚至违反基本的金融学原理(比如”永远上涨”的策略)

维度二:统计可行性(权重 30%)

评分标准
A数据容易获取、质量高、样本量充足、不需要复杂的数据处理
B数据可以获取,但需要一定的清洗和处理
C数据获取困难,或质量存疑,或样本量不足
D数据几乎不可能获取,或质量无法保证

维度三:容量评估(权重 30%)

评分标准
A预估容量 > 5 亿,对组合有实质性贡献
B预估容量 5000 万 - 5 亿,可以作为辅助策略
C预估容量 < 5000 万,对组合贡献有限
D容量极小,不值得投入研发资源

1.3 命题生命周期管理

命题不是一锤子买卖。它有完整的生命周期:

命题生命周期:

  提出 ──→ 验证 ──→ 上线 ──→ 监控 ──→ 退市
   │        │        │        │        │
   ↓        ↓        ↓        ↓        ↓
  评审     样本内    Gate     持续     触发退
  打分     样本外    检查     跟踪     市标准
           验证              指标
  ┌─────────────────────────────────────────────┐
  │  状态机:                                    │
  │                                             │
  │  DRAFT ──→ REVIEW ──→ VALIDATING ──→ LIVE    │
  │    ↑            │                            │
  │    │            ↓                            │
  │    └──── REJECTED                         RETIRED
  │                  │                            │
  │                  └──→ REVISION ──→ REVIEW     │
  └─────────────────────────────────────────────┘

每个阶段的具体要求:

阶段产出物通过条件
DRAFT命题文档(动机、经济学逻辑、数据需求)评审分数 >= B-
REVIEW同行评审意见至少一位高级研究员同意进入验证
VALIDATING样本内结果 + 样本外结果 + 稳健性检验见 5.1 Gate 检查点
LIVE上线后的监控报告持续满足运行指标
RETIRED退市分析报告触发退市条件(见 5.3)

1.4 命题优先级排序

资源有限,不可能同时研究所有命题。机构通常用”影响力-可行性”矩阵来排序:

命题优先级矩阵:

                可行性(高)
                   │
       ┌───────────┼───────────┐
       │  Quick     │           │
       │  Wins      │  Major    │
       │           │  Projects │
       │  立即执行  │           │
  影响  ├───────────┼───────────┤  影响
  (高)│           │           │  (低)
       │  Fill-ins  │  Time     │
       │           │  Sinks    │
       │  有空再做  │           │
       │           │  不做      │
       └───────────┼───────────┘
                   │
                可行性(低)
象限决策典型例子
Quick Wins立即执行有经济学逻辑、数据现成的小改进
Major Projects重点投入新策略类型、需要新数据源的研究
Fill-ins有空再做微小的参数优化、非核心的探索
Time Sinks不做数据不可获取、经济学逻辑薄弱的”猎奇”想法

1.5 Python:命题管理系统

import numpy as np
import pandas as pd
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
 
# ============================================================
# 命题治理系统模拟
# ============================================================
 
class PropositionStatus(Enum):
    DRAFT = "draft"
    REVIEW = "review"
    VALIDATING = "validating"
    LIVE = "live"
    RETIRED = "retired"
    REJECTED = "rejected"
    REVISION = "revision"
 
 
@dataclass
class Proposition:
    """研究命题"""
    id: str
    title: str
    status: PropositionStatus = PropositionStatus.DRAFT
 
    # 评审打分(1-5)
    economics_score: float = 0.0      # 经济学逻辑
    statistics_score: float = 0.0     # 统计可行性
    capacity_score: float = 0.0       # 容量评估
 
    # 元信息
    author: str = ""
    created_at: datetime = field(default_factory=datetime.now)
    reviewer: Optional[str] = None
 
    # 验证结果
    in_sample_sharpe: Optional[float] = None
    out_sample_sharpe: Optional[float] = None
 
    # 实盘结果
    live_sharpe: Optional[float] = None
    live_turnover: Optional[float] = None
 
    def composite_score(self) -> float:
        """综合评分:经济学 40% + 统计 30% + 容量 30%"""
        return (0.4 * self.economics_score +
                0.3 * self.statistics_score +
                0.3 * self.capacity_score)
 
    def can_advance(self) -> tuple:
        """判断命题是否可以推进到下一阶段"""
        if self.status == PropositionStatus.DRAFT:
            if self.composite_score() >= 3.0:
                return True, "综合评分达标,可进入评审"
            return False, f"综合评分 {self.composite_score():.1f} < 3.0"
 
        if self.status == PropositionStatus.REVIEW:
            if self.reviewer is not None:
                return True, "已有评审人,可进入验证"
            return False, "需要指定评审人"
 
        if self.status == PropositionStatus.VALIDATING:
            if (self.in_sample_sharpe and self.out_sample_sharpe and
                self.out_sample_sharpe > 0.5):
                return True, "样本外 Sharpe 达标"
            return False, "样本外验证未通过"
 
        return False, "当前状态无法推进"
 
 
class PropositionGovernance:
    """命题治理系统"""
 
    def __init__(self):
        self.propositions: dict[str, Proposition] = {}
 
    def submit(self, prop: Proposition) -> dict:
        """提交新命题"""
        can_advance, reason = prop.can_advance()
        if not can_advance:
            return {"status": "rejected", "reason": reason}
 
        self.propositions[prop.id] = prop
        prop.status = PropositionStatus.REVIEW
        return {"status": "accepted", "reason": reason}
 
    def prioritize(self) -> pd.DataFrame:
        """按优先级排序所有活跃命题"""
        active = [p for p in self.propositions.values()
                  if p.status in (PropositionStatus.DRAFT,
                                  PropositionStatus.REVIEW,
                                  PropositionStatus.VALIDATING)]
        if not active:
            return pd.DataFrame()
 
        data = [{
            'ID': p.id,
            '标题': p.title,
            '综合评分': p.composite_score(),
            '经济学': p.economics_score,
            '统计可行性': p.statistics_score,
            '容量': p.capacity_score,
            '状态': p.status.value
        } for p in active]
 
        df = pd.DataFrame(data)
        return df.sort_values('综合评分', ascending=False)
 
 
# ============================================================
# 使用示例
# ============================================================
governance = PropositionGovernance()
 
# 提交三个命题
props = [
    Proposition(
        id="PROP-001",
        title="基于异质波动的选股因子",
        economics_score=4.0,   # 有理论支撑
        statistics_score=3.5,  # 数据现成
        capacity_score=4.0,    # 容量充足
        author="张三"
    ),
    Proposition(
        id="PROP-002",
        title="基于社交媒体情绪的日内预测",
        economics_score=2.0,   # 逻辑薄弱
        statistics_score=2.5,  # 数据获取难
        capacity_score=1.5,    # 容量极小
        author="李四"
    ),
    Proposition(
        id="PROP-003",
        title="多因子动态权重优化",
        economics_score=4.5,   # 理论扎实
        statistics_score=4.0,  # 数据充足
        capacity_score=3.0,    # 中等容量
        author="王五"
    ),
]
 
for prop in props:
    result = governance.submit(prop)
    print(f"{'='*50}")
    print(f"命题: {prop.title}")
    print(f"  综合评分: {prop.composite_score():.1f}")
    print(f"  结果: {result['status']} - {result['reason']}")
 
print(f"\n{'='*50}")
print("命题优先级排序:")
print(governance.prioritize().to_string(index=False))

二、数据治理(Data Governance)

2.1 核心思想:没有好数据,一切都是空谈

量化研究有一个铁律:Garbage in, Garbage out。

无论你的模型多精巧,如果输入的数据有问题,输出就一定有问题。而数据的”问题”往往不是明显的错误(比如价格是负数),而是微妙的偏差——前视偏差、幸存者偏差、数据清洗引入的偏差。

这些偏差有多隐蔽?来看一个真实的例子。

案例:一个”永远不会亏”的策略

某研究员发现了一个规律:每天买入前一天的涨幅最大的 10 只股票,年化收益高达 50%。但这个策略有一个致命问题——他在计算”前一天涨幅”时,用的是当天收盘后才能获得的复权价格。也就是说,他用今天的信息做了昨天的决策。

这就是前视偏差(Look-ahead Bias)

前视偏差示意图:

  正确的时序:                    有偏差的时序:
  ─────────                      ─────────

  T 日收盘                       T 日收盘
    ↓                              ↓
  计算 T 日的信号                 用 T 日的复权因子
    ↓                              ↓
  T+1 日开盘买入                 计算 T 日的信号 ← 错!
                                  ↓
                                T+1 日开盘买入

  正确:用 T 日及之前的信息决定 T+1 的交易
  错误:用 T 日的信息"回填"到 T 日的信号计算中

2.2 数据源管理与版本控制

机构对数据的管理远比个人研究者严格:

维度个人研究者机构做法
数据存储本地 CSV 文件数据仓库 + 版本控制
数据更新手动下载自动化管道 + 变更日志
数据溯源”从某网站下的”数据血缘:每条数据可追溯到源
版本管理覆盖更新DVC / 数据快照 / 变更通知
访问控制研究环境隔离,生产数据只读

数据版本控制的核心原则:

数据版本控制的三条铁律:

  1. 永远不要修改原始数据
     原始数据是"真值",发现错误也不改原始数据,
     而是在下游增加"数据修正层"并记录原因

  2. 每次数据处理都要记录完整的环境
     包括:代码版本、依赖版本、参数配置、数据版本
     目的:任何人拿到这些信息都能复现结果

  3. 数据变更必须通知所有使用者
     数据供应商修正历史数据时(非常常见),
     必须通知所有依赖该数据的研究员

2.3 数据质量校验

数据质量校验从四个维度进行:

完整性(Completeness)

检查项目:
  - 缺失值比例(超过 5% 需要标记)
  - 缺失模式(随机缺失 vs 系统性缺失)
  - 时间覆盖(是否存在"数据断档")
  - 股票覆盖(是否所有股票在所有时间都有数据)

一致性(Consistency)

检查项目:
  - 同一只股票在不同数据源中的差异
  - 同一天不同频率数据(日频 vs 分钟频)的对齐
  - 复权因子的一致性(前复权 vs 后复权 vs 不复权)
  - 行业分类的一致性(申万 vs 中信 vs GICS)

准确性(Accuracy)

检查项目:
  - 异常值检测(价格 = 0?成交量 = 0?收益率 > 100%?)
  - 交叉验证(两个数据源的同一指标是否一致)
  - 已知错误列表检查(供应商公布的数据修正)
  - 逻辑校验(开盘价不可能远高于收盘价 + 涨跌停幅度)

时效性(Timeliness)

检查项目:
  - 数据延迟(日频数据通常 T+1 可用,有没有 T+0?)
  - 数据修正(财务数据是否会被修正?修正频率?)
  - 点数据 vs 区间数据(实时数据 vs 收盘后数据)

2.4 系统性偏差检查

这是数据治理最重要的部分。三种最常见的偏差:

幸存者偏差(Survivorship Bias)

import numpy as np
import pandas as pd
 
np.random.seed(42)
 
# ============================================================
# 模拟幸存者偏差的影响
# ============================================================
n_stocks = 500
n_months = 60  # 5 年
 
# 初始市值
initial_market_cap = np.random.lognormal(16, 1, n_stocks)
 
# 每月收益:大公司和小公司有不同的分布
monthly_returns = np.zeros((n_months, n_stocks))
 
for t in range(n_months):
    for s in range(n_stocks):
        # 小公司:收益波动更大,有更多极端亏损
        if initial_market_cap[s] < np.median(initial_market_cap):
            monthly_returns[t, s] = np.random.normal(-0.002, 0.08)
        else:
            monthly_returns[t, s] = np.random.normal(0.002, 0.04)
 
# 模拟退市:连续亏损超过 50% 的股票在当年退市
alive = np.ones(n_stocks, dtype=bool)
for t in range(n_months):
    for s in range(n_stocks):
        if alive[s]:
            cumulative = np.prod(1 + monthly_returns[:t+1, s]) - 1
            if cumulative < -0.5:
                alive[s] = False
                monthly_returns[t:, s] = np.nan  # 退市后没有数据
 
# ============================================================
# 对比:有偏差 vs 无偏差的结果
# ============================================================
# 有幸存者偏差的数据:只包含当前存活的公司
survivor_returns = monthly_returns[:, alive]
# 无偏差的数据:包含所有公司(退市的也包含)
full_returns = monthly_returns.copy()
full_returns[np.isnan(full_returns)] = -0.1  # 退市当月跌 10%
 
# 按初始市值分组
small_mask = initial_market_cap < np.median(initial_market_cap)
large_mask = ~small_mask
 
def calc_annual_return(returns):
    """计算年化收益"""
    valid = returns[~np.isnan(returns)]
    if len(valid) == 0:
        return np.nan
    return (np.prod(1 + valid) ** (12 / len(valid)) - 1)
 
print("=" * 60)
print("幸存者偏差的影响")
print("=" * 60)
 
# 小市值组
small_survivor = calc_annual_return(survivor_returns[:, small_mask])
small_full = calc_annual_return(full_returns[:, small_mask])
 
# 大市值组
large_survivor = calc_annual_return(survivor_returns[:, large_mask])
large_full = calc_annual_return(full_returns[:, large_mask])
 
print(f"\n{'指标':<20} {'有幸存者偏差':>15} {'无偏差':>15}")
print("-" * 50)
print(f"{'小市值年化收益':<18} {small_survivor:>14.1%} {small_full:>14.1%}")
print(f"{'大市值年化收益':<16} {large_survivor:>14.1%} {large_full:>14.1%}")
print(f"{'小市值溢价':<20} {small_survivor - large_survivor:>14.1%} "
      f"{small_full - large_full:>14.1%}")
 
print(f"\n结论:幸存者偏差让小市值溢价看起来比实际大")
print(f"退市公司通常是表现差的(小市值更多),排除它们会高估收益")

前视偏差(Look-ahead Bias)

import numpy as np
import pandas as pd
 
np.random.seed(42)
 
# ============================================================
# 模拟前视偏差的影响
# ============================================================
n_stocks = 300
n_days = 252  # 一年
 
# 模拟日收益率
returns = np.random.normal(0.0005, 0.02, (n_days, n_stocks))
 
# 模拟复权因子:每天收盘后更新
# 复权因子反映了当天的分红和拆股
adjust_factors = np.ones((n_days, n_stocks))
for t in range(1, n_days):
    adjust_factors[t] = adjust_factors[t-1] * np.random.choice(
        [1.0, 0.98, 0.99], n_stocks, p=[0.9, 0.05, 0.05]
    )
 
# ============================================================
# 正确 vs 错误的计算方式
# ============================================================
 
# 错误:用 T 日的复权因子计算 T 日的信号
# (复权因子是 T 日收盘后才更新的)
wrong_signal = np.zeros((n_days, n_stocks))
for t in range(n_days):
    # 用 T 日的复权因子调整 T 日之前的价格
    wrong_signal[t] = adjust_factors[t] * np.random.normal(0, 0.01, n_stocks)
 
# 正确:用 T-1 日的复权因子计算 T 日的信号
correct_signal = np.zeros((n_days, n_stocks))
for t in range(1, n_days):
    correct_signal[t] = adjust_factors[t-1] * np.random.normal(0, 0.01, n_stocks)
 
# 用信号预测次日的收益
wrong_ic = np.array([
    np.corrcoef(wrong_signal[t], returns[min(t+1, n_days-1)])[0, 1]
    for t in range(n_days - 1)
])
correct_ic = np.array([
    np.corrcoef(correct_signal[t], returns[min(t+1, n_days-1)])[0, 1]
    for t in range(1, n_days - 1)
])
 
print("=" * 50)
print("前视偏差对 IC 的影响")
print("=" * 50)
print(f"正确方式的平均 IC: {np.nanmean(correct_ic):.4f}")
print(f"错误方式的平均 IC: {np.nanmean(wrong_ic):.4f}")
print(f"IC 膨胀: {np.nanmean(wrong_ic) - np.nanmean(correct_ic):.4f}")
print(f"\n即使数据本身没有问题,时序对齐错误也会产生虚假信号")

数据清洗偏差(Data-snooping via Cleaning)

数据清洗偏差的经典场景:

  1. 研究员发现某个因子在 2018-2022 年有效
  2. 仔细检查后,发现 2019 年有一段数据异常
  3. "合理地"剔除了那段异常数据
  4. 因子在剩余时间段表现更好了
  5. 但这个"更好"是真实的还是因为你剔除了不利的数据?

  关键问题:你剔除数据的标准是什么?
  - 如果标准是"让因子看起来更好" → 数据清洗偏差
  - 如果标准是独立于因子结果的(比如数据供应商确认错误) → 合理的

2.5 Python:数据质量检查框架

import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Optional
 
# ============================================================
# 数据质量检查框架
# ============================================================
 
@dataclass
class QualityCheckResult:
    """单项检查结果"""
    check_name: str
    passed: bool
    severity: str  # "error", "warning", "info"
    details: str = ""
    affected_rows: int = 0
 
 
class DataQualityChecker:
    """数据质量检查器"""
 
    def __init__(self, df: pd.DataFrame):
        self.df = df
        self.results: list[QualityCheckResult] = []
 
    def check_completeness(self, threshold: float = 0.05) -> QualityCheckResult:
        """检查缺失值"""
        total = self.df.shape[0] * self.df.shape[1]
        missing = self.df.isnull().sum().sum()
        missing_ratio = missing / total
 
        passed = missing_ratio < threshold
        severity = "error" if missing_ratio > 0.1 else "warning" if missing_ratio > threshold else "info"
        details = f"缺失值比例: {missing_ratio:.2%}"
 
        return QualityCheckResult(
            check_name="完整性",
            passed=passed,
            severity=severity,
            details=details,
            affected_rows=int(missing)
        )
 
    def check_price_validity(self, price_col: str) -> QualityCheckResult:
        """检查价格合理性"""
        issues = 0
        issues_details = []
 
        # 价格 <= 0
        neg_count = (self.df[price_col] <= 0).sum()
        if neg_count > 0:
            issues += neg_count
            issues_details.append(f"非正价格: {neg_count} 条")
 
        # 日收益率 > 30%(排除涨跌停)
        returns = self.df[price_col].pct_change()
        extreme = (returns.abs() > 0.30).sum()
        if extreme > 0:
            issues += extreme
            issues_details.append(f"极端收益(>30%): {extreme} 条")
 
        return QualityCheckResult(
            check_name="价格合理性",
            passed=issues == 0,
            severity="error" if issues > 10 else "warning",
            details="; ".join(issues_details) if issues_details else "通过",
            affected_rows=issues
        )
 
    def check_lookahead(self, signal_col: str, return_col: str) -> QualityCheckResult:
        """检查是否存在前视偏差(信号与同期收益的相关性)"""
        corr = self.df[signal_col].corr(self.df[return_col])
        suspicious = abs(corr) > 0.1
 
        return QualityCheckResult(
            check_name="前视偏差检查",
            passed=not suspicious,
            severity="error" if suspicious else "info",
            details=f"信号与同期收益相关系数: {corr:.4f}",
            affected_rows=0
        )
 
    def check_time_gaps(self, date_col: str, freq: str = "D") -> QualityCheckResult:
        """检查时间连续性"""
        dates = pd.to_datetime(self.df[date_col])
        expected = pd.date_range(dates.min(), dates.max(), freq=freq)
        missing_dates = set(expected) - set(dates.unique())
        gap_count = len(missing_dates)
 
        return QualityCheckResult(
            check_name="时间连续性",
            passed=gap_count == 0,
            severity="warning" if gap_count > 5 else "info",
            details=f"缺失交易日: {gap_count} 天",
            affected_rows=gap_count
        )
 
    def run_all_checks(self, price_col: str = "close",
                       signal_col: Optional[str] = None,
                       return_col: Optional[str] = None,
                       date_col: str = "date") -> pd.DataFrame:
        """运行所有检查并生成报告"""
        self.results = [
            self.check_completeness(),
            self.check_price_validity(price_col),
            self.check_time_gaps(date_col),
        ]
 
        if signal_col and return_col:
            self.results.append(self.check_lookahead(signal_col, return_col))
 
        report = pd.DataFrame([{
            '检查项': r.check_name,
            '通过': 'PASS' if r.passed else 'FAIL',
            '严重度': r.severity,
            '详情': r.details,
            '影响行数': r.affected_rows
        } for r in self.results])
 
        return report
 
 
# ============================================================
# 使用示例
# ============================================================
np.random.seed(42)
 
# 模拟有问题的数据
n_rows = 500
dates = pd.date_range("2022-01-01", periods=n_rows, freq="D")
 
# 故意制造一些问题
prices = np.random.lognormal(4, 0.3, n_rows).astype(float)
prices[10] = -1.0          # 非法价格
prices[50] = 0.0           # 非法价格
prices[200] *= 5           # 极端值
 
signal = np.random.normal(0, 1, n_rows)
# 故意让信号和同期收益有微弱相关性(模拟前视偏差)
returns = 0.05 * signal + np.random.normal(0, 0.02, n_rows)
 
df = pd.DataFrame({
    'date': dates,
    'close': prices,
    'signal': signal,
    'return': returns
})
 
# 运行检查
checker = DataQualityChecker(df)
report = checker.run_all_checks(
    price_col='close',
    signal_col='signal',
    return_col='return',
    date_col='date'
)
 
print("=" * 60)
print("数据质量检查报告")
print("=" * 60)
print(report.to_string(index=False))

三、特征治理(Feature Governance)

3.1 核心思想:特征是研究的基本单元,必须可追踪、可复现

在量化研究中,“特征”(Feature)是你用来预测收益的变量。一个因子(如动量、价值)通常由一个或多个特征组成。

个人研究者构建特征的习惯通常是:写一段代码 → 算出数值 → 直接用。问题在于:

  • 三个月后你自己都不记得这段代码是什么逻辑了
  • 换台电脑,代码跑不出来了(环境不一致)
  • 另一个研究员想验证你的结果,完全无法复现

特征治理要解决的就是这些问题。

3.2 特征命名规范与文档化

好的特征命名规范能让任何人一眼看出这个特征是什么:

命名规范:{市场}_{频率}_{类别}_{具体定义}_{版本}

  例子:
  - CN_D_FACTOR_MOMENTUM_20D_V1    (A股 日频 因子 动量 20日 版本1)
  - US_M_FACTOR_VALUE_PB_V2        (美股 月频 因子 价值 市净率 版本2)
  - CN_D_RISK_VOLATILITY_60D_V1    (A股 日频 风险 波动率 60日 版本1)

  反面例子:
  - my_factor                      (你的什么因子?)
  - test_v3                        (测试什么?第几个版本?)
  - factor_new                     (新的因子?多新?)

每个特征必须有文档记录:

字段内容
名称遵循命名规范的唯一标识
定义精确的数学定义
经济学含义为什么这个特征可能预测收益
输入数据依赖哪些原始数据字段
计算逻辑伪代码或核心代码片段
作者创建者
创建时间首次创建的日期
版本历史每次修改的变更说明
预期性质IC 方向、衰减速度、与已知因子的相关性

3.3 特征上线流程

特征上线流程:

  开发 → 自测 → 测试环境 → 同行评审 → 审批 → 生产环境
   │      │        │          │         │        │
   ↓      ↓        ↓          ↓         ↓        ↓
  研究   单元     集成       另一位     治理     自动化
  人员   测试     测试       研究员     委员会   管道
               检查       独立验证     审批

  每个阶段的检查点:
  ┌─────────────────────────────────────────────────┐
  │  自测:                                         │
  │  - 代码能正确运行?                              │
  │  - 边界情况处理了吗(停牌、涨跌停、新股)?       │
  │  - 输出分布合理吗?                              │
  ├─────────────────────────────────────────────────┤
  │  测试环境:                                     │
  │  - 与已有特征的兼容性?                          │
  │  - 计算耗时可接受吗?                            │
  │  - 内存使用合理吗?                              │
  ├─────────────────────────────────────────────────┤
  │  同行评审:                                     │
  │  - 另一位研究员能独立复现结果?                   │
  │  - 经济学逻辑站得住脚?                          │
  │  - IC/ICIR 符合预期?                            │
  ├─────────────────────────────────────────────────┤
  │  审批:                                         │
  │  - 特征文档完整?                                │
  │  - 命名规范符合?                                │
  │  - 上线风险评估?                                │
  └─────────────────────────────────────────────────┘

3.4 特征监控

特征上线只是开始。上线后需要持续监控:

监控指标含义告警阈值
分布漂移(Distribution Drift)特征的均值/方差是否发生系统性变化均值偏移 > 2 个标准差
IC 衰减(IC Decay)特征的预测力是否在下降滚动 12 个月 ICIR < 0.5
缺失率特征的计算是否有异常缺失率突然 > 5%
计算耗时特征计算是否变慢耗时 > 历史均值 + 2 个标准差
极端值比例特征是否有异常波动极端值比例 > 历史均值 + 3 个标准差

3.5 Python:特征监控与衰减检测

import numpy as np
import pandas as pd
 
np.random.seed(42)
 
# ============================================================
# 模拟特征衰减监控
# ============================================================
n_months = 120  # 10 年
n_stocks = 300
 
# 模拟 IC 序列
# 前 5 年:特征有效,IC 稳定
# 第 5-7 年:开始衰减
# 第 7-10 年:几乎失效
ic_series = np.zeros(n_months)
 
for t in range(n_months):
    if t < 60:
        # 有效阶段
        ic_series[t] = np.random.normal(0.05, 0.03)
    elif t < 84:
        # 衰减阶段
        decay = (t - 60) / 24
        ic_series[t] = np.random.normal(0.05 * (1 - 0.6 * decay), 0.03)
    else:
        # 失效阶段
        ic_series[t] = np.random.normal(0.01, 0.03)
 
dates = pd.date_range("2016-01-01", periods=n_months, freq="M")
ic_df = pd.DataFrame({'date': dates, 'IC': ic_series})
 
 
class FeatureMonitor:
    """特征监控器"""
 
    def __init__(self, ic_series: pd.Series, name: str):
        self.ic = ic_series
        self.name = name
        self.alerts: list[str] = []
 
    def check_ic_decay(self, window: int = 12) -> dict:
        """检查 IC 衰减"""
        rolling_icir = (self.ic.rolling(window).mean() /
                       self.ic.rolling(window).std())
        current_icir = rolling_icir.iloc[-1]
 
        # 分阶段分析
        n = len(self.ic)
        phase1_icir = (self.ic[:n//3].mean() / self.ic[:n//3].std())
        phase2_icir = (self.ic[n//3:2*n//3].mean() / self.ic[n//3:2*n//3].std())
        phase3_icir = (self.ic[2*n//3:].mean() / self.ic[2*n//3:].std())
 
        result = {
            '特征名称': self.name,
            '当前滚动ICIR': f"{current_icir:.2f}",
            '第一阶段ICIR': f"{phase1_icir:.2f}",
            '第二阶段ICIR': f"{phase2_icir:.2f}",
            '第三阶段ICIR': f"{phase3_icir:.2f}",
        }
 
        if current_icir < 0.5:
            result['告警'] = 'ICIR 低于 0.5,特征可能失效'
        elif current_icir < 1.0:
            result['告警'] = 'ICIR 低于 1.0,需要关注'
        else:
            result['告警'] = '正常'
 
        return result
 
    def check_distribution_drift(self, current_values: np.ndarray,
                                  reference_values: np.ndarray) -> dict:
        """检查分布漂移"""
        current_mean = np.mean(current_values)
        ref_mean = np.mean(reference_values)
        ref_std = np.std(reference_values)
 
        drift = abs(current_mean - ref_mean) / ref_std
 
        return {
            '特征名称': self.name,
            '参考均值': f"{ref_mean:.4f}",
            '当前均值': f"{current_mean:.4f}",
            '漂移程度': f"{drift:.2f} 个标准差",
            '状态': '严重漂移' if drift > 3 else
                    '轻微漂移' if drift > 2 else '正常'
        }
 
 
# ============================================================
# 使用示例
# ============================================================
monitor = FeatureMonitor(ic_df['IC'], '动量_20D')
 
print("=" * 60)
print("特征衰减监控报告")
print("=" * 60)
result = monitor.check_ic_decay()
for k, v in result.items():
    print(f"  {k}: {v}")
 
# 分布漂移检查
current_feature_values = np.random.normal(0.02, 0.5, 1000)
reference_feature_values = np.random.normal(0.0, 0.5, 1000)
print(f"\n分布漂移检查:")
drift_result = monitor.check_distribution_drift(
    current_feature_values, reference_feature_values
)
for k, v in drift_result.items():
    print(f"  {k}: {v}")

3.6 特征依赖关系管理

特征之间往往存在依赖关系。管理不善会导致”级联失效”:

特征依赖关系示例:

  原始数据层
  ├── 日频行情数据 (OHLCV)
  ├── 财务报表数据
  └── 分析师预期数据
       │
  特征层
  ├── 波动率_60D ←── 依赖:日频行情
  ├── 资产负债率 ←── 依赖:财务报表
  ├── 分析师修正幅度 ←── 依赖:分析师预期
  ├── 低波动_Zscore ←── 依赖:波动率_60D + 行业市值中性化
  └── 质量_综合评分 ←── 依赖:资产负债率 + ROE + 现金流

  如果"日频行情数据"出问题:
  → 波动率_60D 失效
  → 低波动_Zscore 失效
  → 依赖低波动因子的策略全部受影响

  依赖关系管理的原则:
  1. 画出完整的特征依赖图
  2. 上游数据变更必须评估下游影响
  3. 关键上游数据有备份方案

四、回测治理(Backtest Governance)

4.1 核心思想:回测结果是”证据”,不是”证明”

这是量化研究最重要的认知转变之一:

回测结果好 ≠ 策略真的有效。回测结果好只能说明策略在过去的数据上表现好。

过拟合有多严重?Lopez de Prado 在《Advances in Financial Machine Learning》中提出了一个令人警醒的比喻:如果你在 1000 个随机策略中选最好的一个,它的回测表现一定会非常好——但它只是一个随机的策略。

回测过拟合的概率:

  假设你有 N 个策略,每个策略的真实 Sharpe = 0(纯随机)

  你选出回测 Sharpe 最高的那个策略
  它的回测 Sharpe 期望值是多少?

  E[Best Sharpe] ≈ sqrt(2 * ln(N) * T) / sqrt(T)

  其中 T 是回测的月数

  ┌─────────────────────────────────────────┐
  │  试了 10 个策略,回测 5 年:              │
  │    最高 Sharpe ≈ 0.9                     │
  │  试了 100 个策略,回测 5 年:             │
  │    最高 Sharpe ≈ 1.4                     │
  │  试了 1000 个策略,回测 5 年:            │
  │    最高 Sharpe ≈ 1.7                     │
  │                                          │
  │  但它们全是假的!                         │
  └─────────────────────────────────────────┘

4.2 回测标准化框架

为了防止”每个研究员用自己的回测框架”导致的不可比、不可复现问题,机构通常建立统一的回测框架:

回测标准化框架的三个统一:

  ┌────────────────────────────────────────────────────────┐
  │                  统一回测框架                           │
  ├────────────────────────────────────────────────────────┤
  │                                                        │
  │  1. 统一成本模型                                       │
  │     ├── 佣金:按实际费率                               │
  │     ├── 印花税:按实际税率                             │
  │     ├── 滑点:按交易金额的函数(不是固定值)            │
  │     └── 冲击成本:按持仓比例和市场流动性的函数          │
  │                                                        │
  │  2. 统一数据源                                         │
  │     ├── 行情数据:统一供应商、统一复权方式              │
  │     ├── 财务数据:统一报告期处理                        │
  │     ├── 停牌处理:统一规则(剔除 / 持仓 / 前收盘价)   │
  │     └── 涨跌停处理:统一规则(能买到吗?能卖掉吗?)    │
  │                                                        │
  │  3. 统一绩效指标                                       │
  │     ├── 收益指标:年化、超额、对冲                     │
  │     ├── 风险指标:波动率、最大回撤、VaR                 │
  │     ├── 风险调整:Sharpe、Sortino、Calmar              │
  │     └── 交易指标:换手率、交易成本占比、容量            │
  │                                                        │
  └────────────────────────────────────────────────────────┘

4.3 回测代码审查清单

每次提交回测结果时,需要经过以下审查:

类别检查项说明
数据是否使用了幸存者偏差数据?数据是否包含退市股票
数据是否存在前视偏差?信号计算是否只用了当时可得的信息
数据停牌股票如何处理?停牌期间是否正确处理
成本交易成本模型是否合理?滑点是否按交易金额动态计算
成本换手率是否合理?月换手率 > 500% 通常有问题
过拟合参数数量是否过多?参数越多,过拟合风险越大
过拟合是否做了样本外验证?至少需要 30% 数据做样本外
过拟合是否做了稳健性检验?改变参数/时间窗口/股票池后的结果
复现代码是否可独立运行?其他人能否复现结果
复现随机种子是否固定?涉及随机过程时需要固定种子
逻辑是否检查了极端情况?涨跌停、新股、ST 股票
逻辑多空对冲是否正确?真的是市场中性吗

4.4 多层验证体系

机构不会因为一个回测好看就上线。验证是分层的:

多层验证体系:

  第一层:样本内验证 (In-Sample)
  ─────────────────────────────
  - 用全部数据拟合参数
  - 优化策略参数
  - 得到"最好情况"的结果
  - 目的:验证策略是否有基本面的有效性
  - 注意:样本内结果不能用于上线决策

  第二层:样本外验证 (Out-of-Sample)
  ─────────────────────────────
  - 留出 30%-50% 数据完全不参与任何决策
  - 包括:参数选择、特征选择、模型选择
  - 理想做法:Walk-forward 验证(滚动窗口)
  - 目的:验证策略在新数据上的表现
  - 标准:样本外 Sharpe > 样本内 Sharpe 的 50%

  第三层:模拟盘 (Paper Trading)
  ─────────────────────────────
  - 用实时数据运行策略
  - 但不下真实订单
  - 记录"如果交易"的结果
  - 目的:验证执行层面的可行性
  - 时间:至少运行 3-6 个月
  - 注意:模拟盘不考虑市场冲击

  第四层:实盘 (Live Trading)
  ─────────────────────────────
  - 用真实资金交易
  - 从小资金开始(比如目标容量的 10%)
  - 逐步加仓
  - 目的:验证真实执行效果
  - 关键指标:滑点 vs 预期、实际换手率 vs 回测

  每一层都可能发现问题,发现问题就回到上一层修复

4.5 回测结果的可复现性要求

要求说明
固定随机种子任何涉及随机过程(蒙特卡洛、Bootstrap)的代码必须固定种子
记录完整环境Python 版本、库版本、操作系统、数据版本
独立可运行给代码和数据,任何人都能跑出完全相同的结果(精确到小数点后 6 位)
版本控制所有代码纳入 Git 管理,每次回测打 Tag
结果存档回测结果(绩效指标、交易记录、持仓快照)永久存档

4.6 Python:标准化回测框架示例

import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Optional
 
# ============================================================
# 标准化回测框架
# ============================================================
 
@dataclass
class BacktestConfig:
    """回测配置(标准化参数)"""
    # 数据
    data_source: str = "unified_warehouse"
    data_version: str = "2024Q4"
    include_delisted: bool = True     # 包含退市股票
 
    # 成本模型
    commission_rate: float = 0.0003   # 佣金万三
    stamp_tax_rate: float = 0.001     # 印花税千一(卖出)
    slippage_model: str = "linear"    # 滑点模型
    slippage_bps: float = 5.0         # 基础滑点 5bp
 
    # 约束
    max_turnover_monthly: float = 5.0 # 月换手上限 500%
    max_position_pct: float = 0.05    # 单只股票最大持仓 5%
    min_liquidity: float = 1e7        # 最低流动性(成交额)
 
    # 验证
    train_ratio: float = 0.7          # 样本内比例
    random_seed: int = 42
 
 
@dataclass
class BacktestResult:
    """回测结果"""
    # 收益指标
    annual_return: float
    annual_excess_return: float
    annual_volatility: float
 
    # 风险调整
    sharpe: float
    sortino: float
    calmar: float
    max_drawdown: float
 
    # 交易指标
    annual_turnover: float
    cost_ratio: float  # 交易成本占总收益的比例
 
    # 容量
    estimated_capacity: float
 
    # 检查
    is_overfitted: bool
    reproducible: bool
 
 
class StandardBacktest:
    """标准化回测引擎"""
 
    def __init__(self, config: BacktestConfig):
        self.config = config
 
    def run(self, signal: pd.DataFrame, prices: pd.DataFrame) -> dict:
        """执行回测并返回结果"""
        np.random.seed(self.config.random_seed)
 
        n_days = len(signal)
        n_stocks = signal.shape[1]
 
        # 模拟回测
        returns = self._simulate_trading(signal, prices)
 
        # 计算绩效
        result = self._calculate_metrics(returns)
 
        # 过拟合检查
        result['is_overfitted'] = self._check_overfitting(signal)
        result['reproducible'] = True  # 标准化框架默认可复现
 
        return result
 
    def _simulate_trading(self, signal, prices):
        """模拟交易过程"""
        # 简化版:用信号的多空组合收益
        long_mask = signal > signal.quantile(0.8, axis=1, keepdim=False).values[:, 0]
        short_mask = signal < signal.quantile(0.2, axis=1, keepdim=False).values[:, 0]
 
        n_stocks = signal.shape[1]
        long_returns = np.where(long_mask[:, np.newaxis].repeat(n_stocks, axis=1),
                                prices.pct_change().fillna(0).values,
                                0)
        short_returns = np.where(short_mask[:, np.newaxis].repeat(n_stocks, axis=1),
                                 -prices.pct_change().fillna(0).values,
                                 0)
 
        portfolio_returns = (long_returns + short_returns) / 20  # 等权 20 只
        daily_returns = portfolio_returns.sum(axis=1)
 
        # 扣除交易成本
        turnover = np.mean(np.abs(np.diff(signal.values, axis=0)), axis=1)
        cost = turnover * self.config.slippage_bps / 10000
 
        return pd.Series(daily_returns[1:] - cost[:len(daily_returns)-1])
 
    def _calculate_metrics(self, returns):
        """计算标准化绩效指标"""
        annual_factor = 252
        total_return = (1 + returns).prod() - 1
        n_years = len(returns) / annual_factor
        annual_return = (1 + total_return) ** (1 / n_years) - 1
        annual_vol = returns.std() * np.sqrt(annual_factor)
        sharpe = annual_return / annual_vol if annual_vol > 0 else 0
        cumulative = (1 + returns).cumprod()
        rolling_max = cumulative.cummax()
        drawdown = (cumulative - rolling_max) / rolling_max
        max_drawdown = drawdown.min()
 
        return {
            'annual_return': annual_return,
            'annual_volatility': annual_vol,
            'sharpe': sharpe,
            'max_drawdown': max_drawdown,
            'total_return': total_return,
        }
 
    def _check_overfitting(self, signal):
        """检查过拟合指标"""
        # 简化版:信号的自相关太高可能意味着过拟合
        avg_autocorr = np.mean([
            pd.Series(signal.iloc[:, i]).autocorr(lag=1)
            for i in range(min(10, signal.shape[1]))
            if pd.Series(signal.iloc[:, i]).std() > 0
        ])
        return abs(avg_autocorr) > 0.5
 
 
# ============================================================
# 回测结果审查
# ============================================================
 
class BacktestReviewer:
    """回测审查器"""
 
    def review(self, result: dict) -> dict:
        """审查回测结果"""
        issues = []
 
        # 1. Sharpe 过高
        if result['sharpe'] > 3.0:
            issues.append(f"Sharpe = {result['sharpe']:.2f} 过高,可能存在过拟合")
 
        # 2. 最大回撤过小
        if result['max_drawdown'] > -0.05:
            issues.append(f"最大回撤仅 {result['max_drawdown']:.2%},可能低估了风险")
 
        # 3. 过拟合检查
        if result['is_overfitted']:
            issues.append("过拟合检测未通过")
 
        return {
            'passed': len(issues) == 0,
            'issues': issues,
            'verdict': '通过审查' if len(issues) == 0 else
                      '需要修正' if len(issues) <= 2 else '不建议推进'
        }
 
 
# 使用示例
print("=" * 60)
print("标准化回测审查示例")
print("=" * 60)
 
config = BacktestConfig()
bt = StandardBacktest(config)
 
# 模拟信号和价格
np.random.seed(42)
n_days, n_stocks = 500, 100
signal = pd.DataFrame(np.random.normal(0, 1, (n_days, n_stocks)))
prices = pd.DataFrame(
    100 + np.cumsum(np.random.normal(0, 1, (n_days, n_stocks)), axis=0)
)
 
result = bt.run(signal, prices)
print(f"\n回测绩效:")
for k, v in result.items():
    if isinstance(v, float):
        print(f"  {k}: {v:.4f}")
    else:
        print(f"  {k}: {v}")
 
reviewer = BacktestReviewer()
review = reviewer.review(result)
print(f"\n审查结果: {review['verdict']}")
if review['issues']:
    print("问题列表:")
    for issue in review['issues']:
        print(f"  - {issue}")

五、Gate 治理(Gate Governance)

5.1 核心思想:策略上线不是”研究员说了算”

这是治理体系最核心的一环。Gate 治理确保只有通过严格验证的策略才能进入实盘。

白话版本:Gate 就是一道”闸门”,不合格的策略被挡在门外。

为什么需要 Gate?因为一个失败的策略上线后,亏损的不只是策略本身——它会侵蚀整个组合的收益,占用交易资源,分散研究员的精力。

Gate 治理的层次:

  研究员                    Gate 检查                    投资委员会
  ┌───────────┐          ┌───────────────┐           ┌──────────────┐
  │           │          │               │           │              │
  │ "我的策略  │ ───────→ │ 自动化检查    │ ───────→  │  最终决策    │
  │  Sharpe 3  │          │               │           │              │
  │  太好了!" │          │ Sharpe > 1.5? │           │  通过/不通过  │
  │           │          │ IC > 0.03?    │           │  资金分配    │
  └───────────┘          │ 换手率 < 500%?│           │              │
                          │ 容量 > 5000万?│           └──────────────┘
                          │ 相关性 < 0.3? │
                          │               │
                          │ 全部通过 →     │
                          │ 部分通过 →     │
                          │ 不通过 →       │
                          └───────────────┘

5.2 策略上线的 Gate 检查点

指标最低标准理想标准说明
样本外 Sharpe> 1.0> 1.5年化,扣除全部交易成本后
样本外 IC> 0.02> 0.05月度 IC 均值
样本外 ICIR> 0.5> 1.0IC 的稳定性
月换手率< 800%< 300%过高换手意味着成本吞噬收益
最大回撤< 30%< 15%风险控制底线
估计容量> 2000 万> 1 亿考虑市场流动性
与现有策略相关性< 0.5< 0.3高相关性意味着分散化不足
回撤恢复期< 12 个月< 6 个月回撤后能恢复吗
盈利月占比> 55%> 60%大多数月份应该是赚钱的

5.3 实盘监控与熔断机制

策略上线后不是”放任自流”。机构有严格的监控和熔断机制:

实盘监控与熔断:

  正常运行 ──→ 触发告警 ──→ 触发熔断 ──→ 退市评估
     │            │             │           │
     ↓            ↓             ↓           ↓
   定期       指标偏离       自动减仓     投资委员会
   报告       预设阈值       或清仓       决定是否
   (周/月)   (持续监控)   (保护资金)  恢复或退市

  常见告警阈值:
  ┌──────────────────────────────────────────────────┐
  │  告警级别    触发条件                动作          │
  ├──────────────────────────────────────────────────┤
  │  绿灯        所有指标正常           继续运行       │
  │  黄灯        单日亏损 > 2 个标准差  发送告警通知   │
  │  橙灯        连续 5 日亏损          降仓 50%      │
  │  红灯        月度亏损 > 5%         停止交易       │
  │  黑灯        月度亏损 > 10%        清仓 + 紧急审查│
  └──────────────────────────────────────────────────┘

5.4 策略退市标准与流程

策略退市的触发条件:

  1. IC 持续衰减
     - 连续 6 个月滚动 IC < 0.01
     - 连续 3 个月滚动 ICIR < 0.3

  2. 持续亏损
     - 连续 3 个月实盘亏损
     - 累计亏损超过初始资本的 10%

  3. 拥挤度上升
     - 与已知公开因子的相关性 > 0.8
     - 因子收益的波动率显著上升

  4. 容量急剧下降
     - 可交易容量 < 初始估计的 50%
     - 冲击成本 > 预期的 2 倍

  退市流程:
  ┌─────────────────────────────────────────────┐
  │  触发退市条件                                │
  │       ↓                                      │
  │  自动化系统发出退市建议                        │
  │       ↓                                      │
  │  研究员进行原因分析(2 周内)                  │
  │       ↓                                      │
  │  投资委员会评审                               │
  │       ↓                                      │
  │  决策:立即退市 / 观察期 / 修正后恢复         │
  │       ↓                                      │
  │  执行:减仓计划(避免一次性清仓的冲击)        │
  │       ↓                                      │
  │  复盘:为什么失效?有什么教训?                │
  └─────────────────────────────────────────────┘

5.5 多策略组合的相关性管理

多策略相关性管理:

  ┌─────────────────────────────────────────────────┐
  │           组合层面治理                           │
  ├─────────────────────────────────────────────────┤
  │                                                 │
  │  策略 A (动量)  ──┐                             │
  │  策略 B (价值)  ──┼──→ 相关性矩阵              │
  │  策略 C (低波动) ──┘    │                       │
  │                           ↓                     │
  │                     ┌───────────────┐           │
  │                     │ 相关性检查:   │           │
  │                     │               │           │
  │                     │ A-B: 0.1  OK  │           │
  │                     │ A-C: 0.6  !!  │           │
  │                     │ B-C: 0.2  OK  │           │
  │                     │               │           │
  │                     │ 问题:A 和 C  │           │
  │                     │ 高度相关,    │           │
  │                     │ 分散化不足    │           │
  │                     └───────┬───────┘           │
  │                             ↓                   │
  │                     决策:                    │
  │                     1. 降低 A 或 C 的权重      │
  │                     2. 修改 C 减少与 A 的相关  │
  │                     3. 寻找替代策略             │
  │                                                 │
  └─────────────────────────────────────────────────┘

5.6 Python:Gate 检查系统

import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import List
 
# ============================================================
# Gate 治理检查系统
# ============================================================
 
@dataclass
class GateThreshold:
    """Gate 检查阈值"""
    min_sharpe: float = 1.0
    min_ic: float = 0.02
    min_icir: float = 0.5
    max_turnover: float = 8.0      # 年化换手率倍数
    max_drawdown: float = -0.30
    min_capacity: float = 2e7      # 2000 万
    max_correlation: float = 0.5
    min_win_rate: float = 0.55
 
 
@dataclass
class GateCheckResult:
    """单项检查结果"""
    name: str
    value: float
    threshold: float
    passed: bool
    severity: str  # "critical", "warning", "pass"
 
 
class GateGovernance:
    """Gate 治理系统"""
 
    def __init__(self, threshold: GateThreshold = None):
        self.threshold = threshold or GateThreshold()
 
    def check_all(self, strategy_metrics: dict,
                  existing_correlations: List[float] = None) -> dict:
        """执行全部 Gate 检查"""
        t = self.threshold
        checks = []
 
        # Sharpe
        checks.append(GateCheckResult(
            name="样本外 Sharpe",
            value=strategy_metrics['sharpe'],
            threshold=t.min_sharpe,
            passed=strategy_metrics['sharpe'] >= t.min_sharpe,
            severity="critical"
        ))
 
        # IC
        checks.append(GateCheckResult(
            name="样本外 IC",
            value=strategy_metrics['ic'],
            threshold=t.min_ic,
            passed=strategy_metrics['ic'] >= t.min_ic,
            severity="critical"
        ))
 
        # ICIR
        checks.append(GateCheckResult(
            name="样本外 ICIR",
            value=strategy_metrics['icir'],
            threshold=t.min_icir,
            passed=strategy_metrics['icir'] >= t.min_icir,
            severity="critical"
        ))
 
        # 换手率
        checks.append(GateCheckResult(
            name="年化换手率",
            value=strategy_metrics['turnover'],
            threshold=t.max_turnover,
            passed=strategy_metrics['turnover'] <= t.max_turnover,
            severity="warning"
        ))
 
        # 最大回撤
        checks.append(GateCheckResult(
            name="最大回撤",
            value=strategy_metrics['max_drawdown'],
            threshold=t.max_drawdown,
            passed=strategy_metrics['max_drawdown'] >= t.max_drawdown,
            severity="critical"
        ))
 
        # 容量
        checks.append(GateCheckResult(
            name="估计容量",
            value=strategy_metrics['capacity'],
            threshold=t.min_capacity,
            passed=strategy_metrics['capacity'] >= t.min_capacity,
            severity="warning"
        ))
 
        # 盈利月占比
        checks.append(GateCheckResult(
            name="盈利月占比",
            value=strategy_metrics['win_rate'],
            threshold=t.min_win_rate,
            passed=strategy_metrics['win_rate'] >= t.min_win_rate,
            severity="warning"
        ))
 
        # 与现有策略的相关性
        if existing_correlations:
            max_corr = max(existing_correlations)
            checks.append(GateCheckResult(
                name="与现有策略最大相关性",
                value=max_corr,
                threshold=t.max_correlation,
                passed=max_corr <= t.max_correlation,
                severity="critical"
            ))
 
        # 汇总结果
        critical_fails = [c for c in checks
                         if not c.passed and c.severity == "critical"]
        warning_fails = [c for c in checks
                        if not c.passed and c.severity == "warning"]
        all_passed = len(critical_fails) == 0
 
        if all_passed and len(warning_fails) == 0:
            decision = "PASS"
            message = "通过所有 Gate 检查,可以进入投资委员会评审"
        elif all_passed:
            decision = "PASS_WITH_WARNING"
            message = f"通过关键检查,但有 {len(warning_fails)} 项警告需讨论"
        else:
            decision = "FAIL"
            message = f"{len(critical_fails)} 项关键检查未通过,建议重新研究"
 
        return {
            'decision': decision,
            'message': message,
            'checks': checks,
            'critical_fails': critical_fails,
            'warning_fails': warning_fails,
        }
 
 
# ============================================================
# 使用示例
# ============================================================
np.random.seed(42)
 
gate = GateGovernance()
 
# 模拟两个策略的 Gate 检查
strategies = [
    {
        'name': '策略A: 高质量多因子',
        'metrics': {
            'sharpe': 1.8,
            'ic': 0.045,
            'icir': 1.2,
            'turnover': 3.5,
            'max_drawdown': -0.12,
            'capacity': 5e7,
            'win_rate': 0.62,
        },
        'correlations': [0.15, 0.22, 0.08]
    },
    {
        'name': '策略B: 日内反转',
        'metrics': {
            'sharpe': 2.5,     # 看起来很好
            'ic': 0.065,
            'icir': 1.8,
            'turnover': 15.0,  # 但换手率极高
            'max_drawdown': -0.08,
            'capacity': 5000000,  # 容量极小
            'win_rate': 0.55,
        },
        'correlations': [0.6, 0.45, 0.55]  # 与现有策略高度相关
    },
]
 
for strategy in strategies:
    print(f"\n{'='*60}")
    print(f"Gate 检查: {strategy['name']}")
    print(f"{'='*60}")
 
    result = gate.check_all(
        strategy['metrics'],
        strategy['correlations']
    )
 
    print(f"\n检查结果:")
    for check in result['checks']:
        status = "PASS" if check.passed else "FAIL"
        if check.name in ("估计容量",):
            print(f"  [{status}] {check.name}: "
                  f"{check.value/1e7:.1f} 亿 vs {check.threshold/1e7:.1f} 亿")
        else:
            print(f"  [{status}] {check.name}: "
                  f"{check.value:.3f} vs {check.threshold:.3f}")
 
    print(f"\n决策: {result['decision']}")
    print(f"说明: {result['message']}")

六、治理体系的设计原则

6.1 治理不是限制创新,而是防止”假 Alpha”

这是一个需要反复强调的观点。

治理的真正目的:

  错误理解:"治理就是层层审批,拖慢研究速度"

  正确理解:"治理是一个过滤器,
            它让好想法通过、坏想法被识别,
            最终节省的是所有人的时间"

  ┌─────────────────────────────────────────────┐
  │                                             │
  │  没有治理的研究团队:                         │
  │  - 研究员花 3 个月开发策略                   │
  │  - 回测看起来很好                             │
  │  - 上线后亏钱                                 │
  │  - 又花 3 个月排查原因                        │
  │  - 发现是数据问题                             │
  │  - 总计浪费 6 个月                            │
  │                                             │
  │  有治理的研究团队:                           │
  │  - 研究员花 1 个月开发策略                   │
  │  - Gate 检查发现数据问题                     │
  │  - 修正后重新验证                             │
  │  - 通过 Gate 后上线                           │
  │  - 实盘表现符合预期                           │
  │  - 总计 2 个月(其中治理只多花了 1 周)       │
  │                                             │
  └─────────────────────────────────────────────┘

6.2 从个人研究到机构研究的转变

维度个人研究者小团队(3-5 人)大型机构(20+ 人)
命题管理用笔记记录即可共享文档 + 简单评审完整的命题管理系统
数据治理检查清单自动化质量检查数据仓库 + 版本控制 + 血缘追踪
特征治理代码注释命名规范 + 文档特征平台 + 自动化管道
回测治理标准化模板统一回测框架多层验证 + 同行评审
Gate 治理简单的检查清单固定的 Gate 阈值动态阈值 + 投资委员会

关键原则:治理的复杂度要与团队规模匹配。 个人研究者不需要机构的完整流程,但必须理解其中的原理。

最小可用治理(个人研究者):

  1. 命题记录本
     - 每个想法写下来(动机、经济学逻辑)
     - 记录验证结果(通过/不通过/原因)

  2. 数据检查清单
     - 是否包含退市股票?
     - 是否存在前视偏差?
     - 停牌股票如何处理?
     - 换手率和成本是否合理?

  3. 样本外验证
     - 至少留出 30% 数据做测试
     - 不要在样本外数据上调参

  4. 回测复现检查
     - 固定随机种子
     - 记录环境信息
     - 能独立复现结果

6.3 文档化、流程化、自动化

治理体系演进的三个阶段:

治理体系成熟度模型:

  Level 1: 文档化
  ─────────────────
  - 有了检查清单
  - 有了模板
  - 有了规范文档
  - 但依赖人工执行

  Level 2: 流程化
  ─────────────────
  - 检查变成了流程的一部分
  - 有明确的审批流程
  - 有定期的评审机制
  - 但仍有大量手动操作

  Level 3: 自动化
  ─────────────────
  - 数据质量自动检查
  - 回测框架自动标准化
  - Gate 检查自动执行
  - 监控告警自动触发
  - 人工只做最终决策

6.4 工具链与平台支持

功能工具/平台说明
版本控制Git所有代码必须纳入版本控制
数据版本控制DVC / LakeFS数据文件的版本管理
实验追踪MLflow / W&B记录每次实验的参数、结果、环境
任务调度Airflow / Prefect自动化数据管道和回测任务
文档管理Confluence / Notion研究文档的集中管理
监控告警Grafana + Prometheus实盘监控和告警
代码审查GitHub PR / GitLab MR同行评审流程

七、小结:治理体系的完整图景

五大治理体系的关系

研究治理完整图景:

  ┌─────────────────────────────────────────────────────────────┐
  │                     研究治理体系                             │
  ├─────────────────────────────────────────────────────────────┤
  │                                                             │
  │  输入:想法(灵感、文献、市场观察)                          │
  │       ↓                                                     │
  │  ┌─────────────┐                                            │
  │  │  命题治理    │  想法值得研究吗?经济学逻辑成立吗?        │
  │  └──────┬──────┘                                            │
  │         ↓                                                    │
  │  ┌─────────────┐                                            │
  │  │  数据治理    │  数据干净吗?没有偏差吗?                  │
  │  └──────┬──────┘                                            │
  │         ↓                                                    │
  │  ┌─────────────┐                                            │
  │  │  特征治理    │  特征可靠吗?可复现吗?在衰减吗?          │
  │  └──────┬──────┘                                            │
  │         ↓                                                    │
  │  ┌─────────────┐                                            │
  │  │  回测治理    │  回测结果可信吗?成本合理吗?               │
  │  └──────┬──────┘                                            │
  │         ↓                                                    │
  │  ┌─────────────┐                                            │
  │  │  Gate 治理   │  策略准备好上线了吗?通过全部检查了吗?     │
  │  └──────┬──────┘                                            │
  │         ↓                                                    │
  │  输出:可信的投资决策                                        │
  │       ↓                                                    │
  │  持续监控 → 退市评估 → 新的命题                             │
  │                                                             │
  └─────────────────────────────────────────────────────────────┘

核心要点总结

治理领域核心问题关键工具最常见的失误
命题治理想法值得研究吗?命题评审表、优先级矩阵纯数据驱动,没有经济学逻辑
数据治理数据可信吗?质量检查框架、偏差检测幸存者偏差和前视偏差
特征治理特征可靠吗?特征平台、监控仪表盘特征没有文档化,无法复现
回测治理回测结果可信吗?标准化框架、审查清单过拟合、低估交易成本
Gate 治理策略能上线吗?Gate 检查系统、熔断机制跳过 Gate 直接上线

最后的忠告

治理的目的是让你在实盘赚钱,而不是让你在流程中迷失。好的治理应该像一个自动扶梯——你站上去就自然被带到正确的方向,而不是像爬山一样每一步都艰难。

如果你的治理流程让研究员觉得”我在填表格而不是在做研究”,那说明治理设计有问题。最有效的治理,是嵌入到工具和流程中的,而不是依赖人的自觉性的。


推荐延伸阅读

书名作者相关章节
Advances in Financial Machine LearningMarcos Lopez de Prado第 1-4 章(数据治理、回测陷阱、特征工程)
Quantitative Portfolio ManagementMichael Isichenko第 8-10 章(研究流程、风险管理)
The Man Who Solved the MarketGregory Zuckerman文艺作品,但生动展现了文艺复兴科技的研究文化
Expected ReturnsAntti Ilmanen第 2 章(投资哲学)和各因子章节

版本信息

  • 创建日期:2026-03-28
  • 最后更新:2026-03-28
  • 许可:CC BY-NC-SA 4.0