# Polymarket BTC 5m / 15m 盘口数据：AI 回测项目启动说明

> 用途：把本文件完整粘贴给 Codex、Claude Code 或其他代码工具，再附上本地数据目录。请 AI 先阅读并遵守数据口径，再创建一个可复现的回测项目。

## 你要完成的事情

你是一名数据工程师和量化回测工程师。请基于我提供的 Polymarket BTC 5 分钟 / 15 分钟 Up/Down CLOB 盘口数据，建立一个可复现、可审计的回测项目。

第一版目标不是预测收益，而是先把数据正确读入、关联、清洗、模拟成交并输出质量报告。任何不确定的字段、文件覆盖范围或交易规则，都必须先在代码和报告中标记，不能自行编造。

## 重要事实边界

- 这是 CLOB 订单簿快照数据，不是逐笔成交数据。
- 数据可以用于 best bid/ask、价差、盘口深度、盘口失衡、理论成交模拟和结算方向研究。
- 数据不能证明某一笔订单真的成交，也不能证明展示数量全部可被策略成交。
- 数据不包含排队位置、真实主动买卖方向、完整逐笔成交、交易所事件时间、网络延迟和 BTC 美元标的行情，除非输入目录另有文件明确提供。
- `size` 是当时看到的 token 展示数量，不是成交数量，也不是成交保证。
- `winner_side` 和 `target` 是市场结束后才知道的标签，只能用于最终收益计算或监督学习标签，不能作为交易时点特征。
- Polymarket token 价格不是 BTC 美元价格，通常在 0 到 1 附近；回测必须以文件实际值为准。
- 数据集与 Polymarket 没有隶属、背书或官方数据源关系。

## 第一步：先检查输入目录

开始编码前，请先列出实际存在的文件、文件大小、日期范围、Parquet schema 和 JSONL 样例。不要假设覆盖完整，也不要只根据文件名推断市场时间。

常见目录结构如下，但必须以实际目录为准：

```text
data/live/
  ticks_YYYYMMDD_HH.jsonl
  depth_YYYYMMDD_HH.jsonl
  market_outcomes.jsonl
  seen_markets.jsonl
data/hourly/
  ticks_YYYYMMDD_HH.parquet
  depth_YYYYMMDD_HH.parquet
  btc_5m_market_outcomes.parquet
data/daily/
  btc_5m_YYYYMMDD.zip
```

15 分钟文件可能使用 `btc_15m_*` 命名，不能把文件名中的 `5m` 当成真实窗口长度。请优先使用 `interval_min`、`window_start_ts` 和 `window_end_ts` 判断。

如果输入文件不完整，请先生成 `data_inventory.csv` 或同等报告，至少包含：文件名、格式、行数、最小时间、最大时间、市场数量、字段列表、是否可读取、缺失字段。

## 数据生命周期

```text
CLOB WebSocket
    -> 本地订单簿
    -> live/*.jsonl
    -> hourly/*.parquet
    -> daily/*.zip
```

- `live/*.jsonl` 是追加式实时缓冲，适合审计和增量处理。
- `hourly/*.parquet` 是列式研究文件，优先用于分析。
- `daily/*.zip` 是按日分发包；如果有 `part1of2`、`part2of2`，它们只是传输分包，必须合并后再使用。
- `seen_markets` 保存市场与 token 的映射。
- `market_outcomes` 保存最终结算结果。

## 表和字段

### 1. ticks：最佳报价快照

每行是某个市场在某个时刻的 best quote 快照。只有 UP 和 DOWN 的四个 bid/ask 都存在，并且至少一个 best quote 相比上一行发生变化时，才会写入一行。因此它是事件驱动采样，不是固定 1 秒或 100 毫秒采样。

常见字段：

| 字段 | 含义 |
| --- | --- |
| `ts` | 本机采集时间，Unix epoch 秒，可能带小数 |
| `ts_sec` | `floor(ts)` |
| `datetime` | 毫秒级时间字段 |
| `event_slug` | 市场唯一事件键 |
| `expires` | 市场结束时间，UTC ISO 8601 |
| `poly_up_bid` / `poly_up_ask` | UP token 最佳买价 / 卖价 |
| `poly_down_bid` / `poly_down_ask` | DOWN token 最佳买价 / 卖价 |
| `interval_min` | 市场周期，应该是 5 或 15 |
| `window_start_ts` / `window_end_ts` | 市场窗口边界 |
| `offset_s` | `ts - window_start_ts` |
| `up_mid` / `down_mid` | 对应 token 的中间价 |
| `up_spread` / `down_spread` | `ask - bid` |

