问题描述
解决方案
在PTrade中,计算期货持仓占用的保证金和风险敞口(持仓市值),主要依赖于 context.portfolio.positions 获取持仓信息,并结合 contract_multiplier(合约乘数)和 margin_rate(保证金比例)进行计算。
以下是具体的计算逻辑和代码实现。
1. 计算公式
- 合约价值 (Notional Value) = 价格 $\times$ 持仓数量 $\times$ 合约乘数
- 占用保证金 (Occupied Margin) = 合约价值 $\times$ 保证金比例
- 净风险敞口 (Net Exposure) = (多头持仓数量 - 空头持仓数量) $\times$ 价格 $\times$ 合约乘数
2. 核心API说明
我们需要用到 Position 对象中的以下属性(通过 get_position 或 context.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. 代码关键点解析
-
获取持仓对象:
使用context.portfolio.positions遍历所有持仓。每个pos对象包含了计算所需的所有静态数据(合约乘数、保证金率)。 -
区分多空方向:
期货持仓与股票不同,同一个合约可能同时存在多头持仓 (long_amount) 和空头持仓 (short_amount)。计算保证金时,通常是双边收取(多头保证金 + 空头保证金),计算净敞口时则是轧差(多头市值 - 空头市值)。 -
数据获取:
pos.contract_multiplier: 必须使用系统返回的乘数,不要硬编码(例如IC合约乘数是200,IF是300)。pos.margin_rate: 回测中由set_margin_rate设定,实盘中由柜台返回。data[security].price: 使用当前周期的最新价格来计算实时市值。
-
Python版本兼容性:
代码使用了%格式化字符串(如"%.2f" % value),这是为了兼容 PTrade 可能使用的 Python 3.5 环境,避免使用 f-string。
Q&A
Q: 为什么计算出的保证金和账户显示的可用资金变动不完全一致?
A:
- 结算价 vs 最新价:交易所每日结算时使用“当日结算价”计算保证金,而盘中通常使用“最新成交价”或“昨结算价”估算。
- 单向大边优惠:部分期货公司或交易所在特定情况下(如锁仓)只收单边保证金(占用金额较大的一边),而上述代码是按双边累加计算的保守算法。
- 费率差异:回测中设置的保证金率可能与默认值不同,实盘中需确保获取到的
margin_rate是准确的。
Q: 如何计算总杠杆率?
A: 总杠杆率 = (总持仓合约价值 / 账户总资产)。
在上述代码中,可以计算 (abs(long_value) + abs(short_value)) / context.portfolio.portfolio_value。