🚀 AI 一键生成 ptrade 策略代码

如何用 PTrade 编写一个股指期货对冲策略来降低股票持仓风险?

ptrade | 发布时间: 2025/12/8
以下内容由 EasyQuant 生成。

问题描述

我持有一篮子股票,想在市场可能下跌时用股指期货做个对冲,减少损失,具体应该怎么操作?

解决方案

这是一个非常经典的**套期保值(Hedging)**策略需求。

在PTrade中实现这一策略,核心逻辑是:持有股票多头(买入)的同时,在期货市场建立空头(卖出)头寸。当市场下跌时,股票端的亏损可以通过期货端的盈利来抵消,从而锁定组合价值。

以下是具体的实现思路和完整的策略代码。

1. 策略设计思路

要实现自动化对冲,我们需要解决三个问题:

  1. 何时对冲(择时)
    • 为了演示,我们使用一个简单的均线策略作为触发条件:当沪深300指数的短期均线(如5日)下穿长期均线(如20日)形成“死叉”时,认为市场有下跌风险,开启对冲(卖空期货)。
    • 当短期均线上穿长期均线形成“金叉”时,认为风险解除,平掉期货空单。
  2. 用什么对冲(标的)
    • 如果你的股票池是大盘股(如银行、白酒),通常使用 IF(沪深300股指期货)
    • 如果是中小盘股,通常使用 IC(中证500股指期货)IM(中证1000股指期货)
    • 本示例默认使用 IF 合约。
  3. 对冲多少(仓位计算)
    • 最简单的市值对冲期货空单总价值 ≈ 股票持仓总价值
    • 计算公式:需做空的手数 = 股票总市值 / (期货当前价格 * 合约乘数)

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. 关键操作说明

  1. 合约选择 (g.future_code)

    • 代码中写的是 IF2312.CCFX。在实际使用时,你必须将其修改为当前的主力合约(例如 IF2406.CCFX)。
    • 如果你的股票主要是中小盘,建议将合约改为 IC(中证500)或 IM(中证1000)开头的合约代码。
  2. 保证金(Margin)

    • 做空期货需要缴纳保证金。请确保你的账户中有足够的可用资金(Cash)。如果满仓持有股票,是没有资金开期货空单的。通常需要预留 15%-20% 的现金用于期货保证金。
  3. 合约乘数

    • IF (沪深300): 1点 = 300元。
    • IC (中证500): 1点 = 200元。
    • IM (中证1000): 1点 = 200元。
    • 代码中的 get_instruments 会自动获取这个乘数,无需手动硬编码。
  4. 移仓换月

    • 期货合约有到期日(通常是每月第三个周五)。如果你的对冲周期很长,需要在合约到期前,平掉旧合约(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(买入平仓)。