From 338558e03a8816ed827abe2f76cc5fde242d211b Mon Sep 17 00:00:00 2001 From: zlt Date: Tue, 8 Sep 2026 13:24:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=8D=E7=9B=98=E5=91=A8=E6=8A=A5=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=8A=A8=E4=BD=9C=E5=85=A5=E5=8F=A3=EF=BC=9Arun.py=20?= =?UTF-8?q?plan-review=20=E6=BB=9A=E5=8A=A8=E7=AA=97=E5=8F=A3=E3=80=81?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E6=AD=A5=E9=AA=A4=20plan-review=E3=80=81?= =?UTF-8?q?=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- docs/交接_2026-09-08.md | 2 +- plan_review.py | 16 +++++++++++++++- run.py | 17 +++++++++++++++++ test_plan_review_window.py | 32 ++++++++++++++++++++++++++++++++ xxl.py | 8 ++++++-- 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 test_plan_review_window.py diff --git a/docs/交接_2026-09-08.md b/docs/交接_2026-09-08.md index f09ef53..3282ba1 100644 --- a/docs/交接_2026-09-08.md +++ b/docs/交接_2026-09-08.md @@ -12,7 +12,7 @@ | 系统 | 机器 | 代码 | 状态 | |---|---|---|---| | 数据基座 | 178 | 本段未改代码 | 日检 16 项通过;待抽取台账 1,086 份与队列对不上(运维核) | -| 选股系统 | 155 | 1d76b90 | 逐票逻辑状态表在攒(09-04、09-07 两天);催化事件与定价状态随明早构建生效;**接口容器要再重启一次**才把这两列送进实时计划 | +| 选股系统 | 155 | 1d76b90 | 逐票逻辑状态表在攒(09-04、09-07 两天);催化事件与定价状态随明早构建生效;接口容器 09-08 13:10 已重启,实时计划已带这两列,PMS 拉一次解析到 71 行定价状态、17 行事件 | | PMS | 155 | 0fa4a5a(镜像已重建) | 全套 680 例;四容器健康,通道在线,目标 192.168.18.182 模拟侧,表前缀空 | | 择时决策系统 | 188 | 4778efd | PV_CHAIN_ENABLED 与 PV_VISION_ENABLED 已开,研判工作容器 09-08 12:40 重启 | diff --git a/plan_review.py b/plan_review.py index aabc59e..0f68905 100644 --- a/plan_review.py +++ b/plan_review.py @@ -583,6 +583,17 @@ def ledger_table() -> pd.DataFrame: for e in entries]) +def weekly_window(today=None, *, tail_days: int = 7, span_days: int = 21) -> tuple[str, str]: + """每周五固定动作的复盘窗口(2026-09-07 方案第五件之三)。 + 截止日取 7 个自然日前,让最短的 5 日期限对窗口尾部的计划日也算得出收益; + 起点再往前 21 个自然日,约十五个计划日。返回 ISO 日期字符串 (since, until)。""" + import datetime as dt + today = today or dt.date.today() + until = today - dt.timedelta(days=tail_days) + since = until - dt.timedelta(days=span_days) + return since.isoformat(), until.isoformat() + + def main() -> int: ap = argparse.ArgumentParser(description="候选单复盘(只读,名单级观察收益,不是回测)") ap.add_argument("--since", default="2026-07-29") @@ -591,9 +602,12 @@ def main() -> int: ap.add_argument("--start-price", choices=["next_close", "signal_close"], default="next_close", help="next_close=次日收盘起算(默认,实盘口径);signal_close=信号日收盘起算(对账方案 1.8 系列)") ap.add_argument("--out", default="data/review") + ap.add_argument("--weekly", action="store_true", + help="按每周五固定动作的滚动窗口跑(截止 7 天前、往前 21 天),忽略 --since/--until") a = ap.parse_args() hs = tuple(int(x) for x in a.horizons.split(",") if x.strip()) - run(a.since, a.until, hs, a.start_price, a.out) + since, until = (weekly_window() if a.weekly else (a.since, a.until)) + run(since, until, hs, a.start_price, a.out) return 0 diff --git a/run.py b/run.py index 5ba0709..f63d5fa 100644 --- a/run.py +++ b/run.py @@ -187,6 +187,13 @@ def main(): ra.add_argument("--date", help="默认取 score 表最新日(与 plan 同口径)") jg = sub.add_parser("judgement-snapshot") # 日频行业观点快照(只写不判,攒版本史) jg.add_argument("--date", help="计划日,默认今天;补写过去的日期会让版本史失真,会有提示") + pr = sub.add_parser("plan-review") # 复盘周报(每周五固定动作;只读,名单级,不是回测) + pr.add_argument("--date", help="以哪天为今天算滚动窗口,默认今天;调度中心统一追加的就是它") + pr.add_argument("--since", help="给了就按给定窗口跑,不再滚动") + pr.add_argument("--until") + pr.add_argument("--horizons", default="5,10,20") + pr.add_argument("--start-price", choices=["next_close", "signal_close"], default="next_close") + pr.add_argument("--out", default="data/review") f = sub.add_parser("freeze") f.add_argument("--date", help="默认今天") b = sub.add_parser("build") @@ -227,6 +234,16 @@ def main(): elif a.cmd == "judgement-snapshot": import judgement judgement.snapshot(a.date) + elif a.cmd == "plan-review": + import datetime as _dt + import plan_review + since, until = a.since, a.until + if not since and not until: + today = _dt.date.fromisoformat(a.date) if a.date else None + since, until = plan_review.weekly_window(today) + hs = tuple(int(x) for x in a.horizons.split(",") if x.strip()) + print(f"[plan-review] 窗口 {since} ~ {until},期限 {hs},起算 {a.start_price}") + plan_review.run(since, until, hs, a.start_price, a.out) elif a.cmd == "tracks": import tracks tracks.coverage_report() diff --git a/test_plan_review_window.py b/test_plan_review_window.py new file mode 100644 index 0000000..ef211c7 --- /dev/null +++ b/test_plan_review_window.py @@ -0,0 +1,32 @@ +"""复盘周报的固定动作入口(2026-09-07 方案第五件之三):滚动窗口与调度步骤白名单。""" +import datetime as dt + +import plan_review +import xxl + + +def test_weekly_window_default_span(): + # 周五 2026-09-11 触发:截止 7 天前的 09-04,起点再往前 21 天的 08-14 + since, until = plan_review.weekly_window(dt.date(2026, 9, 11)) + assert (since, until) == ("2026-08-14", "2026-09-04") + + +def test_weekly_window_is_rolling_and_iso(): + a = plan_review.weekly_window(dt.date(2026, 9, 11)) + b = plan_review.weekly_window(dt.date(2026, 9, 18)) + assert b != a + assert (dt.date.fromisoformat(b[0]) - dt.date.fromisoformat(a[0])).days == 7 + assert dt.date.fromisoformat(a[1]) - dt.date.fromisoformat(a[0]) == dt.timedelta(days=21) + + +def test_weekly_window_today_default(): + since, until = plan_review.weekly_window() + assert dt.date.fromisoformat(until) == dt.date.today() - dt.timedelta(days=7) + assert since < until + + +def test_plan_review_step_registered_but_not_default(): + assert "plan-review" in xxl.STEP_ORDER + assert xxl.STEP_CMDS["plan-review"] == ["run.py", "plan-review"] + assert "plan-review" not in xxl.DEFAULT_STEPS # 不进默认三步,平台单建周五任务 + assert xxl.STEP_ORDER.index("plan-review") > xxl.STEP_ORDER.index("push-pool") diff --git a/xxl.py b/xxl.py index b746fde..64e3982 100644 --- a/xxl.py +++ b/xxl.py @@ -24,9 +24,10 @@ tasks_periodic 的 notify_xxl),对平台表现完全一致: build 因子构建(run.py build all --mode daily) plan 生成当日选股计划文件 push-pool 计划写入股票池 + 触发决策系统增量补扫 -后面两步不在默认三步里,平台各建一个任务、显式传 steps 才跑: +后面三步不在默认三步里,平台各建一个任务、显式传 steps 才跑: regime-append 把决策系统 08:40 预热的市场区制写进当天计划快照的环境段(平台 08:45 触发) judgement-snapshot 把数据基座当前这一版产业研判与环节评析抄一行,攒版本史(只写不判) + plan-review 候选单复盘周报,滚动窗口(截止 7 天前、往前 21 天),只读(平台每周五收盘后触发) """ import datetime as dt import os @@ -49,7 +50,7 @@ HERE = os.path.dirname(os.path.abspath(__file__)) LOG_PATH = os.path.join(HERE, "data", "xxl_build.log") # 步骤 → 命令(顺序即执行顺序;--date 由触发参数统一追加) -STEP_ORDER = ("build", "plan", "push-pool", "regime-append", "judgement-snapshot") +STEP_ORDER = ("build", "plan", "push-pool", "regime-append", "judgement-snapshot", "plan-review") STEP_CMDS = { "build": ["run.py", "build", "all", "--mode", "daily"], "plan": ["run.py", "plan"], @@ -62,6 +63,9 @@ STEP_CMDS = { # 产业研判与环节评析抄进选股系统自己的表,攒版本史,只写不判。它与出计划互不依赖,同样 # 不在默认三步里,必须显式 steps=judgement-snapshot 才跑。 "judgement-snapshot": ["run.py", "judgement-snapshot"], + # 复盘周报(2026-09-07 方案第五件之三):每周五收盘后平台单建一个任务只跑这一步,写 data/review。 + # 只读,名单级观察收益,不是回测;--date 由平台追加时当作「今天」算滚动窗口。 + "plan-review": ["run.py", "plan-review"], } DEFAULT_STEPS = ("build", "plan", "push-pool") STEP_TIMEOUT_SEC = 3600 # 单步上限一小时,防呆死(正常盘前链全程分钟级)