☰
股票期权量化分析框架:从选股到盈亏计算
2026/10/17 5:20:43 网站建设 项目流程

简介:这份资源围绕股票与期权的挑选展开,面向具备一定Python与数据分析基础、希望将机器学习与量化思路落地到实盘决策的投资者与数据科学学习者。内容涵盖历史波动率推算30天乃至一年价格区间、基于Wycoff方法的牛熊判断、推荐引擎、决策预测与少量博弈论元素,并配有计算器与可视化模块,帮助读者搭建从数据到建议的完整分析链路。压缩包整体约325.82MB,以zip形式提供,内含CSS样式文件、Excel原型及若干网络采集的表格片段,Excel原型是整站构建的基础,Python脚本与pandas、机器学习相关代码承担分析与预测任务,目录按功能模块划分便于检索。目前已有162人学习下载。读者可据此理解波动率定价、推荐逻辑与预测模型的组合方式,并参考Excel原型与脚本结构复现自己的股票期权分析工具。

1. 股票期权分析:从选标的到算盈亏,一套能跑起来的量化框架

很多人做期权亏钱,不是因为方向看错,而是因为一开始就选错了合约。同一个看涨观点,买近月虚值可能三天归零,买远月平值却能扛住两周震荡;同样一笔资金,卖put和买call的风险敞口完全不是一个量级。股票期权分析要解决的核心问题就三个:选哪只股票、选哪个合约、这个价格值不值得下手。这篇笔记不讲教科书上的Black-Scholes推导,而是把我自己搭的一套分析流程拆开——从股票筛选到期权链过滤,从预测性信号到盈亏计算器,每一步都有可复现的代码和参数。适合已经开过期权账户、想从凭感觉下单转向系统化决策的从业者,也适合刚接触期权、想先跑通一套分析框架再投钱的新手。

2. 股票端筛选:先找到值得配期权的标的

2.1 为什么不能直接在期权链里翻合约

期权是股票的衍生品,标的选错了,合约选得再精细也是白搭。我见过太多人打开期权链,按成交量排序,挑一个看起来便宜的合约就买了,结果标的本身处于下跌趋势,call再便宜也是归零。常见做法是先用股票端的量化指标筛出一批候选池,再进入期权链做二次过滤。这一步的核心逻辑是:期权策略的收益结构决定了它对标的的走势特征有不同要求。买call需要标的在到期前有足够大的上行幅度,卖put需要标的不会暴跌,跨式策略需要波动率放大。所以股票筛选不是简单选“好公司”,而是选“走势特征匹配策略”的标的。

我一般会从三个维度做初筛:趋势强度、波动率水平、流动性。趋势强度用ADX或者简单的均线排列来判断,波动率用ATR或者历史波动率百分位,流动性看日均成交额和买卖价差。这三个维度不需要同时最优,但至少要有一个维度突出,否则期权策略很难找到优势。

2.2 用Python拉数据并计算筛选指标

下面这段代码用yfinance拉取股票数据,计算趋势、波动率和流动性三个维度的指标。注意yfinance的接口偶尔会变,如果报错先检查版本。

import yfinance as yf import pandas as pd import numpy as np def screen_stocks(tickers, period="6mo"): """ 对候选股票列表做初筛 tickers: 股票代码列表 period: 回看周期,默认6个月 返回: 包含趋势、波动率、流动性指标的DataFrame """ results = [] for ticker in tickers: try: df = yf.download(ticker, period=period, progress=False) if len(df) < 60: continue # 趋势强度:20日均线相对60日均线的偏离度 ma20 = df['Close'].rolling(20).mean() ma60 = df['Close'].rolling(60).mean() trend_score = (ma20.iloc[-1] - ma60.iloc[-1]) / ma60.iloc[-1] # 波动率:20日年化历史波动率 returns = df['Close'].pct_change().dropna() hv20 = returns.tail(20).std() * np.sqrt(252) # 流动性:日均成交额(百万) avg_volume = df['Volume'].tail(20).mean() avg_price = df['Close'].tail(20).mean() liquidity = avg_volume * avg_price / 1e6 results.append({ 'ticker': ticker, 'trend_score': round(trend_score, 4), 'hv20': round(hv20, 4), 'liquidity_m': round(liquidity, 2), 'last_price': round(df['Close'].iloc[-1], 2) }) except Exception as e: print(f"{ticker} 数据拉取失败: {e}") continue result_df = pd.DataFrame(results) return result_df.sort_values('liquidity_m', ascending=False) # 示例:对一批科技股做初筛 tickers = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'TSLA', 'META', 'GOOGL', 'AMZN'] df = screen_stocks(tickers) print(df.to_string(index=False))

