3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化投资中,红利(Value/Dividend)、**低波动(Low Volatility)与动量(Momentum)是三种具有低相关性且长期被验证有效的经典风格因子。将三者结合构建多因子策略,不仅能平滑单一因子的周期性失效,还能通过因子互补提升组合的夏普比率。同时,引入调仓缓冲机制(Buffer Zone)**可以有效避免边缘标的频繁进出导致的过度交易,从而大幅降低交易成本。
本指南将教你如何在聚宽(JoinQuant)平台中,利用聚宽因子库(jqfactor)和组合优化思想,构建这一经典的多因子选股策略。
在聚宽因子库中,我们可以直接获取这三类风格因子或其描述因子:
book_to_price_ratio(账面市值比因子,即PB的倒数)。账面市值比越高,代表估值越低,红利属性越强。residual_volatility(残差波动率因子)。该因子排除了市场Beta和市值因子的影响,能更纯粹地反映个股的特质低波动属性。因子值越小,波动越低。momentum(动量因子,即相对强弱)。反映个股过去一段时间的超额对数收益率。因子值越大,动量效应越强。为了防止在调仓日,排名在临界点(例如第50名)附近的股票因为微小的排名变动而被频繁买卖,我们设计如下缓冲机制:
以下是基于聚宽 API 编写的完整策略代码。策略采用按月调仓,使用 get_factor_values 获取因子,并进行去极值、标准化和等权重合成。
import pandas as pd
import numpy as np
from jqdata import *
from jqfactor import get_factor_values, winsorize_med, standardlize
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 股票类交易手续费设置
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 策略参数设置
g.factors = ['book_to_price_ratio', 'residual_volatility', 'momentum']
g.N = 40 # 买入触发线(前40名)
g.M = 60 # 卖出触发线(跌出前60名)
# 每月第一个交易日开盘运行
run_monthly(rebalance, monthday=1, time='09:30')
def rebalance(context):
# 1. 获取全A股股票池(剔除ST、停牌、上市未满一年的股票)
current_data = get_current_data()
all_stocks = list(get_all_securities(['stock'], date=context.current_dt).index)
# 过滤标的
all_stocks = [s for s in all_stocks if not current_data[s].is_st
and not current_data[s].paused
and (context.current_dt.date() - get_security_info(s).start_date).days > 365]
# 2. 获取因子值
yesterday = context.previous_date
factor_data = get_factor_values(securities=all_stocks,
factors=g.factors,
start_date=yesterday,
end_date=yesterday)
# 3. 因子数据处理与合成
df_list = []
for factor in g.factors:
val = factor_data[factor].loc[pd.Timestamp(yesterday)]
# 去极值与标准化
val = winsorize_med(val, scale=3)
val = standardlize(val)
df_list.append(val)
df = pd.concat(df_list, axis=1, keys=g.factors).dropna()
# 4. 计算综合得分 (红利+,低波-,动量+)
# 低波动因子值越小越好,因此乘以 -1 进行合成
df['score'] = df['book_to_price_ratio'] * 1.0 - df['residual_volatility'] * 1.0 + df['momentum'] * 1.0
# 5. 股票排序
df_sorted = df.sort_values(by='score', ascending=False)
target_list = list(df_sorted.index) # 综合得分从高到低的股票列表
# 6. 缓冲机制逻辑应用
current_holdings = list(context.portfolio.positions.keys())
keep_list = []
# 检查当前持仓,如果在前 M (60) 名内,则保留
for stock in current_holdings:
if stock in target_list:
rank = target_list.index(stock)
if rank < g.M:
keep_list.append(stock)
# 计算还需要新买入的股票数量
num_to_buy = g.N - len(keep_list)
new_buy_list = []
# 从前 N (40) 名中挑选未持有的股票补足仓位
for stock in target_list[:g.N]:
if stock not in keep_list:
new_buy_list.append(stock)
if len(new_buy_list) == num_to_buy:
break
final_target_portfolio = keep_list + new_buy_list
# 7. 执行交易(等权重配置)
# 卖出不在最终目标组合中的股票
for stock in current_holdings:
if stock not in final_target_portfolio:
order_target(stock, 0)
log.info(f"[卖出] 跌出缓冲区: {stock}")
# 等权重买入/调仓
position_value = context.portfolio.total_value / g.N
for stock in final_target_portfolio:
order_target_value(stock, position_value)
if stock not in current_holdings:
log.info(f"[买入] 综合评分晋级: {stock}")
neutralize 函数对因子进行行业和市值中性化,使组合行业分布更均衡。