feat: 初始化 cryptoHermes 行情网关 v1 MVP + harness 工程文档

Binance USDⓈ-M Futures 行情网关,给上游 Hermes 量化分析引擎提供单一聚合
接口 /v1/market/context(K 线 + funding + OI + 多空比 + taker volume)。
只读公共行情,不下单、不接私钥、不查账户。

## v1 实现范围(Milestone 1-5)

- Clean Architecture 4 层(controller/usecase/repo/entity),接口边界在
  internal/usecase/ports.go
- Binance Futures REST client(K 线 / ticker24h / funding / OI / 多空比
  / taker volume 共 9 个接口),全链路 string 价格避免 float64 精度问题
- TimescaleDB 5 张 hypertable(market_klines / funding_rates /
  open_interest / long_short_ratio / taker_buy_sell_volume),主键含
  时间维度,UpsertMany 幂等
- robfig/cron 定时采集(15m/1h/4h/1d/1w 多周期 K 线 + 衍生品 15 分钟
  落库),未收线 K 线 (close_time > now) 由 mapper/repo 双重过滤
- pkg/httpclient 统一限流(默认 20 req/s, burst 40)+ 重试,避免触发
  Binance 2400 weight/min IP 上限
- /v1/market/context 聚合接口:errgroup 并发拉 snapshot/funding/OI,DB
  K 线不足 200 根回源 Binance 异步补
- cmd/backfill CLI 支持指定 from/to 大段回填(Binance 历史 OI / 多空比
  官方只保留 30 天,必须自己存)
- Docker Compose + Makefile + golang-migrate,本地一键启

技术指标(support/resistance/Vegas/箱体)留待 v2,技术段返回空对象 +
warning 占位。

## Harness 工程文档

- AGENTS.md — AI agent 工作速查(10 个章节)
- ai/project-map.md — 仓库结构、扩展点、控制流
- ai/risk-guardrails.md — G1-G10 守卫规则(每条带可机械验证命令)
- ai/adr/0001-architecture-foundations.md — 9 条架构基础决策
- ai/task-templates.md — 6 种任务契约模板
- ai/harness-health.md — 当前 harness 健康度评估

3 个 grep 守卫已验证通过:controller / usecase 无具体实现依赖,全项目
无私钥/签名字段。
This commit is contained in:
dela
2026-05-24 17:20:51 +08:00
commit cc7f5a4f32
58 changed files with 5356 additions and 0 deletions

184
ai/risk-guardrails.md Normal file
View File