这段代码的逻辑说明:trend_score为正表示短期均线在长期均线之上,趋势偏多;hv20是年化历史波动率,数值越大说明近期波动越剧烈,期权权利金通常也越贵;liquidity_m是日均成交额,低于5000万的标的期权链买卖价差往往很大,不适合做策略。参数方面,period可以改成"1y"看更长周期,但太长会稀释近期趋势信号。均线周期20和60是常用组合,短线可以改成10和30,长线改成50和200。

筛选结果出来后,我一般会保留trend_score大于0、hv20在0.2到0.8之间、liquidity_m大于5000万的标的。hv20太低说明波动不够,期权买方很难赚到钱;hv20太高说明市场已经定价了很大波动,买方容易买贵。

2.3 波动率百分位:判断期权贵不贵的关键指标

历史波动率本身没有绝对的高低,关键看它在过去一年里处于什么位置。这就是波动率百分位(IV Rank的简化版)。计算方法是:取过去252个交易日的滚动20日历史波动率,看当前值在历史分布中的百分位。

def hv_percentile(ticker, lookback=252): """ 计算当前20日历史波动率在过去lookback个交易日中的百分位 返回0-100的数值,越高说明当前波动率越贵 """ df = yf.download(ticker, period="2y", progress=False) returns = df['Close'].pct_change().dropna() rolling_hv = returns.rolling(20).std() * np.sqrt(252) rolling_hv = rolling_hv.dropna() current_hv = rolling_hv.iloc[-1] percentile = (rolling_hv < current_hv).sum() / len(rolling_hv) * 100 return round(percentile, 1), round(current_hv, 4) # 对筛选出的标的逐个计算 for t in ['AAPL', 'NVDA', 'TSLA']: pct, hv = hv_percentile(t) print(f"{t}: HV百分位={pct}%, 当前HV={hv}")

百分位低于30%说明当前波动率处于低位,期权买方有优势;高于70%说明波动率偏高,卖方策略更合适。这个指标不是绝对的,但能帮你避免在波动率高点买入期权。我自己的习惯是:百分位超过60%就不做买方了,宁可等回调。

3. 期权链过滤:从几百个合约里挑出能做的

3.1 期权链的核心筛选维度

股票筛完之后,进入期权链。一个流动性好的标的,期权链上可能有几百个合约,到期日从本周到两年后,行权价从深度实值到深度虚值。全看一遍不现实,必须用规则过滤。我一般按四个维度筛:到期日、行权价、买卖价差、未平仓量。

到期日方面,买方策略我倾向于选30到60天到期的合约。太近的时间价值衰减太快,太远的权利金太贵、杠杆不够。卖方策略可以选更近的,比如7到21天,因为时间价值衰减对卖方有利。行权价方面,买方选delta在0.3到0.5之间的合约,卖方选delta在0.15到0.3之间的。买卖价差要小于合约价格的5%,否则一进一出就亏掉一大截。未平仓量要大于500,保证流动性。

3.2 用代码拉取期权链并做过滤

