Compare commits
4 Commits
97764cee80
...
d4617812c5
| Author | SHA1 | Date | |
|---|---|---|---|
| d4617812c5 | |||
| 3bca54d69b | |||
| 299f3a27ed | |||
| 3ced1474a8 |
File diff suppressed because one or more lines are too long
@@ -202,7 +202,7 @@
|
||||
| 因子贡献分解 | `sanguo_factor/attribution.py` | 日度 Fama-MacBeth 回归式归因(P3 第三刀, 流水线 spec §4.7 五条): 0.7 族代表+控制集(市场/log市值/行业哑变量)+sqrt(市值) WLS; 恒等式可加和+size 泄漏锚点+ridge 对照(λ=1 固定档, **惩罚随列方差缩放 A+λ·diag(A)≡相关形阵+λI——尺度不变, 等权/√市值两通道同效**; 09-27 审计 F-2 修订: 旧 λI 加在权重放大的原始阵上, √市值量级相对收缩 ~1e-9=no-op; 系数统一收缩 1/(1+λ), 仅 VIF>10 触发, 当前 quant12 vif≈1.4 不进通道故历史产物不受影响); 行业表 join 不中→industry_code=0 独立桶(显式 ind_0 哑变量, 0 桶最大时自适应基线), unknown_industry_share 披露——不静默丢样本(2026-09-26 data 契约; 表交付 .SH/.SZ 通用形态, 消费端 _norm_symbol 归一到 vt 形); 产物 attribution_<end>.json 供 /pipeline/attribution factorContribution 消费 |
|
||||
| 归因批(月度 stage4) | `sanguo_factor/attribution_batch.py` | 09-27 落地、10-04 首班起挂月度批评链第四段(runner=`scripts/nas_sync/run_factor_monthly_standalone.sh`): ①15 截面增量前置(12 quant 源+size_log+在位者 composite_quant12_v2a_szneul+挑战者 composite_quant12_szneul, daily_section 220 历日短窗幂等 merge——NAS 截面无每日班, 批前必然新鲜; as_of 撞周末先归一 ≤as_of 最近 bar 日, `latest_bar_date` 库内权威防 sanity raise) ②归因双跑同窗同参(全窗锚 2021-07-01, 挑战者受其截面自然窗限制; 挑战者落工作区 challenger/ 子目录=端点 glob 非递归防顶掉) ③cp 双投递生效位(在位者件→`data/attribution/` 根 glob 取最新; 挑战者件→`challenger/attribution_<end>_<因子名>.json` 带因子名)——#69 影子 A/B 四周对照的归因侧供给; 工作区 `/volume1/stock/factor_cross_section/attribution/` 留全历史、生效位只持最新; fail loud=截面断供/归因挂/投递缺任一→容器非零→班次 final=1(静默旧件=红线) |
|
||||
| 研判卡回放 | `sanguo_factor/verdict_replay.py` | 月度批评同班车:monthly_points 触发点普查(已晋级逐月 t 中位数<集体地板)+七档事后近似成绩(基准回测曲线等权混合,档3/4=需精确验证);产物 verdict_replay.json 供 `/pipeline/research-card` 拼卡(设计=流水线 spec §4.7 P3 第一刀,2026-09-25) |
|
||||
| 先批后评·第一段 | `sanguo_factor/monthly_batch.py` | 月度批评日全量重评批薄 CLI(注册表全量→12M 窗→复用 run_batch_eval),跑批从「人点」变「日历」(决议 J) |
|
||||
| 先批后评·第一段 | `sanguo_factor/monthly_batch.py` | 月度批评日全量重评批薄 CLI(注册表全量→12M 窗→复用 run_batch_eval),跑批从「人点」变「日历」(决议 J);**2026-10-10 加 `--daily`**(决议 H 修订:as_of 直用不归一月末,label=`daily_` 前缀;夜班批 as-of=T-1 双机 21:30,新因子出生次日得 t=盲区 1 月→1 天;判定/报告层月末快照语义不动=月度链照旧) |
|
||||
| 数据缺口比对 | `sanguo_factor/data_gap_check.py` + `config/data_manifest.yaml` | 注册表 params.source vs 底座 manifest,缺口 exit 2 开 issue 转 data session(决议 J/§11.3-②) |
|
||||
|
||||
数据链: eval_store 的 metrics["1"]["t_stat"] 即衰减判定输入(daily_section 日度落库已在产原料)。部署接线(NAS DSM 任务/VPS schtask/对拍汇集)等用户在场执行,步骤在计划档尾节。
|
||||
|
||||
@@ -61,6 +61,7 @@ F 环是「管道」与「飞轮」的分界:归因发现→可能=衰减触
|
||||
- **双机对拍=验证校准期机制(非永久双跑)**:对拍目的是验证与校准(防「当时就是错的」),不是刚需;首班 10-04 双机同评已双绿(一致 9/分歧 0),日度化后再跑**连续 4 周日度对拍关键指标(IC/t/rank)一致**即宣告验证通过→**VPS 独立日度跑**;NAS 降为**月度抽检对拍**(每月末快照对拍一次),「分歧超阈→不出降权告警、只开排查 issue(宁可误报排查,不可错发告警)」机制永久保留;
|
||||
- **出生盲区消解(本修订最大红利)**:新因子出生当夜/次日自动搭上日班车拿到 t,盲区从最长一个月缩到一天——「出生预考」议题不再需要;
|
||||
- **性能注记**:月度冷跑实测 40min 级;日度滚动下 12M 窗每天只滚一天,历史因子值全命中三级缓存(h5→md5 pkl),只有新截面要算,预期十几分钟级;首月实测校准,上限=40min 也放得下夜班窗口。
|
||||
- **调度实配(2026-10-10 落地)**:`monthly_batch --daily` 开关(as_of 直用不归一月末,label=`daily_` 前缀;月度链缺省行为不变)。双机日度任务 21:30(VPS schtask `sanguo-factor-daily`=XML Daily,wrapper=`scripts/factor_research/run_factor_daily_wrapper.ps1` 两段链:daily 批+`monthly_review` 日度对拍件;NAS DSM 任务每日 21:30,runner=`scripts/nas_sync/run_factor_daily_standalone.sh` 一次性容器同款)。**registry 对齐修正(存量错位)**:批侧(日度+月度)`--registry` 从 `config/` 种子改读 `data/` 运行副本——与控制台同源,decompose 新生/判死即时进批视野(旧错位=首班后出生的因子月度批不认识、墓园因子照常占算力;防御=运行副本缺失时从种子拷)。**日度对拍件落 `daily/` 子目录**(判定层端点只读根层,月末快照语义零改);月度四段链照旧每月第一个周日跑(正式报告/归因/缺口开单权威不动,①段重跑幂等)。对拍自动化(NAS 拉 VPS 件+`review_compare` 台账)=验证期工具,后续小班补。
|
||||
- **衰减判定(决议 F,只对已晋级因子)**:
|
||||
|
||||
| 条件 | 起步默认 | 防什么 |
|
||||
|
||||
@@ -81,7 +81,7 @@ def resolve_with_yaml(registry: dict):
|
||||
|
||||
|
||||
def plan_batch(registry: dict, as_of: str, window_months: int = 12,
|
||||
resolver=None) -> dict[str, Any]:
|
||||
resolver=None, daily: bool = False) -> dict[str, Any]:
|
||||
"""注册表→批参数(main 只做 IO 编排,可单测).
|
||||
|
||||
「纯函数」契约勘正(P3-12): resolver=get_factor(默认)时纯读;
|
||||
@@ -94,14 +94,17 @@ def plan_batch(registry: dict, as_of: str, window_months: int = 12,
|
||||
曾漂移 5 名(fa_gross_margin/s0* vs 库名),月度批评会空转.
|
||||
"""
|
||||
resolve = resolver or get_factor
|
||||
end = month_end(as_of)
|
||||
# 日度化(决议 H 修订):daily 时 as_of 直用(T-1 夜班),不归一月末;
|
||||
# 月度缺省行为不变(月末快照=判定层语义,月度链照旧归一).
|
||||
end = as_of if daily else month_end(as_of)
|
||||
names = sorted(n for n, e in registry["factors"].items()
|
||||
if e["status"] not in EXCLUDED_STATUSES)
|
||||
skipped = [n for n in names if resolve(n) is None]
|
||||
return {"factor_names": [n for n in names if resolve(n) is not None],
|
||||
"skipped_unregistered": skipped,
|
||||
"start": window_start(end, window_months),
|
||||
"end": end, "label": f"monthly_{as_of[:7]}"}
|
||||
"end": end,
|
||||
"label": (f"daily_{as_of}" if daily else f"monthly_{as_of[:7]}")}
|
||||
|
||||
|
||||
def decomposer_new_names(registry: dict, factor_names: list[str]) -> list[str]:
|
||||
@@ -152,10 +155,13 @@ def main(argv: list[str] | None = None) -> int:
|
||||
ap.add_argument("--fund-data-dir", default=None,
|
||||
help="财务静态域根目录 override(VPS 镜像路径与 NAS 默认各异时直传,"
|
||||
"缺省走 cfg/默认解析)")
|
||||
ap.add_argument("--daily", action="store_true",
|
||||
help="日度模式(决议 H 修订):as_of 直用不归一月末,"
|
||||
"label=daily_ 前缀;夜班批 as-of=T-1")
|
||||
args = ap.parse_args(argv)
|
||||
registry = load_registry(args.registry)
|
||||
plan = plan_batch(registry, args.as_of, args.window_months,
|
||||
resolver=resolve_with_yaml(registry))
|
||||
resolver=resolve_with_yaml(registry), daily=args.daily)
|
||||
if not plan["factor_names"]:
|
||||
print("[monthly_batch] 注册表无可跑因子(全 retired/graveyard?)", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
@@ -31,13 +31,20 @@ Set-Location C:\sanguo_vnpy_v2
|
||||
|
||||
$PY = 'C:\Python310\python.exe'
|
||||
$EVAL_DB = 'C:\sanguo_vnpy_v2\data\factor_eval.db'
|
||||
$REGISTRY = 'C:\sanguo_vnpy_v2\config\factor_registry.yaml'
|
||||
# 2026-10-10 registry aligned with console runtime copy (data/, not config seed):
|
||||
# decomposer newborns / graveyard verdicts take effect in the next batch view.
|
||||
$REGISTRY = 'C:\sanguo_vnpy_v2\data\factor_registry.yaml'
|
||||
$MANIFEST = 'C:\sanguo_vnpy_v2\config\data_manifest.yaml'
|
||||
$VNDB = 'C:\sanguo_vnpy_v2\data\quant_trading.db'
|
||||
$FUND_DIR = 'C:\sanguo_vnpy_v2\data\static'
|
||||
$OUT_DIR = 'C:\sanguo_vnpy_v2\reports\factor_monthly'
|
||||
$LOG = Join-Path $OUT_DIR 'factor_monthly_cron.log'
|
||||
|
||||
# runtime copy defense: seed from config if missing (machine never ran the API)
|
||||
if (-not (Test-Path $REGISTRY)) {
|
||||
Copy-Item 'C:\sanguo_vnpy_v2\config\factor_registry.yaml' $REGISTRY
|
||||
}
|
||||
|
||||
# as_of = last day of previous natural month
|
||||
$first = Get-Date -Day 1
|
||||
$asof = $first.AddDays(-1).ToString('yyyy-MM-dd')
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# register_factor_daily_task.ps1 -- register schtask sanguo-factor-daily on VPS
|
||||
# (2026-10-10 pipeline spec sec 4.2 decision H revision: cadence monthly -> daily)
|
||||
#
|
||||
# Trigger: DAILY 21:30 local. Rationale: the nightly batch evaluates as_of=T-1
|
||||
# and must start only after the 21:00 xt_eod bridge has finished ingesting the
|
||||
# T-1 session bars (21:00 -> ~21:10 done, 21:30 leaves margin).
|
||||
# Sibling of sanguo-factor-monthly (first Sunday 09:30) which stays untouched:
|
||||
# the monthly chain keeps producing official month-end snapshot reports.
|
||||
#
|
||||
# Run on VPS AFTER vps-deploy has placed the wrapper:
|
||||
# powershell -ExecutionPolicy Bypass -File C:\sanguo_vnpy_v2\scripts\factor_research\register_factor_daily_task.ps1
|
||||
# Verify afterwards: schtasks /query /tn sanguo-factor-daily /fo LIST /v
|
||||
# (Next Run Time should be today/tomorrow 21:30.)
|
||||
|
||||
$env:http_proxy = ''
|
||||
$env:https_proxy = ''
|
||||
$env:all_proxy = ''
|
||||
|
||||
# ExecutionTimeLimit PT0S = no limit: cold full re-eval measured ~40min level;
|
||||
# with rolling factor-value cache warm it is expected to drop to ~15min level
|
||||
# (spec sec 4.2 performance note). IgnoreNew guards overlap with the monthly
|
||||
# chain when first-Sunday 09:30 run spills (rare, batch is idempotent anyway).
|
||||
$taskXml = @'
|
||||
<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<RegistrationInfo>
|
||||
<Description>factor daily re-eval batch (monthly_batch --daily as_of=T-1 -> monthly_review into daily/ subdir), 21:30 nightly after xt_eod</Description>
|
||||
</RegistrationInfo>
|
||||
<Triggers>
|
||||
<CalendarTrigger>
|
||||
<StartBoundary>2026-10-10T21:30:00</StartBoundary>
|
||||
<Enabled>true</Enabled>
|
||||
<ScheduleByDay>
|
||||
<DaysInterval>1</DaysInterval>
|
||||
</ScheduleByDay>
|
||||
</CalendarTrigger>
|
||||
</Triggers>
|
||||
<Settings>
|
||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
||||
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
||||
</Settings>
|
||||
<Actions Context="Author">
|
||||
<Exec>
|
||||
<Command>powershell</Command>
|
||||
<Arguments>-ExecutionPolicy Bypass -File C:\sanguo_vnpy_v2\scripts\factor_research\run_factor_daily_wrapper.ps1</Arguments>
|
||||
</Exec>
|
||||
</Actions>
|
||||
<Principals>
|
||||
<Principal id="Author">
|
||||
<UserId>S-1-5-18</UserId>
|
||||
<RunLevel>HighestAvailable</RunLevel>
|
||||
</Principal>
|
||||
</Principals>
|
||||
</Task>
|
||||
'@
|
||||
$xmlPath = Join-Path $env:TEMP 'sanguo_factor_daily_task.xml'
|
||||
$taskXml | Out-File -FilePath $xmlPath -Encoding Unicode
|
||||
schtasks /create /tn sanguo-factor-daily /xml "$xmlPath" /f
|
||||
Remove-Item $xmlPath -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Output '=== VERIFY next run time (expect: today/tomorrow 21:30) ==='
|
||||
schtasks /query /tn sanguo-factor-daily /fo LIST /v | Select-String -Pattern '下次运行时间|Next Run Time' | ForEach-Object { Write-Output (" {0}" -f $_.Line.Trim()) }
|
||||
@@ -0,0 +1,60 @@
|
||||
# run_factor_daily_wrapper.ps1 -- daily re-eval batch, VPS side (host=vps)
|
||||
# (2026-10-10 pipeline spec sec 4.2 decision H revision: cadence monthly -> daily)
|
||||
#
|
||||
# Two stages only (the monthly 4-stage chain stays untouched and keeps
|
||||
# running first-Sunday 09:30 for official month-end snapshot reports):
|
||||
# 1) monthly_batch --daily as_of = T-1 directly (no month-end normalize),
|
||||
# label=daily_YYYY-MM-DD
|
||||
# 2) monthly_review daily compare artifact into daily/ subdir
|
||||
# (judgement layer reads month-end files in the
|
||||
# parent dir only -- snapshot semantics untouched)
|
||||
#
|
||||
# Schedule: daily 21:30 via register_factor_daily_task.ps1 (XML Daily trigger;
|
||||
# 21:00 xt_eod must have finished ingesting T-1 bars first).
|
||||
# rc semantics: stage1 non-zero -> stop (nothing to review).
|
||||
# stage2 exit 2 = valid signal (alert), not a failure.
|
||||
#
|
||||
# Log: reports\factor_monthly\daily\factor_daily_cron.log
|
||||
# NOTE: keep this file ASCII-only. PS 5.1 parses BOM-less files as GBK and
|
||||
# multi-byte comment text can swallow statement lines (xt_eod lesson).
|
||||
|
||||
$env:http_proxy = ''
|
||||
$env:https_proxy = ''
|
||||
$env:all_proxy = ''
|
||||
Set-Location C:\sanguo_vnpy_v2
|
||||
|
||||
$PY = 'C:\Python310\python.exe'
|
||||
$EVAL_DB = 'C:\sanguo_vnpy_v2\data\factor_eval.db'
|
||||
$REGISTRY = 'C:\sanguo_vnpy_v2\config\factor_registry.yaml'
|
||||
$VNDB = 'C:\sanguo_vnpy_v2\data\quant_trading.db'
|
||||
$FUND_DIR = 'C:\sanguo_vnpy_v2\data\static'
|
||||
$OUT_DIR = 'C:\sanguo_vnpy_v2\reports\factor_monthly\daily'
|
||||
$LOG = Join-Path $OUT_DIR 'factor_daily_cron.log'
|
||||
|
||||
# as_of = T-1 (yesterday): nightly batch evaluates up to last closed session
|
||||
$asof = (Get-Date).AddDays(-1).ToString('yyyy-MM-dd')
|
||||
|
||||
if (-not (Test-Path $OUT_DIR)) { New-Item -ItemType Directory -Path $OUT_DIR -Force | Out-Null }
|
||||
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp factor-daily start (as_of=$asof) ==="
|
||||
|
||||
& $PY -X utf8 -m sanguo_factor.monthly_batch --as-of $asof --daily --registry $REGISTRY --eval-db $EVAL_DB --vnpy-db $VNDB --fund-data-dir $FUND_DIR --report-dir $OUT_DIR --host vps *>> $LOG
|
||||
$rcBatch = $LASTEXITCODE
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp stage1 daily_batch exit=$rcBatch ==="
|
||||
if ($rcBatch -ne 0) {
|
||||
Add-Content -Path $LOG -Value '!!! daily batch failed, stop (no fresh run for review) ==='
|
||||
exit $rcBatch
|
||||
}
|
||||
|
||||
& $PY -X utf8 -m sanguo_factor.monthly_review --as-of $asof --host vps --registry $REGISTRY --eval-db $EVAL_DB --out-dir $OUT_DIR *>> $LOG
|
||||
$rcReview = $LASTEXITCODE
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp stage2 daily_review exit=$rcReview (2=alert signal, valid) ==="
|
||||
|
||||
$final = 0
|
||||
if ($rcReview -eq 1) { $final = 1 }
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp factor-daily done final_rc=$final (batch=$rcBatch review=$rcReview) ==="
|
||||
exit $final
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# run_factor_daily_standalone.sh — 因子日度重评批·NAS 侧(决议 H 修订 2026-10-10)
|
||||
# 「bash wrapper → 一次性容器」同月度链模式(--user 1024:100 --group-add 101,
|
||||
# 主容器重启/CI 重建永不波及)。
|
||||
#
|
||||
# 两段链(as_of=T-1,等 21:00 xt_eod 收完 T-1 bar,DSM 任务 21:30 触发):
|
||||
# ① monthly_batch --daily 全量重评批(注册表全量,12M 窗,as_of 直用不归一月末)
|
||||
# ② monthly_review 日度对拍件落 daily/ 子目录(host=nas,append-only)
|
||||
# ——判定/报告层月末快照语义不动:月度四段链照旧每月第一个周日跑,正式报告
|
||||
# 落根目录,API 端点只读根层,daily/ 子目录天然隔离。
|
||||
#
|
||||
# registry 对齐(2026-10-10 修正存量错位):批侧改读运行副本 /app/data/
|
||||
# factor_registry.yaml(与控制台同源——decompose 新生/判死即时生效);
|
||||
# 旧月度链用 config 种子,首班后新因子不在批视野=盲区不止时序还有注册表
|
||||
# 错位。防御:运行副本缺失时从 config 种子拷(API 从未跑过的机器)。
|
||||
#
|
||||
# 部署位: NAS /volume1/stock/sanguo_vnpy_v2/run_factor_daily.sh(宿主文件,
|
||||
# CI 不覆盖;改 repo 副本后 scp -O 手动同步,同月度纪律):
|
||||
# scp -O scripts/nas_sync/run_factor_daily_standalone.sh \
|
||||
# sanguo-nas:/volume1/stock/sanguo_vnpy_v2/run_factor_daily.sh
|
||||
#
|
||||
# DSM 任务(sanguo-factor-daily, 每日 21:30, 用户=admin):
|
||||
# bash /volume1/stock/sanguo_vnpy_v2/run_factor_daily.sh
|
||||
set -u
|
||||
DOCKER=/var/packages/Docker/target/usr/bin/docker
|
||||
BASE=/volume1/stock/sanguo_vnpy_v2
|
||||
LOG=$BASE/factor_daily_cron.log
|
||||
APP=/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2
|
||||
UID_ADMIN=1024
|
||||
GID_ADMIN=100
|
||||
GID_ADMINS=101
|
||||
EVAL_DB=$BASE/data/factor_eval.db
|
||||
OUT_DIR=$BASE/reports/factor_monthly/daily
|
||||
REGISTRY=/app/data/factor_registry.yaml # 运行副本(控制台同源);缺失时下方向下拷种
|
||||
|
||||
# T-1(昨日):宿主 python3 算,不依赖 GNU date -d
|
||||
AS_OF=$(python3 - <<'PY'
|
||||
from datetime import date, timedelta
|
||||
print((date.today() - timedelta(days=1)).isoformat())
|
||||
PY
|
||||
)
|
||||
|
||||
{
|
||||
echo "=== $(date '+%F %T') factor-daily start (as_of=$AS_OF) ==="
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# 运行副本防御:缺失从 config 种子拷(API 从未起过的机器)
|
||||
if [ ! -f "$APP/data/factor_registry.yaml" ]; then
|
||||
echo "--- 运行副本缺失,从 config 种子拷贝 ---"
|
||||
cp "$APP/config/factor_registry.yaml" "$APP/data/factor_registry.yaml" || exit 1
|
||||
fi
|
||||
|
||||
"$DOCKER" run --rm --name sanguo-factor-daily --user "${UID_ADMIN}:${GID_ADMIN}" \
|
||||
--group-add "${GID_ADMINS}" --no-healthcheck --entrypoint python \
|
||||
-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl \
|
||||
-v /volume1/stock:/volume1/stock \
|
||||
-v "$APP":/app:ro \
|
||||
-w /app \
|
||||
sanguo_vnpy_v2:lock-aligned \
|
||||
-m sanguo_factor.monthly_batch --as-of "$AS_OF" --daily \
|
||||
--registry "$REGISTRY" --eval-db "$EVAL_DB" --report-dir "$OUT_DIR" \
|
||||
--host nas
|
||||
rc_batch=$?
|
||||
echo "=== $(date '+%F %T') stage1 daily_batch exit=$rc_batch ==="
|
||||
if [ "$rc_batch" -ne 0 ]; then
|
||||
echo "!!! stage1 失败,停链(review 无新批可评) ==="
|
||||
exit "$rc_batch"
|
||||
fi
|
||||
|
||||
"$DOCKER" run --rm --name sanguo-factor-daily-rev --user "${UID_ADMIN}:${GID_ADMIN}" \
|
||||
--group-add "${GID_ADMINS}" --no-healthcheck --entrypoint python \
|
||||
-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl \
|
||||
-v /volume1/stock:/volume1/stock \
|
||||
-v "$APP":/app:ro \
|
||||
-w /app \
|
||||
sanguo_vnpy_v2:lock-aligned \
|
||||
-m sanguo_factor.monthly_review --as-of "$AS_OF" --host nas \
|
||||
--registry "$REGISTRY" --eval-db "$EVAL_DB" --out-dir "$OUT_DIR"
|
||||
rc_review=$?
|
||||
echo "=== $(date '+%F %T') stage2 daily_review exit=$rc_review (2=告警信号,有效) ==="
|
||||
|
||||
final=0
|
||||
[ "$rc_review" -eq 1 ] && final=1
|
||||
echo "=== $(date '+%F %T') factor-daily done final_rc=$final (batch=$rc_batch review=$rc_review) ==="
|
||||
exit $final
|
||||
} >> "$LOG" 2>&1
|
||||
@@ -47,7 +47,9 @@ GID_ADMIN=100 # users
|
||||
GID_ADMINS=101 # administrators(Synology ACL 授权组,金丝雀实测必带)
|
||||
EVAL_DB=$BASE/data/factor_eval.db
|
||||
OUT_DIR=$BASE/reports/factor_monthly
|
||||
REGISTRY=/app/config/factor_registry.yaml
|
||||
# 2026-10-10 registry 对齐控制台运行副本(data/,非 config 种子):decompose
|
||||
# 新生/判死即时进批视野;防御=运行副本缺失时从种子拷(API 从未跑过的机器)
|
||||
REGISTRY=/app/data/factor_registry.yaml
|
||||
MANIFEST=/app/config/data_manifest.yaml
|
||||
|
||||
# 上月末(自然月末):宿主 python3 算,不依赖 GNU date -d
|
||||
@@ -70,6 +72,12 @@ PY
|
||||
echo "=== $(date '+%F %T') factor-monthly start (as_of=$AS_OF) ==="
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# 运行副本防御:缺失从 config 种子拷
|
||||
if [ ! -f "$APP/data/factor_registry.yaml" ]; then
|
||||
echo "--- 运行副本缺失,从 config 种子拷贝 ---"
|
||||
cp "$APP/config/factor_registry.yaml" "$APP/data/factor_registry.yaml" || exit 1
|
||||
fi
|
||||
|
||||
"$DOCKER" run --rm --name sanguo-factor-batch --user "${UID_ADMIN}:${GID_ADMIN}" \
|
||||
--group-add "${GID_ADMINS}" --no-healthcheck --entrypoint python \
|
||||
-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl \
|
||||
|
||||
@@ -51,6 +51,40 @@ def test_plan_batch_excludes_dead_and_labels(registry):
|
||||
assert plan["label"] == "monthly_2026-09"
|
||||
|
||||
|
||||
def test_plan_batch_daily_uses_asof_direct(registry):
|
||||
"""日度化(决议 H 修订):daily=True 时 as_of 直用不归一月末,label 前缀 daily_.
|
||||
|
||||
夜班批 as-of=T-1——若仍归一,10-09 会吸到 10-31(未来日),窗 end 越过
|
||||
数据水位即盲区消解失效."""
|
||||
plan = plan_batch(registry, "2026-10-09", daily=True)
|
||||
assert plan["end"] == "2026-10-09"
|
||||
assert plan["label"] == "daily_2026-10-09"
|
||||
# 窗起点同款滚动(含 end 当月共 12M:2025-11 起算)
|
||||
assert plan["start"] == "2025-11-01"
|
||||
# 月度缺省行为不变(防回归)
|
||||
monthly = plan_batch(registry, "2026-10-09")
|
||||
assert monthly["end"] == "2026-10-31"
|
||||
assert monthly["label"] == "monthly_2026-10"
|
||||
|
||||
|
||||
def test_main_daily_flag_reaches_plan(monkeypatch, registry, tmp_path):
|
||||
"""CLI --daily 透传 plan_batch(main 只做 IO 编排)."""
|
||||
calls = {}
|
||||
|
||||
def fake_run(factor_names, start, end, eval_db, label, **kw):
|
||||
calls.update(end=end, label=label)
|
||||
return {"run_id": "x"}
|
||||
|
||||
monkeypatch.setattr(monthly_batch, "run_batch_eval", fake_run)
|
||||
p = str(tmp_path / "reg.yaml")
|
||||
save_registry(p, registry)
|
||||
rc = main(["--as-of", "2026-10-09", "--daily", "--registry", p,
|
||||
"--eval-db", "fake.db"])
|
||||
assert rc == 0
|
||||
assert calls["end"] == "2026-10-09"
|
||||
assert calls["label"] == "daily_2026-10-09"
|
||||
|
||||
|
||||
def test_main_invokes_run_batch_eval_with_plan(monkeypatch, registry, tmp_path):
|
||||
calls = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user