3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
编写一个期权做市商(Market Maker)策略是一个复杂的任务,涉及理论定价、库存管理、订单管理和风险控制。在QMT中,我们可以利用其内置的期权定价函数(BSM模型)和交易接口来实现一个基础的自动双边报价策略。
ContextInfo.bsm_price(...): 计算期权理论价格。ContextInfo.get_market_data_ex(...): 获取标的资产实时行情。ContextInfo.get_trade_detail_data(...): 获取当前挂单状态。cancel(...): 撤单。passorder(...): 下单。# -*- coding: gbk -*-
import time
import datetime
import math
def init(ContextInfo):
# ================= 策略参数设置 =================
# 资金账号 (请修改为您的实际期权账号)
ContextInfo.accID = 'YOUR_OPTION_ACCOUNT_ID'
# 目标期权合约 (例如: 50ETF购x月x.xxx)
# 请确保该合约在您的行情列表中存在
ContextInfo.option_code = '1000xxxx.SHO'
# 标的资产代码 (例如: 50ETF)
ContextInfo.underlying_code = '510050.SH'
# 期权类型: 'C' (认购) 或 'P' (认沽)
ContextInfo.option_type = 'C'
# 行权价 (Strike Price)
ContextInfo.strike_price = 3.000
# 到期日 (格式: YYYYMMDD)
ContextInfo.expire_date = '20231227'
# 无风险利率 (例如 2.5%)
ContextInfo.risk_free_rate = 0.025
# 预设波动率 (IV),实际交易中通常需要根据市场反推或使用波动率曲面
ContextInfo.sigma = 0.20
# 报价单边价差 (Spread的一半)
# Bid = FairPrice - half_spread
# Ask = FairPrice + half_spread
ContextInfo.half_spread = 0.0010
# 单笔报单数量 (张)
ContextInfo.volume = 1
# 策略运行间隔 (秒)
ContextInfo.run_interval = 5
ContextInfo.last_run_time = 0
# 设置账号
ContextInfo.set_account(ContextInfo.accID)
print("期权做市策略初始化完成")
def handlebar(ContextInfo):
# 仅在实盘的最后一根K线(实时行情)运行
if not ContextInfo.is_last_bar():
return
# 控制运行频率
current_time = time.time()
if current_time - ContextInfo.last_run_time < ContextInfo.run_interval:
return
ContextInfo.last_run_time = current_time
# 1. 获取标的资产最新价格
# 获取标的最近一笔Tick数据
tick_data = ContextInfo.get_full_tick([ContextInfo.underlying_code])
if ContextInfo.underlying_code not in tick_data:
print(f"未获取到标的 {ContextInfo.underlying_code} 行情")
return
underlying_price = tick_data[ContextInfo.underlying_code]['lastPrice']
if underlying_price <= 0:
return
# 2. 计算剩余天数 (Days to Maturity)
try:
today = datetime.datetime.now()
exp_date = datetime.datetime.strptime(ContextInfo.expire_date, '%Y%m%d')
delta = exp_date - today
days_to_maturity = delta.days + (delta.seconds / 86400.0)
if days_to_maturity <= 0:
print("期权已到期或过期,停止报价")
return
except Exception as e:
print(f"日期计算错误: {e}")
return
# 3. 计算 BSM 理论价格 (Fair Price)
# bsm_price(optionType, objectPrices, strikePrice, riskFree, sigma, days, dividend)
# 假设分红率为 0
fair_price = ContextInfo.bsm_price(
ContextInfo.option_type,
underlying_price,
ContextInfo.strike_price,
ContextInfo.risk_free_rate,
ContextInfo.sigma,
days_to_maturity,
0
)
if math.isnan(fair_price) or fair_price <= 0:
print("理论价格计算异常")
return
# 4. 计算双边报价价格
# 价格需要符合最小变动单位 (假设为 0.0001,具体视合约而定)
bid_price = round(fair_price - ContextInfo.half_spread, 4)
ask_price = round(fair_price + ContextInfo.half_spread, 4)
print(f"标的价:{underlying_price}, 理论价:{fair_price:.4f}, 拟报买:{bid_price}, 拟报卖:{ask_price}")
# 5. 撤销旧订单 (Cancel Logic)
# 获取当前未成交委托
orders = get_trade_detail_data(ContextInfo.accID, 'STOCK_OPTION', 'ORDER')
for order in orders:
# 仅撤销本策略针对该合约的挂单 (通过合约代码判断)
# 注意:这里会撤销该合约所有未成交单,如果有人工单需谨慎
if order.m_strInstrumentID == ContextInfo.option_code and order.m_nOrderStatus in [48, 49, 50, 51, 52, 55]: # 未成交或部成状态
cancel(order.m_strOrderSysID, ContextInfo.accID, 'STOCK_OPTION', ContextInfo)
print(f"撤单: {order.m_strOrderSysID}")
# 6. 发送新订单 (Place Orders)
# 这里的 opType 使用:
# 50: 买入开仓 (Buy Open)
# 52: 卖出开仓 (Sell Open)
# orderType 1101: 单股/单账号/普通/限价
# prType 11: 指定价 (配合 price 参数)
# 发送买单 (提供流动性 - Bid)
if bid_price > 0:
passorder(50, 1101, ContextInfo.accID, ContextInfo.option_code, 11, bid_price, ContextInfo.volume, ContextInfo)
print(f"报单: 买入开仓 {ContextInfo.option_code} 价格 {bid_price} 数量 {ContextInfo.volume}")
# 发送卖单 (提供流动性 - Ask)
if ask_price > 0:
passorder(52, 1101, ContextInfo.accID, ContextInfo.option_code, 11, ask_price, ContextInfo.volume, ContextInfo)
print(f"报单: 卖出开仓 {ContextInfo.option_code} 价格 {ask_price} 数量 {ContextInfo.volume}")
BSM 定价 (ContextInfo.bsm_price):
ContextInfo.sigma (波动率) 是固定值。在进阶策略中,你需要根据市场隐含波动率(IV)实时更新这个值,或者维护一个波动率曲面。撤单逻辑:
get_trade_detail_data 获取 ORDER 对象,检查 m_nOrderStatus 状态码(如 50=已报, 55=部成)来决定是否撤单。下单接口 (passorder):
opType 与股票不同。50: 买入开仓 (做多波动率/方向)。52: 卖出开仓 (做空波动率/方向,即作为卖方)。时间与频率控制:
ContextInfo.run_interval 控制报价频率(例如每5秒一次),避免过于频繁的撤单报单被交易所限制或产生过多的废单。这是一个基础的演示策略,切勿直接用于实盘大资金交易,因为它缺少以下关键模块:
ContextInfo.get_option_iv 获取市场隐含波动率,或自行计算实时 IV。Q: 如何获取实时的隐含波动率而不是使用固定值?
A: 可以使用 ContextInfo.get_option_iv(optioncode) 接口获取交易所发布的或系统计算的实时隐含波动率,将其赋值给 ContextInfo.sigma。
Q: 为什么我的 passorder 下单没有反应?
A: 1. 检查 ContextInfo.accID 是否填写正确且已登录。2. 检查 opType 是否正确(期权是50/52)。3. 检查价格是否在涨跌停范围内。4. 检查是否有足够的保证金。
Q: 如何实现 Delta 对冲?
A: 你需要计算持仓的 Delta 总和。QMT 没有直接返回持仓 Delta 的接口,你需要遍历持仓,对每个合约调用 BSM 公式计算 Delta(BSM公式的一阶导数),加总后,如果 Delta 偏离 0 太多,就交易 50ETF 现货进行对冲。