上一篇我们给系统装上了神经系统:日志、告警、心跳。本篇解决最后一个工程问题——版本管理。量化项目尤其需要 Git:策略参数从 5/20 改成 10/40 之后回测变好了,是参数的功劳还是上周那次信号逻辑修改的功劳?没有版本记录,这个问题永远答不上来。Git 就是给整个项目装「时间机器」:每次改动都有快照、可对比、可回滚。
本篇目标
- 能完成 Git 最小工作流:init → add → commit,并写出有信息量的提交信息
- 能看懂 git log / git diff,回答「这个改动是哪次提交引入的」
- 能写对 .gitignore:数据、日志、缓存不进仓库
- 能用分支(branch)做参数实验,实验失败直接丢弃
- 能把「策略版本 → 回测结果」的对应关系固化下来,告别「玄学调参」
核心概念
仓库、提交与差异
Git 的世界只有三样东西:仓库(Repository,就是那个 .git 隐藏目录,记录全部历史)、提交(Commit,一次快照,附一条说明)、差异(Diff,两次提交之间的变化)。工作流一句话:改代码 → git add 暂存 → git commit 存档。每条 commit 都带着作者、时间、说明和一个唯一哈希——你回测报告里写下「commit a3f21c9」,三个月后任何人都能精确复现当时的代码。
提交信息:写给未来自己的信
git commit -m "改了点东西" 是最常见的犯罪。好的提交信息要能回答「为什么改」:
差: update
差: 修改策略
好: 双均线快线从5改10:减少震荡期鞭打,Walk-Forward样本外夏普0.4→0.6
好: 修复信号shift缺失导致的未来函数(第17篇坑1复现)
量化项目的黄金习惯:每次回测出报告时,先 commit 再跑。报告与代码版本绑定,参数对比才有意义。
.gitignore:什么不该进仓库
data/ # 行情数据:几十MB到几GB,且可重新拉取
logs/ # 日志:运行产物,不是源代码
__pycache__/ # Python缓存
*.pyc
.venv/ # 虚拟环境
config/secrets.yaml # 任何含密钥的配置!
原则:能由代码重新生成的,不进仓库。数据、日志、缓存都属于此类。唯一要警惕的红线是密钥——券商接口的密码、webhook 地址一旦提交,即使后来删除也留在历史里,等于公开。
划重点:Git 管的是代码与配置的版本,不是数据的版本。行情数据该由第 42 篇的管道负责增量维护,回测结果(净值 CSV、报告)可以进仓库也可以另建「结果目录」——但「产生这份结果的代码是哪个 commit」这个对应关系,必须永远可查。
分支:平行宇宙做实验
分支(Branch)让你在不影响主线(main)的情况下做实验:git switch -c exp-ma10-30 开个新分支改参数、跑回测;好结果就合并回主线,坏结果 git switch main 直接回主线、分支一删,干干净净。每个参数实验一个分支,比在主线上反复改再 Ctrl+Z 可靠一万倍。
回滚:三种后悔药
改错了想反悔,按场景选药:git restore 文件名(还没 add,丢弃工作区改动);git restore --staged 文件名(add 了还没 commit,撤出暂存区);git revert 哈希(已经 commit,生成一个反向提交抵消它)。新手慎用 git reset --hard——它会把改动直接抹掉且不留痕迹。
完整代码
Git 是命令行工具,本篇「代码」就是终端命令序列。建议在项目根目录跟着敲一遍。
# ---------- 1. 初始化与最小工作流 ----------
git init # 在项目根目录创建仓库
git status # 看当前状态(最常用的命令)
git add strategies/ma_cross.py config/strategy.yaml
git commit -m "初始提交:双均线策略与配置(第41篇结构)"
# ---------- 2. .gitignore ----------
Set-Content -Path .gitignore -Value @"
data/
logs/
__pycache__/
*.pyc
.venv/
config/secrets.yaml
"@
git add .gitignore
git commit -m "添加gitignore:排除数据/日志/缓存/密钥"
# ---------- 3. 日常改动:改参数 → 提交 → 看历史 ----------
# (编辑 config/strategy.yaml,把 slow: 20 改成 slow: 60)
git diff # 改动前先看diff确认改对了
git add config/strategy.yaml
git commit -m "慢线20改60:测试长周期均线,预期能抓住更长趋势"
git log --oneline # 一行一条看历史
git log -p -2 # 最近两次提交的完整diff
# ---------- 4. 分支实验:参数A/B对比 ----------
git switch -c exp-ma5-30 # 开实验分支
# (在实验分支上把 fast 改成 5、slow 改成 30,跑回测记录结果)
git add config/strategy.yaml
git commit -m "实验:5/30参数组合,样本外年化-1.2%(劣于主线)"
git switch main # 回主线,实验分支留着备查
# 如果实验失败想彻底丢弃:
# git branch -D exp-ma5-30
# 如果实验成功想合并:
# git merge exp-ma5-30
# ---------- 5. 把回测结果与代码版本绑定(Python 侧) ----------
import subprocess
from datetime import datetime
from pathlib import Path
def get_commit():
"""获取当前git commit哈希,回测报告里必须带上"""
try:
h = subprocess.run(["git", "rev-parse", "--short", "HEAD"],
capture_output=True, text=True, check=True)
msg = subprocess.run(["git", "log", "-1", "--pretty=%s"],
capture_output=True, text=True, check=True)
return h.stdout.strip(), msg.stdout.strip()
except Exception:
return "unknown", "非git环境"
def save_result_with_version(nav_summary, path="logs/result_meta.txt"):
"""回测跑完,把版本信息与结果摘要写在一起"""
commit, msg = get_commit()
entry = (f"{datetime.now():%Y-%m-%d %H:%M} | commit {commit} | {msg} | "
f"结果: {nav_summary}\n")
Path(path).parent.mkdir(exist_ok=True)
with open(path, "a", encoding="utf-8") as f:
f.write(entry)
print(f"已登记: {entry.strip()}")
save_result_with_version("期末净值1.412, 夏普0.83, 回撤-12.5%")
代码逐段解释
- 初始化:
git init后用git add精确添加(而非git add .全加),第一次就养成「看清什么进仓库」的习惯。首次提交信息带上结构出处(第 41 篇),让仓库自带上下文。 - .gitignore:用 PowerShell 的
Set-Content写入忽略规则。注意最后一条secrets.yaml——第 43 篇的 webhook 地址、券商密钥全在这里,这个文件名必须出现在 ignore 里。 - 日常工作流:改动 →
git diff自查 → add → 带信息的 commit。git log --oneline是日常导航,git log -p -2看最近改动的具体内容——「上周改了什么」的问题从此有据可查。 - 分支实验:
switch -c创建并切换。实验分支上改参数、commit(劣质结果也要 commit——记录失败同样有价值,防止半年后再试一遍同样的死路);switch main回主线即恢复原状。合并与删除的命令以注释形式给出。 - 版本绑定:
get_commit用 subprocess 调 git 拿当前哈希与提交说明;save_result_with_version把「代码版本 + 回测摘要」追加写进一个登记文件。这张登记表就是「结果-代码」对应关系的物证——第 46 篇切换实盘前冻结参数时,靠它锁定版本。
示例输出(示意):
已登记: 2026-09-28 10:15 | commit a3f21c9 | 慢线20改60:测试长周期均线 | 结果: 期末净值1.412, 夏普0.83, 回撤-12.5%
git log --oneline(示意):
a3f21c9 (HEAD -> main) 慢线20改60:测试长周期均线
8c2d1e4 添加gitignore:排除数据/日志/缓存/密钥
b91a003 初始提交:双均线策略与配置(第41篇结构)
常见坑与小练习
常见坑
git add .一把梭:数据文件、日志、缓存全部进仓库,仓库瞬间膨胀到几百 MB。先写 .gitignore 再做第一次 add;已经误提交的大文件要立即处理(git rm --cached + 后续避免)。- 密钥进仓库:secrets 文件一旦 commit,删除文件也没用(历史里还在)。对策:ignore 先行;万一已经提交,立即作废该密钥(改密码/换 token),历史清理是进阶操作。
- 一次提交塞十件事:改参数+改引擎+改格式一起 commit,回滚时没法只撤参数。一个逻辑改动一个 commit,粒度以「能独立说明白」为准。
- commit 信息只写 what 不写 why:diff 已经说明了改了什么,信息栏的价值是 why 和结果。「参数改 10/40,Walk-Forward 外样本外夏普提升 0.2」才是合格信息。
- 把 git 当备份用,从不看历史:只 commit 不 log/diff 的仓库只是占空间的 .git 目录。版本管理的价值在「可追溯」,每周至少看一次
git log --oneline回顾改动轨迹。
小练习
- 完整走一遍后悔药三连:改一个文件 →
git restore丢弃;再改 → add →git restore --staged撤暂存;commit 一次 →git revert反向抵消,观察每步后git status的变化。思路提示:三个场景对应三种「后悔深度」,做完你就知道什么时候用哪瓶药。
- 给项目建一个
exp-atr-stop分支,实现第 35 篇的 ATR 止损(引擎层改动),commit 后不合并、留档,然后回 main——体验「实验留痕但不影响主线」。思路提示:分支上的 commit 信息写清实验结论(如「ATR止损使回撤-18%→-11%,但交易数翻倍」)。
- 写一个
report_version.py:每次回测结束自动把「日期、commit、参数、核心指标」追加进logs/result_meta.txt,并把登记文件 commit 进仓库。思路提示:组合本篇的
get_commit与第 19 篇的指标函数;参数从 config/strategy.yaml 读出——「参数进登记表」才能杜绝口头传参数。
本文策略仅用于教学,不构成投资建议,实盘前请充分测试。