关于 odb-microstructure-forensics
用于从 ClickHouse 结果追踪到 Parquet 原始成交数据,调查 Open Deviation Bar 异常的取证工作流。帮助数据和交易分析人员区分算法缺陷与市场微观结构现象。
主要功能
- 01三层逐步追查柱线至成交数据
- 02检测阈值超限和零持续时间柱线
- 03分析时间戳突发和订单流
- 04分类市场现象与数据根因
适用场景
- 01调查异常高大或过短的 ODB 柱线
- 02判断阈值超限是否由数据特性导致
- 03从成交记录追查疑似数据缺口
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ terrylica/ cc-skills --skill odb-microstructure-forensics安装后在 Claude Code 中输入/odb-microstructure-forensics确认已加载。
附带文件references/clickhouse-schema.mdreferences/evolution-log.md
详细介绍
ODB 微观结构取证
通过从 ClickHouse 缓存追溯到原始 Parquet 成交数据,系统地调查 Open Deviation Bar 异常。区分算法正确性问题与市场微观结构现象(强平级联、订单簿扫单、撮合引擎批处理效应)。
自我演进的技能:此技能会在使用过程中不断改进。如果指令有误、参数发生漂移,或需要变通方案,请立即修复此文件,不要拖延。仅针对真实且可复现的问题进行更新。
适用场景
- ODB 柱在相同阈值下,视觉上比相邻柱更大(更高或更宽)
- 柱的持续时间为零或接近零,但价格范围极大
abs_dev_dbps超过阈值(例如,100 dbps 的柱达到 22 dbps)- 单个时间戳出现微型柱集群
- 判断异常是数据错误还是市场微观结构现象
- 调查 Flowsurface 图表 UI 标记的特定时间窗口
数据源
| 来源 | 位置 | 架构 | 访问方式 |
|---|---|---|---|
| ClickHouse 缓存 | 上的 opendeviationbar_cache.open_deviation_bars | 76 列,参见架构参考 | ssh <gpu-host> 'curl -s http://localhost:8123/ -d "..."' |
| Parquet 成交缓存 | 上的 /home/<user>/.cache/opendeviationbar/ticks/{SYMBOL}/{YYYY-MM-DD}.parquet | agg_trade_id, price, quantity, first_trade_id, last_trade_id, timestamp, is_buyer_maker | 使用 Polars 执行 ssh <gpu-host> 'cd /home/<user> && uv run --python 3.14 python3 -c "..."' |
访问方式:始终通过 SSH 直接查询 。SSH 隧道(localhost:18123)仅供 Flowsurface 应用运行时使用,取证查询应直接访问。
调查方法
调查分为 3 层逐步深入:ClickHouse 概览 → 逐柱异常检测 → Parquet 成交级根因分析。
第 1 层:ClickHouse 柱概览
查询可疑时间窗口内的柱。检查结果集中的异常信号。
-- Adjust symbol, threshold, and time window to match the investigation
SELECT
toDateTime64(close_time_us / 1000000, 3) AS close_ts,
toDateTime64(open_time_us / 1000000, 3) AS open_ts,
open, high, low, close,
round((high - low) / open * 10000, 1) AS range_dbps,
round(abs(close - open) / open * 10000, 1) AS abs_dev_dbps,
agg_record_count AS n_agg,
individual_trade_count AS n_trades,
round(duration_us / 1e6, 1) AS dur_s,
first_agg_trade_id AS first_id,
last_agg_trade_id AS last_id,
is_orphan, is_liquidation_cascade AS is_liq
FROM opendeviationbar_cache.open_deviation_bars
WHERE symbol = '{SYMBOL}'
AND threshold_decimal_bps = {THRESHOLD}
AND close_time_us >= toUnixTimestamp('{START_UTC}', 'UTC') * 1000000
AND close_time_us <= toUnixTimestamp('{END_UTC}', 'UTC') * 1000000
ORDER BY close_time_us
FORMAT PrettyCompact
需要标记的异常信号:
| 信号 | 列模式 | 含义 |
|---|---|---|
| 超阈值 | abs_dev_dbps >> threshold / 10 | 单笔成交越过阈值 |
| 持续时间为零 | dur_s = 0 | 整个柱在一个撮合引擎周期内形成 |
| 极端范围 | range_dbps > 2 * threshold / 10 | 价格越过阈值很远 |
| 微量成交计数 | 高范围且 n_agg < 10 | 巨大的单笔成交吃掉了订单簿 |
| 突发聚集 | 多个柱出现在同一秒 | 流动性扫单被拆分到多个柱中 |
第 2 层:隔离异常
筛选出异常柱,获取用于深入检查 Parquet 的 agg_trade_id 范围:
-- Bars with threshold overshoot or zero duration
SELECT close_ts, dur_s, open, high, low, close,
range_dbps, abs_dev_dbps, n_agg,
first_agg_trade_id, last_agg_trade_id
FROM (... Layer 1 query ...)
WHERE dur_s < 1.0 OR abs_dev_dbps > {THRESHOLD / 10 * 1.5}
记录 first_agg_trade_id 和 last_agg_trade_id 的范围——它们是 Parquet 查找键。
第 3 层:Parquet 成交级根因分析
在 上使用 Polars 分析原始成交。按顺序执行以下 3 项分析:
3a. 时间戳突发检测
按时间戳对交易分组,以查找撮合引擎批次(数百笔交易共享完全相同的微秒时间戳):
import polars as pl
df = pl.read_parquet("/home/<user>/.cache/opendeviationbar/ticks/{SYMBOL}/{DATE}.parquet")
burst = df.filter(
(pl.col("agg_trade_id") >= {FIRST_ID}) &
(pl.col("agg_trade_id") <= {LAST_ID})
).sort("agg_trade_id")
# Group by timestamp to find single-cycle batches
ts_groups = burst.group_by("timestamp").agg([
pl.col("price").min().alias("min_price"),
pl.col("price").max().alias("max_price"),
pl.col("quantity").sum().alias("total_qty"),
pl.len().alias("count"),
]).sort("timestamp")
关键诊断:如果同一时间戳下有数百笔交易,且价格覆盖整个区间,那么这就是撮合引擎批次(单笔大额订单扫过订单簿)。
3b. 订单流分析
判断扫单以买入还是卖出为主:
buys = burst.filter(~pl.col("is_buyer_maker")) # taker buy
sells = burst.filter(pl.col("is_buyer_maker")) # taker sell
print(f"Taker buys: {len(buys)} trades, {buys['quantity'].sum():.4f} BTC")
print(f"Taker sells: {len(sells)} trades, {sells['quantity'].sum():.4f} BTC")
清算级联通常有 95% 以上的交易方向一致(全是吃单卖出或全是吃单买入)。
3c. 价格跳空分析
查找价格大幅跳变的单笔交易——它们是阈值超调的直接原因:
with_gap = burst.sort("agg_trade_id").with_columns([
(pl.col("price") - pl.col("price").shift(1)).alias("price_diff"),
(pl.col("timestamp") - pl.col("timestamp").shift(1)).alias("ts_diff_us"),
])
# Trades with gaps exceeding threshold dollar equivalent
threshold_dollars = open_price * threshold_dbps / 10000
big_jumps = with_gap.filter(pl.col("price_diff").abs() > threshold_dollars)
如果单笔交易之间的价格跳空超过阈值,ODB 算法就无法在单个 agg_trade 内拆分——超调不可避免,且符合预期。
根因分类
完成 3 层分析后,对发现的问题进行分类:
| 分类 | 证据模式 | 处理方式 |
|---|---|---|
| 清算级联 | 95% 以上交易方向一致,50-100+ BTC,同一微秒时间戳,扫过 $200+ | Oracle 位级精确——无需修复。记录该事件。 |
| 订单簿稀薄导致的扫单 | 交易笔数较少,但不同价位之间存在较大的价格跳空 | Oracle 位级精确——当时订单簿较为稀薄。 |
| 孤立 K 线 | ClickHouse 中 is_orphan = 1 | 已知现象——写入器边界伪影。分析时跳过。 |
| 算法错误 | 交易分布正常、没有突发,但 K 线仍然超调 | 在 opendeviationbar 上游提交问题。 |
| 数据缺失 | 相邻 K 线之间的 agg_trade_id 不连续 | Parquet 数据缺失。检查采集流水线。 |
阈值超调机制
ODB 算法按顺序处理 agg_trades。当第一笔交易相对于 K 线开盘价的偏离超过阈值时,K 线就会收盘。超调机制如下:
- K 线以价格 P₀ 开盘,阈值为 T(例如,100 dbps = 0.1%)
- 算法扫描交易:P₁、P₂、... Pₙ
- 在交易 Pₖ 处:
|Pₖ - P₀| / P₀ ≥ T——K 线以 Pₖ 收盘 - 如果 Pₖ₋₁ 仍在阈值范围内,而 Pₖ 大幅跳出阈值范围,则超调 =
|Pₖ - P₀| / P₀ - T
阈值越低,超调越大,因为:
- BPR10 阈值 = $70(BTC 价格为 $70k 时)。订单簿出现 $150 的价差 → 超调 2 倍。
- BPR50 阈值 = $350(BTC 价格为 $70k 时)。同样的 $150 价差 → 仍在阈值范围内。
这是离散交易数据固有的特性——不是错误。
相关技能
| 技能 | 关联说明 |
|---|---|
| opendeviation-eval-metrics | 评估 ODB 信号质量(输出指标)。此技能用于调查输入数据质量。 |
| exchange-session-detector | ClickHouse 中的交易时段标记。级联事件通常集中在交易时段边界(纽约时段开盘/收盘)附近。 |
执行后复盘
此技能完成后,在结束前检查:
- 查询是否成功? — 如果 ClickHouse 架构发生变化(列重命名、新增列),请更新第 1 层 SQL 模板。
- Parquet 架构是否发生变化? — 如果 tick 缓存列发生变化,请更新 Polars 代码片段。
- 是否发现了新的根因模式? — 将其连同证据模式和处理措施添加到根因分类表中。
- 阈值超调机制的说明是否成立? — 如果发现了新的超调机制,请将其记录下来。
仅在问题真实且可复现时更新,不要基于推测更新。