Files
etf/framework_v2/ALIGNMENT_GUIDE.md
aszerW 5f08e508ac docs(framework_v2): 完善文档体系 + 修复 .gitignore
## 文档体系(5 个文档,互相关联)
- README.md - 框架总览 + 文档索引
- DATA_ARCHITECTURE.md - 数据架构方案(Schema、验证、性能优化)
- ALIGNMENT_GUIDE.md - CrossMarketAligner 使用指南
- DATA_FLOW_DEMO.md - 从 OHLCV 到最终收益的 7 个阶段推演
- ALIGNMENT_SCHEMA_INTEGRATION.md - Aligner + Schema 整合方案

## 文档特色
- 大量代码示例( 正确 vs  错误对比)
- 数据流可视化(ASCII 图)
- 表格总结(问题、严重度、解决方案)
- 实际场景推演(2024-01-01 ~ 2024-01-31)
- 文档互链(形成知识网络)

## 修复
- .gitignore: 添加 !framework_v2/shared/data/ 例外
- 允许提交对齐器相关文件
2026-05-24 10:29:20 +08:00

8.7 KiB
Raw Blame History

跨市场数据对齐方案

📋 问题背景

在跨市场 ETF 轮动策略中,不同市场的交易日历不同:

市场 典型假日 影响
A 股 春节、国庆 每年约 115 个交易日
美股 马丁路德金日、感恩节 每年约 252 个交易日
港股 佛诞日、圣诞节 每年约 250 个交易日

核心问题:如何在不同交易日历之间对齐因子和收益率?


常见错误

错误 1先计算收益率再 ffill

# ❌ 错误
returns = close.pct_change()
returns_aligned = returns.reindex(a_share_dates, method='ffill')

# 问题:
日期         价格    收益率     A股日历    对齐后收益率
2024-01-01   100     NaN      2024-01-01  NaN
2024-01-02   101     +1%      2024-01-02  +1%  
2024-01-03   102     +1%      2024-01-03  +1%   错误复制了前一天的收益率
              美股休市价格不变

# 结果A 股交易日"继承"了美股休市期间的收益率
# 导致:净值高估/低估

错误 2用 ffill 后的价格计算因子

# ❌ 错误
close_aligned = close.reindex(a_share_dates, method='ffill')
factor = close_aligned.rolling(25).apply(weighted_momentum)

# 问题:
# 25 日窗口中包含 2-3 个重复值ffill 填充)
# 导致:
# 1. 因子计算偏差(重复值影响线性回归)
# 2. 动量得分虚高(价格"不变"被误认为稳定)

正确方案

原则 1因子在原始日历计算再对齐

# ✅ 正确
# 1. 在原始交易日历计算因子
factor = close.rolling(25).apply(weighted_momentum)  # 美股日历

# 2. 对齐因子值到 A 股日历
factor_aligned = factor.reindex(a_share_dates, method='ffill')

# 为什么正确:
# - 因子计算使用原始日历25 个真实交易日)
# - 对齐的是因子值,不是价格
# - ffill 填充的是"最新因子值",而不是"重复价格"

原则 2价格先对齐再计算收益率

# ✅ 正确
# 1. 价格对齐到 A 股日历
close_aligned = close.reindex(a_share_dates, method='ffill')

# 2. 计算收益率
returns = close_aligned.pct_change(fill_method=None)

# 为什么正确:
# - 休市日价格不变ffill
# - 收益率 = (今日价格 - 昨日价格) / 昨日价格 = 0%
# - 不会复制前一天的非零收益率

🏗️ CrossMarketAligner 实现

核心功能

from framework_v2.shared.data.alignment import CrossMarketAligner

# 初始化
aligner = CrossMarketAligner(target_calendar=a_share_dates)

# 1. 对齐因子值
aligned_factor = aligner.align_factor(
    factor_series,
    source_calendar=us_dates,
    code='^GSPC'
)
# 返回 DataFrame:
# - value: 对齐后的因子值
# - is_filled: 是否为 ffill 填充值

# 2. 对齐收益率
returns = aligner.align_returns(
    close_series,
    code='^GSPC'
)
# 返回 Series收益率A 股日历)

# 3. 对齐多标的
returns_df = aligner.align_multi_asset({
    '^GSPC': close_sp500,
    '^IXIC': close_nasdaq,
    '931862.CSI': close_bond
})
# 返回 DataFrame所有标的同索引

# 4. 验证信号与收益率对齐
aligned_signals, aligned_returns = aligner.validate_alignment(
    signals,
    returns_df
)

验证逻辑

