3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的量化策略需求。行业轮动策略的核心在于:自上而下地选股,先选出基本面最好的行业,再从该行业中选出代表性股票。
以下是完整的 Python 策略代码。请将其复制到 QMT 的策略编辑器中。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置基本参数
ContextInfo.holding_num = 5 # 每个行业买入的股票数量
ContextInfo.rebalance_period = 20 # 调仓周期(交易日),约一个月
ContextInfo.industry_top_n = 1 # 选取排名前几的行业
# 2. 设置资金账号 (请替换为您自己的资金账号)
# ContextInfo.set_account('您的资金账号')
# 3. 定义行业列表 (这里以申万一级行业为例,需确保客户端有板块数据)
# 如果本地没有申万数据,可以使用 'CSRC金融业' 等证监会行业名称
ContextInfo.industry_list = [
'申万银行', '申万房地产', '申万医药生物', '申万食品饮料',
'申万电子', '申万计算机', '申万非银金融', '申万有色金属',
'申万化工', '申万采掘', '申万钢铁', '申万公用事业'
]
# 4. 设定回测相关参数 (仅回测有效)
ContextInfo.set_commission(0, [0.0001, 0.001, 0.0003, 0.0003, 0.0003, 5]) # 设置费率
ContextInfo.capital = 1000000 # 初始资金
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 跳过历史K线,只在最后根K线或回测模式下运行
# 如果是实盘,建议结合定时器使用,这里演示简单的bar驱动
# 获取当前K线索引
bar_index = ContextInfo.barpos
# 检查是否达到调仓周期
if bar_index % ContextInfo.rebalance_period != 0:
return
print(f"=== 开始进行行业轮动分析 (Bar: {bar_index}) ===")
# 获取当前时间
current_date = ContextInfo.get_bar_timetag(bar_index)
current_date_str = timetag_to_datetime(current_date, '%Y%m%d')
# 1. 计算各行业 ROE
industry_scores = []
for industry_name in ContextInfo.industry_list:
# 获取行业成分股
stock_list = ContextInfo.get_stock_list_in_sector(industry_name)
if not stock_list:
continue
# 计算该行业的整体ROE
roe = calculate_industry_roe(ContextInfo, stock_list, current_date_str)
if roe is not None:
industry_scores.append((industry_name, roe))
# 2. 对行业按 ROE 从高到低排序
industry_scores.sort(key=lambda x: x[1], reverse=True)
if not industry_scores:
print("未计算出有效行业数据")
return
# 选取排名靠前的行业
target_industries = [x[0] for x in industry_scores[:ContextInfo.industry_top_n]]
print(f"本期选中行业: {target_industries}, ROE: {industry_scores[0][1]:.4f}")
# 3. 在选中行业中选股 (选净利润最大的龙头股)
target_stocks = []
for ind in target_industries:
stocks = ContextInfo.get_stock_list_in_sector(ind)
top_stocks = select_top_stocks(ContextInfo, stocks, current_date_str, ContextInfo.holding_num)
target_stocks.extend(top_stocks)
print(f"本期目标持仓: {target_stocks}")
# 4. 执行交易
rebalance_portfolio(ContextInfo, target_stocks)
def calculate_industry_roe(ContextInfo, stock_list, date_str):
"""
计算行业整体ROE = 行业总净利润 / 行业总净资产
"""
# 财务字段: 净利润(归属母公司), 股东权益(归属母公司)
fields = ['ASHAREINCOME.net_profit_incl_min_int_inc', 'ASHAREBALANCESHEET.total_shrhldr_eqy_excl_min_int']
# 获取财务数据 (注意:get_financial_data 返回数据结构较为复杂,需处理)
# 这里取最近的一个报告期数据
df = ContextInfo.get_financial_data(fields, stock_list, date_str, date_str)
if df is None or df.empty:
return None
# 数据清洗
# get_financial_data 在多股单时间点下返回 DataFrame,index为股票代码,columns为字段
try:
# 确保数据类型为数值
df = df.apply(pd.to_numeric, errors='coerce').fillna(0)
total_net_profit = df['ASHAREINCOME.net_profit_incl_min_int_inc'].sum()
total_equity = df['ASHAREBALANCESHEET.total_shrhldr_eqy_excl_min_int'].sum()
if total_equity == 0:
return 0
return total_net_profit / total_equity
except Exception as e:
# print(f"计算行业ROE出错: {e}")
return None
def select_top_stocks(ContextInfo, stock_list, date_str, top_n):
"""
在指定股票列表中,选取净利润最大的前N只
"""
field = ['ASHAREINCOME.net_profit_incl_min_int_inc']
df = ContextInfo.get_financial_data(field, stock_list, date_str, date_str)
if df is None or df.empty:
return []
try:
df = df.apply(pd.to_numeric, errors='coerce').fillna(0)
# 按净利润降序排列
df_sorted = df.sort_values(by='ASHAREINCOME.net_profit_incl_min_int_inc', ascending=False)
# 取前N只股票代码
return df_sorted.head(top_n).index.tolist()
except:
return []
def rebalance_portfolio(ContextInfo, target_stocks):
"""
调仓执行函数
"""
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'position')
current_holdings = [pos.m_strInstrumentID for pos in positions if pos.m_nVolume > 0]
# 1. 卖出不在目标列表中的股票
for stock in current_holdings:
if stock not in target_stocks:
order_target_value(stock, 0, ContextInfo, ContextInfo.accid)
print(f"卖出: {stock}")
# 2. 买入目标股票
if not target_stocks:
return
# 获取账户总资产 (回测用 ContextInfo.capital 估算,实盘应用 get_trade_detail_data 获取 'account')
# 这里简单演示,假设满仓操作,每只股票等权重
# 获取账户净资产
acc_info = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'account')
total_asset = acc_info[0].m_dBalance if acc_info else ContextInfo.capital
# 每只股票的目标市值
target_value_per_stock = total_asset / len(target_stocks)
for stock in target_stocks:
order_target_value(stock, target_value_per_stock, ContextInfo, ContextInfo.accid)
print(f"买入/调仓: {stock}, 目标市值: {target_value_per_stock}")
数据获取 (get_financial_data):
ASHAREINCOME.net_profit_incl_min_int_inc (净利润) 和 ASHAREBALANCESHEET.total_shrhldr_eqy_excl_min_int (归属母公司股东权益) 这两个核心字段。date_str 最近的已公告财报数据,避免了未来函数(即不会在财报发布前就用到数据)。行业整体法计算:
calculate_industry_roe 函数并没有简单地对每只股票的 ROE 求平均,而是将行业内所有公司的净利润加总,除以所有公司的净资产加总。这在量化中被称为“整体法”,能更真实地反映行业龙头的盈利能力,避免小市值亏损股扭曲行业数据。板块成分股 (get_stock_list_in_sector):
'申万银行' 等名称。注意:在实际运行前,请务必在 QMT 客户端左侧的“板块”树中确认您已下载了对应的板块数据,且板块名称与代码中的字符串一致。如果您的客户端中叫 "SW银行",请相应修改代码。交易执行 (order_target_value):
order_target_value 是最方便的调仓方式。它会自动计算需要买入或卖出的差额。例如,如果目标市值是 10 万,当前持仓市值 8 万,它会自动买入 2 万。init 函数中,找到 ContextInfo.set_account('您的资金账号'),取消注释并填入真实的资金账号(回测模式下通常不需要这一步,但实盘必须设置)。NoneType object is not subscriptable:通常是因为 get_financial_data 没有取到数据。请检查数据管理中是否已下载了财务数据。ContextInfo.industry_list 中的名称是否与 QMT 客户端左侧板块栏中的名称完全一致。