组合优化
选出一篮子股票后,每只配多少是比「选什么」更关键的问题。聚宽的 portfolio_optimizer 函数能在给定约束下自动计算最优权重,支持风险平价、最大夏普、最小方差等经典模型。本章讲清怎么用。
一、核心函数
from jqlib.optimizer import *
optimized_weight = portfolio_optimizer(
date, # 优化日期(注意未来函数)
securities, # 股票代码列表
target, # 目标函数(选一个)
constraints, # 约束条件列表
bounds=[Bound(0.0, 1.0)], # 单标的权重边界
default_port_weight_range=[0.0, 1.0], # 总权重范围
ftol=1e-9, # 求解精度
return_none_if_fail=True # 失败返回 None
)
返回一个 pd.Series,index 是股票代码,value 是优化后的权重。
二、目标函数(选一个)
| 目标函数 | 含义 | 适用场景 |
|---|---|---|
MinVariance(count=250) | 最小化组合方差 | 追求低波动 |
MaxProfit(count=250) | 最大化组合收益 | 预期收益明确时 |
MaxSharpeRatio(rf=0, count=250) | 最大化夏普比率 | 风险收益平衡 |
RiskParity(count=250) | 风险平价(各资产风险贡献相等) | 均衡配置 |
MinTrackingError(benchmark, count=250) | 最小化追踪误差 | 指数增强 |
MaxScore(scores) | 打分最大化 | 因子打分选股 |
MaxFactorValue(factor, count=1) | 因子值最大化 | 因子驱动 |
# 风险平价:每只股票风险贡献相等
target = RiskParity(count=250, risk_budget=None)
# 最大夏普:无风险利率 2%,组合权重和为 1
target = MaxSharpeRatio(rf=0.02, weight_sum_equal=1.0, count=250)
# 因子打分最大化
scores = pd.Series([0.1, 0.2, 0.3], index=['000001.XSHE','000002.XSHE','000005.XSHE'])
target = MaxScore(scores=scores)
三、约束条件(可多个)
| 约束 | 含义 |
|---|---|
WeightConstraint(low=0.5, high=0.9) | 组合总权重上下限 |
WeightEqualConstraint(limit=1.0) | 组合总权重等于某值 |
AnnualStdConstraint(limit=0.15) | 年化波动率上限 |
AnnualProfitConstraint(limit=0.1) | 年化收益下限 |
IndustryConstraint('HY007', low=0, high=0.2) | 某行业权重上下限 |
IndustriesConstraint('jq_l1', low=0, high=0.2) | 行业分类下所有行业各自上限 |
MarketConstraint('stock', low=0, high=0.9) | 某类标的权重上下限 |
BarraConstraint(size=[-0.5,0.5], beta=[None,1.5]) | Barra 风险因子暴露限制 |
TrackingErrorConstraint(benchmark, limit=0.05) | 追踪误差限制 |
TurnoverConstraint(limit=0.5) | 换手率限制 |
MaxDrawdownConstraint(-0.25) | 最大回撤限制 |
constraints = [
WeightConstraint(low=0.9, high=1.0), # 满仓左右
AnnualStdConstraint(limit=0.15), # 波动率不超 15%
IndustryConstraint('HY007', low=0, high=0.2), # 银行业不超 20%
]
四、边界(单标的权重)
| 边界 | 含义 |
|---|---|
Bound(low=0, high=0.1) | 每只权重不超 10% |
IndustryBound(['HY001'], low=0, high=0.05) | 某行业个股额外限制 |
LiquidityBound(0.5, capital=1000000) | 成交量占比不超 50% |
CapBound(0.025, capital=1e8) | 市值占比不超 2.5% |
bounds = [Bound(0, 0.1)] # 每只股票最多 10%
五、完整示例:风险平价调仓
from jqlib.optimizer import *
def initialize(context):
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
run_monthly(rebalance, 1, '09:30')
def rebalance(context):
# 选股:沪深 300 成分
stocks = get_index_stocks('000300.XSHG')[:30]
# 风险平价优化,总权重 90%~100%,单只不超 10%
weights = portfolio_optimizer(
date=context.previous_date,
securities=stocks,
target=RiskParity(count=250),
constraints=[WeightConstraint(low=0.9, high=1.0)],
bounds=[Bound(0, 0.1)],
return_none_if_fail=True
)
if weights is None:
log.warn('优化失败')
return
# 按优化结果调仓
total = context.portfolio.total_value
for stock, w in weights.items():
order_target_value(stock, total * w)
# 卖掉不在目标池的持仓
for stock in context.portfolio.positions:
if stock not in weights.index:
order_target_value(stock, 0)
六、常见模型对比
| 模型 | 特点 | 适合场景 |
|---|---|---|
| 等权重 | 最简单,无需优化 | 基线对比 |
| 风险平价 | 低相关资产均衡分散 | 多资产配置 |
| 最小方差 | 追求绝对稳健 | 震荡市 |
| 最大夏普 | 风险收益最优 | 趋势明确时 |
| 打分最大化 | 给好票更高权重 | 因子选股 |
portfolio_optimizer不会自动执行下单,你需要拿到权重后自己用order_target_value调仓。
小结
portfolio_optimizer 通过「目标函数 + 约束条件 + 边界」三要素求最优权重:目标定方向(风险平价/最大夏普),约束控整体(行业/波动率),边界限个股。拿到权重后用 order_target_value 落地。下一步进入因子研究:因子研究。