diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-18 08:26:18 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-18 08:26:18 +0800 |
| commit | 95215e84e045602db2370586f6089a7f9f91b33b (patch) | |
| tree | 5119907dea5918d6eb26520e6dceed63b8030ad6 /worker/data.py | |
| parent | 5c0ba37eda80d39e6ceca59bb1d5f4942f858995 (diff) | |
| download | strategy-lab-95215e84e045602db2370586f6089a7f9f91b33b.tar.gz | |
fix(data): ETF 身份契约与挂载权限修复(159399 QA 回归)
[问题原因]
前端手动录入表单保存的 market 字段是 SH/SZ/BJ 原文,但 worker 的
split_identity 只接受 market:"cn" 并按代码前缀推交易所,导致
market:"SZ" 的 159399 在发起任何网络请求前就报 unsupported_market。
同时 server 为非 root 容器(uid 65534)准备挂载时,完整文件挂载从不
被 chmod,且所有 chmod 失败都被静默吞掉,worker 容器读数据集可能
Permission denied 且不易定位。
[问题根因]
身份契约在 server 持久化层与 worker 适配层不一致;挂载权限处理是
best-effort 静默吞错,缺少最小作用域约束。
[修复方案]
worker/data.py: market 原样映射 IDENTITY_EXCHANGES(SH/SZ/BJ),
"cn" 保持代码前缀推断(SH=3/6/9 开头,否则 SZ),未知市场仍显式拒绝
绝不猜测;sina 数据源的警告文案改为只陈述可证实事实(无日期参数、
不除权、成交量单位为股且不换算),删除与其它 provider 的 100x 换算
断言。server/src/worker.rs: prepare_mounts 严格化——独立文件挂载仅在
"只读 + /data/ 前缀"范围内 chmod 0644,拒绝符号链接,目录挂载保持
0755/0777,所有 chmod 失败作为错误返回而非吞掉。tests/worker/
test_data.py 新增身份契约用例:SZ/SH 原文、cn 推断不变、SZ 直达
sina provider 的端到端 fetch。
[影响范围]
worker 数据获取链路与 server 任务编排;server/target/release 二进制
已含本改动(2026-09-17 构建);pytest 数据模块全绿。
Diffstat (limited to 'worker/data.py')
| -rw-r--r-- | worker/data.py | 30 |
1 files changed, 19 insertions, 11 deletions
diff --git a/worker/data.py b/worker/data.py index 841a1eb..13f8808 100644 --- a/worker/data.py +++ b/worker/data.py @@ -66,13 +66,20 @@ def split_identity(instrument: dict) -> tuple[str, str]: raise DataError("bad_identity", f"unknown exchange prefix: {exch}") else: code = symbol - market = instrument.get("market", "cn") - if market != "cn": - raise DataError("unsupported_market", f"market not supported by this adapter: {market}") - if len(code) == 6 and code[0] in "369": - exch = "SH" + market = str(instrument.get("market", "cn")).strip() or "cn" + # The server persists the user-facing market field verbatim (SH/SZ/BJ + # from the manual-entry form); "cn" (search-sourced items, any case) + # keeps the original code-prefix inference. Unknown markets must still + # be rejected — never guessed. + if market.upper() in IDENTITY_EXCHANGES: + exch = market.upper() + elif market.lower() == "cn": + if len(code) == 6 and code[0] in "369": + exch = "SH" + else: + exch = "SZ" else: - exch = "SZ" + raise DataError("unsupported_market", f"market not supported by this adapter: {market}") if not code.isdigit() or len(code) != 6: raise DataError("bad_identity", "A-share code must be a 6-digit number") return exch, code @@ -157,11 +164,12 @@ def _fetch_sina(instrument: dict, start: str, end: str, # requested window (ISO strings compare lexicographically like dates) df = df[(df["date"] >= start) & (df["date"] <= end)].reset_index(drop=True) df.attrs["source_warnings"] = [ - "sina returns the full trading history without date parameters; sliced " - "locally to the requested range; data is unadjusted (no adjust parameter)", - "sina volume unit is 股 (shares); verified to be 100x the 手 (lots) " - "convention used by lot-based providers on the same session " - "(2026-09-17 evidence, docs/recovery-01-plan.md)", + "sina fund_etf_hist_sina has no date-range parameter: the provider " + "returned its available history and it was sliced locally to the " + "requested range (actual coverage differences are reported separately)", + "sina ETF klines are unadjusted: the interface has no adjust parameter", + "sina fund_etf_hist_sina 成交量单位为股;数值按 provider 原样保留,未做换算或取整。" + "与其他行情接口对比时,请先核对各 provider 自己公布的口径,不要按惯例直接换算", ] return FetchResult(df, endpoint, params, raw, "sina") |
