3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化投资中,当投资者通过因子分析或多因子模型对股票池中的标的进行了打分(Score),并希望在满足特定风控约束(如组合年化波动率、行业暴露限制或单标的权重上限)的前提下,实现组合整体打分最大化时,可以使用 JoinQuant 聚宽平台提供的 portfolio_optimizer 优化器中的 MaxScore 目标函数。
MaxScore(scores) 模型的核心逻辑是:在满足所有约束条件(Constraints)和单标的边界(Bounds)的前提下,赋予打分高(预期表现更好)的标的更高的权重。
例如:若有 A、B、C 三只股票,打分分别为 3、2、1。如果全仓买入 A 能获得最高总打分,但可能会突破年化波动率不超过 15% 的风控限制。优化器会自动计算并配置一定比例的 B 和 C,使组合在严格满足波动率约束的条件下获得最高打分。
要运行 MaxScore 优化器,需要导入 jqlib.optimizer 模块并调用 portfolio_optimizer 函数。
date: 优化发生的日期(如 context.previous_date)。securities: 参与优化的股票或标的代码列表。target: 设定为 MaxScore(scores),其中 scores 是以标的代码为索引(index)、打分值为数值的 pandas.Series。constraints: 限制条件列表,如 AnnualStdConstraint(收益率标准差限制)、WeightConstraint(总权重限制)等。bounds: 边界条件列表,如 Bound(low, high) 限制单只股票的持仓比例。以下示例展示了如何在聚宽回测/模拟环境中,根据过去 5 日的平均收盘价变化率作为自定义打分,结合 MaxScore 优化器进行定期调仓:
import pandas as pd
from jqdata import *
from jqlib.optimizer import *
def initialize(context):
# 设定基准与真实价格模式
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')
# 按月定时运行调仓
run_monthly(rebalance, monthday=1, time='09:30', reference_security='000300.XSHG')
def rebalance(context):
# 1. 确定股票池(例如:沪深300前20只成分股)
securities = get_index_stocks('000300.XSHG')[:20]
# 2. 计算自定义打分(示例:获取过去5天收盘价并计算简单打分)
hist = attribute_history(securities, 5, '1d', fields=['close'])
scores = (hist['close'].iloc[-1] / hist['close'].iloc[0] - 1.0)
# 确保 scores 为 pandas.Series
scores = pd.Series(scores, index=securities)
# 3. 设置优化目标与约束条件
target = MaxScore(scores=scores)
constraints = [
WeightConstraint(low=0.9, high=1.0), # 组合总仓位限制在 90% ~ 100%
AnnualStdConstraint(limit=0.25, count=250) # 组合年化波动率控制在 25% 以内
]
bounds = [
Bound(low=0.0, high=0.15) # 单只股票权重不超过 15%
]
# 4. 执行组合优化
optimized_weights = portfolio_optimizer(
date=context.previous_date,
securities=securities,
target=target,
constraints=constraints,
bounds=bounds,
default_port_weight_range=[0.0, 1.0],
ftol=1e-09,
return_none_if_fail=True
)
# 5. 根据优化结果调仓
if optimized_weights is not None:
print("优化后的持仓权重:")
print(optimized_weights)
# 卖出不在优化结果中的股票
for stock in context.portfolio.positions.keys():
if stock not in optimized_weights or optimized_weights[stock] == 0:
order_target_value(stock, 0)
# 按优化权重调整目标价值
total_value = context.portfolio.total_value
for stock, weight in optimized_weights.items():
target_value = total_value * weight
order_target_value(stock, target_value)
else:
print("警告:组合优化失败,保持当前仓位不变。")
MaxScore 默认给数值越大的标的分配更高权重。如果你的打分逻辑是“数值越小越好”(如 PE 估值打分),请使用 MinScore 模型或对打分取负数处理。None。建议设置 return_none_if_fail=True 并编写兜底降级逻辑(如等权重配置或维持原仓位)。portfolio_optimizer 的 date 参数建议设为 context.previous_date,计算打分时使用历史完整交易日的数据。