实际波动率的概念

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实际波动率的概念相关的知识,希望对你有一定的参考价值。

要明确实际波动率,首先要从波动率的概念入手。波动率(Volatility):是指关于资产未来价格不确定性的度量。它通常用资产回报率的标准差来衡量。也可以指某一证券的一年最高价减去最低价的值再除以最低价所得到的比率。业内将波动率定义为价格比率自然对数的标准差。波动率的种类有:实际波动率,隐含波动率,历史波动率等等,实际波动率便是波动率的一种。

参考技术A

原文链接:http://tecdat.cn/?p=12174

实际波动率

什么是已实现波动率?

实际波动率是通过分析定义时间内的历史收益来评估投资产品收益的变化。可以使用实体股票价格的波动性/波动性来评估对公司投资的不确定性和/或潜在的财务损失/收益的评估。在统计中,最常用的确定变异性的度量是通过测量标准偏差,即平均值的收益率变异性。它是实际价格风险的指标。

市场中已实现的波动率或实际波动率是由两个因素引起的:持续波动性成分和跳跃成分,这会影响股票价格。股票市场的持续波动受到日内交易量的影响。例如,单笔大宗交易可能会导致工具价格发生重大变化。

分析师利用高频日内数据来确定每小时/每天/每周或每月一次的波动性度量。然后可以将数据用于预测收益的波动性。

已实现的波动率公式

通过计算与给定时间段内资产平均价格的标准差来衡量。由于波动率是非线性的,因此首先通过将收益从股票/资产转换为对数值并测量对数正态收益的标准偏差来计算已实现的方差。

已实现波动率的公式是已实现方差的平方根。

标的的每日收益差异计算如下:

r t = log(P t)-log(P t-1)

    P =股价

    t =时间段

    考虑到股票价格走势的上升和下降趋势,此方法假定均值设置为零。

通过计算定义的时间段内的收益总额来计算已实现的方差

其中N =观察数(每月/每周/每天的回报)。通常,将计算20天,50天和100天的回报。

已实现波动率(RV)公式=√已实现方差

然后将结果进行年度化。通过将每日已实现的方差乘以一年中的多个交易日/周/月来对已实现的波动率进行年度化。年化已实现方差的平方根是已实现波动率。

实际波动率的例子

您可以在此处下载此已实现波动率Excel模板–已 实现波动率Excel模板

例子1

例如,对于具有相似收盘价的两只股票,假定的已实现波动率是针对股票计算20天,50天和100天的值,并按如下所示的值进行年度化:

库存1    库存2    

RV 100 = 25%    RV 100 = 20%    

RV 50 = 35%    RV 50 = 17%    

RV 20 = 50%    RV 20 = 15%    

从给定时间范围内的波动性增加的模式来看,可以推断出股票1的交易价格最近变化很大(即20天),而股票2的交易却没有任何剧烈波动。

范例#2

让我们计算20天道琼斯指数的实现波动率。可以从雅虎财务等在线网站以excel格式提取每日股票价格的详细信息。

股价波动如下图所示。

可以观察到,股票价格正在下跌,最大价格偏差为6美元。

每日收益的偏差计算如下:

日收益的方差是日偏差的平方。

20天的已实现方差的计算是20天的总回报。而已实现波动率的公式是已实现方差的平方根。


介绍

本文比较了几个时间序列模型,以预测SP 500指数的每日实际波动率。基准是SPX日收益系列的ARMA-EGARCH模型。将其与GARCH模型进行比较  。最后,提出了集合预测算法。

假设条件

实际波动率是看不见的,因此我们只能对其进行估算。这也是波动率建模的难点。如果真实值未知,则很难判断预测质量。尽管如此,研究人员为实际波动率开发了估算器。Andersen,Bollerslev Diebold(2008)  和  Barndorff-Nielsen and Shephard(2007)  以及  Shephard and Sheppard(2009)  提出了一类基于高频的波动率(HEAVY)模型,作者认为HEAVY模型给出了  很好的  估计。

假设:HEAVY实现的波动率估算器无偏且有效。

在下文中,将HEAVY估计量作为  观察到的已实现波动率  来确定预测性能。

