3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的量化对冲策略需求。在 JoinQuant(聚宽)平台上,实现这一策略主要分为两步:
Beta ($\beta$) 计算公式:
$$ \beta_p = \sum (w_i \times \beta_i) $$
其中 $w_i$ 是股票 $i$ 在组合中的权重,$\beta_i$ 是股票 $i$ 相对于基准的 Beta。
单个股票 Beta 计算:$\beta_i = \frac{Cov(r_i, r_m)}{Var(r_m)}$
($r_i$ 为股票收益率,$r_m$ 为基准收益率)
对冲所需期货合约数:
$$ N = \frac{TotalValue_{stock} \times \beta_p}{Price_{future} \times Multiplier} $$
其中 $Multiplier$ 是合约乘数(沪深300 IF 为 300)。
以下是一个完整的策略代码。该策略会构建一个简单的股票组合(例如买入低估值股票),并每日计算 Beta,使用沪深300股指期货(IF)进行动态对冲。
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from jqdata import *
def initialize(context):
# 1. 设定基准为沪深300
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 2. 设定手续费
# 股票:买入万3,卖出万3加千1印花税
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 期货:无印花税,买卖万0.23 (示例值,具体视交易所标准)
set_order_cost(OrderCost(open_tax=0, close_tax=0, open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023, min_commission=0), type='index_futures')
# 3. 设定全局变量
g.stock_pool = [] # 股票池
g.future_code = None # 当前主力合约
g.days = 60 # 计算Beta使用的历史天数
# 4. 运行计划
# 每周一开盘调整股票仓位
run_weekly(adjust_stock_position, weekday=1, time='09:30')
# 每天开盘后调整对冲仓位 (股票调仓后立即对冲)
run_daily(adjust_hedge_position, time='09:40')
# 每天收盘后打印当日Beta
run_daily(print_beta, time='15:00')
def adjust_stock_position(context):
"""
选股与调仓逻辑(示例:简单的低PE选股)
"""
# 获取沪深300成分股
stocks = get_index_stocks('000300.XSHG')
# 查询PE数据
q = query(valuation.code, valuation.pe_ratio).filter(
valuation.code.in_(stocks),
valuation.pe_ratio > 0
).order_by(valuation.pe_ratio.asc()).limit(10) # 选PE最小的10只
df = get_fundamentals(q)
g.stock_pool = list(df['code'])
# 卖出不在股票池的股票
for stock in context.portfolio.positions:
if context.portfolio.positions[stock].total_amount > 0:
if stock not in g.stock_pool:
order_target_value(stock, 0)
# 买入股票池中的股票(等权分配)
if len(g.stock_pool) > 0:
# 预留一部分资金给期货保证金,假设使用90%资金买股票
available_cash = context.portfolio.total_value * 0.9
target_value = available_cash / len(g.stock_pool)
for stock in g.stock_pool:
order_target_value(stock, target_value)
def calculate_portfolio_beta(context):
"""
计算持仓组合的Beta值
"""
# 获取基准(沪深300)的历史数据
benchmark_data = attribute_history('000300.XSHG', g.days + 1, '1d', ['close'])
# 计算基准收益率
benchmark_ret = benchmark_data['close'].pct_change().dropna()
# 如果没有持仓,Beta为0
if len(context.portfolio.positions) == 0:
return 0.0
total_value = context.portfolio.positions_value
if total_value == 0:
return 0.0
portfolio_beta = 0.0
# 遍历持仓计算加权Beta
for stock, position in context.portfolio.positions.items():
if position.total_amount == 0:
continue
# 获取个股历史数据
stock_data = attribute_history(stock, g.days + 1, '1d', ['close'])
stock_ret = stock_data['close'].pct_change().dropna()
# 确保数据长度一致
min_len = min(len(benchmark_ret), len(stock_ret))
if min_len < 10: # 数据太少不计算
continue
rb = benchmark_ret.iloc[-min_len:]
rs = stock_ret.iloc[-min_len:]
# 计算协方差矩阵 [[Var(s), Cov(s,b)], [Cov(b,s), Var(b)]]
cov_mat = np.cov(rs, rb)
beta_i = cov_mat[0, 1] / cov_mat[1, 1]
# 计算权重
weight_i = position.value / total_value
# 累加组合Beta
portfolio_beta += beta_i * weight_i
return portfolio_beta
def adjust_hedge_position(context):
"""
调整期货对冲仓位
"""
# 1. 获取当前主力合约
# IF: 沪深300, IC: 中证500, IH: 上证50
# 这里我们用沪深300期货对冲
new_future = get_dominant_future('IF')
# 如果主力合约切换,平掉旧合约
if g.future_code and g.future_code != new_future:
# 平旧合约空单
if context.portfolio.short_positions[g.future_code].total_amount > 0:
order_target(g.future_code, 0, side='short')
g.future_code = new_future
# 2. 计算组合Beta
beta = calculate_portfolio_beta(context)
# 3. 计算股票总市值
stock_value = context.portfolio.positions_value
# 4. 计算需要对冲的价值 (Hedge Value = Stock Value * Beta)
hedge_value = stock_value * beta
# 5. 获取期货当前价格和合约乘数
future_price = get_current_data()[g.future_code].last_price
multiplier = 300.0 # IF合约乘数为300
# 6. 计算所需合约张数 (四舍五入取整)
if future_price > 0:
required_contracts = int(round(hedge_value / (future_price * multiplier)))
else:
required_contracts = 0
# 7. 执行交易(调整空单仓位)
# 注意:order_target 的 side='short' 表示调整空单的目标数量
# 如果计算出的Beta为负或0,则不需要对冲(或者应该做多,这里简化为不持仓)
if required_contracts < 0:
required_contracts = 0
log.info("当前股票市值: %.2f, 组合Beta: %.4f, 需对冲价值: %.2f, 目标空单手数: %d" % (stock_value, beta, hedge_value, required_contracts))
order_target(g.future_code, required_contracts, side='short')
def print_beta(context):
beta = calculate_portfolio_beta(context)
record(Beta=beta)
Beta 计算 (calculate_portfolio_beta):
attribute_history 获取过去 60 天(g.days)的收盘价。pct_change() 计算日收益率。np.cov(stock_ret, benchmark_ret) 计算协方差矩阵。Beta = 协方差 / 基准方差。主力合约切换 (get_dominant_future):
get_dominant_future('IF') 动态获取当前的主力合约。对冲数量计算:
合约数 = (股票总市值 * Beta) / (期货价格 * 合约乘数)。下单函数 (order_target):
side='short'。order_target(security, amount, side='short') 的意思是将空单的持仓调整到 amount 手。如果当前空单少于目标,系统会自动开空;如果多于目标,系统会自动平空。available_cash = context.portfolio.total_value * 0.9 预留了 10% 的资金防止爆仓,实际操作中可能需要更多现金缓冲。Q: 为什么计算 Beta 时要用收益率而不是价格直接计算相关性?
A: 金融时间序列通常是非平稳的(价格有趋势),直接用价格计算协方差会导致伪回归。使用收益率可以将序列转化为平稳序列,统计结果才具有实际意义。
Q: 如果我的股票组合 Beta 是负的怎么办?
A: 如果 Beta 为负,意味着你的组合与大盘走势相反。理论上你应该做多期货来对冲风险。但在 A 股市场,长期来看 Beta 极少为负,通常是在 0.8 到 1.2 之间。代码中加入了 if required_contracts < 0: required_contracts = 0 的保护逻辑。
Q: 为什么使用 order_target 而不是 order?
A: order_target 是目标仓位管理,它会自动计算当前持仓与目标持仓的差额进行买卖。在对冲策略中,我们每天计算的是“应该持有多少手空单”,而不是“今天新增多少手”,所以用 order_target 最方便且不易出错。