def filter_options(ticker, min_days=30, max_days=60, min_delta=0.3, max_delta=0.5, max_spread_pct=0.05, min_oi=500): """ 过滤期权链,返回符合条件的call合约 """ stock = yf.Ticker(ticker) expirations = stock.options filtered = [] for exp in expirations: opt_chain = stock.option_chain(exp) calls = opt_chain.calls # 计算到期天数 exp_date = pd.to_datetime(exp) days_to_exp = (exp_date - pd.Timestamp.now()).days if days_to_exp < min_days or days_to_exp > max_days: continue for _, row in calls.iterrows(): # 过滤delta范围 if pd.isna(row.get('delta', None)): continue if abs(row['delta']) < min_delta or abs(row['delta']) > max_delta: continue # 过滤买卖价差 bid = row['bid'] ask = row['ask'] if bid <= 0 or ask <= 0: continue mid = (bid + ask) / 2 spread_pct = (ask - bid) / mid if spread_pct > max_spread_pct: continue # 过滤未平仓量 if row['openInterest'] < min_oi: continue filtered.append({ 'expiration': exp, 'days': days_to_exp, 'strike': row['strike'], 'bid': bid, 'ask': ask, 'mid': round(mid, 2), 'delta': round(row['delta'], 3), 'iv': round(row['impliedVolatility'], 4), 'oi': int(row['openInterest']), 'volume': int(row['volume']), 'spread_pct': round(spread_pct, 4) }) result = pd.DataFrame(filtered) if len(result) > 0: result = result.sort_values('oi', ascending=False) return result # 示例 calls = filter_options('AAPL') print(calls.head(10).to_string(index=False))

这段代码的关键参数说明:min_days和max_days控制到期日窗口,30到60天是买方策略的常用区间;min_delta和max_delta控制行权价范围,0.3到0.5对应平值到轻度虚值;max_spread_pct设为0.05表示买卖价差不超过中间价的5%;min_oi设为500保证有足够的流动性。这些参数不是固定的,短线交易可以把days缩短到14到30,delta放宽到0.2到0.6。

注意yfinance返回的期权链数据里,delta和impliedVolatility字段有时候是NaN,尤其是流动性差的合约。遇到这种情况直接跳过,不要试图用其他字段推算,数据质量不够的情况下硬算出来的delta没有参考价值。

3.3 隐含波动率与历史波动率的对比

筛选出合约后,还要看一个关键指标:IV相对于HV的溢价。如果IV明显高于HV,说明期权市场定价的波动预期比实际发生的高,买方可能买贵了。计算方法是把筛选出的合约的IV和标的的HV20做对比。

def iv_hv_spread(ticker, calls_df): """ 计算期权IV与标的HV20的差值 正值表示IV高于HV,期权偏贵 """ _, hv = hv_percentile(ticker) avg_iv = calls_df['iv'].mean() spread = avg_iv - hv return round(avg_iv, 4), round(hv, 4), round(spread, 4) avg_iv, hv, spread = iv_hv_spread('AAPL', calls) print(f"平均IV={avg_iv}, HV20={hv}, IV-HV={spread}")

如果spread大于0.1,说明期权比历史波动率贵了10个百分点以上,买方要谨慎。如果spread为负,说明期权相对便宜,买方有优势。这个指标配合波动率百分位一起看,能过滤掉大部分“买贵了”的情况。

4. 预测性分析:用简单模型判断方向概率

4.1 为什么不用复杂模型

很多人一提到预测性分析就想到LSTM、Transformer,但对于期权交易来说,方向预测的准确率只要比50%高一点就有价值,关键是盈亏比和仓位管理。我试过用复杂的深度学习模型,过拟合严重,实盘表现还不如简单的逻辑回归。所以这里给的是一个轻量级的预测框架:用技术指标做特征,用逻辑回归输出方向概率,重点是可解释和可复现。

4.2 特征工程与模型训练

