土法炼钢兴趣小组的算法知识备份

【量化交易】另类数据:新闻、舆情、链上数据、卫星图

文章导航

分类入口
quant
标签入口
#alt-data#news#sentiment#on-chain#satellite

目录

传统量化研究的输入是行情和基本面:日 K、Tick、财报、宏观。这些数据有一个共同点——它们以监管机构和交易所规定的节奏被批量发布,所有人同时拿到,价格里早就反映了一遍又一遍。真正难赚的钱,不在”今天的收盘价”里,而在那些没有被定价完整的角落:一篇刚刚挂在公司官网的招聘公告、一艘油轮在霍尔木兹海峡掉头、Tether 凌晨在 Tron 链上增发两亿、一家连锁零售公司停车场卫星图同比少了 30% 的车流。

这一类不来自交易所、不来自财报系统、需要自己动手才能拿到、并且常常带着合规和工程双重门槛的数据,业内统称为另类数据(Alternative Data,简称 Alt Data)。它和传统数据不是替代关系,而是补充关系:另类数据通常颗粒度细、时效性高、覆盖窄、噪声大、成本高、合规复杂;传统数据正相反。把另类数据用好,需要先承认这个事实,再围绕它设计采集、清洗、对齐、版本化的全套工程体系。

这一篇要讨论的就是这一整条战线。范围限定在能在工程上落地的几类:新闻与舆情、社交媒体、卫星与物联网(IoT)、链上数据(On-Chain)、网页与电商爬取、信用卡与电子支付。每一类都会拆成三层来讲:数据本身长什么样、能从哪里拿、工程化时要注意什么。最后专门用一节讨论怎么把这些”形态各异”的数据,整合进与上一篇 《特征仓库与因子治理》 兼容的工业级特征库。

本文不构成投资建议;爬取与个人数据涉及法律边界,请自行评估。文中提到的所有数据源、商业产品、协议接口仅用于说明工程方案,不代表对任何具体厂商、平台、代币的推荐。所有代码段在 Python 3.11 + transformers 4.41 + web3.py 6.20 + pandas 2.2 下验证可运行,仅作演示用,不要在生产环境直接使用。


一、什么是另类数据

1.1 定义边界

工业界使用另类数据这个术语已经超过十年。这里采用一个对工程友好的定义:任何不通过交易所、监管机构、上市公司常规披露渠道直接获得的、需要二次采集或加工才能用于投资决策的数据。这个定义把握住了三个关键点:

  1. 来源在体系外。它不是 Tick、不是日线、不是季报、不是央行数据库的官方表格。
  2. 需要二次加工。原始形态多半不能直接送进因子矩阵。新闻是文本,卫星是图像,链上是字节码,需要中间一层把它转成时间序列或截面数据。
  3. 目的是投资决策。同样一份卫星图,地理学者用来研究城市化是研究数据,被对冲基金用来预测沃尔玛季度营收就是另类数据。

业内常见的细分维度有按主体(机构发布、用户生成、机器/传感器、链上、交易/支付)和按形态(文本、图像、地理位置、网络流量、链上事件)。这两套维度互不冲突,常常组合使用。本文采用前者作为主线,因为它直接对应不同的合规与工程模式。

另类数据分类树

图中下半部分的象限是工程化决策时的核心权衡:alpha 衰减半衰期(横轴)和获取成本/合规复杂度(纵轴)。象限的位置不是绝对的,而是当下市场的经验位置。新闻情感落在右上角是因为它早就被广泛使用、衰减很快,但获取容易;卫星停车场落在左下角是因为公开数据少,要么 Orbital Insight、Planet 这种厂商按月付费,要么自己买原始影像跑 CV 模型。

1.2 与传统数据的真正区别

把另类数据和传统数据并列起来比较,有几个差异是工程上必须正视的:

1.3 alpha 衰减是这一战线的核心规律

另类数据的一个朴素事实是:一类数据从被某只基金独家发现,到被大多数对冲基金都用上、再到散户产品都跟进,时间窗口极短。Eric Falkenstein 等人的实证研究、AQR 的多篇白皮书、López de Prado 在《Advances in Financial Machine Learning》里反复强调的一点是:alpha 不是凭空消失的,是被使用过程一点点磨平的。

新闻情感是衰减最经典的案例。RavenPack 在 2003 年前后开始商业化新闻情感数据,当时使用它的基金能拿到稳定的 IR;到 2015 年前后,几乎所有美股股票多空基金都接入了新闻情感数据,再加上 Twitter API 的开放,文本情感作为一个独立因子的 IR 已经接近 0.2 以下。今天还能从中赚钱的,要么是结合事件分类的细分用法(比如盈利预告、并购传闻、产能事件),要么是结合订单流的事件驱动短线策略,单独的”情感分加权多头”在大多数市场上已经不再赚钱。

链上数据是当下还在窗口期的代表。2018-2021 年间,Glassnode、CryptoQuant 把”交易所余额、巨鲸地址、稳定币流向”系列因子做成商业化产品,BitMEX 资金费率、Coinbase 溢价等结构化指标一度有显著的 alpha。今天币圈头部基金已经普遍使用,但因为 web3 数据对非加密圈仍有较高的认知门槛,相关因子在加密资产横截面上仍有可观的统计显著性。

理解 alpha 衰减的工程含义,是这一篇所有后续讨论的前提。新数据值得投入,但要把”数据源寿命”作为风险管理的一部分写进研究流程。

1.4 一条经验上的判别准则

并不是所有”听起来很另类”的数据都值得投入。判别一个新数据源是否值得做工程化,可以参考下面这条经验准则:先问四个问题。

四个问题至少要有三个明确”是”,再启动工程化。这个判别经验的依据是过去十年另类数据行业的整体落地率:上规模的产品里有大约一半在 12 个月内进入广泛使用并开始 alpha 衰减,大多数没有过这个判别的项目最终沦为”做了一份漂亮 PPT”的成本中心。


