问题描述
解决方案
在JoinQuant回测中查看持仓的行业分布,主要思路是遍历当前账户的持仓列表,利用 get_industry API 获取每一只股票所属的行业(如申万一级行业),然后累加计算各行业的持仓市值及占比,最后通过 log.info 打印出来。
以下是一个完整的策略示例代码。该策略会随机买入几只股票构建持仓,并在每天收盘后计算并打印当前的行业分布情况。
核心步骤说明
- 获取持仓:通过
context.portfolio.positions获取当前所有持仓对象。 - 查询行业:使用
get_industry(security_list)批量查询持仓股票的行业信息(本例使用申万一级行业sw_l1)。 - 统计计算:遍历持仓,累加相同行业的持仓市值。
- 输出日志:计算各行业占总资产的比例,并打印到日志中。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定要操作的股票池(示例:包含银行、地产、白酒、医药等不同行业)
g.security_pool = [
'000001.XSHE', # 平安银行(银行)
'000002.XSHE', # 万科A(房地产)
'600519.XSHG', # 贵州茅台(食品饮料)
'600276.XSHG', # 恒瑞医药(医药生物)
'601318.XSHG' # 中国平安(非银金融)
]
# 每天开盘时运行买入逻辑(为了演示持仓)
run_daily(buy_stocks, '09:30')
# 每天收盘后运行行业分布统计
run_daily(check_industry_distribution, '15:10')
def buy_stocks(context):
"""
简单的买入逻辑,用于构建持仓以便演示行业统计
"""
# 如果没有持仓,则平均买入股票池中的股票
if len(context.portfolio.positions) == 0:
cash_per_stock = context.portfolio.available_cash / len(g.security_pool)
for stock in g.security_pool:
order_value(stock, cash_per_stock)
log.info("买入示例股票: %s" % stock)
def check_industry_distribution(context):
"""
核心函数:统计并打印持仓行业分布
"""
# 1. 获取当前所有持仓的股票代码
# context.portfolio.positions 是一个字典,key是标的代码
positions = context.portfolio.positions
holding_list = [stock for stock in positions if positions[stock].total_amount > 0]
if not holding_list:
log.info("当前无持仓,无法统计行业分布。")
return
# 2. 获取持仓股票的行业信息
# date=context.previous_date 确保获取的是回测当时的数据
# 这里我们使用 'sw_l1' (申万一级行业) 作为分类标准,也可以换成 'jq_l1' (聚宽一级) 或 'zjw' (证监会行业)
industry_info = get_industry(holding_list, date=context.previous_date)
# 3. 初始化统计字典
industry_stats = {}
total_holding_value = 0.0 # 统计持仓总市值
# 4. 遍历持仓,累加各行业的市值
for stock in holding_list:
position = positions[stock]
market_value = position.value # 该股持仓市值
total_holding_value += market_value
# 获取该股票的行业名称
stock_ind_info = industry_info.get(stock)
industry_name = '未知行业'
if stock_ind_info and 'sw_l1' in stock_ind_info:
industry_name = stock_ind_info['sw_l1']['industry_name']
# 累加市值
if industry_name in industry_stats:
industry_stats[industry_name] += market_value
else:
industry_stats[industry_name] = market_value
# 5. 计算占比并打印日志
total_assets = context.portfolio.total_value # 总资产(包含现金)
log.info("=" * 30)
log.info("【日期】: %s" % context.current_dt.date())
log.info("【总资产】: %.2f" % total_assets)
log.info("【持仓市值】: %.2f (仓位: %.2f%%)" % (total_holding_value, (total_holding_value/total_assets)*100))
log.info("--- 行业分布详情 (按持仓市值占比) ---")
# 按市值从大到小排序
sorted_stats = sorted(industry_stats.items(), key=lambda x: x[1], reverse=True)
for ind_name, value in sorted_stats:
# 计算该行业占总资产的比例
ratio = (value / total_assets) * 100
log.info("行业: %s | 市值: %.2f | 占比: %.2f%%" % (ind_name, value, ratio))
# 如果你想在回测图表下方看到曲线变化,可以使用 record 函数
# 注意:record 的 key 必须是英文,这里简单演示记录某个特定行业的占比
# if ind_name == '银行I':
# record(Bank_Ratio=ratio)
log.info("=" * 30)
常见问答 (Q&A)
Q1: get_industry 函数支持哪些行业分类标准?
A1: get_industry 支持多种分类标准,常用的包括:
sw_l1,sw_l2,sw_l3: 申万一、二、三级行业。jq_l1,jq_l2: 聚宽一、二级行业。zjw: 证监会行业分类。
在代码中修改industry_name = stock_ind_info['sw_l1']['industry_name']中的键名即可切换。
Q2: 为什么有时候 get_industry 返回的行业是“未知行业”?
A2: 这通常有两个原因:
- 该股票在查询日期已经退市或尚未上市。
- 该股票属于新股,行业分类数据尚未更新(通常有滞后)。
- 查询的基金(ETF/LOF)等标的,
get_industry主要针对股票,基金通常没有标准的申万行业分类。
Q3: 如何将这些数据画成图表而不是看日志?
A3: JoinQuant 的 record() 函数可以用来画图,但它只能画时间序列曲线(Line Chart),不能画饼图或柱状图。
如果你想看行业占比的变化曲线,可以在代码循环中调用 record(Industry_Name_En=ratio)。注意 record 的参数名必须是英文且不能包含特殊字符,你需要建立一个中文行业名到英文变量名的映射关系。