问题描述
解决方案
这是一个关于如何在PTrade中编写**期权跨式套利(Long Straddle)**策略的详细指南。
策略原理:买入跨式套利 (Long Straddle)
跨式套利是一种波动率策略,适用于你预期标的资产(如50ETF)会有大幅波动,但无法确定方向(大涨或大跌)的场景。
- 操作方式:同时买入一张平值(ATM)看涨期权(Call)和一张平值(ATM)看跌期权(Put)。两者的标的资产、行权价、到期日均相同。
- 获利条件:标的资产价格大幅偏离行权价(无论上涨还是下跌),且涨跌幅超过权利金成本。
- 风险:如果标的资产价格横盘不动,时间价值(Theta)流逝会导致亏损。
策略实现步骤
在PTrade中实现该策略,主要包含以下逻辑:
- 获取标的价格:获取标的ETF(如510050.SS)的当前价格。
- 筛选合约:
- 获取该标的的所有期权合约。
- 筛选出当月或次月到期的合约。
- 找到行权价最接近当前标的价格的合约(平值合约)。
- 下单交易:同时买入Call和Put。
- 止盈止损:设定获利目标或最大亏损(时间止损)。
PTrade 策略代码
以下是一个完整的动态跨式套利策略代码。代码会自动寻找平值期权进行开仓。
import numpy as np
def initialize(context):
"""
策略初始化函数
"""
# 1. 设置标的资产:上证50ETF
g.underlying = '510050.SS'
# 2. 设置策略参数
g.target_month_index = 0 # 0表示当月,1表示次月
g.open_position = False # 开仓标记
g.call_contract = None # 记录持仓的Call代码
g.put_contract = None # 记录持仓的Put代码
# 3. 设定基准
set_universe([g.underlying])
set_benchmark(g.underlying)
# 4. 设置手续费(根据券商实际情况调整,期权通常按张收费)
# 注意:PTrade回测中期权手续费设置需参考具体文档,此处仅为示例
# set_commission(...)
# 5. 每天开盘后运行策略
run_daily(context, trade_logic, time='10:00')
def get_atm_options(context, underlying):
"""
获取平值(ATM)的Call和Put合约
"""
# 获取标的当前价格
snapshot = get_snapshot(underlying)
if not snapshot:
return None, None
current_price = snapshot[underlying]['last_px']
# 获取该标的的所有期权合约
# 注意:get_option_contracts 是PTrade常用获取期权链的API
# 如果您的环境不支持,需确认是否有权限或使用 get_instruments 配合筛选
try:
options = get_option_contracts(underlying)
except:
log.error("无法获取期权合约列表,请检查权限或API版本")
return None, None
if not options:
return None, None
# 获取所有合约的详细信息
instruments = [get_instruments(x) for x in options]
# 1. 筛选出认购(Call)和认沽(Put)
# 2. 筛选出目标到期月份的合约
# 这里简化逻辑:先找到所有到期日,排序,取最近的一个到期日(当月)
all_dates = sorted(list(set([i.delivery_date for i in instruments])))
# 过滤掉已经过期的(如果回测时间在交割日当天,需小心处理)
valid_dates = [d for d in all_dates if d > context.blotter.current_dt.strftime('%Y%m%d')]
if len(valid_dates) <= g.target_month_index:
return None, None
target_date = valid_dates[g.target_month_index]
# 筛选出目标到期日的合约
target_options = [i for i in instruments if i.delivery_date == target_date]
# 寻找平值合约:行权价与当前价格差值最小的
# 创建一个列表:(合约代码, 行权价, 类型)
# option_type: 'C' for Call, 'P' for Put
candidates = []
for opt in target_options:
# PTrade中 get_instruments 返回的对象通常包含 option_type 或类似字段
# 假设 contract_name 包含 "购" 或 "沽" 或者通过 option_type 判断
# 这里使用 PTrade 标准字段判断
otype = ''
if hasattr(opt, 'option_type'):
otype = opt.option_type
# 如果没有option_type,尝试从名称判断 (备用方案)
if not otype:
if '购' in opt.contract_name: otype = 'C'
elif '沽' in opt.contract_name: otype = 'P'
# 获取行权价 (exercise_price 或 strike_price)
strike = 0.0
if hasattr(opt, 'exercise_price'):
strike = opt.exercise_price
elif hasattr(opt, 'strike_price'): # 部分版本字段名可能不同
strike = opt.strike_price
candidates.append({
'code': opt.contract_code, # 或 trade_code / symbol
'strike': strike,
'type': otype
})
if not candidates:
return None, None
# 找到最接近当前价格的行权价
strikes = sorted(list(set([x['strike'] for x in candidates])))
atm_strike = min(strikes, key=lambda x: abs(x - current_price))
# 获取该行权价对应的Call和Put
call_code = None
put_code = None
for c in candidates:
if c['strike'] == atm_strike:
if c['type'] == 'C':
call_code = c['code']
elif c['type'] == 'P':
put_code = c['code']
return call_code, put_code
def trade_logic(context):
"""
交易主逻辑
"""
# 检查是否已有持仓
has_position = False
positions = context.portfolio.positions
# 简单的持仓检查
if g.call_contract and g.call_contract in positions and positions[g.call_contract].amount > 0:
has_position = True
# --- 开仓逻辑 ---
if not has_position:
log.info("当前无持仓,尝试构建跨式组合...")
# 获取平值合约
call_code, put_code = get_atm_options(context, g.underlying)
if call_code and put_code:
log.info("选定合约 - Call: %s, Put: %s" % (call_code, put_code))
# 买入一张 Call
order(call_code, 1)
# 买入一张 Put
order(put_code, 1)
# 记录当前持仓
g.call_contract = call_code
g.put_contract = put_code
g.open_position = True
else:
log.info("未找到合适的期权合约")
# --- 平仓逻辑 (示例) ---
else:
# 这里演示简单的止盈止损逻辑
# 实际策略中,可以基于组合的Delta、Gamma或总盈亏比例来平仓
# 获取持仓对象
pos_call = get_position(g.call_contract)
pos_put = get_position(g.put_contract)
# 计算总浮动盈亏
total_pnl = pos_call.pnl + pos_put.pnl
# 假设初始成本 (粗略计算,实际应记录开仓均价 * 合约乘数)
# 这里仅做逻辑演示:如果总盈利超过一定金额,或者亏损超过一定金额,则平仓
# 止盈:假设赚了500元
if total_pnl > 500:
log.info("触发止盈,当前盈利: %s" % total_pnl)
order_target(g.call_contract, 0)
order_target(g.put_contract, 0)
g.call_contract = None
g.put_contract = None
# 止损:假设亏了300元
elif total_pnl < -300:
log.info("触发止损,当前亏损: %s" % total_pnl)
order_target(g.call_contract, 0)
order_target(g.put_contract, 0)
g.call_contract = None
g.put_contract = None
# 到期日临近强制平仓 (防止进入交割)
# 获取合约信息判断到期日
# ... (此处省略具体日期判断代码,建议在get_instruments中获取delivery_date并判断)
def handle_data(context, data):
"""
盘中运行函数(分钟级回测会用到)
"""
pass
代码关键点解析
-
get_option_contracts(underlying):- 这是PTrade中获取某标的下所有期权合约列表的关键函数。
- 注意:如果您的PTrade版本较旧或权限受限,可能无法调用此接口。如果报错,请咨询券商开通期权量化权限。
-
寻找平值 (ATM) 合约:
- 逻辑是:
abs(行权价 - 标的现价)最小的合约。 - 代码中通过
get_instruments获取合约详情(包括行权价exercise_price和到期日delivery_date)。
- 逻辑是:
-
下单
order:- 期权交易单位通常为“张”。
order(code, 1)表示买入1张。 - 买入跨式策略要求同时持有Call和Put,因此代码中是连续下单。
- 期权交易单位通常为“张”。
-
风险管理:
- 期权买方最大的敌人是时间价值衰减。如果标的横盘,Call和Put都会亏损。
- 代码中加入了一个简单的基于盈亏金额(PnL)的平仓逻辑。在实盘中,建议使用百分比或结合隐含波动率(IV)的变化来做决策。
常见问题 Q&A
Q: 为什么回测时没有成交?
- A:
- 检查是否开通了期权回测权限。
- 检查资金是否足够(期权权利金虽然便宜,但要注意保证金账户设置)。
- 检查
get_snapshot是否在回测模式下返回了有效数据(部分回测模式不支持快照,需改用get_price)。
Q: 如何避免行权风险?
- A: 在策略中加入日期判断逻辑,在
delivery_date(行权日/到期日)的前1-2天强制平仓。不要持有至到期,除非你打算进行实物交割。
Q: 标的可以是股票吗?
- A: 目前国内场内期权标的主要为ETF(50ETF, 300ETF, 500ETF等)和部分商品期货。个股期权通常为场外期权(OTC),API调用方式可能不同。