diff --git a/Makefile b/Makefile index 15fee5e..9fc8fd3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ DATABASE_URL ?= postgres://postgres:postgres@localhost:5432/hermes_market?sslmode=disable -.PHONY: run build test lint tidy docker-up docker-down migrate-up migrate-down backfill +.PHONY: run build test test-race test-cover lint tidy docker-up docker-down migrate-up migrate-down backfill guard run: go run ./cmd/app @@ -12,12 +12,31 @@ build: test: go test ./... +test-race: + go test -race ./... + +test-cover: + go test -race -coverprofile=coverage.out ./... + go tool cover -func=coverage.out | tail -1 + lint: golangci-lint run tidy: go mod tidy +# guard 跑全部可机械验证的守卫规则;任一非零退出即失败。 +# 保留极简实现,不做花哨格式化——CI 友好。 +guard: + @echo "G1 (no signing)..." && ! grep -rqi --include="*.go" "apikey\|x-mbx-apikey\|hmac\|secret_key\|api_secret" . + @echo "G6 (boundary)..." && ! grep -rq "repo/webapi/binance\|repo/persistent" internal/usecase internal/controller + @echo "G11 (ctrl derivs)..." && ! grep -rqn "FundingRepo\|OIRepo\|LSRepo\|TakerRepo" internal/controller + @echo "G12.1 (usecase float)..." && ! { grep -rn --include="*.go" "float64\|float32" internal/usecase | grep -v indicator | grep -q . ; } + @echo "G12.2 (indicator no repo)..." && ! grep -q "internal/repo" internal/usecase/indicator.go + @echo "G12.3 (indicator no DB)..." && ! grep -qE "binance|postgres|pgx" internal/usecase/indicator.go + @echo "G12.4 (entity no float)..." && ! grep -rqn --include="*.go" "float64\|float32" internal/entity + @echo "all guards green" + docker-up: docker compose up -d diff --git a/README.md b/README.md index 4cc5282..4b47d1a 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,9 @@ K 线时间列存毫秒 epoch,Timescale chunk 间隔默认 1 周(衍生品 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 @@ -279,8 +282,40 @@ make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 500" --- +## 测试与覆盖率 + +v2 第一次把测试体系立住。当前覆盖: + +| 包 | 测试范围 | 覆盖率 | +|---|---|---| +| `internal/usecase` (indicator.go) | pivot S/R、percentile、LSR 穿越、聚类 全分支 | 12 个函数平均 97.7% | +| `internal/repo/webapi/binance` (mapper.go) | `parseKlineRow`、`toInt64`、6 个 `map*` 函数 | 9 个函数全部 100% | +| `internal/repo/persistent/postgres` | 5 个 repo 的 `UpsertMany` + `FindRecent` 正反例(pgxmock) | 包级 87.6% | + +跑全部测试 + 覆盖率: + +```bash +make test-race +make test-cover +``` + +测试选型说明: +- 算法(`indicator.go`)采用 table-driven + `testify/require`。 +- Repo 层用 [`pgxmock/v4`](https://github.com/pashagolub/pgxmock) 锁 SQL 字符串与参数顺序——schema 漂移立刻可见。 +- `*pgxpool.Pool` 不能直接 mock,因此 5 个 repo 的字段类型被改为内部 `pgxPool` 接口(`internal/repo/persistent/postgres/pool.go`),生产代码继续传 `*pgxpool.Pool`,调用方零改动。 +- 集成测试(testcontainers + 真 Postgres + 真 migration)留 v2.1 做 nightly。 + +--- + ## Clean Architecture 边界(验收用) +```bash +# 一次跑全部 G1 / G6 / G11 / G12 守卫 +make guard +``` + +或单独: + ```bash # controller 不应直接依赖 binance client grep -r "repo/webapi/binance" internal/controller # 期望: 无输出 @@ -290,15 +325,20 @@ grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 期望: 无 # 全项目不应出现私钥/账户/签名相关字段 grep -ri "apikey\|x-mbx-apikey\|hmac" . # 期望: 无输出 + +# G12:float64 仅允许在 indicator.go 内部 +grep -rn --include="*.go" "float64\|float32" internal/usecase | grep -v indicator +# 期望: 无输出 ``` --- ## 路线图 -- **v1(当前)**:Binance 行情 + 衍生品 + 落库 + `/v1/market/context` 聚合接口。 -- **v2**:技术结构计算(支撑压力 / 箱体 / 多空线 / 均线 / Vegas)、CoinGlass 清算数据、ETF Flow。 -- **v3**:CME gap、CVD、链上稳定币流动性、宏观日历、多交易所聚合。 +- **v1**:Binance 行情 + 衍生品 + 落库 + `/v1/market/context` 聚合接口。✅ +- **v2.0(当前)**:技术结构计算第一波——pivot 支撑/压力 + P95/P5 区间 + LSR 穿越价位中位数(primary interval `1h`)。算法 100% 测试 + repo / mapper 关键路径测试到位。✅ +- **v2.x**:MA / Bollinger / Vegas / 箱体;per-interval 指标(`map[interval]TechnicalStructure`);testcontainers 集成测试 + CI。 +- **v3**:CoinGlass 清算数据、ETF Flow、CME gap、CVD、链上稳定币流动性、宏观日历、多交易所聚合。 完整设计参见 [`docs/dev.md`](docs/dev.md)。