不要把相邻 ticks 行之间的变化解释成一笔成交，也不要在没有记录的时间段无条件前向填充。

### 2. depth：盘口深度长表

每个盘口档位一行。对同一个 `(event_slug, ts, outcome)`，BID 的 `level=1` 是最高买价，ASK 的 `level=1` 是最低卖价；默认每侧最多 20 档。

常见字段：

| 字段 | 含义 |
| --- | --- |
| `ts` / `ts_sec` / `datetime` | 深度快照时间 |
| `event_slug` | 市场唯一事件键 |
| `interval_min` | 市场周期，5 或 15 |
| `window_start_ts` / `window_end_ts` | 市场窗口边界 |
| `offset_s` | 相对市场开始时间的秒数 |
| `outcome` | `UP` 或 `DOWN` |
| `side` | `BID` 或 `ASK` |
| `level` | 从 1 开始的档位 |
| `price` | 该档价格 |
| `size` | 该档当时展示的 token 数量 |

一个完整快照最多是 `2 outcomes × 2 sides × 20 levels = 80` 行，但实际可能少于 80 行。depth 与 ticks 使用同一触发点，深层价位变化但 best quote 未变化时，当前版本可能不会保存该变化。

### 3. market_outcomes：最终结算标签

常见字段：

| 字段 | 含义 |
| --- | --- |
| `event_slug` | 市场唯一事件键 |
| `window_start_ts` / `window_end_ts` | 市场窗口边界 |
| `winner_side` | 最终获胜方向，`UP` 或 `DOWN` |
| `target` | `UP -> 1`，`DOWN -> 0` |

只有已经明确结算的市场才应该进入最终收益统计。若结算结果缺失，不要把它强行填成 0 或 1。

### 4. seen_markets：市场与 token 映射

常见字段：

| 字段 | 含义 |
| --- | --- |
| `event_slug` | 市场唯一事件键 |
| `market_id` | Gamma 市场 ID |
| `condition_id` | 市场条件 ID |
| `up_asset_id` / `down_asset_id` | UP / DOWN 的 CLOB asset ID |
| `asset_ids` | 两个 asset ID 的数组 |
| `window_start_ts` / `window_end_ts` | 市场窗口边界 |
| `expires` | 市场结束时间 |

ticks 和 depth 可能只保存 UP/DOWN 标签，没有重复保存 asset ID。需要核对原始 token 时，以 `event_slug` 关联 `seen_markets`。

## 关联规则

主关联键是 `event_slug`；盘口快照的时间键是 `event_slug + ts`：

```text
ticks.event_slug
depth.event_slug
market_outcomes.event_slug
seen_markets.event_slug
```

不要只按日期、行号或裸 `ts` 关联不同市场。depth 聚合时至少使用：

```text
event_slug, ts, outcome, side, level
```

如果需要 token 原始标识，再从 `seen_markets` 补充 `up_asset_id`、`down_asset_id`、`market_id` 和 `condition_id`。

## 时间和市场边界

- 5 分钟市场正常情况下 `window_end_ts - window_start_ts = 300`。
- 15 分钟市场正常情况下 `window_end_ts - window_start_ts = 900`。
- 只研究市场窗口内交易时，默认使用：

```text
window_start_ts <= ts < window_end_ts
```

- `offset_s < 0` 是开盘前数据；`offset_s >= 窗口秒数` 是市场结束后数据，不能默认当作市场内可交易数据。
- 先统一 UTC，再进行日期和小时切分。
- 检查 `ts_sec == floor(ts)`、`offset_s ≈ ts - window_start_ts`、窗口长度、`datetime` 与 `ts` 的一致性。
- 采集器可能允许 `offset_s` 达到窗口结束后约 300 秒，必须单独标记或剔除。

## 成交模拟规则

假设策略在时间 `t` 想买入某个 outcome 的 `Q` 股：

1. 找到对应 outcome 的 `ASK` 侧。
2. 从 `level=1` 开始向更深档位消耗。
3. 每档成交量取 `min(该档 size, 剩余数量)`。
4. 直到完成 Q 或盘口深度耗尽。
5. 计算加权平均价：

```text
average_price = sum(fill_size * price) / sum(fill_size)
```

卖出时使用对应 outcome 的 `BID` 侧，从 `level=1` 开始向更低价格档位消耗。

回测中必须显式设置：

- 决策到下单延迟；
- 使用当前快照还是下一条快照；
- 展示 `size` 的可成交比例；
- 最大成交深度；
- 深度不足时允许部分成交还是取消；
- 手续费；
- 额外滑点；
- 订单有效期和是否允许跨快照等待。

