组合优化

选出一篮子股票后,每只配多少是比「选什么」更关键的问题。聚宽的 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 落地。下一步进入因子研究:因子研究