问题描述
解决方案
这是一个非常经典的**套期保值(Hedging)**策略需求。
在PTrade中实现这一策略,核心逻辑是:持有股票多头(买入)的同时,在期货市场建立空头(卖出)头寸。当市场下跌时,股票端的亏损可以通过期货端的盈利来抵消,从而锁定组合价值。
以下是具体的实现思路和完整的策略代码。
1. 策略设计思路
要实现自动化对冲,我们需要解决三个问题:
- 何时对冲(择时):
- 为了演示,我们使用一个简单的均线策略作为触发条件:当沪深300指数的短期均线(如5日)下穿长期均线(如20日)形成“死叉”时,认为市场有下跌风险,开启对冲(卖空期货)。
- 当短期均线上穿长期均线形成“金叉”时,认为风险解除,平掉期货空单。
- 用什么对冲(标的):
- 如果你的股票池是大盘股(如银行、白酒),通常使用 IF(沪深300股指期货)。
- 如果是中小盘股,通常使用 IC(中证500股指期货) 或 IM(中证1000股指期货)。
- 本示例默认使用 IF 合约。
- 对冲多少(仓位计算):
- 最简单的市值对冲:
期货空单总价值 ≈ 股票持仓总价值。 - 计算公式:
需做空的手数 = 股票总市值 / (期货当前价格 * 合约乘数)。
- 最简单的市值对冲:
2. PTrade 策略代码实现
请将以下代码复制到 PTrade 的策略编辑区域。
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 1. 设定我们要持有的股票篮子(这里仅作示例,实盘请换成你实际持有的股票)
g.stock_list = ['600519.SS', '600036.SS', '601318.SS']
set_universe(g.stock_list)
# 2. 设定对冲使用的股指期货合约
# 注意:期货合约代码会随时间变化(如 IF2309, IF2312),实盘需定期更换或写逻辑自动获取主力合约
# 这里以 IF2312.CCFX 为例
g.future_code = 'IF2312.CCFX'
# 3. 设定基准指数用于判断趋势(这里用沪深300指数)
g.benchmark_index = '000300.SS'
# 4. 设定均线参数
g.short_window = 5
g.long_window = 20
# 5. 标记是否处于对冲状态
g.is_hedging = False
# 6. 开启交易(每天运行一次,也可以改为分钟级)
# 这里的 '09:31' 是为了避开集合竞价,确保有开盘价
run_daily(context, trade_logic, time='09:31')
def trade_logic(context):
"""
交易主逻辑
"""
# --- 第一步:管理股票持仓 (模拟持有股票) ---
# 如果没有股票持仓,先买入股票(模拟用户已有的持仓)
# 实盘中如果已有持仓,这一步可以忽略或根据实际情况调整
for stock in g.stock_list:
if get_position(stock).amount == 0:
# 简单的等权重买入
cash_per_stock = context.portfolio.cash / len(g.stock_list)
order_value(stock, cash_per_stock)
# --- 第二步:判断市场趋势 ---
# 获取基准指数的历史收盘价
hist = get_history(g.long_window + 2, '1d', 'close', g.benchmark_index)
if len(hist) < g.long_window:
return
close_prices = hist['close'].values
# 计算均线
ma_short = close_prices[-g.short_window:].mean()
ma_long = close_prices[-g.long_window:].mean()
# 获取当前股票持仓的总市值
stock_market_value = get_stock_market_value(context)
# --- 第三步:执行对冲逻辑 ---
# 情况A:死叉(短期 < 长期)且尚未对冲 -> 开启对冲(卖空期货)
if ma_short < ma_long and not g.is_hedging:
log.info("市场趋势转弱 (死叉),开始执行对冲...")
# 计算需要做空的手数
amount = calculate_hedge_amount(context, stock_market_value)
if amount > 0:
# 卖出开仓 (Sell Open)
buy_open_id = sell_open(g.future_code, amount)
if buy_open_id:
g.is_hedging = True
log.info("已卖空期货合约: %s, 数量: %d 手" % (g.future_code, amount))
# 情况B:金叉(短期 > 长期)且正在对冲 -> 结束对冲(平掉期货)
elif ma_short > ma_long and g.is_hedging:
log.info("市场趋势转强 (金叉),结束对冲,平仓期货...")
# 获取当前期货持仓
future_position = get_position(g.future_code)
# 注意:期货分多头(long)和空头(short),对冲持有的是空头
short_amt = future_position.short_amount
if short_amt > 0:
# 买入平仓 (Buy Close)
# 注意:上期所区分平今平昨,中金所(IF/IC/IM)通常不严格区分,但建议使用 close_today=False 兼容
buy_close(g.future_code, short_amt)
log.info("已平仓期货合约: %s, 数量: %d 手" % (g.future_code, short_amt))
g.is_hedging = False
# 情况C:保持对冲状态,但股票市值变动较大,需要动态调整期货仓位(可选高级功能)
# 此处为了代码简洁,暂不展示动态调仓逻辑
def get_stock_market_value(context):
"""
计算当前账户中所有股票持仓的总市值
"""
total_value = 0.0
positions = context.portfolio.positions
for sid, pos in positions.items():
# 过滤掉期货持仓,只计算股票
# PTrade中股票代码通常以 .SS 或 .SZ 结尾,或者通过 business_type 判断
if pos.business_type == 'stock':
total_value += pos.last_sale_price * pos.amount
return total_value
def calculate_hedge_amount(context, stock_value):
"""
计算需要对冲的期货手数
公式:手数 = 股票总市值 / (期货价格 * 合约乘数)
"""
# 获取期货合约信息(主要是合约乘数)
future_info = get_instruments(g.future_code)
if future_info is None:
log.error("无法获取合约信息: %s" % g.future_code)
return 0
multiplier = future_info.contract_multiplier # 合约乘数,IF通常是300
# 获取期货当前价格
# 注意:回测和交易获取价格方式略有不同,这里使用 get_snapshot 兼容性较好,或者用 get_price
# 在回测中 get_snapshot 可能不可用,改用 get_history 取最新一分钟收盘价
if is_trade():
snap = get_snapshot(g.future_code)
current_price = snap[g.future_code]['last_px']
else:
# 回测环境
h = get_history(1, '1m', 'close', g.future_code)
if len(h) > 0:
current_price = h['close'].values[-1]
else:
return 0
if current_price == 0:
return 0
# 单张合约价值
contract_value = current_price * multiplier
# 计算手数(四舍五入或向下取整)
# 这里使用简单的市值对冲(Beta=1假设)
amount = int(stock_value / contract_value)
return amount
def handle_data(context, data):
"""
盘中运行函数,本策略主要逻辑在 run_daily 中执行,此处留空
"""
pass
3. 关键操作说明
-
合约选择 (
g.future_code):- 代码中写的是
IF2312.CCFX。在实际使用时,你必须将其修改为当前的主力合约(例如IF2406.CCFX)。 - 如果你的股票主要是中小盘,建议将合约改为 IC(中证500)或 IM(中证1000)开头的合约代码。
- 代码中写的是
-
保证金(Margin):
- 做空期货需要缴纳保证金。请确保你的账户中有足够的可用资金(Cash)。如果满仓持有股票,是没有资金开期货空单的。通常需要预留 15%-20% 的现金用于期货保证金。
-
合约乘数:
- IF (沪深300): 1点 = 300元。
- IC (中证500): 1点 = 200元。
- IM (中证1000): 1点 = 200元。
- 代码中的
get_instruments会自动获取这个乘数,无需手动硬编码。
-
移仓换月:
- 期货合约有到期日(通常是每月第三个周五)。如果你的对冲周期很长,需要在合约到期前,平掉旧合约(
buy_close),开新合约(sell_open)。上述代码未包含自动移仓逻辑,需要人工关注到期日。
- 期货合约有到期日(通常是每月第三个周五)。如果你的对冲周期很长,需要在合约到期前,平掉旧合约(
4. 风险提示
- 基差风险:期货价格和现货指数价格不完全一致(通常贴水),这会导致对冲效果出现偏差。
- Beta系数:上述代码假设 Beta=1(即股票组合波动与指数完全一致)。如果你的股票组合波动比指数大很多,可能需要增加期货手数;反之则减少。
- 爆仓风险:如果市场大幅上涨,股票端赚钱,但期货端会亏钱。虽然总体盈亏平衡,但期货端需要追加保证金。如果现金不足,期货会被强平。务必保留充足现金。
Q&A
Q: 如何查看我的股票组合和哪个指数相关性最高?
A: 可以计算你的持仓组合每日收益率与 IF(沪深300)、IC(中证500) 的相关系数。通常大盘蓝筹选 IF,中小盘成长选 IC,微盘股选 IM。
Q: 为什么代码里用 sell_open 而不是 order?
A: 在 PTrade 的期货交易中,sell_open(卖出开仓)是专门用于建立空头头寸的函数,语义更清晰。对应的平仓函数是 buy_close(买入平仓)。