Jikipedia
第 05 篇

简单市值轮动策略

从「每天对固定几只股票做均线判断」升级到「每 10 天在全 A 股里重选一次市值最小的 5 只股票持有」——真正的选股 + 轮动。本页新引入三样东西:rundaily 定时循环、context.currentdt 取当前时间、用 getindexstocks + getfundamentals 从全市场选股。素材见 raw/collections/jq-getting-started/05-23-简单市值轮动策略md.md。

量化市值轮动run_daily选股小市值全A股

这是什么

「简单小市值轮动策略」的完整实现课。策略一句话:每隔 10 个交易日,等金额持有市值排名最小的前 5 只股票,卖出其他股票。它展示了如何把「每隔 N 天做一次」的周期控制、全市场选股、动态换仓这三件事用聚宽代码做出来。

核心要点

策略定义与初始化

  • 策略:每隔若干交易日,等金额持有市值最小的前几只股票,卖出其他股票;间隔定为 10 天。
  • 初始化参数:g.stocksnum = 5(持有最小市值股票数)、g.period = 10(轮动频率,多少天交易一次)、g.days = 1(记录策略进行到第几天)。
  • 股票范围 = 全市场,但全市场不断变化(新股上市、退市),所以股票范围要在每个周期内现算,不能在初始化里定死。

run_daily —— 周期循环的另一种写法

  • run_daily(daily, time='every_bar') 把自定义函数插入 initialize,让它按周期循环,效果等同 handle_data。套路:
    def initialize(context):
        ... 初始化 ...
        run_daily(daily, time='every_bar')   # 注册周期函数
    def daily(context):
        ... 每个周期要做的 ...
    
  • time 参数控制循环行为:'every_bar' 按当前周期循环——按天回测就每天开盘运行,按分钟回测就每分钟开始运行。time 还有其他取值(见 API「定时运行」节)。
  • 推荐用 run_daily,JQ 上的 handle_data 将被弃用

每 10 天交易一次的周期控制

  • 基本回测只提供每日循环,「每十天一次」要用程序实现:维护计数变量 g.daysif g.days % g.period == 1: 才执行选股买卖,否则 pass;每天末尾 g.days += 1

选股(找出全市场市值最小的 5 只)

  • 取当前时间:date = context.current_dt.strftime('%Y-%m-%d')
  • 确定范围:scu = get_index_stocks('000001.XSHG') + get_index_stocks('399106.XSHE')——上证指数 + 深证综指成分并起来 ≈ 全 A 股。
  • 查市值排序:get_fundamentals(query(valuation.code, valuation.market_cap).filter(valuation.code.in_(scu)).order_by(valuation.market_cap.asc()), date=date),按市值从小到大排。
  • 取前 5:buylist = list(df['code'][:g.stocksnum])(df 是 pandas DataFrame)。

换仓与买卖

  • 卖出:遍历当前持仓 context.portfolio.positions,凡不在 buylist 里的 → order_target(stock, 0) 清仓(原来持有、现在不是最小市值前 5,就该卖掉)。
  • 买入:position_per_stk = context.portfolio.cash / g.stocksnum(现金平分 5 份),对 buylist 里每只 order_value(stock, position_per_stk)

机制 / 论证

  • 为什么全市场要在周期内现取:市值轮动依赖「当下市值排名」,而上市/退市让市场构成不断变——任何写死的股票范围都会过期。这跟第 4 章「固定两只股票」形成对比:轮动策略的对象本身是动态的。
  • 计数取模实现低频动作g.days % g.period == 1 把「每天运行」降频成「每 10 天第 1 天运行」——在不支持任意间隔的按日循环里,这是标准手法;也顺便说明 run_daily 的 time 参数只控制触发,低频业务逻辑仍要自己写。
  • 卖出条件不是「跌了就卖」而是「不在名单就卖」:策略定义的是「持有市值最小前 5」,任何离开前 5 的持仓都违反定义,所以要调仓。这是轮动策略的核心动作模式:先对账(已持有 vs 该持有),差多少补多少

可操作

  • 可复用骨架(参数与逻辑):
    1. initialize:设 g.stocksnum=5g.period=10g.days=1run_daily(daily, time='every_bar')
    2. daily(context)if g.days % g.period == 1: 做选股换仓,否则 pass;末尾 g.days += 1
    3. 选股:date=context.current_dt.strftime('%Y-%m-%d')scu = get_index_stocks('000001.XSHG') + get_index_stocks('399106.XSHE')get_fundamentals(query(valuation.code).filter(valuation.code.in_(scu)).order_by(valuation.market_cap.asc()), date=date)buylist = list(df['code'][:g.stocksnum])
    4. 换仓:不在 buylist 的持仓 order_target(stock, 0)per = cash/g.stocksnum,每只 order_value(stock, per)
  • 参数:5 只最小市值股、10 天轮动、全 A 范围(上证指数 000001.XSHG + 深证综指 399106.XSHE)、等金额买入。
  • 教训:任何「动态选股」策略都要把「当前持仓 ∩ 目标名单」做差,名单外一律卖掉——否则旧持仓会赖着不走。

术语

  • 市值轮动:定期重选市值最小的一批股票持有。
  • run_daily / time='every_bar':自定义周期函数(handle_data 的推荐替代)。
  • g.days % g.period == 1:取模计数实现「每隔 N 天」。
  • get_index_stocks:取指数成分股;000001.XSHG = 上证指数、399106.XSHE = 深证综指。
  • get_fundamentals + valuation.market_cap:查市值表。
  • context.current_dt:策略当前时间。

不确定 / 待验证

  • raw 正文一处写 context.portfolio.position、另一处写 positions(后者是正写);g.stocksbnumg.stocksnum 的笔误。→ 落地按 positions/stocksnum 修正。
  • 「上证指数 + 深证综指 = 全 A 股市场所有股票」是原素材的说法(不含北交所等,2015 年语境);范围口径 [需要验证]。
  • get_fundamentals 按日取 valuation.market_cap 的排序结果会包含停牌/ST 股(本篇未过滤,过滤在 2.0 篇引入)。

相关

更新 2026-09-06

检索知识库

按标题、类型或正文检索