class CrossMarketAligner:
    def _validate_factor_alignment(self, aligned, is_filled, code):
        """验证因子对齐"""
        # 1. 检查 NaN 比例
        nan_ratio = aligned.isna().sum() / len(aligned)
        if nan_ratio > 0.1:
            warnings.warn(f"{code}: 因子 NaN 比例过高")
        
        # 2. 检查填充比例
        fill_ratio = is_filled.sum() / len(is_filled)
        if fill_ratio > 0.3:
            warnings.warn(f"{code}: 因子填充比例过高")
    
    def _validate_returns(self, returns, code):
        """验证收益率"""
        # 1. 检查 NaN 比例
        nan_ratio = returns.isna().sum() / len(returns)
        if nan_ratio > 0.1:
            raise ValueError(f"{code}: 收益率 NaN 比例过高")
        
        # 2. 检查异常值
        max_return = returns.abs().max()
        if max_return > 0.5:  # 单日涨跌 > 50%
            warnings.warn(f"{code}: 发现异常收益率")
        
        # 3. 检查索引
        if not returns.index.equals(self.target_calendar):
            raise ValueError(f"{code}: 收益率索引与目标日历不匹配")

📊 测试验证

测试 1因子对齐

源日历(美股): 8 天
目标日历A股: 10 天

对齐后因子值:
            value  is_filled
2024-01-01   0.10      False     ← 真实值
2024-01-02   0.15      False     ← 真实值
2024-01-03   0.15       True     ← ffill 填充
2024-01-04   0.18      False     ← 真实值
...

✓ 填充值正确标记
✓ NaN 比例检查通过

测试 2收益率对齐

原始价格(美股日历):
2024-01-01    100.0
2024-01-02    101.0
2024-01-04    102.0    ← 2024-01-03 休市

对齐后收益率A股日历:
2024-01-01    0.000000  ← 首日
2024-01-02    0.010000  ← +1%
2024-01-03    0.000000  ← 0%(休市,价格不变)✓
2024-01-04    0.009901  ← +0.99%

✓ 休市日收益率 = 0%
✓ 无 NaN
✓ 索引匹配 A 股日历

测试 3ffill 陷阱对比

❌ 错误做法:先 pct_change再 reindex
步骤 1 - 收益率:
2024-01-01         NaN
2024-01-02    0.010000
2024-01-04    0.009901

步骤 2 - reindex + ffill:
2024-01-01         NaN
2024-01-02    0.010000
2024-01-03    0.010000  ← 错误!复制了前一天的收益率
2024-01-04    0.009901

✅ 正确做法:先 reindex 价格,再 pct_change
步骤 1 - 价格 reindex:
2024-01-01    100.0
2024-01-02    101.0
2024-01-03    101.0     ← ffill价格不变
2024-01-04    102.0

步骤 2 - pct_change:
2024-01-01    0.000000
2024-01-02    0.010000
2024-01-03    0.000000  ← 正确!收益率 = 0%
2024-01-04    0.009901

测试结果

============================================================
  测试总结
============================================================
  ✓ 通过 - 因子对齐
  ✓ 通过 - 收益率对齐
  ✓ 通过 - 多标的对齐
  ✓ 通过 - 信号与收益对齐
  ✓ 通过 - ffill 陷阱

总计: 5/5 通过

🎯 在策略中使用

完整流程

from framework_v2.shared.data.alignment import CrossMarketAligner

class RotationStrategy(StrategyBase):
    def run_backtest(self, data: dict) -> dict:
        # 1. 获取数据
        index_data = data['index_data']
        a_share_dates = data['a_share_dates']
        valid_codes = data['valid_codes']
        
        # 2. 创建对齐器
        aligner = CrossMarketAligner(target_calendar=a_share_dates)
        
        # 3. 计算因子(原始日历)→ 对齐到 A 股日历
        factor_dict = {}
        for code in valid_codes:
            close_series = index_data[code]['close']
            
            # 在原始日历计算因子
            factor_series = self._factor.compute(
                pd.DataFrame({'close': close_series})
            )
            
            # 对齐到 A 股日历
            aligned = aligner.align_factor(
                factor_series,
                source_calendar=close_series.index,
                code=code
            )
            
            factor_dict[code] = aligned['value']
        
        factor_df = pd.DataFrame(factor_dict)
        
        # 4. 生成信号
        signals = self._selector.generate(factor_df)
        
        # 5. 计算收益率(价格对齐 → 收益率)
        returns_df = aligner.align_multi_asset({
            code: index_data[code]['close']
            for code in valid_codes
        })
        
        # 6. 验证信号与收益率对齐
        aligned_signals, aligned_returns = aligner.validate_alignment(
            signals,
            returns_df
        )
        
        # 7. 执行回测
        ...

⚠️ 注意事项

  1. 填充值标记is_filled 列标记哪些是 ffill 填充的,可用于后续分析
  2. NaN 处理:收益率对齐后自动填充为 0表示"无数据,收益率为 0"
  3. 异常检测:单日收益率 > 50% 会发出警告
  4. 索引验证:对齐后严格验证索引是否匹配目标日历
  5. 统计信息:通过 aligner.get_stats() 获取对齐统计

📚 相关文档


创建日期: 2026-05-06 版本: 1.0.0