Skip to content

Latest commit

 

History

History
296 lines (214 loc) · 11.4 KB

File metadata and controls

296 lines (214 loc) · 11.4 KB

DEV_GUIDE.md — 开发指南

🚨 第一条铁律:代码改动必须同步文档

任何代码修改都必须在同一次提交里同步更新对应文档。

  • 没有文档更新的代码改动 = 未完成,不得 commit。
  • Commit message 末尾必须有"文档同步"段落。
  • 完整的"代码路径 → 文档"映射表见 ../CLAUDE.md

Commit 前自检流程(强制)

# 1. 单测必须 100% 通过
pytest tests/ -q

# 2. 自检:代码改动是否覆盖文档
git diff --stat | grep -E "\.(py|j2|yaml|json)$"   # 列出代码改动
git diff --stat | grep -E "docs/.*\.md$"           # 列出文档改动
# 两者都不应为空;纯重构也至少要在对应 module md 留改动痕迹

# 3. 检查新建/删除的文件
git status --short
# 新建 .py → 文档对应 module md 的"文件清单"表里加了行?
# 删除 .py → 文档表里那一行删了?
# 新建模块(新目录)→ 新建 docs/modules/<新>.md + 改 4 份顶层 md?

代码 ↔ 文档同步检查表

按改动位置查"必须更新的文档"。完整版见 ../CLAUDE.md 的同步映射表。常用速查:

