feat(ak): gdhs一次性补史入口--gdhs-start——滚动3年窗外扩,源头边界2013Q1实测钉死 [vps]

This commit is contained in:
2026-09-08 13:45:29 +08:00
parent a37d324d01
commit 040a39e9c1
2 changed files with 40 additions and 4 deletions
@@ -54,6 +54,8 @@
python akshare_static_download.py --types dragon_tiger --start 20260101 --end 20260715
# 强制重下
python akshare_static_download.py --types valuation --force
# gdhs 一次性补史 (滚动 3 年窗之外, marker 幂等):
python akshare_static_download.py --types gdhs --gdhs-start 20130331
输出目录结构 (OUT_DIR/data/static):
{OUT_DIR}/
@@ -874,21 +876,31 @@ def download_one_unit(
def build_gdhs_units(
now: Optional[datetime.date] = None,
gdhs_start: Optional[str] = None,
) -> List[Tuple[str, Callable[[], pd.DataFrame]]]:
"""构造 gdhs units: 近 3 年季度末 × 披露守卫, 每期 1 unit。
只枚举距今 >GDHS_DISCLOSE_DAYS 的季度末 (披露完毕), 防止未披露期拉回
空 df 走「真空写空+done」语义后被 marker 锁死永不重拉。marker 去重:
已抓期 skip, 无新期时零网络调用 (ak-weekly 每周六空转)。
gdhs_start (YYYYMMDD, --gdhs-start): 一次性补史入口, 枚举扩到该起点
(2026-09-08 实测源头边界: 东财端点最早 2013Q1 有货, 2012Q4 及更早返空)。
已 marker 的期照旧 skip → 重跑幂等; 老期披露守卫天然满足。
"""
now = now or datetime.date.today()
cutoff = now - datetime.timedelta(days=GDHS_DISCLOSE_DAYS)
# floor=min(起点, 窗起点): 起点晚于滚动窗起点时退化为默认枚举, 绝不收窄周更覆盖
window_floor = datetime.date(now.year - 2, 1, 1)
floor = (min(datetime.datetime.strptime(gdhs_start, "%Y%m%d").date(), window_floor)
if gdhs_start else window_floor)
periods = []
for y in range(now.year - 2, now.year + 1):
for y in range(floor.year, now.year + 1):
for (m, d) in [(3, 31), (6, 30), (9, 30), (12, 31)]:
p = datetime.date(y, m, d)
if p <= cutoff:
periods.append(f"{y}{m:02d}{d:02d}")
if p > cutoff or p < floor:
continue
periods.append(f"{y}{m:02d}{d:02d}")
units = [(f"{p}_{GDHS}", partial(fetch_gdhs, p)) for p in periods]
logger.info(
"[%s] 季度末 %d 个 (披露守卫 >%d 天, %s..%s)",
@@ -1175,6 +1187,9 @@ def parse_args() -> argparse.Namespace:
help="限制处理股票数 (per-stock 类生效), 测试用",
)
p.add_argument("--force", action="store_true", help="强制重下, 忽略 marker")
p.add_argument("--gdhs-start", default="",
help="gdhs 一次性补史起点 YYYYMMDD (源头最早 20130331); "
"默认仅滚动 3 年窗")
p.add_argument(
"--repair", action="store_true",
help="只重取 missing/empty(size<1KB 或 df.empty)/corrupt 的 parquet, "
@@ -1245,7 +1260,7 @@ def run_type_dispatch(
return run_one_type(t, units, args)
if t == GDHS:
units = build_gdhs_units()
units = build_gdhs_units(gdhs_start=args.gdhs_start or None)
return run_one_type(t, units, args)
if t in PER_PERIOD_TYPES:
@@ -207,6 +207,27 @@ class TestHotAndGdhsFamily:
assert "20250930_gdhs" in ids
assert "20251231_gdhs" not in ids
def test_gdhs_units_backfill_flag_extends_history(self):
"""--gdhs-start 一次性补史: 枚举扩到指定起点; 默认(无 flag)行为零变化。
源头边界 (2026-09-08 实测): 东财端点最早 2013Q1 有货, 2012Q4 及更早
返回空 → akshare 内部 TypeError。补史从 20130331 起枚举无死期。
"""
import datetime as _dt
now = _dt.date(2026, 9, 8)
# 默认: 滚动 3 年窗 9 期, 与线上现状一致
ids = [u[0] for u in mod.build_gdhs_units(now=now)]
assert ids[0] == "20240331_gdhs" and len(ids) == 9
# 补史: 2013Q1 起, 新增 44 期 (2013Q1..2023Q4), 与滚动窗无缝衔接
ids2 = [u[0] for u in mod.build_gdhs_units(now=now, gdhs_start="20130331")]
assert ids2[0] == "20130331_gdhs"
assert len(ids2) == len(ids) + 44
assert len(set(ids2)) == len(ids2) and ids2 == sorted(ids2)
assert ids2[-9:] == ids
# 起点晚于滚动窗起点 → 退化为默认枚举
ids3 = [u[0] for u in mod.build_gdhs_units(now=now, gdhs_start="20250101")]
assert ids3 == ids
# ======================== ③ northbound 无数据崩溃安全包装 ========================