Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
集成与开发阅读约 11 分钟

社交媒体加密货币信号:构建可复现的数据管道

一份实用的工程指南,介绍如何收集社交媒体加密货币信号、将其与链上及市场数据关联,并在不误导自己的前提下衡量它们之间的关系。

TL;DR

社交媒体加密货币研究最好作为一个工程问题来处理:定义一个固定的观察窗口,收集带时间戳的帖子,将其与通过可靠 RPC 端点获取的价格和链上数据关联,并计算你之后可以重新计算的互动指标。本文为你提供思维模型、可运行的收集与关联流程、用于比较信号变体的结果表方法,以及最常导致结论失效的故障模式。它将有据可查的平台行为、你自己的测量结果和建议分开,以便你知道哪些数字仍需自己生成。

范围与核心思维模型

“社交媒体加密货币”这个说法涵盖了两件容易混淆的不同事物。第一是去中心化社交网络,即身份、内容或奖励存在于链上的区块链平台。第二,也是本文关注的重点,是将传统社交平台作为关于加密资产的数据来源:你想将其与市场或网络行为关联起来的帖子数量、互动、情绪和机器人活动。

思维模型是一个三表连接。第一张表是帖子:作者、时间戳、文本和互动计数。第二张表是市场数据:匹配时间戳上的价格和交易量。第三张表是链上活动:相同时间戳上的转账、活跃地址或合约事件。任何“社交热度预测回报”形式的论断,都是关于这三张表在定义窗口内如何共同变化的陈述。如果你无法重建全部三张表,你就无法审计该论断。

将每个指标视为一个定义,而非事实。“互动”可能意味着点赞加转发,也可能意味着按粉丝数归一化的互动率。已发表的研究专门使用基于互动的互动模型,以避免原始帖子数量和朴素情绪评分的弱点,因此请选择一个定义,并在整个研究中保持不变。

  • 观察窗口:收集的确切开始和结束时间戳。
  • 实体解析:如何将帖子映射到一个资产代码或合约地址。
  • 连接键:通常是 UTC 时间戳桶,有时也包括合约地址。
  • 基线:你将信号与之比较的对象,例如交易量匹配的对照窗口。

假设、前提条件以及你必须自己测量的内容

本流程假设你拥有对社交平台 API 或导出数据集的合法访问权限、带时间戳的市场数据源,以及资产所在链的 RPC 端点。它不假设任何特定提供商。关于端点选择,RPC Assistant 涵盖了具体选项,例如最佳以太坊 RPC API 和 BNB Chain RPC 提供商选项;本页专注于方法论。

你可以依赖的有据可查的行为:区块链 RPC 调用返回特定区块高度的数据,而区块高度映射到时间戳。这为你提供了链上数据的确定性连接键。其他一切,包括你的社交样本有多大的代表性以及互动是否能预测任何东西,都是你必须运行的测量。

在收集之前,将假设、资产列表、窗口和指标定义写入一个带版本的文件。如果你在研究中途更改其中任何一项,请重新开始窗口,而不是修补它。

  • 具有每条帖子时间戳和互动计数的社交数据源。
  • 粒度不粗于你的帖子桶的价格序列。
  • 相关链的 RPC_URL,保持在源代码控制之外。
  • 在保留派生指标的同时保留原始负载的存储层。

使用稳定模式收集帖子

先收集原始负载,再派生指标。保留原始数据让你在发现定义错误后重新计算互动,这种情况很常见。下面的 Python 示例为每条帖子写入一行 JSON,带有归一化的 UTC 时间戳,并将原始对象保留在 raw 下,因此不会丢失任何内容。

速率限制和分页是两个操作约束。为每个查询持久化一个游标,以便中断的运行可以恢复而不是重复。基于平台的帖子标识符去重,而不是基于文本,因为相同的文本经常被转发。

import json, os, time, datetime as dt
import requests

API_BASE = os.environ["SOCIAL_API_BASE"]
TOKEN = os.environ["SOCIAL_API_TOKEN"]
QUERY = "$EXAMPLE"
OUT = "posts.jsonl"

def fetch_page(cursor=None):
    params = {"q": QUERY, "limit": 100}
    if cursor:
        params["cursor"] = cursor
    r = requests.get(
        f"{API_BASE}/search",
        params=params,
        headers={"Authorization": f"Bearer {TOKEN}"},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

cursor = None
with open(OUT, "a", encoding="utf-8") as f:
    while True:
        page = fetch_page(cursor)
        for post in page.get("items", []):
            record = {
                "id": post["id"],
                "author_id": post["author"]["id"],
                "created_at": dt.datetime.fromisoformat(
                    post["created_at"].replace("Z", "+00:00")
                ).astimezone(dt.timezone.utc).isoformat(),
                "text": post.get("text", ""),
                "likes": post.get("like_count", 0),
                "reposts": post.get("repost_count", 0),
                "raw": post,
            }
            f.write(json.dumps(record) + "\n")
        cursor = page.get("next_cursor")
        if not cursor:
            break
        time.sleep(1.0)

通过 RPC 拉取链上活动

链上数据锚定你的时间线。最简单可靠的信号是区块生产本身:获取区块时间戳,以便每个社交桶映射到一个区块范围。从那里,你可以扩展到特定资产的转账计数或合约事件。

下面的 curl 示例使用基于 HTTP 的 JSON-RPC。将 RPC_URL 替换为你自己的端点。如果你需要帮助选择一个,RPC 端点指南解释了请求形状,多链 RPC 端点指南涵盖了在多个链上运行相同模式。

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_getBlockByNumber",
    "params": ["0x134fd90", false]
  }'

在 UTC 桶上关联社交、市场和链数据

