Week 17:项目工程化、CLI 入口与可复现实验
Week 17 的目标是把前面做过的项目整理成一个干净、可运行、可复现的仓库。对 USTC 统计学生来说,这一步非常重要:老师、同学、面试官或未来的你,不会只看“我跑过一次”,而会看项目是否能被别人重新运行。
本文命令默认使用 fish shell。进入项目后激活环境:
source .venv/bin/activate.fish
0. 本周详细教学:语法、规范、验收
本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。
0.1 本周真正要学会什么
| 维度 | 要求 |
|---|---|
| 知识点 | README、seed、依赖、运行说明、复现 |
| 代码语法 | 能从空文件写出本周核心脚本,而不是只复制运行 |
| 程序规范 | 函数拆分、路径清楚、输入输出明确、错误能解释 |
| 交付物 | README.md |
| 验收方式 | 从 fish 终端运行命令,得到可复查的文件或指标 |
0.2 代码语法精讲
下面的代码不是最终答案,而是本周必须理解的最小骨架:
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 本周和主线的连接
- 回到总计划:USTC AI / Quant 练习手册
- 查详细练习索引:技术练习详解
- 查质量评分:最终质量门槛
1. 本周学习目标
完成本周后,你应该能把一个项目整理到以下状态:
- 目录结构清楚:代码、数据、报告、图片、配置分离。
- 没有临时代码、随手命名的 notebook、无用输出文件。
- 使用
uv管理依赖,pyproject.toml能说明项目需要什么包。 - 有 CLI 入口,可以用一条命令运行主流程。
- README 写清楚安装、运行、输出、项目结构和复现步骤。
- 随机种子固定,输出结果可复查。
.gitignore不提交.venv、缓存、大文件和临时文件。
建议最终结构:
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。
进入项目:
cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
source .venv/bin/activate.fish
pwd
ls
确认你看到:
pyproject.toml
src
reports
figures
如果项目不在这个路径,进入你自己的项目根目录即可。判断标准:当前目录应包含 pyproject.toml,而不是只在总学习目录。
3. 先做清点,不急着重构
工程化第一步不是乱改文件,而是弄清楚现在有什么。
find . -maxdepth 3 -type f | sort
这条命令用于人工检查项目文件。重点找:
Untitled.ipynb、test.py、tmp.py这类临时文件。- 重复保存的结果,例如
result_final_final.csv。 - 不该提交的缓存,例如
__pycache__。 - 是否有数据说明。
- 是否有 README。
如果你不确定某个文件是否还在用,先不要删。可以用 VS Code 搜索文件名或导入名。
4. 建立 .gitignore
如果项目还没有 .gitignore,创建:
code .gitignore
写入:
.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 状态:
git status --short
如果项目还没有 Git 仓库,可以初始化:
git init
不要提交前先确认 .venv/ 没有出现在 git status --short 里。
5. 固定依赖:用 uv 管理环境
查看当前依赖:
cat pyproject.toml
如果 Week 15-16 用到了 pandas、numpy、matplotlib、scikit-learn,确保它们在依赖中:
uv add pandas numpy matplotlib scikit-learn
如果你没有用 scikit-learn,不要为了好看添加它。依赖越少越容易复现。
重新同步环境:
uv sync
source .venv/bin/activate.fish
解释:
uv add:把依赖写进pyproject.toml,并更新锁文件。uv sync:根据项目配置创建或同步.venv。uv.lock:记录具体版本,帮助别人复现环境。
如果你需要导出传统 requirements:
uv export --format requirements-txt --output-file requirements.txt
但对新项目,优先保留 pyproject.toml 和 uv.lock。
6. 整理 Python 包结构
推荐把业务代码放在 src/quant_backtest_mini/。
检查:
ls src/quant_backtest_mini
应至少包含:
__init__.py
backtest.py
metrics.py
report.py
如果你现在只有一个 backtest.py 放在项目根目录,可以移动:
mkdir -p src/quant_backtest_mini
mv backtest.py src/quant_backtest_mini/backtest.py
printf "" > src/quant_backtest_mini/__init__.py
移动后,要把导入改成包内导入。例如:
from quant_backtest_mini.metrics import summarize
而不是依赖当前工作目录碰巧能找到文件。
7. 添加 CLI 入口
CLI 的作用是让别人不用打开 Python 文件,也能运行项目。
创建 src/quant_backtest_mini/cli.py:
import argparsefrom 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:
from quant_backtest_mini.cli import main
if name == “main”: main()
现在可以运行:
python -m quant_backtest_mini --data data/prices.csv --lookback 20 --cost-bps 5
如果提示找不到模块,检查是否已用 uv sync 安装当前项目。也可以在 pyproject.toml 里确认项目包配置是否正确。
8. 在 pyproject.toml 中添加命令入口
打开:
code pyproject.toml
在文件中加入脚本入口,具体位置通常在依赖列表之后:
[project.scripts]
quant-backtest-mini = "quant_backtest_mini.cli:main"
然后同步:
uv sync
source .venv/bin/activate.fish
运行:
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5
这就是一个项目级 CLI。以后 README 里可以直接写这条命令。
9. 固定随机种子和运行参数
如果项目里有任何随机过程,例如模拟数据、随机森林、抽样,都要固定随机种子。
示例:
RANDOM_SEED = 42
在 numpy 中:
rng = np.random.default_rng(RANDOM_SEED)
在 scikit-learn 中:
RandomForestClassifier(random_state=RANDOM_SEED)
同时,把核心运行参数写进 README:
默认参数:lookback=20, cost_bps=5
执行假设:t 日收盘生成信号,t+1 日持仓
随机种子:42
可复现不是“我电脑上能跑”,而是别人能知道你用了什么参数跑。
10. 写 README
打开 README:
code README.md
推荐结构:
# 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 是模拟数据或小样本数据,必须说明来源。可以创建:
code data/README.md
写入:
# 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. 可复现运行检查
从一个干净终端重新跑:
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
检查输出文件:
ls reports figures
用 Python 检查关键文件存在:
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 提交前检查
查看状态:
git status --short
你希望看到的是源码、配置、报告、图片、小数据说明,而不是虚拟环境和缓存。
常见不该提交:
.venv/
__pycache__/
.ipynb_checkpoints/
*.log
添加文件:
git add README.md pyproject.toml uv.lock .gitignore data src reports figures
提交:
git commit -m "Organize reproducible quant backtest project"
如果只是课程练习,也可以暂不推送远程仓库。但至少学会让本地历史清楚。
14. 清理临时代码
删除前先确认临时文件没有用。常见候选:
tmp.py
scratch.py
Untitled.ipynb
result_old.csv
result_final_final.csv
删除单个确认无用的文件:
rm tmp.py
不要用危险的大范围删除命令。初学阶段宁愿手动删慢一点,也不要误删报告或数据。
15. 文件布局验收
执行:
pwd
ls
ls src/quant_backtest_mini reports figures data
理想结构:
README.md
pyproject.toml
uv.lock
.gitignore
data/
figures/
reports/
src/
包目录至少包含:
__init__.py
__main__.py
backtest.py
cli.py
metrics.py
report.py
16. 练习
- 给 CLI 增加
--output参数,让用户指定结果 CSV 保存路径。 - 给 CLI 增加
--no-plots参数,在只想生成表格时不画图。 - 在 README 中加入一段“Methodology”,解释为什么
position = signal.shift(1)。 - 把 Week 16 的报告结论精简成 README 的 “Results summary”。
- 新建一个空目录,重新 clone 或复制项目,只按 README 运行,检查是否能复现。
- 请同学只看 README,不口头解释,让对方尝试运行;记录对方卡在哪里,再改 README。
17. 验收检查
在项目根目录执行:
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
喜欢的话,留下你的评论吧~