🚀 AI 一键生成 ptrade 策略代码

PTrade 期货交易:如何基于合约乘数与保证金率计算账户风险敞口?

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

问题描述

如何计算当前期货持仓占用的保证金和风险敞口?

解决方案

在PTrade中,计算期货持仓占用的保证金和风险敞口(持仓市值),主要依赖于 context.portfolio.positions 获取持仓信息,并结合 contract_multiplier(合约乘数)和 margin_rate(保证金比例)进行计算。

以下是具体的计算逻辑和代码实现。

1. 计算公式

  • 合约价值 (Notional Value) = 价格 $\times$ 持仓数量 $\times$ 合约乘数
  • 占用保证金 (Occupied Margin) = 合约价值 $\times$ 保证金比例
  • 净风险敞口 (Net Exposure) = (多头持仓数量 - 空头持仓数量) $\times$ 价格 $\times$ 合约乘数

2. 核心API说明

我们需要用到 Position 对象中的以下属性(通过 get_positioncontext.portfolio.positions 获取):

  • long_amount: 多头持仓数量
  • short_amount: 空头持仓数量
  • contract_multiplier: 合约乘数(例如沪深300股指期货为300)
  • margin_rate: 保证金比例(例如0.10表示10%)

3. 策略代码实现

以下是一个完整的策略示例,包含一个自定义函数 calculate_futures_risk,用于遍历当前账户的所有期货持仓并计算总保证金和敞口。

def initialize(context):
    # 初始化举例:设置关注的期货品种
    # 注意:实际回测或交易时请确保合约代码在有效期内
    g.security = ['IF2309.CCFX', 'RB2310.XSGE'] 
    set_universe(g.security)
    
    # 设置期货保证金比例(仅回测有效,实盘以柜台为准)
    # 假设IF保证金10%,RB保证金8%
    set_margin_rate('IF', 0.10)
    set_margin_rate('RB', 0.08)
    
    # 设置手续费(可选)
    set_future_commission('IF', 0.000023)
    set_future_commission('RB', 0.000045)

def handle_data(context, data):
    # 示例:简单的开仓逻辑,为了产生持仓以便计算
    if not context.portfolio.positions:
        # 买入开仓 IF
        buy_open(g.security[0], 2)
        # 卖出开仓 RB
        sell_open(g.security[1], 10)
    
    # --- 核心部分:计算保证金和敞口 ---
    calculate_futures_risk(context, data)

def calculate_futures_risk(context, data):
    """
    计算并打印当前期货账户的保证金占用和风险敞口
    """
    total_margin_occupied = 0.0
    total_net_exposure = 0.0
    
    log.info("====== 开始计算期货持仓风险 ======")
    
    # 遍历当前所有持仓
    # context.portfolio.positions 是一个字典,key是标的代码,value是Position对象
    for security in context.portfolio.positions:
        pos = context.portfolio.positions[security]
        
        # 过滤掉非期货持仓(如果有股票混杂)
        # 注意:PTrade文档中Position对象有business_type字段,但通常通过代码后缀或合约乘数判断也可
        if pos.contract_multiplier is None or pos.contract_multiplier == 0:
            continue
            
        # 获取当前价格
        # 在handle_data中,使用 data[security].price 获取最新价
        # 如果是盘后或结算,可能需要用 pos.last_sale_price
        current_price = data[security].price
        
        # 如果取不到价格(例如停牌),暂跳过或使用昨收
        if current_price != current_price: # check NaN
            log.info("无法获取 %s 的当前价格,跳过计算" % security)
            continue

        multiplier = pos.contract_multiplier
        margin_rate = pos.margin_rate
        
        # 1. 计算多头端
        long_qty = pos.long_amount
        long_value = long_qty * current_price * multiplier
        long_margin = long_value * margin_rate
        
        # 2. 计算空头端
        short_qty = pos.short_amount
        short_value = short_qty * current_price * multiplier
        short_margin = short_value * margin_rate
        
        # 3. 单个合约汇总
        instrument_margin = long_margin + short_margin
        instrument_net_exposure = long_value - short_value
        
        total_margin_occupied += instrument_margin
        total_net_exposure += instrument_net_exposure
        
        log.info("合约: %s, 多头: %d手, 空头: %d手, 现价: %.2f" % (
            security, long_qty, short_qty, current_price
        ))
        log.info("  -> 合约乘数: %d, 保证金率: %.2f" % (multiplier, margin_rate))
        log.info("  -> 占用保证金: %.2f, 净敞口市值: %.2f" % (instrument_margin, instrument_net_exposure))

    log.info("--------------------------------")
    log.info("账户总占用保证金: %.2f" % total_margin_occupied)
    log.info("账户总净风险敞口: %.2f" % total_net_exposure)
    
    # 可以在这里添加风控逻辑,例如:
    # if total_margin_occupied > context.portfolio.portfolio_value * 0.8:
    #     log.warning("警告:保证金占用过高!")
    
    log.info("====== 计算结束 ======")

4. 代码关键点解析

  1. 获取持仓对象:
    使用 context.portfolio.positions 遍历所有持仓。每个 pos 对象包含了计算所需的所有静态数据(合约乘数、保证金率)。

  2. 区分多空方向:
    期货持仓与股票不同,同一个合约可能同时存在多头持仓 (long_amount) 和空头持仓 (short_amount)。计算保证金时,通常是双边收取(多头保证金 + 空头保证金),计算净敞口时则是轧差(多头市值 - 空头市值)。

  3. 数据获取:

    • pos.contract_multiplier: 必须使用系统返回的乘数,不要硬编码(例如IC合约乘数是200,IF是300)。
    • pos.margin_rate: 回测中由 set_margin_rate 设定,实盘中由柜台返回。
    • data[security].price: 使用当前周期的最新价格来计算实时市值。
  4. Python版本兼容性:
    代码使用了 % 格式化字符串(如 "%.2f" % value),这是为了兼容 PTrade 可能使用的 Python 3.5 环境,避免使用 f-string。

Q&A

Q: 为什么计算出的保证金和账户显示的可用资金变动不完全一致?
A:

  1. 结算价 vs 最新价:交易所每日结算时使用“当日结算价”计算保证金,而盘中通常使用“最新成交价”或“昨结算价”估算。
  2. 单向大边优惠:部分期货公司或交易所在特定情况下(如锁仓)只收单边保证金(占用金额较大的一边),而上述代码是按双边累加计算的保守算法。
  3. 费率差异:回测中设置的保证金率可能与默认值不同,实盘中需确保获取到的 margin_rate 是准确的。

Q: 如何计算总杠杆率?
A: 总杠杆率 = (总持仓合约价值 / 账户总资产)。
在上述代码中,可以计算 (abs(long_value) + abs(short_value)) / context.portfolio.portfolio_value