e3b688354f
merge_increment/verify_increment 增量staging→验证→合并工具; raw_redownload/run_daily_update/import_vnpy_daily 强化; 补 data_platform 与 index_downloader 测试.
147 lines
6.4 KiB
Bash
Executable File
147 lines
6.4 KiB
Bash
Executable File
#!/bin/bash
|
||
# 每日数据增量(C-S3 实走用):raw 真实价 + qfq 前复权,最近 N 天 → NAS。
|
||
#
|
||
# **安全流程(截断 bug 修复后)**:staging → 验证 → 合并主库 → rsync 主库 NAS。
|
||
# raw_redownload.py 的 save_one() 用"本次几天"覆盖写整年 → 历史被截。
|
||
# 现在下载只写 staging,verify_increment 把关,merge_increment 合并进主库
|
||
# (按 date 去重 keep='last',绝不截断),最后推主库到 NAS。
|
||
#
|
||
# 跳过 v1 daily_all_update(新浪源接口坏 KeyError:date,C-S3 不用 daily mixed)。
|
||
# raw/qfq 用 raw_redownload.py(akshare 新浪 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 —— 不合并、不推 NAS,staging 留存排查"
|
||
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) ==="
|