二、新闻与舆情

2.1 数据形态与时效

新闻这一类信号,从工程角度可以分成三层:

时间戳处理是新闻数据工程化的第一道关。RavenPack 的字段中专门区分了 timestamp_TMSTAMP_UTCtimestamp_DETECTED_UTCtimestamp_RPNA_DATE_UTC,分别代表事件原始时间、被识别处理的时间、归档日期。研究时如果错把后者当作前者,回测里”我提前 10 分钟看到新闻”和”我落后 30 分钟看到新闻”的结果可以差出几十个 bp。

A 股语境下的几个常见时效陷阱:

2.2 NLP 嵌入与中文情感打分

文本要变成能用的因子,最朴素的两步:分类与嵌入。

分类层面,业内做法有两种主流:基于关键词规则的快速判别、基于预训练模型的细分类。规则法适合”这是不是公告”、“这是不是关于新能源车”这种粗粒度问题,依赖人工词典维护;模型法适合细粒度的事件类型识别,比如把”盈利预告超预期 / 减持公告 / 关联交易”做成多分类。

嵌入层面,自从 BERT 出现,文本表示已经是把句子或者段落映射到一个向量。中文金融文本上常见的开源模型:

下面给出一段最小可运行的代码,用 transformers 跑批量中文新闻的情感分。这段代码不追求工程化,仅演示如何把一份新闻 DataFrame 转成情感时间序列:

import pandas as pd
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

MODEL = "IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment"
device = "cuda" if torch.cuda.is_available() else "cpu"

tokenizer = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForSequenceClassification.from_pretrained(MODEL).to(device).eval()

LABELS = {0: "neg", 1: "neu", 2: "pos"}

@torch.no_grad()
def score_batch(texts, max_len=256, batch_size=32):
    """对一批中文文本计算情感概率。返回 DataFrame: [neg, neu, pos]。"""
    out = []
    for i in range(0, len(texts), batch_size):
        chunk = texts[i:i + batch_size]
        enc = tokenizer(
            chunk, return_tensors="pt", padding=True,
            truncation=True, max_length=max_len,
        ).to(device)
        logits = model(**enc).logits
        probs = torch.softmax(logits, dim=-1).cpu().numpy()
        out.append(probs)
    import numpy as np
    arr = np.concatenate(out, axis=0)
    return pd.DataFrame(arr, columns=["neg", "neu", "pos"])

def aggregate_to_daily(news_df: pd.DataFrame) -> pd.DataFrame:
    """
    news_df 必须包含: stock_id, ts (UTC timezone-aware), title, body
    返回按 (stock_id, date) 聚合的 sentiment 因子。
    """
    text = (news_df["title"].fillna("") + "。" + news_df["body"].fillna("")).tolist()
    scores = score_batch(text)
    df = pd.concat([news_df.reset_index(drop=True), scores], axis=1)

    # 关键: 用文章时间戳所属交易日,而不是入库日
    df["date"] = df["ts"].dt.tz_convert("Asia/Shanghai").dt.date

    agg = df.groupby(["stock_id", "date"]).agg(
        n_news=("title", "size"),
        sent=("pos", "mean"),
        sent_neg=("neg", "mean"),
        # 加权: 标题往往更具信号
        sent_w=("pos", lambda s: (s * (df.loc[s.index, "title"].str.len().clip(0, 64))).sum()
                / df.loc[s.index, "title"].str.len().clip(0, 64).sum().clip(min=1)),
    ).reset_index()
    return agg

这段代码的几个工程要点值得展开:

2.3 中文舆情的几个特殊性

A 股的舆情数据有一些和美股显著不同的点:

工程实现上,建议把情感打分和事件分类做成两条独立的流水线。前者输出连续值,后者输出离散事件标签。最终因子可以是两者的组合:在没有事件触发时使用情感分平滑值;事件触发时切到事件因子。

2.4 事件研究的标准模板

新闻类因子真正能挖到 alpha 的地方,是事件研究(Event Study)。事件研究的标准模板可以归纳成下面几步:

  1. 事件标定。先根据规则或模型把每条新闻打成事件类型标签,例如:盈利预告超预期、并购公告、监管处罚、产能扩张、高管变动、回购公告、可转债公告、股东增减持。每个事件类型都要有清晰的纳入与排除规则。
  2. 事件窗与估计窗。事件窗(event window)通常是 \([-T_1, +T_2]\),比如 \([-1, +5]\) 个交易日;估计窗(estimation window)取事件前更长一段,比如 \([-120, -10]\),用于估计正常收益(normal return)的基准模型。
  3. 正常收益建模。最简单是市场模型:\(R_{i,t} = \alpha_i + \beta_i R_{m,t} + \varepsilon_{i,t}\),在估计窗内估出 \(\alpha, \beta\),再外推到事件窗。更精细的可以用 Fama-French 三因子或五因子。
  4. 异常收益与累积\(AR_{i,t} = R_{i,t} - \hat{R}_{i,t}\);累积异常收益(CAR)\(CAR_i = \sum_{t \in event} AR_{i,t}\)
  5. 统计检验。横截面 t 检验、Patell 标准化检验、BMP(Boehmer-Musumeci-Poulsen)检验都常用。要警惕事件聚集(cross-sectional dependence)导致的标准误低估。

A 股语境下,事件研究里几个经常踩坑的地方:

2.5 来源加权与时间衰减

实际生产中,单条新闻的情感分往往不直接喂给模型,而是先聚合成”过去 N 小时的滚动情感分”,并按下面两个权重做加权:

下面这个函数把来源加权和时间衰减串起来,作为前面 aggregate_to_daily 的工业化版本:

import numpy as np
import pandas as pd

SOURCE_WEIGHT = {
    "bloomberg": 1.0, "reuters": 1.0, "xinhua": 0.9,
    "cs": 0.8, "stcn": 0.8, "cnstock": 0.8,
    "cls": 0.7, "wallstreetcn": 0.7, "yicai": 0.7,
    "xueqiu_official": 0.5, "weibo_finance": 0.4,
    "self_media": 0.3,
}

