数据获取与预处理:REITs数据来源与清洗实战

做REITs量化投资,第一步就是搞数据。这活儿看着简单,其实坑特别多。我刚开始做REITs因子研究时,就因为在数据清洗上偷了懒,结果回测曲线漂亮得不行,实盘一跑直接翻车。嗯,从那以后我再也不敢小看数据预处理了。

数据来源:Wind vs Bloomberg

国内做REITs研究,数据源基本就两个选择:Wind和Bloomberg。我个人习惯用Wind做主力,因为覆盖的A股REITs更全,更新也及时。Bloomberg呢,适合做全球REITs对比分析时用。

说几个实际体验:

  • Wind:API接口方便,用Python的wset函数就能拉数据。但要注意,有些REITs的财务数据会有延迟,尤其是季报披露期。
  • Bloomberg:数据质量高,但贵。而且它的字段命名跟Wind不一样,比如分红率在Wind叫dividend_yield,在Bloomberg可能叫DVD_YLD

我建议你两个都备着。为什么?因为做因子研究最怕数据源单一,万一某天Wind某个字段出错了,你还能用Bloomberg交叉验证。我在项目中就遇到过Wind把某只REITs的NAV数据算错了,还好用Bloomberg对了一下才发现。

数据清洗:别让脏数据毁了你的模型

数据拉回来,第一件事不是跑模型,而是清洗。你想想看,如果输入的是垃圾,输出能是黄金吗?

清洗流程我一般分三步走:

  1. 去重:同一只REITs在不同交易日可能出现重复记录,尤其是跨市场品种。
  2. 格式统一:日期格式、数值精度、字段命名,全部标准化。比如日期统一成YYYY-MM-DD,价格保留两位小数。
  3. 逻辑校验:检查价格是否为正、分红率是否在合理范围(比如0%-20%)。

核心原则:清洗时宁可多删,不要少删。一条脏数据可能让你的因子IC值从0.05变成-0.03,整个策略逻辑都变了。

缺失值处理:填还是不填?

REITs数据缺失很常见,尤其是早期上市的品种。比如某只REITs刚上市前三个月,可能没有完整的财务数据。这时候怎么办?

我常用的方法有三种:

方法 适用场景 我的经验
直接删除 缺失比例小于5% 最省事,但要注意别把关键数据删了
前向填充 时间序列数据,如价格、成交量 适合日频数据,但别用在财务指标上
插值法 连续型数据,如NAV、分红率 线性插值就够用,别搞太复杂

举个例子。我曾经处理一只物流REITs的数据,它的分红率在2023年Q2缺失了。我直接用前向填充,把Q1的数据填进去。结果呢?Q3实际分红率比Q1低了30%,我的因子信号完全错了。后来我改成用同行业均值插值,效果好了很多。

小技巧:对于财务类缺失值,可以用同行业、同规模REITs的均值来填充。这样比简单前向填充更合理。

异常值检测:揪出那些离谱的数据

异常值检测,说白了就是找那些明显不对劲的数据。比如某只REITs一天涨了50%,或者分红率突然变成负数。这些数据不处理,模型会学歪。

我常用的检测方法:

  • 3σ原则:超出均值±3倍标准差的数据,标记为异常。适合正态分布的数据。
  • 箱线图法:用四分位数来识别异常值,对非正态分布更友好。
  • 业务规则:比如REITs的日收益率超过±20%,基本就是数据错误。

我记得有一次,某只REITs的成交量数据突然飙到正常值的100倍。用3σ法检测出来了,但我不确定是数据错误还是真有大事发生。后来查了新闻,发现是那天有笔大宗交易。嗯,这种异常值就不能简单删除,得结合业务判断。

注意:异常值不一定是错误。有些异常值可能是市场极端事件,比如疫情爆发时的暴跌。处理前一定要先确认原因。

知识体系:数据预处理全流程

下面这张图是我自己总结的数据预处理流程,每次做新项目都会拿出来对照一遍。

REITs数据预处理全流程 数据获取 Wind / Bloomberg 数据清洗 去重 / 格式统一 / 逻辑校验 缺失值处理 删除 / 填充 / 插值 异常值检测 3σ / 箱线图 确认异常原因 数据错误 → 删除或修正 市场事件 → 保留并标记 干净可用的数据集

实战代码:数据预处理模板

下面是我自己用的数据预处理模板,每次做新项目都会拿它改一改。你直接复制就能用。

import pandas as pd
import numpy as np

def clean_reits_data(df):
    """
    REITs数据预处理函数
    df: 原始DataFrame,包含日期、代码、价格、分红率等字段
    """
    # 1. 去重
    df = df.drop_duplicates(subset=['date', 'code'])
    
    # 2. 格式统一
    df['date'] = pd.to_datetime(df['date'])
    df['price'] = df['price'].round(2)
    
    # 3. 逻辑校验
    df = df[df['price'] > 0]
    df = df[df['dividend_yield'].between(0, 0.2)]
    
    # 4. 缺失值处理(前向填充)
    df = df.sort_values(['code', 'date'])
    df['nav'] = df.groupby('code')['nav'].ffill()
    
    # 5. 异常值检测(3σ法)
    for col in ['price', 'volume']:
        mean = df[col].mean()
        std = df[col].std()
        df = df[df[col].between(mean - 3*std, mean + 3*std)]
    
    return df

# 使用示例
raw_data = pd.read_csv('reits_data.csv')
clean_data = clean_reits_data(raw_data)
print(f"清洗前:{len(raw_data)}条,清洗后:{len(clean_data)}条")

个人建议:这个模板我用了两年,基本覆盖了90%的常见问题。但每个数据集都有自己的特殊性,比如有些REITs的分红率可能因为特殊分红而超过20%,这时候阈值就得调整。灵活一点,别死板套用。

数据预处理这步,说白了就是磨刀。刀磨好了,后面砍柴才快。我见过太多人急着跑模型,结果被数据坑得死去活来。你想想看,与其花一周调参,不如花一天把数据搞干净。这笔账,怎么算都划算。

专注资料整理