from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score def build_features(ticker, period="2y"): """ 构建预测特征:动量、波动率、成交量变化 """ df = yf.download(ticker, period=period, progress=False) df['ret5'] = df['Close'].pct_change(5) df['ret20'] = df['Close'].pct_change(20) df['vol_ratio'] = df['Volume'] / df['Volume'].rolling(20).mean() df['hv20'] = df['Close'].pct_change().rolling(20).std() * np.sqrt(252) df['ma_ratio'] = df['Close'] / df['Close'].rolling(50).mean() # 标签:未来5天是否上涨 df['target'] = (df['Close'].shift(-5) > df['Close']).astype(int) df = df.dropna() feature_cols = ['ret5', 'ret20', 'vol_ratio', 'hv20', 'ma_ratio'] return df[feature_cols], df['target'] def train_model(ticker): X, y = build_features(ticker) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, shuffle=False ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) model = LogisticRegression(max_iter=1000) model.fit(X_train_scaled, y_train) pred = model.predict(X_test_scaled) acc = accuracy_score(y_test, pred) # 输出最新一天的方向概率 latest = scaler.transform(X.iloc[[-1]]) prob = model.predict_proba(latest)[0][1] return acc, prob, model.coef_[0] acc, prob, coef = train_model('AAPL') print(f"测试集准确率: {acc:.3f}") print(f"当前上涨概率: {prob:.3f}") print(f"特征权重: {dict(zip(['ret5','ret20','vol_ratio','hv20','ma_ratio'], coef.round(3)))}")

这段代码的逻辑:用过去5天和20天的收益率、成交量比率、20日历史波动率、价格与50日均线的比值作为特征,预测未来5天是否上涨。逻辑回归的系数可以告诉你哪个特征在起主要作用。准确率能到55%以上就算不错了,不要追求太高,过拟合的模型实盘会打脸。

参数方面,test_size=0.2表示用最后20%的数据做测试,shuffle=False保证时间顺序不乱。max_iter=1000是逻辑回归的迭代次数,如果报收敛警告可以调大。特征窗口5天和20天是常用值,短线可以改成3天和10天。

4.3 把预测概率转化为期权策略建议

预测概率本身不能直接下单,要结合盈亏比和波动率。我一般用这样的规则:如果上涨概率大于0.6,且IV-HV为负,考虑买call;如果上涨概率小于0.4,且IV-HV为正,考虑卖call;如果概率在0.4到0.6之间,不做方向性策略,考虑跨式或观望。

def strategy_suggestion(prob, iv_hv_spread, hv_pct): """ 根据预测概率和波动率指标给出策略建议 """ if prob > 0.6 and iv_hv_spread < 0: return "买入看涨期权(方向+波动率双重优势)" elif prob > 0.6 and iv_hv_spread >= 0: return "谨慎买入看涨期权(方向有利但波动率偏贵)" elif prob < 0.4 and iv_hv_spread > 0.1: return "卖出看涨期权(方向+波动率双重优势)" elif prob < 0.4: return "观望或轻仓卖出看涨" elif hv_pct > 70: return "波动率高位,考虑卖出跨式" else: return "信号不明确,观望" suggestion = strategy_suggestion(prob, spread, 45) print(f"策略建议: {suggestion}")

这个规则表不是万能的,但能帮你把预测信号和期权定价结合起来,避免只看方向不看波动率。实际使用中,我还会加入仓位管理:单笔期权买方仓位不超过总资金的2%,卖方不超过5%,因为卖方虽然胜率高但尾部风险大。

5. 盈亏计算器:下单前先算清楚最坏情况

5.1 期权盈亏计算的核心变量

期权盈亏计算比股票复杂,因为涉及到期日、行权价、权利金、隐含波动率等多个变量。一个完整的计算器要能回答:到期时标的在什么价格我保本、什么价格我翻倍、什么价格我亏光。对于买方,最大亏损就是权利金,关键是算保本点和目标收益对应的标的涨幅。对于卖方,最大收益是权利金,关键是算被行权后的亏损敞口。

5.2 到期盈亏计算代码

def option_payoff(option_type, strike, premium, spot_range): """ 计算期权到期时的盈亏 option_type: 'call' 或 'put' strike: 行权价 premium: 买入/卖出的权利金(每股) spot_range: 标的到期价格数组 """ if option_type == 'call': intrinsic = np.maximum(spot_range - strike, 0) else: intrinsic = np.maximum(strike - spot_range, 0) pnl = intrinsic - premium return pnl def breakeven(option_type, strike, premium): """计算保本点""" if option_type == 'call': return strike + premium else: return strike - premium # 示例:AAPL 现价190,买入行权价195的call,权利金3.5 spot = np.arange(170, 220, 1) pnl = option_payoff('call', 195, 3.5, spot) be = breakeven('call', 195, 3.5) print(f"保本点: {be}") print(f"标的涨到210时盈亏: {option_payoff('call', 195, 3.5, np.array([210]))[0]:.2f}") print(f"标的跌到180时盈亏: {option_payoff('call', 195, 3.5, np.array([180]))[0]:.2f}")

