- collector.symbols 与 supportedSymbols 加 SOL/BNB/DOGE,env-default 与 README 同步 - entity 追加 TechnicalStructure.Intervals(每周期 Bollinger + Vegas,omitempty 不破坏既有字段)与 DerivativesBundle.Signal - 新增纯解析 usecase derivatives_signal.go:fundingBias 绝对阈值、oiSignal 用 P20 baseline + 价格方向(OI up + 价格 up/down → long/short_building,funding 不参与方向)、lsrRegime 绝对阈值;signal 数据不足走 warnings 进 DataQuality - indicator.go 加 sma/stddev/ema/bollinger/vegas + computeIntervalTechnicals;IndicatorComputer 签名收 klines map - harness 同步:G12 扩入 derivatives_signal.go(含 Makefile G12.5/6 + ADR-0002 适用范围 + project-map / harness-health 更新)
237 lines
11 KiB
Markdown
237 lines
11 KiB
Markdown
# Project Map — cryptoHermes
|
||
|
||
代码 agent 工作的地形图。每个目录写明:**职责**、**可以依赖谁**、**不可以依赖谁**、**扩展点在哪**。
|
||
|
||
完整设计见 `docs/dev.md`。
|
||
|
||
---
|
||
|
||
## 顶层布局
|
||
|
||
```
|
||
cryptoHermes/
|
||
├── cmd/ 二进制入口
|
||
├── config/ cleanenv 加载
|
||
├── internal/ 业务代码(不可被外部 import)
|
||
├── migrations/ SQL(含 hypertable)
|
||
├── pkg/ 项目内可复用基础设施
|
||
├── docs/ 设计文档(dev.md 是源头)
|
||
├── ai/ harness 工程文档(本目录)
|
||
├── docker-compose.yml Timescale + 服务
|
||
├── Dockerfile multi-stage golang:1.23-alpine → alpine:3.20
|
||
├── Makefile 所有验证命令
|
||
└── go.mod Go 1.23
|
||
```
|
||
|
||
---
|
||
|
||
## `cmd/` — 二进制入口
|
||
|
||
| 路径 | 职责 |
|
||
|---|---|
|
||
| `cmd/app/main.go` | 主服务。读 config → 调 `internal/app.Run(cfg)` |
|
||
| `cmd/backfill/main.go` | 历史 K 线回填 CLI。直接装配 binance client + kline repo,不走 fiber |
|
||
|
||
**约束**:`cmd/` 里只允许装配 + 调用,不允许写业务逻辑。
|
||
|
||
---
|
||
|
||
## `config/` — 配置
|
||
|
||
- `config.go`:cleanenv 加载,`yaml + env` 双源。环境变量优先(见 `env:` tag)。
|
||
- `config.yml`:默认配置。**不要在这里放敏感信息**(没有敏感信息可放,因为本服务无私钥)。
|
||
|
||
**扩展点**:新增配置项 → `config.go` 加字段(同时加 `yaml:` 和 `env:` tag)→ `config.yml` 加默认值 → 在 README "配置" 段补一行。
|
||
|
||
---
|
||
|
||
## `internal/` — 业务核心
|
||
|
||
Clean Architecture 四层。**依赖方向单向:`controller → usecase → entity ← repo`**。
|
||
|
||
```
|
||
┌──────────────┐
|
||
│ controller │ HTTP 层:参数校验、调 usecase、序列化
|
||
└──────┬───────┘
|
||
│ depends on
|
||
▼
|
||
┌──────────────┐
|
||
│ usecase │ 业务编排。只依赖 ports.go 中的接口
|
||
└──────┬───────┘
|
||
│ depends on (interface only)
|
||
▼
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ entity │ ◄────── │ repo │ 外部依赖实现
|
||
└──────────────┘ └──────────────┘
|
||
(binance / postgres)
|
||
```
|
||
|
||
### `internal/entity/` — 纯业务实体
|
||
|
||
- `kline.go`, `ticker.go`, `funding.go`, `open_interest.go`, `long_short_ratio.go`, `taker_volume.go`, `market_context.go`
|
||
- **价格/成交量字段全部 `string`**,避免 float64 精度问题。落库时 PG 转 `NUMERIC(36,18)`。
|
||
- `Kline` 有 `IsClosed bool` 字段——未收线 K 线 (`close_time > now()`) 由 mapper 自动判定。
|
||
- `LongShortRatio` 有常量 `RatioTypeGlobalAccount` / `RatioTypeTopTraderPosition` / `RatioTypeTopTraderAccount`。
|
||
- `market_context.go` 还承载技术结构与衍生品信号的输出类型:`TechnicalStructure`(含 `Intervals map[string]IntervalTechnicals`,新增字段对消费方完全可选)、`Bollinger`、`Vegas`、`DerivativesSignal`(`fundingBias` / `oiSignal` / `lsrRegime`)。所有数值仍是 string。
|
||
|
||
**不依赖**:fiber、pgx、binance、任何外部库。可以从这里 import 到任何其他层。
|
||
|
||
### `internal/usecase/` — 业务接口与编排
|
||
|
||
- `ports.go`:**全部对外接口在这里定义**。这是 Clean Arch 的边界文件。
|
||
- `MarketDataProvider`(K线、ticker24h)
|
||
- `DerivativesProvider`(funding/OI/多空比/taker volume 共 7 个方法)
|
||
- `KlineRepository` / `FundingRepository` / `OpenInterestRepository` / `LongShortRatioRepository` / `TakerVolumeRepository`
|
||
- `IndicatorComputer`(pivot S/R + range + LSR 中位 + 每周期 Bollinger/Vegas)
|
||
- `DerivativesSignalComputer`(funding/OI/LSR → fundingBias/oiSignal/lsrRegime;返回 warnings 由 caller 合并到 DataQuality)
|
||
- `market_context.go`:聚合 `/v1/market/context`。errgroup 并发拉 snapshot/funding/OI,DB K 线不足 200 根回源 Binance 补,异步写回。`supportedSymbols` 白名单当前为 BTC/ETH/SOL/BNB/DOGE USDT 五个。
|
||
- `market_query.go`:`/v1/market/derivatives` 后端。`GetDerivatives(symbol, period)` 并发拉 current funding/OI + 串行查 funding/OI/多空比历史,错误降级为 warnings 返回。
|
||
- `indicator.go`、`derivatives_signal.go`:**纯解析 usecase**,零 repo 依赖。允许 transient `float64`(G12 例外),输出转回 string 写入 entity。新增同形态文件时必须同步扩 Makefile G12.1 白名单与 G12.5/6 子规则,参考 `ai/adr/0002-indicator-numeric-boundary.md`。
|
||
|
||
**约束**(grep 可验证):
|
||
```bash
|
||
grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 必须无输出
|
||
```
|
||
usecase 不可直接 import 任何具体实现。新增业务流程 → 在 `ports.go` 加接口 → 在 usecase 编排 → 由 `internal/app/app.go` 注入实现。
|
||
|
||
### `internal/controller/` — HTTP 层
|
||
|
||
- `restapi/router.go` + `middleware.go`:fiber app、recover、logger、request-id
|
||
- `restapi/v1/health_routes.go`:`/v1/health`
|
||
- `restapi/v1/market_routes.go`:`/v1/market/{context,klines,snapshot,derivatives}`
|
||
|
||
**约束**:
|
||
```bash
|
||
grep -r "repo/webapi/binance\|repo/persistent" internal/controller # 必须无输出
|
||
grep -rn "FundingRepo\|OIRepo\|LSRepo\|TakerRepo" internal/controller # 必须无输出(G11)
|
||
```
|
||
controller 只能依赖 usecase 和 entity,不能直接调 binance client 或 postgres repo。衍生品多源查询必须经 usecase(详见 `ai/risk-guardrails.md` G11)。`KlineRepo` 暂时允许直查 `/klines`,因为它现在是 thin query。
|
||
|
||
**扩展点**:新增路由 → 加 handler → 通过 `MarketDeps` 注入依赖(不要直接 import repo 包)。
|
||
|
||
### `internal/repo/` — 外部依赖实现
|
||
|
||
| 子目录 | 实现哪些接口 |
|
||
|---|---|
|
||
| `webapi/binance/` | `MarketDataProvider`、`DerivativesProvider` |
|
||
| `persistent/postgres/` | 5 个 `*Repository` |
|
||
|
||
**Binance client**(`webapi/binance/`):
|
||
- `client.go`:包 `pkg/httpclient`,统一 base URL `https://fapi.binance.com`,错误用 `ExternalAPIError{Source, Path, StatusCode, Message}` 包装。
|
||
- `dto.go`:贴近 Binance 原始响应(K 线是数组)。
|
||
- `mapper.go`:DTO → entity。**K 线按下标解析**(`row[0]=openTime ... row[10]=takerBuyQuoteVolume`),自动设置 `IsClosed = close_time < now()`。
|
||
- `market.go`:`GetKlines` / `GetKlinesRange` / `GetTicker24h`
|
||
- `derivatives.go`:funding × 2 + OI × 2 + 多空比 × 2 + taker volume
|
||
|
||
**Postgres repos**(`persistent/postgres/`):
|
||
- 5 个 repo,每个有 `UpsertMany`(`ON CONFLICT DO UPDATE`)和 `FindRecent`(按时间倒序拉,返回前反转成正序)。
|
||
- `kline_repo.go` 的 `UpsertMany` 会过滤掉 `IsClosed=false` 的 K 线。
|
||
- 使用 pgxpool + transaction。
|
||
|
||
**扩展点**:
|
||
- 新增数据源(CoinGlass / ETF)→ 新建 `webapi/coinglass/`,实现新的 Provider 接口(先在 `ports.go` 定义)。
|
||
- 新增表 → 新 migration + 新 repo + 在 `ports.go` 加新接口。
|
||
|
||
### `internal/worker/` — Collector
|
||
|
||
- `collector.go`:被 cron 调度的采集逻辑。每个 `Collect*` 方法遍历 `symbols × intervals/periods`,拉数据 → upsert。
|
||
- **不直接被 controller 调用**,只通过 `internal/app/scheduler.go` 触发。
|
||
|
||
### `internal/app/` — 装配与生命周期
|
||
|
||
- `app.go`:DI 装配的唯一位置。
|
||
- 顺序:pool → binanceClient → repos → contextUC → collector → scheduler → fiber app
|
||
- graceful shutdown:监听 SIGINT/SIGTERM,10s 超时,先停 cron、再停 fiber、最后关 pool。
|
||
- `scheduler.go`:cron spec 在这里。
|
||
|
||
**这里是 Clean Arch 的"组合根"**——所有具体类型在这里被实例化并注入抽象接口。
|
||
|
||
---
|
||
|
||
## `pkg/` — 项目内可复用基础设施
|
||
|
||
| 包 | 职责 |
|
||
|---|---|
|
||
| `pkg/httpclient/` | 通用 HTTP client + 重试(429/5xx 最多 2 次)+ `x/time/rate` 限流 |
|
||
| `pkg/logger/` | slog 包装(按 env 选 JSON / Text) |
|
||
| `pkg/postgres/` | pgxpool 初始化 + Ping |
|
||
|
||
**约束**:`pkg/` 不依赖 `internal/`,反向依赖。可以被外部项目 import(虽然目前没有)。
|
||
|
||
---
|
||
|
||
## `migrations/` — SQL
|
||
|
||
10 个文件(5 张表 × up/down)。
|
||
|
||
每个 `.up.sql` 末尾必须有:
|
||
```sql
|
||
SELECT create_hypertable('<table>', '<time_col>',
|
||
chunk_time_interval => <ms>,
|
||
if_not_exists => TRUE,
|
||
migrate_data => TRUE);
|
||
```
|
||
|
||
chunk 间隔:
|
||
- K 线 / OI / LS / taker → 1 周 = 604800000 ms
|
||
- funding → 30 天 = 2592000000 ms
|
||
|
||
**改 migration 前必读 `ai/risk-guardrails.md` 中"DB migration"段**。
|
||
|
||
---
|
||
|
||
## 数据流(控制流)
|
||
|
||
### 写路径(采集)
|
||
|
||
```
|
||
cron tick
|
||
└→ internal/app/scheduler.go
|
||
└→ internal/worker/collector.go (Collect*)
|
||
├→ binance.GetXxx (HTTP → DTO → entity)
|
||
└→ repo.UpsertMany (entity → SQL upsert)
|
||
```
|
||
|
||
### 读路径(API)
|
||
|
||
```
|
||
HTTP request
|
||
└→ internal/controller/restapi/v1/market_routes.go
|
||
├→ /context → usecase.MarketContext.Build
|
||
│ ├→ marketData.GetTicker24h ┐
|
||
│ ├→ derivatives.GetCurrent… ├ errgroup 并发
|
||
│ ├→ derivatives.GetCurrent… ┘
|
||
│ ├→ klineRepo.FindRecent × 5 周期
|
||
│ │ └ DB 不足 → 回源 binance.GetKlines(异步写回)
|
||
│ └→ fundingRepo / oiRepo / lsRepo.FindRecent
|
||
├→ /derivatives → usecase.MarketQuery.GetDerivatives
|
||
│ ├→ derivatives.GetCurrentFunding ┐ errgroup
|
||
│ ├→ derivatives.GetCurrentOI ┘
|
||
│ └→ fundingRepo / oiRepo / lsRepo.FindRecent
|
||
├→ /klines → klineRepo.FindRecent(thin query,允许直查)
|
||
└→ /snapshot → marketData.GetTicker24h
|
||
```
|
||
|
||
---
|
||
|
||
## 不可逾越的边界(自动验证见 AGENTS.md §6)
|
||
|
||
1. **controller ↛ binance/postgres**:必须经 usecase。
|
||
2. **usecase ↛ binance/postgres 包**:只依赖 `ports.go` 接口。
|
||
3. **entity ↛ 任何外部库**:可被所有层 import。
|
||
4. **pkg ↛ internal**:基础设施不知道业务。
|
||
5. **任何代码 ↛ 私钥/签名**:grep `apikey|hmac|x-mbx-apikey|secret_key|api_secret` 必须无 Go 代码命中。
|
||
|
||
---
|
||
|
||
## 关键扩展点速查
|
||
|
||
| 场景 | 改哪 |
|
||
|---|---|
|
||
| 新增 REST 路由 | `internal/controller/restapi/v1/`,依赖通过 `MarketDeps` 注入 |
|
||
| 新增业务编排 | `internal/usecase/ports.go` 加接口 → 新 usecase 文件 → `internal/app/app.go` 装配 |
|
||
| 新增数据表 | 新 migration → 新 entity(可选)→ 新 repo → `ports.go` 接口 → 装配 |
|
||
| 新增数据源 | `ai/specs/<source>.md` → 新 `internal/repo/webapi/<source>/` → 新 Provider 接口 |
|
||
| 新增定时任务 | `internal/worker/collector.go` 加方法 → `internal/app/scheduler.go` 注册 cron |
|
||
| 改配置项 | `config/config.go` 加字段 → `config/config.yml` 加默认 → README 记录 |
|