def rolling_sentiment(news_df: pd.DataFrame, half_life_hours: float = 6.0) -> pd.DataFrame:
    """
    news_df: 已经包含 [stock_id, ts(UTC), source, pos, neg, neu]
    输出: 每分钟一档的滚动情感分。
    """
    df = news_df.copy()
    df["sw"] = df["source"].map(SOURCE_WEIGHT).fillna(0.2)
    df["raw"] = df["pos"] - df["neg"]
    df = df.sort_values(["stock_id", "ts"])

    lam = np.log(2) / (half_life_hours * 3600)
    out = []
    for sid, g in df.groupby("stock_id"):
        ts = g["ts"].view("int64") // 10**9
        score = g["raw"].to_numpy() * g["sw"].to_numpy()
        # O(N) 指数加权前缀
        ema = np.zeros_like(score, dtype=float)
        prev_t = ts.iloc[0]; prev_v = 0.0
        for i, (t, s) in enumerate(zip(ts, score)):
            decay = np.exp(-lam * (t - prev_t)) if i > 0 else 1.0
            prev_v = prev_v * decay + s
            ema[i] = prev_v
            prev_t = t
        g = g.assign(ema=ema)
        out.append(g)
    return pd.concat(out, ignore_index=True)

这段实现的几个细节:

2.6 中文金融文本上微调情感模型的几个建议

通用情感模型(Erlangshen、bge-zh、阿里通义、百度文心)在金融文本上常见的几类系统性误判:

工程上修这类问题的标准做法是构造一份 5000-20000 条的金融文本人工标注数据,做 LoRA 或全参数微调。标注规范要把”情感”和”事件类别”分开两个字段,模型多任务训练。在中文金融语料上的实证,多任务 LoRA 比单任务全参微调收敛更快、过拟合更少。


三、社交媒体

3.1 Twitter(X)、Reddit、雪球的角色定位

社交媒体作为另类数据,本质是把”市场关注度”和”零售情绪”显式化。它的信号强度有两个上限:

第一个上限是参与人群。推特覆盖的金融讨论以美股为主,A 股极少;雪球、股吧覆盖 A 股;Reddit 的 r/wallstreetbets(WSB)覆盖美股散户;Discord 和 Telegram 是币圈的主战场。任何跨市场推论都要谨慎。

第二个上限是话题特性。社交媒体放大的是”易于传播的故事”,复杂业务、长周期投资、价值投资标的天然没声音。这些声音对应的可观测量级,主要是”meme 行情”——以 GameStop(GME)、AMC、BBBY 为代表的美股零售股逼空行情。

从因子化角度,社交媒体的可用信号有:

3.2 数据采购与采集的现实

2023 年 X(Twitter)API 改革后,免费层基本无法用于学术研究和商业研究。Basic 层 100 美元/月、Pro 层 5000 美元/月、Enterprise 层数万美元起。三方厂商如 Bright Data、Apify 提供爬取服务,但合规风险由使用方承担。学术研究领域的 Decahose 也已经停止。这导致 2023 年之后的 Twitter 数据可得性比 2020 年差了一个数量级。

Reddit 通过 Pushshift 历史归档曾是最大的开放数据源,2023 年 Reddit 收紧 API 后 Pushshift 公共服务关停,目前只有 academictorrents 上的旧归档可用,新数据需要自己通过 Reddit API(按调用付费)抓。

雪球的开放程度低于美国同类平台,没有官方 API。研究者通常通过爬虫采集,但需要注意《雪球用户协议》《数据安全法》《个人信息保护法》对个人发表内容的合规要求。雪球用户的发帖历史属于”用户公开发布的信息”,对其做聚合统计的法律边界相对清晰;但批量获取、转售、用于个人 ID 画像则越界。

工程化采集时,下面几条是底线:

3.3 meme 行情的统计特征

GameStop 在 2021 年 1 月 27 日的逼空把”meme 行情”这个概念推到了所有量化研究者面前。事后多篇论文(如 Pedersen 2022、van der Beck & Jaunin 2021)整理出了几个稳定的统计特征:

把这些做成因子并不难,难的是把它做成风险管理工具而不是 alpha 来源——因为 meme 标的本身的容量极小,空头一旦被卷入,损失可以是无限的。Melvin Capital 的清盘是最直接的反面教材。

3.4 一个实用的简化指标

下面给一个最简的”市场关注度异常”指标,核心思路是把单日提及量除以过去 N 日的均值,再做对数化处理:

import numpy as np
import pandas as pd

def attention_score(mention_df: pd.DataFrame, lookback: int = 20) -> pd.DataFrame:
    """
    mention_df: columns = [date, stock_id, mentions]
    返回每日的关注度异常分。
    """
    df = mention_df.sort_values(["stock_id", "date"]).copy()
    df["ma"] = (
        df.groupby("stock_id")["mentions"]
          .transform(lambda s: s.rolling(lookback, min_periods=5).mean())
    )
    # 加 1 平滑,避免除零
    df["ratio"] = (df["mentions"] + 1) / (df["ma"] + 1)
    df["score"] = np.log(df["ratio"]).clip(-3, 3)
    # 横截面 z-score: 每日做一次
    df["score_xs"] = df.groupby("date")["score"].transform(
        lambda s: (s - s.mean()) / s.std(ddof=0).clip(min=1e-6)
    )
    return df[["date", "stock_id", "score", "score_xs"]]

这个指标的几个边界条件:

3.5 散户结构变化对信号有效性的影响

社交媒体类信号有一个常被忽略的特征:它的有效性强烈依赖于”散户在该市场的占比”。美股零售投资者占成交量约 20-25%(2021 年峰值约 30%),A 股零售投资者占成交量长期在 60-80%。直觉上 A 股的社媒信号应该更强,但实际上并不是这样:

