第4周跟练:交付一份能复现、讲得清的研究
每次约2小时,共5个单元。把代码、数据、结果、解释和个人贡献整理成一个别人能接手的小项目。本周沿用本站原创合成研究模板,不要求策略赚钱,也不要求增加机器学习模型。
本周交付:别人能重跑,你能独立解释
整理目录 → 画时间轴 → 复现原结果 → 成本与未来变动检查 → 报告与讲解 → 新目录验收
想象你要向带教同事交接一个小研究:不只发一张收益截图,还要交代输入、代码、运行命令、失败记录与结论边界。报告可为1–2页正文,明细作为附件;5分钟讲清核心证据。
本周使用本站已有的单条合成价格研究引擎,和前两周的双标的CSV格式不同。前几周学到的“键、缺失、时点、核对”继续使用,但不把cleaned.csv直接塞进这个引擎。
相关题:q-010 研究流程、q-313 时间边界;在面试练习搜索I10、I17、I21、I23。
准备:把代码、自动结果和自己的文字分开
全部解压,用VS Code打开这个文件夹
打开直接含research.py、week4.py和README.md的目录。research.py与原项目一致,附5项计算测试;新增5项交付检查,共10项。本周只用Python标准库,不需要安装pandas或数据库。
textweek4-study/ research.py, week4.py, 01–04脚本 # 计算与操作代码 test_research.py, test_delivery.py # 检查代码 README.md, 大写模板文件 # 说明与空白模板 reports/ # 运行后生成:数据、结果、环境、指纹 notes/ # 你填写:报告、手算、日志、讲解 .venv/, __pycache__/ # 本机环境与缓存,不作为交付附件检查点:原始数据快照不手工改;reports可以重生成;notes要保留自己的文字和修改历史。生成结果存在,不等于本次命令成功。
创建环境,确认用的是哪个Python
Python 3.10+。已有本目录.venv则跳过第一行;只有py命令可用时,第一行将python改为py。不会安装可回第1周环境教程。
powershellpython -m venv .venv .\.venv\Scripts\python.exe -c "import sys; print(sys.executable); print(sys.version)" .\.venv\Scripts\python.exe -m unittest -v应显示本目录解释器,测试显示Ran 10 tests与OK。无需激活环境或修改PowerShell策略。macOS/Linux用python3和./.venv/bin/python,脚本参数去掉.\前缀。
复制个人工作模板,只做一次
首次创建notes。若里面已经写过内容,跳过对应复制命令,先备份,避免被空白模板覆盖。
powershellNew-Item -ItemType Directory -Path notes -Force Copy-Item REPORT_TEMPLATE.md notes/report.md Copy-Item EXPERIMENT_LOG.csv notes/experiment-log.csv Copy-Item HAND_CHECK.csv notes/hand-check.csv Copy-Item TALK_OUTLINE.md notes/talk-outline.mdMarkdown就是带简单标记的文本,直接用VS Code编辑;Ctrl+Shift+V可预览。先完成文字与证据,本周不要求导出PDF。macOS/Linux可用mkdir -p与cp,或在文件管理器手动复制。
版本怎么留
修改前保留完整快照,命名如week4-v01-baseline、week4-v02-cost-check,并写明改动原因。若会Git,用status、diff查看变化,再只add需要的文件并commit;不会Git不阻塞本周学习。不要复制.venv和缓存,也不要放入密钥或未授权数据。
脚本不会覆盖notes,但重跑会更新reports。一次只改一件事;旧结果另存后,再按02→03→04生成新证据。
单元1 · 先画时间轴,再谈回测
跟着做
25分钟读q-313;45分钟先手写[10,11,12]的shift(1)和shift(-1),再用本页7个价格的小例预测6段持仓;30分钟做I10,解释四个时点;20分钟把手算写入notes/hand-check.csv。
.\.venv\Scripts\python.exe .\01_timing.pyshift(1)是把上一条值贴到当前行,得到[缺失,10,11];shift(-1)得到[11,12,缺失],含未来值,不能直接作为当时决策信息。是否安全还取决于信号在何时可得、何时下单、何时成交。本模板故意留一整期执行延迟。
查看本步骤的注释函数
来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。
def timing_rows(prices, cost_bps=5):
result = backtest(prices, cost_bps)
rows = []
for i in range(1, len(prices)):
# i是收益区间:观测i-1收盘到观测i收盘,不代表真实交易日。
rows.append(dict(
interval=i,
signal_start=i - 3 if i >= 3 else "",
signal_end=i - 2 if i >= 3 else "",
execution_observation=i - 1,
previous_close=prices[i - 1], close=prices[i],
position=result["positions"][i - 1],
turnover=result["turnovers"][i - 1],
net_return=result["returns"][i - 1],
equity=result["equity"][i - 1],
))
return rows查看运行入口:01_timing.py
from week4 import REPORTS, timing_rows, write_csv
# 与独立验收使用不同样例。先在纸上预测,再运行。
prices = [10, 11, 10, 12, 11, 13, 12]
rows = timing_rows(prices, cost_bps=0)
for row in rows:
print(row)
REPORTS.mkdir(exist_ok=True)
write_csv(REPORTS / "timing-demo.csv", rows)
print("已写入reports/timing-demo.csv。signal_start/end为空表示尚无足够历史。")
应看到什么
价格为[10,11,10,12,11,13,12],费用0。6段持仓[0,0,1,0,1,0];仓位变化[0,0,1,1,1,1]。第3段用观测0→1的上涨形成信号,假设在观测2执行,收益来自观测2→3。
停下来检查:第3段虽然赚20%,不是因为先看见10→12再买入。第4段退出来自更早11→10的下跌。前两段没有足够历史,仓位是0。
留下的证据:timing-demo.csv与自己的hand-check.csv;用一句话给信号、执行、收益各标出观测编号。
单元2 · 按说明复现,保留完整证据
跟着做
25分钟读下载包README;45分钟先运行10项测试,再运行02_reproduce.py;30分钟对照daily.csv前6行与timing-first-six.csv,手算至少一段有仓位的收益;20分钟核对种子、Python版本和代码/数据哈希。
.\.venv\Scripts\python.exe .\02_reproduce.pyresearch.py负责原始计算;辅助脚本调用它后补充时点表和环境记录。manifest里的哈希是文件内容指纹,方便发现版本变化。相同哈希不证明逻辑正确,也不替代人工检查。
查看原研究引擎:research.py
"""A reproducible TEACHING experiment, not a live trading strategy.
Only the Python standard library is needed. See README.md for timing,
transaction-cost assumptions, and the six learner tasks.
"""
import csv
import hashlib
import json
import math
from pathlib import Path
import platform
import random
ROOT = Path(__file__).resolve().parent
def validate_prices(prices):
if len(prices) < 2 or any(not math.isfinite(p) or p <= 0 for p in prices):
raise ValueError("Need at least two finite, positive prices")
def maximum_drawdown(equity):
"""Positive loss fraction; initial capital of 1 is part of the path."""
peak, drawdown = 1.0, 0.0
for value in equity:
peak = max(peak, value)
drawdown = max(drawdown, 1 - value / peak)
return drawdown
def backtest(prices, cost_bps=5):
validate_prices(prices)
if not math.isfinite(cost_bps) or not 0 <= cost_bps < 10000:
raise ValueError("cost_bps must be in [0, 10000)")
rate = cost_bps / 10000
previous_position, wealth = 0, 1.0
positions, returns, equity, turnovers = [], [], [], []
for i in range(1, len(prices)):
# Interval i means close[i-1] -> close[i].
# Decide from the move ending at close[i-2], NOT close[i-1].
# This leaves a full interval to form the order before execution.
position = int(prices[i - 2] > prices[i - 3]) if i >= 3 else 0
turnover = abs(position - previous_position)
asset_return = prices[i] / prices[i - 1] - 1
# Simplified proportional fee charged before the interval return.
growth = (1 - rate * turnover) * (1 + position * asset_return)
wealth *= growth
positions.append(position)
returns.append(growth - 1)
equity.append(wealth)
turnovers.append(turnover)
previous_position = position
# Mark-to-market at the end; no forced liquidation or exit fee.
return dict(positions=positions, returns=returns, equity=equity,
turnovers=turnovers)
def synthetic_prices(seed=2027, observations=301):
rng = random.Random(seed)
prices = [100.0]
for _ in range(observations - 1):
# Independent lognormal increments: no planted predictive signal.
prices.append(round(prices[-1] * math.exp(rng.gauss(0.0001, 0.01)), 6))
return prices
def write_csv(path, rows):
with path.open("w", encoding="utf-8-sig", newline="") as file:
writer = csv.DictWriter(file, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
def main():
prices = synthetic_prices()
reports = ROOT / "reports"
reports.mkdir(exist_ok=True)
price_rows = [dict(observation=i, close=p) for i, p in enumerate(prices)]
write_csv(reports / "synthetic_prices.csv", price_rows)
metrics = []
for cost in (0, 5, 20):
result = backtest(prices, cost)
# Buy-and-hold: one initial purchase, same proportional cost.
benchmark = [(1 - cost / 10000) * p / prices[0] for p in prices[1:]]
metrics.append(dict(cost_bps=cost,
strategy_return=result["equity"][-1] - 1,
strategy_max_drawdown=maximum_drawdown(result["equity"]),
benchmark_return=benchmark[-1] - 1,
benchmark_max_drawdown=maximum_drawdown(benchmark),
total_turnover=sum(result["turnovers"])))
if cost == 5:
daily = [dict(interval=i, previous_close=prices[i - 1],
close=prices[i], position=result["positions"][i - 1],
turnover=result["turnovers"][i - 1],
net_return=result["returns"][i - 1],
equity=result["equity"][i - 1],
benchmark_equity=benchmark[i - 1])
for i in range(1, len(prices))]
write_csv(reports / "daily.csv", daily)
write_csv(reports / "metrics.csv", metrics)
manifest = dict(data_kind="synthetic, not historical market data",
seed=2027, observations=len(prices),
python=platform.python_version(), costs_bps=[0, 5, 20],
code_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
data_sha256=hashlib.sha256((reports / "synthetic_prices.csv").read_bytes()).hexdigest(),
timing="position for interval i uses move ending at close i-2",
final_valuation="mark to market, no terminal liquidation cost")
(reports / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
for row in metrics:
print(f"Cost {row['cost_bps']:2} bp | "
f"Strategy {row['strategy_return']:.2%} | "
f"Drawdown {row['strategy_max_drawdown']:.2%} | "
f"Benchmark {row['benchmark_return']:.2%}")
print("Teaching output written to reports/. Complete REPORT_TEMPLATE.md.")
if __name__ == "__main__":
main()
查看运行入口:02_reproduce.py
import platform
import sys
import research
from week4 import REPORTS, load_run_prices, timing_rows, write_csv
# 原引擎只用标准库;直接调用原流程,避免另造一套回测公式。
research.main()
prices = load_run_prices()
# 与daily.csv前6行核对;附上信号和成交观测编号。
write_csv(REPORTS / "timing-first-six.csv", timing_rows(prices[:7], 5))
(REPORTS / "environment.txt").write_text(
f"Python: {platform.python_version()}\nExecutable: {sys.executable}\n"
f"Platform: {platform.platform()}\nDependencies: Python standard library only\n",
encoding="utf-8",
)
print("补充完成:timing-first-six.csv、environment.txt。继续运行03_experiments.py。")
应看到什么
301个价格观测、300段结果、3行成本指标。daily.csv使用5bp;manifest记seed=2027与原引擎/数据SHA256;environment.txt记当前解释器、Python版本和平台。
停下来检查:每一行是一个收益区间,observation不是实际日期。请找到一段position=1且turnover=1的记录,用成本公式手算,允许浮点舍入差异。
留下的证据:reports中的价格快照、daily、metrics、manifest、环境记录,以及你写在notes中的手算和发现。
单元3 · 一个改变,一组对照
跟着做
20分钟做I17;50分钟比较0/5/20bp成本,先预测方向再运行;30分钟检查末尾价格变动;20分钟填写notes/experiment-log.csv,保留全部结果和失败,不挑最好的一行。
.\.venv\Scripts\python.exe .\03_experiments.py三种费用使用相同价格、信号与持仓路径。每次仓位变化只扣一次单边比例费用;0→1→0两次变化自然累计为2,不再额外乘2。未来变动实验只改最后价格,检查更早的持仓、收益、净值与换手不变。
查看本步骤的注释函数
来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。
def cost_rows(prices):
rows = []
base_positions = backtest(prices, 0)["positions"]
for cost in (0, 5, 20):
result = backtest(prices, cost)
# 费用对照必须使用相同数据、信号和持仓路径,只改变费用。
if result["positions"] != base_positions:
raise ValueError("持仓路径变了,不是单因素成本对照")
benchmark = [(1 - cost / 10000) * p / prices[0] for p in prices[1:]]
rows.append(dict(
cost_bps=cost,
strategy_return=result["equity"][-1] - 1,
strategy_max_drawdown=maximum_drawdown(result["equity"]),
benchmark_return=benchmark[-1] - 1,
benchmark_max_drawdown=maximum_drawdown(benchmark),
total_turnover=sum(result["turnovers"]),
))
if any(b["strategy_return"] > a["strategy_return"] + 1e-12 for a, b in zip(rows, rows[1:])):
raise ValueError("固定持仓路径下,提高费用不应提高终值")
return rows
def prefix_check(prices):
validate_prices(prices)
if len(prices) < 4:
raise ValueError("本实验至少需要4个观测")
changed = list(prices)
changed[-1] *= 1.5 # 只改最后一个观测;之前的输入完全不动
before, after = backtest(prices, 5), backtest(changed, 5)
# 排除受最后一个价格影响的最后收益区间;逐项比较更早结果。
passed = all(before[key][:-1] == after[key][:-1]
for key in ("positions", "returns", "equity", "turnovers"))
if not passed:
raise ValueError("修改未来价格影响了此前结果,请检查信息使用时点")
return dict(changed_observation=len(prices) - 1,
unchanged_intervals=len(prices) - 2,
passed=passed,
limitation="仅检查这一种未来输入变动,不是无前视的完整证明")查看运行入口:03_experiments.py
import csv
import json
import math
from week4 import ROOT, REPORTS, load_run_prices, cost_rows, prefix_check, result_note, fingerprint, write_csv
prices = load_run_prices()
rows = cost_rows(prices)
# 再核对原引擎metrics,避免辅助脚本与原报告口径悄悄分叉。
with (REPORTS / "metrics.csv").open(encoding="utf-8-sig", newline="") as handle:
original = list(csv.DictReader(handle))
if len(original) != len(rows) or any(
not math.isclose(float(original[i][key]), value, rel_tol=1e-12, abs_tol=1e-12)
for i, row in enumerate(rows) for key, value in row.items()
):
raise ValueError("成本对照与原metrics不一致,请重跑02并检查代码修改")
write_csv(REPORTS / "experiments.csv", rows)
check = prefix_check(prices)
(REPORTS / "prefix-check.json").write_text(json.dumps(check, ensure_ascii=False, indent=2), encoding="utf-8")
(REPORTS / "result-note.md").write_text(result_note(rows), encoding="utf-8")
# 最后才记录指纹。它只检查文件一致性,不能证明逻辑正确或个人掌握。
(REPORTS / "delivery-manifest.json").write_text(
json.dumps(fingerprint(ROOT), ensure_ascii=False, indent=2), encoding="utf-8"
)
print(result_note(rows))
print(f"未来变动检查:此前{check['unchanged_intervals']}个区间不变。")
print("已记录delivery-manifest.json。改代码或结果后,请记录原因并重跑02、03。")
应看到什么
成本结果有3行,每行累计仓位变化148;提高成本降低原模板终值。修改观测300后,之前299个区间不变。输出experiments.csv、prefix-check.json、result-note.md和delivery-manifest.json。
停下来检查:result-note是自动核对单,不是个人报告。前缀检查只覆盖一个反例,不能排除全样本选参数、错误复权、历史成分回填等其他泄漏。
留下的证据:三种成本的全部结果、实验前预测、实际结果、根因或局限、下一步。自动表与手写实验日志分别保留。
单元4 · 写1–2页报告,做5分钟讲解
跟着做
20分钟做I21/I23;60分钟按下方写作指引填写notes/report.md;20分钟按TALK_OUTLINE讲5分钟;20分钟修正讲不清的段落,再运行文件检查。
.\.venv\Scripts\python.exe .\04_check_delivery.py报告要让读者知道:你问了什么、证据来自哪里、结果如何、哪些解释尚未验证。04只核对计算代码与生成结果是否和03记录的指纹一致,notes中的报告、日志和独立贡献必须人工验收。
查看本步骤的注释函数
来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。
def fingerprint(root):
"""对计算代码与reports结果做文件指纹。notes中的人工报告单独人工验收。"""
paths = list(root.glob("*.py"))
if (root / "reports").exists():
paths += [p for p in (root / "reports").iterdir()
if p.is_file() and p.name != "delivery-manifest.json"]
return {p.relative_to(root).as_posix(): sha256(p) for p in sorted(paths)}
def verify_fingerprint(root, expected):
actual = fingerprint(root)
if actual != expected:
changed = sorted(name for name in set(actual) | set(expected)
if actual.get(name) != expected.get(name))
raise ValueError("文件已变化、缺失或新增:" + ", ".join(changed))
return len(actual)查看运行入口:04_check_delivery.py
import json
from week4 import ROOT, REPORTS, verify_fingerprint
manifest = REPORTS / "delivery-manifest.json"
if not manifest.exists():
raise SystemExit("先依次完成01、02、03,生成交付指纹。")
count = verify_fingerprint(ROOT, json.loads(manifest.read_text(encoding="utf-8")))
print(f"计算文件与结果指纹一致:{count}个文件。")
print("这不是报告评分:请人工检查notes/report.md、失败记录、贡献说明与5分钟讲解。")
print("还需在新文件夹重跑10项测试与01–04;指纹一致不能代替重跑。")
应看到什么
04打印计算文件与结果指纹一致;你另外提交一份填好的报告、至少3条具体局限、一次失败/检查记录,以及一个能在一天内验证的下一步任务。
停下来检查:能分别指出原模板提供什么、自己新增什么、AI辅助什么。不要将自动结果核对单改个标题就说成独立研究;也不要把亏损结果删掉。
留下的证据:notes/report.md、experiment-log.csv、hand-check.csv、talk-outline.md,以及DELIVERY_CHECKLIST中的逐项证据。
手算一段:仓位、费用和收益如何接起来
01的小例价格为[10,11,10,12,11,13,12],费用0。先填写自己的6行手算表,再展开核对。
完成手算后看时点与结果
| 区间i | 信号来自 | 执行观测 | 仓位 | 仓位变化 | 策略收益 |
|---|---|---|---|---|---|
| 1 | 历史不足 | 0 | 0 | 0 | 0 |
| 2 | 历史不足 | 1 | 0 | 0 | 0 |
| 3 | 0→1:10→11上涨 | 2 | 1 | 1 | 20% |
| 4 | 1→2:11→10下跌 | 3 | 0 | 1 | 0 |
| 5 | 2→3:10→12上涨 | 4 | 1 | 1 | 约18.181818% |
| 6 | 3→4:12→11下跌 | 5 | 0 | 1 | 0 |
费用0时累计收益为1.2×(13/11)-1≈41.818182%,不能把20%与18.181818%直接相加。该小例专为手算,与301观测的研究结果不同。
turnover = abs(position - previous_position)
rate = cost_bps / 10000
growth = (1 - rate * turnover) * (1 + position * asset_return)
net_return = growth - 1
equity = previous_equity * growth1bp=0.0001,5bp=0.05%。第3段入场时,若费用5bp,净收益=(1-0.0005)×1.2-1=19.94%;退出段即使仓位0,也会因为仓位从1变0而扣一次费用。
基准在第一段开始前买入一次,按同一费用率扣费;期末只按市值估值,双方都没有强制平仓费用。最大回撤包含初始本金1,显示为正的损失比例。
原版参考结果:亏损也能形成合格报告
下表由原引擎实际运行生成:Python 3.12.14、seed=2027、301个合成观测。请先运行自己的代码再核对;跨环境存在差异时查代码/数据与版本,不修改结果去迎合表格。
| 费用bp | 策略累计收益 | 策略最大回撤 | 基准累计收益 | 基准最大回撤 | 累计仓位变化 |
|---|---|---|---|---|---|
| 0 | -15.4798% | 16.9766% | -22.7898% | 27.3741% | 148 |
| 5 | -21.5099% | 22.5434% | -22.8284% | 27.3741% | 148 |
| 20 | -37.1536% | 37.6382% | -22.9442% | 27.3741% | 148 |
怎样解释,而不是只报数字
观察:5bp下策略累计收益-21.5099%,基准-22.8284%;策略减基准为1.3184个百分点。策略亏损,但这一情景下亏得比基准少。
有依据的解释:固定持仓下,0→5→20bp使策略终值逐步降低。原策略累计仓位变化148,基准只买入一次,费用暴露不同;不能只看低费用下相对表现就断言稳健。
尚未证明:这是单次合成输入和简化成交模型,没有市场预测证据,也没有独立样本外验证。累计收益差不是回归alpha;不把300个观测区间直接当交易日来年化。
自动结果核对单如何生成
def result_note(rows):
lines = [
"# 自动生成的结果核对单(不是个人研究报告)", "",
"来源:本次合成价格;同一策略持仓路径,费用为单边仓位变化费用。",
"观测编号不是真实交易日,不年化;未模拟期末强制平仓。", "",
"|费用bp|策略累计收益|策略最大回撤|基准累计收益|基准最大回撤|累计仓位变化|",
"|---|---|---|---|---|---|",
]
for r in rows:
lines.append(f'|{r["cost_bps"]}|{r["strategy_return"]:.4%}|'
f'{r["strategy_max_drawdown"]:.4%}|{r["benchmark_return"]:.4%}|'
f'{r["benchmark_max_drawdown"]:.4%}|{r["total_turnover"]}|')
row = next(r for r in rows if r["cost_bps"] == 5)
difference = 100 * (row["strategy_return"] - row["benchmark_return"])
lines += ["", f"5bp下,策略累计收益减基准累计收益为{difference:+.4f}个百分点。",
"这是两个累计收益的算术差,不是回归alpha,也不是风险调整后的超额收益。",
"该结果不能证明真实市场有效;费用对照只检验简化成本模型中的敏感性。",
"", "请自己补充:问题、时点假设、具体证据、失败、至少3项局限、个人贡献与下一步。",
"不要删除负结果,不要把本核对单当成自己已完成的报告。"]
return "\n".join(lines) + "\n"研究报告:每一节写什么、证据放哪里
以notes/report.md为工作文件。正文只保留核心问题、完整成本表与关键结论,CSV明细和日志作为附件。以下句式帮助组织证据,不应照抄成个人贡献。
问题与一句话结论
把问题缩小到本周能检验的范围。写“固定规则对费用有多敏感”,不要写“能否发现稳定赚钱的策略”。
证据位置:三种成本对照、同一持仓路径检查。
表达示例:我检查了同一合成价格上固定延迟规则的成本敏感性;结论仅适用于本次输入和简化成交假设。
数据与方法
注明合成数据、301观测/300区间、seed=2027、单位、时点和数据指纹;用一笔交易解释信号与收益。
证据位置:manifest.json、synthetic_prices.csv、timing-first-six.csv。
表达示例:第i段持仓只读取截至i-2的涨跌,假设在i-1执行;该延迟不等于已经仿真真实成交。
基准与全部结果
三种费用都列出策略/基准收益与回撤、累计仓位变化。说明基准只初始买入一次,两者都不扣期末强平费。
证据位置:本次experiments.csv与daily.csv;不能抄网页数字而不重跑。
表达示例:策略累计收益减基准累计收益按百分点报告,不把这个差值称为alpha或风险调整后收益。
观察、解释、尚未证明
先陈述数值,再提出可能原因,最后限定证据范围。解释必须能回到表里的字段,不能用“模型有效”代替理由。
证据位置:费用变化而持仓不变;成本路径与基准交易频率的差异。
表达示例:随着费用提高,终值降低;这符合固定持仓的扣费公式。它支持计算一致性,不能证明未来有可交易优势。
失败与局限
至少3条,每条写限制→影响→核查办法。失败可以是初次手算错位、哈希不符或一个被否定的解释;如未遇到实际故障,明确记录主动构造的反例。
证据位置:notes/experiment-log.csv、测试输出和prefix-check.json。
表达示例:合成价格不是市场样本,无法验证市场预测能力;下一步先写授权数据与可得时点方案,而不是把正收益当作证据。
贡献、复现与下一步
写清哪些代码来自模板、哪些由自己实现、AI做过什么及如何核验。下一步只改变一个条件,预先写判断标准。
证据位置:修改日志/版本、运行命令、新目录结果、独立讲解。
表达示例:模板已提供回测与测试;我完成成本解释、手算和交付核对。下一步检查期末平仓费对终值的影响:仅在末尾仓位非零时扣费,其余保持不变。
把空泛局限写成可行动的下一步
- 合成输入:不能评价A股预测能力 → 下一步先确定授权数据、字段与历史可得口径,再考虑接入。
- 简化成交:未模拟涨跌停、停牌、整手、滑点与排队 → 先挑一种限制写最小反例,验证无法成交时仓位如何处理。
- 统计证据不足:只看过这一份输入,不能证明稳健 → 预先登记规则与评价标准,再安排未被用于选择的验证;不要不断换种子挑赢家。
- 期末估值:未扣末尾清仓费 → 下一步只补这一项,检查末仓非零/为零两类情形,写明结论改变多少。
查看可复制的完整报告模板
# 研究报告:[自己填写标题]
> 目标1–2页正文,明细和日志另附。把本文件复制到notes/report.md再填写。
> 所有[]必须换成你实际验证的内容;不知道就写“未验证”及补查办法。
## 1. 问题与一句话结论
问题:[固定信号在三种费用下怎样变化?什么现象会让你怀疑实现有错?]
结论:[在什么数据与假设下,看到了什么;不能外推到哪里?]
## 2. 数据与范围
- 来源:本站原创合成生成器;seed=[ ],观测数=[ ],区间数=[ ]。
- 观测编号是顺序标签,不是交易日;不是A股行情,没有历史下载日期。
- 快照位置:reports/synthetic_prices.csv;SHA256见manifest.json。
- 检查:[完整正价格、时点手算等实际检查,注明文件/行或观测编号]。
## 3. 方法与基准
- 区间i=[ ];信号观察到=[ ];执行假设=[ ];持仓作用区间=[ ]。
- 仓位定义、换手定义、费用扣除公式:[ ]。
- 基准:[买入一次、同费用口径、期末估值方式]。
- 未建训练模型,未宣称独立样本外验证;若尝试其他规则,完整列出尝试。
## 4. 所有成本结果
从本次reports/experiments.csv抄写,勿凭记忆填写。
|费用bp|策略累计收益|策略最大回撤|基准累计收益|基准最大回撤|累计仓位变化|
|---|---|---|---|---|---|
|0|[ ]|[ ]|[ ]|[ ]|[ ]|
|5|[ ]|[ ]|[ ]|[ ]|[ ]|
|20|[ ]|[ ]|[ ]|[ ]|[ ]|
解释:[先写观察,再写可能原因,最后写尚不能证明什么。]
累计收益差用“百分点”;不是alpha。不把观测数直接当交易日进行年化。
## 5. 失败与局限
至少3条,每条写“限制→影响→如何核查或改进”。
- 数据:[ ]。
- 成交/成本:[ ]。
- 统计证据/信息时点:[ ]。
- 失败实验:[预期、实际、根因、修正、复测,指向notes/experiment-log.csv]。
## 6. 个人贡献与AI辅助
- 模板已提供:[引擎、信号、生成器、测试或辅助脚本等]。
- 我独立完成:[具体文件/解释/实验,以及如何证明]。
- AI参与:[生成、解释或调试的具体内容,我怎样核验]。
- 我仍不能独立解释:[ ]。
## 7. 下一步:一个可检验的小任务
假设=[ ];只改=[ ];保持=[ ];预期区分的两种解释=[ ];
成功/失败判定=[ ];预计用时=[ ]。不能只写“多调参”。
## 8. 复现
Python版本、执行顺序、测试数量与实际结果:[ ]。
本次引擎/数据哈希见manifest.json;全部计算文件指纹见delivery-manifest.json。
新目录重跑日期、目录、差异说明:[ ]。个人报告与日志另人工核对,不由指纹自动评分。
查看5分钟讲解提纲与面试追问
# 5分钟讲解(不照念完整报告)
- 0:00–0:40:研究问题是什么,数据是不是市场数据?
- 0:40–1:40:用一个区间解释可得信息、信号、执行、收益;指向timing-first-six.csv。
- 1:40–2:40:三种费用全部结果,基准是什么;收益和回撤怎样计算?
- 2:40–3:40:一次失败/检查,以及至少3个具体局限。
- 3:40–4:30:模板做了什么、你做了什么、AI做了什么,展示一处证据。
- 4:30–5:00:一个下一步实验,只改什么,怎样判断结果。
模拟追问:
1. 为什么不能说“代码shift了,所以没有未来信息”?
2. 原始结果为负,报告还要交吗?
3. 换一台机器能复现吗?哈希相同是否证明策略可靠?
4. 这段程序能直接用于A股吗?缺了哪些具体规则?
5. 去掉AI,你能现场解释哪一行,独立修改哪个小实验?
AI可以帮你解释某一行、审查遗漏或润色表达;每个数字回到输出核对,每个结论写清范围。不会解释的内容写为待补项,别用复杂术语遮住不理解。
常见问题:复现失败、结果不好、报告写不出来
ModuleNotFoundError: research / week4
先定位:只复制了一个步骤文件,或在错误目录运行。
怎么处理:完整解压,确认research.py、week4.py与01–04同目录。使用本目录.venv解释器。本周只用标准库,不要pip install research。
修后检查:在此目录运行-m unittest -v,应10项通过。
FileNotFoundError: reports或manifest不存在
先定位:跳过了前置步骤,或复制了不完整输出。
怎么处理:按01→02→03→04运行;03读取02生成的价格与manifest,04读取03生成的交付指纹。
修后检查:02成功生成301行价格,03成功生成三种费用结果后再运行04。
Ran 0 tests / FAILED
先定位:目录不对、test文件缺失,或修改破坏了时点/费用规则。
怎么处理:检查当前目录与两个test文件;读失败测试名称,先用短价格列表手算。不要删除断言或改预期来迁就错误。
修后检查:原计算5项与交付5项共10项通过;独立验收仍需人工进行。
文件已变化、缺失或新增 / 哈希不一致
先定位:03之后改动了.py或reports,或者把不同版本结果混在一起。
怎么处理:先保留旧快照与修改理由,查报错文件。确认改动合理后重跑02、03、04;不要手改manifest字符串。notes中的人工报告不在自动指纹范围。
修后检查:确认本次完整运行成功,04通过;意外篡改同一文件时仍应报错。
PermissionError / CSV被占用
先定位:Excel或其他程序占用结果文件,或目录不可写。
怎么处理:关闭占用文件,在自己的可写目录重跑。输出文件存在可能只是旧版本,不代表本次成功。
修后检查:终端成功结束,查看本次数据行数与manifest。
为什么收益为负,报告还要写
先定位:把研究交付误解成展示最高收益。
怎么处理:保留负结果。检查计算是否正确,解释在何种数据/费用下发生,写出被否定的假设与下一步。不能反复换种子只展示最好结果。
修后检查:报告列出全部0/5/20bp结果,结论不宣称已发现盈利能力。
费用升高却更赚钱 / 换手算了两遍
先定位:可能改变了持仓、扣费方向错误,或对0→1→0的两次变化又额外乘2。
怎么处理:先确认价格和positions完全一致;逐段用abs(position-previous_position)算换手,再按单边费用率扣除。
修后检查:本模型相同持仓下提高费用不提高终值,原模板累计仓位变化148。
看过当期价格再决定当期持仓
先定位:信号和收益列错位,或以为只写shift就自动安全。
怎么处理:写出信息可得、信号、成交和收益时点。第3段持仓来自观测0→1,执行在2,收益来自2→3。
修后检查:改变末尾价格不改变更早结果;另外手算一段有仓位记录。
把-0.215写成-0.215% / 把差值叫alpha
先定位:小数收益、百分比、百分点与风险调整指标混淆。
怎么处理:小数乘100才是百分比;两个累计收益相减后乘100是百分点差。本项目没有估计回归alpha。
修后检查:如-0.2对应-20%;-20%减-25%是+5个百分点,仍然是亏损。
报告只有表格 / 局限写成“存在风险”
先定位:缺少可检验的问题、机制解释和具体影响。
怎么处理:每个核心结果补“观察→解释→未证明”;每个局限补“限制→影响→下一步”。至少写合成数据、简化成交和统计证据三方面。
修后检查:读者能指出你结论的适用范围,并知道下一步要做什么。
换目录后指纹不同 / 同种子结果有差异
先定位:解释器绝对路径、平台或Python版本变化;也可能代码或数据不一致。
怎么处理:先比较代码与价格哈希,再逐项比较关键CSV。environment记录路径不同属于可解释差异,不能要求整个交付manifest逐字节相同;数值差异要定位,不能一律归因于环境。
修后检查:同环境同代码应复现关键CSV;跨环境差异逐项记录,先解决无法解释的计算差异。
笔记被覆盖 / AI写的报告自己讲不清
先定位:重复复制模板,或把生成文字当成已经掌握。
怎么处理:模板只复制一次;已写内容先备份。让AI解释或审查具体问题,回到代码和输出自己核验;不让脚本改写notes。
修后检查:能脱离参考做5分钟讲解,指出一处自己的证据、一次失败和一个下一步。
浏览器小练习:改变费用,先预测再运行
使用01的短价格序列,核心函数与本地引擎一致。初次运行应得到相同6段持仓和累计仓位变化4;随后把费用列表中的20改为50,预测累计收益变化,观察持仓是否保持不变。
只用于检查时点和成本,不写本机文件或替代交付测试。首次需联网下载Python;刷新或离开前复制保存代码。
在你的浏览器内运行。首次需联网下载 Python;支持标准库和 Pyodide 提供的 NumPy、pandas 等包。行情抓取、系统文件访问和部分第三方库请在本地环境运行。输出自检仅比对结果关键词,不代替逻辑审查。
单元5 · 60分钟独立交付验收
在新目录重跑,确认不是依赖旧输出
保留当前交付快照,另建一个空文件夹。复制根目录代码、README、模板和notes,暂不复制reports、.venv、缓存。按README创建新环境,运行10项测试与01→02→03→04。
逐项比较新旧synthetic_prices.csv、daily.csv、metrics.csv、experiments.csv。同环境同代码应复现关键结果;环境记录中的绝对路径变化可解释,不要求两个delivery-manifest逐字节相同。出现数值差异要查来源,不能一概忽略。
在副本中破坏一次,证明检查能发现
保留旧结果,在新目录的reports/daily.csv仅改一个数,运行04,应报文件变化。先记录这次主动反例,再按02→03→04恢复。不要手改manifest让它“通过”,也不要把主动反例写成真实市场异常。
不看参考讲5分钟
讲清研究问题、数据、时点、成本、结果、失败、局限、贡献和下一步。可查看自己的报告和证据,但不要照念参考答案;现场解释一段持仓与一笔费用。
评分沿用首月:时间边界、计算与成本、可复现交付、研究表达、独立贡献,各0–2分。0=不能完成,1=需提示或只能复述,2=独立完成并解释;至少8/10,前两项必须各2分。60分钟验收后,再用15分钟评分、25分钟补错、20分钟安排下月任务。
隔24–48小时:新价格与空白提纲
20分钟,只拿[100,120,100,110,100,110]、费用0,手算5段持仓、收益与累计收益,指出退出段使用哪一段历史。然后从空白提纲讲出假设、最薄弱证据和一个下一步实验,最后才运行代码核对。
独立复测完成后再看参考
持仓[0,0,1,0,1],收益[0,0,10%,0,10%],累计21%。第3段持仓来自更早100→120的上涨,第4段退出来自120→100的下跌;不能使用当期涨跌决定当期仓位。下一步要能区分具体原因,不能只说“继续调参”。
查看完整交付清单
# 第4周交付验收(自评,不是自动阅卷)
## 文件
- [ ] 根目录保留研究引擎、辅助脚本、两个test文件与README。
- [ ] reports中保留本次数据快照、逐期/成本结果、时点表、两个manifest、环境与前缀检查。
- [ ] notes/report.md是我填写的报告;experiment-log.csv包含全部实验和失败;有手算与讲解提纲。
- [ ] 所有数值来自本次输出,原始模板与我的修改可区分。
- [ ] 交付压缩包不含.venv、__pycache__、密钥、账户信息或未授权的数据。
## 新目录复现
- [ ] 保留交付快照,另建空目录,复制代码/模板/notes,重新建环境,不复制旧reports。
- [ ] 运行10项测试,再依次运行01–04,全部成功退出。
- [ ] 比较旧/新synthetic_prices.csv、daily.csv、metrics.csv、experiments.csv。
- [ ] Python或平台变化时记录environment.txt及manifest差异,不能把忽略差异当作通过。
- [ ] 04检查只验证计算文件完整性,不验证报告逻辑或我是否独立完成。
## 60分钟独立验收:5项各0–2分
0=不能完成;1=需提示或只能复述;2=独立完成并解释。
|项目|分数|证据|
|---|---|---|
|时间边界(必须2分)|||
|计算与成本(必须2分)|||
|可复现交付|||
|研究表达|||
|独立贡献|||
合计至少8/10,关键项必须各2分。时间安排:60分钟验收、15分钟评分、25分钟修正、20分钟下月安排。
隔24–48小时:只拿价格[100,120,100,110,100,110],费用0,20分钟手算5段持仓/收益/累计值;
从空白提纲讲清假设、最薄弱证据和下一步,再运行代码核对。不要提前看网页折叠答案。
复现失败先补README和版本记录;时点解释失败,回第1周和q-313画短序列;报告讲不清,缩到一个问题和一组对照。四周都独立通过后,再沿长期路线补统计验证与更完整研究。
资料与下一步
资料入口核验于2026-10-20。本周只查版本记录、unittest和数据泄漏;不需要为了写报告先学完整机器学习框架。原研究项目的5项测试继续保留,第4周包另加5项交付检查。