fix(monitor): strategy 侧休市表镜像+logs_stale 闸门迁 trading_day——国庆连休假黄同治 [vps]
CI/CD / test (push) Successful in 41s
CI/CD / nas-deploy (push) Successful in 30s
CI/CD / nas-verify (push) Successful in 9s

infra 提醒(b24f905 只落 data 侧)+域内自决, 两件:
- strategy_registry.EXCHANGE_HOLIDAYS 年度休市表(与 data 域逐字同步,
  CI test_holiday_table_synced_with_data_domain 强制两表相等, 单边改即红;
  年更义务经 data 年更闸+同步测传导双表)
- calendar_union 抽函数(镜像 data _calendar_days): 假定集剔休市表日,
  表缺年不剔(黄照旧绝不静默转绿), 确认集恒保留(表错不吞真相)
  → 10-05/06/07(表内周一~三)eve 交易日语义检整体不评
- logs_stale 闸门 weekday→trading_day: 引擎假日闲置不写日志=常态,
  裸 weekday 只免周末漏国庆周一连休(morn 假黄另一根因, prune 治不了它);
  表外长假尾复市日(10-08) morn 一班诚实黄属已知, 开市落笔自愈
- spec 同步: §1 时间线/§4.1 表/§4.2-5)/§4.3(五检+并集语义)/§7.1 坐标
  /§9.A-15,16 新坑/§10 值班行/§12 坐标+行号漂移勘正
