← 返回第4周学习计划
第4周 · 从运行代码到研究交付

第4周跟练:交付一份能复现、讲得清的研究

每次约2小时,共5个单元。把代码、数据、结果、解释和个人贡献整理成一个别人能接手的小项目。本周沿用本站原创合成研究模板,不要求策略赚钱,也不要求增加机器学习模型。

本周交付:别人能重跑,你能独立解释

整理目录 → 画时间轴 → 复现原结果 → 成本与未来变动检查 → 报告与讲解 → 新目录验收

想象你要向带教同事交接一个小研究:不只发一张收益截图,还要交代输入、代码、运行命令、失败记录与结论边界。报告可为1–2页正文,明细作为附件;5分钟讲清核心证据。

本周使用本站已有的单条合成价格研究引擎,和前两周的双标的CSV格式不同。前几周学到的“键、缺失、时点、核对”继续使用,但不把cleaned.csv直接塞进这个引擎。

相关题:q-010 研究流程、q-313 时间边界;在面试练习搜索I10、I17、I21、I23。

准备:把代码、自动结果和自己的文字分开

  1. 全部解压,用VS Code打开这个文件夹

    打开直接含research.py、week4.py和README.md的目录。research.py与原项目一致,附5项计算测试;新增5项交付检查,共10项。本周只用Python标准库,不需要安装pandas或数据库。

    text
    week4-study/
      research.py, week4.py, 01–04脚本   # 计算与操作代码
      test_research.py, test_delivery.py # 检查代码
      README.md, 大写模板文件            # 说明与空白模板
      reports/                          # 运行后生成:数据、结果、环境、指纹
      notes/                            # 你填写:报告、手算、日志、讲解
      .venv/, __pycache__/               # 本机环境与缓存,不作为交付附件

    检查点:原始数据快照不手工改;reports可以重生成;notes要保留自己的文字和修改历史。生成结果存在,不等于本次命令成功。

  2. 创建环境,确认用的是哪个Python

    Python 3.10+。已有本目录.venv则跳过第一行;只有py命令可用时,第一行将python改为py。不会安装可回第1周环境教程。

    powershell
    python -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,脚本参数去掉.\前缀。

  3. 复制个人工作模板,只做一次

    首次创建notes。若里面已经写过内容,跳过对应复制命令,先备份,避免被空白模板覆盖。

    powershell
    New-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.md

    Markdown就是带简单标记的文本,直接用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。

powershell
.\.venv\Scripts\python.exe .\01_timing.py

shift(1)是把上一条值贴到当前行,得到[缺失,10,11];shift(-1)得到[11,12,缺失],含未来值,不能直接作为当时决策信息。是否安全还取决于信号在何时可得、何时下单、何时成交。本模板故意留一整期执行延迟。

查看本步骤的注释函数

来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。

python
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
python
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版本和代码/数据哈希。

powershell
.\.venv\Scripts\python.exe .\02_reproduce.py

research.py负责原始计算;辅助脚本调用它后补充时点表和环境记录。manifest里的哈希是文件内容指纹,方便发现版本变化。相同哈希不证明逻辑正确,也不替代人工检查。

查看原研究引擎:research.py
python
"""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
python
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,保留全部结果和失败,不挑最好的一行。

powershell
.\.venv\Scripts\python.exe .\03_experiments.py

三种费用使用相同价格、信号与持仓路径。每次仓位变化只扣一次单边比例费用;0→1→0两次变化自然累计为2,不再额外乘2。未来变动实验只改最后价格,检查更早的持仓、收益、净值与换手不变。

查看本步骤的注释函数

来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。

python
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
python
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分钟修正讲不清的段落,再运行文件检查。

powershell
.\.venv\Scripts\python.exe .\04_check_delivery.py

报告要让读者知道:你问了什么、证据来自哪里、结果如何、哪些解释尚未验证。04只核对计算代码与生成结果是否和03记录的指纹一致,notes中的报告、日志和独立贡献必须人工验收。

查看本步骤的注释函数

来自week4.py,依赖共用导入与research.py。请运行完整下载包,不要只复制片段。

python
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
python
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历史不足0000
2历史不足1000
30→1:10→11上涨21120%
41→2:11→10下跌3010
52→3:10→12上涨411约18.181818%
63→4:12→11下跌5010

费用0时累计收益为1.2×(13/11)-1≈41.818182%,不能把20%与18.181818%直接相加。该小例专为手算,与301观测的研究结果不同。

text
turnover = abs(position - previous_position)
rate = cost_bps / 10000
growth = (1 - rate * turnover) * (1 + position * asset_return)
net_return = growth - 1
equity = previous_equity * growth

1bp=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个观测区间直接当交易日来年化。

自动结果核对单如何生成
python
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股预测能力 → 下一步先确定授权数据、字段与历史可得口径,再考虑接入。
  • 简化成交:未模拟涨跌停、停牌、整手、滑点与排队 → 先挑一种限制写最小反例,验证无法成交时仓位如何处理。
  • 统计证据不足:只看过这一份输入,不能证明稳健 → 预先登记规则与评价标准,再安排未被用于选择的验证;不要不断换种子挑赢家。
  • 期末估值:未扣末尾清仓费 → 下一步只补这一项,检查末仓非零/为零两类情形,写明结论改变多少。
查看可复制的完整报告模板
markdown
# 研究报告:[自己填写标题]

> 目标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分钟讲解提纲与面试追问
markdown
# 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 练习区

在你的浏览器内运行。首次需联网下载 Python;支持标准库和 Pyodide 提供的 NumPy、pandas 等包。行情抓取、系统文件访问和部分第三方库请在本地环境运行。输出自检仅比对结果关键词,不代替逻辑审查。

Python · 独立工作线程 · 执行限时10秒

单元5 · 60分钟独立交付验收

  1. 在新目录重跑,确认不是依赖旧输出

    保留当前交付快照,另建一个空文件夹。复制根目录代码、README、模板和notes,暂不复制reports、.venv、缓存。按README创建新环境,运行10项测试与01→02→03→04。

    逐项比较新旧synthetic_prices.csv、daily.csv、metrics.csv、experiments.csv。同环境同代码应复现关键结果;环境记录中的绝对路径变化可解释,不要求两个delivery-manifest逐字节相同。出现数值差异要查来源,不能一概忽略。

  2. 在副本中破坏一次,证明检查能发现

    保留旧结果,在新目录的reports/daily.csv仅改一个数,运行04,应报文件变化。先记录这次主动反例,再按02→03→04恢复。不要手改manifest让它“通过”,也不要把主动反例写成真实市场异常。

  3. 不看参考讲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的下跌;不能使用当期涨跌决定当期仓位。下一步要能区分具体原因,不能只说“继续调参”。

查看完整交付清单
markdown
# 第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项交付检查。