Week 17:项目工程化、CLI 入口与可复现实验

发布于 2026-07-26 17:00 4490 字 23 min read

Week 17:项目工程化、CLI 入口与可复现实验。fish-first 终端教学,面向 CachyOS、VS Code、uv 和 Python 学习路线。
Oh My Pi / weekly tutorial / week17-project-engineering
miku@cachyos:~/Code/python-learning$ omp teach week17-project-engineering --fish-first --step-by-step
source591 行教学文档
weekWeek 17
shellfish-first 命令版
backlink20 周计划

Week 17:项目工程化、CLI 入口与可复现实验

返回总计划:USTC 统计 + AI / 量化 20 周成长计划

Week 17 的目标是把前面做过的项目整理成一个干净、可运行、可复现的仓库。对 USTC 统计学生来说,这一步非常重要:老师、同学、面试官或未来的你,不会只看“我跑过一次”,而会看项目是否能被别人重新运行。

本文命令默认使用 fish shell。进入项目后激活环境:

ompfish
source .venv/bin/activate.fish

0. 本周详细教学:语法、规范、验收

本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。

0.1 本周真正要学会什么

维度要求
知识点README、seed、依赖、运行说明、复现
代码语法能从空文件写出本周核心脚本,而不是只复制运行
程序规范函数拆分、路径清楚、输入输出明确、错误能解释
交付物README.md
验收方式从 fish 终端运行命令,得到可复查的文件或指标

0.2 代码语法精讲

下面的代码不是最终答案,而是本周必须理解的最小骨架:

omppython
mkdir -p src reports figures outputs
python src/train.py --config configs/baseline.yaml
python src/evaluate.py --model outputs/best.pt
python src/make_report.py --output reports/final_report.md

读代码时按四步检查:输入从哪里来;中间变量的类型和 shape 是什么;函数或脚本输出什么;哪些错误应该显式报出来。

0.3 本周程序规范

  • 所有路径用相对路径或 `pathlib.Path`,不要写死 `/home/miku/...`。
  • 核心逻辑进 `src/`,notebook 只做探索和解释。
  • 每个脚本能从 fish 终端运行,并在 README 写出命令。
  • 输出必须落盘到 `reports/`、`figures/` 或 `outputs/`,不能只在屏幕上看。

0.4 本周练习分层

层级任务不合格表现合格验收
最小练习手写上面的最小骨架只在 notebook 里运行终端运行成功
标准练习把逻辑拆成函数/模块一个大脚本从头写到尾至少 2 个函数,职责清楚
项目练习生成本周交付物 README.md只有屏幕输出文件落盘,可复查
复盘练习写 3 个错误和修复只写“已解决”写清报错、原因、修复、预防

0.5 本周和主线的连接

1. 本周学习目标

完成本周后,你应该能把一个项目整理到以下状态:

  1. 目录结构清楚:代码、数据、报告、图片、配置分离。
  2. 没有临时代码、随手命名的 notebook、无用输出文件。
  3. 使用 uv 管理依赖,pyproject.toml 能说明项目需要什么包。
  4. 有 CLI 入口,可以用一条命令运行主流程。
  5. README 写清楚安装、运行、输出、项目结构和复现步骤。
  6. 随机种子固定,输出结果可复查。
  7. .gitignore 不提交 .venv、缓存、大文件和临时文件。

建议最终结构:

ompprompt
quant-backtest-mini/
├── README.md
├── pyproject.toml
├── uv.lock
├── .gitignore
├── data/
│   ├── README.md
│   └── prices.csv
├── figures/
│   ├── equity_curve.png
│   ├── drawdown.png
│   └── cost_sensitivity.png
├── reports/
│   ├── week16_metrics_by_split.csv
│   ├── week16_cost_sensitivity.csv
│   └── week16_quant_project_report.md
└── src/
    └── quant_backtest_mini/
        ├── __init__.py
        ├── __main__.py
        ├── backtest.py
        ├── cli.py
        ├── metrics.py
        └── report.py

注意:这里的 data/README.md 是项目内部数据说明,不是本博客源文档。Week 17 允许你在自己的量化项目里写 README;但本教程文档本身只放在 docs/week17-project-engineering.md

