Skip to content

Latest commit

 

History

History
79 lines (52 loc) · 7.43 KB

backtesting.md

File metadata and controls

79 lines (52 loc) · 7.43 KB

本地练习历史与 B50 回测

这套流程不依赖中心服务器。练习记录存在本机 SQLite,公开比赛回放只读已有缓存,报告由用户主动下载和分享;没有遥测或自动上传,也不会根据个人成绩自动修改评分参数。

记录练习

DX 默认进入「Rating 总览」,展示 B50 与总分。「用时补录」查看可补录的 AC 题目,「练习历史」查看逐次尝试,「全部成绩」查看已记录的题目成绩。切换页面保留当前用户和年度。

页面顶部的「手动添加做题记录」可为已同步过提交的 Codeforces 题新增一次尝试,也可从题目卡片进入。

  • 用时为 1~86400 整数秒。未完成时填写已经投入的时间,不把它当成 AC 耗时。
  • 结果分为已 AC、未完成;已 AC 必须在本机存在该题的 AC 提交。
  • 类型分为未知、首次独立、熟题独立重做、使用题解/提示/AI。类型是用户自报,不是自动推断的事实。
  • 已 AC 的练习默认带入所选用户、本题最近一次已同步 AC 时间;选择「首次独立解题」时改用首次 AC 时间。可手动修改或清空,手动编辑后不再被自动覆盖。未完成或没有 AC 记录时留空,不拿填表时间替代。
  • 手填记录标为手动计时。「比赛计时」由服务端重新核对提交时间轴后保存为比赛间隔估算,同一比赛结果重复点击不会生成多份有效记录。
  • 较慢的记录仍保留。每题有效、未归档、非辅助的 AC 记录取最短用时,生成现有 B50 使用的 problem_times。
  • 填错后,在练习历史里「作废此条」,再补一条正确记录。作废不删历史,最佳成绩自动回退。题目弹窗还可以作废本题全部计分记录。
  • 换绑或解绑后,没有其他活跃账号关联的练习记录会归档,不会在新身份解出同题后重新混入。

Schema v10 增加 practice_attempts。升级时每份旧用时生成一条 legacy / unknown 记录,保留原秒数;练习日期为 NULL,无法恢复此前已被覆盖的尝试。原 B50 用时和分数不变。其他安装可先备份再运行:

npm run backup
npm run db:init
npm run dashboard

旧接口 POST /api/dx/time 保留用于兼容:它表示更正当前最佳记录(旧最佳作废,新增未知类型的手动记录),不伪装成一次新训练。新页面走 POST /api/dx/attempts。/api/dx/time/clear 作废全部可计分历史并清除当前最佳,不物理删除历史。

公开比赛的离线回测

npm run study:backtest -- --year=2026
# 快速检查少量缓存,不作为正式校准结论
npm run study:backtest -- --year=2026 --limit=5 --min-contests=1 --out=results/cf-study/b50-smoke.json

输入为 data/cf-study/manifest.json 列出的比赛及 raw 中的 standings、ratingChanges、status 缓存,默认输出 results/cf-study/b50-backtest.json。缓存缺失会直接报错,不会悄悄联网;补采集可沿用 study:fetch、study:expand、study:backfill 流程,详见 研究说明。可用 --manifest=路径 指定更完整的比赛清单,原始响应仍从项目的 CF 缓存目录读取。

回测重新调用题序重建逻辑,去掉 T97 构建时的选手与题目 Rating 距离窗口。不能直接使用 samples.json,因为它会漏掉高手做得很快的简单题。仍保留原有严格题序与子任务共用起点规则;这些筛选会遗漏跳题等情况。生产曲线已扩为 800~3500,手动记录可在该范围回测;公开比赛重建器目前只支持 800~2500,故公开比赛报告不能验证 2500 以上。各完成度算法统一使用当前 T97,报告记录 T97 指纹与拟合输入范围;旧指纹的报告不能与新报告合并。

按选手串起比赛,在指定 UTC 年末之前截取成绩。默认要求至少在 10 场缓存比赛中有符合重建条件的记录,这不是选手真实参赛总数,也不代表充分训练。满 B35/B15 与未满分开;另外按最后观察到的赛前 Rating 分段、有效题数分组,并给出前 25/50/100/200 次观察到的解题时的结果。

选手以 handle 哈希固定分为 development 和 holdout 两组,不在输出中保留 handle。这个拆分用于今后的候选参数比较;T97 已用研究比赛拟合过,因此这里不是独立验证 T97 的实验。不同进度分组的人群覆盖不同,也不能把组间差异当成训练的因果效果。

报告对比 linear-clipped-v1(旧的线性外推后截到 101)、bounded-tail-v2(有界快速尾段,慢速段仍是反比例)与 gentle-decay-v3(当前曲线:慢速段使用 ln(t/T97)=ad+bd²,d=97-A)。前两版保留原有结果;第三版可能改变单题评分和榜单选题,因此各版独立计算 B35/B15。报告记录曲线指纹、慢速曲线参数、版本、拟合范围、比赛覆盖和筛选限制;模型元信息不同的旧报告不能与新版混合汇总。统计包含六个完成度区间,以及每人榜内 [100,101)、恰好 101 的比例;汇总时按人等权,不按卡片数量加权。

本机报告与自愿分享

DX 页点击「本地回测 / 导出」,先查看对比,再下载 JSON。预览和下载均只请求本机服务,下载时按当前数据重新生成。

报告包含:

  • 当前最佳成绩(含未知来源旧记录)的两版对比,按所选出题年度分区,不是当年历史快照。
  • 有明确日期、在所选年末前的首次/重做独立手动记录,以及两类各自的榜单。
  • 全部有效尝试的计时来源、类型、结果与日期缺失数量。
  • 第 25/50/100/200/500 次有日期独立手动尝试时的榜单;未完成尝试计入投入次数但不计分。
  • 缺评级、缺用时、超出拟合范围、晚于出题年度、未知出题日期等覆盖信息。未知出题日期沿用产品规则归旧题。

所有历史回算使用当前可见的题目评级,并不是当时评级快照。只记录本机已知的尝试,不宣称覆盖完整训练过程。辅助、未知类型、比赛估算和未知日期记录不进入独立手动训练过程分析。没有成功数据时比例为 null,不能解释成 0%。

导出采用字段白名单,不包含姓名、账号、用户 ID、题号、代码、逐题用时、具体练习日期、数据库路径或请求标识。仍含练习数量和分布,用户应在决定分享前查看内容。

收到自愿分享的报告后,在本机汇总:

npm run backtest:merge -- --out=results/local-backtest-merged.json report1.json report2.json

只接受相同算法/曲线版本、年度和年界时区的报告。拒绝重复传入同一路径、无效分布和覆盖输入文件;按全部/满榜/未满分开汇总。每人只选一份最新报告:去掉身份字段后无法自动识别跨文件是否为同一个人,去重需要组织者自行保证。程序不发送报告。

API

  • POST /api/dx/attempts:userId, problemId, seconds, outcome, practiceKind, timingSource, attemptedAt, requestId。requestId 用于请求重试去重。
  • GET /api/dx/attempts?user=1&offset=0:每页 50 条,包括已作废、排除已归档记录。
  • POST /api/dx/attempts/void:userId, id,只能作废该用户的记录。
  • GET /api/dx/backtest?user=1&year=2026:字段白名单汇总;加 download=1 返回 JSON 附件。

写接口沿用本机回环与同源检查。读接口只读,不执行迁移。