101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""hk_fundamentals_history_collector.py — 港股历史估值采集(补12维面板 pe_q/pb_q)
|
||
|
||
数据源:akshare stock_hk_valuation_baidu(百度股市通,PE(TTM)/市净率 近五年每日历史)
|
||
写入:stock_fundamentals_history(与A股同表,code=5位港股,date+code主键)
|
||
调度:一次性补采 + 每日增量(当日估值)
|
||
限速:0.4s/只,620只×2指标约10分钟。
|
||
"""
|
||
import sqlite3
|
||
import sys
|
||
import time
|
||
import warnings
|
||
|
||
# ── 消息通道统一路由(broadcast/xmpp by delivery) ──
|
||
try:
|
||
from messenger import install_stdio_hook as _msh
|
||
_msh()
|
||
except Exception:
|
||
pass
|
||
warnings.filterwarnings("ignore")
|
||
|
||
DB_PATH = "/home/hmo/MoFin/data/mofin.db"
|
||
SLEEP = 0.4
|
||
|
||
|
||
def fetch_hk_valuation(code, indicator, period="近五年"):
|
||
import akshare as ak
|
||
try:
|
||
df = ak.stock_hk_valuation_baidu(symbol=code, indicator=indicator, period=period)
|
||
if df is None or df.empty:
|
||
return []
|
||
out = []
|
||
for _, row in df.iterrows():
|
||
d = str(row.get("date"))[:10]
|
||
v = row.get("value")
|
||
if d and v is not None and str(v) != "nan":
|
||
try:
|
||
out.append((d, float(v)))
|
||
except (ValueError, TypeError):
|
||
pass
|
||
return out
|
||
except Exception:
|
||
return []
|
||
|
||
|
||
def main():
|
||
conn = sqlite3.connect(DB_PATH, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
conn.execute("""
|
||
CREATE TABLE IF NOT EXISTS stock_fundamentals_history (
|
||
code TEXT NOT NULL, date TEXT NOT NULL,
|
||
pe_ttm REAL, pe_static REAL, pb REAL, mcap_total REAL, mcap_flow REAL,
|
||
PRIMARY KEY (code, date)
|
||
)""")
|
||
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)} 只(百度接口 PE/PB 近五年)", flush=True)
|
||
|
||
ok = fail = 0
|
||
t0 = time.time()
|
||
for idx, code in enumerate(codes):
|
||
try:
|
||
pe_list = fetch_hk_valuation(code, "市盈率(TTM)")
|
||
pb_list = fetch_hk_valuation(code, "市净率")
|
||
pe_map = dict(pe_list)
|
||
pb_map = dict(pb_list)
|
||
dates = sorted(set(pe_map.keys()) | set(pb_map.keys()))
|
||
if not dates:
|
||
fail += 1
|
||
continue
|
||
n = 0
|
||
for d in dates:
|
||
pe = pe_map.get(d)
|
||
pb = pb_map.get(d)
|
||
if pe is None and pb is None:
|
||
continue
|
||
conn.execute(
|
||
"INSERT OR REPLACE INTO stock_fundamentals_history (code, date, pe_ttm, pb) "
|
||
"VALUES (?,?,?,?)", (code, d, pe, pb))
|
||
n += 1
|
||
ok += 1
|
||
if (idx + 1) % 50 == 0:
|
||
conn.commit()
|
||
print(f" [{idx+1}/{len(codes)}] ok={ok} fail={fail} | {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()
|
||
n = conn.execute("SELECT COUNT(*), COUNT(DISTINCT code) FROM stock_fundamentals_history WHERE length(code)=5").fetchone()
|
||
conn.close()
|
||
print(f"\n完成:ok={ok} fail={fail} 耗时{time.time()-t0:.0f}s", flush=True)
|
||
print(f"stock_fundamentals_history 港股: {n[0]} 条, {n[1]} 只", flush=True)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|