本期作者:Boris B
作者介绍:1+1=6
前言
本文是一部分预测股价内容。结果(识别出的异常)是LSTM模型(在GAN体系结构中)中的一个特征(输入)具体请看这篇文章:
动机
期权估值是一项非常困难的工作。首先,它需要使用大量的数据点,其中一些是非常主观的(如隐含波动率——见下文),很难精确计算。作为一个例子让我们调用的θ,θ的计算:
其中,N(d1)和N(d2)为标准正态分布的累积分布函数,分别为:
期权定价难易的另一个例子是Black-Scholes公式,它用于计算期权价格本身。以t为期限,以S0为当期价格的欧式看涨期权价格计算公式为:
其次,Black-Scholes模型,也被称为二叉树,需要满足很多假设才能使模型准确。然而,这些规则在现实生活中往往无法实现。其中包括:
1、该模型假设基础波动率(σ)在期权的整个期间内保持不变,不受基础股票价格水平变化的影响。很多时候,波动性随着执行价格的变化而变化——执行价格与基础价格之间的差异越大,波动性就越大。这种现象被称为波动微笑(见下图)。
2、假设一个恒定的无风险回报率(随着全球经济每天的变化,这很难预测)。
3、该模型不考虑流动性风险和额外费用。
4、假设股票收益服从对数正态分布(即模型忽略了价格大幅偏离、暴涨或下跌的可能性,而这些在实际交易中很容易观察到)。
5、假设没有派息。股利支付改变了股票的当前估值,这反过来又会改变期权价格。
5、该模型仅适用于欧式期权。
获取全部代码,见文末
"""
===== IMPORTANT:
Execute the following code cells (below) first in order to instantiate the 'options_df' dataframe
"""
curr_price_ = options_df.iloc[0,0]
plt.figure(figsize=(10, 6))
plt.scatter(options_df.Strike, options_df.IVAsk, label='IV to Strike', \
c='royalblue')
plt.vlines(curr_price_, options_df.IVAsk.min(), \
options_df.IVAsk.max(), \
label='Current GS price - ${}'.format(curr_price_), \
linestyles='--', colors='gray')
plt.xlabel('Strike price - $')
plt.ylabel('IV')
plt.title('Options Volatility smile')
plt.legend(fontsize='large')
plt.savefig('imgs/Options/volatilitysmile.jpg', dpi=100)
plt.show()
不是一个完美的波动微笑,只使用了一天的数据
上述假设在现实生活中很少得到满足,这正是可以观察到异常现象的原因。这反过来又创造了许多我们可以利用机器学习/深度学习来探索和利用的机会,比如套利交易。
希腊值(Greeks值)的选择很重要,以便我们充分了解异常检测是否正确,简要介绍一下:
隐含波动率-σ:隐含波动率是一个衡量的估计价格可能会改变多少。更高的数字意味着交易员相信期权可能会带来巨大的变化。基本上就是波动指数。
Delta-δ:Delta衡量期权价格相对于标的股票价格变化的变化程度。 Delta为0.5意味着股票移动每1美元期权将变为50美分(δ是价格的一阶导数)。
Gamma-γ:γ测量当股票价格变化时δ的变化速度。数值很大意味着这是一个非常“活跃”的选项 ,并且可以快速获得或损失价值(γ是价格的二阶导数)。
Theta-θ:θ衡量由于时间衰减,期权每天损失的速度。随着到期日的临近,theta增加。
Vega:vega衡量期权价格对隐含波动率变化的敏感程度。非现金期权或到期前较长的期权对隐含波动率的变化更为敏感,
Rho:rho是衍生品价格相对于无风险利率变化的变化率。
数据
# Filtered options data for Goldman Sachs
options_df = pd.read_csv('option_GS_df.csv', parse_dates=["Expiration", ' DataDate'])
options_df['date_diff'] = (options_df['Expiration'] - options_df[' DataDate']).dt.days
options_df['Spread'] = options_df['Ask'] - options_df['Bid']
使用的数据是2016年1月5日高盛期权的每日报价。它包含了不同到期日的定价(加上波动性和greeks)以及在那一天产生的行权价格。当日的现价(GS)为174.09美元。有22个特征和858行。
获取全部代码,见文末
options_df.shape
output >>> (858, 22)
print(', '.join(x for x in options_df.columns))
output >> UnderlyingPrice, OptionSymbol, Type, Expiration,
DataDate, Strike, Last, Bid, Ask, Volume, OpenInterest,
T1OpenInterest, IVMean, IVBid, IVAsk, Delta, Gamma,
Theta, Vega, AKA, date_diff, Spread
基本上我们需要的所有功能都是可用的。让我们把数据可视化成一个配对图,其中:
sns_options_colors = ["#9b59b6", "#3498db"]
sns_options_vars = ['Strike', 'Bid', 'Delta', \
'Gamma', 'Vega', 'Spread']
sns.set_palette(sns.color_palette(sns_options_colors))
sns.set_context("paper", rc={"axes.labelsize": 22})
options_g = sns.pairplot(options_df, \
vars=sns_options_vars, \
diag_kind='kde', \
hue='Type', \
markers=["o", "x"], \
plot_kws=dict(edgecolor='white', \
linewidth=.85, alpha=.85), \
diag_kws=dict(shade=True),\
height=5)
for i, j in zip(*np.triu_indices_from(options_g.axes, 1)):
options_g.axes[i, j].set_visible(False)
xlabels, ylabels = [], []
for ax in options_g.axes[-1,:]:
xlabel = ax.xaxis.get_label_text()
xlabels.append(xlabel)
for ax in options_g.axes[:,0]:
ylabel = ax.yaxis.get_label_text()
ylabels.append(ylabel)
i,j=0,0
for i in range(len(xlabels)):
for j in range(len(ylabels)):
options_g.axes[j,i].xaxis.set_label_text(col_names_pairplot[xlabels[i]])
options_g.axes[j,i].yaxis.set_label_text(col_names_pairplot[ylabels[j]])
plt.savefig('imgs/Options/options_pairplot.jpg')
plt.show()
无监督学习查找异常值
什么是异常?在通常情况下,异常是选项逻辑中的任何不匹配。例如,两个执行价格相同但在执行日相差1-2天的看涨期权的买入价(或卖出价)应该几乎相同(除非有不寻常的情况,这种情况可能在greeks以某种方式得到了解释)。因此,这两种期权的买入价格之间的差异将不是“正常的”。或者,例如,高Theta或小Vega的(OTM)选项有很长的到期时间。等等。
我们将再次跳过期权定价背后的技术和数学方面(如随机过程、布朗运动和扩散方程)。相反,我们试图看看是否可以利用机器学习来使用数据近似所有这些数学公式(数据驱动方法而不是模型驱动方法)。结果可能不像原来的公式那样精确,但计算量较小。或者,另一方面,机器学习可以“学习”如何执行金融模型(如期权定价),甚至比金融数学更好,因为我们可以在模型中加入许多新的方法和数据,允许模型揭示人类隐藏的模式和相关性。
为了检测异常,我们将使用孤立森林(Isolation Forest)。
GS期权数据(x轴是当前价格和行使价格之间的差异,y轴是买入价和卖出价的平均值 - 基本上是期权的价格),其中圆圈表示看涨期权,星星表示看跌期权。 聚类颜色基于Vega。
让我们将一个交易日(2016/01/05)的期权数据可视化,看看我们能否直观地识别异常。有几件事看起来可疑——这些可能或不是异常现象:
GS选项数据(遵循图1中的逻辑),但聚类颜色基于Delta。
在图2中 - 考虑到合约具有相同的特征,买入/卖出的平均值,当前价格与执行价格之间的差异,非常非常接近的到期日期,如果订购合约的价格小于其它合约,则很奇怪。是底部的选项,蓝色包围着灰色的颜色(它有点隐藏)。在另一边,有一个相同异常的调用:紫色圆圈被灰色圆圈包围。
GS期权数据(x轴是Delta,y轴是当前价格和行使价之间的差异)。 聚类颜色基于Delta,聚类形状(圆形,方形,加号和x)基于Theta。
在图3中(右侧)有几个看涨期权(我们知道它们是看涨期权,因为看涨期权有一个正的delta,介于0和1之间),Theta的范围明显更低 - 圆圈的θ小于-4.5,考虑到具有相同特征的其他选项(附近)具有高于-1.5。
GS期权数据(x轴是Delta,y轴是Spread),其中聚类颜色范围来自Bid和Ask的平均价格。
我们感兴趣的主要特征是买入/卖出价格(或我们创建的特征:平均值)和点差(=卖出价)。 从理论上讲,相同类型、接近执行价格和到期日的期权价格和价差不应存在显著差异。
好的,让我们进入异常检测。我们来打赌:通过数据分布,算法将学习这些optoins规则,并设法找出不遵循“平均”分布的数据点。
获取全部代码,见文末
it_X_train = options_df[['Strike', 'Delta', 'Gamma', 'date_diff']]
it_X_train['s_k'] = options_df['UnderlyingPrice'] - options_df['Strike']
it_X_train['b_a_mean'] = (options_df['Bid'] + options_df['Ask']) / 2
it_X_train['b_a_mean'] = it_X_train['b_a_mean'].apply(lambda x: int(round(x, 0)))
it_X_train['s_k'] = it_X_train['s_k'].apply(lambda x: int(round(x, 0)))
我们不会使用所有的特征来检测异常。我们使用的特征是:
print(', '.join(col_names[x] for x in it_X_train.columns))
output >>> Strike, Delta, Gamma, Difference between Date of valuation and Exercise date, Difference between Current and Strike prices, Average of Bid and Ask.
我们为什么要使用这些特征?我们希望使用的特征应该遵循上面描述的选项逻辑。
孤立森林的逻辑非常简单:
clf = IsolationForest(max_samples='auto', contamination=.025,\
n_estimators=10, \
random_state=19117, max_features=it_X_train.shape[1])
clf.fit(it_X_train)
y_pred_train = clf.predict(it_X_train)
结果
让我们把模型的结果可视化。x轴是当前和执行价格之间的差值,y轴是买入价和卖出价的平均值。蓝色的x和紫色的圆分别是看跌和看涨期权,在特征分布中没有异常。其他的则是异常现象。
获取全部代码,见文末
it_outlier = -1
sns_options_colors = ["#9b59b6", "#3498db"]
sns.set_palette(sns.color_palette(sns_options_colors))
sns.set_context("paper", rc={"axes.labelsize": 10})
plt.figure(figsize=(14, 8))
it_x_ = 's_k'
it_y_ = 'b_a_mean'
plt.scatter(it_X_train[(it_X_train['y']==1) & (it_X_train['Type']=='put')][it_x_], \
it_X_train[(it_X_train['y']==1) & (it_X_train['Type']=='put')][it_y_], \
label='Normal put', c=sns_options_colors[1], s=10*4, alpha=.55, marker='x')
plt.scatter(it_X_train[(it_X_train['y']==1) & (it_X_train['Type']=='call')][it_x_], \
it_X_train[(it_X_train['y']==1) & (it_X_train['Type']=='call')][it_y_], \
label='Normal call', c=sns_options_colors[0], s=10*4, alpha=.55, marker='o')
plt.scatter(it_X_train[(it_X_train['y']==-1) & (it_X_train['Type']=='call')][it_x_], \
it_X_train[(it_X_train['y']==-1) & (it_X_train['Type']=='call')][it_y_], \
label='Anomaly call', c='red', s=60*4, edgecolor='black', marker='o', alpha=.7)
plt.scatter(it_X_train[(it_X_train['y']==-1) & (it_X_train['Type']=='put')][it_x_], \
it_X_train[(it_X_train['y']==-1) & (it_X_train['Type']=='put')][it_y_], \
label='Anomaly put', c='orange', s=60*4, edgecolor='black', marker='x')
plt.xlabel(col_names[it_x_])
plt.ylabel(col_names[it_y_])
plt.legend(fontsize='medium')
plt.savefig('imgs/Options/it_result_options.jpg', dpi=200)
plt.show()
这些是孤立森林识别出的异常。尽管从上一张图表中看不到,因为我们似乎无法修复python和Tableau之间的y轴刻度,所识别的异常(红色和橙色)是我们在上面的4个图(交叉引用了每个合约的索引)。
sns_options_colors = ["#f9a602", "#3498db"]
sns_options_vars = ['b_a_mean', 'Delta', 'Gamma', 's_k', 'date_diff']
sns.set_palette(sns.color_palette(sns_options_colors))
sns.set_context("paper", rc={"axes.labelsize": 14})
options_g = sns.pairplot(it_X_train, \
vars=sns_options_vars, \
diag_kind='kde', \
hue='y', \
markers=["o", "x"], \
plot_kws=dict(edgecolor='white', \
linewidth=.85, alpha=.85), \
diag_kws=dict(shade=True),\
height=3)
for i, j in zip(*np.triu_indices_from(options_g.axes, 1)):
options_g.axes[i, j].set_visible(False)
xlabels, ylabels = [], []
for ax in options_g.axes[-1,:]:
xlabel = ax.xaxis.get_label_text()
xlabels.append(xlabel)
for ax in options_g.axes[:,0]:
ylabel = ax.yaxis.get_label_text()
ylabels.append(ylabel)
for i in range(len(xlabels)):
for j in range(len(ylabels)):
options_g.axes[j,i].xaxis.set_label_text(col_names_pairplot[xlabels[i]])
options_g.axes[j,i].yaxis.set_label_text(col_names_pairplot[ylabels[j]])
plt.savefig('imgs/Options/it_result_pairplot.jpg', dpi=100)
plt.show()
橙色点代表异常,蓝色点代表正常
从这对图中可以观察到期权定价异常的几个例子:
期权定价异常对于预测股价走势有何重要性?请看:
那么我们将从选项数据和异常检测中使用什么?选项数据非常大。 我们每个交易日都有:
print('So for every day of the training data we have {} data points.'.\
format(options_df.shape[0]*options_df.shape[1]))
So for every day of the training data we have 18876 data points.
显然,我们不能每天都包含18887个特征。因此,我们将只包含60和90天的看涨期权,并将执行价与当前价格(买入价和卖出价的平均值)相等,加上和减去5%,10%和20%。还有delta,gamma和差价。共有12个特征。
在异常检测部分,我们将创建一个布尔特征,显示在执行价格或到期日期的当天是否检测到异常。当然,用一个数字来表示偏差的严重程度会好得多。我们将把它留到后面的阶段。这意味着我们的总数据集中增加了13个新特征(共有125个特征)。
这里只是展示了如何尝试在一天内进行异常检测。对于LSTM,我们将所有数据(所有日期)放在一起。日期本身对于异常检测并不重要,因为我们使用的是估值日期和到期日期之间的差异。对于每个异常,我们将1(表示异常)分配给训练数据中的相应日期。
使用自动编码器对异常检测也很有意义。
原文:
https://towardsdatascience.com/unsupervised-learning-for-anomaly-detection-in-stock-options-pricing-e599728958c7