STRIKE SELECTION
逐腿 Delta 选股:Optopsy 的行权价是怎么定下来的
Optopsy 不让你手工指定行权价,而是给每条腿一个 Delta 目标区间,由引擎在你的期权链里挑出最接近目标的那一张合约。理解这条链路,才能解释为什么有时候一行结果都出不来。
- 三个字段:
target是理想值,min/max是可接受区间,约束min ≤ target ≤ max。 - Delta 取绝对值:区间用 0–1 的正数表示,与看涨看跌无关。
- 数据里的
delta列是硬要求:缺它,任何策略都无法选出行权价。 - 每条腿独立选:多腿策略是先各自选好、再按日期与到期日拼组合。
max_entry_dte / exit_dte 框住候选合约min_bid_ask 剔掉流动性差的合约abs(delta) 在 [min, max] 的候选target 的一行optopsy/filters.py 的过滤与选取函数绘制的示意图;非官方流程图。TARGETRANGE
Optopsy TargetRange 三个字段与默认值:怎么给每条腿设 Delta
每个腿的 Delta 参数接受两种写法:一个 TargetRange 对象,或者等价的字典。
| 字段 / 角色 | 含义 | 典型取值 | 适用场景 | 注意点 |
|---|---|---|---|---|
target | 理想 Delta 值,引擎挑最接近它的合约 | 0.30 | 决定行权价离平值有多远 | 它是「最近」而不是「精确等于」,实际选中的合约不一定正好等于 target |
min | 可接受的最小 Delta,低于它的合约被剔除 | 0.20 | 防止选到过远的虚值 | 区间设太窄会导致选不出合约、结果为 0 行 |
max | 可接受的 Delta 上界,高于它的合约被剔除 | 0.40 | 防止选到过近的实值 | 与 min 一起决定候选池大小 |
| 标准虚值腿(默认) | 离平值较远、时间价值为主 | 0.30 / 0.20 / 0.40 | 大多数方向性策略的主腿 | 官方称其为 standard OTM leg 的默认值 |
| 平值腿(默认) | 贴近平值,时间价值占比偏高 | 0.50 / 0.40 / 0.60 | 跨式、宽跨的对称腿,或蝶式中间腿 | 平值附近合约最密集,通常候选最多 |
| 虚值翼(默认) | 更远的保护腿 | 0.10 / 0.05 / 0.20 | 铁鹰、鹰式的外侧翼腿 | 要与短腿的 Delta 明显分层,否则不再是翼 |
| 股票代理(默认) | 用深度实值 call 代理股票头寸 | 0.80 / 0.60 / 0.95 | Covered / 领口系列不传 stock_data 时 | 这是近似:真实股票的股息与流动性特征代理不出来 |
| 字典写法 | 与 TargetRange 等价的普通字典 | {"target":0.30,"min":0.20,"max":0.40} | 不想额外 import 类型时 | 键名必须准确,写错键会因严格校验而报错 |
依据官方参数文档(TargetRange 与默认 Delta 表)与 optopsy/types.py 的类型定义整理。
约束 min ≤ target ≤ max 由 Pydantic 在运行前校验,违反会直接报错而不是静默取默认值。这是官方标注的「严格参数校验」行为。
SELECTION PIPELINE
Optopsy 怎么从期权链选出一张合约:四步过滤步骤
三个过滤条件不是并行的,而是依次收窄候选池;最后一步的「取最近」是决定性的。
第 4 步依据 optopsy/filters.py 中 _select_closest_delta 的实现(含其 docstring 对分组键的描述);前三步依据官方参数文档。
results = op.long_calls(data, max_entry_dte=60, exit_dte=30)
max_entry_dte 限制入场时的剩余到期天数,exit_dte 决定在哪一天出场(0 表示持有到期)。只有这两个参数都落在你的数据覆盖范围内,才能匹配到入场与出场两条报价。
预期输出:候选集缩小到满足 DTE 条件的合约;不满足则整条链路无候选
results = op.long_calls(data, max_entry_dte=45, min_bid_ask=0.10)
min_bid_ask 剔除买卖价差低于阈值的合约。默认 0.05;流动性好的标的可以提到 0.15 甚至 0.20。这一步是「结果为空」的常见元凶之一。
预期输出:短腿与翼腿都要能找到满足价差的合约,四腿策略尤其明显
leg2_delta={"target": 0.20, "min": 0.15, "max": 0.25}
只保留 abs(delta) 落在 [min, max] 内的合约。这一步直接把「目标区间太窄」变成「没有候选」。
预期输出:每个腿各自得到一个候选池,彼此独立
# 源码实现要点(filters._select_closest_delta):
# 1) 先按 [delta_min, delta_max] 过滤 abs(delta)
# 2) 再按 (underlying_symbol, quote_date, expiration, option_type) 分组
# 3) 每组内取 abs(delta) 最接近 target 的那一行
分组键里的四个字段很关键:同一个报价日、同一个到期日、同一种期权类型才在同组竞争。所以同一个报价日不同到期日会各自选出一张,这正是「按 DTE 分桶」结果能出现多行的原因。
预期输出:每组恰好保留一行;多腿策略随后按日期与到期日把各腿拼起来
为什么是「分组内取最近」而不是「全局排序」
如果全局只留一行,那四腿策略就没法在同一个报价日取出四条不同的合约。按分组键取最近,等于在「每个报价日 × 每个到期日 × 每种期权类型」的口袋里各挑一张,多腿组合才有原料可用。
Delta 是数据给的,不是算出来的
Optopsy 直接消费你数据里的 delta 列,不自己计算希腊字母。所以不同数据源的 Delta 口径差异会直接反映到选股结果上——这是跨数据源比较结果时必须先对齐的东西。
BEYOND THE DOCS
Optopsy 官方文档没写的三处实现细节说明
官方文档讲清了参数语义,但下面三点只在源码里能看到,而它们直接决定你会拿到哪一张合约。
| 细节 | 官方文档怎么写的 | 源码里实际是 | 对你的影响 |
|---|---|---|---|
| Delta 的比较方式 | 写「Select strikes by delta」,未说明方向处理 | 比较的是 abs(delta),区间也用 0–1 正数 | 看跌腿不需要写负 Delta;写负数会导致区间过滤全空 |
| 「最近」的分组边界 | 只写 per-leg delta targeting | 分组键是标的 + 报价日 + 到期日 + 期权类型四个字段 | 同一报价日的不同到期日会各自产生一行,直接影响你能看到几个 DTE 分桶 |
| 不在区间的处理 | 写 min/max 会排除超出的期权 | 是「先过滤、再取最近」,不是在全部候选中排序后截取 | 区间设得比数据实际 Delta 分布更窄时,该腿候选直接归零,而不是退化成取边界值 |
对照官方参数文档与 optopsy/filters.py 的 _select_closest_delta 实现逐条核对(采集 2026-09-23)。
这三条不是官方文档的错误,而是抽象层级不同:文档面向使用者讲参数,源码面向实现讲分组。它们的差别恰恰是排查「为什么结果和预期不一样」时最有用的信息。
LEG MAPPING
Optopsy 腿数与参数的对应关系:怎么判断设错哪一腿
多腿策略里,把 Delta 设到不存在的腿上不会报错,只会静默不生效。下表是完整映射。
| 策略 | 有效参数 | 通常不设或用默认的 | 数据要求 | 注意点 |
|---|---|---|---|---|
long_calls / short_puts 等单腿 | leg1_delta | — | 标准 8 列(含 delta) | 只设 leg1 即可 |
long_straddles / long_strangles | leg1_delta、leg2_delta | — | 标准 8 列 | 跨式两腿 Delta 绝对值接近,宽跨需分层 |
long_call_spread / short_put_spread 等 | leg1_delta、leg2_delta | — | 标准 8 列 | 两腿 Delta 太接近会选到相邻行权价,价差过窄 |
call_back_spread / put_front_spread 等比例价差 | leg1_delta、leg2_delta | — | 标准 8 列 | 腿的数量比是 1:2,参数仍是两个腿 |
long_call_butterfly 等蝶式 | leg1_delta、leg2_delta、leg3_delta | — | 标准 8 列 | 三腿行权价需单调,Delta 设反会选不出组合 |
iron_condor 等四腿 | leg1_delta–leg4_delta | leg4_delta 有时用默认翼腿值 | 标准 8 列 | 四腿都要有满足价差的合约,这是最容易结果稀疏的一类 |
covered_call / collar | leg1_delta(股票代理)、leg2_delta(期权) | — | 可选 stock_data(真实 OHLCV) | 股票代理腿用深度实值 Delta 近似 |
long_call_calendar 等日历 / 对角 | leg1_delta、leg2_delta + 两组 *_dte_* | — | 两个到期日都要有数据 | 跨字段校验:前月 max DTE 必须小于后月 min DTE |
依据官方参数文档的腿数说明与各策略模块默认值整理。
一个实操建议:先用 raw=True 打印出实际的 strike 与 delta_entry 列,看看引擎到底选中了哪几张合约。这比反复调参数更快定位问题。
EMPTY RESULTS
Optopsy 结果为空怎么办:按顺序排查的方法
空结果是期权回测最常见的「报错」——它不报错,只是没有行。下面按命中概率从高到低排序。
| 排查顺序 | 检查什么 | 怎么验证 | 修法 |
|---|---|---|---|
| 1 | 数据里有没有 delta 列,值是否有效 | 打印 df['delta'].describe() 看是否有值、范围是否在 0–1 | 补上 Delta 列;若数据源用百分数表示(如 30),需先换算 |
| 2 | leg*_delta 区间是否比数据实际分布更窄 | 把区间临时放宽到 0.05–0.95 再跑一次 | 若放宽后出结果,说明区间过窄;按数据实际分布重新设 |
| 3 | min_bid_ask 是否把候选全过滤掉了 | 把 min_bid_ask 降到 0.0 或 0.01 再跑 | 流动性差的标的或长尾行权价需要更低阈值 |
| 4 | DTE 条件是否超出数据覆盖范围 | 打印 expiration - quote_date 的天数分布 | 把 max_entry_dte / exit_dte 调到数据实际存在的范围内 |
| 5 | 数据的时间区间是否覆盖了你要测的日期 | 检查 quote_date 的最小值与上界值 | 扩大数据下载区间,或用 csv_data(start_date=..., end_date=...) 明确范围 |
| 6 | 多腿策略是否有一腿找不到合约 | 逐腿先单独跑一次单腿策略,确认每条腿都有候选 | 放宽最难满足的那一腿(通常是翼腿)的 Delta 区间 |
| 7 | 装的是不是老包 | 检查 len(op.__all__) 与 hasattr(op,'TargetRange') | 换到 Python 3.12–3.13 重装(见安装页) |
前四项来自官方 getting-started 的空结果排查清单,后三项由本项目的源码核验与 pip 版本实测补充。
FAQ
Optopsy 逐腿 Delta 常见问题
TargetRange 里的 Delta 要写正数还是负数?
写正数。源码比较的是 abs(delta),区间也用 0–1 的正数表示,与看涨看跌方向无关。写负数会导致区间过滤后没有候选。以源码实现为准。
设了 leg3_delta 但只跑两腿策略,会报错吗?
不会报错,也不会生效。多余参数在严格校验通过后被忽略。这是很容易被忽略的静默行为,建议只设该策略真正用到的腿。
为什么同一份数据,不同人跑出来的行权价不一样?
因为 Optopsy 直接消费数据里的 delta 列,不自己计算希腊字母。不同数据源的 Delta 口径(模型、无风险利率假设、是否含股息)不同,选出的合约就会不同。跨数据源比较前要先对齐这一点。
target 和 min/max 三者是什么关系?
target 是理想值,引擎选最接近它的合约;min/max 是硬性可接受区间,超出范围的合约先被剔除。约束是 min ≤ target ≤ max,由 Pydantic 在运行前校验,违反会直接报错。
为什么四腿策略的结果明显比单腿少?
每一腿都要在同一个报价日、同一个到期日找到满足 Delta 区间与 bid-ask 阈值的合约,四个条件同时成立的概率远低于单腿。这是结构性的,不是 bug。放宽翼腿 Delta 区间与 min_bid_ask 是最有效的两步。
行权价选得太近或太远怎么办?
改 target。把 target 调小会让行权价更远(更虚值),调大则更近平值。改 target 的同时记得同步调整 min/max,否则会出现「目标值不在可接受区间内」的校验错误。以官方参数文档为准。