本节摘要:行情数据回答「市场在做什么」,宏观与情绪数据尝试回答「为什么、以及接下来情绪往哪边」。本节先介绍官方 data_providers 模块的定位:聚合宏观指标、新闻与情绪三类「慢变量」数据源(聚合对象与配置方式以官方文档为准);再逐一拆解三类的数据特性——宏观指标低频但权威、新闻事件高频但非结构化、情绪评分连续但方法论敏感;然后给出它们进入策略的统一输入形态:与 K 线时间轴对齐的事件流与数值序列,附一个重采样示意脚本;最后是全节的克制提醒:这类数据噪信比高、滞后与修订常见,把它当过滤器与情境变量,不要当神奇开关。本节不构成任何对这些数据预测能力的背书。
| 类型 | 形态 | 频率 | 特性 |
|---|---|---|---|
| 宏观指标 | 数值序列(利率、就业、通胀类) | 低频(按发布日历) | 权威、滞后、常被修订 |
| 新闻事件 | 带时间戳的事件流(标题、来源、标签) | 高频 | 非结构化、重复多、来源质量参差 |
| 情绪评分 | 连续分值序列(聚合自多渠道的口径,示意) | 连续更新 | 方法论敏感、口径漂移、易被噪声污染 |
与行情数据最大的不同在于时间语义:K 线是规则网格,宏观按日历跳变,新闻随时炸出,情绪则自带平滑与滞后。直接把它们扔进策略,时间轴对不齐是第一故障。
时间轴对齐问题(示意) K 线网格 │ ▶│ ▶│ ▶│ ▶│ ▶│ ▶│ ▶│ ▶│ 规则网格 宏观指标 │ ● │ 只在发布时刻有值 新闻事件 │ ✦ ✦✦ ✦ ✦ ✦ │ 随机到达 情绪评分 │ ~ ~ ~~~ ~ ~~ ~~~ ~ ~~ │ 连续但滞后 │ ▼ 统一重采样到 K 线网格(forward-fill,见下文)
事件特征:把新闻/宏观事件映射为离散标记或计数(「过去 24 小时高影响力事件数」,示意特征名),进入策略后天然适合做过滤器——事件密集期收窄风险预算,而不是预测方向。
数值特征:把宏观与情绪序列重采样到 K 线频率,缺失段用「上次已知值」前向填充(forward-fill),并把「距离上次发布过了多久」一并作为特征——因为一个三天前的利率数据与三小时前的含义完全不同(研究口径的常见处理,具体以你的研究设计为准)。
# resample_demo.py —— 事件与评分对齐到 K 线网格(教学示意) def forward_fill(grid_ts, points): """grid_ts: 网格时间戳列表; points: [(时间戳, 值), ...] 按时间升序""" values, last, emitted = [], None, 0 for t in grid_ts: while emitted < len(points) and points[emitted][0] <= t: last = points[emitted][1] # 只用「当时已知」的值 emitted += 1 values.append(last) # 没有新值就沿用上次已知值 return values if __name__ == "__main__": grid = [0, 60, 120, 180, 240] # K 线时间戳(示意) sentiment = [(0, 0.1), (150, 0.6)] # 情绪评分两次更新 print(forward_fill(grid, sentiment)) # 期望:[0.1, 0.1, 0.6, 0.6, 0.6] —— 注意 120 时刻仍是 0.1
这个十行函数是本节最重要的一段代码:points[emitted][0] <= t 这一行的「小于等于」就是时间对齐的全部纪律——在 t 时刻,只能看见 t 之前发布的值。放松成「小于等于加上一点点」,回测立刻变成未来函数。
把这类数据接入策略的三条保守姿势(设计建议,非投资建议):
| 姿势 | 做法 | 避开的坑 |
|---|---|---|
| 过滤器 | 事件密集或情绪极端时,收紧第 5.3 节的风险预算 | 试图用新闻预测方向(噪信比坑) |
| 情境开关 | 只在特定宏观状态下允许某类策略运行 | 全天候赋予同一权重(状态错配坑) |
| 特征降维 | 只取少数经过研究流程检验的特征 | 特征堆量带来的多重检验问题(第 6 章展开) |
把「过滤器」姿势写成代码(教学示意,非投资建议):
# event_feature_demo.py —— 事件计数特征与过滤器判定(纯标准库) def event_count_feature(grid_ts, events, window): """grid_ts: K 线时间戳;events: 事件时间戳升序列表;window: 统计窗口长度""" counts, emitted = [], 0 for t in grid_ts: while emitted < len(events) and events[emitted] <= t: emitted += 1 # 同前文脚本:只用 t 之前的事件 counts.append(sum(1 for e in events[:emitted] if t - e < window)) return counts def risk_budget_scale(count, threshold, floor=0.5): """过滤器示意:事件密集时收缩风险预算系数,不预测方向""" return floor if count >= threshold else 1.0 if __name__ == "__main__": grid = [0, 60, 120, 180] events = [50, 100, 110] counts = event_count_feature(grid, events, window=120) print("事件计数:", counts) print("预算系数:", [risk_budget_scale(c, threshold=2) for c in counts])
两个函数刻意做得很「笨」:计数只看可见事件,缩放只有两档。克制本身就是设计——过滤器一旦变聪明(开始预测方向、开始调阈值),它就悄悄变成了你在第 6 章要为此付出多重检验代价的又一个参数源。缩放系数进入的是第 5.3 节的风险预算,而不是信号本身:事件密集期少下注,而不是反着下。
配套的验证纪律仍然属于第 6 章:任何加入宏观/情绪特征的策略版本,都要与不含该特征的版本同台对比,并接受 walk-forward 与多重检验校正的审视(strategy_evolution 套件,官方口径)。
噪信比:绝大多数新闻与行情变动没有稳定因果关系;能稳定提取的信号远少于直觉预期。滞后:数据可用时刻通常晚于事件发生时刻,策略里必须用「可用时刻」而非「发生时刻」对齐(上文脚本同理)。修订:宏观指标发布后常被修订,库中要区分「首次值」与「修订值」,回测应使用当时可见的首次值序列,否则又是一次隐性偷看。
以修订为例走一遍错法:你今天的回测拉了「当前最新版」宏观序列,里面三月的数值已经是两次修订后的终值;而三月的那个交易日,市场上所有人(包括你的策略)能看到的都是首次值。用终值回测三月,等于给策略开了上帝视角——这类错不会报错、不会崩,只会让你的回测安静地比实盘好看。
一句话立场:这类数据是情境与过滤器,不是水晶球;接入它们的第一课是学会克制。研究纪律的系统训练见《量化投资方法论》第 15 章《批判性思维与研究纪律》。
| 疑问 | 处置 |
|---|---|
| 前向填充后序列开头是 None | 正常:首个数据点之前没有已知值;策略侧要处理 None,不能当 0 |
| 修订值要不要回写历史 | 区分首次值与修订值两套口径,回测用当时可见的首次值 |
| 事件与 K 线总差一根对不齐 | 检查对齐脚本的「小于等于」边界与可用时刻口径 |
| 情绪数据换了供应商还能拼接吗 | 口径不同视为新特征,重走研究与验证流程,不做无缝拼接 |
| 特征到底留几个 | 从少起步;每多一个特征,第 6 章的多重检验代价就多一分 |
至此数据层有了两路输入:高频的行情与慢速的宏观/情绪。多源必然多口径——下一节处理数据层最后的、也是最见功力的一段:时区、精度、符号的统一,以及缺口检测与十项质检清单。