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 无具体实现依赖,全项目 无私钥/签名字段。
7.0 KiB
ADR 0001 — 架构基础决策
状态:Accepted 日期:2026-05-24 决策者:项目维护者 适用范围:cryptoHermes v1 全部代码
背景
cryptoHermes 是给上游 Hermes 量化分析引擎用的 Binance Futures 行情网关。在 MVP 启动前需要锁定一组基础技术决策,避免后续 PR 反复重新评估。
需求边界(来自 docs/dev.md 第 1 章):
- 只读公共行情,不下单、不接私钥
- BTCUSDT / ETHUSDT,分钟到周级 K 线 + 衍生品(funding / OI / 多空比 / taker volume)
- 历史数据必须自存(Binance 历史 OI / 多空比官方只保留 30 天)
- Hermes 只调用
/v1/market/context一个聚合接口 - 部署形态:单体服务 + Postgres,国内开发机通过 HTTPS_PROXY 访问 Binance
决策
D1. Web 框架:Fiber v2
选了 Fiber v2(而不是 net/http 标准库、Gin、Echo、Chi)。
理由:
- 性能足够(fasthttp 底层),单体网关不需要再调优
- API 与 Express/Gin 相近,团队上手快
- 内置 recover/logger/req-id 中间件
- v2 稳定,社区活跃
代价:
- 基于 fasthttp,不兼容
net/http中间件生态 - 错误类型用
*fiber.Error,需要在 controller 包一层
何时重新评估:如果需要 HTTP/2 server push、需要复用 net/http 生态中间件、或者 v2 长期不维护——届时考虑迁回标准库。
D2. 数据库:TimescaleDB on PG 16
选了 TimescaleDB(而不是纯 Postgres、InfluxDB、ClickHouse)。
理由:
- 时序数据天然适合 hypertable:5 张表全部按时间分片,查询近期数据走最新 chunk,旧数据自动归档
- SQL 接口和普通 PG 完全兼容,pgx 直连,无需新驱动
- 没有引入新的运维栈(开发机用 Docker 起
timescale/timescaledb:latest-pg16即可) - Chunk 间隔可按表调(K 线 1 周、funding 30 天)
代价:
- 必须跑
SELECT create_hypertable(...)才能享受时序优化,新表容易忘 - 升级 PG 大版本需要看 Timescale 兼容矩阵
何时重新评估:如果数据量进入 TB 级、需要列存压缩或者 OLAP 复杂聚合——考虑 ClickHouse。
D3. Architecture:Clean Architecture(4 层 + ports.go 接口边界)
4 层:controller / usecase / repo / entity。
接口边界:internal/usecase/ports.go 是唯一定义对外接口的地方。
理由:
- 上游 Hermes 只通过 REST 调用本服务,但 Binance 这个数据源大概率会扩展到 CoinGlass / ETF / CME。把数据源抽象成
MarketDataProvider/DerivativesProvider接口,新增数据源只需要新建internal/repo/webapi/<source>/,不动业务编排。 - 单测可以 mock 接口,不需要起真 DB / 真 Binance。
internal/app/app.go是唯一的组合根,DI 集中可见。
强制约束(见 ai/risk-guardrails.md G6):
controller 不可直接 import binance/postgres
usecase 不可直接 import binance/postgres
代价:
- 多一层
ports.go,每个新接口要写两次(接口 + 实现) - 对小型项目可能略 overkill,但本项目已经规划到 v3(多数据源),早做减少返工
何时重新评估:如果 v2 之后接口数量爆炸(> 30 个)→ 考虑按子域拆分 ports.go 为多个文件。
D4. 传输层:REST-only(v1 不上 gRPC / 消息队列 / WebSocket)
选了 REST(而不是 gRPC、NATS、WS 推送)。
理由:
- Hermes 是同语言 Go 服务,但跨服务调用频率是分钟级(拿一次 context),REST + JSON 完全够
- gRPC 多一份 proto 维护负担,团队/工具收益不明显
- WebSocket 推送是独立工程量(重连、消息丢失、订阅状态机),v1 不进入
- Cron + REST 拉取模型对运维友好(任何 HTTP 监控就够)
代价:
- 如果未来 Hermes 需要 sub-second 行情,REST 拉模型撑不住
何时重新评估:Hermes 接入实盘策略后,如果发现"分钟级"延迟成为瓶颈 → 写新 ADR 引入 WS。
D5. 价格 / 成交量:全链路 string
所有金额字段端到端用 string:Binance DTO 是 string、entity 是 string、JSON 响应是 string。落库时由 PG 转 NUMERIC(36,18)。
理由:
- Binance 原样返回字符串,避免 float64 转换丢精度
108000.12这种数 float64 表示就会丢尾数;累积统计后偏差更大- 上游量化分析对价格精度敏感,宁可让 Hermes 自己决定怎么解析
代价:
- 加减乘除需要 PG 端做或者引入 decimal 库
- JSON 响应里 price 是字符串,前端/客户端要适配
强制约束:见 ai/risk-guardrails.md G2。
D6. 数据源边界:只用 Binance USDⓈ-M Futures 公共 API
v1 不引入 CoinGlass、Glassnode、ETF Flow、CME、链上数据。
理由:
- Binance 一家覆盖 80% 的需求(K 线 / 衍生品 / 多空比)
- 引入新数据源等于引入新的鉴权、限流、错误模式、SLA——v1 优先把一条链路打通
- 路线图 v2 / v3 才扩展数据源(README "路线图" 段)
强制约束:
- 新增数据源前必须先写
ai/specs/<source>.md(见 AGENTS.md §10) - 不可使用 Binance 的私有签名接口(见 G1)
D7. 配置层:cleanenv(yaml + env override)
选了 cleanenv(而不是 viper、koanf、env-only)。
理由:
- 一份 yaml 默认值 + 环境变量覆盖,开发和部署都方便
- 比 viper 轻,没有多余抽象
env-default:tag 让默认值跟字段定义放在一起,可读性好
代价:无明显缺点。如果后续需要热重载(不需要)才会考虑换。
D8. 限流:golang.org/x/time/rate token bucket
挂在 pkg/httpclient/client.go 里,默认 RPS=20 / Burst=40,从配置读。
理由:
- 标准库附属,零外部依赖
- token bucket 适合突发场景
- Binance IP 限 2400 weight/min,20 req/s × 60s = 1200 req/min,留出一半余量给重试和 backfill
强制约束:所有外部 HTTP 调用走 pkg/httpclient,见 G5。
D9. Scheduler:robfig/cron v3
选了 robfig/cron v3(而不是自己写 ticker、go-co-op/gocron)。
理由:
- cron 表达式所有人都看得懂,schedule 修改是配置而非代码
- 稳定老牌,3.0 之后无破坏性变化
- 和 Fiber 在同一进程内运行,graceful shutdown 容易做
代价:
- 没有分布式锁——但本服务 v1 是单实例,不需要
不在本 ADR 范围
- 下单 / 账户 / 私钥:永久禁止(G1)。要做必须新仓库。
- 多交易所聚合:v3 才考虑,届时再写 ADR。
- WS / 实时推送:见 D4,需要单独写 ADR。
- 观测性(metrics / tracing):MVP 阶段只用 slog 结构化日志,Prometheus / OTel 留待生产部署前评估。
后续 ADR 的触发条件(来自 AGENTS.md §10)
- 换 HTTP 框架 → 新 ADR
- 引入消息队列 / WS → 新 ADR
- 跨服务通信改 gRPC → 新 ADR
- 数据库换型(PG → ClickHouse)→ 新 ADR
- 引入新数据源 →
ai/specs/<source>.md(不一定要 ADR,除非影响整体架构)