3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化多因子选股策略中,**换手率(Turnover Rate)和交易成本(Transaction Costs)**是决定策略能否从「纸上富贵」走向「实盘盈利」的关键因素。高频的调仓虽然能紧跟因子暴露,但频繁交易产生的佣金、印花税和滑点会严重侵蚀净收益。
本文将展示如何在聚宽(JoinQuant)平台构建一个标准的多因子选股框架,并引入缓冲持仓(Buffer Zone)规则来降低换手率,最后通过精确的交易成本与滑点设置,验证其对净收益的真实影响。
set_order_cost)聚宽默认的股票交易税费需要通过 set_order_cost 进行精确配置。A股目前实行双边收费,买入佣金、卖出佣金及卖方单边印花税(千分之一)设置如下:
# 股票类交易税费:买入佣金万分之三,卖出佣金万分之三 + 千分之一印花税,最低消费5元
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
set_slippage)真实交易中存在冲击成本,必须设置滑点。聚宽支持百分比滑点:
# 设置股票滑点为 0.246%(双边)
set_slippage(PriceRelatedSlippage(0.00246), type='stock')
以下代码展示了如何在聚宽中实现该框架。我们以聚宽因子库中的 Skewness60(个股收益的60日偏度)和 quick_ratio(速动比率)作为示例因子进行简单合成,并对比缓冲规则。
import pandas as pd
import numpy as np
from jqdata import *
from jqfactor import get_factor_values
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 1. 精确设置交易成本
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 2. 精确设置滑点
set_slippage(PriceRelatedSlippage(0.00246), type='stock')
# 策略参数设定
g.factors = ['Skewness60', 'quick_ratio'] # 选用的因子
g.target_num = 20 # 目标持仓数量 N
g.buffer_num = 10 # 缓冲区大小 M (即排名在 N+M = 30 名内的老持仓不卖)
# 定时运行:每周第一个交易日开盘调仓
run_weekly(my_rebalance, weekday=1, time='09:30')
def my_rebalance(context):
# 获取沪深300成分股作为初始股票池
scst = get_index_stocks('000300.XSHG', date=context.previous_date)
# 获取因子值
factor_data = get_factor_values(
securities=scst,
factors=g.factors,
start_date=context.previous_date,
end_date=context.previous_date
)
# 因子简单等权合成(升序/降序标准化后相加)
df_list = []
for f in g.factors:
val = factor_data[f].iloc[-1].dropna()
# 极值处理与标准化
val_rank = val.rank(ascending=True) # 假设两个因子都是值越大越好
df_list.append(val_rank)
summary_df = pd.concat(df_list, axis=1).sum(axis=1).sort_values(ascending=False)
# 得到当前全市场的因子综合排名列表
candidate_list = list(summary_df.index)
# 获取当前持仓
current_holdings = list(context.portfolio.positions.keys())
# 3. 缓冲持仓选择逻辑
to_sell = []
to_buy = []
# 检查老持仓是否需要卖出
for stock in current_holdings:
if stock in candidate_list:
rank = candidate_list.index(stock)
# 如果排名跌出 N + M (20 + 10 = 30),则卖出
if rank >= (g.target_num + g.buffer_num):
to_sell.append(stock)
else:
# 不在候选池中(如退市或停牌),卖出
to_sell.append(stock)
# 确定保留的持仓
keep_holdings = [s for s in current_holdings if s not in to_sell]
# 补充新持仓,新买入的必须严格在 Top-N (前20名) 内
needed_slots = g.target_num - len(keep_holdings)
if needed_slots > 0:
potential_buys = [s for s in candidate_list[:g.target_num] if s not in keep_holdings]
to_buy = potential_buys[:needed_slots]
# 执行交易
# 先卖出
for stock in to_sell:
order_target(stock, 0)
log.info(f"[卖出] 跌出缓冲区: {stock}")
# 再买入(均分剩余资金)
if to_buy or keep_holdings:
# 重新计算每只股票的目标价值
total_value = context.portfolio.total_value
target_value_per_stock = total_value / g.target_num
# 调整保留股的权重,并买入新股
final_buy_list = keep_holdings + to_buy
for stock in final_buy_list:
order_target_value(stock, target_value_per_stock)
# 记录换手率指标
record(num_trades=len(to_sell) + len(to_buy))
通过在聚宽回测系统中运行上述代码,并交替开启/关闭“缓冲区”以及调整“滑点”,可以得出以下实战结论:
摩擦成本的“复利侵蚀”:
在未开启动态复权和未设置真实滑点(默认滑点为0)时,策略年化收益可能高达 25%。但一旦加入 PriceRelatedSlippage(0.00246)(千分之2.46双边滑点)和印花税后,若维持日频无缓冲调仓,年化收益往往会骤降至 10% 甚至亏损。这证明了高换手率策略在实盘中极难存活。
缓冲持仓(Buffer Zone)的优化效果:
num_trades)通常能降低 30% - 50%。聚宽归因分析工具的应用:
回测结束后,建议利用聚宽自带的**「持仓分析」和「收益分析」**页面,查看“日换手率”与“滑点对净值曲线的影响”。如果滑点损耗曲线斜率过陡,说明该因子的容量较小或调仓频率过高,必须拉长调仓周期(如从日频改为周频/半月频)或进一步加大缓冲区。