数据来源

    SPX每日数据(平仓收益)

    SPX盘中高频数据(HEAVY模型估计)

    VIX

    VIX衍生品(VIX期货)

    在本文中,我主要关注前两个。

    数据采集

    实际波动率估计和每日收益

    我实现了Shephard和Sheppard的模型,并估计了SPX的实现量。

    head(SPXdata)
    SPX2.rv       SPX2.r     SPX2.rs SPX2.nobs SPX2.open2000-01-03 0.000157240 -0.010103618 0.000099500      1554  34191.162000-01-04 0.000298147 -0.039292183 0.000254283      1564  34195.042000-01-05 0.000307226  0.001749195 0.000138133      1552  34196.702000-01-06 0.000136238  0.001062120 0.000062000      1561  34191.432000-01-07 0.000092700  0.026022074 0.000024100      1540  34186.142000-01-10 0.000117787  0.010537636 0.000033700      1573  34191.50SPX2.highlow SPX2.highopen SPX2.openprice SPX2.closeprice2000-01-03   0.02718625   0.005937756        1469.25         1454.482000-01-04   0.04052226   0.000000000        1455.22         1399.152000-01-05  -0.02550524   0.009848303        1399.42         1401.872000-01-06  -0.01418039   0.006958070        1402.11         1403.602000-01-07  -0.02806616   0.026126203        1403.45         1440.452000-01-10  -0.01575486   0.015754861        1441.47         1456.74DATE   SPX2.rvol2000-01-03 2000-01-03 0.0125395372000-01-04 2000-01-04 0.0172669342000-01-05 2000-01-05 0.0175278642000-01-06 2000-01-06 0.0116721032000-01-07 2000-01-07 0.0096280842000-01-10 2000-01-10 0.010852972

    SPXdata$SPX2.rv 是估计的实际方差。 SPXdata$SPX2.r 是每日收益(平仓/平仓)。 SPXdata$SPX2.rvol 是估计的实际波动率

    SPXdata$SPX2.rvol  

    基准模型:SPX每日收益率建模

    ARMA-EGARCH

    考虑到在条件方差中具有异方差性的每日收益,GARCH模型可以作为拟合和预测的基准。

    首先,收益序列是平稳的。

    Augmented Dickey-Fuller Testdata:  SPXdata$SPX2.rDickey-Fuller = -15.869, Lag order = 16, p-value = 0.01alternative hypothesis: stationary

    分布显示出尖峰和厚尾。可以通过t分布回归分布密度图来近似  。黑线是内核平滑的密度,绿线是t分布密度。

    请点击输入图片描述

    acf(SPXdata$SPX2.r)  ##自相关系数图

    请点击输入图片描述

    Box-Ljung testdata:  SPXdata$SPX2.rX-squared = 26.096, df = 1, p-value = 3.249e-07

    自相关图显示了每周相关性。Ljung-Box测试确认了序列存在相关性。

    Series: SPXdata$SPX2.r ARIMA(2,0,0) with zero mean     Coefficients:ar1      ar2-0.0839  -0.0633s.e.   0.0154   0.0154sigma^2 estimated as 0.0001412:  log likelihood=12624.97AIC=-25243.94   AICc=-25243.93   BIC=-25224.92

    auro.arima 表示ARIMA(2,0,0)可以对收益序列中的自相关进行建模,而eGARCH(1,1)在波动率建模中很受欢迎。因此,我选择具有t分布的ARMA(2,0)-eGARCH(1,1)作为基准模型。

    *---------------------------------**       GARCH Model Spec          **---------------------------------*Conditional Variance Dynamics   ------------------------------------GARCH Model     : eGARCH(1,1)Variance Targeting  : FALSE Conditional Mean Dynamics------------------------------------Mean Model      : ARFIMA(2,0,0)Include Mean        : TRUE GARCH-in-Mean       : FALSE Conditional Distribution------------------------------------Distribution    :  std Includes Skew   :  FALSE Includes Shape  :  TRUE Includes Lambda :  FALSE

    我用4189个观测值进行了回测(从2000-01-03到2016-10-06),使用前1000个观测值训练模型,然后每次向前滚动预测一个,然后每5个观测值重新估计模型一次 。下图显示  了样本外  预测和相应的实际波动率。

    预测显示与实现波动率高度相关,超过72%。

    cor(egarch_model$roll.pred$realized_vol, egarch_model$roll.pred$egarch.predicted_vol,method = "spearman")
    [1] 0.7228007

    误差摘要和绘图

    Min.    1st Qu.     Median       Mean    3rd Qu.       Max. -0.0223800 -0.0027880 -0.0013160 -0.0009501  0.0003131  0.0477600

    请点击输入图片描述

    平均误差平方(MSE):

    [1] 1.351901e-05

    改进:实际GARCH模型和LRD建模

    实际GARCH

    realGARCH 该模型由  Hansen,Huang和Shek(2012)  (HHS2012)提出,该模型 使用非对称动力学表示将实际(已实现)波动率测度与潜在  真实波动率联系起来。与标准GARCH模型不同,它是收益和实际波动率度量的联合建模(本文中的HEAVY估计器)。 

    模型:

    *---------------------------------**       GARCH Model Spec          **---------------------------------*Conditional Variance Dynamics   ------------------------------------GARCH Model     : realGARCH(2,1)Variance Targeting  : FALSE Conditional Mean Dynamics------------------------------------Mean Model      : ARFIMA(2,0,0)Include Mean        : TRUE GARCH-in-Mean       : FALSE Conditional Distribution------------------------------------Distribution    :  norm Includes Skew   :  FALSE Includes Shape  :  FALSE Includes Lambda :  FALSE

    滚动预测过程与上述ARMA-EGARCH模型相同。下图显示  了样本外  预测和相应的实际波动率。

    请点击输入图片描述

    预测与实际的相关性超过77%

    cor(arfima_egarch_model$roll.pred$realized_vol, arfima_egarch_model$roll.pred$arfima_egarch.predicted_vol,method = "spearman")
    [1] 0.7707991

    误差摘要和图:

    Min.    1st Qu.     Median       Mean    3rd Qu.       Max. -1.851e-02 -1.665e-03 -4.912e-04 -1.828e-05  9.482e-04  5.462e-02

    均方误差(MSE):

    [1] 1.18308e-05

    备注:

    用于每日收益序列的ARMA-eGARCH模型和用于实际波动率的ARFIMA-eGARCH模型利用不同的信息源。ARMA-eGARCH模型仅涉及每日收益,而ARFIMA-eGARCH模型基于HEAVY估算器,该估算器是根据日内数据计算得出的。RealGARCH模型将它们结合在一起。

    以均方误差衡量,ARFIMA-eGARCH模型的性能略优于realGARCH模型。这可能是由于ARFIMA-eGARCH模型的LRD特性所致。

    集成模型

    随机森林 

    现在已经建立了三个预测

    ARMA egarch_model

    realGARCH rgarch model

    ARFIMA-eGARCH arfima_egarch_model

    尽管这三个预测显示出很高的相关性,但预计模型平均值会减少预测方差,从而提高准确性。使用了随机森林集成。

    varImpPlot(rf$model)

    请点击输入图片描述

    随机森林由500棵树组成,每棵树随机选择2个预测以适合实际值。下图是拟合和实际波动率。

    请点击输入图片描述

    预测与实际波动率的相关性:

    [1] 0.840792

    误差图:

    请点击输入图片描述

    均方误差:

    [1] 1.197388e-05

    MSE与实际波动率方差的比率

    [1] 0.2983654

    备注

    涉及已实际量度信息的realGARCH模型和ARFIMA-eGARCH模型优于标准的收益序列ARMA-eGARCH模型。与基准相比,随机森林集成的MSE减少了17%以上。

    从信息源的角度来看,realGARCH模型和ARFIMA-eGARCH模型捕获了日内高频数据中的增量信息(通过模型,HEAVY实际波动率估算)

    进一步研究:隐含波动率

    以上方法不包含隐含波动率数据。隐含波动率是根据SPX欧洲期权计算得出的。自然的看法是将隐含波动率作为预测已实现波动率的预测因子。但是,大量研究表明,无模型的隐含波动率VIX是有偏估计量,不如基于过去实际波动率的预测有效。 Torben G. Andersen,Per Frederiksen和Arne D. Staal(2007)  同意这种观点。他们的工作表明,将隐含波动率引入时间序列分析框架不会带来任何明显的好处。但是,作者指出了隐含波动率中增量信息的可能性,并提出了组合模型。

    因此,进一步的发展可能是将时间序列预测和隐含波动率(如果存在)的预测信息相结合的集成模型。

    最受欢迎的见解

    1.HAR-RV-J与递归神经网络(RNN)混合模型预测和交易大型股票指数的高频波动率

    2.R语言中基于混合数据抽样(MIDAS)回归的HAR-RV模型预测GDP增长

    3.波动率的实现:ARCH模型与HAR-RV模型

    4.R语言ARMA-EGARCH模型、集成预测算法对SPX实际波动率进行预测

    5.GARCH(1,1),MA以及历史模拟法的VaR比较

    6.R语言多元COPULA GARCH 模型时间序列预测

    7.R语言基于ARMA-GARCH过程的VAR拟合和预测

    8.matlab预测ARMA-GARCH 条件均值和方差模型

    9.R语言对S&P500股票指数进行ARIMA + GARCH交易策略