这说明社媒类因子在不同市场上的应用方式不能照搬。在 A 股,把社媒数据作为”龙虎榜信号的辅助过滤”比单独使用更稳健;在美股,把 WSB / Twitter 信号与 Robinhood 持仓数据(已停止公开)的等价替代结合,效果更接近原始研究。


四、卫星与物联网

4.1 数据形态

卫星数据这条线有几个核心量:

4.2 几类经典用法

停车场车流量估算大型连锁商超的客流。Orbital Insight 在 2015-2018 年靠这个对沃尔玛、Costco、Home Depot 季度营收做提前预测,平均提前期 4-6 周。用 CNN 在卫星图像上数车,理论上简单,但工程上要解决遮挡(树荫、建筑阴影)、季节性变化、节假日效应、多云覆盖率等一系列问题。今天这个 alpha 在头部对冲基金间已经被广泛使用,单独用作多空已经磨平大半。

油轮跟踪估算原油库存与流向。AIS 数据加上船型库(DWT、Beam)可以反推某条航线的总运力变化,进一步推算到港、卸货、库存。Kpler、Vortexa 是商业化代表。这条线对应的因子在原油、成品油的方向性投机和 Brent-WTI 价差交易上仍有可用性,特别是地缘政治冲突期间,比如 2022 年俄乌战争后的”暗船队”流向跟踪。

夜光增长率估算地区经济活动。Henderson、Storeygard、Weil(2012)在 American Economic Review 上系统验证了夜光强度与 GDP 增长的相关性,相关系数在跨国面板上约 0.3-0.6。在中国应用上,夜光对县级经济活动跟踪的有效性被多篇国内学者文章验证。但这个信号衰减极慢——它捕获的是低频结构性信息,alpha 来源更多在 EM 资产配置上,而不是个股层面。

农作物长势通过 NDVI(Normalized Difference Vegetation Index)跟踪。USDA 的 CDL(Cropland Data Layer)每年更新一次,结合 Sentinel-2 影像可以估算大豆、玉米、小麦的长势异常,对农产品期货的方向性头寸有效。

工业开工率。钢厂、水泥厂、炼油厂的烟羽热信号在热红外波段可观测。Sentinel-3 SLSTR 和商业热红外卫星(如 SatVu)能反推产能利用率,对应大宗商品供给侧研究。

集装箱港口堆场。盐田港、洛杉矶/长滩港、鹿特丹港的集装箱堆放面积反映贸易景气。2021 年全球供应链危机期间,这一指标比官方港口运量数据提前 4-6 周。

4.3 采购与处理的现实

卫星数据的成本结构和大多数另类数据不同:原始数据成本高、处理成本更高。一份 0.5 米分辨率的 PlanetScope 影像,按平方公里计费可能在数美元到数十美元。要覆盖美国 1000 家沃尔玛门店一年 52 周的影像,原始数据成本就是六位数美元。再加上 GPU 训练 CNN 数车的算力成本、数据科学家工资,一个工业级停车场监测系统的年成本通常在七位数美元。

这个成本意味着:除非已经验证 alpha 显著且容量足够大,不要自建卫星图像处理流水线。中小型基金的现实选择是直接采购处理后的指标数据:

物联网这条线的分散性更明显。一个常见实践是和数据 broker 合作,比如 Cuebiq、SafeGraph 提供基于手机定位(在用户授权的前提下)的客流数据,可以替代部分停车场卫星图。但 2022 年之后随着 Apple ATT(App Tracking Transparency)和 GDPR 监管收紧,这一类数据的覆盖率下降明显。

4.4 工程上的几个共性问题

不管来源是哪种,处理这一类数据时几个共性问题:

4.5 一个停车场客流的最小处理范例

为了让上面的讨论落到工程实操层面,下面给一个”停车场客流”的简化处理流程。完整工业实现要复杂得多,这里抽出关键骨架,便于理解每一步在做什么:

import numpy as np
import pandas as pd
from datetime import timedelta

def normalize_parking(raw: pd.DataFrame) -> pd.DataFrame:
    """
    raw columns: [date, store_id, image_id, vehicle_count, cloud_cover, sat_id]
    返回: 按 (date, store_id) 归一化后的客流强度。
    """
    df = raw.copy()
    # 1. 云覆盖率 >= 30% 视为不可用
    df = df[df["cloud_cover"] < 0.30]
    # 2. 多颗卫星同日数据取中位数,避免单卫星偏差
    df = df.groupby(["date", "store_id"], as_index=False)["vehicle_count"].median()
    # 3. 季节基线: 同 store 过去 52 周同周几的中位数
    df["dow"] = pd.to_datetime(df["date"]).dt.dayofweek
    df = df.sort_values(["store_id", "date"])
    df["baseline"] = (
        df.groupby(["store_id", "dow"])["vehicle_count"]
          .transform(lambda s: s.shift(1).rolling(52, min_periods=12).median())
    )
    # 4. 异常: log(vehicle / baseline)
    df["anomaly"] = np.log((df["vehicle_count"] + 1) / (df["baseline"] + 1))
    df["anomaly"] = df["anomaly"].clip(-2, 2)
    return df

def aggregate_to_brand(parking: pd.DataFrame, store_to_brand: pd.DataFrame) -> pd.DataFrame:
    """
    把门店级异常聚合到品牌级,权重为门店历史中位车流量。
    """
    merged = parking.merge(store_to_brand, on="store_id", how="inner")
    weights = (
        parking.groupby("store_id")["vehicle_count"].median().rename("w")
    )
    merged = merged.merge(weights, on="store_id", how="left")
    merged["wx"] = merged["anomaly"] * merged["w"]
    out = (
        merged.groupby(["date", "stock_id"])
              .agg(brand_signal=("wx", "sum"), w_sum=("w", "sum"))
    )
    out["brand_signal"] = out["brand_signal"] / out["w_sum"].clip(lower=1)
    return out.reset_index()[["date", "stock_id", "brand_signal"]]