- TDD 4 新测, portfolio 776+data_platform 682 绿零回归

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-03 22:05:02 +08:00
parent bf1fe98c82
commit 7f9b4133e5
4 changed files with 127 additions and 32 deletions
@@ -53,6 +53,7 @@
| 09-29 | QMT 事故(0929 案卷)→ 哨兵链强化复盘;09-29 日历首战立功(0928 ETF 缺写被确认集规则抓红) |
| 10-01 | v1.2/v1.3:fills_vs_paper 逐对账重设计(P1-3)+ engines_no_restart 盘窗口径重设计(P2-3)+ data_monitor_alive 互为外盯(P1-10)+ logs_stale 补盯 shadow_*(09-30)+ setpoint 7→3(_ex 退役配套) |
| 10-03 | 三域联合报警甄别(真故障 0,全假日假黄/常态误读)→ **用户令建本档统一监控设计**;同日假黄休市表 b24f905 落地+国庆日历勘正(**10-08 周四复市**,非 10-09) |
| 10-03 夜 | strategy 侧同治:EXCHANGE_HOLIDAYS 镜像进 strategy_registry(假定集剔休市日,语义逐字同 data)+ logs_stale 闸门 weekday→trading_day(裸 weekday 漏国庆周一连休=10-05~07 morn 假黄根因);CI 同步测强制两域表相等(infra 提醒触发,域内自决) |
---
@@ -110,7 +111,7 @@
- **假期语义(判读必读)**:柜台非交易日不给初始化交易→废单 msg=`[120141][证券交易未初始化][init_date=T-1,curr_date=T]`。此 msg **不匹配** HOLIDAY_WORDS(「非交易/休市/闭市/非交易日/节假日」——「证券交易未初始化」无「非交易」子串)→走 RED 不走 AMBER:自动触发 relogin、桥通常 0s 恢复(桥本就活,柜台侧不给开市)、**schtask rc=1 属预期**。2026 国庆实证 10-02(10-05/06/07 同);**复市日(2026-10-08 周四)09:15 转绿=桥复市金标准**(§10 值班表)。
### §3.5 坑史
→ §9.A 详表(A-1~A-14;防御链本体坑=A-6/7/8/9/10/11)。
→ §9.A 详表(A-1~A-16;防御链本体坑=A-6/7/8/9/10/11)。
---
@@ -122,16 +123,16 @@
- 三件套:`strategy_monitor.py`(153 行主入口) + `strategy_registry.py`(58 行 v1.3 十一检) + `strategy_checks.py`(368 行检查实现);wrapper=`strategy_monitor_wrapper.ps1`(恒 exit 0 纪律);注册=`register_strategy_monitor_schtasks.ps1`(promote 后 infra 执行一次工法)
- 班次:morn 08:05(盘前,relogin 07:50 后 15min)/ eve 21:30(盘后,错峰 20:45 日报+21:00 xt_eod 之后 23:50 data-eve 之前,09-28 核批)/ noon 按需不注册
- 产物:`data/monitor/health_strategy_{date}_{shift}.json` + `health_strategy_latest.json`(**health_latest.json 是数据域的,永不写**);alerts 写同库 `source="strategy"`;退出码 0=绿黄/3=红(wrapper 恒转 0)
- 只读纪律:不修数据;探测器自身抛错=单检降黄不阻断全班(strategy_monitor.py:48-56)
- 只读纪律:不修数据;探测器自身抛错=单检降黄不阻断全班(strategy_monitor.py:63-71)
### §4.1 十一检注册表(infra 抄录自 strategy_registry.py:13-58,2026-10-03 时点)
### §4.1 十一检注册表(infra 抄录自 strategy_registry.py:13-59,2026-10-03 时点;logs_stale 闸门当夜改 trading_day)
| key | fn | 班次 | severity | 闸门 | 关键参数 |
|---|---|---|---|---|---|
| strategy.engines_expected | engines_expected | morn/noon | red | — | expected=3(10-01 _ex 退役 setpoint 7→3,现役 58/59/63) |
| strategy.engines_actual | engines_actual | morn/noon | red | — | wmic needle=`sanguo_trader.shadow --account`(自匹配免疫)vs DB running(排 live 桌 mode='live') |
| strategy.engines_no_restart | engines_no_restart | morn/noon | red | — | 近 3 确认交易日 09:15-15:30 盘窗出生=违例(§11.0;10-01 P2-3 重设计,旧「当日 00:00 起」双失效已废) |
| strategy.logs_error_storm | logs_error_storm | morn/noon | yellow | — | live_*+shadow_* 26h 窗 ERROR/Traceback ≤20 |
| strategy.logs_stale | logs_stale | morn | yellow | weekday | live_*+shadow_* mtime ≤4d(09-30 修订:修前只盯 live_*=双失明) |
| strategy.logs_stale | logs_stale | morn | yellow | trading_day | live_*+shadow_* mtime ≤4d(09-30 修订:修前只盯 live_*=双失明;10-03 夜 weekday→trading_day 治连休假黄) |
| strategy.is_report_ran | is_report_ran | eve | yellow | trading_day | is_daily 当日行存在(20:45 后) |
| strategy.is_identity_resid | is_identity_resid | eve | red | trading_day | 当日行 \|identity_resid\|≤1e-4;无行=绿(缺失归 is_report_ran) |
| strategy.orders_leftover | orders_leftover | eve | red | trading_day | 重放当日无 open/ack/partial 残留(expire=终态不算) |
@@ -145,7 +146,7 @@
① 08-17 开盘零成交事故(重启丢任务)→「引擎在不在」是策略面第一问;paper_accounts running 行数=部署意图的权威面。② `_count_running`(:87-98)=`SELECT COUNT(*) FROM paper_accounts WHERE status='running' AND (mode IS NULL OR mode!='live')` 对注册表 expected,不等=红;**排 live 桌**(wmic needle 数的是 shadow 进程,live 桌回归日两口径不混——10-01 P1-3 同族)。③ expected=3(现役 58 通路日线/59 通路 15m/63 挑战者),翻转史 §4.4。④ :176-181;注册表 strategy_registry.py:14-17。⑤ 翻换间隙红窗=已知有界(§9.A-4);DB 与进程分歧定位配 engines_actual:DB 多=supervisor 未收割,进程多=手工拉起未登记。
**2) engines_actual**(morn/noon · red)
① DB 说 running ≠进程真活(08-17 形态之二)。② `run_wmic`(:29-35) 快照 name='python.exe' 进程 CSV→`parse_wmic_csv`(:38-57,行尾 rpartition 取 CreationDate、首逗号剥 NODE 列、cmdline 自带逗号不炸)→cmdline 含 NEEDLE `sanguo_trader.shadow --account`(:22) 计数 vs DB running,不等=红(:184-189)。③ 与 expected 同源=3。④ :184-189。⑤ wmic 自匹配史(08-21,§9.A-1)→needle 口径=监控/其它 python 进程天然不含该串,无需 +1;wmic 缺失/超时=异常上抛由 `check_entry`(strategy_monitor.py:48-56)单检降黄不阻断全班。
① DB 说 running ≠进程真活(08-17 形态之二)。② `run_wmic`(:29-35) 快照 name='python.exe' 进程 CSV→`parse_wmic_csv`(:38-57,行尾 rpartition 取 CreationDate、首逗号剥 NODE 列、cmdline 自带逗号不炸)→cmdline 含 NEEDLE `sanguo_trader.shadow --account`(:22) 计数 vs DB running,不等=红(:184-189)。③ 与 expected 同源=3。④ :184-189。⑤ wmic 自匹配史(08-21,§9.A-1)→needle 口径=监控/其它 python 进程天然不含该串,无需 +1;wmic 缺失/超时=异常上抛由 `check_entry`(strategy_monitor.py:63-71)单检降黄不阻断全班。
**3) engines_no_restart**(morn/noon · red)
① §11.0 会话日盘中重启=任务重启丢上下文(08-17 同族)。② 近 N 个**确认**交易日盘窗(ymd+0915 ~ ymd+1530)内出生的 shadow 进程=违例(:192-215);confirmed 日集来自 `trading_days`(:65-80,dbbardata 000300 近 50 日 **ro uri** 连接,库不可达=[] 调用方降级)。③ lookback_confirmed=3;窗口 09:15-15:30(10-01 P2-3 重设计定稿)。④ :192-215。⑤ 旧口径双失效已废(creation≥当日 00:00:盘中重启次日必绿[只盖 00:00-08:05]+08:05 当日必然未确认→red 恒降黄永不可达)——新口径违例日均为已确认日→red 可达不经降级;夜间/盘前出生=合法 deploy 窗不计;连休无新增确认日=自然绿(§9.A-3)。
@@ -153,8 +154,8 @@
**4) logs_error_storm**(morn/noon · yellow)
① 08-19 三日体检(影子三 bug+成交断供)靠事后翻日志→前移为窗口计数。② LOG_GLOBS=("live_*.log","shadow_*.log")(:26) 中 mtime 落 26h 窗的文件,ERROR/Traceback 行(:23)总数 ≤阈值(:220-241)。③ window_hours=26(防旧失败每日重炒,与 data 域 _LOG_WINDOW_H 同值 §7.4);max_errors=20(09-28 v1 初值,「跑两周后校准」纪律,至今未动)。④ :220-241。⑤ 09-30 修订前只盯 live_*=LIVE=0 时代恒 vacuous 绿(§9.A-2);假日桥 rpc timeout 族+退役实例死文件残留=存量计数,**判读看走势**(14080→2640 回落=残留判读成立;10-08 复市应清,§9.A-5);yellow=线索不是结论,升级看现役三引擎 ERROR 源。
**5) logs_stale**(morn · yellow · weekday 闸门)
① 引擎哑火最晚被发现的面;日志 mtime=「引擎还活着」最便宜信号。② live_*+shadow_* 全体 mtime 最大值距今 ≤max_age_days,超=黄;零文件=黄(files=0)(:244-259)。③ max_age_days=4(带过周末;长假第 5 日起黄=诚实信号,§10);weekday=True 纯周末不评。④ :244-259。⑤ 4d 带只免周末——国庆 10-05~08 morn 假黄已知取舍勿处置;09-30 同 storm 补 shadow_*(§9.A-2)。
**5) logs_stale**(morn · yellow · trading_day 闸门)
① 引擎哑火最晚被发现的面;日志 mtime=「引擎还活着」最便宜信号。② live_*+shadow_* 全体 mtime 最大值距今 ≤max_age_days,超=黄;零文件=黄(files=0)(:244-259)。③ max_age_days=4(带过周末);trading_day 闸门(10-03 夜自 weekday 迁入——假日引擎闲置不写日志=常态,裸 weekday 只免周末漏国庆周一连休,§9.A-15)。④ :244-259。⑤ 休市表内假日整体不评;表外长假尾复市日(如 10-08)morn 一班诚实黄、开市引擎落笔即愈勿处置;09-30 同 storm 补 shadow_*(§9.A-2)。
**6) is_report_ran**(eve · yellow · trading_day)
① 偏差日报断流曾靠人翻前端发现(P3 批次 09-26 上线后入监)。② pipeline.db is_daily 当日行存在(:264-268);产物由 api 常驻 APScheduler 日报链(20:45/20:50 族)写,21:30 eve 班核对。③ 无参数(存在性)。④ :264-268;`_pipeline_one` :161-166。⑤ 缺失归本检管径——identity_resid 无行=绿的分工防双报;交易日闸门+未确认降黄走 §7。
@@ -172,12 +173,12 @@
① 周报 W40 口径事故(fill_rate 分母)后产物存在性入监。② strategy_weekly 本 ISO 周行存在(week_start≥本周一,任一 strategy_id 即算,:325-334)。③ 无参数。④ :325-334;产物 job=`pipeline_weekly_report`(api 常驻每日 20:45,sanguo_trader/scheduler.py 注册,失败不影响 api 启动)。⑤ 周界=ISO 周一(`date.weekday()`),与跨年周序一致勿手搓。
**11) data_monitor_alive**(morn/eve · red)
① 10-01 P1-10 另一半:data-monitor 两班全死且其 SCHTASKS 自检自己也死时=全链静默的结构洞,我方反向外盯兜底。② `{data_root}/monitor/health_latest.json`(**数据域文件,我方只读永不写**)mtime 距今 ≤max_age_hours;**文件缺失亦红**(:337-353;ctx 路径构造 strategy_monitor.py:104-105)。③ max_age_hours=26(data 两班 08:05/23:50,最晚班+一个日历日余量)。④ :337-353。⑤ 与 data 域 SCHTASKS 19 项盯我方两班互为反向(§7.5);残余盲区=我方双班全死无人评(MON-202610-02)。
① 10-01 P1-10 另一半:data-monitor 两班全死且其 SCHTASKS 自检自己也死时=全链静默的结构洞,我方反向外盯兜底。② `{data_root}/monitor/health_latest.json`(**数据域文件,我方只读永不写**)mtime 距今 ≤max_age_hours;**文件缺失亦红**(:337-353;ctx 路径构造 strategy_monitor.py:114-116)。③ max_age_hours=26(data 两班 08:05/23:50,最晚班+一个日历日余量)。④ :337-353。⑤ 与 data 域 SCHTASKS 19 项盯我方两班互为反向(§7.5);残余盲区=我方双班全死无人评(MON-202610-02)。
### §4.3 日历/降级域内特记(总纲=§7,此处只记策略侧差异)
- **无闸门六检**(engines×3/storm/data_monitor_alive):节假日照评。预期形态=引擎常驻不停(假期绿);storm 吸收假日桥 rpc timeout 族(10-08 复市自愈);data-monitor 节假日 schtask 照跑(health 新鲜)。
- **trading_day 五检**(is_report/identity_resid/orders_leftover/fills/weekly):∉并集(周末/连休)整体不评**也不消解**(周五开的假警周一不自动消,strategy_monitor.py:112-113);未确认工作日红级降黄+`evidence.unconfirmed_day=True`(:117-120)。
- **weekday 一检**(logs_stale):纯周末不评,与日历无关。
- **无闸门五检**(engines×3/storm/data_monitor_alive):节假日照评。预期形态=引擎常驻不停(假期绿);storm 吸收假日桥 rpc timeout 族(10-08 复市自愈);data-monitor 节假日 schtask 照跑(health 新鲜)。
- **trading_day 六检**(is_report/identity_resid/orders_leftover/fills/weekly/logs_stale[10-03 夜迁入]):∉并集(周末/连休/休市表日)整体不评**也不消解**(周五开的假警周一不自动消,strategy_monitor.py:123);未确认工作日红级降黄+`evidence.unconfirmed_day=True`(:128-131)。
- **日历并集=确认集∪假定集**(`calendar_union` strategy_monitor.py:48-61,镜像 data `_calendar_days`):假定集=近 15 日工作日**剔 EXCHANGE_HOLIDAYS 休市表日**(strategy_registry.py:69-79,与 data 域表逐字同步,**CI 同步测强制两表相等**,§9.A-16);表缺年=不剔(黄照旧绝不静默转绿);确认集恒保留(dbbardata 实证日哪怕在表内也评——表错不吞真相)。
- **resolve_passed 语义**(:134):本班绿→按 check_key 全量消解 open 告警。10-01 死行争议终裁=data 对我错:resolve 按 check_key 消解、查库时序在 08:05 班前会看到「已消解」假象——**判读 open/red 前先对班次时刻**(§9.A-13)。
- noon 班=按需人工 `--shift noon`,不注册 schtask(registry shifts 含 noon 的检随之可手工单跑)。
@@ -190,7 +191,7 @@
- 未来增删实例同工序;expected 值与 `_count_running` 排 live 桌口径联动(§4.2-1②)。
### §4.5 坑史
→ §9.A 详表(A-1~A-14)。
→ §9.A 详表(A-1~A-16)。
---
@@ -313,20 +314,20 @@ kind 语义(检查分发依据,check_entry data_monitor.py:56-232):
## §7 横切语义学(owner: infra,两域对齐引用)✅ infra 已填
### §7.1 交易日历(两 monitor 逐字同款,strategy_monitor.py:88-98 / data_monitor 同构)
### §7.1 交易日历(两 monitor 逐字同款,strategy_monitor.py `calendar_union` :48-61 / data_monitor `_calendar_days` 同构)
- **日历 = 确认集 ∪ 假定集**:确认集=dbbardata `000300.SSE interval='d'` 实证日期(近 50 交易日查库,ro 连接);假定集=近 15 日工作日(含今日,纯 weekday<5 启发)
- **设计动机(09-25 实案,循环依赖治理)**:dbbardata 晚间源滞后/漏日时,若日历只信 dbbardata 会把全体检查判错(数据源坏→日历塌→监控整体失真);假定集兜住「今天该不该有数据」的期望面
- **10-03 起(b24f905)**:假定集剔除 `EXCHANGE_HOLIDAYS` 休市表日(细节 §5.5,data 域)
- **10-03 起(b24f905)**:假定集剔除 `EXCHANGE_HOLIDAYS` 休市表日(当夜起两域各持镜像:data §5.5 / strategy §4.3,两表 CI 强制相等 §9.A-16;表缺年不剔+确认集恒保留=fail-safe)
- 判别:今日 ∈ 确认集=确认交易日;∈ 并集-确认集=未确认工作日(走 §7.2);∉ 并集=周末/连休(trading_day 检整体不评)
### §7.2 三态容忍(未确认降级)
- 未确认工作日上的**红级**违例 → 降**黄**,`evidence.unconfirmed_day=True`(strategy_monitor.py:117-120)
- 未确认工作日上的**红级**违例 → 降**黄**,`evidence.unconfirmed_day=True`(strategy_monitor.py:128-131)
- 覆盖三场景:节假日、合并窗、确认源自身滞后
- 纪律:只降红→黄,**不消解、不静默转绿**
### §7.3 trading_day vs weekday 闸门
- `trading_day=True`:今日 ∉ 并集 → 该检整体**不评**(不误开告警,也不消解已开告警——周五开的假警周一不会自动消)
- `weekday=True`:纯周末不看(与日历无关,如 logs_stale 只在工作日班评)
- `weekday=True`:纯周末不看(与日历无关)——strategy 域 10-03 夜起无在役检用此闸(logs_stale 迁 trading_day 治连休假黄),字段保留备将来纯日历无关检
### §7.4 过程日志窗 26h
- 两个 monitor 日志类检查共用 26h 回看窗(防旧失败每日重炒进窗);strategy logs_error_storm 与 data 域 _LOG_WINDOW_H 同值
@@ -402,10 +403,14 @@ kind 语义(检查分发依据,check_entry data_monitor.py:56-232):
**A-12 fills_vs_paper 全表总量口径误红(P1-3,09-28~30 三日)**:现象=0:28/0:201/0:32 等「live 总数 vs shadow 总数」黄/红。根因=v1 无账户配对——live 桌停摆期 live_trades 结构性空、回归日两桌各跑各的策略总量必不平。判别法=告警 detail 无 pairs 字段(v1 形状)。修法坐标=逐对账 `_dual_track_pairs`+无 live 桌 n/a 绿(strategy_checks.py:112-138,300-322)。
**A-13 resolve_passed 班次时序误读(10-01 死行争议终裁)**:现象=人工查库见「告警已消解」与值班印象矛盾。根因=绿班按 check_key 全量消解 open 告警,查库时刻若在班前/班间会看到前一态。判别法=先对 08:05/21:30 班次时刻再看 open 集。修法坐标=strategy_monitor.py:125-134;alerts 排序用 first_seen 勿用全 NULL 的 acked_at。
**A-13 resolve_passed 班次时序误读(10-01 死行争议终裁)**:现象=人工查库见「告警已消解」与值班印象矛盾。根因=绿班按 check_key 全量消解 open 告警,查库时刻若在班前/班间会看到前一态。判别法=先对 08:05/21:30 班次时刻再看 open 集。修法坐标=strategy_monitor.py:139-148;alerts 排序用 first_seen 勿用全 NULL 的 acked_at。
**A-14 停退工序双坑(10-01 _ex 退役实证)**:现象=①守卫 SQL 0 命中险些漏停;②先 kill 后 UPDATE 的实例 60s 内复活成孤儿。根因=①`_ex` 判据在 strategies 列非 name 列;②supervisor 60s 轮询按 DB 状态拉起,kill→UPDATE 间隙=running 态被复活。判别法=守卫命中数应等于预期停退数;复活进程 cmdline 与 DB 行状态矛盾。修法坐标=工序=只 UPDATE stopped 让 supervisor 自收割+守卫用 `strategies LIKE '%\_ex%'`(supervisor docstring 明载;§4.4 工序铁律)。
**A-15 logs_stale 裸 weekday 闸门漏连休假日(10-03 infra 提醒触发)**:现象=国庆 10-05/06/07(周一~三)morn 连续假黄。根因=引擎假日闲置不写日志=常态,logs_stale 闸门 weekday=True 只免周末,连休中的工作日照评(09-30 日志至 10-05 已 5d>4d 带);同日历 prune 治标只治 eve 族——morn 无闸门/weekday 闸门检不查日历并集。判别法=假黄日为休市表内工作日+日志 mtime=最后交易日。修法坐标=闸门 weekday→trading_day(strategy_registry.py:31-33)+假定集剔休市表日(strategy_monitor.py:48-61);表外长假尾复市日 morn 一班诚实黄属已知勿处置。
**A-16 两域休市表漂移(EXCHANGE_HOLIDAYS 双副本)**:现象潜势=data/strategy 两注册表各持一份年度休市表,年更只改一边→同日两 monitor 判定矛盾(data 绿 strategy 黄或反)。根因=跨域不 import(监控互不耦合的代价)。判别法=同日两域 trading_day 判定不一致即查两表。修法坐标=CI 同步测 `test_holiday_table_synced_with_data_domain`(tests/portfolio/test_strategy_monitor.py)强制两表相等,单边改即红;年更义务经 data 侧年更闸+同步测传导双表。
### §9.B 数据面(data,20 条;格式=现象/根因/判别法/修法坐标)
1. **mirror 假黄连三日(0926~28)**:push.log DONE 行只带 [HH:MM:SS] 不带日期(仅 start 行带)→旧 `want_day in line` 过滤永剩 start 行。判别=DONE 行格式先实证。修=start 段归属法(nas_monitor_check.py:166-185)。
2. **dmsk mtime 80h 季频假黄(0927)**:季频披露驱动域无新披露不写盘,mtime 无结构意义(Q2 水位 06-30 停滞)。判别=域是日历驱动还是披露驱动。修=季频锚 `_dmsk_min_expected`(Q1→04-30/半年→**08-31**[1010 P2-1 勘正,法定=结束后 2 个月]/三季→10-31/年报→次年 04-30;nas_monitor_check.py:106-118)。
@@ -442,7 +447,7 @@ kind 语义(检查分发依据,check_entry data_monitor.py:56-232):
| 值班形态 | 含义 | 动作 |
|---|---|---|
| 连休窗 dbbardata/valuation 黄 | 休市无新数据,expected 日历旧把假日当交易日 | **b24f905 休市表已根治**——若再出现=休市表缺该假日,data 域补表(勿手工消警) |
| logs_stale 长假第 5 日起黄 | 4d 带只免周末,长假陈旧=诚实信号 | 确认引擎进程在即可,开市自愈,勿处置 |
| logs_stale 长假中黄 | 休市表日已不评;表外长假尾复市日 morn 一班黄=最后交易日后陈旧 | 确认引擎进程在即可,开市落笔自愈,勿处置 |
| error_storm 假日窗黄(rpc timeout 族) | 桥离线+引擎轮询超时 | 开市日验证自愈;复市日仍黄才升级 |
| probe0915 假期 rc=1 | 柜台非交易日不给初始化 | 无动作;**复市日 09:15 转绿=桥复市金标准**(2026-10-08) |
| bs-daily rc=2 单日 | baostock 登录失败 graceful | 次日 LOOKBACK 自愈;**连两日 rc=2 才升级查** |
@@ -469,8 +474,8 @@ kind 语义(检查分发依据,check_entry data_monitor.py:56-232):
| 组件 | 主文件 | 行数 | 关键坐标 |
|---|---|---|---|
| strategy-monitor | scripts/strategy_monitor/strategy_monitor.py | 153 | 主循环 :109-149;单检异常降黄 :48-56;resolve_passed :125-134 |
| strategy registry v1.3 | scripts/strategy_monitor/strategy_registry.py | 58 | 十一检 :13-58(闸门/班次/阈值全参数) |
| strategy-monitor | scripts/strategy_monitor/strategy_monitor.py | 164 | 主循环 :120-163;日历并集 calendar_union :48-61(剔休市表日);单检异常降黄 :63-71;resolve_passed :139-148 |
| strategy registry v1.3 | scripts/strategy_monitor/strategy_registry.py | 79 | 十一检 :13-59(闸门/班次/阈值全参数);EXCHANGE_HOLIDAYS :69-79(与 data 表 CI 强制同步) |
| strategy checks | scripts/strategy_monitor/strategy_checks.py | 368 | CHECK_FNS :356-368;needle :22 / LOG_GLOBS :26 / ro 日历 :65-80 / 排 live 桌 :87-98 / 双轨配对 :112-138 |
| strategy wrapper | scripts/strategy_monitor/strategy_monitor_wrapper.ps1 | 16 | 恒 exit 0 :16(env 清代理+迁移日志位) |
| data-monitor | scripts/data_platform/data_monitor.py | 535 | check_entry :56-232 / 日志层 :237-299 / schtask 层 :304-344 / inbox 摄取 :361-386 / `_calendar_days` :402-417 / main :419-531 |
+18 -7
View File
@@ -45,6 +45,21 @@ def default_logs_dir():
return os.path.join(_REPO, "logs")
def calendar_union(confirmed_days, now_date, lookback=15):
"""结果层日历 = 确认集(dbbardata 实证)∪近 lookback 日工作日假定集(镜像
data_monitor._calendar_days)。10-03 假黄根治: 假定集剔 EXCHANGE_HOLIDAYS
休市日(已知假日=零期望, 国庆连休不天天假黄); 表缺年=不剔(黄照旧绝不
静默转绿); 确认集恒保留(表错不吞真相)。"""
union = set(confirmed_days)
d0 = now_date - dt.timedelta(days=lookback)
while d0 <= now_date:
hol = reg.EXCHANGE_HOLIDAYS.get(d0.year)
if d0.weekday() < 5 and (hol is None or d0.strftime("%Y%m%d") not in hol):
union.add(d0.isoformat())
d0 += dt.timedelta(days=1)
return union
def check_entry(e, ctx):
"""注册表项分发; 探测器自身抛错=黄(单检坏了不阻断全班)."""
fn = chk.CHECK_FNS[e["fn"]]
@@ -86,15 +101,11 @@ def main(argv=None):
db_path = os.path.join(args.data_root, "alerts.db")
# 日历语义逐字镜像 data_monitor: 确认集(dbbardata 000300 实证)∪近 15 日
# 工作日假定集(含今日)——dbbardata 晚间源滞后/漏日会把全体判错, 假定集兜底。
# 工作日假定集(剔休市表日)——dbbardata 晚间源滞后/漏日会把全体判错,
# 假定集兜底。
days = chk.trading_days(args.main_db, now.date())
confirmed = {d.replace("-", "") for d in days}
union = set(days)
d0 = now.date() - dt.timedelta(days=15)
while d0 <= now.date():
if d0.weekday() < 5:
union.add(d0.isoformat())
d0 += dt.timedelta(days=1)
union = calendar_union(days, now.date())
is_confirmed = today_compact in confirmed
ctx = {"main_db": args.main_db, "pipeline_db": args.pipeline_db,
+26 -5
View File
@@ -4,9 +4,10 @@
初值阈值跑两周后校准(数据轨同款纪律)。
闸门字段:
trading_day=True → 交易日闸门: 日历=确认集(dbbardata 000300 实证)∪近 15 日
工作日假定集; 今日不在并集=周末/连休, 检查整体不评
(不误开也不消解); 在并集但不在确认集=未确认工作日,
红级违例降黄(节假日/日历源滞后三态容忍, data_monitor 同款)。
工作日假定集(剔 EXCHANGE_HOLIDAYS 休市表日, 表缺年不剔);
今日不在并集=周末/连休, 检查整体不评(不误开也不消解);
在并集但不在确认集=未确认工作日, 红级违例降黄
(节假日/日历源滞后三态容忍, data_monitor 同款)。
weekday=True → 纯工作日闸门(周末不看, 与日历无关)。
shifts: morn 08:05 盘前 / eve 21:30 盘后 / noon 午间按需(不注册 schtask)。
"""
@@ -28,8 +29,8 @@ REGISTRY = [
"window_hours": 26, "max_errors": 20, "enabled": True,
"title": "引擎日志(live+shadow)26h 窗 ERROR/Traceback 超带"},
{"key": "strategy.logs_stale", "fn": "logs_stale",
"shifts": ("morn",), "severity": "yellow", "trading_day": False,
"weekday": True, "max_age_days": 4, "enabled": True,
"shifts": ("morn",), "severity": "yellow", "trading_day": True,
"max_age_days": 4, "enabled": True,
"title": "引擎日志(live+shadow)停滞(>4 天无新写)"},
{"key": "strategy.is_report_ran", "fn": "is_report_ran",
"shifts": ("eve",), "severity": "yellow", "trading_day": True,
@@ -56,3 +57,23 @@ REGISTRY = [
"max_age_hours": 26, "enabled": True,
"title": "data-monitor健康文件停滞(>26h两班全死,互为外盯)"},
]
# 假定集剔除用(10-03 假黄根治, b24f905 data 侧同治)。2026 表=VPS dbbardata
# 000300 实证(至 1002)+交易所 2026-09-17 中秋国庆公告(1001~1007 休,
# **10-08[周四]复市**——勿沿用 10-09 旧误记)。与 data 域
# scripts/data_platform/monitor_registry.EXCHANGE_HOLIDAYS 逐字同步
# (tests/portfolio/test_strategy_monitor.py 强制两表相等, 单边改 CI 即红);
# data 侧年更闸强制其表含当年, 经同步测传导到我表。表缺年=不扣假定集
# (黄照旧绝不静默转绿)。错登真交易日会掩盖真缺失——只登实证/官方公告日,
# 勿凭记忆添。
EXCHANGE_HOLIDAYS = {
2026: {
"20260101", "20260102", # 元旦
"20260216", "20260217", "20260218", "20260219", "20260220", "20260223", # 春节
"20260406", # 清明
"20260501", "20260504", "20260505", # 劳动节
"20260619", # 端午
"20260925", # 中秋
"20261001", "20261002", "20261005", "20261006", "20261007", # 国庆(1008 复市)
},
}
+58
View File
@@ -515,6 +515,64 @@ def test_disabled_registry_entry_never_runs(fx, monkeypatch):
assert "strategy.engines_expected" not in {c["key"] for c in health["checks"]}
# ---------- 10-03 假黄根治: 休市表 prune + logs_stale 日历闸门 ----------
def test_holiday_table_day_skips_morn_and_eve(fx, monkeypatch):
"""2026-10-05(周一, 国庆休市表内): 假定集剔除→非交易日语义——eve 五检
整体不评; logs_stale 改挂 trading_day 闸门后同跳(引擎假日闲置不写日志=
常态, 裸 weekday 闸门漏国庆=10-05~07 三天假黄根因, b24f905 data 侧同治)."""
old = (dt.datetime(2026, 9, 23, 23, 50) - dt.timedelta(days=7)).timestamp()
os.utime(fx.log, (old, old)) # 日志陈旧(>4d)
fresh = dt.datetime(2026, 10, 5, 8, 0).timestamp()
os.utime(fx.root / "monitor" / "health_latest.json", (fresh, fresh))
monkeypatch.setattr(sm, "_TODAY_OVERRIDE", dt.date(2026, 10, 5))
assert _run(fx, "morn") == 0
assert not [r for r in _alerts(fx)
if r["check_key"] == "strategy.logs_stale"]
assert _run(fx, "eve") == 0
health = json.loads((fx.root / "monitor"
/ "health_strategy_2026-10-05_eve.json")
.read_text(encoding="utf-8"))
assert "strategy.is_report_ran" not in {c["key"] for c in health["checks"]}
def test_unlisted_weekday_still_evaluated_fail_safe(fx, monkeypatch):
"""表未登记的工作日(2026-10-08 复市日)不剔: 照评照降黄——表缺日/表错年
绝不静默转绿(data 侧同款 fail-safe, 复市日 is_report 假黄属诚实态)."""
monkeypatch.setattr(sm, "_TODAY_OVERRIDE", dt.date(2026, 10, 8))
fresh = dt.datetime(2026, 10, 8, 8, 0).timestamp()
os.utime(fx.root / "monitor" / "health_latest.json", (fresh, fresh))
assert _run(fx, "eve") == 0
ev = [r for r in _alerts(fx)
if r["check_key"] == "strategy.is_report_ran"][0]
assert ev["severity"] == "yellow"
def test_calendar_union_prunes_holidays_missing_year_fail_safe():
"""纯函数: 假定集剔休市表日; 表缺年不剔(跨年窗口逐年查表);
确认集恒保留(表错不吞真相)."""
u = sm.calendar_union([], dt.date(2026, 10, 8))
assert "2026-10-05" not in u # 国庆(表内)
assert "2026-10-08" in u # 复市日(表外, 照常假定)
assert "2026-09-25" not in u # 中秋(表内)
u27 = sm.calendar_union([], dt.date(2027, 1, 4))
assert "2027-01-04" in u27 # 2027 无表→不剔(黄照旧)
assert sm.calendar_union(["2026-10-05"],
dt.date(2026, 10, 8)) >= {"2026-10-05"}
def test_holiday_table_synced_with_data_domain():
"""两域休市表逐字同步: data 侧年更闸强制其表含当年, 本测强制我表=其表,
单边改动 CI 即红(漂移不可能静默发生)."""
dp = str(Path(__file__).resolve().parents[2] / "scripts" / "data_platform")
sys.path.insert(0, dp)
try:
import monitor_registry as data_reg
assert sm.reg.EXCHANGE_HOLIDAYS == data_reg.EXCHANGE_HOLIDAYS
finally:
sys.path.remove(dp)
# ---------- wmic CSV 解析(纯函数) ----------
def test_parse_wmic_csv_handles_commas_and_empty():