Files

118 lines
4.4 KiB
Python
Raw Permalink 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
# -*- coding: utf-8 -*-
"""backfill_hk_index.py — 恒生指数(hkHSI)历史日K回填(一次性脚本)
背景(阶段3 港股接入):stock_daily 的 hkHSI 仅 2024 起(643 条),温区回填
需要更长历史(对齐 A 股约 10 年)。本脚本从腾讯日K接口拉取 hkHSI 全量历史
(约 3000 根 ≈ 12 年),写入 stock_daily。
数据源(与 market_data.fetch_tx_klines 同一腾讯接口,hkHSI 已验证返回 day 键):
http://ifzq.gtimg.cn/appstock/app/fqkline/get?param=hkHSI,day,,,{count},qfq
用法:
python3 backfill_hk_index.py # 拉 3000 根(约12年)
python3 backfill_hk_index.py --count 4000
写入:INSERT OR IGNORE(code,date) 唯一键防重复,已有日期跳过)。
"""
import json
import sqlite3
import sys
import urllib.request
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
_MOFIN_ROOT = _SCRIPT_DIR.parent.parent
DB_PATH = Path(_MOFIN_ROOT) / "data" / "mofin.db"
UA = "Mozilla/5.0"
INDEX_CODE = "hkHSI"
PAGE_SIZE = 2000 # 腾讯接口单次上限(实测 count=2000 ok, 2500 报 param error
def _fetch_page(count, end_date=""):
"""拉一页:param=hkHSI,day,start,end,count,qfq。end_date 空=截至最新"""
url = (f"https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?"
f"param={INDEX_CODE},day,,{end_date},{count},qfq")
req = urllib.request.Request(url, headers={"User-Agent": UA})
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(req, timeout=30) as r:
text = r.read().decode("utf-8", errors="replace")
data = json.loads(text)
node = (data.get("data") or {}).get(INDEX_CODE, {})
return node.get("qfqday") or node.get("day") or []
def fetch_hsi(count=3000):
"""分页拉取 hkHSI 日K(单次上限 2000,超过则向前翻页)"""
from datetime import datetime as _dt, timedelta as _td
import time as _t
uniq = {}
end_date = ""
while len(uniq) < count:
want = min(PAGE_SIZE, count - len(uniq) + 5)
page = _fetch_page(want, end_date)
if not page:
break
new_cnt = 0
for b in page:
if b[0] not in uniq:
uniq[b[0]] = b
new_cnt += 1
if new_cnt == 0:
break # 没有新数据,历史到头
# 向前翻页:以本页最早日期的前一天为 end
earliest = page[0][0]
end_date = (_dt.strptime(earliest, "%Y-%m-%d") - _td(days=1)).strftime("%Y-%m-%d")
if len(page) < 100: # 不足一页说明历史拉完
break
_t.sleep(1) # 翻页间隔,遵守访问频率
return [uniq[d] for d in sorted(uniq)]
def main():
count = 3000
for a in sys.argv[1:]:
if a.startswith("--count"):
count = int(a.split("=")[-1] if "=" in a else sys.argv[sys.argv.index(a) + 1])
print(f"拉取 {INDEX_CODE} 最近 {count} 根日K...")
bars = fetch_hsi(count)
if not bars:
print("拉取失败/为空,终止")
return 1
print(f"拉到 {len(bars)} 根,时间范围 {bars[0][0]} ~ {bars[-1][0]}")
conn = sqlite3.connect(str(DB_PATH), timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
# stock_daily 列:code, date, open, close, high, low, volume(与 daily_kline_collector 一致)
inserted = 0
skipped = 0
for b in bars:
# 格式: [date, open, close, high, low, volume, ...]
try:
date, open_, close, high, low = b[0], b[1], b[2], b[3], b[4]
volume = float(b[5]) if len(b) > 5 and b[5] not in (None, "") else 0
except (IndexError, ValueError):
skipped += 1
continue
cur = conn.execute(
"INSERT OR IGNORE INTO stock_daily (code, date, open, close, high, low, volume) "
"VALUES (?,?,?,?,?,?,?)",
(INDEX_CODE, date, float(open_), float(close), float(high), float(low), volume)
)
inserted += cur.rowcount
conn.commit()
# 验证
total = conn.execute(
"SELECT COUNT(*), MIN(date), MAX(date) FROM stock_daily WHERE code=?", (INDEX_CODE,)
).fetchone()
conn.close()
print(f"写入 {inserted} 根(跳过 {skipped} 根格式异常 / 已有日期自动 IGNORE")
print(f"stock_daily 的 {INDEX_CODE}: 共 {total[0]} 条,{total[1]} ~ {total[2]}")
return 0
if __name__ == "__main__":
sys.exit(main())