问题描述
如何构建一个由10只股票组成的风险最小化的投资组合?
解决方案
构建一个由10只股票组成的风险最小化投资组合,通常包含两个步骤:
- 选股(Selection):从股票池(如沪深300)中筛选出波动率较低的10只股票。低波动率通常意味着个股风险较低。
- 优化(Optimization):使用投资组合优化器,对这10只股票的权重进行配置,使得整体组合的方差(风险)最小。
在聚宽(JoinQuant)中,我们可以利用 jqlib.optimizer 库中的 portfolio_optimizer 函数和 MinVariance(最小方差)目标函数来实现这一策略。
以下是完整的策略代码:
策略逻辑说明
- 股票池:以沪深300指数成分股为基础。
- 选股逻辑:每月调仓一次。在调仓日,计算所有成分股过去一年的日收益率标准差(波动率),选取波动率最低的10只股票。
- 权重优化:使用
MinVariance模型,计算这10只股票在组合风险最小化情况下的最优权重。 - 交易执行:根据优化后的权重调整持仓。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
from jqlib.optimizer import *
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 设定基准为沪深300
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定交易费率
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定全局变量
g.stock_num = 10 # 持仓股票数量
# 设定每月第一个交易日进行调仓
run_monthly(rebalance, monthday=1, time='09:30')
def rebalance(context):
"""
调仓主函数
"""
print(f"日期: {context.previous_date} 开始调仓")
# 1. 获取股票池(沪深300)
universe = get_index_stocks('000300.XSHG')
# 2. 过滤停牌和ST股票
current_data = get_current_data()
universe = [stock for stock in universe if not current_data[stock].paused and not current_data[stock].is_st]
if len(universe) < g.stock_num:
print("可选股票不足,跳过本次调仓")
return
# 3. 选股:选取过去250个交易日波动率最低的10只股票
# 获取过去250天的收盘价
history_data = get_price(universe, end_date=context.previous_date, count=250, fields=['close'], panel=False)
# 计算日收益率
# pivot转换格式:行是日期,列是股票代码
close_df = history_data.pivot(index='time', columns='code', values='close')
returns_df = close_df.pct_change().dropna()
# 计算每只股票的标准差(波动率)
volatility = returns_df.std()
# 取波动率最小的前10只股票
target_stocks = volatility.nsmallest(g.stock_num).index.tolist()
print(f"选定低波动股票: {target_stocks}")
# 4. 组合优化:计算最小化方差的权重
# 使用 jqlib.optimizer 中的 portfolio_optimizer
# 目标:MinVariance (最小化组合方差)
# 约束:WeightEqualConstraint(1.0) 保证总仓位为100%
# 边界:Bound(0, 1) 每只股票权重在0到1之间(不做空)
try:
optimized_weights = portfolio_optimizer(
date=context.previous_date,
securities=target_stocks,
target=MinVariance(count=250), # 优化目标:最小方差
constraints=[
WeightEqualConstraint(limit=1.0), # 总权重等于1
WeightConstraint(low=0.0, high=1.0) # 单只股票权重限制
],
bounds=[Bound(0.0, 1.0)], # 边界条件
default_port_weight_range=[0.0, 1.0],
return_none_if_fail=True
)
except Exception as e:
print(f"优化器运行出错: {e}")
optimized_weights = None
# 5. 执行交易
if optimized_weights is None:
print("组合优化失败,使用等权重分配")
# 如果优化失败,退化为等权重
weight_value = 1.0 / len(target_stocks)
optimized_weights = pd.Series(weight_value, index=target_stocks)
# 获取当前持仓
current_positions = context.portfolio.positions.keys()
# 卖出不在目标列表中的股票
for stock in current_positions:
if stock not in target_stocks:
order_target_value(stock, 0)
# 买入/调整目标股票
total_value = context.portfolio.total_value
for stock in target_stocks:
target_weight = optimized_weights.get(stock, 0.0)
if target_weight > 0:
target_value = total_value * target_weight
order_target_value(stock, target_value)
print("调仓完成")
代码关键点解析
get_index_stocks('000300.XSHG'): 获取沪深300成分股作为基础池,保证流动性。volatility.nsmallest(g.stock_num): 这是实现“10只股票”约束的关键步骤。我们先通过历史波动率筛选出最稳定的10只股票,而不是直接把300只股票扔给优化器(那样优化器可能会选出几十只股票,或者集中在极少数股票上)。portfolio_optimizer: 这是聚宽提供的核心优化函数。target=MinVariance(count=250): 告诉优化器我们的目标是让组合的整体方差最小,计算基于过去250天的数据。constraints: 限制所有股票权重之和为1(满仓操作)。
- 容错处理: 如果优化器因为数据原因求解失败(返回
None),策略会自动退化为等权重持有这10只股票,保证策略的稳健性。