2. 前置条件

你需要已经有一个待整理项目,例如 Week 15-16 的 quant-backtest-mini

进入项目:

ompfish
cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
source .venv/bin/activate.fish
pwd
ls

确认你看到:

ompprompt
pyproject.toml
src
reports
figures

如果项目不在这个路径,进入你自己的项目根目录即可。判断标准:当前目录应包含 pyproject.toml,而不是只在总学习目录。

3. 先做清点,不急着重构

工程化第一步不是乱改文件,而是弄清楚现在有什么。

ompfish
find . -maxdepth 3 -type f | sort

这条命令用于人工检查项目文件。重点找:

  • Untitled.ipynbtest.pytmp.py 这类临时文件。
  • 重复保存的结果,例如 result_final_final.csv
  • 不该提交的缓存,例如 __pycache__
  • 是否有数据说明。
  • 是否有 README。

如果你不确定某个文件是否还在用,先不要删。可以用 VS Code 搜索文件名或导入名。

4. 建立 .gitignore

如果项目还没有 .gitignore,创建:

ompfish
code .gitignore

写入:

ompprompt
.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
.ruff_cache/
.mypy_cache/
.pytest_cache/
.DS_Store
*.log
.env
.env.*

解释:

条目 为什么忽略
.venv/ 虚拟环境很大,别人应自己用 uv 创建
__pycache__/ Python 运行缓存,不是源码
.ipynb_checkpoints/ Jupyter 自动缓存
*.log 临时日志通常不可复现
.env 可能包含密钥或本机路径

检查 Git 状态:

ompfish
git status --short

如果项目还没有 Git 仓库,可以初始化:

ompfish
git init

不要提交前先确认 .venv/ 没有出现在 git status --short 里。

5. 固定依赖:用 uv 管理环境

查看当前依赖:

ompfish
cat pyproject.toml

如果 Week 15-16 用到了 pandas、numpy、matplotlib、scikit-learn,确保它们在依赖中:

ompfish
uv add pandas numpy matplotlib scikit-learn

如果你没有用 scikit-learn,不要为了好看添加它。依赖越少越容易复现。

重新同步环境:

ompfish
uv sync
source .venv/bin/activate.fish

解释:

  • uv add:把依赖写进 pyproject.toml,并更新锁文件。
  • uv sync:根据项目配置创建或同步 .venv
  • uv.lock:记录具体版本,帮助别人复现环境。

如果你需要导出传统 requirements:

ompfish
uv export --format requirements-txt --output-file requirements.txt

但对新项目,优先保留 pyproject.tomluv.lock

6. 整理 Python 包结构

推荐把业务代码放在 src/quant_backtest_mini/

检查:

ompfish
ls src/quant_backtest_mini

应至少包含:

ompprompt
__init__.py
backtest.py
metrics.py
report.py

如果你现在只有一个 backtest.py 放在项目根目录,可以移动:

ompfish
mkdir -p src/quant_backtest_mini
mv backtest.py src/quant_backtest_mini/backtest.py
printf "" > src/quant_backtest_mini/__init__.py

移动后,要把导入改成包内导入。例如:

omppython
from quant_backtest_mini.metrics import summarize

而不是依赖当前工作目录碰巧能找到文件。

7. 添加 CLI 入口

CLI 的作用是让别人不用打开 Python 文件,也能运行项目。

创建 src/quant_backtest_mini/cli.py

omppython
import argparse

from quant_backtest_mini.backtest import load_prices, plot_results, run_backtest from quant_backtest_mini.metrics import summarize

def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description=“Run a mini momentum backtest.”) parser.add_argument(“—data”, default=“data/prices.csv”, help=“Path to price CSV.”) parser.add_argument(“—lookback”, type=int, default=20, help=“Momentum lookback days.”) parser.add_argument(“—cost-bps”, type=float, default=5.0, help=“Transaction cost in bps.”) return parser

def main() -> None: parser = build_parser() args = parser.parse_args()

plain prices = load_prices(args.data) result = run_backtest(prices, lookback=args.lookback, cost_bps=args.cost_bps) summary = summarize(result[“net_strategy_return”], result[“turnover”])

