Files

98 lines
3.9 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_stocks.py — 港股通个股长历史日K回填(周末·阶段4前置)
背景:stock_daily 港股个股只有约2年(2024-07起,522条),回测样本不足。
本脚本对港股通名单(hk_connect_stocks 613只)每只拉 2000 根(腾讯接口单次上限,约8年),
回填入 stock_daily,供港股策略回测 + 实盘技术分析。
数据源:腾讯日K接口 hk 前缀(单次上限2000根,恒指回填已验证)。
http://ifzq.gtimg.cn/appstock/app/fqkline/get?param=hk{code},day,,,2000,qfq
跑法(heavy_run 受控,约10分钟):
flock + nice19 + ionice,见 heavy_run.sh 等效约束
幂等:INSERT OR IGNOREcode,date 唯一键),可重复执行。
"""
import json
import sqlite3
import sys
import time
import urllib.request
from pathlib import Path
DB_PATH = "/home/hmo/MoFin/data/mofin.db"
UA = "Mozilla/5.0"
COUNT = 2000 # 腾讯单次上限
SLEEP = 0.3 # 限速防封(铁律)
def fetch_klines(code):
"""拉单只港股 2000 根日K(腾讯 hk 前缀)"""
url = (f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?"
f"param=hk{code},day,,,{COUNT},qfq")
req = urllib.request.Request(url, headers={"User-Agent": UA})
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(req, timeout=20) as r:
text = r.read().decode("utf-8", errors="replace")
data = json.loads(text)
node = (data.get("data") or {}).get(f"hk{code}", {})
return node.get("qfqday") or node.get("day") or []
def main():
conn = sqlite3.connect(DB_PATH, timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
codes = [r[0] for r in conn.execute(
"SELECT code FROM hk_connect_stocks WHERE is_active=1 ORDER BY code").fetchall()]
print(f"港股通个股长历史回填:{len(codes)} 只,每只 {COUNT} 根", flush=True)
total_new = 0
ok = empty = fail = 0
t0 = time.time()
for idx, code in enumerate(codes):
try:
bars = fetch_klines(code)
if not bars:
empty += 1
continue
new = 0
for b in bars:
# 健壮解析:只取前6字段(date,open,close,high,low,volume),且都须为数字
# (腾讯港股部分 bar 末尾带 dict 附加字段,会导致 float() 报错)
try:
if not isinstance(b, (list, tuple)) or len(b) < 6:
continue
date = b[0]
open_, close, high, low = float(b[1]), float(b[2]), float(b[3]), float(b[4])
volume = float(b[5]) if b[5] not in (None, "") else 0.0
except (IndexError, ValueError, TypeError):
continue
cur = conn.execute(
"INSERT OR IGNORE INTO stock_daily (code, date, open, close, high, low, volume) "
"VALUES (?,?,?,?,?,?,?)",
(code, date, open_, close, high, low, volume))
new += cur.rowcount
total_new += new
ok += 1
if (idx + 1) % 50 == 0:
conn.commit()
print(f" [{idx+1}/{len(codes)}] ok={ok} empty={empty} fail={fail} 新增{total_new}根 | {time.time()-t0:.0f}s", flush=True)
except Exception as e:
fail += 1
if fail <= 5:
print(f" FAIL {code}: {str(e)[:60]}", flush=True)
time.sleep(SLEEP)
conn.commit()
# 验证
stat = conn.execute(
"SELECT COUNT(DISTINCT code), MIN(date), MAX(date) FROM stock_daily WHERE length(code)=5").fetchone()
conn.close()
print(f"\n完成:ok={ok} empty={empty} fail={fail} 新增{total_new}根 耗时{time.time()-t0:.0f}s", flush=True)
print(f"stock_daily 港股: {stat[0]} 只, {stat[1]} ~ {stat[2]}", flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())