ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

股票期权分析实战:从数据清洗到建议引擎的完整决策流程

股票期权分析实战:从数据清洗到建议引擎的完整决策流程 简介这是一份面向股票与期权交易者、金融数据分析爱好者的Python项目资料围绕标的筛选、期权挑选、预测分析与决策建议展开。站点基于Excel原型搭建主文件夹包含CSS样式数据层集成历史波动率、Wyckoff方法的多空判断并规划有推荐引擎、博弈论辅助、计算器及即将加入的机器学习模块第一个选项卡提供摘要分析与三十日/一年期价格估计第二个选项卡负责推荐逻辑整体适合希望将数据分析、可视化和预测模型应用于投资场景的Python学习者参考。从预览信息看站点保留了Excel原型的设计思路并基于开源数据整理股票价格与期权相关指标适合作为量化投研与机器学习辅助决策的入门模板。资源以zip压缩包形式发布大小约325.82MB文件总数与具体类型明细暂无数据。已有162人浏览学习项目模块划分清晰、可二次开发适合作为构建期权分析工具、补充量化投研思路的实践样例。1. 股票期权分析项目不是又一个预测模型而是把决策拆成可计算步骤这份「股票期权分析」项目我拆完第一感觉是它没有把宝押在「预测准不准」上而是把股票挑选、期权挑选、预测性分析、建议引擎和盈亏计算器打包成了一整套决策流程。你输入一个股票池它先算出每只股票的上涨概率和风险指标再基于历史波动率筛出值得关注的期权合约最后用博弈论思路告诉你「该不该动手、动手买哪个」盈亏计算器则把你最坏和最好的结果都摊在眼前。适合想用 Python 做量化分析和期权交易的从业者尤其是对 pandas、机器学习建模有一定基础但还没把「预测结果」转化成「下单动作」的人。这个项目让我意识到期权分析的关键不是概率多准确而是从数据到动作的每一环都别断掉。2. 数据准备与特征工程先拉干净数据再谈预测模型2.1 从 yfinance 拉行情复权价与时间窗的选择做股票期权分析第一步不是写模型而是把行情数据搞干净。这个项目用的是 yfinance 作为数据源虽然它现在没有官方 API但胜在免费、无需 token个人研究和复现足够用。我一般会先用它拉日线数据再把列名统一成小写避免后面写特征时大小写翻车。import yfinance as yf import pandas as pd def fetch_stock_data(ticker, start2020-01-01, end2024-12-31): df yf.download(ticker, startstart, endend, auto_adjustTrue) df.rename(columnslambda x: x.lower(), inplaceTrue) df[return_1d] df[close].pct_change() return df df fetch_stock_data(AAPL) print(df.tail())这段代码里有两个关键参数。auto_adjustTrue表示拿到的 price 已经按拆股和股息做了复权这样计算收益率不会出现因为除权造成的假跳空return_1d是当天相对前一天的收益率后面算波动率、构造标签都要用它。注意rename这一步不能省yfinance 返回的列名是大写的Close、Openpandas 列名区分大小写统一小写后写df[close]才不会报 KeyError。时间窗的选择也有讲究。我一般把训练窗口设为 4 年左右覆盖至少一个完整的涨跌周期预测窗口则根据你要预测的持有周期来定项目里默认用 5 日后的涨跌作为标签对应短线期权仓位。如果你的策略是周频或月频就把shift(-5)改成shift(-20)同时特征窗口也要同步调整否则特征周期和标签周期不匹配模型学出来的东西会怪。2.2 技术指标与标签构造别把未来函数写进去特征工程这一步项目里封装了一个add_features函数把均线、RSI、波动率、目标标签一次性算完。很多新手在这里最容易踩坑用当天的收盘价去预测当天或者用了未来信息后没剔除回测结果好看到假。def add_features(df, window14): close df[close] df[ma_5] close.rolling(5).mean() df[ma_20] close.rolling(20).mean() delta close.diff() gain delta.clip(lower0).rolling(window).mean() loss (-delta.clip(upper0)).rolling(window).mean() rs gain / loss df[rsi] 100 - (100 / (1 rs)) df[volatility] df[return_1d].rolling(window).std() * (252 ** 0.5) df[target] (close.shift(-5) close).astype(int) return df.dropna()这里有三个地方值得细看。第一个是 RSI 的计算gain用的是下跌时的零值平均loss用的是上涨时的零值平均二者相除得到相对强度再转成 0-100 区间。第二个是volatility的年化处理把日收益标准差乘以 252 的平方根换算成年化波动率期权定价里 IV 和这个量直接可比。第三是target用close.shift(-5)拿到 5 日后收盘价再和今天比较得到 1/0 标签这样模型学的是「未来 5 天是否上涨」而不是当天涨跌。dropna()会把指标还没收敛的前 14 行删掉避免把空值喂给模型。如果你用这套特征直接跑机器学习我建议先做个相关性检查。像ma_5和ma_20高度相关是正常的但决策树类模型不敏感如果你换成线性模型就最好先标准化或剔除冗余特征。项目里默认是梯度提升树所以没有做特征缩放这是合理的。2.3 参数表与数据清洗边界下面这张表是项目里比较核心的几个参数我复现时照着默认值跑了一遍基本没出大问题。如果你想迁移到自己的股票池改的最多的就是 ticker、start/end 和 window。参数默认值作用我调整时注意的点tickerAAPL标的代码换成你的股票池需保证 yfinance 能拉到start/end2020/2024数据区间至少覆盖一个涨跌周期auto_adjustTrue复权设为 False 会导致除权日收益率异常window14RSI/波动率窗口与标的交易频率强相关日线用 14shift 天数5预测持有期改成 20 时特征窗口也要放大数据清洗的边界比很多人想的要宽。除了去掉空值还要检查是否有交易日缺失比如美股节假日、是否有价格突变拆股未复权时会出现。我一般会在fetch_stock_data后面加一行df df.loc[df[volume] 0]把停牌日剔掉。项目里的 dropna 会处理大部分问题但如果你发现某只股票的数据量比其他股票少很多大概率是上市时间短或者退市了这时候要么补全要么直接跳过别硬算。另外要强调一点yfinance 拉到的数据是「生存者偏差」的一部分。你现在能拉到的都是还活着的股票如果你用这个数据集做全市场回测会高估策略表现。所以项目更适合用于已知股票池的「决策辅助」而不是用来发现「未来会涨的股票」。这个认知决定了我后面怎么解读预测结果。3. 预测模型与建议引擎机器学习给概率博弈论给动作3.1 价格方向预测梯度提升与时间序列交叉验证特征造好之后项目里的预测部分用的是GradientBoostingClassifier。为什么选它而不是 LSTM 或者 XGBoost因为这份资源的定位是「能跑起来、能解释」梯度提升对表格数据非常友好不需要做特征缩放调参下限低而且可以输出概率后面做建议引擎要用概率而不是单纯的 0/1 标签。常见做法是先拿决策树模型跑通再根据效果换成更重的模型。项目里默认参数已经能出一个不错的基准。from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report def train_model(df, features): X df[features] y df[target] split_idx int(len(df) * 0.8) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] model GradientBoostingClassifier( n_estimators200, max_depth3, learning_rate0.05, random_state42 ) model.fit(X_train, y_train) print(classification_report(y_test, model.predict(X_test))) return model这个切分方式是「按时间顺序」的 8/2 切分不是随机切分这是对的。时间序列数据如果随机打乱会让模型偷看未来信息测试集分数虚高。但我必须提醒你train_model里的单次时间切分仍然不够稳健因为实验窗口可能正好是某一段特殊行情。我通常在跑通后直接用后续的滚动回溯替代这次切分那个结果才敢作为调参依据。参数上n_estimators200控制树的棵数太小容易欠拟合太大会过拟合且有显存压力max_depth3限制每棵树深度防止模型记住噪声learning_rate0.05是每棵树的贡献步长调小一点能提升泛化但需要更多棵树。如果训练集和验证集准确率差距很大先降learning_rate再增n_estimators而不是无脑调深度。项目里还开了random_state42保证每次运行可复现。3.2 基于博弈论的建议为什么「预测涨」不等于「该买入」拿到模型输出的上涨概率后项目里并没有简单粗暴地「概率大于 0.5 就买入」。这正是它标题里「建议」二字的落点。它引入了一个非常轻量的博弈论思路你作为交易者面对的不是一张静态概率表而是一个由对手盘组成的市场。你买入后赚到的钱本质上来自对手盘的损失所以需要考虑「如果你赢了能赢多少输了会输多少」。项目里实现了一个简化的recommend_action函数它把预测概率、当前仓位和最大亏损容忍度组合成一个决策分数。虽然不包含严格的纳什均衡推导但决策逻辑体现了博弈论里的 maxmin 思想——先看最坏情况是否可承受再考虑期望收益。def recommend_action(prob_up, position_pct, max_loss_tolerance0.05): # 假设单次收益波动 ±3%用概率加权成期望收益 expected_gain prob_up * 0.03 - (1 - prob_up) * 0.03 if expected_gain 0.005 and position_pct max_loss_tolerance: action 买入 elif expected_gain -0.005: action 减仓 else: action 持有 return {action: action, expected_gain: expected_gain, prob_up: prob_up}这段代码里的position_pct是该股票在你组合里的仓位占比max_loss_tolerance是你最多允许它亏掉总资金的比例。当期望收益超过千分之五且仓位没超限才买入当期望收益为负则减仓其余情况持有。这个推荐不是让你照单全收而是把「预测」翻译成「动作」并且把风险敞口量化出来。如果你自己改可以把 3% 换成从历史收益分布里算出的真实波动幅度把 0.005 换成你的盈亏平衡点。3.3 建议输出样式置信区间与决策表项目最终的建议不是一行干巴巴的「买入」而是一个结构化的结果表。每个 ticker 的输出字段包括模型概率、期望收益、动作、建议的开仓仓位。我复现时把recommend_action套在run_recommendation上连续跑了好几个月的滚动预测发现它的价值不在预测多准而在强制你去想「如果概率只有 0.55我该不该买」。这在期权场景里尤其重要因为期权有权利金成本你的胜率必须高于某个阈值才能覆盖成本。def run_recommendation(ticker, df, features, max_loss_tolerance0.05): model train_model(df, features) # 用最新一个交易日的数据预测 latest_features df[features].iloc[[-1]] prob_up model.predict_proba(latest_features)[:, 1][0] rec recommend_action(prob_up, position_pct0.02, max_loss_tolerancemax_loss_tolerance) rec[ticker] ticker return rec这个函数把训练和推荐连在一起。注意在实际使用中应该用历史数据训练只对最后一天做预测而不是把训练集和测试集都包含在同一个 df 里再 predict。如果上了生产环境通常会把训练好的模型用joblib.dump存下来后续每天拉新数据后只做特征工程和 predict不再重新训练。下面的表是典型输出字段的样式数据是我为了演示随便造的字段格式是项目里真实存在的字段示例值含义tickerAAPL股票代码prob_up0.585日上涨概率expected_gain0.006期望收益百分比action买入建议动作suggested_position0.04建议仓位占组合比例这个表格对期权交易的指导意义在于当你对某只股票形成「买入」建议后下一步才轮到期权挑选。很多新手反过来先看到某个期权便宜就买完全不管标的本身的上涨概率。项目里建议引擎放在预测之后、期权筛选之前就是要你把顺序捋正。4. 期权挑选与盈亏计算器把行权价、到期日和权利金算明白4.1 期权链抓取与筛选逻辑delta 和隐含波动率怎么用预测建议给出了标的方向但期权交易还要选行权价和到期日。项目里用 yfinance 直接拉期权链然后按 delta 和隐含波动率过滤。这个选择很务实因为个人拿实时期权链的成本很高yfinance 的期权链延迟半小时左右但用来做策略参考足够。import yfinance as yf def get_options_chain(ticker, expiry): tk yf.Ticker(ticker) chain tk.option_chain(expiry) calls chain.calls calls[ticker] ticker calls[expiry] expiry return calls calls get_options_chain(AAPL, 2025-06-20) print(calls[[strike, lastPrice, delta, impliedVolatility]].head())option_chain(expiry)返回一个对象里面包含 calls 和 puts 两个 DataFrame。需要注意的是expiry必须是 yfinance 认可的标准到期日格式一般是每月的第三个星期五直接用字符串可能找不到。我一般在代码里先调用tk.options拿到所有可用的到期日列表再选离目标持有期最近的日期否则经常报错。def nearest_expiry(ticker, target_days30): tk yf.Ticker(ticker) expiries tk.options # 返回所有可用到期日字符串 for exp in expiries: days (pd.to_datetime(exp) - pd.Timestamp.today()).days if 20 days target_days 20: return exp return expiries[0]这里的目标是找一个 20 到 60 天之间到期的合约太便宜不选太贵不选。到期日太近theta 衰减很快太远权利金里时间价值占比太高。筛选逻辑上我一般只看 delta 绝对值在 0.3 到 0.5 之间的期权。delta 接近 0.5 表示平值附近对股价波动最敏感适合做方向性交易delta 太小虚值便宜但胜率低权利金归零风险大。隐含波动率我会和历史波动率比较如果 IV 比 HV 高出 20% 以上说明期权被高估不应该做买方可以考虑卖方策略。4.2 盈亏计算器单腿和组合策略的到期收益项目里的计算器部分不是一个复杂的希腊字母计算器而是把「到期时的盈亏」这条最硬的线画清楚。它允许你输入买入价格、行权价和到期日当天的标的价格输出这笔期权合约的盈亏。这个功能用来做压力测试特别有用——你可以在买入前把所有可能的价格点都过一遍。def call_option_pnl(strike, premium, price_at_expiry, contracts1): multiplier 100 # 标准美股期权合约对应 100 股 intrinsic max(price_at_expiry - strike, 0) total_pnl (intrinsic - premium) * multiplier * contracts return total_pnl def put_option_pnl(strike, premium, price_at_expiry, contracts1): multiplier 100 intrinsic max(strike - price_at_expiry, 0) total_pnl (intrinsic - premium) * multiplier * contracts return total_pnl这两个函数就是简单的「末日盈亏」买入看涨到期时股价越高赚越多买入看跌到期时股价越低赚越多。premium是买入一份权利金的价格每股multiplier乘 100 是因为一张期权对应 100 股现货。注意这里没有计算卖出的保证金占用、没有考虑提前行权也没有把卖出权利金的收益展现出来——如果要做卖出策略需要把premium前的符号反过来并在独立账户里加保证金监控。项目里还带了一个plot_options_payoff的数据可视化函数把到期日价格从 0 到当前价两倍的范围画成盈亏曲线这个图非常直观。如果只是单腿还不够项目里也支持简单的组合策略比如牛价差def bull_call_spread_pnl(strike_short, strike_long, premiums, price_at_expiry, contracts1): long_pnl call_option_pnl(strike_long, premiums[0], price_at_expiry, contracts) short_pnl -call_option_pnl(strike_short, premiums[1], price_at_expiry, contracts) return long_pnl short_pnl # 示例买入行权价100卖出行权价110两个权利金分别为6和2 pnl bull_call_spread_pnl(110, 100, [6, 2], price_at_expiry115)牛价差的好处是最大亏损有限最大盈利也封顶并且权利金净支出比单腿买更低。项目里把这种组合计算器和单腿计算器放在同一个模块里你只要传入两腿参数就能对比不同策略的盈亏曲线。对期权新手来说先跑一遍这个计算器再决定要不要实盘能避免很多无谓损失。4.3 期权筛选参数对比表把计算器和筛选逻辑放一起我整理了一张参数表也是我调优时最常改的四个值。参数推荐范围作用踩坑提示delta0.3 - 0.5方向敏感度用绝对值判断impliedVolatility与 HV 对比期权贵贱IV 高时买方成本高expiry20-60 天时间价值衰减速度太短 theta 损耗剧烈contracts1 张起仓位控制永远不要因为便宜就加张数这个表格里最容易被忽视的是expiry。很多人觉得离到期越远越安全但期权的时间价值衰减在最后 30 天会加快如果你想做一个 5 日方向的短线预测选择 20 到 60 天到期的期权既可以避开最陡的 theta 衰减又不会因为时间太长导致权利金太高。项目里的默认逻辑会根据预测持有期自动推荐到期日原理就是尽量让到期日落在你预测窗口的两到三倍之外。5. 避坑指南我在复现这套项目时踩过的四个坑5.1 现象回测收益率曲线很漂亮实盘一跑就变脸我最早跑通项目时用全部历史数据做了特征和预测回测收益率曲线看着每年翻倍兴奋得差点直接开实盘。结果在最近一年的 out-of-sample 数据上跑准确率直接跌到 51%和抛硬币没区别。原因出在数据泄露我在dropna()之前就生成了 target但dropna()是在所有特征计算完成后执行的导致模型训练集和测试集之间没有足够的时间间隔某些滚动指标把未来信息带进了训练。解决方法是所有特征和目标都按时间顺序构造然后用一个独立的 out-of-sample 期间做验证。我这里强制要求训练集和测试集之间至少间隔 20 个交易日模拟真实交易中「历史数据训练、未来预测」的场景。另外每次训练前把特征列单独 copy 出来避免后续操作改到原 DataFramedef clean_features(df): feature_cols [close, ma_5, ma_20, rsi, volatility, target] return df[feature_cols].copy()这个copy()极其重要。pandas 的切片返回的是视图如果你在切片后继续赋值容易触发 SettingWithCopyWarning更危险的是悄悄改到原始 df导致训练集和验证集边界模糊。5.2 现象期权到期日格式化后变成 1970-01-01项目早期版本里我用pd.to_datetime(expiry)直接转换 yfinance 返回的到期日字符串结果一部分日期变成了 1970-01-01。原因是 yfinance 的期权到期日字符串格式在不同市场不一样有的带星期几如 Fri Jun 20 2025有的纯数字2025-06-20to_datetime遇到非标准格式就解析成 epoch 时间。解决方法是统一用parse_dates先标准化或者直接取tk.options返回列表里的原始字符串不做转换。从那以后我所有期权日期都保留字符串只在显示时转格式expiry_str 2025-06-20 # 保留原始字符串 # 只在需要计算天数时转一次 expiry_dt pd.to_datetime(expiry_str, format%Y-%m-%d, errorscoerce)注意errorscoerce如果格式无法解析它会给 NaT 而不是 1970-01-01。这样后续筛选时一眼就能看出脏数据。5.3 现象批量拉取几百只股票时内存直接爆炸这个项目如果跑全股票池我见过一次内存占用 8GB 的场景。原因不是 yfinance 本身而是我每只股票都拉了 6 年日线并存了所有技术指标的中间结果DataFrame 没有及时清理。解决方法是训练前只保留模型需要的特征列使用df[[close, ma_5, ma_20, rsi, volatility, target]]复制一份同时在fetch_stock_data里加一个downsampleTrue参数对超长历史只保留近 5 年。内存问题更常见的来源是rolling().mean()生成了很多临时列建议每步只保留结果列中间变量覆盖掉。我习惯在循环里用del手动释放不再需要的对象并且用gc.collect()及时回收。如果是按股票池循环每处理完一只股票就把该股票的 DataFrame 设为None避免累积占用。这样处理后同样跑几百只股票内存峰值能控制到 2GB 以内。5.4 现象预测引擎说买入但我按行权价选期权后亏了这是最让我意识到「预测和期权挑选不能脱节」的坑。有一次模型对某股票给出 0.62 的上涨概率我选了 delta 0.45 的浅虚值看涨期权结果股价只涨了 1%期权到期归零因为时间价值和隐含波动率双杀。原因在于我选期权时只看了 delta忽略了隐含波动率已经很高权利金里包含了太多「预期波动」成本。解决方法是把推荐引擎的输出和期权筛选逻辑连起来当预测概率超过 0.6 且预期涨幅大于 3% 时才允许购买期权并且选择 IV 比历史波动率低的合约否则宁可买入正股。项目后来加了一个should_trade_option函数把「方向概率」和「波动率成本」两个条件同时检查后才输出具体的期权行权价。def should_trade_option(prob_up, expected_move, iv, hv, min_prob0.6, min_move0.03): if prob_up min_prob: return False, 预测概率不足 if expected_move min_move: return False, 预期涨幅不足 if iv hv * 1.2: return False, 隐含波动率过高 return True, 满足期权交易条件从那以后我每次跑完预测都会先过一遍这个条件再去看期权链。这相当于用两个独立的过滤器做交叉验证减少了单一信号带来的误判。6. 最后的验证技巧用滚动回溯而不是一次性切分来评估无论是预测模型还是期权策略最怕的就是一次性时间切分给你一个虚假的优越感。我自己吃过亏以后养成了一个习惯任何模型的评估都强制走一遍滚动回溯rolling window backtest而不是用train_test_split或者单次切分。滚动回溯的思路是固定训练窗口长度每走完一个预测周期就向前滚动把新数据和真实结果纳入下一次训练这样每一步都是在「当时无法知道未来」的条件下做预测评估结果才接近真实可复现的上限。下面这段代码是项目里我后来补上的滚动评估函数它能输出每个滚动期的概率预测你也可以直接用它替代train_model里的单次切分def rolling_backtest(df, features, train_days500, step_days5): probs [] for start in range(0, len(df) - train_days - step_days, step_days): train df.iloc[start:start train_days] test df.iloc[start train_days:start train_days step_days] model GradientBoostingClassifier(n_estimators100, max_depth3) model.fit(train[features], train[target]) prob model.predict_proba(test[features])[:, 1] probs.extend(prob) return probs这个函数的核心是start不断向前移动每 5 个交易日重新训练一次模型。注意train_days500意味着每次只取最近 500 天数据训练模拟「只使用当时可得的信息」做预测而不是一次把 1400 天全塞进去。如果你把step_days调成 20就是每 20 天才重新训练长持有期策略可以这样。我在跑完滚动回溯后还会用 matplotlib 画一条「滚动累计准确率」曲线看模型在不同市场阶段是否稳定而不是只看一个总准确率。验证技巧的最后一步是把这个滚动回溯和期权计算器结合起来。我在每个滚动周期结束时假设买入当时最贵的合约用相同本金算一笔盈亏这样得到的是「策略最终是赚是亏」而不是「预测对不对」。从那以后我每次评审新模型都会强制走一遍滚动回溯加成本模拟没走完这两个步骤的模型我不会相信它的零样本表现。希望这个习惯也能帮到读者祝你这套项目跑通顺利希望帮到你。本文还有配套的精品资源点击获取
返回列表