plain for key, value in summary.items(): print(f”{key}: {value:.4f}”)

plain plot_results(result) result.to_csv(“reports/backtest_daily_results.csv”) print(“saved reports/backtest_daily_results.csv and figures”)

if name == “main”: main()

再创建 src/quant_backtest_mini/__main__.py

omppython
from quant_backtest_mini.cli import main

if name == “main”: main()

现在可以运行:

ompfish
python -m quant_backtest_mini --data data/prices.csv --lookback 20 --cost-bps 5

如果提示找不到模块,检查是否已用 uv sync 安装当前项目。也可以在 pyproject.toml 里确认项目包配置是否正确。

8. 在 pyproject.toml 中添加命令入口

打开:

ompfish
code pyproject.toml

在文件中加入脚本入口,具体位置通常在依赖列表之后:

omptoml
[project.scripts]
quant-backtest-mini = "quant_backtest_mini.cli:main"

然后同步:

ompfish
uv sync
source .venv/bin/activate.fish

运行:

ompfish
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5

这就是一个项目级 CLI。以后 README 里可以直接写这条命令。

9. 固定随机种子和运行参数

如果项目里有任何随机过程,例如模拟数据、随机森林、抽样,都要固定随机种子。

示例:

omppython
RANDOM_SEED = 42

在 numpy 中:

omppython
rng = np.random.default_rng(RANDOM_SEED)

在 scikit-learn 中:

omppython
RandomForestClassifier(random_state=RANDOM_SEED)

同时,把核心运行参数写进 README:

ompprompt
默认参数:lookback=20, cost_bps=5
执行假设:t 日收盘生成信号,t+1 日持仓
随机种子:42

可复现不是“我电脑上能跑”,而是别人能知道你用了什么参数跑。

10. 写 README

打开 README:

ompfish
code README.md

推荐结构:

ompprompt
# Quant Backtest Mini

A minimal momentum backtest project for learning time-series validation and quant metrics.

What this project does

  • Loads daily close prices.
  • Computes daily returns.
  • Builds a simple N-day momentum signal.
  • Applies a one-day signal delay to avoid look-ahead bias.
  • Subtracts transaction costs based on turnover.
  • Reports annual return, annual volatility, Sharpe, max drawdown, and turnover.

Project structure

写出 data、src、reports、figures 分别放什么。

Setup

uv sync source .venv/bin/activate.fish

Run

quant-backtest-mini —data data/prices.csv —lookback 20 —cost-bps 5

Outputs

  • reports/backtest_daily_results.csv
  • figures/equity_curve.png
  • figures/drawdown.png

Reproducibility

  • Python dependencies are managed by uv.
  • Lock file: uv.lock
  • Random seed: 42 if simulated data is regenerated.
  • Default parameters: lookback=20, cost_bps=5.

Limitations

  • Educational project only.
  • No slippage model.
  • No liquidity constraints.
  • No live trading.
  • Results do not imply future profitability.

README 要让别人能在 3 分钟内知道:这是什么、怎么装、怎么跑、会生成什么、限制是什么。

11. 数据说明

如果 data/prices.csv 是模拟数据或小样本数据,必须说明来源。可以创建:

ompfish
code data/README.md

写入:

ompprompt
# Data

prices.csv contains daily close prices used for the mini backtest.

Columns:

  • date: trading date
  • close: closing price

Source:

  • Simulated data generated for learning, or replace this line with the real data source.

Notes:

  • This data is for educational backtesting only.
  • Do not treat results as investment advice.

如果真实数据体积很大,不要直接提交大文件。README 中写清楚下载方式,或只提交小样本。

12. 可复现运行检查

从一个干净终端重新跑:

ompfish
cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
uv sync
source .venv/bin/activate.fish
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5

检查输出文件:

ompfish
ls reports figures

用 Python 检查关键文件存在:

ompfish
python -c "from pathlib import Path; required = ['README.md', 'pyproject.toml', 'uv.lock', 'src/quant_backtest_mini/cli.py', 'src/quant_backtest_mini/__main__.py', 'reports/backtest_daily_results.csv', 'figures/equity_curve.png', 'figures/drawdown.png']; missing = [p for p in required if not Path(p).exists()]; print('missing:', missing); raise SystemExit(1 if missing else 0)"

