简介:这是一份面向股票与期权交易者、量化分析爱好者及数据科学学习者的股票期权分析项目资源包。资源围绕“挑选股票与期权”设计,覆盖摘要分析、推荐系统、决策预测等模块,并结合历史波动率预测未来30天乃至一年价格,同时基于Wyckoff方法给出牛熊市的算法化观点,内容还融入数据科学、博弈论及即将实现的机器学习,整体偏重预测性分析与辅助决策。压缩包为zip格式,容量325.82MB,文件总数为0,上游未提供文件明细,不过从项目背景看,主体包含网站源文件、Excel原型及分析脚本等材料。目前已有162人浏览学习,适合希望将Excel原型转化为Web化股票分析工具、需要参考期权筛选与价格预测思路的中高级学习者。通过该项目,读者能获得完整站点结构、Excel模型参考、推荐引擎构思及机器学习需求标注,便于快速理解并复现一套可扩展的期权分析框架。
1. 股票期权分析到底在分析什么:别把选股和选期权当成两道题
股票期权分析不是把股票的 K 线和期权的行情放在同一个页面里看,而是要把“选股票”和“选期权”串成一个完整决策链路:先选出值得跟踪的股票,再判断它未来一段时间的价格区间,最后在期权链里挑出风险收益比合适的合约。很多人直接跳过第一步,一上来就挑期权,结果标的本身没有趋势,隐含波动率还贵得离谱,买进去只能耗时间价值。下面这套方案会按选股、选期权、预测、计算器、避坑的顺序展开,新手能按步骤复现,熟手可以重点看参数边界和容易翻车的细节。期权分析这件事,最难的从来不是公式,而是每一步都留下可验证的中间结果。
2. 股票挑选:用多因子打分把“想买的股票”变成可计算的对象
2.1 先想清楚:你是要买股票,还是要拿它做期权标的
常见做法是直接套用价值投资那一套选股逻辑:低市盈率、高股息、稳定增长。但如果是为期权挑选标的,权重应该明显不同。期权交易赚的是价格波动和时间衰减之间的差额,标的完全不上不下,再便宜的期权也难做;反过来,波动率长期偏高的股票,即使估值贵一些,只要期权隐含波动率处于合理分位,仍然值得进入观察池。
我一般会把股票池分成两层。第一层是“基本面不踩雷”,亏损、被立案调查、长期停牌的一票否决;第二层才是打分排序。对于期权标的,流动性指标(成交额、换手率)和历史波动率(20日、60日)的权重我会给到 60% 以上,估值只占 20% 左右。
这里有个血泪经验:不要拿日成交额只有几千万元的股票做期权,哪怕它期权链存在,买卖价差常常能把理论上的盈利吃光。这个坑后面单独展开,但选股阶段就应该用成交额下限把它挡在门外。
所以这一小节的核心是:在写任何打分脚本之前,先定义一个“是否适合做期权标的”的筛选函数。它的输出不是排名,而是布尔值,pass 或者 fail。函数里至少要有成交额下限、停牌天数上限、净利润同比下限三个条件。
2.2 数据准备:行情、财务、波动率最少要拿哪几样
要支撑后面的打分和计算器,最少需要四类数据:日线行情、财务指标、波动率数据、交易日历。日线行情里必须包含前复权收盘价、成交额、换手率;财务指标只需要最新一期报告里的净利润同比、市盈率、市净率;波动率数据可以直接用历史收益率滚动计算,不需要额外接口;交易日历决定期权剩余到期日怎么算,后面避坑章会细说。
字段清单可以用下面的表:
| 字段 | 用途 | 最少长度 |
|---|---|---|
| date | 交易日 | 250 个自然日以上 |
| close_adj | 前复权收盘价 | 同上 |
| amount | 当日成交额 | 同上 |
| turnover_rate | 换手率 | 同上 |
| pe_ttm / pb | 估值 | 最新一期 |
| net_profit_yoy | 基本面筛查 | 最新一期 |
这里要提醒一句:前复权收盘价和原始收盘价在除权除息后差别很大,计算历史波动率时混用会导致收益率序列出现假跳变。我一般统一用前复权价计算收益率,期权计算器里再单独用现价。
数据来源不需要依赖某个特定平台,常见的行情数据源都能给出这些字段。关键是拉数之后先做三件事:按日期去重、按代码分组做缺失值检查、把停牌日的涨跌幅置为 0 或者直接剔除。停牌日的处理会影响波动率,如果选择置为 0,那么停牌时间越长,波动率被稀释得越厉害;如果选择剔除,则要保证剩余交易日的连续性。两种方式各有适用场景,但在同一个股票池里必须保持口径一致。
2.3 打分模型:趋势、波动、流动性、估值四个维度的实现脚本
打分模型我倾向用百分位打分,而不是用原始数值。因为股票之间的成交额、市盈率量纲差异太大,直接用原始值加权会被极端值控制。百分位打分的思路是:对每个因子在股票池内做排序,取该股票的名次百分比,然后加权求和。这样即使某只股票成交额是另一只的 100 倍,它对分数的贡献也不会失控。
下面是一个最小可运行的 Python 脚本,输入是整理好的 DataFrame,输出是打分排序结果。注意它只做演示,实际操作中还要加行业中性化,否则同一个行业的多只股票会同时霸榜,导致期权候选池行业集中度过高。
import pandas as pd import numpy as np def score_stocks(df, trend_w=0.2, vol_w=0.3, liq_w=0.2, value_w=0.1): # df 必须包含: code, close_adj, amount, turnover_rate, pe_ttm grouped = [] for code, g in df.groupby('code'): g = g.sort_values('date') ret = g['close_adj'].pct_change().dropna() # 趋势因子:收盘价在 60 日均线之上记 1,偏离度作为强度 ma60 = g['close_adj'].rolling(60).mean().iloc[-1] price = g['close_adj'].iloc[-1] trend = 1 if price > ma60 else 0 trend_dev = (price / ma60 - 1) if ma60 > 0 else 0 # 历史波动率:20 日年化 hist_vol = ret.tail(20).std() * np.sqrt(252) if len(ret) >= 20 else np.nan # 流动性:最近 20 日平均成交额的对数 liq = np.log1p(g['amount'].tail(20).mean()) # 估值:市盈率倒数(盈利收益率),越大越便宜 ep = 1.0 / g['pe_ttm'].iloc[-1] if g['pe_ttm'].iloc[-1] > 0 else 0 grouped.append({ 'code': code, 'trend': trend + min(trend_dev, 0.2), # 偏差超过 20% 封顶 'vol': hist_vol, 'liq': liq, 'ep': ep, 'turnover': g['turnover_rate'].tail(20).mean(), }) f = pd.DataFrame(grouped) # 每个因子做百分位标准化,缺少数值会生成 NaN,要先填充 for col in ['trend', 'vol', 'liq', 'ep']: f[col + '_pct'] = f[col].rank(pct=True) # 综合分:波动率适中给高分,太低和太高都扣分 f['vol_score'] = 1 - np.abs(f['vol_pct'] - 0.5) * 2 f['total'] = (f['trend_pct'] * trend_w + f['vol_score'] * vol_w + f['liq_pct'] * liq_w + f['ep_pct'] * value_w) return f.sort_values('total', ascending=False) # 假设 df 已包含近一年日线,调用方式: # result = score_stocks(df) # result.head(20)逻辑说明:这个脚本把选股拆成四个可解释的因子,其中波动率因子用了“适中加分”的策略,因为期权交易需要波动,但波动过高往往伴随事件风险或流动性陷阱。trend 因子用 60 日均线位置作为方向过滤,避免在明确的下跌趋势里做多期权。
参数说明:trend_w、vol_w、liq_w、value_w 四个权重在示例里加起来是 0.8,如果想加行业中性化,可以把剩余权重分配给行业偏差惩罚项。实际操作中我会把基本面筛查放在打分之前,而不是作为权重项,比如净利润同比为负直接剔除。vol_score 用的“离 0.5 越近越高”是一种对稳健性的妥协,如果你只做买方,可以把权重再往高波动倾斜,甚至在打分前加一个波动率下限。
这个脚本跑完,会得到一个包含 code、total 的排序表。建议取前 20 只进入期权候选池,而不是只取前 3 只,因为后续期权筛选还会根据隐含波动率和价差再淘汰一批。记住打分只是排序工具,不是买入信号。
熟手可以注意一个边界:百分位打分对股票池构成非常敏感。如果池子里突然加入 50 只低波动银行股,原来高波动股票的分位会被整体压低。想消除这个影响,可以改用 z-score 标准化,或者按行业分组打分再合并。没有绝对正确的权重,关键是每次调整权重后都用历史数据做一次回看,确认打分排名的换手率没有高到无法执行。
3. 期权挑选:从 IV 分位到希腊字母,先用规则筛再用计算器验
3.1 为什么选期权不能只看股价和到期日
很多新手看期权链时,只盯着行权价和权利金,觉得便宜就是好。这是最大的误区。期权价格由标的价格、行权价、剩余到期时间、无风险利率、隐含波动率共同决定,其中隐含波动率(IV)是唯一直接反映市场情绪的变量。同一个行权价,IV 高意味着权利金贵,但贵不代表不能买,关键看它处于历史什么位置。
我常用的筛选逻辑是“先看 IV 分位,再看 Delta,最后看价差”。IV 分位解决贵不贵的问题,Delta 解决方向和敏感度的问题,买卖价差解决能不能进出场的问题。三个条件都满足,才把这份合约放进观察列表。
希腊字母不一定每个都要算一遍,实际交易里 Delta、Theta、Gamma 最常用。Delta 在 0.3 到 0.6 之间的期权,既保留一定跟随标的价格运动的弹性,又不会像深度实值期权那样权利金贵到没有杠杆意义;Theta 用来衡量每天时间价值损耗,短线持仓必须知道每天要亏多少时间价值。Gamma 则影响 Delta 的变化速度,买入方喜欢 Gamma,卖出方怕 Gamma,但这在筛选阶段不是首要条件。
3.2 期权链数据清洗与关键字段
期权链数据比股票日线更容易藏问题。通常一个标的会挂出不同行权价、不同到期日、不同合约类型的多行记录。最关键字段包括:合约代码、行权价、到期日、call/put、买价、卖价、最新价、成交量、持仓量、隐含波动率。
拿到原始数据后,我一般先做四步清洗:
- 删除买卖价任一为 0 的合约,这类通常是做市商还没正常报价。
- 删除剩余到期日小于 3 天的合约,避免临近到期 Gamma 异常放大。
- 计算价差比例 =(卖价 - 买价)/ 卖价,超过 5% 的先标记,超过 10% 的剔除。
- 用合约代码解析出到期日,不能相信行情展示的字符串格式,有的接口会混入“当月/下月”这种文本字段。
下面是一个清洗示例:
import pandas as pd def clean_option_chain(chain, min_days=3, max_spread_pct=0.10): # chain 必需字段: strike, expiry, cp_flag, bid, ask, volume, open_interest, iv chain = chain.copy() # 计算剩余到期日,注意 expiry 要先转成日期类型 chain['expiry'] = pd.to_datetime(chain['expiry']) chain['days_to_expiry'] = (chain['expiry'] - pd.Timestamp.today().normalize()).dt.days # 过滤无效报价 chain = chain[(chain['bid'] > 0) & (chain['ask'] > 0)] # 过滤临近到期 chain = chain[chain['days_to_expiry'] >= min_days] # 价差比例 chain['spread_pct'] = (chain['ask'] - chain['bid']) / chain['ask'] chain = chain[chain['spread_pct'] <= max_spread_pct] # 持仓量、成交量至少有一个大于 0,避免完全没人气的合约 chain = chain[(chain['open_interest'] > 0) | (chain['volume'] > 0)] return chain.sort_values(['strike', 'days_to_expiry'])逻辑说明:clean_option_chain 的输出是后续所有筛选的基础。价差比例是期权交易里最容易被忽略的隐性成本,买价和卖价之间的 gap 在你成交瞬间就是亏损,所以把它作为硬过滤条件比后置提醒有效得多。
参数说明:min_days 默认 3 天,如果你做周度策略可以调到 7;max_spread_pct 默认 10%,但实盘建议调到 5% 以内。对于价格很低的深度虚值期权,bid 可能只有 0.01,这种合约的 spread_pct 即使不大,绝对滑点也很吓人,建议额外加一个最小权利金过滤,比如权利金小于 0.05 的合约直接剔除。
3.3 用 IV 分位 + Delta 筛出候选合约
清洗完之后,下一步是给每个合约算 IV 分位。分位的计算方式:对该标的所有同一到期日档期的历史 IV 序列,取当前值在过去一年中的排名百分比。没有历史 IV 数据库时,可以用一个近似方法:用当前 IV 与过去 20 日已实现波动率对比,比值大于 1.2 视为偏贵,小于 0.8 视为偏便宜。
Delta 可以直接从行情接口拿,也可以自己用 Black-Scholes 求导。下面给出一个筛选脚本,输入是干净期权链,输出是符合 IV 分位和 Delta 条件的候选合约。
def pick_options(clean_chain, iv_low=0.3, iv_high=0.7, delta_min=0.3, delta_max=0.6): # clean_chain 里需要有 iv_rank(0~1)和 delta 字段 cond = ( (clean_chain['iv_rank'] >= iv_low) & (clean_chain['iv_rank'] <= iv_high) & (clean_chain['delta'].abs() >= delta_min) & (clean_chain['delta'].abs() <= delta_max) & (clean_chain['cp_flag'].isin(['C', 'P'])) ) candidates = clean_chain[cond].copy() # 同标的同到期日下,优先选价差小的 candidates = candidates.sort_values(['spread_pct', 'days_to_expiry']) return candidates逻辑说明:iv_rank 是当前 IV 在过去一年中的百分位,取值 0.3 到 0.7 表示“不贵也不便宜”;Delta 过滤把候选合约限制在轻度虚值到轻度实值区间。排序用 spread_pct 而不是成交量,因为成交量只是活跃度,价差才是实际交易成本。
参数说明:如果你预期标的有大行情,可以把 iv_high 提高到 0.8,容忍更贵的 IV;如果只想做卖方赚时间价值,则要求 iv_rank 大于 0.7,因为高 IV 大概率回归均值。注意 put 的 Delta 是负值,筛选时使用 abs(delta),否则 put 全部被过滤掉。
还需要补充一个细节:不同到期日的 Delta 不可直接横比,同一天到期、不同行权价的 Delta 才有比较意义。所以实际操作中我会先固定一个目标到期日,比如 30 到 45 天,再在该到期日下的不同行权价之间按 Delta 筛选。为什么不选到期时间太长的期权?因为 Theta 损耗虽慢,但资金占用时间长,胜率未必高;太短的期权又受 Gamma 影响剧烈。30 到 45 天是权利金、时间损耗和灵活性之间相对平衡的区间。
3.4 用 Black-Scholes 计算器验证理论价:市价与理论价的背离怎么看
筛选出来的合约,价格到底合不合理,需要一个基准。Black-Scholes 是最常用的参考,虽然它有简化假设,但作为“市价隐含了什么 IV”的换算工具仍然可靠。这里实现一个最小计算器:
import math from scipy.stats import norm def bs_price(spot, strike, days, r, sigma, option_type='C'): # 注意:这里 days 按自然日传入,所以除以 365;如果改用交易日,请除以 252 t = days / 365.0 if t <= 0: return max(0, spot - strike) if option_type == 'C' else max(0, strike - spot) d1 = (math.log(spot / strike) + (r + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t)) d2 = d1 - sigma * math.sqrt(t) if option_type == 'C': price = spot * norm.cdf(d1) - strike * math.exp(-r * t) * norm.cdf(d2) else: price = strike * math.exp(-r * t) * norm.cdf(-d2) - spot * norm.cdf(-d1) return price # 示例:现价 4.5,行权价 4.6,剩余 45 个自然日,无风险利率 0.02,历史波动率 0.25 price_c = bs_price(4.5, 4.6, 45, 0.02, 0.25, 'C') print(f"Call 理论价: {price_c:.4f}")逻辑说明:bs_price 把到期时间换算成年化 t,d1、d2 是标准正态累积函数的输入。这个公式没有考虑股息率,如果标的是分红较高的品种,需要在 spot 上做前瞻调整,否则 call 的理论价会偏高。
参数说明:r 用无风险利率,我一般取一年期国债到期收益率或货币基金年化;sigma 用 20 日历史波动率,但更稳妥的是用同一标的近月平值期权的 IV 作为 sigma 输入,这样算出来的是“理论价”和“市价”之间的偏差。
拿到理论价之后,对比市价:如果市价显著高于理论价,说明市场在定价一个更高的波动率;如果市价低于理论价,要么是流动性差导致的折价,要么是有人在做波动率空头。不要直接说“低估就买”,至少要结合 IV 分位一起判断。计算器不是水晶球,它只是把价格拆解成可比较的参数。
4. 预测性分析:不猜涨跌,估计价格区间和胜率,再给建议
4.1 预测性分析在期权里到底预测什么
很多人一听到预测性分析就想找个模型告诉他“下周涨还是跌”,这在期权里不是最好的切入点。期权的收益是非线性的,真正影响一张 call 能否盈利的,不是方向单点,而是到期时标的价格落在哪个区间。所以我会把预测目标拆成两个:标的价格的分布区间,以及在这个区间内某个行权价的盈利概率。
这样做的好处是,即使方向判断错了,只要区间判断还在合理范围,期权组合的亏损也可控。预测性分析本质上是在给“建议”加概率边界,而不是拍脑袋给一个目标价。
常见做法有两种:一种是用历史收益率的均值和标准差做参数化模拟,另一种是直接用期权市场隐含的波动率做风险中性模拟。前者适合判断“当前价格是否偏离历史常态”,后者适合判断“市场定价是否已经透支预期”。我个人推荐两个都看,但在给建议时以历史分布为主、隐含波动率为辅。
4.2 用蒙特卡洛模拟估计到期价格区间和概率
蒙特卡洛的假设是:标的价格服从几何布朗运动,日收益率均值 mu 和波动率 sigma 可以用历史数据估计。做法是生成 N 条随机路径,计算到期价格,再统计这些价格的分位数和超过行权价的概率。脚本如下:
import numpy as np def simulate_price(spot, days, mu, sigma, n_sims=10000, seed=42): np.random.seed(seed) # days 传交易日数,由交易日历计算得出 dt = 1 / 252.0 # 每步收益率的漂移项和随机项 drift = (mu - 0.5 * sigma ** 2) * dt vol = sigma * np.sqrt(dt) # 生成 n_sims 条路径的日收益率,并累乘到到期 returns = np.random.normal(drift, vol, size=(n_sims, days)) price_paths = spot * np.exp(np.cumsum(returns, axis=1)) # 只需要到期日价格 final_prices = price_paths[:, -1] return final_prices def price_distribution(spot, days, mu, sigma, strikes): finals = simulate_price(spot, days, mu, sigma) percentiles = np.percentile(finals, [5, 25, 50, 75, 95]) prob_above = {strike: float(np.mean(finals > strike)) for strike in strikes} return percentiles, prob_above # 示例:现价 4.5,剩余 45 个交易日,mu 用过去 60 日年化收益,sigma 用 20 日年化波动率 percentiles, prob_above = price_distribution(4.5, 45, 0.05, 0.25, [4.2, 4.5, 4.8]) print(percentiles) print(prob_above)逻辑说明:drift 是年化收益率的对数形式,vol 是每步波动率。用 cumsum 累加日收益率再取 exp,得到价格路径。最终 prices 是到期日价格的经验分布。percentiles 输出 5% 到 95% 的分位区间,表示价格落在该区间的概率约为 90%。prob_above 表示每个行权价被突破的概率。
参数说明:mu 和 sigma 的估计窗口很关键。mu 用 60 日年化收益率容易出现极端值,我会先做截尾处理,把超过 3 倍标准差的收益率替换掉;sigma 用 20 日还是 60 日取决于你持有的期权剩余期限,原则是估计窗口和剩余期限尽量匹配。剩余 45 个交易日的期权,用 30 到 60 日窗口都合理,太短会过度响应最近行情。
注意这个模型没有考虑波动率微笑和跳跃风险,所以它适合普通流动性好的标的,不适合财报公布前或重大事件前使用。财报前用蒙特卡洛会低估尾部风险,这是实盘里最常见的预测翻车场景,后面避坑章会再讲。
4.3 把预测结果转成仓位建议:胜率、盈亏比与单笔仓位
有了到期价格分布之后,“建议”就不再是口号,而可以换算成三个数字:胜率、盈亏比、建议仓位。
胜率用 prob_above 或 prob_below,取决于你买 call 还是 put。盈亏比则需要结合期权权利金:如果买一张行权价 4.8 的 call,当前市价 0.2,预测到期价格分布的 50% 分位是 4.9,那么盈利时平均能赚 0.5(4.9-4.8-0.2 取均值),亏损时最大亏 0.2,盈亏比约 2.5。这里不要用理论上的最大收益去算,用模拟分布里的条件均值更实际。
单笔仓位可以参考凯利公式的一个保守版本:
f = win_rate - (1 - win_rate) / payoff_ratio position_pct = max(0, min(0.05, f * 0.5))其中 payoff_ratio 用模拟分布里的条件盈利均值除以权利金,乘 0.5 是因为期权胜率估计误差大,拿半个凯利已经足够。凯利公式没有魔法,它只是防止你在胜率很高时下注过重。
建议的最终输出格式不是“买入”,而是一段描述:例如“标的当前价 4.5,45 天后落于 4.2-4.8 区间的概率约 90%,4.8 call 的胜率 35%,盈亏比 2.5,建议仓位不超过 2%。”这段描述里有概率、有价格边界、有仓位上限,别人拿到可以直接执行或否定,而不是争论方向。这也是预测性分析的价值所在:它不在预测本身,而在于把预测变成可证伪的建议。
5. 股票期权分析中常见的 5 个坑:现象、原因、解决
5.1 数据与计算层的三个坑
坑一:剩余到期日按自然日算,导致理论价和希腊字母全部偏移。
现象:用计算器算出的 call 理论价明显低于市价,Delta 也不对,怎么看怎么别扭。
原因:期权剩余期限在定价模型里必须用交易日或 365 天年化。很多人直接把到期日减当前日期得到 45 天,但模型里 t = days / 365,遇到节假日和周末,实际交易时间只有 30 天左右,导致时间价值被高估。
解决:统一用交易日历。如果没有交易日历,可以用一个简单修正:交易日 ≈ 自然日 × 5 / 7,再用这个值除以 252。或者更稳妥地,剩余交易日直接用 np.busday_count(today, expiry)。更规范的做法是在数据准备阶段就生成一个交易日掩码:把一年里的交易日列成表,剩余到期天数用交易日下标差,再用这个天数除以 252 输入模型。这个口径和 BS 公式里的年化约定一致。
坑二:高分红标的的股息率被忽略。
现象:某标的每年分红 3%,用标准 BS 算出来的 call 理论价长期高于市价,做买方总觉得买贵了。
原因:Black-Scholes 原始公式假设标的不分红。对高股息股票,call 持有者拿不到股息,定价时要扣除股息的现值。忽略股息,call 理论价偏高,put 理论价偏低,整个筛选逻辑都会偏。
解决:在 bs_price 里加上股息率参数 q,把 spot 替换成 spot * exp(-q * t),也就是用标的前瞻价格。q 可以用最近一年的股息率估计,或者用期货价格反推隐含股息率。q 的取值不必很精确,只要方向对,理论价和市价的偏差就会大幅缩小。比如股息率 3%,剩余 90 天,q * t = 0.0075,对 2 元权利金影响约 0.015 元,看起来小,但在做多腿组合时误差会被杠杆放大。
坑三:历史波动率计算时把停牌日当成正常交易日。
现象:某股票长期停牌后复牌,波动率被算得极高,进入期权候选池,结果实盘发现那只是复牌当天的跳空,之后波动率迅速收敛。
原因:停牌日没有交易,收益率应该是缺失值或 0,但很多人把停牌日也放进 pct_change 序列,停牌前后的收盘价跳变被当作真实波动。
解决:先剔除停牌日再做收益率。如果行情接口没有停牌标记,可以用成交额为 0 或者成交量 0 识别停牌日,剔除后重新计算波动率。还有一种隐蔽情况:股票复牌当天没有涨跌幅限制,如果只用当日收益率,会把方差拉得非常大。我一般会额外设置一个收益率截尾阈值,超过 0.2 的收益率按 0.2 计算,虽然粗糙,但能避免单日跳空长期污染波动率估计。
5.2 策略与执行层的两个坑
坑四:只看当前 IV 的绝对值,不看 IV 分位。
现象:看到某个期权 IV 45%,觉得“高 IV 就是贵”,于是全部避开;或者看到 IV 25% 觉得便宜,直接重仓买方,结果方向对了期权还是亏钱。
原因:不同标的的 IV 常态区间完全不同。有些股票常年 IV 40%,40% 对它来说是正常水平;有些股票常年 18%,25% 已经算偏贵。当前 IV 必须放到自身历史分布里看才有意义。
解决:在期权链数据里持久化保存历史 IV,每天计算 iv_rank。没有历史 IV 时,用当前 IV 与 20 日已实现波动率的比值做近似,比值超过 1.3 视为偏高,低于 0.7 视为偏低。IV 分位还有一个附加作用:判断卖方逻辑是否成立。IV 分位大于 0.8 时,卖方胜率历史统计往往更高,但要注意卖方赚的是概率,不是确定性。
坑五:把预测性分析的结果当成单点预测来下注。
现象:模拟结果显示 95% 置信区间是 4.2-5.0,实际到期价格跌到 3.8,于是觉得模型不靠谱,彻底放弃这套分析。
原因:蒙特卡洛模拟给的是分布,不是单点。5% 的尾部概率意味着每 20 次就有 1 次落在区间外。期权交易里,尾部事件虽然概率低,但亏损幅度大,只用区间中位数做决策相当于忽略风险。
解决:把预测结果用于仓位控制和行权价选择,而不是方向押注。比如模拟显示落在 4.2 以下的概率只有 5%,那就不该买行权价 5.0 的裸 call;但也不能因为概率低就满仓,单笔仓位仍然按半个凯利控制。另外,财报、议息、政策发布等事件前,把模拟的尾部概率手动调高,或者直接放弃该周交易。给建议时应该保留两个数字:预测区间和预测失败时的最大亏损。如果最大亏损超过你能承受的范围,即使胜率 80% 也不应该做。
如果同时踩了上面几个坑,通常第一个信号就是:理论价和市价偏离超过 30%,但你对不上原因。我的排查顺序是:先查剩余到期天数,再查股息率,然后查波动率口径,最后才怀疑模型本身。这条习惯看起来简单,但能省下大量复盘时间。很多“模型失效”的结论,最后都发现是输入口径错了,而不是公式错了。
6. 进阶用法:把整套股票期权分析变成一张周更决策清单
前面几章的脚本是零散的,真正能长期用的,是把选股、选期权、预测、计算器串成一张每周更新的决策清单。每周固定跑一遍,输出的表格直接作为下一周的操作依据,而不是临场翻行情。
我的习惯是:每周日晚上拉一次股票池数据,跑选股打分;周一开盘前拉期权链,跑清洗和 IV 分位筛选;再把候选合约的到期价格分布和仓位建议填入清单。清单字段就六列:股票代码、当前价、IV 分位、Delta、行权价、权利金、理论价、胜率、建议仓位。看起来多,实际上每行都是一个完整的决策理由。
自动化脚本不需要重新发明,把前面三个函数串起来就行:
import pandas as pd def weekly_report(stock_df, chain, spot, days, mu, sigma): scores = score_stocks(stock_df) top_codes = set(scores.head(20)['code']) clean = clean_option_chain(chain) candidates = pick_options(clean) rows = [] for _, row in candidates.iterrows(): if row['code'] not in top_codes: continue theo = bs_price(row['spot'], row['strike'], row['days_to_expiry'], 0.02, sigma) _, prob = price_distribution(row['spot'], days, mu, sigma, [row['strike']]) rows.append({'code': row['code'], 'strike': row['strike'], 'theo': theo, 'win_rate': prob[row['strike']]}) return pd.DataFrame(rows)这段代码把选股、选期权、计算器、预测性分析全部串起来。weekly_report 的入参里 spot 是当前标的价格,days 是目标到期交易日数,mu 和 sigma 来自第 4 章的估计。输出只有一行一行的候选和胜率,不做任何自动交易决策。真正下单时,还要看当天价差是否恶化、事件日历上有没有财报。
最后说一个我自己的教训:以前我总想往模型里加更多因子,黑匣子越做越大,结果每次回测都漂亮,实盘却连续翻车。后来我把脚本砍到只剩上面这几个函数,每一条输出都能解释为什么,反而更容易坚持执行。期权分析没有银弹,但有纪律就有复利。希望帮到你。
本文还有配套的精品资源,点击获取