注意几个隐含假设:


五、链上数据

5.1 链上数据的工程含义

加密资产的最大特点是结算系统本身就是公开数据库。每一笔转账、每一个合约调用、每一次状态变更都被记录在区块链上,可以被任何人重放。这让链上数据从工程上区别于其他另类数据——它不是”被采集到的”,是”原本就在那里、需要解码”。

但”原本就在那里”不等于”容易拿到”。要从原始链上数据得到有用的因子,要经过下面这条流水线:

链上数据采集与因子化架构

L1(数据源层)。完整数据需要归档节点(Archive Node),它存了从创世块到当前块的全部状态。Erigon 在以太坊主网当前需要约 8 TB SSD,Geth 需要更多。如果只关心交易和事件不关心历史状态,全节点(Full Node)就够,需要约 1.5 TB。中小研究团队的现实选择是租用 Alchemy、Infura、QuickNode 的 RPC 服务,按调用计费。

L2(同步与索引层)。把链上数据从节点同步到本地数据库。开源工具链中,cryo(Paradigm 出品)、ethereum-etlsubsquid 是常用选择。它们把 block、tx、receipt、log、trace 从节点导出成 Parquet 或 PostgreSQL,方便后续 SQL 查询。

L3(解码与聚合层)。原始事件日志只是 (address, topic0, topic1, topic2, topic3, data),要变成”USDT 从 A 转到 B 金额 X”需要 ABI 反序列化,ERC20、Uniswap V2/V3、Aave、Compound 各有自己的事件签名。地址聚类(Address Clustering)是把同一个实体(人或机构)控制的多个地址识别出来,常用启发式是 common-spend:同一笔交易的所有输入归属于同一实体(在 UTXO 模型上的 Bitcoin 直接适用,EVM 上需要更复杂的启发式)。

L4(因子层)。把聚合后的指标对齐到时间序列,进入特征仓库。链上常见因子:活跃地址数、NVT(Network Value to Transactions)、MVRV(Market Value to Realized Value)、稳定币净发行、交易所净流入、DEX 滑点曲线。

5.2 几个有信号价值的链上指标

交易所净流入。把所有已知归属于中心化交易所(CEX)的地址作为一个集合,统计每个区块内净流入(流入-流出)的总量。链上传统观点是:净流入大幅上升预示卖压增加,净流出预示惜售。Coinbase、Binance、Kraken 的标签数据是 Etherscan、Arkham、Nansen 等公司维护的核心资产之一。

稳定币净发行。Tether(USDT)、Circle(USDC)的发行和销毁是链上可观测的。USDT 在 Tron 上的增发往往和加密市场情绪同步,是宏观流动性指标。要小心的是 USDT 的发行不一定立即进入交易,可能滞留在发行方钱包数小时到数天。

巨鲸地址行为。把余额超过特定阈值(比如 BTC 1000 枚以上)的地址作为巨鲸集合,监控其转账行为。巨鲸向交易所大额转入往往是抛售前奏,但反向解读也常见——很多大额转账是冷热钱包内部调度,需要结合标签判断。

DEX 池子状态。Uniswap V3 的 slot0liquidity 在每次 swap 后都会更新,可以推算池子的实时滑点曲线。Curve 的 stableswap pool 偏移量可以监控稳定币失锚风险——2023 年 3 月硅谷银行事件后 USDC 短暂脱锚到 0.87,3pool 状态先于 CEX 价格出现剧烈偏移。

MEV 与三明治攻击。Mempool 中的待打包交易可被搜索者(searcher)用于抢跑。Flashbots 提供的 mev-inspect 数据集可以系统识别 sandwich、frontrun、arbitrage 三类 MEV。在做 DEX 交易策略时,MEV 暴露是必须考虑的一阶成本。

5.3 web3.py 拉链上指标

下面给一段最小可运行代码,用 web3.py 通过 RPC 拉取以太坊上某个 ERC20 代币的转账事件,并做最简单的”交易所净流入”统计。这段代码不带缓存、不带去重、不带容错,仅作演示:

import os
from typing import List, Set
import pandas as pd
from web3 import Web3
from eth_utils import to_checksum_address

RPC_URL = os.environ["ETH_RPC_URL"]   # e.g. https://eth-mainnet.g.alchemy.com/v2/<key>
USDC = to_checksum_address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

# 已标记为 CEX 热钱包的地址子集(演示用,生产应使用更全面的标签库)
CEX_HOT_WALLETS: Set[str] = {
    to_checksum_address("0x28C6c06298d514Db089934071355E5743bf21d60"),  # Binance 14
    to_checksum_address("0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549"),  # Binance 15
    to_checksum_address("0x71660c4005BA85c37ccec55d0C4493E66Fe775d3"),  # Coinbase 1
    to_checksum_address("0x503828976D22510aad0201ac7EC88293211D23Da"),  # Coinbase 2
}

w3 = Web3(Web3.HTTPProvider(RPC_URL))

def fetch_transfers(token: str, start: int, end: int, chunk: int = 2000) -> pd.DataFrame:
    """按区块分片拉 Transfer 事件,返回 DataFrame。"""
    rows = []
    for fr in range(start, end, chunk):
        to = min(fr + chunk - 1, end)
        logs = w3.eth.get_logs({
            "fromBlock": fr,
            "toBlock": to,
            "address": token,
            "topics": [TRANSFER_TOPIC],
        })
        for log in logs:
            from_addr = to_checksum_address("0x" + log["topics"][1].hex()[-40:])
            to_addr = to_checksum_address("0x" + log["topics"][2].hex()[-40:])
            value = int(log["data"], 16)
            rows.append((log["blockNumber"], log["transactionHash"].hex(),
                         from_addr, to_addr, value))
    df = pd.DataFrame(rows, columns=["block", "tx", "from", "to", "value"])
    df["value"] = df["value"] / 1e6  # USDC 是 6 位小数
    return df

