🚀 AI 一键生成 ptrade 策略代码

PTrade 高级策略:如何构建市场中性 Alpha 对冲策略?

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

问题描述

如何编写一个市场中性的 Alpha 对冲策略,使用股指期货对冲股票组合的 Beta 风险?

解决方案

这是一个基于 PTrade 平台的市场中性 Alpha 对冲策略的设计与实现。

策略核心逻辑

  1. Alpha 端(多头股票组合)
    • 选股范围:沪深300成分股。
    • 选股因子:低市盈率(PE)。我们假设低估值的股票在长期能跑赢指数。
    • 权重分配:等权重买入排名靠前的股票(例如前 30 只)。
  2. Beta 端(空头期货对冲)
    • 对冲标的:沪深300股指期货(IF)。
    • 对冲比例:等市值对冲(Dollar Neutral)。即 空头期货合约价值 ≈ 多头股票持仓市值
    • 目标:剥离市场波动(Beta),仅获取选股带来的超额收益(Alpha)。
  3. 资金管理
    • 预留约 20%-30% 的资金作为期货保证金和应对价格波动的缓冲,其余资金用于买入股票。

策略代码实现

import numpy as np
import math
import datetime

def initialize(context):
    """
    初始化函数,设置策略参数
    """
    # 设定基准为沪深300
    set_benchmark('000300.SS')
    
    # 开启真实市价成交模式(回测中模拟滑点)
    set_slippage(slippage=0.002)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')
    
    # 策略参数设置
    g.stock_index = '000300.SS'      # 股票基准指数
    g.future_code_prefix = 'IF'      # 期货品种代码前缀 (IF: 沪深300, IC: 中证500)
    g.future_exchange = '.CCFX'      # 期货交易所后缀
    g.stock_count = 30               # 持仓股票数量
    g.margin_reserve_ratio = 0.2     # 预留现金比例(用于期货保证金)
    
    # 全局变量
    g.target_stocks = []             # 目标股票列表
    g.current_future = None          # 当前持有的期货合约
    
    # 设置定时任务:每周第一个交易日进行调仓
    # 注意:这里为了演示方便设为每周,实际Alpha策略可能为月度调仓
    run_daily(context, rebalance_strategy, time='10:00')

def before_trading_start(context, data):
    """
    盘前处理:更新股票池,确定当月期货合约
    """
    # 1. 获取当月主力合约代码 (简化的合约推算逻辑)
    # 实际交易中建议使用主力合约切换算法,这里简化为交易当月合约
    current_dt = context.blotter.current_dt
    year_str = str(current_dt.year)[2:] # 取年份后两位
    month_str = "{:02d}".format(current_dt.month)
    
    # 简单的合约代码构建:IF + YYMM + .CCFX
    # 注意:临近交割日(每月第三个周五)需要处理移仓换月,本示例简化处理
    g.current_future = g.future_code_prefix + year_str + month_str + g.future_exchange

def rebalance_strategy(context):
    """
    调仓主逻辑:选股 -> 买卖股票 -> 计算对冲市值 -> 调整期货空单
    """
    log.info("开始执行调仓...")
    
    # --- 第一步:Alpha选股 (低PE策略) ---
    # 获取沪深300成分股
    index_stocks = get_index_stocks(g.stock_index)
    
    # 过滤停牌和ST股 (简单过滤)
    # 注意:get_stock_status 在回测中可能需要注意未来函数,这里使用简单逻辑
    trade_stocks = []
    for stock in index_stocks:
        # 过滤掉停牌的股票(这里假设无法获取行情的即为停牌或未上市)
        # 实际生产中建议使用 get_stock_status 接口
        trade_stocks.append(stock)
    
    # 获取基本面数据:PE (市盈率)
    # q = query(valuation.code, valuation.pe_ratio).filter(valuation.code.in_(trade_stocks)).order_by(valuation.pe_ratio.asc()).limit(g.stock_count)
    # PTrade API 使用 get_fundamentals
    df = get_fundamentals(trade_stocks, 'valuation', ['pe_ttm'], date=None, count=1)
    
    if df is None or len(df) == 0:
        log.warning("未获取到基本面数据,跳过本次调仓")
        return

    # 排序并取前 N 只股票 (低PE)
    # 注意:PTrade返回的df索引通常是股票代码,或者需要根据列排序
    # 假设返回的是DataFrame,列为 pe_ttm
    # 过滤掉PE为负或异常值的
    df = df[df['pe_ttm'] > 0]
    df = df.sort_values(by='pe_ttm', ascending=True)
    g.target_stocks = df.index[:g.stock_count].tolist()
    
    log.info("目标持仓股票数量: %s" % len(g.target_stocks))

    # --- 第二步:执行股票交易 ---
    # 1. 卖出不在目标列表中的股票
    positions = get_positions()
    for security in positions:
        # 仅处理股票持仓
        if positions[security].business_type == 'stock' and positions[security].amount > 0:
            if security not in g.target_stocks:
                order_target_value(security, 0)
    
    # 2. 买入目标股票
    # 计算股票账户可用资金 (总资产 * (1 - 预留比例))
    total_value = context.portfolio.portfolio_value
    target_stock_value = total_value * (1 - g.margin_reserve_ratio)
    
    if len(g.target_stocks) > 0:
        value_per_stock = target_stock_value / len(g.target_stocks)
        for security in g.target_stocks:
            order_target_value(security, value_per_stock)
    
    # --- 第三步:执行期货对冲 (Beta Hedging) ---
    adjust_futures_position(context)

