dela 7e31e9cbaf
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
fix: map decrypted CoinGlass liq heatmap payload
2026-05-25 17:12:41 +08:00
2026-05-24 21:18:19 +08:00

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 Architecturecontroller / usecase / repo 分层usecase 只依赖接口。
  • Fiber v2 REST API + robfig/cron 定时采集 + TimescaleDB hypertable 存时序数据。
  • Rate Limit 节流(默认 20 req/s可重复运行的 upsertK 线只入库已收线。

技术栈

类型 选型
语言 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 张 hypertablemarket_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.1Docker 容器访问宿主机代理用 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 containerimage migrate/migrate:v4.18.1跑完立即退出。app 用 depends_on: condition: service_completed_successfully 等它结束,保证 app 启动时表一定存在。

容器 healthcheck

  • postgrespg_isready5s 间隔
  • appwget --spider /v1/health15s 间隔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

合并前再过一遍:

  • .envPOSTGRES_PASSWORD 已改为强密码
  • 国内机器:.envHTTP_PROXY / HTTPS_PROXY 已配
  • docker compose config 渲染后无 ${VAR:?} 报错
  • docker compose up -ddocker compose ps 全部 healthy / exited(0)
  • curl /v1/health 200curl /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.goenv: 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 中的 DSNDocker 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 目前按 primary 1h 计算;technical.intervals 已提供每周期 Bollinger / Vegas。完整 per-interval TechnicalStructure 与箱体突破信号仍在 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 线时间列存毫秒 epochTimescale 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) parseKlineRowtoInt64、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" .   # 期望: 无输出

# G12float64 仅允许在 indicator.go / derivatives_signal.go 内部
grep -rn --include="*.go" "float64\|float32" internal/usecase | grep -vE "indicator|derivatives_signal"
# 期望: 无输出

路线图

  • v1Binance 行情 + 衍生品 + 落库 + /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 仍只按 primary 1h 输出。
    • ⚠️ 箱体与 per-interval 突破信号未实现。
    • testcontainers 集成测试 + CI 未起。
  • v3CoinGlass 清算数据、ETF Flow、CME gap、CVD、链上稳定币流动性、宏观日历、多交易所聚合。

完整设计参见 docs/dev.md


License

Internal project. Not for redistribution.

Description
No description provided
Readme 357 KiB
Languages
Go 97.9%
Makefile 1.6%
Dockerfile 0.5%