def cex_net_inflow(transfers: pd.DataFrame, cex: Set[str]) -> pd.DataFrame:
    """CEX 净流入 = 流入 CEX - 从 CEX 流出。"""
    inflow = transfers[transfers["to"].isin(cex)].groupby("block")["value"].sum()
    outflow = transfers[transfers["from"].isin(cex)].groupby("block")["value"].sum()
    net = (inflow.subtract(outflow, fill_value=0)
                  .rename("cex_net_inflow").reset_index())
    return net

# 用法示例
# tail = w3.eth.block_number
# transfers = fetch_transfers(USDC, tail - 200, tail)
# net = cex_net_inflow(transfers, CEX_HOT_WALLETS)

这段代码在工程上要补足的点:

5.4 链上数据的几个重要陷阱

5.5 一个稳定币流向的因子样例

下面给一个常用因子的最小实现:稳定币(USDT/USDC)从交易所流向 DeFi 协议的净规模。这个因子在加密资产横截面上反映”链上加杠杆意愿”,是研究 DeFi TVL 与币价 lead-lag 关系的常用输入:

import pandas as pd

DEFI_PROTOCOL_LABELS = {
    # Uniswap V3 router
    "0xE592427A0AEce92De3Edee1F18E0157C05861564": "uniswap_v3",
    # Aave V3 pool
    "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2": "aave_v3",
    # Compound V3 USDC
    "0xc3d688B66703497DAA19211EEdff47f25384cdc3": "compound_v3",
    # Curve 3pool
    "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7": "curve_3pool",
}

def stablecoin_to_defi(transfers: pd.DataFrame, cex: set, defi: set) -> pd.DataFrame:
    """
    transfers: USDT/USDC Transfer 事件,包含 [block, from, to, value, ts]
    返回: 每日 CEX -> DeFi 净流入金额。
    """
    df = transfers.copy()
    df["leg"] = "other"
    df.loc[df["from"].isin(cex) & df["to"].isin(defi), "leg"] = "cex_to_defi"
    df.loc[df["from"].isin(defi) & df["to"].isin(cex), "leg"] = "defi_to_cex"

    df["date"] = pd.to_datetime(df["ts"], unit="s").dt.floor("D")
    pivoted = df.pivot_table(
        index="date", columns="leg", values="value",
        aggfunc="sum", fill_value=0,
    )
    pivoted["net_to_defi"] = (
        pivoted.get("cex_to_defi", 0) - pivoted.get("defi_to_cex", 0)
    )
    return pivoted.reset_index()

工程上的几点说明:


六、网页与电商

6.1 这一战线能做什么

爬取网页是另类数据最古老、最广泛的方式。对量化研究有价值的几类:

6.2 合规边界

爬取的合规边界是这一战线的核心问题。中文语境下需要同时关注:

历史上有几个标志性案例值得理解边界:

工程实施上的几条底线:

6.3 几个工程上的共性套路

爬虫流水线的工程模式相对成熟:

6.4 招聘数据的一个简单建模

招聘数据是网页类数据中信号噪声比较高的一类。下面给一个在 A 股语境下用过的简化建模:

import pandas as pd
import numpy as np

def hiring_growth_factor(jobs_df: pd.DataFrame, window: int = 90) -> pd.DataFrame:
    """
    jobs_df columns: [date, stock_id, n_open_jobs]
    返回: 招聘异常增长因子,按日横截面 z-score。
    """
    df = jobs_df.sort_values(["stock_id", "date"]).copy()
    g = df.groupby("stock_id")
    df["base"] = g["n_open_jobs"].transform(
        lambda s: s.shift(1).rolling(window, min_periods=30).median()
    )
    df["growth"] = (df["n_open_jobs"] - df["base"]) / df["base"].clip(lower=1)
    df["growth"] = df["growth"].clip(-1, 5)

    df["zscore"] = df.groupby("date")["growth"].transform(
        lambda s: (s - s.median()) / (s.std(ddof=0).clip(min=1e-6))
    )
    return df[["date", "stock_id", "growth", "zscore"]]

要点:

6.5 三个值得长期关注的网页数据子方向

工程化能落地、合规风险相对可控、长期具有信号价值的子方向,按经验排序:

不建议长期投入的子方向:


七、信用卡与电子支付

7.1 美国市场的成熟数据源

美国对冲基金能用的支付类另类数据源已经相当成熟:

这些数据的使用范式是:基金按月或按年付订阅费,得到去标识化的聚合时间序列,按商家或品牌跟踪营收同比。麦当劳的销售季报发布前一周到三周,就可以从这些数据里看到大致方向。AlphaSense、AlternativeData.org 维护过这一类厂商的目录。

实证上,Froot 等多位学者的研究显示:信用卡数据对美国消费类股票的季报营收预测,在事件前 4 周相关系数能稳定在 0.4-0.7。一些头部对冲基金(如 Citadel、Two Sigma、PointSeven)2015-2018 年间从这里获得了显著的 alpha,到 2020 年后已经基本被广泛使用,alpha 衰减明显。

7.2 中国市场的可得性

中国市场上类似数据的可得性比美国差得多,原因是结构性的:

这意味着 A 股研究中”信用卡级消费数据”基本不可得。可替代的手段是:

合规上需要注意,《个人信息保护法》对消费数据的处理要求显著严于美国 CCPA,未经用户授权获取消费明细在中国是明确违法的。任何声称提供”信用卡级消费数据”的国内来源,都需要追溯其原始授权链路。

7.3 工程化使用的几个原则

不管是用美国的 Yodlee 还是中国的替代源,几条工程化原则通用:

7.4 nowcast 的标准建模