def adjust_futures_position(context):
    """
    调整期货空单仓位以匹配股票市值
    """
    # 1. 计算当前股票持仓总市值
    stock_market_value = 0.0
    positions = get_positions()
    for security in positions:
        pos = positions[security]
        if pos.business_type == 'stock':
            stock_market_value += pos.last_sale_price * pos.amount
            
    log.info("当前股票持仓总市值: %.2f" % stock_market_value)
    
    if stock_market_value <= 0:
        return

    # 2. 获取期货合约信息
    future_code = g.current_future
    
    # 获取期货最新价格
    # 注意:get_price 返回的是 DataFrame 或 Panel
    price_data = get_price(future_code, count=1, frequency='1m', fields=['close'])
    if price_data is None or len(price_data) == 0:
        log.warning("无法获取期货价格: %s" % future_code)
        return
        
    # 兼容不同版本的返回值,获取最新收盘价
    if isinstance(price_data, dict):
         # 如果是字典
         current_future_price = price_data['close'][-1]
    else:
         # 如果是DataFrame
         current_future_price = price_data['close'].iloc[-1]

    # 获取合约乘数 (沪深300 IF 合约乘数为 300)
    # 可以通过 get_instruments 获取,这里为了简化直接赋值,IF/IC/IH 乘数不同需注意
    contract_multiplier = 300.0 
    if 'IC' in future_code:
        contract_multiplier = 200.0
    elif 'IM' in future_code:
        contract_multiplier = 200.0
        
    # 3. 计算需持有的空单合约数量
    # 公式:空单价值 = 股票市值
    # 合约数量 = 股票市值 / (期货价格 * 合约乘数)
    # 向上取整或四舍五入,这里采用四舍五入
    target_contracts = int(round(stock_market_value / (current_future_price * contract_multiplier)))
    
    log.info("目标对冲合约: %s, 目标空单数量: %d" % (future_code, target_contracts))
    
    # 4. 执行期货交易
    # 获取当前期货持仓
    current_short_pos = 0
    pos = get_position(future_code)
    if pos:
        # PTrade中期货持仓方向:sell_amount 或 short_amount
        current_short_pos = pos.short_amount
    
    diff = target_contracts - current_short_pos
    
    if diff > 0:
        # 需要增加空单 (卖出开仓)
        log.info("增加空单: %d 手" % diff)
        sell_open(future_code, amount=diff)
    elif diff < 0:
        # 需要减少空单 (买入平仓)
        # 注意:平仓时优先平今还是平昨取决于交易所规则,这里简化为直接平仓
        close_amount = abs(diff)
        log.info("减少空单: %d 手" % close_amount)
        buy_close(future_code, amount=close_amount)
    else:
        log.info("期货仓位无需调整")

def handle_data(context, data):
    """
    盘中运行函数
    """
    # 可以在这里添加止损或风控逻辑
    pass

代码关键点解析

  1. 选股逻辑 (rebalance_strategy)

    • 使用 get_fundamentals 获取市盈率(pe_ttm)。
    • 选取 PE 最低的 30 只股票构建组合。这是一种经典的价值因子 Alpha。
    • 使用 order_target_value 确保每只股票等权重配置。
  2. 对冲逻辑 (adjust_futures_position)

    • 计算股票总市值:遍历持仓,累加所有类型为 'stock' 的持仓市值。
    • 计算期货合约数合约数量 = 股票总市值 / (期货价格 * 合约乘数)
    • 完全对冲:这里采用的是 1:1 的市值对冲(Beta 默认为 1)。如果你的股票组合 Beta 偏离 1 较大,可以在计算 target_contracts 时乘以组合的 Beta 值。
  3. 期货交易指令

    • sell_open(contract, amount): 卖出开仓(建立空单)。
    • buy_close(contract, amount): 买入平仓(减少空单)。
    • 注意:PTrade 的期货接口与股票不同,需要明确开平仓方向。
  4. 资金分配

    • g.margin_reserve_ratio = 0.2:预留 20% 资金。这是因为期货交易需要保证金(通常 10%-15%),且股票满仓波动可能导致可用资金不足。预留现金可以防止因期货亏损(虽然股票端会盈利)导致的保证金不足强平风险。

进阶优化建议

  1. 移仓换月:上述代码简单地使用了当月合约。在实盘中,必须在交割日(每月第三个周五)之前,将持仓从当月合约平仓,并开仓下月合约。
  2. Beta 动态调整:低估值股票的 Beta 值通常小于 1。如果按 1:1 对冲,可能会导致“过度对冲”,即市场上涨时,期货亏损大于股票盈利。建议计算组合过去 N 天的 Beta 值来调整对冲比例。
  3. 风格因子控制:单纯低 PE 可能会导致行业集中(如全买了银行股)。可以使用多因子模型或行业中性化处理来分散风险。

Q&A

Q: 为什么使用 get_fundamentals 而不是 get_history 来选股?
A: get_fundamentals 专门用于获取财务数据(如 PE、PB、ROE 等),而 get_history 主要用于获取量价数据(开盘价、收盘价等)。Alpha 策略通常基于基本面因子,因此需要财务数据。

Q: 代码中的 contract_multiplier = 300.0 是什么意思?
A: 这是沪深300股指期货(IF)的合约乘数。一张 IF 合约的价值 = 指数点位 * 300元。如果是中证500(IC)或中证1000(IM),乘数则是 200元。

Q: 如何处理期货的保证金不足问题?
A: 策略中设置了 g.margin_reserve_ratio。如果市场大幅上涨,期货空单会亏损(需要追加保证金),虽然股票多头会盈利,但股票盈利是未结盈亏,不能直接用于期货保证金。因此必须保留一部分现金(Cash)来应对期货端的浮动亏损。