这段代码计算的是到期日的静态盈亏,不考虑提前平仓。保本点对call是行权价加权利金,对put是行权价减权利金。实际交易中,你可以在到期前任何时间平仓,盈亏取决于当时的期权价格,而不是到期内在价值。但到期盈亏是判断这笔交易值不值得做的基准。

5.3 加入隐含波动率的动态盈亏估算

到期盈亏只考虑了内在价值,忽略了时间价值和波动率变化。更精确的估算需要用Black-Scholes公式反推期权理论价格。下面是一个简化版的计算,用scipy的norm模块。

from scipy.stats import norm def bs_price(S, K, T, r, sigma, option_type='call'): """ Black-Scholes期权理论价格 S: 标的现价 K: 行权价 T: 到期时间(年) r: 无风险利率 sigma: 隐含波动率 """ d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T)) d2 = d1 - sigma*np.sqrt(T) if option_type == 'call': price = S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2) else: price = K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1) return price def dynamic_pnl(S_new, K, T_remain, r, sigma_new, entry_premium, option_type='call'): """ 计算标的变动后的期权盈亏(考虑时间价值和波动率变化) """ new_price = bs_price(S_new, K, T_remain, r, sigma_new, option_type) return new_price - entry_premium # 示例:买入AAPL 195 call,权利金3.5,剩余30天,IV 25% # 假设标的涨到200,IV不变,剩余20天 pnl_dynamic = dynamic_pnl(200, 195, 20/365, 0.05, 0.25, 3.5, 'call') print(f"标的涨到200时的动态盈亏: {pnl_dynamic:.2f}")

这个计算器的价值在于:你可以模拟不同情景下的盈亏,比如标的涨了但IV下降,期权价格可能不涨反跌。很多买方亏钱就是因为只看了方向,没考虑IV回落。参数方面,T_remain是剩余到期时间(年化),r是无风险利率,sigma_new是新的隐含波动率。实际使用中,IV的变化很难预测,但你可以做敏感性分析:IV下降5个百分点,盈亏会变多少。

6. 避坑与常见问题:我踩过的五个坑

6.1 坑一:流动性陷阱——买卖价差吃掉利润

现象:筛选出一个看起来不错的合约,买入后想平仓,发现bid和ask差了20%,一进一出亏了10%。

原因:只看成交量没看买卖价差。有些合约成交量很大,但做市商报价很宽,尤其是深度虚值或远月合约。

解决:在过滤条件里加上spread_pct < 0.05,并且优先选未平仓量大于1000的合约。如果价差还是大,用限价单在中间价附近挂单,不要用市价单。

6.2 坑二:IV高位买入——方向对了还亏钱

现象:标的确实涨了,但买的call没怎么涨,甚至跌了。

原因:买入时IV处于高位,标的上涨的同时IV回落,时间价值衰减和波动率下降抵消了内在价值的增长。

解决:买入前必看HV百分位,超过60%就不做买方。如果非要做,选delta更高的实值合约,减少时间价值占比。

6.3 坑三:到期日选太近——时间价值衰减太快

现象:买入7天到期的虚值call,标的横盘三天,期权归零。

原因:近月虚值合约的时间价值衰减呈加速趋势,最后一周每天衰减可能超过10%。

解决:买方策略选30到60天到期的合约,给标的足够的运动时间。如果要做末日轮,仓位控制在总资金的0.5%以内。

6.4 坑四:预测模型过拟合——回测漂亮实盘打脸

现象:用复杂模型回测准确率70%,实盘连续亏损。

原因:特征太多、样本太少、未来函数泄露。比如用了未来数据做特征,或者训练集和测试集没有按时间分割。