复杂因子计算优化案例:深度不平衡买卖压力指标波动率计算

在金融行业的数据分析工作中,数据预处理及特征工程的质量往往决定了数学模型的实际效果。

某些金融指标涉及原始大量数据中高维多列的复杂运算,这将耗费大量的计算资源和开发时间。

本案例基于证券交易的level2快照数据,开发了10分钟频率的深度不平衡、买卖压力指标和波动率的计算脚本,旨在为DolphinDB使用者在开发其他类似因子计算脚本时提供参考范例,提高开发效率。

优化前后计算效率

  • 分布式表数据总量:2,874,861,174
  • 上证50指数的成分股数据量:58,257,708
  • 处理后的结果表数据量:267,490

存储引擎

十档买卖量价存储方式

计算对象

逻辑CPU核数

计算耗时(s)

OLAP

40列

列为单位

8

450

OLAP

40列

矩阵

8

450

TSDB

40列

矩阵

8

27

TSDB

4列

矩阵

8

25


1. Snapshot数据文件结构

字段

含义

字段

含义

字段

含义

SecurityID

证券代码

LowPx

最低价

BidPrice[10]

申买十价

DateTime

日期时间

LastPx

最新价

BidOrderQty[10]

申买十量

PreClosePx

昨收价

TotalVolumeTrade

