问题描述
解决方案
这是一个基于 沪深300指数 的增强策略示例。
策略逻辑说明
- 基准选择:以沪深300指数(
000300.XSHG)为基准。 - 获取权重:使用
get_index_weights获取指数成分股及其原始权重。 - 增强因子:使用 市盈率(PE) 作为增强因子。
- 逻辑:价值投资思路,认为低PE股票未来表现更好。
- 增强方式(负向剔除法):
- 按PE从小到大排序。
- 剔除 PE最高的后20%的股票(认为是高估值或业绩差的股票)。
- 保留剩余80%的股票,并按其在指数中的原始权重比例进行归一化,重新分配仓位。
- 调仓频率:按月调仓。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
import pandas as pd
def initialize(context):
"""
初始化函数
"""
# 1. 设定基准:沪深300
g.index_id = '000300.XSHG'
set_benchmark(g.index_id)
# 2. 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 3. 设置手续费(模拟股票交易佣金)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 4. 设定调仓周期:每月第一个交易日运行
run_monthly(rebalance, monthday=1, time='09:30')
# 打印日志
log.info("策略初始化完成,基准:{}".format(g.index_id))
def rebalance(context):
"""
调仓核心逻辑
"""
log.info("开始进行调仓...")
# --- 第一步:获取指数成分股及原始权重 ---
# 注意:使用上一交易日日期,避免未来函数
date = context.previous_date
# get_index_weights 返回 DataFrame,索引为股票代码,包含 'weight', 'display_name', 'date'
index_weights = get_index_weights(g.index_id, date)
if index_weights is None or index_weights.empty:
log.warn("未获取到指数权重数据,跳过本次调仓")
return
# 提取股票列表
stock_list = list(index_weights.index)
# --- 第二步:获取增强因子数据(此处以PE为例) ---
q = query(
valuation.code,
valuation.pe_ratio
).filter(
valuation.code.in_(stock_list),
valuation.pe_ratio > 0 # 过滤掉亏损股票(PE为负)
)
df_fund = get_fundamentals(q, date=date)
if df_fund.empty:
log.warn("未获取到财务数据")
return
# 将财务数据索引设为股票代码,方便合并
df_fund.set_index('code', inplace=True)
# --- 第三步:数据合并与增强逻辑 ---
# 合并 权重数据 和 因子数据
# inner join 确保股票既在指数中,又有财务数据
df_merged = pd.concat([index_weights['weight'], df_fund['pe_ratio']], axis=1, join='inner')
# 增强逻辑:剔除PE最高的20%的股票(负向剔除)
# 1. 按PE升序排列 (小的在前)
df_merged = df_merged.sort_values(by='pe_ratio', ascending=True)
# 2. 计算保留的数量 (保留前80%)
keep_count = int(len(df_merged) * 0.8)
df_target = df_merged.iloc[:keep_count].copy()
# 3. 权重归一化
# 因为剔除了部分股票,剩余股票的权重加起来小于100%,需要重新调整让它们加起来等于100%
total_weight = df_target['weight'].sum()
df_target['final_weight'] = df_target['weight'] / total_weight
log.info("选股完成,原始成分股数量: {}, 剔除后数量: {}".format(len(index_weights), len(df_target)))
# --- 第四步:执行交易 ---
do_trade(context, df_target)
def do_trade(context, df_target):
"""
执行下单交易
"""
# 获取当前账户总资产
total_value = context.portfolio.total_value
# 1. 卖出不在目标持仓中的股票
current_positions = context.portfolio.positions
for stock in current_positions:
if stock not in df_target.index:
order_target_value(stock, 0)
# 2. 买入/调整目标股票
# 遍历目标股票,按计算出的权重下单
# 注意:为了防止资金不足导致无法买入最后几只,通常可以预留少量现金或按比例下单
for stock in df_target.index:
target_weight = df_target.loc[stock, 'final_weight']
target_value = total_value * target_weight
# 下单调整到目标金额
order_target_value(stock, target_value)
代码关键点解析
-
get_index_weights(index_id, date):- 这是本策略的核心API。它返回指定日期指数成分股的权重信息。
- 返回的 DataFrame 中,
index是股票代码,weight列是权重(通常是百分比形式,如 0.5 代表 0.5%)。 - 注意:我们在计算最终权重时进行了归一化(
df_target['weight'] / total_weight),所以无论原始权重是百分比还是小数都不影响最终比例。
-
增强逻辑 (Negative Screening):
- 代码中使用了
pd.concat将指数权重和 PE 数据对齐。 - 通过
sort_values和切片iloc[:keep_count]实现了剔除高估值股票的操作。 - 这是一种最基础且稳健的增强方法:保留了指数的大部分特征(Beta),同时试图通过剔除垃圾股来获取超额收益(Alpha)。
- 代码中使用了
-
权重归一化:
- 剔除股票后,剩余股票的原始权重之和肯定小于 1。
- 必须执行
df_target['weight'] / total_weight,将资金分配填满,否则会导致仓位长期不满,跑输基准。
-
交易执行:
- 使用
order_target_value根据总资产和目标权重计算每只股票应持有的金额,自动进行买卖调整。
- 使用
常见问题 Q&A
Q: 为什么要使用 context.previous_date 获取权重?
A: 为了避免未来函数。在回测中,如果在开盘前(09:30)获取“今天”的指数权重,这在现实中是不可能的(因为今天的权重可能受今日行情影响)。使用前一交易日的数据更符合实盘逻辑。
Q: 这个策略可以换成其他指数吗?
A: 可以。只需将 g.index_id 修改为其他指数代码,例如中证500(000905.XSHG)或上证50(000016.XSHG)。
Q: 如何更换增强因子?
A: 修改 get_fundamentals 中的查询语句。例如,如果你想用 ROE(净资产收益率) 增强:
- 查询
indicator.roe。 - 排序时改为
ascending=False(因为ROE越高越好,我们要保留头部股票)。