一、实验介绍--订单流模型拟合
1.1 实验知识点
- 指数核 hawkes 过程拟合
- 正反馈强度分析
- 订单量影响分析
1.2 实验环境
- R 3.4.1
- Rstudio
二、订单流模型拟合
在上节中我们对订单流数据做了一些统计分析 , 对交易的一些特征有了一些粗浅的理解 , 在本节中 我们要做的是利用实际数据来拟合 hawkes 过程 ,看一看真实数据的订单流动力学中有什么特征。
首先我们仍是选出交易时间内的数据:
library(tidyverse)
library(lubridate)
dat <- read.csv(url("http://labfile.oss.aliyuncs.com/courses/883/pigu.csv"))
bisect_lower_bound <- function(x) {
date <- as.POSIXct(x[1],origin="1970-01-01",tz="America/Chicago") + days(1)
hour(date) <- 8
minute(date) <- 30
second(date) <- 0
k <- as.numeric(date)
l = -1
r = length(x)+1
while(r-l>1) {
mid = round((l+r)/2)
if (x[mid] >= k) r=mid
else l=mid
}
r
}
bisect_higher_bound <- function(x) {
date <- as.POSIXct(x[1],origin="1970-01-01",tz="America/Chicago") + days(1)
hour(date) <- 15
minute(date) <- 0
second(date) <- 0
k <- as.numeric(date)
l = -1
r = length(x)+1
while(r-l>1) {
mid = round((l+r)/2)
if (x[mid] <= k) l=mid
else r=mid
}
l
}
start <- bisect_lower_bound(dat$time)
end <- bisect_higher_bound(dat$time)
trade <- dat[start:end,]
2.1 指数核 hawkes 过程拟合
形象地来看,限价单组建起了买方和卖方的堡垒,而市价单则对对手的堡垒发起冲击。
那么作为进攻部队,市价单对买方卖方力量的博弈是非常关键的,在本节中我们就单独看一看市价单的动力学是怎么样的。
我们先把所有的市价单单独提出来:
market_order <- trade[trade$action_type=="T",]
head(market_order)
## X action_item action_type ask_price ask_vol bid_price bid_vol
## 959396 959395 trd T 145925 643 145900 364
## 959397 959396 trd T 145925 640 145900 364
## 959413 959412 trd T 145925 642 145900 372
## 959414 959413 trd T 145925 642 145900 366
## 959415 959414 trd T 145925 642 145900 365
## 959416 959415 trd T 145925 642 145900 364
## price time vol
## 959396 145925 1358260200 3
## 959397 145925 1358260200 3
## 959413 145900 1358260200 1
## 959414 145900 1358260200 6
## 959415 145900 1358260200 1
## 959416 145900 1358260200 1
然后做一些预处理,把时间相同的订单进行合并,并把所有时间减去初始值
m <- market_order %>% group_by(time) %>% summarise(sum(vol))
time <- m$time - m$time[1]
vol <- m$`sum(vol)`
首先我们不考虑订单量的大小,把所有买单卖单视作同质的,用指数核的 hawkes 过程进行拟合,
这里可以直接使用第二节所用的似然函数,然后用 nlminb 函数做优化:
log_likelihood <- function(params , event) {
mu = params[1]
alpha = params[2]
beta = params[3]
n = length(event)
t_n = event[n]
kernel_sum <- numeric(n)
for (i in 2:n) {
kernel_sum[i] = (kernel_sum[i-1]+alpha) * exp(-beta*(event[i] - event[i-1]))
}
lambda <- kernel_sum + mu
L = sum( log(lambda) ) - mu * t_n - alpha*n/beta + alpha/beta*sum(exp(-beta*(t_n-event)))
#print(L)
-L
}
nlminb(c(1,1,1) , log_likelihood ,event= time)
## $par
## [1] 1.522979 145.265214 368.465284
##
## $objective
## [1] -80224.79
##
## $convergence
## [1] 0
##
## $iterations
## [1] 39
##
## $evaluations
## function gradient
## 44 131
##
## $message
## [1] "relative convergence (4)"
拟合得到的 beta 的值非常大,说明前面事件对后面事件的影响衰减得非常快。注意 1/beta 被定义为 Hawkes 过程的记忆时间 , 超过这个时间的后续事件基本不受这个事件的影响 。 这里我们可以看到影响周期小于一毫秒 , 说明频率确实非常快。
2.2 正反馈强度分析
索罗斯在他的“金融炼金术”中提出了一种叫做 reflexity(自反性)的理论 , 这个理论说的是投资者和交易者的认知偏差会改变标的的基本面。例如一支股票持续上涨,会使得投资者对提高对它基本面的认知 ,又反过来推动股价的进一步上升, 从而形成正反馈。
但是如何量化地对正反馈进行分析一直是一个问题。在 Hawkes 过程中,根据 lambda 的表达式我们可以把事件发生的强度分为两部分 , 一部分是背景的强度 , 一部分则是由前面事件激发而得到的强度,这部分“衍生”的强度的平均值可以被认为是正反馈的强度。
例如在指数核中,我们可以通过积分计算出正反馈机制的比例是 alpha / beta , 下面我们把一天的时间按照30分钟的间隔分为13段 , 看看每一段的背景强度和 reflexity 分别是多少。
time_cut <- cut(time , breaks = seq(-0.01 , time[length(time)]+0.01 , length.out=14) )
reflexity <- as.data.frame(cbind(time , time_cut))
reflexity_res <- list()
for (i in 1:13) {
ref_time <- time[reflexity$time_cut==i]
ref_time <- ref_time - ref_time[1]
ref_res <- nlminb(c(1,1,1) , log_likelihood ,event= ref_time )
reflexity_res[[i]] <- ref_res
}
## Warning messages:
## 1: In log(lambda) : \u4ea7\u751f\u4e86NaNs
## 2: In nlminb(c(1, 1, 1), log_likelihood, event = ref_time) :
## NA/NaN function evaluation
## 3: In log(lambda) : \u4ea7\u751f\u4e86NaNs
## 4: In nlminb(c(1, 1, 1), log_likelihood, event = ref_time) :
## NA/NaN function evaluation
## 5: In log(lambda) : \u4ea7\u751f\u4e86NaNs
## 6: In nlminb(c(1, 1, 1), log_likelihood, event = ref_time) :
## NA/NaN function evaluation
mu_vec <- c()
alpha_vec <- c()
beta_vec <- c()
for (i in 1:13) {
mu_vec <- c(mu_vec , reflexity_res[[i]]$par[1])
alpha_vec <- c(alpha_vec , reflexity_res[[i]]$par[2])
beta_vec <- c(beta_vec , reflexity_res[[i]]$par[3])
}
time_vec <- seq(market_order$time[1]-0.01 , market_order$time[nrow(market_order)]+0.01 , length.out=14)
time_vec <- as.POSIXct(time_vec,origin="1970-01-01",tz="America/Chicago")
par(mfrow=c(2,1))
plot(time_vec[1:(length(time_vec)-1)],mu_vec , col="blue" , type="b" , xlab="time" , ylab="mu" , main="background intensity in different periods")
plot(time_vec[1:(length(time_vec)-1)] , col="red" , type="b",alpha_vec/beta_vec , xlab="time" , ylab="alpha/beta",main = "reflexity in different periods" )
我们把背景强度和 reflexity 分别画出来,可以看到背景强度形成了一个碗状,说明开盘和收盘时的事件的背景强度较大。而 reflexity 则在各个 时间段基本一致,基本围绕 0.4 波动。
2.3 考虑订单数量
前面我们把订单视作同质进行分析 ,但是很明显量大的订单对市场的冲击更大,我们应该 把订单的数量考虑进去。
首先我们看一下整个时间段的订单强度:
plot(time , vol , type="l" , ylab="volume" , xlab="time" , main="market order intensity")
然后我们可以开始尝试建模,将量的因素考虑进去:
其中 nj 代表第 j 个事件的数量。
log_likelihood_volume <- function(params , event , vol) {
mu = params[1]
alpha = params[2]
beta = params[3]
n = length(event)
t_n = event[n]
kernel_sum <- numeric(n)
for (i in 2:n) {
kernel_sum[i] = (kernel_sum[i-1]+alpha*vol[i-1]) * exp(-beta*(event[i] - event[i-1]))
}
lambda <- kernel_sum + mu
L = sum( log(lambda) ) - mu * t_n - alpha/beta*sum(vol) + alpha/beta*sum(vol*exp(-beta*(t_n-event)))
#print(L)
-L
}
nlminb(c(1,1,1) , log_likelihood_volume ,event= time , vol= vol )
## Warning in log(lambda): NaNs produced
## Warning in nlminb(c(1, 1, 1), log_likelihood_volume, event = time, vol =
## vol): NA/NaN function evaluation
## Warning in log(lambda): NaNs produced
## Warning in nlminb(c(1, 1, 1), log_likelihood_volume, event = time, vol =
## vol): NA/NaN function evaluation
## Warning in log(lambda): NaNs produced
## Warning in nlminb(c(1, 1, 1), log_likelihood_volume, event = time, vol =
## vol): NA/NaN function evaluation
## $par
## [1] 1.557608 7.106736 293.679848
##
## $objective
## [1] -82261.69
##
## $convergence
## [1] 0
##
## $iterations
## [1] 44
##
## $evaluations
## function gradient
## 56 150
##
## $message
## [1] "relative convergence (4)"
2.4 订单数量的幂指数
前面我们假设订单量的影响是线性的,可真实是这样的吗,我们尝试在订单数量上加入一个幂指数。
那么强度的表达式变为
log_likelihood_volume_exponent <- function(params , event , vol) {
mu = params[1]
alpha = params[2]
beta = params[3]
k = params[4]
n = length(event)
t_n = event[n]
kernel_sum <- numeric(n)
vol <- vol^k
for (i in 2:n) {
kernel_sum[i] = (kernel_sum[i-1]+alpha*vol[i-1]) * exp(-beta*(event[i] - event[i-1]))
}
lambda <- kernel_sum + mu
L = sum( log(lambda) ) - mu * t_n - alpha/beta*sum(vol) + alpha/beta*sum(vol*exp(-beta*(t_n-event)))
-L
}
nlminb(c(1,1,1,1) , log_likelihood_volume_exponent ,event= time , vol= vol)
## $par
## [1] 1.5214343 42.1613593 321.6189710 0.5287265
##
## $objective
## [1] -86579.32
##
## $convergence
## [1] 0
##
## $iterations
## [1] 51
##
## $evaluations
## function gradient
## 67 231
##
## $message
## [1] "relative convergence (4)"
从参数我们可以看到k的值大约是 0.5 , 说明订单的影响大约是数量的根号,并不是线性增长的。 这是非常有趣的一个现象 , 说明订单量的冲击是边际递减的。
三、 总结
在前面我们对市价单的动力学进行建模 , 我们看到了一些有趣的现象, 例如日内交易背景强度的变化;订单数量影响的幂指数小于1。 这些都可以做进一步地挖掘,可以让我们更加深入地理解市场的动力学。