成交总量

OfferPrice[10]

申卖十价

OpenPx

开始价

TotalValueTrade

成交总金额

OfferOrderQty[10]

申卖十量

HighPx

最高价

InstrumentStatus

交易状态

……

……

本案例用到的字段为Snapshot中的部分字段,包括: 股票代码、快照时间、申买十价,申买十量,申卖十价,申卖十量。

样本为2020年上证50指数的成分股:


  • 股票代码
601318,600519,600036,600276,601166,600030,600887,600016,601328,601288,
600000,600585,601398,600031,601668,600048,601888,600837,601601,601012,
603259,601688,600309,601988,601211,600009,600104,600690,601818,600703,
600028,601088,600050,601628,601857,601186,600547,601989,601336,600196,
603993,601138,601066,601236,601319,603160,600588,601816,601658,600745

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_量化计算

  • 股票名称
中国平安、贵州茅台、招商银行、恒瑞医药、兴业银行、中信证券、伊利股份、民生银行、交通银行、农业银行、
浦发银行、海螺水泥、工商银行、三一重工、中国建筑、保利地产、中国中免、海通证券、中国太保、隆基股份、
药明康德、华泰证券、万华化学、中国银行、国泰君安、上海机场、上汽集团、海尔智家、光大银行、三安光电、
中国石化、中国神华、中国联通、中国人寿、中国石油、中国铁建、山东黄金、中国重工、新华保险、复星医药、
洛阳钼业、工业富联、中信建投、红塔证券、中国人保、汇顶科技、用友网络、京沪高铁、邮储银行、闻泰科技


2020年上交所14460个证券的Snapshot数据已经提前导入至DolphinDB数据库中,一共约28.75亿条快照数据,导入方法见​​国内股票行情数据导入实例​​,一共174列。

2. 指标定义

  • Weighted Averaged Price(WAP):加权平均价格

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_02


  • Depth Imbalance(DI):深度不平衡

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_因子计算_03


  • Press:买卖压力指标

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_量化计算_04

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_量化交易_05

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_因子计算_06

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_07

特征数据重采样(10min窗口,并聚合计算波动率)

重采样利用​group by SecurityID, interval( TradeTime, 10m, "none" )​方法

  • Realized Volatility(RV):波动率定义为对数收益率的平方和的平方根

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_08

股票的价格始终是处于买单价和卖单价之间,因此本项目用加权平均价格来代替股价进行计算

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_09

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_10


3. SQL优化

这些指标的计算SQL语句由以下几部分组成:

SELECT 指标计算函数(参数)
FROM 数据源
WHERE 日期筛选条件,股票筛选条件,交易时间筛选条件
GROUP BY 股票代码, interval(时间列,时间单位,缺失值填充方式)

本教程的优化部分为指标计算函数的处理过程。

3.1 新手:以列为单位进行计算

数据采用OLAP存储引擎存储。

根据指标定义公式,以列为单位进行计算,开发者能够快速写出以下SQL代码:

