3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在PTrade中,cvxopt 是一个强大的凸优化库,常用于解决二次规划(Quadratic Programming, QP)问题。在量化投资中,最经典的应用场景就是马科维茨(Markowitz)均值-方差投资组合优化。
投资组合优化的目标通常是在给定预期收益的情况下最小化风险(方差),或者在给定风险的情况下最大化收益。这是一个典型的二次规划问题。
标准形式如下:
$$ \text{minimize} \quad \frac{1}{2} x^T P x + q^T x $$
$$ \text{subject to} \quad G x \leq h $$
$$ \text{and} \quad A x = b $$
对应到投资组合优化(最小化方差模型):
以下是一个完整的策略示例。该策略选取上证50成分股,每隔20个交易日,利用 cvxopt 计算最小方差投资组合的权重并进行调仓。
import numpy as np
import pandas as pd
from cvxopt import matrix, solvers
# 设置cvxopt输出不显示进度
solvers.options['show_progress'] = False
def initialize(context):
"""
初始化函数
"""
# 设定基准
set_benchmark('000016.SS')
# 设定滑点
set_slippage(0.002)
# 设定佣金
set_commission(commission_ratio=0.0003)
# 设定股票池:上证50
g.index = '000016.SS'
# 调仓周期(天)
g.rebalance_days = 20
# 计数器
g.days_counter = 0
# 历史数据窗口长度(用于计算协方差)
g.history_window = 60
# 初始股票池获取(也可以在before_trading_start中动态获取)
g.stocks = get_index_stocks(g.index)
set_universe(g.stocks)
def handle_data(context, data):
"""
盘中运行函数
"""
# 检查是否达到调仓周期
if g.days_counter % g.rebalance_days == 0:
rebalance_portfolio(context)
g.days_counter += 1
def rebalance_portfolio(context):
"""
执行投资组合优化与调仓
"""
# 1. 获取股票池
# 注意:get_index_stocks建议在before_trading_start调用,这里为了演示逻辑完整性放在一起
stocks = get_index_stocks(g.index)
if not stocks:
return
# 2. 获取历史收盘价数据
# PTrade Python3.5环境下,多只股票单字段返回DataFrame,列为股票代码
hist_data = get_history(g.history_window, '1d', 'close', security_list=stocks, fq='pre')
# 检查数据有效性
if hist_data is None or len(hist_data) < g.history_window:
log.info("历史数据不足,跳过本次调仓")
return
# 3. 计算收益率和协方差矩阵
# 计算日收益率
returns = hist_data.pct_change().dropna()
# 如果数据缺失过多,剔除缺失数据的股票
returns = returns.dropna(axis=1)
valid_stocks = returns.columns.tolist()
n = len(valid_stocks)
if n < 2:
log.info("有效股票数量不足,跳过调仓")
return
# 计算协方差矩阵 (P矩阵的核心)
cov_mat = returns.cov()
# 4. 构建 cvxopt 所需的矩阵参数
# 目标函数: minimize (1/2) * w.T * P * w
# P: 协方差矩阵
P = matrix(cov_mat.values)
# q: 线性项系数,最小化方差时为0向量
q = matrix(0.0, (n, 1))
# 不等式约束: w >= 0 => -w <= 0
# G: -I (负单位矩阵)
G = matrix(-np.eye(n))
# h: 0向量
h = matrix(0.0, (n, 1))
# 等式约束: sum(w) = 1
# A: 全1行向量
A = matrix(1.0, (1, n))
# b: 标量1.0
b = matrix(1.0)
# 5. 求解二次规划问题
try:
sol = solvers.qp(P, q, G, h, A, b)
# 获取权重,转换为list
weights = np.array(sol['x']).flatten()
except Exception as e:
log.error("优化求解失败: {}".format(e))
return
# 6. 执行调仓
log.info("优化成功,开始调仓...")
# 获取当前账户总资产
total_value = context.portfolio.portfolio_value
# 先卖出不在有效列表中的股票
current_positions = list(context.portfolio.positions.keys())
for stock in current_positions:
if stock not in valid_stocks:
order_target_value(stock, 0)
# 根据优化后的权重买入/调整
for i, stock in enumerate(valid_stocks):
target_weight = weights[i]
# 过滤掉极小的权重(例如小于0.1%),避免产生碎股或无意义交易
if target_weight < 0.001:
target_weight = 0.0
target_value = total_value * target_weight
order_target_value(stock, target_value)
log.info("调仓完成")
数据准备 (get_history):
g.history_window 天的收盘价。pct_change() 计算日收益率。cov() 计算协方差矩阵,这是衡量资产间风险关联的核心数据。矩阵构建 (cvxopt.matrix):
cvxopt 的求解器需要特定的矩阵格式。求解 (solvers.qp):
solvers.qp(P, q, G, h, A, b) 进行求解。sol['x'] 中,即最优的权重向量。交易执行:
order_target_value 根据计算出的权重和当前总资产价值进行下单。if target_weight < 0.001,忽略过小的权重,避免产生过多的佣金损耗。.format() 而非 f-string,且使用了 PTrade 标准的 API 调用方式,兼容 Python 3.5 及以上环境。.dropna() 来确保协方差矩阵是正定的且计算不出错。try...except 块。Q: 为什么使用 cvxopt 而不是 scipy.optimize?
A: 虽然 scipy 也能做优化,但 cvxopt 是专门为凸优化设计的,对于二次规划(QP)问题,它的求解速度通常更快,且对于约束条件的处理(矩阵形式)更加规范和直观,非常适合处理资产数量较多的投资组合问题。
Q: 如何加入预期收益率约束(例如要求组合预期年化收益 > 10%)?
A: 你需要在不等式约束 $G, h$ 中增加一行。假设 $\mu$ 是预期收益率向量,要求 $\mu^T w \geq 0.10$,即 $-\mu^T w \leq -0.10$。你需要将这一行拼接到 $G$ 矩阵的底部,并将 $-0.10$ 拼接到 $h$ 向量的底部。
Q: 为什么代码中要设置 solvers.options['show_progress'] = False?
A: cvxopt 默认会在控制台打印求解过程的迭代日志。在回测或实盘日志中,这些大量的文本信息会干扰正常的策略日志查看,因此通常将其关闭。