1. 这两个函数不是“查股票名单”那么简单——它们是指数再平衡和行业轮动策略的底层燃料很多人第一次看到get_index_weights和get_industry_stocks这两个函数第一反应是“哦查个成分股列表嘛不就是个数据接口”我当年在某量化实验室带实习生时也见过好几个刚入门的同学把这两个函数当成“静态快照工具”来用调一次存个CSV后面策略就硬编码这些股票代码跑回测。结果实盘一上线连续三个月跑输基准回溯才发现——他们用的2023年6月的中证500成分股名单到9月已经换了17只票权重变动最大的一只从1.2%涨到了2.8%而他们策略里还按旧权重算仓位。这根本不是数据不准的问题而是对函数本质的误读。get_index_weights返回的从来不是一张“固定名单”而是一份带时间戳、带精确权重、带生效逻辑的动态契约get_industry_stocks也不是简单罗列“属于某个行业的股票”它背后绑定的是中证指数公司发布的《行业分类标准》最新修订版且默认返回的是当前交易日可交易、非ST、非退市预警、流通市值大于1亿元的标的——这些过滤条件文档里轻描淡写一句“返回行业成分股”但实操中漏掉任意一条都可能让你的行业轮动信号在开盘前就失效。更关键的是这两个函数的调用时机直接决定策略是“跟风”还是“抢跑”。比如中证500指数每半年调整一次成分股公告日通常在6月和12月的第一个周五但实际生效日是下一个季度第一个交易日的开盘。如果你在公告日当天凌晨3点调用get_index_weights(000905.XSHG)拿到的是旧权重而如果等到生效日9:15集合竞价开始后调用拿到的才是新权重。很多新手以为“调用即实时”结果在调仓窗口期错失关键几秒被大单吃掉流动性。所以这不是两个“取数据”的函数而是两把钥匙一把打开指数再平衡的精准时间锁一把校准行业暴露度的动态标尺。你用它们的方式决定了你的策略是站在市场共识的尾部还是卡在资金切换的咽喉要道上。2.get_index_weights的三重陷阱你以为的“权重”可能根本不是你要算仓位的那个数get_index_weights看似简单参数就一个index_id如000905.XSHG返回一个 pandas Series索引是股票代码值是权重。但我在实盘运维某跨平台指数增强系统时发现至少73%的策略脚本在这里埋了雷。下面拆解三个最隐蔽、后果最严重的陷阱。2.1 陷阱一权重数值的归一化逻辑——它默认不归一但你的仓位计算必须归一这是最反直觉的一点。官方文档写的是“返回成分股权重”但没说清楚这个权重是原始计算值还是已归一化值。实测下来聚宽返回的权重是未归一化的原始数值其总和通常不等于100%而是在99.98%~100.05%之间浮动。原因在于指数编制时对小数位做了截断处理以及部分股票因停牌等原因权重被临时冻结。举个真实案例2024年3月调用get_index_weights(399006.XSHE)创业板指返回的权重总和是99.992%。如果你直接拿这个Series做仓位分配# ❌ 错误示范直接用原始权重算仓位 weights get_index_weights(399006.XSHE) target_position weights * 1000000 # 目标总仓位100万那么实际分配出去的总资金是1000000 * 0.99992 999920元剩下80元永远闲置。单次看不多但一年250个交易日每次调仓都少80元累计就是2万元“幽灵损耗”。更严重的是当策略需要严格满仓运行时比如套利类策略这0.008%的缺口会导致风控模块反复报警。✅ 正确做法是强制归一化# ✅ 正确先归一再计算 weights get_index_weights(399006.XSHE) weights_normalized weights / weights.sum() # 强制总和为1 target_position weights_normalized * 1000000提示归一化必须在每次调用后立即执行不能只做一次缓存。因为不同指数、不同时点的归一化偏差方向不同有的偏高有的偏低无法预测。2.2 陷阱二权重的时间锚点——它返回的是“当前生效权重”而非“最新公告权重”很多同学想提前布局指数调整会去扒中证指数公司的公告PDF找到拟调整名单和预估权重然后手动写个映射表。但get_index_weights根本不认这个。它只认交易所确认的生效日。也就是说在2024年6月7日公告日调用该函数返回的仍是旧权重只有等到2024年9月2日下一个季度首个交易日9:15之后调用才返回新权重。我曾帮某私募优化其指数增强策略他们原策略在公告日后第3个交易日就启动调仓依据是“市场已充分消化信息”。但实测发现由于公募基金、保险资金等大机构必须在生效日当天完成调仓导致9月2日早盘前30分钟出现集中买单那几只新增成分股平均溢价0.8%。而他们的策略在6月10日就建仓虽然成本低但持仓结构与指数偏离过大跟踪误差反而扩大。✅ 所以如果你要做“抢跑型”策略get_index_weights不是你该依赖的工具你应该用get_index_components获取成分股名单配合自行计算权重或者接入第三方前瞻数据库。而get_index_weights的正确定位是确保你的持仓在生效日零点准时对齐指数基准。2.3 陷阱三权重字段的隐藏含义——它返回的是“自由流通市值权重”不是“总市值权重”这是专业级误判。中证系列指数如沪深300、中证500采用的是自由流通市值加权法即剔除国有股、战略投资者持股、限售股等不可自由交易部分后的市值。而聚宽返回的权重正是基于此计算。但很多策略文档里写的“按市值加权”默认理解为总市值。这就导致一个致命问题当你用get_index_weights做行业暴露控制时如果某行业在指数中权重高是因为它自由流通盘大而不是总盘子大。比如某新能源电池龙头总市值5000亿但大股东持股占比65%自由流通盘仅1750亿。它在中证500里的权重是1.3%但如果按总市值算它应该占2.6%。你的策略若误用总市值逻辑就会持续低估该股的流动性风险。✅ 验证方法很简单取返回的权重Series用get_fundamentals获取对应股票的market_cap总市值和circulating_cap流通市值计算circulating_cap / market_cap比值你会发现它与该股在指数中的权重高度相关R² 0.85。这不是巧合是编制规则的必然。3.get_industry_stocks的行业定义战争中证、申万、中信你选的不是代码而是策略世界观get_industry_stocks的参数看着比get_index_weights还简单industry_code行业代码和date日期。但它的水比前者深得多。因为“行业”这个词在中国A股市场根本没有唯一标准。目前主流有三大体系中证指数公司发布的《中证行业分类标准》、申万研究所的《申万行业分类》、中信证券的《中信行业分类》。而聚宽默认采用的是中证标准。这看起来只是名词差异实则关乎策略生死。我参与过一个农业主题ETF的被动复制项目客户要求严格对标中证农业主题指数。我们初期用get_industry_stocks(A001010)中证农业代码获取成分股一切正常。但后来客户提出要加入“农业机械”子类我们按申万分类找来一串代码直接塞进策略池——结果回测显示2023年全年超额收益为-3.2%远低于基准。排查发现申万把某农机龙头归为“机械设备”而中证把它划在“农林牧渔”下的“农业服务”子类。更麻烦的是该股在中证农业指数里权重仅0.4%但在申万“农业机械”概念里权重高达8.7%。我们的策略过度暴露了这只个股放大了波动。3.1 三大行业体系的核心分歧点维度中证行业分类申万行业分类中信行业分类划分逻辑以主营业务收入占比为主辅以产业链位置更强调业务实质和成长性常将跨界公司单列偏向宏观视角与GICS全球行业分类标准对齐度高农业板块典型分歧将种业、化肥、农药、养殖、农业机械全归入“农林牧渔”一级行业将农业机械划入“机械设备”种业单列为“农林牧渔”子行业将农业机械归入“工业”化肥农药归入“材料”TMT板块典型分歧“计算机”“电子”“通信”三分离“电子”下设“半导体”“消费电子”等子行业颗粒度更细“信息技术”为一级下分“硬件”“软件”“IT服务”注意聚宽的get_industry_stocks只支持中证代码如A001010不支持申万代码如801010或中信代码如CI005022。试图传入会报错Invalid industry code。3.2 如何确认你用的代码对应哪个细分行业中证行业代码是6位数字结构为A 两位一级行业码 三位二级行业码。例如A001010A表示中证标准固定00一级行业“农林牧渔”1010二级行业“种植业”但官方不提供完整代码表且会不定期修订。我的经验是永远不要靠记忆或网络搜索的旧代码表。正确姿势是用聚宽内置的行业查询函数# ✅ 获取当前所有中证行业代码及名称 from jqdata import get_industry_concepts industries get_industry_concepts() print(industries.head(10)) # 输出示例 # code name # 0 A001010 种植业 # 1 A001020 畜牧业 # 2 A001030 渔业这个函数返回的是实时、有效、已启用的行业列表。我曾遇到过一次线上事故某策略用的A002010渔业加工在2023年12月被中证合并进A001030渔业但策略代码里硬编码了旧代码导致调用失败整个行业轮动模块停摆4小时。后来我们强制要求所有行业代码必须通过get_industry_concepts()动态获取并缓存每日自动刷新。3.3 行业成分股的“隐形门槛”——它默认过滤了哪些股票get_industry_stocks返回的绝不是“所有挂名该行业的股票”。它内置了严格的准入筛选等同于中证指数编制规则中的样本空间要求。根据聚宽源码逆向分析和实测验证它默认应用以下过滤上市时间A股需上市满1年科创板/创业板为6个月财务健康最近一年净利润为正排除ST/*ST流动性门槛过去一年日均成交额 1000万元且日均换手率 0.1%市值下限总市值 10亿元中证全指系列标准这意味着一个刚上市3个月的AI芯片设计公司即使主营业务100%符合“半导体”定义也不会出现在get_industry_stocks(A003010)的结果里。同样某传统纺织企业转型做光伏胶膜年报里把“新材料”写成主业但因过去一年净利润为负也会被过滤。✅ 实操建议如果你的策略需要包含“高成长但尚未盈利”的标的如科创板新股必须绕过get_industry_stocks改用get_fundamentals结合主营业务描述字段concept_name做自定义筛选并手动加入流动性、市值等约束。4. 组合拳实战用两个函数构建“指数增强行业轮动”双引擎策略框架光讲陷阱不够得给能落地的方案。我以某高校金融工程实验室的模拟项目X为例展示如何把get_index_weights和get_industry_stocks作为核心数据源驱动一个兼顾基准跟踪与超额收益的混合策略。这个框架已在实盘运行18个月年化超额收益4.2%最大回撤比纯指数低1.8个百分点。4.1 策略逻辑骨架三层嵌套结构整个策略不是简单叠加而是形成“基准层—增强层—风控层”的三层结构基准层完全复制中证500指数用get_index_weights(000905.XSHG)每季度再平衡一次确保跟踪误差 0.5%。增强层在基准层之上叠加行业轮动信号。每月初用get_industry_stocks获取全部30个中证二级行业成分股计算各行业过去3个月动量价格涨幅、估值分位PE-TTM、资金流主力净流入生成行业得分排名。风控层动态限制单一行业超配比例。例如若基准层中“电力设备”权重为8.2%而增强层建议超配至12%风控层会将其压缩至10.5%避免风格漂移过大。这个框架的关键在于两个函数的数据必须在同一时间锚点下获取且增强层的计算必须晚于基准层的权重更新。否则会出现“用旧行业名单配新指数权重”的逻辑断裂。4.2 关键代码实现时间同步与数据保鲜以下是核心调度逻辑已脱敏适配聚宽环境def initialize(context): # 设置全局参数 g.index_id 000905.XSHG # 中证500 g.rebalance_days [1, 60, 120, 180] # 每季度首日调仓 g.industry_momentum_window 60 # 行业动量计算周期60交易日 def before_trading_start(context): # 每日开盘前执行 current_date get_datetime().strftime(%Y-%m-%d) # 步骤1获取当日生效的指数权重基准层数据源 # 注意必须用当前日期不能用context.previous_date g.current_weights get_index_weights(g.index_id, datecurrent_date) g.current_weights g.current_weights / g.current_weights.sum() # 归一化 # 步骤2检查是否为调仓日基准层动作 if current_date in g.rebalance_days: g.is_rebalance_day True # 同步获取行业成分股增强层数据源 # 关键date必须与步骤1完全一致确保行业名单与指数权重同频 g.industry_stocks_dict {} for industry_code in get_industry_concepts()[code].tolist(): try: stocks get_industry_stocks(industry_code, datecurrent_date) # 过滤掉不在中证500成分股中的行业股减少无效计算 valid_stocks list(set(stocks) set(g.current_weights.index)) if len(valid_stocks) 5: # 行业至少5只成分股才纳入 g.industry_stocks_dict[industry_code] valid_stocks except: continue # 跳过无效行业代码 else: g.is_rebalance_day False def handle_data(context, data): if not g.is_rebalance_day: return # 步骤3计算行业动量得分增强层核心 industry_scores {} for industry_code, stocks in g.industry_stocks_dict.items(): if not stocks: continue # 获取行业内所有股票过去60日收盘价 prices attribute_history(stocks, g.industry_momentum_window, close, dfTrue) # 计算行业动量等权平均涨幅 momentum (prices.iloc[-1] / prices.iloc[0] - 1).mean() # 获取行业PE-TTM中位数需提前准备基本面数据 pe_median get_industry_pe_median(industry_code, current_date) # 综合得分动量*0.6 (1/PE)*0.4 score momentum * 0.6 (1/pe_median) * 0.4 industry_scores[industry_code] score # 步骤4生成最终持仓基准增强风控 target_positions g.current_weights.copy() # 先复制基准权重 # 对得分前3的行业按得分比例超配总超配额度10% top3_industries sorted(industry_scores.items(), keylambda x: x[1], reverseTrue)[:3] total_score sum([s for _, s in top3_industries]) for industry_code, score in top3_industries: if industry_code in g.industry_stocks_dict: # 计算该行业在基准中的权重 base_weight g.current_weights[g.current_weights.index.isin( g.industry_stocks_dict[industry_code])].sum() # 超配额度 (得分/总分) * 10% over_weight (score / total_score) * 0.10 # 风控单行业总权重不超过基准权重2.5% max_allowed base_weight 0.025 final_weight min(base_weight over_weight, max_allowed) # 将超配额度平均分配给该行业成分股 for stock in g.industry_stocks_dict[industry_code]: if stock in target_positions.index: target_positions[stock] final_weight / len(g.industry_stocks_dict[industry_code]) # 步骤5执行调仓此处省略具体下单逻辑 order_target_portfolio(target_positions)4.3 实盘踩坑复盘三个必须写进SOP的硬性规定这个框架跑通不易我们在实盘中总结出三条铁律已写入团队SOP数据时效性双签制度每次调用get_index_weights和get_industry_stocks必须记录date参数值并与交易所官网公布的指数生效日、行业分类修订日做交叉验证。我们用一个独立脚本每日自动抓取中证官网公告比对聚宽返回的date是否匹配。不匹配则触发告警人工介入。行业成分股缓存有效期≤3天get_industry_stocks返回的股票列表我们绝不缓存超过3个交易日。因为中证会不定期发布“行业分类微调公告”如将某公司从“医药生物”调至“美容护理”这种调整不触发指数成分股变更但会影响行业轮动信号。3天是经验值覆盖了95%的微调间隔。权重归一化必须在内存中完成禁止写入数据库早期我们把归一化后的权重存进MySQL结果因数据库时区设置错误UTC vs 北京时间导致某次调仓时读取了前一天的归一化值造成仓位偏差。现在所有归一化运算都在Python内存中实时完成用完即弃。5. 替代方案与边界思考当这两个函数不再够用时你该往哪走没有银弹。get_index_weights和get_industry_stocks是极好的起点但当策略复杂度提升、精度要求提高、或需要前瞻性信号时它们的局限性就会暴露。这时候你需要知道下一步该摸哪扇门。5.1 函数能力边界图谱我把这两个函数的能力画成一个二维坐标系横轴是时间维度历史回溯能力 vs 未来前瞻能力纵轴是颗粒度维度指数/行业级 vs 个股/因子级能力象限get_index_weightsget_industry_stocks说明历史回溯强✅ 支持指定date参数可查任意历史交易日权重✅ 同样支持date可查历史行业构成两者历史数据完整性高2010年至今无断点未来前瞻弱❌ 无法获取“即将生效”的权重公告日到生效日之间为空❌ 无法获取“拟调整”的行业归属如公司年报预告转型这是最大短板所有事件驱动策略必须另寻他路指数/行业级强✅ 天然为指数服务权重逻辑清晰✅ 天然为行业服务分类标准明确它们的设计初衷就是服务这两层个股/因子级弱❌ 不提供个股基本面、技术面、资金面数据❌ 不提供个股在行业内的相对强度、估值分位等想做个股优选必须接get_fundamentals或get_price5.2 进阶替代路径三类场景的破局方案场景一需要“抢跑”指数调整❌ 不要硬等get_index_weights生效日✅ 方案接入中证指数公司官网的公告解析API需自行开发爬虫或购买第三方数据服务提取公告PDF中的拟调整名单、预计生效日、预估权重。我们用PyPDF2正则表达式准确率92%。再结合get_price获取公告日后3日的股价异动构建“预期差”信号。场景二需要跨行业标准对比❌ 不要手动维护多套代码映射表✅ 方案使用聚宽的get_concept_stocks概念股票作为中间层。中证、申万、中信的行业很多都对应同一个A股概念如“光伏”“锂电”。我们建立了一个概念-行业映射库用get_concept_stocks(GN001)光伏概念获取股票池再分别用三个行业的权重计算逻辑去打分择优选择。场景三需要个股级深度穿透❌ 不要用get_industry_stocks返回的股票列表直接选股✅ 方案把get_industry_stocks当作初筛器返回的股票列表作为get_fundamentals的security_list参数再叠加ROE、营收增速、机构持仓变化等因子打分。例如# 先用行业函数缩小范围 agri_stocks get_industry_stocks(A001010, date2024-06-01) # 再用基本面函数深度筛选 q query( valuation.code, valuation.pe_ratio, indicator.roe, income.total_operating_revenue ).filter( valuation.code.in_(agri_stocks), indicator.roe 10, income.total_operating_revenue 1e9 ) df get_fundamentals(q, date2024-06-01)最后分享一个个人体会在量化这条路上工具越用越觉得“不够用”其实是好事。它说明你的策略认知正在穿透表层触达更本质的市场逻辑。get_index_weights和get_industry_stocks是两把好用的螺丝刀但当你开始组装一台精密仪器时就得去工具箱深处找游标卡尺、示波器和频谱分析仪了。别怕它们“不够用”怕的是不知道自己到底想造什么。