dev.md 是 v1 设计稿但路线图已与 README 冲突;加废弃声明 + 优先级表,
指向 README / AGENTS / ai/adr 作为权威来源。AGENTS §2 与 harness-health
同步 v2.x 进度(Bollinger / Vegas / derivatives signal ✅;per-interval
完整化 + 箱体 + CI 未完)。README 路线图分段 v2.0/v2.x/v3,与 G12 守卫
扩白名单(derivatives_signal.go)保持一致。
14 KiB
cryptoHermes — Market Data Gateway
为上游 Hermes 量化分析引擎提供统一的币圈行情与衍生品上下文。Hermes 只需要调用本服务的 /v1/market/context 一个接口,就能拿到 BTC/ETH 多周期 K 线、当前价、funding、OI、多空比等用于策略分析的数据。
本服务只做行情采集与聚合,不下单、不接私钥、不查账户、不托管任何资金。
特性
- Binance USDⓈ-M Futures 公共接口,无需 API Key。
- 多周期 K 线 / 24h ticker / funding / Open Interest / 全局多空比 / 大户持仓多空比 / Taker 主动买卖量。
- 历史数据自动落库(Binance 官方历史 OI / 多空比只保留 30 天,必须自己存)。
- Clean Architecture:controller / usecase / repo 分层,usecase 只依赖接口。
- Fiber v2 REST API + robfig/cron 定时采集 + TimescaleDB hypertable 存时序数据。
- Rate Limit 节流(默认 20 req/s),可重复运行的 upsert,K 线只入库已收线。
技术栈
| 类型 | 选型 |
|---|---|
| 语言 | Go 1.23 |
| HTTP | Fiber v2 |
| DB | TimescaleDB (PG 16) |
| DB Driver | pgx v5 (pgxpool) |
| Scheduler | robfig/cron v3 |
| Config | cleanenv (yaml + env) |
| 限流 | golang.org/x/time/rate |
| Migration | golang-migrate CLI |
快速开始
1. 依赖
# Go 1.23+
go version
# golang-migrate CLI(用于跑 migration)
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
2. 启动数据库
make docker-up
会用 timescale/timescaledb:latest-pg16 启一个 Postgres,监听本地 5432。
3. 执行 migration
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/hermes_market?sslmode=disable"
make migrate-up
会建 5 张 hypertable:market_klines / funding_rates / open_interest / long_short_ratio / taker_buy_sell_volume。
4. (可选)回填历史 K 线
第一次启动时 DB 是空的,直接调 /v1/market/context 会触发回源 Binance。建议先回填:
for s in BTCUSDT ETHUSDT SOLUSDT BNBUSDT DOGEUSDT; do
for i in 15m 1h 4h 1d 1w; do
make backfill ARGS="--symbol $s --interval $i --limit 500"
done
done
也可以指定起止时间做大段回填:
make backfill ARGS="--symbol BTCUSDT --interval 1h --from 1704067200000 --to 1735689600000"
5. 启动服务
make run
服务会:
- 监听
:8080 - 立刻启动 cron,每 15 分钟拉一次 funding / OI / 多空比 / taker volume / 15m K 线,整点拉 1h,每 4h 拉 4h,每日 00:05 拉 1d,每周一 00:10 拉 1w。
6. 验证
curl -s localhost:8080/v1/health | jq .
curl -s 'localhost:8080/v1/market/context?symbol=BTCUSDT' | jq 'keys'
一键部署(docker compose)
适合生产或测试环境快速起服务。3 步上线:
# 1. 准备环境变量
cp .env.example .env
# 编辑 .env,必须填写 POSTGRES_PASSWORD(留空时 compose 直接拒绝启动)
# 国内机器还要确认 GO_MODULE_PROXY,并按需填写 HTTP_PROXY / HTTPS_PROXY
# 如果 .env 是旧文件,补一行:GO_MODULE_PROXY=https://goproxy.cn,direct
# HTTP_PROXY / HTTPS_PROXY 不要填 127.0.0.1;Docker 容器访问宿主机代理用 host.docker.internal
# 2. 启动(自动建表)
docker compose up -d
# 3. 验证
curl -s http://localhost:8080/v1/health | jq .
docker compose ps # 应看到 3 行:postgres healthy / migrate exited(0) / app running
启动顺序
docker compose up 会按如下依赖链推进,任何一环失败都会阻断:
postgres 起 → pg_isready healthcheck 通过
→ migrate 容器跑 migrations/ up → 退出 0
→ app 起,连 DB,启 cron
migrate 是一次性 init container(image migrate/migrate:v4.18.1),跑完立即退出。app 用 depends_on: condition: service_completed_successfully 等它结束,保证 app 启动时表一定存在。
容器 healthcheck
- postgres:
pg_isready,5s 间隔 - app:
wget --spider /v1/health,15s 间隔,start_period 20s
docker compose ps 会显示 (healthy) 或 (unhealthy)。k8s / 反向代理 / LB 可直接根据 healthcheck 状态做流量切换。
优雅停机
docker compose stop 默认 10s,本项目设 stop_grace_period: 30s:
- app 收到 SIGTERM
- fiber
ShutdownWithContext(10s)等当前 HTTP 请求结束 - cron Scheduler.Stop() 等当前 collector 任务结束
- 30s 超时后 docker 才发 SIGKILL
日志
容器 stdout 走 docker json-file driver:
- 单文件最大 50 MB
- 保留 5 个滚动文件
- 应用业务日志额外挂载到宿主机
./logs/
查实时日志:
docker compose logs -f app
docker compose logs -f postgres
回填历史
启动后 DB 是空的;调用 /v1/market/context 会 fallback 到 Binance(带 warnings)。要立刻获得满载历史,进容器跑 backfill:
for s in BTCUSDT ETHUSDT SOLUSDT BNBUSDT DOGEUSDT; do
docker compose exec app /app/backfill --symbol $s --interval 1h --limit 500
done
# 其它 interval 同理
或等 cron 自然落库(15m K 线每 15 分钟一次,1h 每整点一次)。
生产 checklist
合并前再过一遍:
.env中POSTGRES_PASSWORD已改为强密码- 国内机器:
.env中HTTP_PROXY/HTTPS_PROXY已配 docker compose config渲染后无${VAR:?}报错docker compose up -d后docker compose ps全部 healthy / exited(0)curl /v1/health200,curl /v1/market/context?symbol=BTCUSDT返回非空technical- 宿主机
./logs/目录有 app 写入权限(默认 alpine root 用户写没问题) make guard在仓库根目录全绿(即使是 deploy 修改,守卫规则也必须不破)
国内网络
fapi.binance.com 国内直连不稳定,通过环境变量挂代理即可(Go 默认尊重 HTTPS_PROXY):
HTTPS_PROXY=http://127.0.0.1:7890 make run
配置
config/config.yml(所有字段都可被环境变量覆盖,参见 config/config.go 的 env: tag):
app:
port: 8080
env: local
binance:
base_url: https://fapi.binance.com
timeout: 10s
retry_count: 2
rps: 20 # IP 级限速,超过会自动 sleep
burst: 40
postgres:
dsn: postgres://postgres:postgres@localhost:5432/hermes_market?sslmode=disable
max_conns: 10
min_conns: 2
collector:
enabled: true # 设为 false 可只跑只读 API,不开 cron
symbols: [BTCUSDT, ETHUSDT, SOLUSDT, BNBUSDT, DOGEUSDT]
intervals: [15m, 1h, 4h, 1d, 1w]
default_limit: 500
常用环境变量:
| 变量 | 说明 |
|---|---|
CONFIG_PATH |
配置文件路径,默认 config/config.yml |
POSTGRES_DSN |
覆盖 yaml 中的 DSN(Docker Compose 里就是这么干的) |
APP_PORT |
HTTP 端口;compose 部署中容器内固定为 8080 |
APP_HOST_PORT |
docker compose 暴露到宿主机的 HTTP 端口 |
GO_MODULE_PROXY |
Docker build 阶段下载 Go module 使用的 Go proxy,不是 HTTP 代理 |
HTTPS_PROXY |
走代理访问 Binance |
API
GET /v1/health
{ "status": "ok", "time": 1717000000000 }
GET /v1/market/context?symbol=BTCUSDT
Hermes 主接口。聚合返回 K 线 + 衍生品 + 数据质量信息:
{
"symbol": "BTCUSDT",
"generatedAt": 1717000000000,
"snapshot": {
"lastPrice": "108000.12",
"priceChangePercent": "2.14",
"highPrice": "109200.00",
"lowPrice": "104800.00",
"volume": "...",
"quoteVolume": "..."
},
"klines": {
"15m": [ /* 300 根 */ ],
"1h": [ /* 300 根 */ ],
"4h": [ /* 300 根 */ ],
"1d": [ /* 300 根 */ ],
"1w": [ /* 300 根 */ ]
},
"derivatives": {
"funding": { "current": { ... }, "history": [ ... ] },
"openInterest": { "current": { ... }, "history": [ ... ] },
"longShortRatio": {
"global": [ ... ],
"topTraderPosition": [ ... ]
},
"takerBuySellVolume": []
},
"technical": {
"support": [], "resistance": [],
"rangeHigh": null, "rangeLow": null, "longShortLine": null
},
"dataQuality": {
"source": "binance",
"warnings": []
}
}
technical.support/resistance/rangeHigh/rangeLow/longShortLine目前按 primary1h计算;technical.intervals已提供每周期 Bollinger / Vegas。完整 per-intervalTechnicalStructure与箱体突破信号仍在 v2.x 待办。
GET /v1/market/klines?symbol=BTCUSDT&interval=1h&limit=300
直接读 DB 返回 K 线数组。interval 取值 15m/1h/4h/1d/1w。
GET /v1/market/snapshot?symbol=BTCUSDT
24h ticker 实时拉取(不走 DB)。
GET /v1/market/derivatives?symbol=BTCUSDT&period=1h
衍生品聚合:funding / OI / 多空比。period 默认 1h。
目录结构
cryptoHermes/
├── cmd/
│ ├── app/ # 主服务入口
│ └── backfill/ # 历史 K 线回填 CLI
├── config/ # cleanenv 配置
├── internal/
│ ├── app/ # 装配 DI、cron scheduler、graceful shutdown
│ ├── controller/ # Fiber 路由 + middleware
│ ├── entity/ # 业务实体(不依赖 Fiber/DB/Binance)
│ ├── usecase/ # 业务接口与编排
│ ├── repo/
│ │ ├── webapi/binance/ # Binance HTTP client
│ │ └── persistent/postgres/ # 5 个 repository
│ └── worker/ # 实际跑采集逻辑的 collector
├── migrations/ # SQL migration(含 hypertable 转换)
├── pkg/
│ ├── httpclient/ # 通用 HTTP client + retry + rate limit
│ ├── logger/ # slog 包装
│ └── postgres/ # pgxpool 初始化
├── docs/dev.md # 完整设计文档
├── docker-compose.yml
├── Dockerfile
├── Makefile
└── go.mod
数据库
5 张 hypertable,主键设计保证 upsert 幂等:
| 表 | 主键 | 时间列 |
|---|---|---|
market_klines |
(source, symbol, interval, open_time) |
open_time |
funding_rates |
(source, symbol, funding_time) |
funding_time |
open_interest |
(source, symbol, period, timestamp) |
timestamp |
long_short_ratio |
(source, symbol, period, ratio_type, timestamp) |
timestamp |
taker_buy_sell_volume |
(source, symbol, period, timestamp) |
timestamp |
K 线时间列存毫秒 epoch,Timescale chunk 间隔默认 1 周(衍生品表)/ 30 天(funding)。
常用命令
make run # 跑服务
make build # 编译两个二进制到 ./bin/
make test # 跑测试
make test-race # -race 模式跑测试
make test-cover # 跑测试 + 输出总覆盖率
make guard # 跑全部可机械验证的守卫规则(G1/G6/G11/G12)
make tidy # go mod tidy
make docker-up # 起 Timescale
make docker-down # 停 docker
make migrate-up # apply migration
make migrate-down # 回滚 1 步
make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 500"
测试与覆盖率
v2 第一次把测试体系立住。当前覆盖:
| 包 | 测试范围 | 覆盖率 |
|---|---|---|
internal/usecase |
pivot S/R、percentile、LSR 穿越、Bollinger、Vegas、derivatives signal | 算法路径有表驱动与边界用例覆盖 |
internal/repo/webapi/binance (mapper.go) |
parseKlineRow、toInt64、6 个 map* 函数 |
9 个函数全部 100% |
internal/repo/persistent/postgres |
5 个 repo 的 UpsertMany + FindRecent 正反例(pgxmock) |
包级 87.6% |
跑全部测试 + 覆盖率:
make test-race
make test-cover
测试选型说明:
- 算法(
indicator.go/derivatives_signal.go)采用 table-driven +testify/require。 - Repo 层用
pgxmock/v4锁 SQL 字符串与参数顺序——schema 漂移立刻可见。 *pgxpool.Pool不能直接 mock,因此 5 个 repo 的字段类型被改为内部pgxPool接口(internal/repo/persistent/postgres/pool.go),生产代码继续传*pgxpool.Pool,调用方零改动。- 集成测试(testcontainers + 真 Postgres + 真 migration)留 v2.1 做 nightly。
Clean Architecture 边界(验收用)
# 一次跑全部 G1 / G6 / G11 / G12 守卫
make guard
或单独:
# controller 不应直接依赖 binance client
grep -r "repo/webapi/binance" internal/controller # 期望: 无输出
# usecase 不应直接依赖具体 binance/postgres 实现
grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 期望: 无输出
# 全项目不应出现私钥/账户/签名相关字段
grep -ri "apikey\|x-mbx-apikey\|hmac" . # 期望: 无输出
# G12:float64 仅允许在 indicator.go / derivatives_signal.go 内部
grep -rn --include="*.go" "float64\|float32" internal/usecase | grep -vE "indicator|derivatives_signal"
# 期望: 无输出
路线图
- v1:Binance 行情 + 衍生品 + 落库 +
/v1/market/context聚合接口。✅ - v2.0:技术结构计算第一波——pivot 支撑/压力 + P95/P5 区间 + LSR 穿越价位中位数(primary interval
1h)。算法测试 + repo / mapper 关键路径测试到位。✅ - v2.x(当前):进行中。
- ✅ Bollinger / Vegas 已落地到
technical.intervals。 - ✅ Derivatives signal 已落地到
derivatives.signal。 - ⚠️ 完整 per-interval
TechnicalStructure尚未升级,Support / Resistance / Range / LSLine 仍只按 primary1h输出。 - ⚠️ 箱体与 per-interval 突破信号未实现。
- ❌ testcontainers 集成测试 + CI 未起。
- ✅ Bollinger / Vegas 已落地到
- v3:CoinGlass 清算数据、ETF Flow、CME gap、CVD、链上稳定币流动性、宏观日历、多交易所聚合。
完整设计参见 docs/dev.md。
License
Internal project. Not for redistribution.