信用卡数据最常见的应用是季度营收 nowcast。建模标准模板如下:

  1. 特征构造\(x_{i,t}\) 是公司 \(i\) 在季度 \(t\) 内累计到当前的信用卡支出指标(同比变化或环比变化),按周或按日更新。
  2. 目标变量\(y_{i,t}\) 是公司公布的季度营收同比变化,事后才知道。
  3. 滚动训练。在每个截面日,用过去 \(K\) 个季度的 \((x, y)\) 配对样本训练一个线性或 GBM 模型,外推当前季度的 \(\hat{y}_{i,t}\)
  4. 校准。把 \(\hat{y}\) 与 sell-side consensus 比较,差值作为 surprise 因子。
  5. 事件应用。临近财报发布日,按 surprise 排名做横截面多空,事件后清仓。

实证上这套模板在 2015-2018 年间的美国零售股、餐饮股上的多空收益最高能到年化 8-12%。2020 年后随着数据广泛使用,alpha 衰减到年化 2-4%,但依然显著。这种”先看到、再消失”的轨迹是另类数据 alpha 衰减的标准曲线。

值得提醒的是:信用卡数据并不能等同于公司全部营收。Yodlee 等聚合源覆盖的样本是有偏的(年轻、城市、网购占比高),与实际营收存在系统性偏移。把这种偏移当作误差忽略可以,但不能假设它是常数——在疫情、节假日、促销季它会显著变化,回归模型如果不处理这一点会出现稳定的预测偏差。


八、Alt 数据的工程化

最后这一节回到工程问题:怎么把上面这些形态各异的数据,整合到一个能稳定支撑研究、回测、信号生产的体系里。这里的”工程化”不是一句”上 Kafka 就行”能糊弄过去的,要解决五个具体问题:清洗、嵌入、对齐、版本化、与传统因子整合。

8.1 清洗

另类数据的清洗规则按数据形态分:

文本类

图像类

链上

数值类(信用卡、招聘)

8.2 嵌入

文本和图像在送入因子模型前,要先做表征学习。常见做法:

嵌入向量入库后,可以走两条路:直接作为高维特征进 LightGBM/XGBoost;或者先用 PCA/UMAP 降维,再做线性因子。前者预测力强、可解释性差,后者可解释性强、预测力弱。

8.3 对齐

对齐是这条流水线最关键的工程问题。Point-In-Time(PIT)原则要求:在任意 t 时刻,研究能看到的因子值必须等价于”如果系统当时已上线,t 时刻它会输出什么”。另类数据的 PIT 比传统财报数据更难,因为:

工程上必须把”事件时间”(event time)和”采集时间”(ingestion time)分离,并显式记录两个时间戳。所有回测查询都按”在采集时间 ≤ t 的数据中,事件时间 = t’ 的最新版本”做。这就是上一篇 《特征仓库与因子治理》 里讨论的 bitemporal 表的标准用法。

8.4 版本化

另类数据流水线有四个层次的版本要追踪:

只要这四层有一个版本变了,下游回测就有”今天的数据和昨天的数据是同一组吗”的隐患。规范的做法是把版本元数据做成不可变记录,每条特征行都带 (data_version, parser_version, model_version, feature_version) 四元组。

8.5 与传统因子库整合

另类数据因子最终要和传统因子(动量、价值、规模、波动率、流动性)放在一起做组合优化、风险归因。整合时几个工程模式:

8.6 一段串起来的最终样例

下面这段代码把上面几节连起来:从原始新闻 + 链上 USDT 流入两路数据,经过情感打分和聚合,落到一份带版本元数据的 Parquet,模拟最终入特征库的形态:

import pandas as pd

def write_feature_partition(
    df: pd.DataFrame,
    feature_name: str,
    data_version: str,
    parser_version: str,
    model_version: str,
    feature_version: str,
    out_dir: str,
):
    """把因子按 (date, feature_name) 分区写到 Parquet。"""
    required = {"date", "stock_id", "value", "ingestion_ts"}
    assert required.issubset(df.columns), f"missing cols: {required - set(df.columns)}"

    df = df.copy()
    df["feature_name"] = feature_name
    df["data_version"] = data_version
    df["parser_version"] = parser_version
    df["model_version"] = model_version
    df["feature_version"] = feature_version

    # 按日期分区,方便增量加载
    for d, sub in df.groupby("date"):
        path = f"{out_dir}/feature={feature_name}/date={d}/part.parquet"
        sub.to_parquet(path, index=False)

# 用法
# news_factor = aggregate_to_daily(news_df).rename(columns={"sent_w": "value"})
# news_factor["ingestion_ts"] = pd.Timestamp.utcnow()
# write_feature_partition(
#     news_factor[["date", "stock_id", "value", "ingestion_ts"]],
#     feature_name="news_sent_w",
#     data_version="cls-financial-2025Q1",
#     parser_version="parser_v3.2",
#     model_version="erlangshen-roberta-330m-2024-08",
#     feature_version="v1.0",
#     out_dir="/data/features",
# )

这段写入逻辑虽然简单,背后承担的是”任何下游回测都能精确追溯到当时使用的数据源版本和模型版本”。一旦研究者在 2027 年想重现 2025 年的某次回测,他需要的就是这套元数据。没有它,alt-data 就会沦为”看着像能用、谁也不能保证它当时输出过什么”的不可信资产。

8.7 监控与回滚

另类数据流水线和传统数据流水线最大的差别在于”故障是常态,不是异常”。一个工业级系统至少要监控下面几类指标:

回滚机制必须双向。常规情况下数据按”事件时间 + 采集时间”递增写入;发现历史数据有错误时,要支持按 (data_version, time_range) 维度做回滚和重算,并把”重算前的版本”和”重算后的版本”都保留,方便回测端选择。

8.8 alt-data 流水线的最小可用清单