解决:特征控制在5到8个,用时间序列分割(shuffle=False),准确率超过60%就要怀疑过拟合。简单模型+严格风控比复杂模型更可靠。

6.5 坑五:忽略仓位管理——一次亏损吃掉十次盈利

现象:卖方策略连续赚了十次小钱,一次标的暴涨亏掉全部利润还倒贴。

原因:卖方最大收益有限,最大亏损无限,仓位过重时尾部风险致命。

解决:卖方仓位不超过总资金的5%,并且用价差策略(如bull put spread)限制最大亏损。买方仓位不超过2%,因为归零概率高。

7. 进阶技巧:把分析框架串成每日工作流

7.1 每日检查清单与自动化脚本

把前面所有步骤串起来,形成一个每日运行的脚本。我自己的习惯是每天早上花15分钟跑一遍,输出一份候选清单和策略建议。

def daily_workflow(tickers): """ 每日工作流:筛选股票 -> 过滤期权 -> 预测方向 -> 输出建议 """ print("=" * 50) print("第一步:股票初筛") stock_df = screen_stocks(tickers) candidates = stock_df[ (stock_df['trend_score'] > 0) & (stock_df['hv20'].between(0.2, 0.8)) & (stock_df['liquidity_m'] > 5000) ] print(candidates.to_string(index=False)) print("\n" + "=" * 50) print("第二步:期权链过滤与预测") for _, row in candidates.iterrows(): ticker = row['ticker'] try: calls = filter_options(ticker) if len(calls) == 0: continue acc, prob, _ = train_model(ticker) avg_iv, hv, spread = iv_hv_spread(ticker, calls) hv_pct, _ = hv_percentile(ticker) suggestion = strategy_suggestion(prob, spread, hv_pct) print(f"\n{ticker}:") print(f" 模型准确率={acc:.3f}, 上涨概率={prob:.3f}") print(f" IV-HV={spread:.3f}, HV百分位={hv_pct}%") print(f" 建议: {suggestion}") print(f" 候选合约数: {len(calls)}") except Exception as e: print(f"{ticker} 分析失败: {e}") continue # 运行 daily_workflow(['AAPL', 'MSFT', 'NVDA', 'AMD', 'TSLA'])

这个工作流脚本把股票筛选、期权过滤、方向预测、策略建议串在一起,输出一份可操作的清单。实际使用中,我会把结果保存到CSV,方便跟踪历史信号的表现。

7.2 用价差策略替代单腿:降低尾部风险

单腿买方归零概率高,单腿卖方尾部风险大。进阶做法是用价差策略:看涨时用bull call spread(买平值call+卖虚值call),看跌时用bear put spread。价差策略降低了权利金成本,也限制了最大亏损。

def bull_call_spread(long_strike, short_strike, long_premium, short_premium, spot_range): """ 牛市看涨价差盈亏 """ long_pnl = option_payoff('call', long_strike, long_premium, spot_range) short_pnl = -option_payoff('call', short_strike, short_premium, spot_range) return long_pnl + short_pnl # 示例:买195 call花3.5,卖205 call收1.2 spot = np.arange(180, 220, 1) spread_pnl = bull_call_spread(195, 205, 3.5, 1.2, spot) max_loss = spread_pnl.min() max_profit = spread_pnl.max() be = 195 + (3.5 - 1.2) print(f"最大亏损: {max_loss:.2f}") print(f"最大盈利: {max_profit:.2f}") print(f"保本点: {be:.2f}")

价差策略的最大亏损是净权利金支出,最大盈利是行权价差减净权利金。这个结构适合方向判断有把握但不想承担无限风险的场景。我自己的习惯是:单腿买方只在IV-HV为负且概率大于0.65时做,其他情况一律用价差。

7.3 一个我坚持了三年的习惯

每次下单前,把盈亏计算器的结果截图保存,包括保本点、最大亏损、目标收益对应的标的涨幅。交易结束后,回头对比实际走势和当初的假设。这个习惯帮我发现了很多次“看对方向但选错合约”的问题——有时候不是方向错了,是到期日太近或者行权价太远。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询