至少输出三种情景：

```text
乐观：展示 size 的 100% 可成交，短延迟，当前快照价格
基准：展示 size 的 25% 到 50% 可成交，固定延迟或下一条快照
保守：只使用 level=1，更长延迟，更低成交比例，增加滑点
```

不要：

- 用 `mid` 作为买入或卖出成交价；
- 买入使用 BID，卖出使用 ASK；
- 把 `size` 当作已成交数量；
- 把每一条 depth 行当成独立市场事件；
- 用未来快照回填当前缺失盘口；
- 在没有规则的情况下让订单无限跨快照成交。

## 数据质量检查

请实现可重复运行的质量检查，至少检查：

- 三类表的字段是否符合实际 schema；
- 文件是否可读、日期范围和 UTC 时间是否合理；
- `window_end_ts - window_start_ts` 是否符合 5m / 15m；
- `event_slug`、窗口时间和 `expires` 是否一致；
- bid 是否高于 ask；异常只记录，不要静默修正；
- price 和 size 是否为有限值，size 是否大于 0；
- outcome 是否只有 `UP` / `DOWN`；side 是否只有 `BID` / `ASK`；
- level 是否从 1 开始且同一快照不重复；
- BID 是否从高到低、ASK 是否从低到高；
- 每个市场是否有首个完整快照；
- 最后一条快照距离窗口结束多久；
- 相邻快照是否有明显断线空档；
- 是否只采集到 UP 或 DOWN；
- outcome 是否存在且已明确结算。

每天约有 288 个 5 分钟窗口、96 个 15 分钟窗口只是理论覆盖参考，不能把它当作必须补齐的硬性条件，因为采集器可能只发现相邻市场，也可能受到网络、API 或进程中断影响。

## 防止未来数据泄漏

- 所有特征只能使用快照时刻及之前的数据。
- `winner_side` 和 `target` 只能用于最终结算或标签。
- 不要用市场结束后的盘口给市场结束前的订单成交。
- 不要把未来第一条完整盘口回填到当前缺失时刻。
- 如果做滚动特征、前向填充、重采样或跨市场聚合，请把时间边界写进测试。

## 建议的项目结构

请根据实际语言和环境调整，但优先建立类似结构：

```text
backtest-project/
  data/                 # 原始数据，只读，不覆盖
  src/
    ingest.py           # 读取和 schema 检查
    inventory.py        # 文件清单和覆盖率
    normalize.py        # UTC、类型和窗口统一
    joins.py             # event_slug 关联
    orderbook.py        # depth 快照和成交模拟
    strategies/          # 策略代码
    metrics.py           # 收益、成交、风险指标
    report.py            # HTML/CSV/Markdown 报告
  tests/
  configs/
    baseline.yaml
    optimistic.yaml
    conservative.yaml
  reports/
  README.md
  pyproject.toml
```

Python 项目请使用独立虚拟环境；保留原始数据，只写入派生数据和报告。不要把一次性 notebook 作为唯一实现。

## 第一版必须交付

1. 输入文件 inventory 和覆盖率报告；
2. 可复现的读取、类型统一和关联脚本；
3. ticks / depth / outcomes 的 schema 检查；
4. 一个最小策略接口；
5. 按 ASK 买入、BID 卖出的分档成交模拟器；
6. 乐观、基准、保守三组配置；
7. 防止未来数据泄漏的测试；
8. CSV 或 Parquet 的逐笔模拟成交记录；
9. 按市场、日期、UP/DOWN、持有时间和情景拆分的结果报告；
10. README，说明数据来源、假设、运行方式和已知限制。

结果报告至少包含：市场数量、有效快照数量、有 outcome 的市场数量、缺失/断线市场、下单数量、理论成交数量、部分成交数量、使用的深度档位、决策延迟、成交比例、手续费、滑点、UP/DOWN 分项结果，以及不同成交情景下的收益范围。

## 工作方式要求

1. 先检查实际文件和环境，再写代码。
2. 发现字段与本说明不一致时，优先以实际文件为准，并在 README 记录差异。
3. 不要为了让结果好看而删除异常、补齐缺失或修改原始文件。
4. 先用一个小日期范围跑通，再扩大到全量数据。
5. 每完成一个阶段都运行测试，并给出实际检查结果。
6. 如果缺少某项数据（例如 BTC 标的价格、逐笔成交或费用规则），明确列为缺口，不要模拟成已经存在。

请现在开始：先输出你发现的文件清单、schema、覆盖范围和需要我确认的唯一关键问题；如果没有阻塞问题，就直接创建项目骨架和第一版数据质量检查。