把这一篇所有讨论收束成一份”最小可用清单”,给准备启动这条战线的团队:

  1. 选定 1-2 类数据源,明确法律合规边界,签合同或确认许可范围。
  2. 搭原始数据落库:分区策略以 (source, event_date) 为主键,原始 payload 和解析字段双写。
  3. 实现解析器版本化和模型版本化。版本元数据进入每条特征行。
  4. 实现 ingestion_ts 与 event_ts 双时间戳,所有下游查询走 PIT 接口。
  5. 把 universe 和 trading calendar 与传统因子库共享。
  6. 监控覆盖率、延迟、漂移、去重率、解析错误率五大指标,自动报警。
  7. 回测前置一个 alpha 衰减自检:过去 1/3/12 个月 IC vs 全样本 IC 对比报告。
  8. 风险归因接口预留:因子载荷可以喂给 Barra 风格风险模型。
  9. 文档化数据字典:每个字段的来源、更新频率、修订规则、缺失含义、单位、时区。
  10. 灾备:原始数据备份至少两个独立位置,避免供应商单点失效。

走完这十步,alt-data 才算真正变成可以放心给研究员用的”基础设施”。在此之前任何”alpha 来自另类数据”的说法都需要打折听。

8.9 团队组织上的几个观察

最后从团队组织角度补一个观察:alt-data 这条战线对组织能力的要求和传统量化团队不同。它需要”工程能力 ≈ 研究能力”的双重投入,而不是”工程为研究服务”的单向支持关系。

具体表现:

具备这些条件的团队,alt-data 才能真正成为长期资产;否则,做出来的更多是”给 LP 看的故事”,而不是能赚钱的因子。


我对这一战线的看法

另类数据这条线最常见的两类错误:一是过度乐观——觉得只要拿到独家数据,alpha 就来了。事实是数据采购成本只占总成本的小头,工程化、清洗、对齐、版本化的工作量是数据本身的 5-10 倍,而 alpha 衰减意味着即便走完所有这些工作,得到的因子半衰期也常常以季度为单位。二是过度保守——觉得另类数据”门槛高、不规范、合规风险大”,于是干脆不碰。这同样是错的,因为传统数据上的 alpha 已经被几代量化研究者磨得很薄,在一个量化竞争已经如此激烈的市场里不去开辟新数据维度,等于把战场限定在所有人都已饱和的区域。

正确的姿态是把另类数据当成基础设施来建:选两到三类自己有能力 sustain 的数据源(比如新闻 + 链上,或者卫星 + 招聘),花 6 到 12 个月把它从原始数据到特征库的全链路打通。第一个版本不要奢望产生显著 alpha,目标是让流水线”稳定运行、PIT 严格、版本可追溯”。在这个基础上做因子研究和事件驱动策略,比起”去采购一份贵的数据再发现没法落地”健康得多。

A 股语境下的特殊性更明显:链上数据天然不适用,信用卡级数据基本不可得,卫星和物联网在合规与采购成本上门槛较高,新闻和舆情、招聘是最现实的起点。把这两条线做扎实,已经能把整个研究体系的输入端拉开和大多数同行的差距。

最后一点:另类数据不是孤立的”加分项”,而是会反过来重塑研究流程的”基础设施变更”。一旦把链上 / 新闻 / 招聘任何一类数据真正接入特征仓库,研究员的提问方式都会随之变化——从”这个因子是不是显著”变成”这个事件类型在过去 5 年的累积异常收益是不是稳健”。后者的提问方式天然更接近真实交易,也更难被同行以纯数学手段复制。这才是另类数据这条战线值得长期投入的根本原因。


风险提示

本文讨论的是另类数据的采集、处理、工程化方法,不构成任何投资建议。所有具体数据源、商业产品、协议接口、代币标的、交易所均为公开信息列举,不代表对任何具体厂商、平台、代币或服务的推荐。爬取网页、调用第三方 API、处理用户生成内容涉及《数据安全法》《个人信息保护法》《反不正当竞争法》及目标平台用户协议的合规边界,使用者须自行评估并承担法律责任。文中代码均为最小可运行示例,未经工业级压力测试,不要在生产环境直接使用。文中提到的链上代币、加密资产、衍生品具有高波动性,可能导致全部本金亏损。任何实盘使用都需自行承担风险,包括但不限于:模型对未来分布失效、数据源中断、合规边界变化、极端事件下系统不可用。


九、参考资料

论文

书籍

规范与文档

数据源与工具


上下篇导航

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-05-01 · quant

【量化交易】量化交易全景:从信号到订单的工程链路

量化交易不是策略写得好就能赚钱,更难的是把数据、特征、因子、信号、组合、执行、风控、复盘这八段链路在工程上连成一条不漏数据、不串时间、不丢订单的流水线。本文是【量化交易】系列的总目录与读图,给出八段链路的输入输出、失败模式、不变量清单,并用研究流程图把从一个想法到一笔实盘订单之间所有该过的卡点串起来。

2026-05-01 · quant

【量化交易】市场结构:交易所、做市商、暗池、ECN

系统梳理全球市场结构(Market Structure)的工程图景:从证券交易所、衍生品交易所、加密交易所,到做市商、暗池、ECN/ATS,再到 Maker-Taker 收费、PFOF、Reg NMS 与 MiFID II 的监管影响;给出量化策略选择交易场所的判断框架与基于 ccxt 的多交易所行情聚合代码。

2026-05-01 · quant

【量化交易】市场微结构:订单簿、价差、流动性、冲击

系统讲解市场微结构的核心概念与可计算工具:限价订单簿的数据模型、报价/有效/已实现价差、Roll 模型、四维流动性度量、Kyle's lambda、订单流不平衡(OFI)、Almgren-Chriss 框架下的临时与永久冲击、PIN 与 VPIN、Hawkes 过程,并给出基于 polars 的 L2 增量处理与系数估计代码。

2026-05-01 · quant

【量化交易】订单类型与执行语义:限价、市价、IOC、FOK、冰山

把 Limit、Market、IOC、FOK、Iceberg、Stop、MOO/MOC 这些常被混为一谈的订单类型还原为价格、数量、时效、可见性、触发五个独立维度,并对照 A 股、港股、美股、CME、Binance 五个市场的实际语义差异,给出量化系统中的订单工厂、状态机与风控前置校验的工程实现。


By .