@@ -0,0 +1,184 @@
# Risk Guardrails — cryptoHermes
可机械验证 / 不可逾越的规则。每条规则给出**为什么**、**怎么验证**。
任何 PR 都应该满足这里全部规则。
---
## G1. 绝不接入交易能力Hard Stop
**规则**:本服务只读公共行情。**禁止**任何下单、撤单、查询账户/持仓/资金、签名请求、托管、转账。
**为什么**:项目定位是"上游 Hermes 量化分析引擎的行情网关"。一旦引入交易能力,安全边界、合规风险、密钥管理责任全部上升一个数量级。`docs/dev.md` 第 1 章原文:"本服务只提供市场数据与分析上下文,不做交易、不下单、不托管资金"。
**怎么验证**
```bash
# Go 代码不可出现私钥/签名相关字段
grep -ri --include="*.go" "apikey\|x-mbx-apikey\|hmac\|secret_key\|api_secret" .
# 期望:无输出(注释/字符串/文档中说明"不接入"不算)
# Binance 私有接口前缀不可出现
grep -r --include="*.go" "fapi/v1/order\|fapi/v1/account\|fapi/v2/account\|fapi/v2/positionRisk" .
# 期望:无输出
```
**例外**:无。如果上游需求变化要做交易,**必须先开新仓库**或新模块(独立鉴权 / 独立部署),不能在本仓库内直接放开。
---
## G2. 价格 / 成交量绝不用 float64Hard Stop
**规则**:所有金额、价格、成交量字段**全链路用 `string`**。DTO、entity、JSON 响应都是 `string`。落库时由 PG 转 `NUMERIC(36,18)`
**为什么**`108000.12` 这种价格 float64 表示就会丢精度。Binance 原样返回字符串,本服务保留字符串到 PG。这一点在所有 5 个 entity 文件里都已经做到了,新代码必须遵守。
**怎么验证**
```bash
# 任何新增 entity 字段如果叫 *Price/*Volume/*Quantity/*Amount类型必须是 string
grep -rn --include="*.go" "Price\s*float\|Volume\s*float\|Quantity\s*float\|Amount\s*float" internal/entity
# 期望:无输出
```
**例外**:纯统计/分析(如指标计算的中间过程)可以用 float64但**结果落库或返回 API 时必须转回 string**。
---
## G3. 未收线 K 线IsClosed=false不入库
**规则**:从 Binance 拉到的最新一根 K 线如果 `close_time > now()`**不能写入 `market_klines`**。
**为什么**:未收线 K 线的 close/high/low/volume 还会变。如果入库,后续不更新就是脏数据,更新又会触发 upsert 风暴。`internal/repo/persistent/postgres/kline_repo.go``UpsertMany` 已经过滤 `IsClosed=false``internal/repo/webapi/binance/mapper.go` 已经自动设置 `IsClosed = closeTime < now()`。新代码(含 backfill必须遵守。
**怎么验证**:手动跑一次 collector立刻 `SELECT count(*) FROM market_klines WHERE close_time > extract(epoch from now()) * 1000` 应为 0。
---
## G4. UpsertMany 必须幂等
**规则**:所有 repo 的 `UpsertMany``INSERT ... ON CONFLICT (<PK>) DO UPDATE SET ... updated_at = now()`。**禁止** `INSERT` 不带 ON CONFLICT 或用 `INSERT ... ON CONFLICT DO NOTHING`(后者错过了更新场景)。
**为什么**cron 会重复跑、backfill 会和 cron 重叠、重启会重拉。重复执行必须不能产生重复行也不能丢更新(例如 OI 后修正、close 值微调)。
**怎么验证**
```bash
make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 100"
psql $DATABASE_URL -c "SELECT count(*) FROM market_klines WHERE symbol='BTCUSDT' AND interval='1h'"
make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 100"
psql $DATABASE_URL -c "SELECT count(*) FROM market_klines WHERE symbol='BTCUSDT' AND interval='1h'"
# 期望:两次 count 一致
```
---
## G5. Rate Limit 不可绕开
**规则**:所有外部 HTTP 调用走 `pkg/httpclient`,该 client 已挂 `golang.org/x/time/rate`(默认 20 req/sburst 40。**禁止** 在业务代码里直接用 `net/http` 调 Binance。
**为什么**Binance USDⓈ-M Futures IP 级限额 2400 weight/min。一旦超限会被封 IP 几分钟到几小时。本地代理在国内是稀缺资源,封了影响整个开发环境。
**预算**:新增 collector 任务前估算 weight
- `GET /fapi/v1/klines`1limit ≤ 100→ 10limit ≤ 1000
- `GET /fapi/v1/ticker/24hr`1单 symbol/ 40全量
- `GET /fapi/v1/openInterest`1
- `GET /futures/data/*`1
**怎么验证**
```bash
grep -rn --include="*.go" "net/http\"" internal/repo/webapi
# 期望:只在 pkg/httpclient 里repo/webapi 不应直接 import net/http
```
---
## G6. Clean Architecture 边界
**规则**:见 `ai/project-map.md` "不可逾越的边界"段。3 个 grep 必须无输出。
**为什么**:一旦 controller / usecase 直接 import 具体实现,测试就需要起 DB 起 Binance后续替换数据源CoinGlass也变成大手术。
**怎么验证**
```bash
grep -r "repo/webapi/binance\|repo/persistent" internal/controller # 必须无输出
grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 必须无输出
```
---
## G7. Migration 必须 down/up 互逆
**规则**:任何 SQL migration 改动后,本地必须跑:
```bash
make migrate-down && make migrate-up
```
两步都成功(不报错、不丢表)才能提交。
**为什么**生产回滚是兜底手段down 脚本错了就回不去。
**额外规则**
- 新 migration **不可改已发布 migration 的内容**——只能加新文件(编号递增)。
- 改表结构如果会丢数据drop column / change type必须在 PR 里显式说明。
- 改 hypertable chunk 间隔需要先看 `docs/dev.md` 第 10 章。
---
## G8. K 线数组下标解析不可破
**规则**`internal/repo/webapi/binance/mapper.go``mapKline` 的下标解析对应 Binance 官方约定:
```
[0] openTime ms
[1] open string
[2] high string
[3] low string
[4] close string
[5] volume string
[6] closeTime ms
[7] quoteAssetVolume string
[8] numberOfTrades int
[9] takerBuyBaseVolume string
[10] takerBuyQuoteVolume string
[11] ignore
```
**禁止**调整顺序、跳过位置、改用对象解析Binance 这个接口就是数组)。
**为什么**:错一位整张表数据就废了,回滚成本极高。`docs/dev.md` 第 9.2 节有完整说明。
**怎么验证**:动了 `mapper.go` 之后跑一次回填,对比 Binance 网页 K 线图同一根的 OHLCV 是否一致。
---
## G9. 配置项不引入秘钥
**规则**`config/config.yml` 和环境变量列表里不允许出现 `api_key``secret``token``password`DB 密码除外,且必须从环境变量读、不能 hardcode
**为什么**:本服务不需要任何 Binance 私有鉴权,也不向外部传秘密。一旦引入会破坏 G1。
**怎么验证**
```bash
grep -i "api_key\|api_secret\|access_token" config/
# 期望:无输出
```
---
## G10. WebSocket / 实时推送不在 v1 范围
**规则**v1 只用 REST 拉取。不引入 gorilla/websocket、不订阅 `wss://fstream.binance.com`
**为什么**WS 长连接的可观测性、重连、消息丢失处理是独立工程量。v1 用 REST + cron 已经能满足 Hermes 的"分钟级上下文"需求。要做 WS 必须先写 ADR。
**怎么验证**
```bash
grep -rn --include="*.go" "websocket\|fstream.binance" .
# 期望:无输出
```
---
## 升级守护规则的时机
同一类错误第二次出现 → 写一条新规则进来。修改本文件 = 改动了项目契约,需要在 PR 描述里说明原因并 @ 维护者。
新增 Hard Stop 规则时,**必须给出可机械验证的命令**——只能靠 review 来执行的规则会被绕过。