/**
part1: Define calculation function
*/
def calPress(BidPrice0,BidPrice1,BidPrice2,BidPrice3,BidPrice4,BidPrice5,BidPrice6,BidPrice7,BidPrice8,BidPrice9,BidOrderQty0,BidOrderQty1,BidOrderQty2,BidOrderQty3,BidOrderQty4,BidOrderQty5,BidOrderQty6,BidOrderQty7,BidOrderQty8,BidOrderQty9,OfferPrice0,OfferPrice1,OfferPrice2,OfferPrice3,OfferPrice4,OfferPrice5,OfferPrice6,OfferPrice7,OfferPrice8,OfferPrice9,OfferOrderQty0,OfferOrderQty1,OfferOrderQty2,OfferOrderQty3,OfferOrderQty4,OfferOrderQty5,OfferOrderQty6,OfferOrderQty7,OfferOrderQty8,OfferOrderQty9)
WAP = (BidPrice0*OfferOrderQty0+OfferPrice0*BidOrderQty0)\\(BidOrderQty0+OfferOrderQty0)
Bid_1_P_WAP_SUM = 1\\(BidPrice0-WAP) + 1\\(BidPrice1-WAP) + 1\\(BidPrice2-WAP) + 1\\(BidPrice3-WAP) + 1\\(BidPrice4-WAP) + 1\\(BidPrice5-WAP) + 1\\(BidPrice6-WAP) + 1\\(BidPrice7-WAP) + 1\\(BidPrice8-WAP) + 1\\(BidPrice9-WAP)
Offer_1_P_WAP_SUM = 1\\(OfferPrice0-WAP)+1\\(OfferPrice1-WAP)+1\\(OfferPrice2-WAP)+1\\(OfferPrice3-WAP)+1\\(OfferPrice4-WAP)+1\\(OfferPrice5-WAP)+1\\(OfferPrice6-WAP)+1\\(OfferPrice7-WAP)+1\\(OfferPrice8-WAP)+1\\(OfferPrice9-WAP)
BidPress = BidOrderQty0*((1\\(BidPrice0-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty1*((1\\(BidPrice1-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty2*((1\\(BidPrice2-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty3*((1\\(BidPrice3-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty4*((1\\(BidPrice4-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty5*((1\\(BidPrice5-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty6*((1\\(BidPrice6-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty7*((1\\(BidPrice7-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty8*((1\\(BidPrice8-WAP))\\Bid_1_P_WAP_SUM) + BidOrderQty9*((1\\(BidPrice9-WAP))\\Bid_1_P_WAP_SUM)
OfferPress = OfferOrderQty0*((1\\(OfferPrice0-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty1*((1\\(OfferPrice1-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty2*((1\\(OfferPrice2-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty3*((1\\(OfferPrice3-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty4*((1\\(OfferPrice4-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty5*((1\\(OfferPrice5-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty6*((1\\(OfferPrice6-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty7*((1\\(OfferPrice7-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty8*((1\\(OfferPrice8-WAP))\\Offer_1_P_WAP_SUM) + OfferOrderQty9*((1\\(OfferPrice9-WAP))\\Offer_1_P_WAP_SUM)
return log(BidPress)-log(OfferPress)


/**
part2: Define variables and assign values
*/
stockList=`601318`600519`600036`600276`601166`600030`600887`600016`601328`601288`600000`600585`601398`600031`601668`600048`601888`600837`601601`601012`603259`601688`600309`601988`601211`600009`600104`600690`601818`600703`600028`601088`600050`601628`601857`601186`600547`601989`601336`600196`603993`601138`601066`601236`601319`603160`600588`601816`601658`600745
dbName = "dfs://snapshot_SH_L2_OLAP"
tableName = "snapshot_SH_L2_OLAP"
snapshot = loadTable(dbName, tableName)

/**
part3: Execute SQL
*/
result = select
avg((OfferPrice0\\BidPrice0-1)) as BAS,
avg((BidOrderQty0-OfferOrderQty0)\\(BidOrderQty0+OfferOrderQty0)) as DI0,
avg((BidOrderQty1-OfferOrderQty1)\\(BidOrderQty1+OfferOrderQty1)) as DI1,
avg((BidOrderQty2-OfferOrderQty2)\\(BidOrderQty2+OfferOrderQty2)) as DI2,
avg((BidOrderQty3-OfferOrderQty3)\\(BidOrderQty3+OfferOrderQty3)) as DI3,
avg((BidOrderQty4-OfferOrderQty4)\\(BidOrderQty4+OfferOrderQty4)) as DI4,
avg((BidOrderQty5-OfferOrderQty5)\\(BidOrderQty5+OfferOrderQty5)) as DI5,
avg((BidOrderQty6-OfferOrderQty6)\\(BidOrderQty6+OfferOrderQty6)) as DI6,
avg((BidOrderQty7-OfferOrderQty7)\\(BidOrderQty7+OfferOrderQty7)) as DI7,
avg((BidOrderQty8-OfferOrderQty8)\\(BidOrderQty8+OfferOrderQty8)) as DI8,
avg((BidOrderQty9-OfferOrderQty9)\\(BidOrderQty9+OfferOrderQty9)) as DI9,
avg(calPress(BidPrice0,BidPrice1,BidPrice2,BidPrice3,BidPrice4,BidPrice5,BidPrice6,BidPrice7,BidPrice8,BidPrice9, BidOrderQty0,BidOrderQty1,BidOrderQty2,BidOrderQty3,BidOrderQty4,BidOrderQty5,BidOrderQty6,BidOrderQty7,BidOrderQty8,BidOrderQty9,OfferPrice0,OfferPrice1,OfferPrice2,OfferPrice3,OfferPrice4,OfferPrice5,OfferPrice6,OfferPrice7,OfferPrice8,OfferPrice9,OfferOrderQty0,OfferOrderQty1,OfferOrderQty2,OfferOrderQty3,OfferOrderQty4,OfferOrderQty5,OfferOrderQty6,OfferOrderQty7,OfferOrderQty8,OfferOrderQty9)) as Press,
sqrt(sum(pow((log((BidPrice0*OfferOrderQty0+OfferPrice0*BidOrderQty0)\\(BidOrderQty0+OfferOrderQty0))-prev(log((BidPrice0*OfferOrderQty0+OfferPrice0*BidOrderQty0)\\(BidOrderQty0+OfferOrderQty0)))),2))) as RV
from snapshot
where date(TradeTime) between 2020.01.01 : 2020.12.31, SecurityID in stockList, (time(TradeTime) between 09:30:00.000 : 11:29:59.999) || (time(TradeTime) between 13:00:00.000 : 14:56:59.999)
group by SecurityID, interval( TradeTime, 10m, "none" ) as TradeTime

上述SQL中,涉及​BidPrice0-9​、​BidOrderQty0-9​、​OfferPrice0-9​、​OfferOrderQty0-9​共40列数据,且由于指标定义较为复杂,即使公式化简后,仍然难以解决代码冗长、修改困难的问题。

计算处理效率:

  • 分布式表数据总量:2,874,861,174
  • 上证50指数的成分股数据量:58,257,708
  • 逻辑CPU核数:8
  • 平均占用CPU核心数:4.5个
  • 耗时:450秒

3.2 进阶:将列拼接为矩阵进行计算

数据采用OLAP存储引擎存储。

上述公式定义中都是列与列之间的计算,实际计算往往发生在​BidPrice​,​BidOrderQty​,​OfferPrice​,​OfferOrderQty​这4个大组之间(每组10列),因此可以将这四个大组看成n行10列的矩阵。在SQL中进行matrix拼接,再传入聚合函数中进行矩阵运算,示例代码如下:

/**
part1: Define calculation function
*/
defg featureEnginee(bidPrice,bidQty,offerPrice,offerQty)
bas = offerPrice[0]\\bidPrice[0]-1
wap = (bidPrice[0]*offerQty[0] + offerPrice[0]*bidQty[0])\\(bidQty[0]+offerQty[0])
di = (bidQty-offerQty)\\(bidQty+offerQty)
bidw=(1.0\\(bidPrice-wap))
bidw=bidw\\(bidw.rowSum())
offerw=(1.0\\(offerPrice-wap))
offerw=offerw\\(offerw.rowSum())
press=log((bidQty*bidw).rowSum())-log((offerQty*offerw).rowSum())
rv=sqrt(sum2(log(wap)-log(prev(wap))))
return avg(bas),avg(di[0]),avg(di[1]),avg(di[2]),avg(di[3]),avg(di[4]),avg(di[5]),avg(di[6]),avg(di[7]),avg(di[8]),avg(di[9]),avg(press),rv


/**
part2: Define variables and assign values
*/
stockList=`601318`600519`600036`600276`601166`600030`600887`600016`601328`601288`600000`600585`601398`600031`601668`600048`601888`600837`601601`601012`603259`601688`600309`601988`601211`600009`600104`600690`601818`600703`600028`601088`600050`601628`601857`601186`600547`601989`601336`600196`603993`601138`601066`601236`601319`603160`600588`601816`601658`600745
dbName = "dfs://snapshot_SH_L2_OLAP"
tableName = "snapshot_SH_L2_OLAP"
snapshot = loadTable(dbName, tableName)

/**
part3: Execute SQL
*/
result1 = select
featureEnginee(
matrix(BidPrice0,BidPrice1,BidPrice2,BidPrice3,BidPrice4,BidPrice5,BidPrice6,BidPrice7,BidPrice8,BidPrice9),
matrix(BidOrderQty0,BidOrderQty1,BidOrderQty2,BidOrderQty3,BidOrderQty4,BidOrderQty5,BidOrderQty6,BidOrderQty7, BidOrderQty8,BidOrderQty9),
matrix(OfferPrice0,OfferPrice1,OfferPrice2,OfferPrice3,OfferPrice4,OfferPrice5,OfferPrice6,OfferPrice7,OfferPrice8, OfferPrice9),
matrix(OfferOrderQty0,OfferOrderQty1,OfferOrderQty2,OfferOrderQty3,OfferOrderQty4,OfferOrderQty5,OfferOrderQty6, OfferOrderQty7,OfferOrderQty8,OfferOrderQty9)) as `BAS`DI0`DI1`DI2`DI3`DI4`DI5`DI6`DI7`DI8`DI9`Press`RV
from snapshot
where date(TradeTime) between 2020.01.01 : 2020.12.31, SecurityID in stockList, (time(TradeTime) between 09:30:00.000 : 11:29:59.999) || (time(TradeTime) between 13:00:00.000 : 14:56:59.999)
group by SecurityID, interval( TradeTime, 10m, "none" ) as TradeTime map

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_量化计算_11通过矩阵化处理,代码大量减少,且在自定义聚合函数中已经可以比较容易的看出指标公式的计算代码,这将极大地方便后续公式定义和指标计算代码的修改。

计算处理效率:

  • 分布式表数据总量:2,874,861,174
  • 上证50指数的成分股数据量:58,257,708
  • 逻辑CPU核数:8
  • 平均占用CPU核心数:4.5个
  • 耗时:450秒

3.3 高性能1:V2.00的TSDB存储和计算

DolphinDB V2.00新增了TSDB存储引擎,在创建分区数据库和表时与OLAP存储引擎的不同之处是必须指定​engine​和​sortColumns​,创建语句如下:

dbName = "dfs://snapshot_SH_L2_TSDB"
tableName = "snapshot_SH_L2_TSDB"
dbTime = database(, VALUE, 2020.01.01..2020.12.31)
dbSymbol = database(, HASH, [SYMBOL, 20])
db = database(dbName, COMPO, [dbTime, dbSymbol], engine=TSDB)
createPartitionedTable(dbHandle=db, table=tbTemp, tableName=tableName, partitionColumns=`TradeTime`SecurityID, sortColumns=`SecurityID`TradeTime)

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_时序数据库_12计算代码与3.2进阶代码完全相同。

由于TSDB存储引擎的查询优化,数据计算性能得到了极大地提升。原来需要运行450秒完成的计算,优化后只需运行27秒,计算速度是未优化代码的17倍。

计算处理效率:

  • 分布式表数据总量:2,874,861,174
  • 上证50指数的成分股数据量:58,257,708
  • 逻辑CPU核数:8
  • 平均占用CPU核心数:7.6个
  • 无level file index cache下的计算耗时:27秒
  • 有level file index cache下的计算耗时:16秒

3.4 高性能2:V2.00的TSDB使用Array Vector存储和计算

DolphinDB从V2.00.4开始,分布式表的存储支持​​数组向量(array vector)​​​,因此在数据存储时可以把​BidPrice0-9​、​BidOrderQty0-9​、​OfferPrice0-9​、​OfferOrderQty0-9​共40列数据以array vector形式存储为​BidPrice​、​BidOrderQty​、​OfferPrice​、​OfferOrderQty​4列,SQL的示例代码如下:

/**
part1: Define calculation function
*/
defg featureEnginee(bidPrice,bidQty,offerPrice,offerQty)
bas = offerPrice[0]\\bidPrice[0]-1
wap = (bidPrice[0]*offerQty[0] + offerPrice[0]*bidQty[0])\\(bidQty[0]+offerQty[0])
di = (bidQty-offerQty)\\(bidQty+offerQty)
bidw=(1.0\\(bidPrice-wap))
bidw=bidw\\(bidw.rowSum())
offerw=(1.0\\(offerPrice-wap))
offerw=offerw\\(offerw.rowSum())
press=log((bidQty*bidw).rowSum())-log((offerQty*offerw).rowSum())
rv=sqrt(sum2(log(wap)-log(prev(wap))))
return avg(bas),avg(di[0]),avg(di[1]),avg(di[2]),avg(di[3]),avg(di[4]),avg(di[5]),avg(di[6]),avg(di[7]),avg(di[8]),avg(di[9]),avg(press),rv


/**
part2: Define variables and assign values
*/
stockList=`601318`600519`600036`600276`601166`600030`600887`600016`601328`601288`600000`600585`601398`600031`601668`600048`601888`600837`601601`601012`603259`601688`600309`601988`601211`600009`600104`600690`601818`600703`600028`601088`600050`601628`601857`601186`600547`601989`601336`600196`603993`601138`601066`601236`601319`603160`600588`601816`601658`600745
dbName = "dfs://snapshot_SH_L2_TSDB_ArrayVector"
tableName = "snapshot_SH_L2_TSDB_ArrayVector"
snapshot = loadTable(dbName, tableName)

/**
part3: Execute SQL
*/
result = select
featureEnginee(BidPrice,BidOrderQty,OfferPrice,OfferOrderQty) as `BAS`DI0`DI1`DI2`DI3`DI4`DI5`DI6`DI7`DI8`DI9`Press`RV
from snapshot
where date(TradeTime) between 2020.01.01 : 2020.12.31, SecurityID in stockList, (time(TradeTime) between 09:30:00.000 : 11:29:59.999) || (time(TradeTime) between 13:00:00.000 : 14:56:59.999)
group by SecurityID, interval( TradeTime, 10m, "none" ) as TradeTime map

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_13使用TSDB的array vector进行数据存储,在性能上相较于TSDB的分列存储提升不明显,但是代码更加精简,方便后期的修改和维护。

计算效率:

  • 分布式表数据总量:2,874,861,174
  • 上证50指数的成分股数据量:58,257,708
  • 逻辑CPU核数:8
  • 平均占用CPU核心数:7.6个
  • 无level file索引缓存下的计算耗时:25秒
  • 有level file索引缓存下的计算耗时:15秒

4. OLAP到TSDB的性能提升原因

4.1 数据库分区方法

OLAP存储引擎创建分区数据库的代码:

dbName = "dfs://snapshot_SH_L2_OLAP"
dbTime = database(, VALUE, 2020.01.01..2020.12.31)
dbSymbol = database(, HASH, [SYMBOL, 20])
db = database(dbName, COMPO, [dbTime, dbSymbol])

TSDB存储引擎创建分区数据库的代码:

dbName = "dfs://snapshot_SH_L2_TSDB"
dbTime = database(, VALUE, 2020.01.01..2020.12.31)
dbSymbol = database(, HASH, [SYMBOL, 20])
db = database(dbName, COMPO, [dbTime, dbSymbol], engine=TSDB)

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_因子计算_14DolphinDB的分区规则是建立在数据库层面上的,本案例中,OLAP存储引擎和TSDB存储引擎的分区规则相同,第一层按天分区,第二层以证券代码采用哈希算法分20个区。因此,14460个证券每天的数据被相对均匀地存储在20个分区内,上证50指数成分股的数据也被分散存储在多个分区内;从另一个角度理解,同一个分区内包含多个哈希值相同的证券的数据。

在DolphinDB中,无论是OLAP存储引擎还是TSDB存储引擎,数据的切片思想和算法是一致的,所以本案例中,数据库创建代码中唯一的差异是,TSDB存储引擎需要指定参数​engine​为​TSDB​,该参数默认值是​OLAP​。

4.2 数据表创建方法

OLAP存储引擎创建分区数据表的代码:

dbName = "dfs://snapshot_SH_L2_OLAP"
db = database(dbName)
tableName = "snapshot_SH_L2_OLAP"
createPartitionedTable(dbHandle=db, table=tbTemp, tableName=tbName, partitionColumns=`TradeTime`SecurityID)

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_因子计算_15TSDB存储引擎创建分区数据表的代码:

dbName = "dfs://snapshot_SH_L2_TSDB"
db = database(dbName)
tableName = "snapshot_SH_L2_TSDB"
createPartitionedTable(dbHandle=db, table=tbTemp, tableName=tableName, partitionColumns=`TradeTime`SecurityID, sortColumns=`SecurityID`TradeTime)

复杂因子计算优化案例:深度不平衡、买卖压力指标、波动率计算_DolphinDB_16在使用​​​DolphinDB V2.00 TSDB存储引擎​​​创建分区数据表时,设置了​sortColumns=`Sec​​urityID`TradeTime​,​sortColumns​一般由两部分组成:

  • sortKey​:可以由一个或多个查询索引列组成,不包含数据的时间列
  • 时间列:数据中的时间列,作为分区内数据按时间顺序的排序列

本案例中,​sortKey=`SecurityID​,时间列为​TradeTime​。与OLAP存储引擎相比,TSDB存储的每个分区中的同一​SecurityID​的数据在磁盘文件中是连续的,且按照​TradeTime​严格排序。相比于OLAP存储引擎,TSDB存储引擎在根据​sortColumes​字段进行数据过滤查询时,可以根据分区内的索引文件从磁盘上只加载满足过滤条件的数据。

本案例的计算样本是2020年上证50指数成分股,计算数据源是2020年上交所14460个证券的全部数据。如果采用OLAP存储引擎,需要将上证50指数成分股涉及的分区内的无关证券的数据同时从磁盘上加载到内存后,进行数据的解压、过滤和计算操作。如果采用TSDB,只需要将上证50指数成分股的数据从磁盘上加载到内存,进行数据的计算操作。

4.3 OLAP存储引擎与TSDB存储引擎的差异

OLAP存储引擎保持了数据写入时的顺序,以一种append-only的方式往磁盘上存储数据。分区表的每个分区内的每个列是一个文件,对于某个列文件而言,多个​SecurityID​的数据是根据数据写入顺序添加到文件中的,所以同一个​SecurityID​的数据很大概率是散乱地分布在整个列文件的各个位置。本案例中,查询数据表存储了14460个证券的数据,虽然只查询上证50指数成分股的数据,但是在OLAP存储引擎下,会将上证50指数成分股涉及的分区内的无关证券的数据同时从磁盘上读取到内存进行解压,降低了数据提取的效率。

TSDB存储引擎设置了​sortColumns=`SecurityID`TradeTime​,对同一个​SecurityID​的数据的存储是连续的,且存储的时候需要压缩:

  • 如果将同一个​SecurityID​的所有数据一起压缩,那每次查询的时候,即便真正需要查询的数据只是这个​SecurityID​全部数据的一小部分,仍然需要将所有的数据都从磁盘读到内存中以进行解压,降低数据读取效率。
  • TSDB存储引擎的做法是将同一个​SecurityID​在同一个LevelFile里的全部数据一起压缩存储,并且指定一个时间区间以供查询,而这个时间区间内的数据只占据了这个​SecurityID​所有数据的一小部分。在本案例中,同一个​SecurityID​的数据根据​sortColumns​最后一列​TradeTime​排序,并将数据拆成一定大小(默认为16KB)的数据块(block)来存储。在读取数据的时候,TSDB根据查询语句中的股票列和时间列过滤条件先快速定位到满足过滤条件的数据块,再将这些数据块读到内存里进行解压,而这些数据块通常只占所有数据的一小部分,这样就可以大大提升查询的效率。

4.4 小结

本案例中,使用TSDB存储引擎比OLAP存储引擎计算性能提升的主要原因如下: