A股量化数据工程:从 REST 接口到策略信号(第 5 篇):板块成分股映射与对齐

A股量化数据工程:从 REST 接口到策略信号(第 5 篇):板块成分股映射与对齐

前言

第 4 篇拿到了板块树,叶子节点下挂的就是成分股。本篇把成分股批量落进 board_stock_map,并用股票实时行情把「板块」和「个股涨跌」对齐,得到一个最朴素的板块强弱指标:成分股涨跌幅中位数。这一步是把树变成可量化信号的关键。

一、接口原始字段梳理

1)板块成分股

接口地址:https://api.biyingapi.com/hszg/gg/{板块代码}/{LICENCE}

字段 说明
dm 股票代码(带市场后缀,如 600519.SH)
mc 股票名称
jys 交易所

2)股票实时行情(用于对齐涨跌)

接口地址:https://api.biyingapi.com/hsstock/real/time/{股票代码.市场}/{LICENCE}

字段 说明
p 最新价
o 今开
h 最高
l 最低
yc 昨收
cje 成交额
v 成交量
ud 涨跌额
pc 涨跌幅
zf 振幅
t 行情时间
pe 市盈率
tr 换手率
pb_ratio 市净率
tv 成交量(原始)

二、自研衍生统计指标

  1. 板块强弱(中位数) :板块内成分股 pc 的中位数,规避个别大权重股失真。
  2. 上涨占比 :pc > 0 的成分股比例,衡量板块内部广度。
  3. 龙头偏离:板块强弱减去其最大权重股涨跌幅,捕捉「权重拖累 / 题材发散」。

以上均基于原始 pc 字段二次计算,接口不返回板块聚合值。

三、数据表设计

sql 复制代码
CREATE TABLE IF NOT EXISTS board_stock_map (
    board_code TEXT,
    dm    TEXT,
    mc    TEXT,
    jys   TEXT,
    PRIMARY KEY (board_code, dm)
);

CREATE TABLE IF NOT EXISTS stock_realtime (
    dm   TEXT,
    t    TEXT,
    p REAL, o REAL, h REAL, l REAL, yc REAL,
    cje REAL, v REAL, ud REAL, pc REAL, zf REAL,
    pe REAL, tr REAL, pb_ratio REAL, tv REAL,
    PRIMARY KEY (dm, t)
);

CREATE TABLE IF NOT EXISTS board_strength (
    board_code TEXT,
    t    TEXT,
    median_pc REAL,
    up_ratio  REAL,
    PRIMARY KEY (board_code, t)
);

四、完整代码

python 复制代码
import requests
import sqlite3
import time
import statistics

LICENCE = "YOUR_LICENCE"
BASE = "https://api.biyingapi.com"
DB_PATH = "quant.db"

def biying_api_get_retry(url, timeout=15, max_retry=3):
    for i in range(max_retry):
        try:
            r = requests.get(url, timeout=timeout)
            r.raise_for_status()
            return r.json()
        except Exception as e:
            if i == max_retry - 1:
                raise
            time.sleep(1 + i)

def fetch_stocks_of_board(bcode):
    data = biying_api_get_retry(f"{BASE}/hszg/gg/{bcode}/{LICENCE}")
    return data.get("data", []) if isinstance(data, dict) else []

def fetch_stock_realtime(dm):
    if "." not in dm:
        return {}
    code, market = dm.split(".", 1)
    url = f"{BASE}/hsstock/real/time/{code}.{market}/{LICENCE}"
    data = biying_api_get_retry(url)
    return data.get("data", {}) if isinstance(data, dict) else {}

def save_stock_map(bcode, rows):
    con = sqlite3.connect(DB_PATH)
    con.executemany(
        "INSERT OR REPLACE INTO board_stock_map(board_code,dm,mc,jys) VALUES(?,?,?,?)",
        [[bcode, r.get("dm"), r.get("mc"), r.get("jys")] for r in rows])
    con.commit(); con.close()

def board_strength(bcode, t):
    con = sqlite3.connect(DB_PATH)
    rows = con.execute(
        "SELECT dm FROM board_stock_map WHERE board_code=?", (bcode,)).fetchall()
    con.close()
    pcs = []
    for (dm,) in rows:
        rt = fetch_stock_realtime(dm)
        if rt.get("pc") is not None:
            pcs.append(rt["pc"])
            # 顺便落实时
            con = sqlite3.connect(DB_PATH)
            con.execute(
                "INSERT OR REPLACE INTO stock_realtime"
                "(dm,t,p,o,h,l,yc,cje,v,ud,pc,zf,pe,tr,pb_ratio,tv)"
                "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
                [dm, rt.get("t"), rt.get("p"), rt.get("o"), rt.get("h"),
                 rt.get("l"), rt.get("yc"), rt.get("cje"), rt.get("v"),
                 rt.get("ud"), rt.get("pc"), rt.get("zf"), rt.get("pe"),
                 rt.get("tr"), rt.get("pb_ratio"), rt.get("tv")])
            con.commit(); con.close()
    if not pcs:
        return None, None
    median_pc = round(statistics.median(pcs), 3)
    up_ratio = round(sum(1 for x in pcs if x > 0) / len(pcs), 3)
    con = sqlite3.connect(DB_PATH)
    con.execute(
        "INSERT OR REPLACE INTO board_strength(board_code,t,median_pc,up_ratio) VALUES(?,?,?,?)",
        (bcode, t, median_pc, up_ratio))
    con.commit(); con.close()
    return median_pc, up_ratio

if __name__ == "__main__":
    bcode = "BK0735"   # 示例:某个概念板块代码,实际以 hszg/list 返回为准
    stocks = fetch_stocks_of_board(bcode)
    save_stock_map(bcode, stocks)
    print(f"板块 {bcode} 成分股 {len(stocks)} 只,示例:", stocks[:3])
    mp, ur = board_strength(bcode, "2026-09-20")
    print(f"板块强弱(成分股涨跌幅中位数)={mp}  上涨占比={ur}")

五、关键业务说明

  • 成分股是股票不是指数 :对齐涨跌要用 hsstock/real/time 的 pc,而非指数接口。
  • 中位数优于均值:成分股里常有 1 只权重股拉偏均值,中位数更能代表板块真实温度。
  • 批量拉取限速:板块成分股可能几十到几百只,注意免费版每日调用上限,建议缓存实时结果、定时刷新。

六、拓展练习

  1. 对 hszg/list 里所有概念叶子节点批量算 board_strength,做一次「全概念强弱榜」。
  2. 把「上涨占比」与「中位数涨跌幅」做成散点,圈出「高广度 + 强涨幅」的活跃板块。
  3. 用 board_strength 历史序列(定时落库)计算板块动量,找持续性强的主线。

下篇预告

A股量化数据工程:从 REST 接口到策略信号(第 6 篇):板块轮动资金监控

下一篇用 hizj / hibk / higg 拉取行业、概念、个股的资金流向与净流入排名,把「价格强弱」升级成「资金强弱」,捕捉主力进出的板块轮动。
免责申明 :本篇全部代码、接口调用仅作为量化编程学习演示,仅为数据演示,不构成投资建议。市场有风险,投资需谨慎。

相关推荐
vx_Biye_Design1 小时前
django就业信息推荐系统61953-计算机课程设计、毕业设计
java·vue.js·spring boot·python·架构·django·课程设计
零基础1231 小时前
VoiceStudio 开源项目深度解析:特性、对比与实战测试
人工智能·经验分享·python·开源
2601_962885721 小时前
如何用 Python 做均线粘合选股?(多头发散前的变盘候选)
开发语言·python
zhangzeyuaaa1 小时前
Ruby 方法参数完全指南:默认值、可变参数与关键字参数
前端·python·ruby
知守观1 小时前
HTML 转 PDF 方案实战:iTextPDF 与 wkhtmltopdf 踩坑复盘,附 Flying Saucer / openhtmltopdf 选型对比
java·后端
2601_962071571 小时前
类变量和全局变量的隔离性有什么区别?
java·开发语言·jvm
小小张说故事1 小时前
pytest 写了用例却不跑、fixture 不生效、参数化全失效?9 个高频坑对照表
python
海绵宝宝转agent1 小时前
LeetCode100 LRU缓存思路讲解
java·开发语言·缓存
专业程序开发源1 小时前
springboot高校学生社团管理系统74810-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计