改了哪里 至少改这些 md
tickbridge/sources/*.py modules/tickbridge.md + 如新增 fetcher 还要改 modules.md / CONTEXT.md / data_flow.md
stocklens/pipeline/* pipeline.md;流程改动还要改 DATA_FLOW.md
stocklens/services/scoring/* services.md + 如改公式还要改 ARCHITECTURE.md;建议同时加 tests/unit/test_*.py
stocklens/storage/models.py storage.md(表清单)+ DATA_FLOW.md(写入阶段)
stocklens/config/registry_fields.py config.md + .env.example
stocklens/utils/concurrency.py concurrency.md(池清单)+ DATA_FLOW.md(并发资源)
stocklens/notification/senders/* notification.md + README "推送渠道"清单
api/v1/endpoints/* api.md(端点表)
bot/commands/* bot.md + README "Bot 集成"
main.py(启动逻辑) main.md + 本文件"常用命令"
templates/*.j2 notification.md
strategies/*.yaml agent.md

Commit Message 模板

type: 一句话主题

具体改动列表:
- ...

文档同步:
- docs/modules/xxx.md: 更新 yyy 段落
- docs/MODULES.md: 修订文件清单(如适用)
- README.md: 更新 zzz(如适用)

typefeat / fix / refactor / perf / docs / test / chore

写完 commit message 再扫一遍:"文档同步"段落空的?回去补文档再 commit。


Git 提交规范

  • 使用上面的 commit message 模板
  • 描述简洁明了,中英文皆可
  • dev 分支开发
  • 别用 --no-verify 跳过 hook

禁止提交的文件

  • .env(敏感信息)
  • data/logs/reports/stocklens/reports/(运行时生成)
  • .venv/ / venv/(虚拟环境)
  • static/(如果以后加回前端,构建产物不入库)

.env.example 是允许提交的。


常用命令

# 激活虚拟环境(项目使用 .venv)
source .venv/bin/activate

# 一次性安装项目(editable 模式,需要 pip >= 23)
pip install -e .

# 启动 web 服务(无参;监听地址走 WEBUI_HOST / WEBUI_PORT,默认 0.0.0.0:8000)
python main.py

# 自定义端口启动
WEBUI_PORT=5678 python main.py

# 跑全部单测
pytest tests/ -q

# 详细输出
pytest tests/unit/ -v

# 跑独立子包测试
pytest tickbridge/tests/test_smoke.py tickbridge/tests/test_cache.py -q
pytest querybus/tests/ -q
pytest pulsefan/tests/ -q

# Lint + 格式化(ruff)
ruff check .                  # 检查
ruff check . --fix            # 自动修复(仅安全的:unused imports / isort)
ruff format .                 # 格式化

# 安装 git hook(提交前自动跑 ruff + 通用 hygiene 检查)
pre-commit install            # 一次性安装到本地 .git/hooks/
pre-commit run --all-files    # 手动跑全仓

# 测试覆盖率(当前 19.0% / 516 测试,逐步往上;目标 35%)
pytest tests/ --cov                                       # 终端汇总
pytest tests/ --cov --cov-report=html                    # 生成 htmlcov/index.html

# 重置管理员密码(auth 启用时)
stocklens-reset-password      # 通过 entry_points 注册的 CLI

DB Schema Migrations (Alembic, G2)

启动时 DatabaseManager.__init__ 会自动跑 alembic upgrade head。本地手动用:

# 查看当前 DB 的 revision
alembic current

# 查看所有 revisions
alembic history

# 升级到最新(应用新 migration)
alembic upgrade head

# 降级到上一版(极少用;baseline 的 downgrade 是 no-op)
alembic downgrade -1

改 schema 的标准流程(铁律 #3):

  1. stocklens/storage/models.py 修改 ORM 类(加字段 / 加表 / 改索引)
  2. 跑自动生成:
    alembic revision --autogenerate -m "add_xxx_column_to_yyy"
  3. 检查生成的 migration 文件alembic/versions/<hash>_xxx.py):
    • SQLite + render_as_batch=True 会自动生成 batch 操作(CREATE NEW + COPY + RENAME 模式,绕开 SQLite 的 ALTER TABLE 限制)
    • 不要相信 autogenerate 100% 正确:某些情况下(比如改 server_default、加 CHECK constraint)需要手工调整
    • 如果是数据迁移(不是纯结构),加 op.execute("UPDATE ...") 步骤
  4. 跑一次 alembic upgrade head 在本地 DB 上确认 migration 工作
  5. 同次提交models.py + alembic/versions/<hash>_xxx.py + 相关文档更新

baseline migration49a950e262eb_baseline_existing_schema.py)锁定了 G2 接入时的 schema。它的 upgrade()Base.metadata.create_all(idempotent),所以同一个 baseline 既能初始化新 DB 也能在已有 DB 上 stamp 一个 alembic_version 行。永远不要修改这个文件

override DB URL(CI / 多环境):

ALEMBIC_DATABASE_URL="sqlite:////tmp/test.db" alembic upgrade head

启动时也会读这个变量,所以 docker-compose 里可以独立配置。

Docker 部署

项目带一份 production-grade Dockerfile + docker-compose.yml

# 1. 准备配置(首次)
cp .env.example .env
# 编辑 .env:填 STOCK_LIST / LLM key / 推送 webhook

# 2. 一键构建并启动
docker compose up -d --build

# 3. 查看日志
docker compose logs -f stocklens

# 4. 停止
docker compose down

# 端口可在 .env 调整 HOST_PORT,CPU/内存上限通过
# STOCKLENS_CPU_LIMIT / STOCKLENS_MEM_LIMIT 调(默认 2 核 / 2 GiB)

镜像设计要点:

  • 二阶段构建builder(含 gcc/headers)解析依赖 → runtime(slim,只带 site-packages)。
  • 非 root 用户:UID/GID=1000 跑应用;./data ./logs 通过 bind mount 持久化。
  • tini PID 1:信号转发,docker compose down 优雅关闭。
  • 健康检查:每 30s 拉 /api/health,3 次失败标记 unhealthy。
  • .dockerignore 严格剔除.git/data/logs/tests/.venv/docs/ 都不进镜像,最终镜像 ~250 MiB(基线 python:3.11-slim ≈ 130 MiB)。

fail-open 日志规范

业务代码里大量"主路径失败 → 退化到次路径"的 try/except(DB → API → cache、多源 fall-over、可选指标采集),统一用 stocklens.utils.fail_open.fail_open(tickbridge 端用 tickbridge.core.fail_open.fail_open):

from stocklens.utils.fail_open import fail_open

try:
    weekly_df = fetcher.get_weekly_data(code)
except Exception as exc:  # noqa: BLE001 — DB-only fallback below
    fail_open(logger, "weekly_data fetch", exc)
    weekly_df = None

历史上散落的 logger.debug("silent except (%s) at %s:%d: %s", ...) 这种"留痕格式"已经全部迁移(45 处),现在都长得一样:[fail-open] <op>: <ExcType>: <msg> | <detail>,可以 grep "\[fail-open\]" 精准捞排查线索。默认级别是 WARNING(生产可见),需要 DEBUG 时显式传 level=logging.DEBUG

API 限流环境变量(slowapi)

变量 默认 说明
STOCKLENS_RATE_LIMIT_ENABLED true false 关闭所有限流
STOCKLENS_RATE_LIMIT_DEFAULT 120/minute 全局默认限制
STOCKLENS_RATE_LIMIT_ANALYZE 30/minute /api/v1/analysis/analyze
STOCKLENS_RATE_LIMIT_LOGIN 10/minute /auth/login(如使用 slowapi 限流)
STOCKLENS_RATE_LIMIT_STORAGE_URI memory:// 多实例可换 redis://...

⚠️ main.py 已不再接受任何 CLI 分析参数。分析 / 回测 / 推送等业务一律通过 web UI 与 REST API 触发:POST /api/v1/analysis/analyzePOST /api/v1/backtest/... 等(详见 docs/modules/api.md)。


并发调节

所有线程池可通过环境变量调节(避免 LLM/数据源 rate-limit):

# 全局降到 4 worker
export STOCKLENS_DEFAULT_WORKERS=4

# 仅限制搜索池
export STOCKLENS_SEARCH_WORKERS=2

# API 服务长任务/IO 任务分别配置
export API_LONG_POOL_WORKERS=4
export API_IO_POOL_WORKERS=16

完整列表见 modules/concurrency.md


配置

所有环境变量见 .env.example(130+ 项,带中文注释)。最小运行配置:

STOCK_LIST=AAPL
GEMINI_API_KEY=your_key   # 或任意一个 LLM key

代码风格

  • 类型注解:所有公开函数必须有
  • Docstring:Google 风格,中文注释
  • 导入顺序:stdlib → third-party → local,各组间空行
  • 日志logging.getLogger(__name__),不用 print
  • 异常
    • 捕获尽量窄。优先用 stocklens.exceptions 中的领域异常: DataSourceError / LLMError / StorageWriteError / SearchError / NotificationError / DSAError
    • 最外层兜底用 Exception必须as exc 并加 logger.warning/exception/debug(...) 留痕;
    • 绝不允许 except Exception: passexcept Exception: continue(无日志的静默吞噬);
    • 兜底 except Exception 不再需要写 # noqa: BLE001 —— 2026-05 起 BLE001 已在 .ruff.toml 全仓 ignore(理由:BLE001 规则与 CLAUDE.md "outermost Exception fallback MUST log" 规定冲突,所有现存站点均已带 logger 调用);
    • 写库 + ORM 错误优先捕获 sqlalchemy.exc.IntegrityError / OperationalError,再 fallback 到 StorageWriteError
  • 重型库(newspaper3k / fake_useragent / akshare 等)一律 lazy import
  • 并发:所有 ThreadPoolExecutor 通过 stocklens.utils.concurrency.get_pool_workers(name) 取 worker 数,不要硬编码 os.cpu_count()
  • 跨层依赖:核心层 (stocklens/) 禁止 import bot/ / api/;跨层共享类型放 stocklens.contracts
  • 数据库写:批量保存用 SQLite INSERT ... ON CONFLICT DO UPDATE(参考 stock_repo.save_dataframe),不要循环 select-then-add

添加新模块时的步骤(强制)

  1. 新建 stocklens/<新目录>/ + __init__.py
  2. 新建 docs/modules/<新>.md — 至少写文件清单、核心类、依赖关系、配置项、注意事项五段
  3. docs/CONTEXT.md 的目录映射图里加行
  4. docs/MODULES.md 速查表里加行
  5. docs/ARCHITECTURE.md 的"目录与模块映射"表里加行
  6. docs/INDEX.md 的快速导航表里加行
  7. CLAUDE.md 的"代码 → 文档同步映射"里加行
  8. 写至少一个 pytest 用例(tests/unit/test_<新>.py
  9. pytest tests/ -q
  10. 一次 commit 提交以上全部改动

少做任何一步,commit 都视为不完整。