Files
sanguo_vnpy_v2/scripts/data_platform/run_daily_update.sh
T
claude_dev e3b688354f fix(data): data_platform硬化(增量merge/verify+raw_redownload/run_daily_update)+测试
merge_increment/verify_increment 增量staging→验证→合并工具; raw_redownload/run_daily_update/import_vnpy_daily 强化; 补 data_platform 与 index_downloader 测试.
2026-07-15 07:12:46 +08:00

147 lines
6.4 KiB
Bash
Executable File
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.
#!/bin/bash
# 每日数据增量(C-S3 实走用):raw 真实价 + qfq 前复权,最近 N 天 → NAS。
#
# **安全流程(截断 bug 修复后)**:staging → 验证 → 合并主库 → rsync 主库 NAS。
# raw_redownload.py 的 save_one() 用"本次几天"覆盖写整年 → 历史被截。
# 现在下载只写 stagingverify_increment 把关,merge_increment 合并进主库
# (按 date 去重 keep='last',绝不截断),最后推主库到 NAS。
#
# 跳过 v1 daily_all_update(新浪源接口坏 KeyError:dateC-S3 不用 daily mixed)。
# raw/qfq 用 raw_redownload.pyakshare 新浪 stock_zh_a_daily,全量 29600 已验证)。
# 15min baostock 增量 = 分期项(默认跳,日内策略落地时加)。
#
# **长任务防休眠**:本脚本被 nohup / harness 后台跑前,先 `caffeinate -i -s &`
# (Mac Mini 空闲睡眠会挂进程,详见 memory/feedback-unattended-tasks-prevent-sleep)。
#
# env:
# DAYS=7 增量回看天数(C-S3 实走需当日,7 天兜底停牌/补缺/周末)
# STOCK_LIST=... 全市场 csv(默认 data_cache/stock_info
# SKIP_PROBE=1 跳探针预检(调试用)
# SKIP_RAW=1 跳 raw 增量
# SKIP_QFQ=1 跳 qfq 增量
# SKIP_VERIFY=1 跳 verify(调试用,默认把关)
# SKIP_MERGE=1 跳 merge(调试用)
# SKIP_NAS=1 跳推 NAS(烟测用)
set -uo pipefail # 不用 -e:单只失败不退(raw_redownload 内部已容错记 fail
NAS=sanguo-nas
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
MAIN=$ROOT/data_cache # 主库(canonical 全量,绝不被下载直接写)
STAGING=$ROOT/data_cache/daily_update # staging(增量暂存,每次清空重下)
SL=${STOCK_LIST:-$ROOT/data_cache/stock_info/stock_basic_info_raw_20260326_113530.csv}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DAYS=${DAYS:-7}
START=$(python3 -c "import datetime;print((datetime.date.today()-datetime.timedelta(days=$DAYS)).isoformat())")
START_YEAR=${START:0:4}
# 持久日志:stdout+stderr 同时 tee 到文件(保留实时显示 + 持久化)
LOGDIR="$ROOT/data_cache/daily_update/logs"
mkdir -p "$LOGDIR"
LOGFILE="$LOGDIR/daily_$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOGFILE") 2>&1
mkdir -p "$STAGING/raw" "$STAGING/qfq" "$MAIN/raw" "$MAIN/qfq"
echo "=== 日志: $LOGFILE ==="
echo "=== $(date) 每日增量 start=$START DAYS=$DAYS ==="
echo " MAIN=$MAIN STAGING=$STAGING"
cd "$SCRIPT_DIR"
# 拉 stock_info(代码列表)若本地缺
if [ ! -f "$SL" ]; then
mkdir -p "$(dirname "$SL")"
rsync -az "$NAS:/volume1/stock/A股数据/stock_info/" "$(dirname "$SL")/" || true
fi
# P. 探针预检(新浪可用性,省时防傻跑 5h 才发现源挂了)
if [ "${SKIP_PROBE:-0}" != "1" ]; then
echo "=== P. 探针预检:sh600000 最近 7 天 ==="
PROBE_START=$(python3 -c "import datetime;print((datetime.date.today()-datetime.timedelta(days=7)).isoformat())")
PROBE_END=$(python3 -c "import datetime;print(datetime.date.today().isoformat())")
if ! PROBE_START="$PROBE_START" PROBE_END="$PROBE_END" timeout 30 python3 -c '
import os, warnings
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"] = "*"
warnings.filterwarnings("ignore")
import akshare as ak
df = ak.stock_zh_a_daily(
symbol="sh600000",
start_date=os.environ["PROBE_START"].replace("-",""),
end_date=os.environ["PROBE_END"].replace("-",""),
adjust="",
)
if df is None or df.empty:
print("PROBE: empty result")
raise SystemExit(1)
print("PROBE OK: %d rows" % len(df))
'; then
echo "!!! 探针失败:新浪不可用,abort 省时"
exit 2
fi
fi
# 0. 清空 staging(每次干净增量——staging 是"本次拉的几天",不能累积)
echo "=== 0. 清空 staging parquet ==="
find "$STAGING/raw" -name '*.parquet' -delete 2>/dev/null || true
find "$STAGING/qfq" -name '*.parquet' -delete 2>/dev/null || true
# 1. raw 增量(C-S3 撮合真实价,adjustflag=3 / akshare adjust=""
if [ "${SKIP_RAW:-0}" != "1" ]; then
echo "=== 1. raw 增量(最近 $DAYS 天 → $STAGING/raw==="
STOCK_LIST="$SL" RAW_DIR="$STAGING/raw" SLEEP=0.5 \
python3 raw_redownload.py --all --start "$START" --adjust ""
rc=$?
if [ "$rc" -eq 3 ]; then
echo "!!! 断路器触发(新浪限流/故障),abort 整个流程"
exit 3
elif [ "$rc" -ne 0 ]; then
echo " raw warn(单只失败已记, exit=$rc"
fi
fi
# 2. qfq 增量(C-S3 warmup 信号,无除权缺口)
if [ "${SKIP_QFQ:-0}" != "1" ]; then
echo "=== 2. qfq 增量(最近 $DAYS 天 → $STAGING/qfq==="
STOCK_LIST="$SL" RAW_DIR="$STAGING/qfq" SLEEP=0.5 \
python3 raw_redownload.py --all --start "$START" --adjust qfq
rc=$?
if [ "$rc" -eq 3 ]; then
echo "!!! 断路器触发(新浪限流/故障),abort 整个流程"
exit 3
elif [ "$rc" -ne 0 ]; then
echo " qfq warn(单只失败已记, exit=$rc"
fi
fi
# 3. verify staging(安全闸门:不通过则不合并、不推 NAS,staging 留存供排查)
if [ "${SKIP_VERIFY:-0}" != "1" ]; then
echo "=== 3. verify staging ==="
for KIND in raw qfq; do
[ -d "$STAGING/$KIND/$START_YEAR" ] || { echo " $KIND/$START_YEAR 不存在,跳 verify"; continue; }
if ! python3 verify_increment.py --staging "$STAGING/$KIND" --start "$START"; then
echo " !!! $KIND verify FAILED —— 不合并、不推 NASstaging 留存排查"
echo " !!! 详见上方 JSON 输出(failed_symbols / fatal_samples"
exit 1
fi
done
fi
# 4. 合并 staging → 主库(按 date 去重 keep='last',绝不截断)
if [ "${SKIP_MERGE:-0}" != "1" ]; then
echo "=== 4. merge staging → main ==="
for KIND in raw qfq; do
[ -d "$STAGING/$KIND/$START_YEAR" ] || { echo " $KIND/$START_YEAR 不存在,跳 merge"; continue; }
python3 merge_increment.py --staging "$STAGING/$KIND" --main "$MAIN/$KIND" \
|| { echo " !!! $KIND merge 失败(可能不变式违反),不推 NAS"; exit 1; }
done
fi
# 5. rsync 主库 → NAS(改:推 $MAIN 不是 $STAGING;原脚本推 staging 是 bug 之一)
if [ "${SKIP_NAS:-0}" != "1" ]; then
echo "=== 5. rsync 主库 → NAS ==="
rsync -az "$MAIN/raw/" "$NAS:/volume1/stock/A股数据/日线数据/raw/" || echo " raw push warn"
rsync -az "$MAIN/qfq/" "$NAS:/volume1/stock/A股数据/日线数据/qfq/" || echo " qfq push warn"
fi
echo "=== 完成 $(date) ==="