在查看结果之前选择桶大小。对于流动性资产,小时桶是合理的默认值;日桶减少噪音但隐藏短暂的峰值。连接是从社交桶到市场和链桶的左连接,以便安静时段作为零保留在数据集中,而不是消失。

JavaScript 示例计算每个桶的简单互动分数,并附加区块时间戳范围。它故意不计算回报;那属于单独的步骤,以便连接可以独立测试。

const BUCKET_MS = 60 * 60 * 1000;

function bucketKey(iso) {
  return Math.floor(new Date(iso).getTime() / BUCKET_MS) * BUCKET_MS;
}

function buildBuckets(posts, blocks) {
  const map = new Map();
  for (const p of posts) {
    const k = bucketKey(p.created_at);
    const b = map.get(k) || { key: k, posts: 0, engagement: 0, authors: new Set() };
    b.posts += 1;
    b.engagement += (p.likes || 0) + 2 * (p.reposts || 0);
    b.authors.add(p.author_id);
    map.set(k, b);
  }
  for (const blk of blocks) {
    const k = bucketKey(blk.timestampIso);
    const b = map.get(k) || { key: k, posts: 0, engagement: 0, authors: new Set() };
    b.blockNumber = blk.number;
    map.set(k, b);
  }
  return [...map.values()]
    .sort((a, b) => a.key - b.key)
    .map(b => ({ ...b, uniqueAuthors: b.authors.size, authors: undefined }));
}

module.exports = { bucketKey, buildBuckets };

在信任互动之前衡量机器人占比

高互动可能意味着真正的兴趣或自动化活动。已发表的关于互动系数的研究发现,任一方向的极端值都对应较低的未来回报,非常高的值通常表明机器人驱动的活动。你不需要复现那项研究来应用这个教训:衡量一个机器人代理指标,并将其与互动一起报告。

一个实用的代理指标是来自最近创建账户的帖子占比、桶内近似重复文本的占比,以及帖子与唯一作者的比例。这些都不是决定性的。它们的价值在于成本低、确定性强,并且可以从原始数据重新计算。

  • 每个唯一作者的帖子数超过你预先设定的阈值。
  • 使用文本的归一化哈希计算的重复文本占比。
  • 账户年龄分布,如果你的来源暴露创建日期。
  • 每个作者的互动,当少数账户主导时会崩溃。

结果表流程与预期验证

在得出结论之前生成结果表。每一行是一个信号变体;每一列是在同一窗口上计算的指标。只填入你的管道输出的数字,绝不要用说明性值。一个可辩护的表格如下所示:

信号变体桶数平均互动机器人代理占比与下一桶回报的相关性备注
原始帖子数你的计数你的值你的值你的值基线
互动总和你的计数你的值你的值你的值加权转发
每作者互动你的计数你的值你的值你的值减少机器人膨胀
过滤后(机器人代理低于截止值)你的计数你的值你的值你的值截止值预先固定

验证是程序性的,而非数值性的。从原始负载重新运行管道,并确认表格字节完全相同。如果不是,你的连接或时间戳处理是非确定性的。只有在那项检查之后,你才应该解释相关性,即使如此,也要将其视为窗口内的描述性而非窗口外的预测性。

排查常见管道故障

大多数错误结论都追溯到一小部分机械性错误。在质疑假设之前检查这些。

如果你的 RPC 调用间歇性失败或返回过时区块,连接将静默丢弃链行。记录你实际收到的区块高度,并将其与时间戳的预期高度进行比较。关于提供商级别的可靠性问题,区块链节点托管指南和专用节点选项解释了共享与专用基础设施之间的权衡。

  • 时区漂移:混合本地和 UTC 时间戳会使桶偏移数小时。
  • 代码冲突:两个资产共享一个符号会合并成一个序列。
  • 幸存者偏差:被摘牌的资产从后续窗口中消失。
  • 前视泄漏:在那些点赞存在之前的时间使用帖子的最终点赞数。
  • 分页缺口:中断的爬取留下看起来像安静时段的空洞。

局限性与权衡

社交数据是样本的样本。平台更改 API、删除帖子并改变互动计数的含义,因此今天可复现的管道明年可能无法复现,除非你保留原始负载。即使保留原始数据,已删除的帖子也消失了。

链上数据更持久,但并非没有解释空间。转账计数包括交易所内部移动,活跃地址指标可能被单个行为者夸大。结合两个来源减少了对任一来源的依赖,但并不能消除混杂:广泛的市场变动可以同时驱动社交量和链上活动,而两者都不导致对方。

最后,互动指标对定义敏感。改变转发的权重,或从总和切换到每作者比率,可能会翻转相关性的符号。这种敏感性本身就是一个值得报告的发现。

生产级研究的后续步骤

一旦管道具有确定性,就从三个方向扩展它。首先,添加一个你预期不会受影响的对照资产,以便你可以看到你的信号是特定的还是只是市场范围的。其次,在多个窗口上运行相同流程,并报告稳定性而不是单一结果。第三,从相关性转向明确的、预先注册的决策规则,并在样本外评估它。

对于基础设施,将社交收集层与链查询层分开,以便一个层的速率限制不会阻塞另一个层。在扩展查询量之前审查 RPC 定价,如果你的区块扫描工作负载变得连续,考虑使用专用节点。网络目录列出了你可以瞄准的链,一旦你的方法论固定,RPC Assistant 可以帮助你选择一个具体的端点。

  • 将假设文件与代码一起版本化。
  • 分别存储原始负载和派生表。
  • 按计划从原始数据重新运行以捕捉漂移。
  • 在每个互动数字旁边报告机器人代理指标。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始