这一步比“我觉得整理好了”可靠。

13. Git 提交前检查

查看状态:

ompfish
git status --short

你希望看到的是源码、配置、报告、图片、小数据说明,而不是虚拟环境和缓存。

常见不该提交:

ompprompt
.venv/
__pycache__/
.ipynb_checkpoints/
*.log

添加文件:

ompfish
git add README.md pyproject.toml uv.lock .gitignore data src reports figures

提交:

ompfish
git commit -m "Organize reproducible quant backtest project"

如果只是课程练习,也可以暂不推送远程仓库。但至少学会让本地历史清楚。

14. 清理临时代码

删除前先确认临时文件没有用。常见候选:

ompprompt
tmp.py
scratch.py
Untitled.ipynb
result_old.csv
result_final_final.csv

删除单个确认无用的文件:

ompfish
rm tmp.py

不要用危险的大范围删除命令。初学阶段宁愿手动删慢一点,也不要误删报告或数据。

15. 文件布局验收

执行:

ompfish
pwd
ls
ls src/quant_backtest_mini reports figures data

理想结构:

ompprompt
README.md
pyproject.toml
uv.lock
.gitignore
data/
figures/
reports/
src/

包目录至少包含:

ompprompt
__init__.py
__main__.py
backtest.py
cli.py
metrics.py
report.py

16. 练习

  1. 给 CLI 增加 --output 参数,让用户指定结果 CSV 保存路径。
  2. 给 CLI 增加 --no-plots 参数,在只想生成表格时不画图。
  3. 在 README 中加入一段“Methodology”,解释为什么 position = signal.shift(1)
  4. 把 Week 16 的报告结论精简成 README 的 “Results summary”。
  5. 新建一个空目录,重新 clone 或复制项目,只按 README 运行,检查是否能复现。
  6. 请同学只看 README,不口头解释,让对方尝试运行;记录对方卡在哪里,再改 README。

17. 验收检查

在项目根目录执行:

ompfish
source .venv/bin/activate.fish
uv sync
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5
python -c "from pathlib import Path; required = ['README.md', 'pyproject.toml', 'uv.lock', '.gitignore', 'src/quant_backtest_mini/cli.py', 'src/quant_backtest_mini/__main__.py']; missing = [p for p in required if not Path(p).exists()]; print('missing:', missing); raise SystemExit(1 if missing else 0)"

通过标准:

  • README 写清楚 setup、run、outputs、limitations。
  • uv sync 后能重新创建环境。
  • fish 激活命令写成 source .venv/bin/activate.fish
  • CLI 可以一条命令运行主流程。
  • 结果文件生成位置清楚。
  • .venv/、缓存和日志不会被提交。
  • 随机种子、默认参数和交易假设写清楚。

18. 常见错误

错误 1:README 只有一句话

README 不是口号。至少要有项目说明、环境安装、运行命令、输出、限制。

错误 2:依赖只存在于本机环境

如果你手动 pip install 过,但没有写进 pyproject.toml,别人无法复现。用 uv add 记录依赖。

错误 3:CLI 只能在某个目录碰巧运行

使用包导入,不要依赖当前工作目录下的相对导入。推荐 python -m quant_backtest_mini 或项目脚本入口。

错误 4:提交 .venv

.venv 不应提交。别人应该用 uv sync 创建自己的环境。

错误 5:结果不可复现

如果数据生成和模型训练有随机性,必须固定随机种子,并在 README 写明。

错误 6:工程化时顺手改策略结论

Week 17 的重点是整理项目,不是继续调参优化策略。不要为了 README 好看而更改 Week 16 的研究结论。

19. 下一步

进入 Week 18:把项目经历整理成简历和面试叙述。你现在应该能把这个量化小项目概括成一句专业描述:实现时间序列回测框架,包含 rolling validation、交易成本、最大回撤、Sharpe、换手率等指标,并分析样本外失效风险。

plain

喜欢的话,留下你的评论吧~