第 06 篇
市值轮动策略 2.0
市值轮动 1.0 的工程化升级:把选股、交易、止损抽成自定义函数(模块化);提示要过滤停牌/涨停/ST 股;把资金分配从「平分当前现金买入」改成「把每只持仓价值调成总资产的均分」(均衡分配);加入每日止损。素材见 raw/collections/jq-getting-started/06-24-市值轮动策略20md.md。
这是什么
在简单市值轮动(1.0,20 多行代码)基础上让策略「更像工程」:代码模块化、过滤不可交易股票、资金真正均衡、加止损。原素材主题是自定义函数、共享函数库、均衡分配资金三项,外加止损。讲「策略越写越复杂时,怎么组织代码才不失控」。
核心要点
自定义函数(模块化)
- 动机:策略逐渐完善、代码变多,为可读性与复用性,按功能把代码抽成函数。
- 把 1.0 的 daily 拆成两个函数:
- 选股函数
pick(context):算当前日期 → 取scu = get_index_stocks('000001.XSHG') + get_index_stocks('399106.XSHE')(全 A)→get_fundamentals(...)排序取前g.stocksnum→return buylist。注意:raw 里 pick 示例把排序字段从市值换成了市盈率(valuation.pe_ratio > 0且按 pe_ratio 升序)——见「待验证」。 - 交易函数
trade(context, buylist):遍历持仓、不在 buylist 的清仓;现金平分买入;返回'Done!'。
- 选股函数
- 结果:daily 只剩骨架——
if g.days % g.period == 1: buylist = pick(context); trade(context, buylist) else: pass。
过滤停牌、涨停和 ST 股
- 有些股票无法交易(涨停封死、停牌),要过滤;ST 股风险大,也要过滤。
- JoinQuant 提供「共享数据库」,可直接用现成函数减少工作量(本篇没给具体函数名 → 见待验证;配套函数在股票数据篇:get_extras(is_st) 等)。
- ST 定义:Special Treatment(特别处理)。1998 年沪深交易所对财务状况或其它状况异常的上市公司股票做特别处理,股票简称前加 ST 警示投资风险。
均衡分配资金
- 提出问题:1.0 每次都「等金额买入」,为何 5 只股票的持仓总价仍会不同?
- 原因:1.0 每周期把「当前可用现金」平分去买每只股票。若某只股票一直在 buylist 里,每周期都会再买它,随时间推移它的持仓越来越多 → 资金分配不均衡。
- 解法:从「每次平分现金去买」改成「每次把该持有的股票市值调成总资产的均分」。例子:轮动持股 5 只、总资产 100000(含股票与现金),每次交易都调整为每只持有价值 20000。
- 代码变化:分母从
context.portfolio.cash(现金)换成context.portfolio.total_value(总资产 = 现金 + 股票),下单从order_value(买金额)换成order_target_value(把仓位调到目标金额):position_per_stk = context.portfolio.total_value / g.stocksnum for stock in buylist: order_target_value(stock, position_per_stk)
止损
- 止损 = 亏损到一定程度及时减仓,避免更大亏损。
- 简单做法:每天监测每个持仓的亏损情况,清仓亏损达到 20% 的股票:
for stock in context.portfolio.positions: if context.portfolio.positions[stock].price / context.portfolio.positions[stock].avg_cost < 0.8: order_target(stock, 0) - 止损要每天运行,不是每轮动周期运行:所以
stop(context)放在 daily 开头,先每日止损,再判断是否到轮动日。
机制 / 论证
- 为什么 1.0 会持仓失衡:order_value 用「当前现金」,而现金会随买卖波动;一只长期入选的股票每个轮动日都被加仓,别的股票却只在入选时才被买。本质是「加仓量」与「目标仓位」脱钩。改用
order_target_value(目标价值)后,系统每次把持仓拉回目标线,多头持仓自动止盈、清仓逻辑更干净。 - 均衡分配的分母要用 total_value 而非 cash:target 类下单直接设定「每只股票值多少钱」,基准是总资产(含已持有股票市值),这样才是「每只 2 万」而不是「每只再花现金 2 万」。
- 止损为什么单独每天跑:轮动周期 10 天一次,止损却要第一时间触发;把 stop() 放 daily 开头、与轮动判断并列,就实现「每天止损 + 每 10 天调仓」两种节奏。
可操作
- 模块化套路:把 daily 拆成
pick(context)(选股,返回名单)与trade(context, buylist)(下单);函数返回名单/状态,便于阅读与复用。 - 选股函数可换因子:把
order_by的字段换成valuation.pe_ratio并filter(valuation.pe_ratio > 0),同一套函数就从「小市值」变成「低市盈率」轮动——这是参数化选股的起点。 - 均衡分配模板:
per = context.portfolio.total_value / g.stocksnum;对名单内order_target_value(stock, per);名单外order_target(stock, 0)。 - 每日止损模板:
positions[stock].price / positions[stock].avg_cost < 0.8→order_target(stock, 0)(0.8 = 亏 20%,可调);放在 daily 开头。 - 过滤思想:股票池要剔除停牌、涨停(买不进)、ST(风险);用平台共享数据函数(见股票数据篇 get_extras / get_current_data)。
术语
- 自定义函数 / 模块化:把选股、交易、止损各抽成函数。
- 共享函数库:平台预置可直接调用的函数集合。
- ST(Special Treatment):财务状况或其它状况异常的上市公司被特别处理,简称加 ST。
- total_value vs cash:总资产(现金 + 持仓市值)vs 现金。
- order_target_value:把持仓价值调到目标金额。
- avg_cost:持仓平均成本。
不确定 / 待验证
- raw 的 pick() 示例注释说「按当前市值从小到大排序」但代码是
valuation.pe_ratio升序、且 filterpe_ratio > 0——正文与代码不一致(本篇主题仍是市值轮动)。→ 按代码记录:示例把排序字段换成了 PE;实际市值轮动 2.0 完整代码本篇未给出。 - 「JoinQuant 共享数据库可以直接使用这些函数」未给出过滤停牌/涨停/ST 的具体函数名 → [需要验证] 对应 API(股票数据篇的 get_extras('is_st')、get_current_data 的 paused/high_limit 等可能对应)。
- raw 多处
context.portfolio.position(单数)与positions混用 → positions 为正写。 - ST 过滤在 3.0 与后续量化课堂(多因子等)会反复出现,本篇只有思想没有代码 → 概念页 jq-stock-pool 里汇总。
相关
- 简单市值轮动策略 — 1.0 基础版
- 市值轮动策略 3.0 — 3.0 进阶版
- 止盈止损与持仓管理(聚宽写法) — 止损止盈写法
- 股票池(交易对象范围与过滤) — 股票池与过滤
- 聚宽新手入门教程(JoinQuant 平台实操) — 本教程总览
更新 2026-09-06