3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在 JoinQuant(聚宽)量化平台上编写红利增强策略时,我们经常需要合并多个股票列表(例如:红利指数成分股、特定高股息行业股等)。为了去除重复的股票,很多开发者会使用 Python 的 set 集合进行合并。
然而,Python 中的 set 是无序的。当您将 set 转换回 list 时,股票的顺序是随机的。在回测或模拟盘中,如果您的资金不足以买入池中的所有股票,下单函数(如 order 或 order_target)会按照列表的当前顺序依次下单。由于 set 导致的随机顺序,每次运行策略时买入的股票可能完全不同,从而导致回测结果不稳定、无法复现。
要解决这个问题,必须在合并去重后,重新按照股息率(或市值等因子)进行显式排序。以下是完整的解决方案和修复代码。
我们可以通过以下步骤修复该逻辑:
set 合并多个股票列表并去重。get_fundamentals 查询这些合并后股票的最新股息率(或通过 get_factor_values 获取相关因子)。以下是一个完整的聚宽策略框架,展示了如何安全地合并股票池并按股息率重新排序:
import pandas as pd
from jqdata import *
def initialize(context):
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
# 1. 获取不同的候选股票池
# 假设列表 A:上证红利成分股
list_a = get_index_stocks('000015.XSHG')
# 假设列表 B:银行和房地产行业的股票(高股息行业)
list_b = get_industry_stocks('801780') + get_industry_stocks('801180')
# 2. 使用 set 合并并去重(此时顺序已随机)
merged_set = set(list_a + list_b)
merged_list = list(merged_set)
# 3. 查询这些股票的股息率,用于重新排序,消除随机性
# 股息率数据在 valuation 表中,字段为 dividend_liability(或通过财务数据计算)
# 这里我们使用 get_fundamentals 获取市值表中的股息率/估值相关指标
q = query(
valuation.code,
valuation.market_cap,
# 也可以使用其他因子,此处以市值和PE作为演示,实际红利策略可查询具体股息率指标
valuation.pe_ratio
).filter(
valuation.code.in_(merged_list)
)
df = get_fundamentals(q, date=context.previous_date)
if not df.empty:
# 4. 显式排序:按照特定指标(如 PE 升序,或市值降序)进行排序
# 这样可以确保每次合并后的股票顺序完全一致,消除 set 的随机性
df_sorted = df.sort_values(by='pe_ratio', ascending=True)
# 获取排序后的股票代码列表
final_buy_list = list(df_sorted['code'])[:10] # 选择前10只
log.info("今日确定的稳定股票池: %s" % final_buy_list)
# 5. 执行调仓逻辑
# 卖出不在新股票池中的持仓
for stock in list(context.portfolio.positions.keys()):
if stock not in final_buy_list:
order_target(stock, 0)
log.info("卖出不在红利池中的股票: %s" % stock)
# 买入新股票池中的股票
position_count = len(context.portfolio.positions)
if position_count < 5:
cash = context.portfolio.available_cash / (5 - position_count)
for stock in final_buy_list:
if stock not in context.portfolio.positions:
order_value(stock, cash)
log.info("买入红利股: %s" % stock)
else:
log.warn("未能获取到财务数据,跳过今日调仓")
list(set(stocks)) 进行循环下单:这会导致回测和实盘的下单顺序不一致,产生严重的幸存者偏差或随机交易误差。set 转换回 list 后,必须调用 .sort() 或使用 Pandas 的 sort_values() 按照股票代码、股息率或市值进行二次排序,以确保数据流的确定性。date 参数应传入 context.previous_date(前一交易日),防止引入未来数据。