简介这是一份以股票与期权挑选为核心的综合分析资源包面向对量化选股、期权策略和预测性分析感兴趣的 Python 学习者和金融数据分析初学者。内容围绕作者的 Excel 原型扩展而来包含摘要分析、推荐引擎、决策与预测、博弈论视角以及后续机器学习方向形成一套从数据清洗到可视化展示的探索型方案。压缩包共收录文件较多以 Python 脚本、数据文件及原型设计文档为主整体大小约 325.82MB适合作为学习数据分析流程、理解技术指标和思考智能投顾逻辑的参考素材。已有 162 人浏览学习。通过这套资料读者可了解基于历史波动率估算 30 天至一年价格区间的做法并接触 Wyckoff 方法对牛熊趋势的算法判断同时获得推荐模块与决策辅助工具的灵感便于在自己的项目中继续扩展。1. 期权挑选不是猜涨跌而是给不确定性标价打开期权链的那一刻大多数人会懵同一标的有十来个行权价、三四个到期月每个合约的报价看着都不贵但买哪个心里完全没底。很多人选期权的方式是“看方向”——觉得股票要涨就买Call要跌就买Put结果往往是方向猜对了、钱却亏了原因很简单方向只是期权收益的一个变量波动率、时间衰减、买卖价差和行权价离现价的远近每一项都能把一次正确的判断变成亏损。我做期权分析这几年最深的感觉是期权挑选的本质不是预测涨跌而是给“不确定性”标价。谁能把未来的价格分布、波动率水平以及当前合约的隐含波动率放在一起比较谁就能在期权链里筛出胜率和赔率都站得住的那一档合约。这套东西落到实操上就是三件事先给标的股票做预测性分析估计未来一段时间的价格波动区间再把预测结果喂给期权定价模型和市场上正在卖给我们的隐含波动率做对比最后用几个硬指标过滤掉不划算的合约。本文会把每一环做成可直接运行的计算器和挑选清单供做备兑卖出或方向性买方的读者参考。2. 预测性分析用 EWMA 滚动波动率为期权做“标的分布”2.1 为什么定价公式里最不可靠的输入是波动率常见做法是拿 Black-Scholes 公式给期权定价公式本身并不复杂标的价格、行权价、无风险利率、到期时间、波动率五个输入扔进去理论价格就出来了。问题在于这里面真正可从市场观察到的只有前四个波动率反而是从期权市价反推出来的隐含波动率。换言之期权市价本身就包含着市场对未来波动的判断如果我们拿着这个判断回去给期权定价就会陷入循环论证。所以做期权挑选时真正有意义的工作是把“自己预测的波动率”和“市场用隐含波动率交易的价格”摆在一起。若自己的预测值明显低于隐含波动率说明买期权是在支付高溢价更适合做卖方反之若预测值明显高于隐含波动率说明期权便宜了买方才有统计优势。进一步讲预测性分析的输出不只是一个波动率数字而是标的在到期日的价格分布。有了这个分布欧式期权到期收益可以被直接计算Call 的收益是 max(S_T - K, 0)Put 是 max(K - S_T, 0)只要把未来价格模拟出几千条路径胜率和期望收益就都出来了。因此做期权的挑选首先要解决的不是期权本身而是标的股票未来价格的概率分布长什么样。2.2 用带衰减因子的 EWMA 做滚动波动率预测对大部分个股和 ETF 标的日收益率序列往往存在波动率集聚现象大涨大跌之后继续大波动平静期之后继续平静。学术上常用 GARCH(1,1) 建模但实操里指数加权移动平均EWMA更省事效果也不差。EWMA 的本质是给历史日收益率按时间衰减加权最近的样本权重最大这天然匹配波动率集聚的直觉而且参数只有一个衰减因子最容易在本地跑通。下面是一段可以直接运行的 EWMA 滚动波动率代码数据格式为本地 CSV包含 date 和 close 两列。这里故意不引入抓行情接口方便读者把任意数据导出成该格式后使用。import pandas as pd import numpy as np # 读取本地行情数据要求至少包含 date 和 close 两列 df pd.read_csv(underlying.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 计算日对数收益率 df[log_ret] np.log(df[close] / df[close].shift(1)) df df.dropna().reset_index(dropTrue) # EWMA 波动率预测 def ewma_vol(log_ret, lam0.94): 用 RiskMetrics 的 EWMA 方法估计日波动率。 lam 是衰减因子推荐 0.94数值越大历史信息衰减越慢。 var_t log_ret.iloc[0] ** 2 vols [] for ret in log_ret: # 核心递推公式: sigma_t^2 lam * sigma_{t-1}^2 (1 - lam) * ret_{t-1}^2 var_t lam * var_t (1 - lam) * (ret ** 2) vols.append(np.sqrt(var_t)) return np.array(vols) df[ewma_vol] ewma_vol(df[log_ret], lam0.94) # 计算年化波动率并换算成到期区间的预测波动 annual_vol df[ewma_vol].iloc[-1] * np.sqrt(252) # 一年按 252 个交易日 horizon_years 30 / 365 # 假设我们要看 30 天到期的期权 horizon_vol annual_vol * np.sqrt(horizon_years) # 到期区间波动用于后续定价和模拟 print(f当前年化波动率: {annual_vol:.2%}) print(f未来30天预测波动率: {horizon_vol:.2%})核心逻辑就一个递推式今天的方差等于昨天的方差按 lambda 衰减再加上昨日实际收益的平方项。从代码里能看到两个关键参数一是lam0.94这是 RiskMetrics 在金融时间序列上的经典取值适合日频数据日常使用我一般会额外算 lambda 等于 0.90 和 0.97 的两个版本做上下边界防止参数选择对结论产生过大的影响。二是年化时乘以sqrt(252)这是国内和美股通行的交易日基数若研究对象是商品期货期权换成 365 反而会造成波动率虚低因为期货节假日同样有跳空风险。代码跑完后得到horizon_vol它是在未来 30 天这个特定区间内标的收益的标准差。这个数字接下来会作为预测波动率与期权市价隐含的波动率做对比。如果你希望更接近 GARCH(1,1) 的效果可以用pip install arch自行扩展但 EWMA 对期权挑选来说已经够用它抓住了波动率集聚且没有复杂的参数拟合不容易在本地环境里翻车。2.3 把预测波动率转换成标的价格分布波动率单独拿出来无法直接指导选哪个行权价必须把它变成价格分布。标准做法是假设标的价格服从几何布朗运动在这个假设下到期日价格 S_T 满足# 蒙特卡洛模拟到期价格分布 S0 df[close].iloc[-1] # 当前标的价格 mu 0.03 # 低风险偏好下的年化漂移率保守估计先设为无风险利率 T horizon_years # 到期时间单位年 simulations 20000 # 模拟路径条数越多分布越稳定 # 固定随机种子保证结果可复现 rng np.random.default_rng(42) Z rng.standard_normal(simulations) # 几何布朗运动的到期价格公式 ST S0 * np.exp((mu - 0.5 * annual_vol ** 2) * T annual_vol * np.sqrt(T) * Z) # 分布的分位数信息 percentiles np.percentile(ST, [5, 25, 50, 75, 95]) print(f5%分位: {percentiles[0]:.2f}) print(f50%分位: {percentiles[2]:.2f}) print(f95%分位: {percentiles[4]:.2f})代码里mu的取值需要特别说明在短期期权定价中标的的预期收益率对期权理论价格影响很小但对到期概率分布有影响。我做股票期权挑选时习惯把 mu 设为无风险利率而不是历史平均收益率原因有两个一是未来一段时间的超额收益很难预测用了历史高收益容易把 Call 的胜率算得虚高二是这能让模型保持保守宁可少一点收益预期也不要在挑选时高估胜率。模拟得到 ST 之后便可以计算任意行权价的到期胜率。比如某行权价 K 的 Call 胜率就是 ST 大于 K 的路径占比Put 相反。在下一章里我们会把这些胜率和期权的市场价格、希腊字母一起纳入挑选决策形成完整的挑选规则。3. 期权挑选的量化取舍理论价、胜率与希腊字母3.1 把预测分布喂给期权定价算出“你认为它值多少钱”有了一组到期价格路径我们可以不看 Black-Scholes 公式的解析解直接对路径做平均算出某个行权价期权的“蒙特卡洛理论价”。这样做的最大好处是它不依赖波动率是常数的假设也不怕标的未来分布带偏度缺点是计算量稍大但对个人电脑来说 2 万条路径的定价只是瞬间的事。代码如下def mc_option_price(ST, K, T, r, option_typecall): 用蒙特卡洛路径直接计算期权期望收益的现值。 ST: 模拟的到期价格数组 K: 行权价 T: 到期时间年 r: 无风险利率小数形式 if option_type call: payoff np.maximum(ST - K, 0) else: payoff np.maximum(K - ST, 0) # 期望收益折现到当前时间 price np.exp(-r * T) * np.mean(payoff) return price # 示例计算 5 个行权价对应的蒙特卡洛理论价 K_list [S0 * 0.9, S0 * 0.95, S0 * 1.0, S0 * 1.05, S0 * 1.1] r 0.02 # 无风险利率取当地短期国债利率水平 for K in K_list: mc_price mc_option_price(ST, K, T, r, call) print(fK{K:.2f}, 蒙特卡洛理论价{mc_price:.2f})这里的参数r取 2%对应无风险利率的保守值如果你做的是科创板或创业板个股期权它们的隐含折现率往往因为融券成本偏高而不同可以直接把 r 改为自己的资金成本。蒙特卡洛理论价出来后一定要和当前市场的期权报价做差。差值大于 0表示按你的预测分布市场报价偏低了差值小于 0说明你正在为别人的乐观预期买单。许多期权交易软件的“理论价格”用的就是这种方式区别只是标的分布假设更丰富。但仅有理论价还不够。理论价差只回答“贵不贵”没回答“赚不赚”。胜率、赔率和希腊字母风险需要纳入下一层筛选。3.2 用胜率和盈亏比筛掉“看着便宜但赢不了”的行权价期权挑选里胜率和赔率必须一起看。一个极其虚值的Call可能用蒙特卡洛定价看是“贵了”但当它的行权价在模拟分布 95% 分位之外时胜率可能只有 5%这种合约哪怕再贵 20%对买方来说也是低胜率赌博反过来深度实值期权胜率高但权利金占用巨大一旦标的没动Theta 磨损就能让收益率变成负数。常见的做法是同时计算两个指标再画一条决策线胜率不低于某个阈值且盈亏比不低于某个倍数两个都满足才进入候选池。def option_stats(ST, K, option_price_market, option_type, premium_per_contract10000): 胜率和盈亏比计算 ST: 模拟的到期价格数组 K: 行权价 option_price_market: 当前市场权利金每张 option_type: call 或 put premium_per_contract: 一份合约对应的标的数量股票期权通常为10000股或100股按合约规格填写 if option_type call: payoff np.maximum(ST - K, 0) else: payoff np.maximum(K - ST, 0) win_prob np.mean(payoff option_price_market) # 到期收益超过权利金算胜 avg_profit np.mean(payoff[payoff option_price_market] - option_price_market) avg_loss np.mean(abs(payoff[payoff option_price_market] - option_price_market)) profit_loss_ratio avg_profit / avg_loss if avg_loss 0 else np.inf # 期望收益 胜率 * 平均盈利 - 失败率 * 平均亏损 expected_return win_prob * avg_profit - (1 - win_prob) * avg_loss return win_prob, profit_loss_ratio, expected_return # 以一个行权价为例假设市场期权报价是 2.50 元 Kex S0 * 1.03 market_price 2.50 wp, plr, er option_stats(ST, Kex, market_price, call) print(f行权价{Kex:.2f}: 胜率{wp:.2%}, 盈亏比{plr:.2f}, 期望收益每张{er:.2f})这段代码中胜率的定义是“到期时权利金被完全覆盖”的路径占比也就是这一单赚到钱的概率盈亏比是赚的时候平均赚多少与亏的时候平均亏多少的比。注意期望收益是对每一个模拟路径的盈亏做了平均它才是长期重复这笔交易的真实收益预期。我一般会用“期望收益大于 0 且胜率不低于 35%”作为底线再结合希腊字母做最终挑选。3.3 希腊字母不是装饰是帮你看清风险暴露的仪表盘希腊字母在期权挑选里承担的角色是风险归因Delta 告诉你方向敞口有多大Theta 告诉你每持有一天要消耗多少时间价值Vega 告诉你波动率一变合约价值会怎么动。计算器里实现这几个字母最常见的是布莱克-舒尔斯解析公式from math import log, sqrt, exp from scipy.stats import norm def bs_greeks(S, K, T, r, sigma, option_typecall): S: 当前标的价格 K: 行权价 T: 剩余期限年 r: 无风险利率 sigma: 波动率注意这里和前面的 horizon_vol 一致用小数 d1 (log(S / K) (r 0.5 * sigma ** 2) * T) / (sigma * sqrt(T)) d2 d1 - sigma * sqrt(T) if option_type call: delta norm.cdf(d1) theta - (S * norm.pdf(d1) * sigma) / (2 * sqrt(T)) - r * K * exp(-r * T) * norm.cdf(d2) else: delta norm.cdf(d1) - 1 theta - (S * norm.pdf(d1) * sigma) / (2 * sqrt(T)) r * K * exp(-r * T) * norm.cdf(-d2) gamma norm.pdf(d1) / (S * sigma * sqrt(T)) vega S * norm.pdf(d1) * sqrt(T) return delta, gamma, theta, vega delta, gamma, theta, vega bs_greeks(S0, Kex, T, r, annual_vol) print(fDelta{delta:.3f}, Gamma{gamma:.3f}, Theta每天{theta/365:.4f}元, Vega{vega:.3f}元)看代码里的 theta单位是每年价格变化因此日度损耗要除以 365。我选期权时心里会有这样一笔账如果距离到期还有 30 天Theta 每天吃 0.05 元权利金是 2元那这单裸多每天要承受 2.5% 的时间价值损耗意味着标的方向判断不够快就很难翻身。因此在挑选时我倾向于把 Theta/权利金 的比率控制在 1.5% 以内超过这个值宁可换更远月或更实值的合约。Gamma 则反向参考近月平值期权的 Gamma 很大短线方向对了会很爽但容错率极低止损空间稍大就容易亏损。Vega 的用法则要在“买期权”和“卖期权”之间切换当隐含波动率处于历史 80% 分位以上时Vega 暴露会让买方承受波动率回落的风险这时候买方的胜率会被大幅压低相对地卖方在此时赚取波动率回落的收益是顺风局。这一层分析做完才真正算完成了“挑选”。4. 一个可落地的期权挑选计算器Python 命令行的完整实现4.1 计算器的输入设计与输出口径把前两章的逻辑合并可以做成一个命令行工具输入六项数据当前标的价格、行权价、剩余到期天数、市场报价的权利金、隐含波动率从行情终端读取、你自己的预测波动率用第 2 章的 EWMA 结果。输出包括内在价值与时间价值、BSM 理论价值、蒙特卡洛理论价值、胜率、盈亏比、Delta、Theta/Vega、以及最终的建议倾向。建议逻辑不搞“买”和“卖”的二元指令而是输出三种偏中性结论适合买方、适合卖方、暂不参与。规则如下def generate_advice(mc_price, market_price, win_prob, plr, iv, horizon_vol): 建议逻辑按三条规则叠加避免单一指标误导。 # 规则1: 预测波动率 vs 隐含波动率 vol_gap iv - horizon_vol # 规则2: 蒙特卡洛理论价 vs 市场价 price_gap mc_price - market_price # 规则3: 胜率与盈亏比底线 if win_prob 0.35 or plr 1.2: return 暂不参与胜率或盈亏比不达标 if vol_gap 0.05: # 隐含波动率比预测水平高很多卖方更有利 if price_gap -0.05 * market_price: return 适合卖方市场对波动率的定价偏高 return 适合卖方但差价空间一般注意管理Gamma风险 else: if price_gap 0.05 * market_price: return 适合买方理论价高于市价且波动率预期不过热 return 买方卖方均无显著优势建议等待更优报价这里的阈值是按经验设定的你可以按自己的风险偏好调整。vol_gap 超过 0.05 表示隐含波动率比预测波动率高出 5 个百分点这时候卖出期权赚波动率回归的钱是比较典型的卖方逻辑。price_gap 用占市价的百分比说话避免不同价位合约之间横向比较失真。注意当胜率或盈亏比达不到底线时脚本会直接否决这笔交易这条规则放在最前面是刻意的——它防止在负期望的合约上用“波动率优势”来合理化高风险行为。4.2 完整计算器脚本与运行效果下面拼出一个完整的、无外部数据依赖的脚本。它把所有辅助函数整合到同一个文件里方便读者直接复制保存运行。import numpy as np from math import log, sqrt, exp from scipy.stats import norm def ewma_vol_forecast(close_series, lam0.94, trading_days252): log_ret np.log(close_series / np.roll(close_series, 1))[1:] var_t log_ret[0] ** 2 for ret in log_ret[1:]: var_t lam * var_t (1 - lam) * ret ** 2 ann_vol np.sqrt(var_t) * np.sqrt(trading_days) return ann_vol def bs_price(S, K, T, r, sigma, option_typecall): d1 (log(S / K) (r 0.5 * sigma ** 2) * T) / (sigma * sqrt(T)) d2 d1 - sigma * sqrt(T) if option_type call: return S * norm.cdf(d1) - K * exp(-r * T) * norm.cdf(d2) else: return K * exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1) def mc_price_from_st(ST, K, T, r, option_typecall): if option_type call: payoff np.maximum(ST - K, 0) else: payoff np.maximum(K - ST, 0) return exp(-r * T) * np.mean(payoff) # 模拟到期价格 def simulate_st(S0, ann_vol, T_years, mu, n20000, seed42): rng np.random.default_rng(seed) Z rng.standard_normal(n) return S0 * np.exp((mu - 0.5 * ann_vol ** 2) * T_years ann_vol * np.sqrt(T_years) * Z) if __name__ __main__: # 用户输入部分 S0 100.0 # 当前标的价格 K 103.0 # 行权价 days 30 # 到期天数 market_price 2.5 # 市场权利金报价 iv 0.28 # 隐含波动率, 从行情终端读取 forecast_vol 0.22 # 用EWMA或其他模型得到的预测年化波动率 r 0.02 # 无风险利率 T days / 365 ST simulate_st(S0, forecast_vol, T, mur) # 评估指标 mc_val mc_price_from_st(ST, K, T, r, call) bs_val bs_price(S0, K, T, r, forecast_vol, call) wp, plr, exp_ret option_stats(ST, K, market_price, call) print(fBS理论价: {bs_val:.3f}) print(f蒙特卡洛理论价: {mc_val:.3f} (波动率按{forecast_vol:.2%}计算)) print(f胜率: {wp:.2%}, 盈亏比: {plr:.2f}, 期望收益: {exp_ret:.3f}) print(generate_advice(mc_val, market_price, wp, plr, iv, forecast_vol))运行这个脚本时重点看三个数之间的关系BS 理论价和蒙特卡洛理论价之间如果差异很大说明标的收益率分布偏离对数正态假设这可能是波动率水平不同导致的而市场报价和理论价之间的差才是真正能放进策略里的边际优势。也正因如此forecast_vol的取值会直接改变结论——若预测波动率设得过高所有买方合约都会显示理论价远高于市价导致建议系统整体偏多反过来设得过低系统又会把所有交易判成卖方机会。这也是我建议把 EWMA 的 lambda 从 0.90 到 0.97 分别跑一遍的原因得出的波动率区间至少能帮你判断结论是否具有鲁棒性。5. 期权挑选的五个高频踩坑现象、原因与解决5.1 隐含波动率已经高位运行还在继续买 Call现象标的价格平稳但期权权利金异常昂贵买 Call 后标的小涨却亏损随后几天标的微跌期权价值快速缩水。原因隐含波动率处于历史较高分位此时 Vega 很高波动率均值回归压过了方向收益。解决在下单前查看隐含波动率百分位比如过去一年内当前 IV 处于哪个位置我一般规定 IV 百分位高于 70% 时不主动买方开仓只考虑卖方策略或垂直价差控制波动率暴露。5.2 只看到期收益分布把 Theta 磨损抛到脑后现象模型显示某个 30 天到期的 Call 有 45% 胜率、盈亏比 2.0买进去以后每天亏一点点一个月后即使方向对了也没赚多少。原因期望收益是按到期收益算的没有把持仓过程中的每日 Theta 成本纳入资金占用。解决用第 3.3 节的 theta/权利金 比率做过滤阈值设置为 1.5% 以内如果目标标的波动率很高导致近月 Theta 太大就延长到期日至 45 天以上牺牲一点 Delta 换取时间价值衰减放缓。5.3 用 Black-Scholes 公式给深度虚值期权定价偏差很大现象一个行权价在标的当前价格 20% 之外的 CallBS 理论价显示为 0但市场上仍有人报价 0.05判断“高估了很多”去做卖方结果几天后标的因突发事件暴涨这单亏损巨大。原因BSM 假设收益率服从对数正态分布但真实市场存在明显的厚尾深度虚值期权的尾部风险被严重低估。解决不要单独依赖 BS 定价做深度虚值合约的筛选改用蒙特卡洛模拟并且模拟时把波动率替换成带偏度的随机波动率模型或者至少同时看胜率对尾部显著的行权价胜率底线应从 35% 提高到 50% 再考虑卖方。5.4 预测分布算出来了但概率没有被校准现象蒙特卡洛显示某个行权价的 Call 胜率 60%实际交易 20 次胜率只有 40%系统性高估。原因模拟时用了简单几何布朗运动且 mu 设定的漂移率偏高导致价格中心上移虚值 Call 胜率被高估。解决把 mu 改为无风险利率并用最近一年的实际行情回测模拟分布的 30 天校准情况。操作上很简单每个月记录一次预测的 90% 置信区间再检查实际价格落入区间的频率是否接近 90%。若偏差大需要调整 mu 或选择更复杂的分布模型。5.5 忽略除息除权对期权合约要素的影响现象标的股票在期权存续期内分红除息股价跳空低开持有 Call 的账户瞬间出现大幅亏损而模型计算的期望收益完全是正的。原因股票的除息降低了标的价格但很多初学者用不复权的价格序列做预测实际期权定价使用的是调整后期权行权价或标的价格两边没对齐。解决做预测性分析时如果有已知的除息日应把 S0 调整为除息后的估计价格再模拟如果是 ETF 期权则不必过度担心但个股期权必须在期权到期前检查分红日历。这是我吃过亏的地方后来养成的习惯是在每次批量计算前先跑一遍是否有除权除息事件有则在参数里注明。6. 把计算器放进历史里还原本相一个轻量级回测技巧计算器给出的建议到底靠不靠谱最终要看它在历史上是否赚到了钱。常见做法是每个月第一个交易日跑一次建议模块记录建议方向再到期权到期日计算实际收益连续统计 12 个月。这样回测的意义在于验证胜率和盈亏比是否和模拟一致而不只是验证价格预测准不准。回测逻辑很简单# 伪代码表示回测主循环具体行情数据按各自平台导出 for month in range(12): # 1. 在每月初获取当前价格、IV、剩余30天到期的期权报价 # 2. 用EWMA预测波动率跑计算器得到建议 # 3. 如果是“适合买方”记录当天买入的Call报价 # 4. 在到期日计算这份Call的实际现金了结收益 # 5. 汇总每笔交易的收益率回测中我常加两个细节。第一个是把计算器的建议都保存下来即使某次建议是“暂不参与”也记录持有现金的收益率为零这样统计出来的总收益率才是“按建议执行”的真实表现。第二个是严格记录交易滑点期权买卖价差普遍比股票大尤其虚值合约的价差可能达到权利金的 10% 以上我一般用买一卖一价的中间价作为成交价成本上再增加 0.5% 的摩擦系数防止回测结果过于乐观。回测运行完要关注的不是总收益率而是胜率是否和模型输出一致、实际盈亏比是否有 20% 以上的缩水。如果胜率差很多大概率是预测波动率偏乐观应调低 forecast_vol 或者增加 mu 的保守程度如果盈亏比缩水严重则需要检查是否总是被价差吃掉利润可以考虑换到流动性更好的平值或近实值合约。我自己就犯过一个典型错误回测时没算除息连续几个月的 Call 买方亏损都误判为模型失效后来把除息日历加入模拟后才恢复正常结论。这些验证步骤不需要复杂的回测框架用 Excel 或 pandas 就能完成但它们决定了计算器给出的建议是可用于实战还是停留在理论上看着合理。希望这套分析和计算流程能帮你在期权链里更有底气地挑出那一笔值得下手的合约也希望我的踩坑教训能让你少走一段弯路。本文还有配套的精品资源点击获取