from __future__ import annotations import argparse import csv import json import time from collections import Counter from datetime import datetime from pathlib import Path import pandas as pd from retry_public_5m_proxy_baostock_per_request import ( PACKAGE_ROOT, cache_path, date_key, fetch_one_with_optional_timeout, to_baostock_code, ) LOCAL_DAILY_ROOT = Path("E:/quant/a_share_daily_front_20230101_20260508_complete/daily") SOURCE_COVERAGE = PACKAGE_ROOT / "open_volume_stall_scan_coverage.csv" FETCH_LEDGER = PACKAGE_ROOT / "open_volume_stall_proxy_fetch_inventory.csv" SUMMARY_JSON = PACKAGE_ROOT / "open_volume_stall_proxy_fetch_summary.json" def daily_dates(symbol: str) -> list[str]: path = LOCAL_DAILY_ROOT / f"{symbol}.csv" if not path.exists(): return [] df = pd.read_csv(path, dtype=str, keep_default_na=False, encoding="utf-8-sig") return sorted(df["trade_date"].map(date_key).tolist()) def previous_dates(dates: list[str], d: str, n: int) -> list[str]: prev = [item for item in dates if item < d] return prev[-n:] def iso(d: str) -> str: return f"{d[:4]}-{d[4:6]}-{d[6:8]}" def load_targets() -> list[dict[str, str]]: if not SOURCE_COVERAGE.exists(): return [] df = pd.read_csv(SOURCE_COVERAGE, dtype=str, keep_default_na=False, encoding="utf-8-sig") need = df[df["scan_status"].isin(["NO_5M_PROXY_CACHE", "NO_PROXY_PREVIOUS_5M_VOLUME"])].copy() unique: set[tuple[str, str]] = set() for row in need.to_dict("records"): symbol = row["symbol"] d = date_key(row["scan_trade_date"]) dates = daily_dates(symbol) for item in [*previous_dates(dates, d, 5), d]: unique.add((symbol, item)) rows = [] for symbol, d in sorted(unique): path = cache_path(symbol, iso(d)) status = "FETCH_OK" if path.exists() and path.stat().st_size > 0 else "PENDING" count = "0" cache = "" if status == "FETCH_OK": try: count = str(len(pd.read_csv(path, dtype=str, keep_default_na=False, encoding="utf-8-sig"))) except Exception: count = "0" cache = str(path.relative_to(PACKAGE_ROOT)).replace("\\", "/") rows.append( { "symbol": symbol, "trade_date": iso(d), "baostock_code": to_baostock_code(symbol) or "", "fetch_status": status, "row_count": count, "cache_path": cache, "error": "", } ) return rows def write_rows(rows: list[dict[str, str]]) -> None: keys = ["symbol", "trade_date", "baostock_code", "fetch_status", "row_count", "cache_path", "error"] with FETCH_LEDGER.open("w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=keys, extrasaction="ignore") writer.writeheader() writer.writerows(rows) def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--limit", type=int, default=100) parser.add_argument("--timeout", type=int, default=180) parser.add_argument("--sleep", type=float, default=0.5) args = parser.parse_args() rows = load_targets() attempted = 0 for row in rows: if attempted >= args.limit: break if row["fetch_status"] == "FETCH_OK": continue status, cache, count, error = fetch_one_with_optional_timeout(row["symbol"], row["trade_date"], args.timeout) row["fetch_status"] = status row["cache_path"] = cache row["row_count"] = count row["error"] = error attempted += 1 write_rows(rows) if args.sleep: time.sleep(args.sleep) write_rows(rows) summary = { "generated_at": datetime.now().isoformat(timespec="seconds"), "requested_rows": len(rows), "attempted": attempted, "fetch_status_counts": dict(Counter(row["fetch_status"] for row in rows)), } SUMMARY_JSON.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(summary, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()