Files
sanguo_vnpy_v2/scripts/data_platform/raw_redownload.py
T
claude_dev 1ed7b72aca feat(data): raw真实价数据源(task#79)—raw_dir+dir_key路由+新浪源重下
根因: daily_dir mixed-adjust(hfq bulk+akshare raw tail)致3-30 -94%假跌。
方案(Linus三问简化单raw, 除权留分期项#3):
- datareader read_parquet_daily/15min 加 dir_key 参数
- data_source iter_bars/fetch_day: adjust=raw→raw_dir(缺配置报错防混源), qfq→daily_dir
- engine PaperEngine 默认 adjust=raw
- config 加 raw_dir; scripts/raw_redownload.py 新浪源adjust='' 直连+单线程限速
- 验证: 浦发606行close 6.5/14.6 mean10.08 0跳变, 撮合成交价9.71-10.25真实
- 测试9/9+trader全量108/108通过
2026-07-07 22:19:11 +08:00

145 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""raw 日线重下(task #79):akshare 新浪源 adjust="" 真实价。
根因:daily_dir 历史数据是 mixed-adjusthfq bulk + akshare raw tail 拼接),
3-30 类单日 -94% 假跌。本脚本从头重下**单一 raw** 到 raw_dir,与 daily_dir 同构
{prefix}{symbol}_daily.parquet,按 year 分目录),datareader 直接读。
**约束(用户反馈,见 memory/feedback-data-download-constraints**
- 直连不走代理(unset proxy env + NO_PROXY=*
- 单线程 + SLEEP 间隔限速(不并发猛打,防数据源封 IP)
用法:
# 验证单只/几只
python3 raw_redownload.py --symbols 600000,000001 --start 2024-01-01
# 全市场(读 STOCK_LIST csv,后台跑)
python3 raw_redownload.py --all --start 2024-01-01
env:
RAW_DIR 本地 raw 根(默认 /tmp/stock_dl/A股数据/日线数据/raw
SLEEP 每只请求间隔秒(默认 1.0,限速防封)
STOCK_LIST stock_basic_info csv 路径(--all 读,默认拉到本地的 csv)
"""
import argparse
import csv
import logging
import os
import sys
import time
# 直连:进程级 unset 代理(用户约束)
for _k in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy"]:
os.environ.pop(_k, None)
os.environ["NO_PROXY"] = "*"
os.environ["no_proxy"] = "*"
import pandas as pd # noqa: E402
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("raw_redl")
RAW_DIR = os.environ.get("RAW_DIR", "/tmp/stock_dl/A股数据/日线数据/raw")
SLEEP = float(os.environ.get("SLEEP", "1.0"))
DEFAULT_STOCK_LIST = "/tmp/stock_dl/A股数据/stock_info/stock_basic_info_raw_20260326_113530.csv"
def prefix_for(code: str) -> str:
"""sh/sz 前缀(与 datareader.guess_exchange 一致)。"""
return "sh" if code.startswith(("60", "68", "51", "56", "58")) else "sz"
def download_one(ak, code: str, start: str, end: str):
"""新浪源拉 raw 日线,返回 (df, None) 或 (None, err)。"""
sym = f"{prefix_for(code)}{code}"
try:
df = ak.stock_zh_a_daily(
symbol=sym,
start_date=start.replace("-", ""),
end_date=end.replace("-", ""),
adjust="", # raw 不复权
)
except Exception as e: # noqa: BLE001
return None, f"{type(e).__name__}: {str(e)[:100]}"
if df is None or df.empty:
return None, "empty"
df["date"] = pd.to_datetime(df["date"])
df["year"] = df["date"].dt.year
return df, None
def save_one(code: str, df) -> int:
"""按 year 分组写 parquet(与 daily_dir 同构),返回写入行数。"""
n = 0
for year, g in df.groupby("year"):
ydir = os.path.join(RAW_DIR, str(int(year)))
os.makedirs(ydir, exist_ok=True)
out = os.path.join(ydir, f"{prefix_for(code)}{code}_daily.parquet")
g.drop(columns=["year"]).to_parquet(out)
n += len(g)
return n
def load_all_codes(stock_list: str) -> list[str]:
"""从 stock_basic_info csv 读代码列表(容错列名)。"""
codes = []
with open(stock_list, encoding="utf-8", errors="replace") as f:
reader = csv.DictReader(f)
for row in reader:
for k in ("code", "symbol", "ts_code", "代码", "股票代码"):
if k in row and row[k]:
c = str(row[k]).strip().split(".")[0]
if c.isdigit() and len(c) == 6:
codes.append(c)
break
return codes
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--symbols", help="逗号分隔代码,如 600000,000001")
ap.add_argument("--all", action="store_true", help="全市场(读 STOCK_LIST csv")
ap.add_argument("--start", default="2024-01-01")
ap.add_argument("--end", default=None, help="默认今天")
args = ap.parse_args()
end = args.end or time.strftime("%Y-%m-%d")
if args.symbols:
codes = [c.strip() for c in args.symbols.split(",") if c.strip()]
elif args.all:
sl = os.environ.get("STOCK_LIST", DEFAULT_STOCK_LIST)
if not os.path.exists(sl):
log.error("STOCK_LIST 不存在: %s(先跑 run_daily_update.sh 拉取)", sl)
sys.exit(1)
codes = load_all_codes(sl)
log.info("--all 从 %s 读到 %d", sl, len(codes))
else:
ap.error("需指定 --symbols 或 --all")
import akshare as ak
import warnings
warnings.filterwarnings("ignore")
ok = fail = rows = 0
for i, code in enumerate(codes, 1):
df, err = download_one(ak, code, args.start, end)
if df is None:
fail += 1
log.warning("[%d/%d] %s FAIL %s", i, len(codes), code, err)
else:
try:
n = save_one(code, df)
ok += 1
rows += n
log.info("[%d/%d] %s ok %d rows", i, len(codes), code, n)
except Exception as e: # noqa: BLE001
fail += 1
log.error("[%d/%d] %s SAVE FAIL %s", i, len(codes), code, e)
time.sleep(SLEEP) # 限速(用户约束)
log.info("=== 完成: ok=%d fail=%d rows=%draw_dir=%s ===", ok, fail, rows, RAW_DIR)
if __name__ == "__main__":
main()