简介:这份资源面向对量化投资与数据分析感兴趣的 Python 学习者与交易爱好者,围绕股票与期权的挑选、预测性分析和决策辅助展开。内容源自作者自制的 Excel 原型,并整合了网络收集的片段,涵盖数据科学、推荐引擎、决策预测、博弈论与机器学习等方向,适合具备一定 Python 与 pandas 基础、希望把数据分析落地到股票期权场景的中级读者。压缩包整体约 325.82MB,以 zip 形式提供,内含 CSS 样式文件、Excel 原型及配套脚本等,其中 Excel 原型是站点构建的基础,CSS 负责页面呈现,脚本则支撑分析与计算逻辑。资源包含摘要分析与推荐两大模块:前者输入股票代码即可依据历史波动率给出 30 天乃至一年的价格区间,并基于 Wycoff 方法输出牛熊市算法观点;后者提供类似推荐系统的选股与期权建议,并配有计算器辅助决策。目前已有 162 人学习,适合想研究预测建模、推荐算法与博弈论在交易中应用的读者参考借鉴。
1. 从一次选股翻车说起:这套期权分析方案到底解决什么问题
去年帮一个做量化的朋友看他的策略回测,发现一个很典型的问题:股票筛选模块跑得挺好,但一到期权组合就全靠拍脑袋。他花了三天写了个均线策略选股票,却在期权端凭感觉挑行权价和到期日,结果股票端赚的钱全在期权端亏回去了。这不是个例,我见过太多人把股票分析和期权分析当成两件事做,中间没有任何衔接。
这套方案要解决的就是这个断层:用一套统一的 Python 分析框架,把股票筛选、期权合约挑选、预测性分析、收益计算器和决策建议串成一条流水线。适合两类人:一是正在做量化/金融方向毕设或课设的学生,需要一套结构完整、能跑通、能写进论文的方案;二是想给自己搭一套半自动决策工具的从业者,不需要机构级基础设施,一台笔记本加免费数据源就能跑起来。
核心思路不复杂:先用基本面和技术面指标把股票池筛出来,再用预测模型给出方向性判断,然后基于这个判断去期权链里找性价比最高的合约,最后用计算器模块算清楚盈亏平衡点和最大回撤。整套东西用 pandas 做数据处理,scikit-learn 做预测,plotly 做可视化,不需要 GPU,不需要付费数据接口。
2. 需求拆解与技术选型:为什么不用现成的量化平台
2.1 四个核心需求与优先级排序
动手写代码之前,我一般会先把需求按「必须做」和「锦上添花」分开。这套方案里,四个模块的优先级是这样的:
| 模块 | 优先级 | 核心输出 | 依赖数据 |
|---|---|---|---|
| 股票筛选器 | P0 | 候选股票列表及评分 | 历史行情、财务指标 |
| 期权合约挑选 | P0 | 推荐合约及理由 | 期权链、希腊字母 |
| 预测性分析 | P1 | 方向概率、置信区间 | 特征工程后的行情数据 |
| 收益计算器 | P1 | 盈亏平衡点、最大回撤 | 用户输入参数 |
P0 的意思是,没有这两个模块,整套方案就不成立。P1 是增强体验的部分,但也是毕设里最容易出彩的地方——预测性分析和计算器能体现「数据科学」的深度,光有筛选器就只是个脚本。
提示:如果你的毕设时间紧张,先把 P0 跑通,P1 用简化版实现,比如预测模块先用逻辑回归而不是 LSTM,计算器先用静态公式而不是蒙特卡洛模拟。
2.2 技术栈选型:为什么是 pandas + scikit-learn + plotly
选型这件事,我踩过的坑是:一开始想用现成的量化平台,结果发现它们的 API 要么收费,要么文档稀烂,要么回测逻辑是个黑匣子,出了问题根本不知道去哪查。后来统一用下面这套:
# 核心依赖清单,requirements.txt
pandas==2.1.0 # 数据处理,期权链的宽表转长表全靠它
numpy==1.24.0 # 数值计算,希腊字母的近似计算
scikit-learn==1.3.0 # 预测模型,随机森林和逻辑回归够用了
plotly==5.17.0 # 交互式可视化,K线叠加期权持仓
yfinance==0.2.28 # 免费行情数据源,股票和期权链都能拉
scipy==1.11.0 # 统计检验和优化,计算器模块用
为什么不用 TensorFlow 或 PyTorch?因为这套方案的数据量不大——通常你关注的股票池就几十到几百只,特征维度在 20 以内,随机森林和梯度提升树的效果不比深度学习差,而且训练时间从小时级降到秒级。毕设答辩的时候,你也不希望评委等你跑模型等五分钟。
yfinance 是个争议点。它的数据有延迟,期权链的覆盖也不如付费源全。但对于课程设计和个人工具来说,它是唯一一个不需要申请 API key、不需要绑信用卡就能用的方案。如果你有 Wind 或 Tushare 的权限,把数据获取层抽象成一个接口,替换掉 yfinance 就行。
2.3 数据流设计:从原始行情到决策建议
整套方案的数据流是这样的:
行情数据 → 特征工程 → 股票评分 → 预测模型 → 方向判断
↓
期权链数据 → 合约过滤 → 希腊字母计算 → 性价比排序 → 推荐合约
↓
用户参数 → 收益计算器 → 盈亏平衡点 → 最大回撤 → 决策建议
关键设计点是:预测模型的输出不是直接拿来做交易信号,而是作为期权合约挑选的输入之一。比如模型判断某只股票未来两周有 65% 概率上涨,这个 65% 会进入期权挑选模块,用来计算不同行权价合约的预期收益。这样两个模块就是耦合的,而不是各跑各的。
3. 股票筛选与预测模块:从特征工程到方向判断
3.1 特征工程:哪些指标真正有用
我试过把能拿到的指标全塞进去,结果模型过拟合得一塌糊涂。后来做了一轮特征筛选,留下这几类:
def build_features(df):
"""
输入:单只股票的日线数据,包含 Open/High/Low/Close/Volume
输出:带特征列的 DataFrame
"""
feat = pd.DataFrame(index=df.index)
# 动量类:过去 5/10/20 日收益率
for window in [5, 10, 20]:
feat[f'ret_{window}d'] = df['Close'].pct_change(window)
# 波动率类:20 日滚动标准差
feat['vol_20d'] = df['Close'].pct_change().rolling(20).std()
# 量价类:成交量比率
feat['vol_ratio'] = df['Volume'] / df['Volume'].rolling(20).mean()
# 技术指标:RSI
delta = df['Close'].diff()
gain = delta.where(delta > 0, 0).rolling(14).mean()
loss = -delta.where(delta < 0, 0).rolling(14).mean()
feat['rsi_14'] = 100 - (100 / (1 + gain / loss))
# 目标变量:未来 10 日是否上涨
feat['target'] = (df['Close'].shift(-10) > df['Close']).astype(int)
return feat.dropna()
这段代码里,动量类特征捕捉趋势,波动率类特征衡量风险,量价类特征判断资金流向,RSI 作为超买超卖的辅助。目标变量定义为「未来 10 日是否上涨」,是个二分类问题。
参数怎么改: window 列表可以根据你的持仓周期调整,短线用 [3, 5, 10],中线用 [10, 20, 60]。 target 的预测周期要和期权到期日匹配——如果你主要交易 2 周后到期的期权,就用 shift(-10) 。
3.2 预测模型:随机森林为什么比 LSTM 更适合这个场景
我用同一个股票池对比过随机森林、XGBoost 和 LSTM。结果随机森林的 AUC 在 0.62 左右,XGBoost 差不多,LSTM 反而更低,而且训练时间长了两个数量级。原因很简单:金融数据的信噪比极低,深度模型很容易学到噪声。
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import roc_auc_score
def train_predictor(features_df):
"""
时序交叉验证训练随机森林
返回:模型对象、最新预测概率
"""
X = features_df.drop(columns=['target'])
y = features_df['target']
# 时序交叉验证,不能用随机 KFold
tscv = TimeSeriesSplit(n_splits=5)
auc_scores = []
for train_idx, val_idx in tscv.split(X):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
model = RandomForestClassifier(
n_estimators=200,
max_depth=5, # 控制深度,防止过拟合
min_samples_leaf=50, # 叶子节点最少样本数
random_state=42
)
model.fit(X_train, y_train)
pred_proba = model.predict_proba(X_val)[:, 1]
auc_scores.append(roc_auc_score(y_val, pred_proba))
# 用全量数据重新训练,预测最新一天
final_model = RandomForestClassifier(
n_estimators=200, max_depth=5, min_samples_leaf=50, random_state=42
)
final_model.fit(X, y)
latest_prob = final_model.predict_proba(X.iloc[[-1]])[0, 1]
return final_model, latest_prob, np.mean(auc_scores)
关键点有三个:第一,必须用 TimeSeriesSplit 而不是 KFold ,否则就是用未来数据预测过去,回测结果会好得离谱但实盘一塌糊涂。第二, max_depth 和 min_samples_leaf 是防过拟合的核心参数,金融数据里树太深必然过拟合。第三,AUC 在 0.6 以上就算可用,不要追求 0.9,那一定是数据泄漏。
3.3 股票评分与排序:把概率变成可操作的列表
预测模型输出的是每只股票上涨的概率,但概率本身不能直接排序——你还需要考虑风险收益比。我一般会算一个综合评分:
def score_stocks(predictions_df):
"""
输入:包含 prob(上涨概率)、vol(波动率)、ret_20d(20日收益)的 DataFrame
输出:按综合评分排序的股票列表
"""
df = predictions_df.copy()
# 概率归一化到 0-1
df['prob_norm'] = (df['prob'] - df['prob'].min()) / (df['prob'].max() - df['prob'].min())
# 波动率取倒数,波动越小得分越高
df['vol_score'] = 1 / (1 + df['vol_20d'])
# 动量得分,过去 20 日收益为正加分
df['momentum_score'] = df['ret_20d'].apply(lambda x: 1 if x > 0 else 0.5)
# 综合评分,权重可调
df['total_score'] = (
0.5 * df['prob_norm'] +
0.3 * df['vol_score'] +
0.2 * df['momentum_score']
)
return df.sort_values('total_score', ascending=False)
权重 0.5/0.3/0.2 是我自己的偏好,偏重预测概率。如果你更保守,可以把波动率权重提到 0.4。这个评分不追求绝对正确,它的作用是给你一个可解释的排序依据——答辩的时候,评委问「为什么选这只股票」,你能说清楚是概率高、波动低还是动量好。
4. 期权合约挑选与收益计算器:从希腊字母到盈亏平衡点
4.1 期权链过滤:流动性是第一道门槛
期权和股票最大的区别是:股票你几乎总能成交,期权可能挂单半天没人理。所以挑选合约的第一步不是看收益,而是看流动性。
def filter_options(options_chain, min_volume=100, max_spread=0.1):
"""
输入:yfinance 拉取的期权链 DataFrame
输出:过滤后的可交易合约
"""
df = options_chain.copy()
# 成交量过滤:日成交量低于 100 的不要
df = df[df['volume'] >= min_volume]
# 买卖价差过滤:价差超过中间价 10% 的不要
df['mid'] = (df['bid'] + df['ask']) / 2
df['spread_pct'] = (df['ask'] - df['bid']) / df['mid']
df = df[df['spread_pct'] <= max_spread]
# 剔除深度虚值和深度实值
df = df[(df['strike'] > df['underlying_price'] * 0.8) &
(df['strike'] < df['underlying_price'] * 1.2)]
return df
min_volume=100 和 max_spread=0.1 是两个经验值。成交量太低意味着你想平仓的时候可能平不掉,价差太大意味着开仓就亏了 10%。深度虚值和深度实值的合约流动性通常很差,除非你有特定的策略需求,否则先过滤掉。
4.2 希腊字母计算与性价比排序
希腊字母是期权的「体检报告」,但很多人只看 Delta 就下单了。我一般会算四个核心指标:
from scipy.stats import norm
import numpy as np
def calculate_greeks(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':
delta = norm.cdf(d1)
theta = (-S * norm.pdf(d1) * sigma / (2 * np.sqrt(T))
- r * K * np.exp(-r * T) * norm.cdf(d2)) / 365
else:
delta = norm.cdf(d1) - 1
theta = (-S * norm.pdf(d1) * sigma / (2 * np.sqrt(T))
+ r * K * np.exp(-r * T) * norm.cdf(-d2)) / 365
gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
vega = S * norm.pdf(d1) * np.sqrt(T) / 100
return {'delta': delta, 'gamma': gamma, 'theta': theta, 'vega': vega}
Delta 告诉你方向性暴露,Gamma 告诉你 Delta 的变化速度,Theta 是每天的时间损耗,Vega 是波动率敏感度。对于买方来说,Theta 是敌人——每天醒来都在亏钱。所以性价比排序的时候,我会把 Theta 作为一个负向指标。
def rank_options(options_df, predicted_prob, predicted_move):
"""
根据预测概率和预期波动,给期权合约打分
predicted_prob: 模型输出的上涨概率
predicted_move: 预期涨跌幅(可用波动率估算)
"""
df = options_df.copy()
# 预期收益 = 概率 * 预期涨幅 - (1-概率) * 预期跌幅
df['expected_return'] = (
predicted_prob * predicted_move * df['delta'] -
(1 - predicted_prob) * predicted_move * abs(df['delta'])
)
# 时间损耗调整
df['theta_adj'] = df['expected_return'] + df['theta'] * 10 # 假设持有 10 天
# 性价比 = 调整后收益 / 权利金
df['value_score'] = df['theta_adj'] / df['mid']
return df.sort_values('value_score', ascending=False)
这个排序逻辑的核心是:不只看方向对不对,还要看对了能赚多少、错了会亏多少、时间损耗吃掉多少。 theta_adj 里的 * 10 是假设持有 10 天,你可以根据实际持仓周期调整。
4.3 收益计算器:盈亏平衡点和最大回撤
计算器模块是这套方案里最实用的部分,因为它把「感觉能赚」变成「算清楚能赚多少」。
def profit_calculator(option, contracts=1, commission=0.65):
"""
输入:单个期权合约信息
输出:盈亏平衡点、最大亏损、最大盈利、回撤比例
"""
premium = option['mid'] * 100 * contracts # 每股 100 股
strike = option['strike']
option_type = option['type']
if option_type == 'call':
breakeven = strike + option['mid'] + commission / (100 * contracts)
max_loss = premium + commission * contracts
max_profit = float('inf') # 理论上无限
else:
breakeven = strike - option['mid'] - commission / (100 * contracts)
max_loss = premium + commission * contracts
max_profit = (strike - option['mid']) * 100 * contracts - commission * contracts
return {
'breakeven': round(breakeven, 2),
'max_loss': round(max_loss, 2),
'max_profit': round(max_profit, 2) if max_profit != float('inf') else '无限',
'return_on_risk': round(max_profit / max_loss, 2) if max_profit != float('inf') else 'N/A'
}
commission=0.65 是单张合约的典型佣金,不同券商不一样,改成你自己的数字。 return_on_risk 是收益风险比,低于 1 的合约我一般直接跳过——赚一块钱要冒亏一块钱的风险,不值。
注意:这个计算器假设持有到期,实际交易中你可能提前平仓,盈亏会不同。但作为决策参考,持有到期的盈亏平衡点是最保守的估计。
5. 避坑与常见问题:那些回测很好实盘翻车的地方
5.1 数据泄漏:回测 AUC 0.9 的模型实盘亏钱
现象 :模型在回测中 AUC 达到 0.85 以上,实盘跑了两周发现胜率不到 50%。
原因 :最常见的是用了未来数据。比如在特征工程里用了 df['Close'].rolling(20).mean() 但没有 shift(1) ,导致当天的特征包含了当天的收盘价,而目标变量又是基于当天收盘价算的。另一个隐蔽的泄漏是用了全量数据的均值和标准差做归一化,而不是只用训练集。
解决 :所有滚动计算的特征,在生成后统一 shift(1) ,确保第 t 天的特征只用到 t-1 及之前的数据。归一化用 StandardScaler 在训练集上 fit,然后 transform 验证集和测试集。检查方法很简单:如果回测 AUC 超过 0.75,先怀疑数据泄漏,再高兴。
5.2 期权链数据缺失:yfinance 拉不到某些到期日
现象 :调用 yfinance 的 option_chain() 方法时,某些到期日返回空 DataFrame,或者只有几个行权价。
原因 :yfinance 的数据来自公开源,覆盖不全。深度实值、深度虚值、远月合约经常缺失。另外,非交易时段拉取的数据可能是缓存的旧数据。
解决 :拉取前先检查 options 列表是否为空,然后逐个到期日 try-except。对于缺失的到期日,用相邻到期日插值,或者直接在推荐列表里标注「数据不足,仅供参考」。我一般会在代码里加一个数据质量检查:
def check_data_quality(options_df):
"""检查期权链数据质量,返回警告列表"""
warnings = []
if len(options_df) < 10:
warnings.append("合约数量过少,流动性可能不足")
if options_df['volume'].sum() == 0:
warnings.append("总成交量为零,数据可能未更新")
if options_df['impliedVolatility'].isna().sum() > len(options_df) * 0.5:
warnings.append("隐含波动率缺失超过 50%")
return warnings
5.3 过度拟合行权价:把回测当实盘
现象 :在历史数据上测试时,发现某个特定行权价的合约收益特别高,于是实盘重仓,结果亏钱。
原因 :历史数据里某个行权价表现好,可能只是巧合。期权价格受波动率曲面影响,不同行权价的定价效率不同,历史最优行权价在未来不一定最优。
解决 :不要单独看某个行权价的回测收益,要看整个策略在多个行权价上的平均表现。我一般会要求:推荐合约必须在过去 20 个交易日中,至少有 15 天满足流动性条件。另外,行权价的选择应该基于 Delta 区间(比如 0.3-0.5),而不是具体某个价格。
5.4 忽略交易成本:佣金和滑点吃掉利润
现象 :计算器显示预期收益 20%,实盘到手只有 12%。
原因 :佣金、滑点、买卖价差都没算进去。期权合约的买卖价差通常比股票大得多,尤其是流动性差的合约。
解决 :在收益计算器里强制扣除佣金和滑点。滑点我一般按买卖价差的 50% 估算。如果算完之后的收益风险比低于 1.5,直接放弃这个合约。另外,避免频繁交易——每笔交易都在给券商打工。
5.5 预测模型频繁重训:今天准明天不准
现象 :模型周一预测准确率 70%,周三降到 50%,周五又回到 65%。
原因 :金融数据的分布随时间变化,今天有效的特征明天可能失效。频繁重训会让模型追逐最新噪声,反而不稳定。
解决 :固定重训周期,比如每周一次或每两周一次。重训时用滚动窗口,比如只用过去 2 年的数据,而不是全部历史数据。另外,记录每次重训后的 AUC 变化,如果连续三次下降,说明市场结构变了,需要重新做特征筛选。
6. 验收与答辩:怎么证明这套方案真的能跑
6.1 验收清单:五个必须通过的测试
答辩之前,我一般会跑一遍下面这个清单。每一项都有明确的通过标准,不通过就回去改:
| 测试项 | 操作 | 通过标准 |
|---|---|---|
| 数据获取 | 拉取 10 只股票的 2 年日线 + 期权链 | 成功率 100%,无空 DataFrame |
| 特征工程 | 对单只股票生成特征 | 无 NaN,无未来数据泄漏 |
| 模型训练 | 时序交叉验证 | AUC 在 0.55-0.75 之间 |
| 期权挑选 | 对 3 只股票推荐合约 | 每个推荐都有流动性数据支撑 |
| 计算器 | 输入 5 组参数 | 盈亏平衡点计算误差 < 1% |
数据获取的测试重点是稳定性。yfinance 偶尔会抽风,所以代码里要有重试机制。我一般会加一个 @retry 装饰器,失败后等 5 秒重试,最多 3 次。
模型训练的 AUC 范围很关键:低于 0.55 说明模型没学到东西,高于 0.75 说明大概率有数据泄漏。答辩的时候,评委如果问「为什么 AUC 只有 0.62」,你可以解释金融数据的信噪比特性,这比强行刷到 0.9 然后被问穿要好。
6.2 答辩演示:用 plotly 做交互式展示
静态图表在答辩里吃亏,因为评委看不到你的分析过程。我一般会用 plotly 做三个交互式图表:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def create_dashboard(stock_df, options_df, predictions):
"""
创建答辩用交互式仪表盘
包含:K线图 + 期权持仓 + 预测概率
"""
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
row_heights=[0.7, 0.3],
subplot_titles=('价格走势与期权持仓', '预测概率')
)
# K线图
fig.add_trace(
go.Candlestick(
x=stock_df.index,
open=stock_df['Open'],
high=stock_df['High'],
low=stock_df['Low'],
close=stock_df['Close'],
name='标的走势'
),
row=1, col=1
)
# 行权价参考线
for strike in options_df['strike'].unique()[:3]:
fig.add_hline(
y=strike, line_dash="dash",
annotation_text=f"行权价 {strike}",
row=1, col=1
)
# 预测概率柱状图
fig.add_trace(
go.Bar(
x=predictions.index,
y=predictions['prob'],
name='上涨概率',
marker_color='steelblue'
),
row=2, col=1
)
fig.update_layout(
height=700,
title_text="股票期权分析仪表盘",
xaxis_rangeslider_visible=False
)
return fig
这个仪表盘的好处是:你可以现场调整参数,比如把预测周期从 10 天改成 20 天,图表实时更新。评委看到的是「活的」分析工具,而不是一堆截图。演示的时候,我一般会准备三个场景:一只预测上涨的股票、一只预测下跌的股票、一只横盘的股票,分别展示推荐合约的差异。
6.3 答辩话术:怎么解释「预测不准」
这是必问题。我的回答框架是:先承认预测有不确定性,然后展示你怎么量化这个不确定性,最后说明决策建议里已经包含了容错空间。
具体话术:「模型输出的不是确定性的涨跌,而是概率。比如 65% 的上涨概率,意味着 100 次类似情况里有 65 次上涨。我们的期权挑选模块会基于这个概率计算预期收益,并且用收益风险比过滤掉性价比低的合约。所以即使预测错了,亏损也在可控范围内。」
如果评委追问「为什么不用更复杂的模型」,回答:「我对比过 LSTM 和随机森林,在这个数据量级下,随机森林的 AUC 更高且训练时间更短。金融数据信噪比低,复杂模型容易过拟合。」这个回答既展示了你的技术判断,也避免了「为了复杂而复杂」的陷阱。
6.4 一个我踩过的坑:别在答辩前夜改模型
最后说一个血泪教训。有一次答辩前夜,我觉得模型 AUC 不够高,临时换了一个特征组合,结果第二天演示的时候,推荐列表里出现了一只明显异常的股票——后来发现是新特征里有个除零错误,导致评分溢出。从那以后我每次答辩前都强制走一遍验收清单,而且最后一版代码在答辩前 48 小时就冻结,只改注释不改逻辑。
希望这套方案能帮你把股票期权分析这个题目跑通,不管是交毕设还是自己用,先把 P0 模块跑起来,再慢慢加预测和计算器。完整代码和配置模板我整理好了,按你的数据源改一下就能跑。
本文介绍基于Python的股票筛选与期权分析一体化方案,涵盖特征工程、随机森林方向预测、期权链流动性过滤、希腊字母性价比排序及盈亏平衡点计算。强调避免数据泄漏、交易成本和模型过拟合等实盘陷阱,采用pandas、scikit-learn和plotly构建可落地、可答辩的轻量级量化框架,适用于毕设与个人交易工具开发。

331

被折叠的 条评论
为什么被折叠?



