点过程基础及模拟
点过程基础
假设你蹲在一个交通站台后面,看着人来人往。你觉得乘客的到达似乎存在某种数学规律, 于是你把每个人到达的时刻记录了下来。有什么办法可以对这些人到达的时刻进行建模?你渐渐进入了沉思状态。也许提炼这些点形成的集合所具有的特征是一个好办法。你想到乘客到来的速率肯定是一个重要特征,如果是在一个偏僻的小公交站,可能半天也看不到一个人 ; 到了市中心的大车站,人潮涌动可能让你难以计数。不同人到来的间隔是另外一个有意思的特征,乘客不是从工厂出来的产品,肯定不会乖乖地等间隔的到来,那么不同乘客到来的间隔有什么规律呢?要回答这些问题,必须要借助概率的语言,更确切地说,是点过程。
泊松过程
泊松过程有以下几个性质:
不相交的时间段上到来的数量是相互独立的;两个点几乎肯定不会同时到达;在某个给定的时间段到达的数量服从泊松分布,分布均值正比于时间段的长度。
从数学层面来描述具有这样性质的过程,首先我们从第二个性质开始 , 我们用 N(a,b] 表示 a < t <= b 这段时间发生的事件数。
对于一个趋近于0的 Δt , 我们声明 , 对任意t
P(N(t, t + Δt]=1)=λΔt
由于 λ 的含义是单位时间内事件的数量 , 所以可以定义为事件发生的强度。
由于两个点不会同时到达 , 在小段时间里发生两次的概率约等于0
P(N(t, t + Δt >= 2) → 0
那么对于任意时间段 (a,b] , 我们可以先将其划分为多个小时间段 , 然后由不同时间段的 独立性, 用二项分布来计算概率分布 , 再用泊松分布近似:
可以看到 N(a,b] 近似服从参数为 λ(b-a) 的泊松分布。
现在我们来看一看两个事件的间隔服从什么分布 , 间隔为t0 意味着这t 0 时间段没有事件发生, 那么可以很容易的进行计算:
我们可以计算出 interval 的密度函数:
可以发现这正是指数分布。
我们可以利用间隔服从指数分布的性质,模拟服从泊松过程的事件,第k个事件的时刻就是第 k-1 个事件时刻加参数为λ 的指数分布的随机变量:
举个例子, 我们可以模拟一个 λ 为 0.5 的泊松过程 , 总共模拟 50 个事件, 可以画出事件与时间的关系:
> x <- cumsum(rexp(50, rate=0.5))
> y <- rep(0, 50)
> plot(x, y, main="events arrival")
还可以画出累积事件与时间的关系 , 按照我们的估算 , 发生 50 个 λ 为 0.5 的事件大约要用 100 的时间,我们可以从图中进行验证。
> y_cum <- cumsum(c(0, rep(1, 50)))
> plot(stepfun(x, y_cum), do.points=F, main="cumsum of events")
** Hawkes过程**
在泊松过程中,强度保持恒定,事件的发生遵循“无记忆性”的原则,在现实世界中,很多情况 都不符合这样的假定,例如犯罪行为往往具有空间上的聚集性,这是由于罪犯在得手后倾向于在附近继续作案;而在高频交易中趋势交易者会跟踪大订单,使得市场在短时间涌入大量订单。在这些系统中,事件发生的速率都是不均匀的。如何描述这种空间上的聚集性,或者说是正反馈的机制呢?我们需要对模型进行扩展,不再把 λ 固定为一个确定的值,而是让他成为一个关于时间的函数,即 λ(t) 。
比较精确的定义是当 Δt 趋近为 0 时:
P(N(t, t + Δt]=1)=λ(t)Δt
其余的假设相似 , 在小间隔内发生2次或以上事件的概率趋近为0 。
λ(t) 的定义则为:
的 λ0(t) 代表的是背景的强度 , 而 v(t - ti) 则代表发生在 t 时刻之前的事件对时刻 t 产生的正向影响 , v 函数就是核函数 , 简单来说 , ti离t 越近 , 对t时刻造成的影响就应更大。
先使用一个比较简单的核函数:指数函数来看一看 Hawkes 过程究竟有什么特性:可以定义
把背景强度设为恒定的 μ , 那么强度随时间变化的函数可以表示为
理论框架搭设完毕,我们尝试来模拟一下 Hawkes 过程,这里我们使用 Ogata(1981) 的方法进行模拟,他的方法的大意是先用一个 λ 的上界作为参数喂到指数分布里给出一个间隔 ,进而算出新的事件发生的时间。 但是实际情况在间隔里 λ(t) 是应该不断变小的(核函数的距离不断拉长),所以我们只能以一定概率来接受这个新的点,如果被拒绝应该模拟新的点。换句话说,有一些模拟出来的点需要被删掉, 使得间距和真实的更接近。
假设μ = 1.2 , α = 0.6 , β = 0.8。
> set.seed(728)
> n <- 100
> params <- c(0.5, 0.3, 1.2)
> mu <- params[1]
> alpha <- params[2]
> beta <- params[3]
>
> lambda_intensity <- function(event, t, params){
+ mu <- params[1]
+ alpha <- params[2]
+ beta <- params[3]
+ partial <- event[event
> event_time <- numeric(n)
>
> for (i in 1:n){
+ if (i==1){
+ lambda_star <- mu
+ event_time[1] <- rexp(1, rate=lambda_star)
+ } else {
+ lambda_star <- lambda_intensity(event_time, event_time[i-1], params) + alpha
+ event_time[i] <- event_time[i-1] + rexp(1, rate=lambda_star)
+ while (runif(1) > lambda_intensity(event_time, event_time[i], params)/lambda_star) {
+ lambda_star <- lambda_intensity(event_time, event_time[i], params)
+ event_time[i] <- event_time[i] + rexp(1, rate=lambda_star)
+ }
+ }
+ }
模拟了 100 个事件,可以把事件和对应的强度画在一张图上
> time <- seq(min(event_time), max(event_time), 0.001)
> intensity <- unlist(lapply(time, function(x) lambda_intensity(event_time, x, params)))
>
> plot(event_time, rep(0, n), col="blue", ylim=c(-1, 5), main="simulated hawkes process", ylab="lambda intensity")
> lines(time, intensity, type='l', col="red")
可以通过数值验算一下我们的结果是否合理:
强度的实际平均值为 Nt / t大约为 100/150 ,而我们可以推导理论的平均值:
图片里的 λ0 是我们定义的 μ , 而图片里的 μ 代表我们要算来和实际对比的强度的平均值 E[λ(t)],注意从第一排到第二排的变换是根据 λ(t) 的定义。
可以计算出理论值
E[λ(t)] = μ / (1 - (α/β)) = 2/3
与实际的较为接近,说明模拟是正确的。
hawkes过程参数估计
指数核Hawkes过程模拟优化
对指数核函数的 Hawkes 过程进行模拟 , 当我们把 事件个数调大时,比如从 100 调到 1000 时,可以发现程序运行的时间大大增加了, 这是由于在算时刻对应的强度时需要调用前面所有的事件时刻,所以复杂度是 O(N^2).
根据这个性质把计算复杂度降低到 O(N):
> set.seed(728)
> n <- 100000
> params <- c(0.5, 0.3, 1.2)
> mu <- params[1]
> alpha <- params[2]
> beta <- params[3]
> event_time <- numeric(n)
> last_kernel_sum <- numeric(n)
>
> for (i in 1:n){
+ if (i==1){
+ lambda_star <- mu
+ event_time[1] <- rexp(1, rate=lambda_star)
+ } else {
+ lambda_last <- mu + last_kernel_sum[i-1]
+ lambda_star <- lambda_last + alpha
+ event_time[i] <- event_time[i-1] + rexp(1, rate=lambda_star)
+ last_kernel_sum[i] <- (last_kernel_sum[i-1] + alpha)*exp(-beta*(event_time[i] - event_time[i-1]))
+ lambda_this <- mu + last_kernel_sum[i]
+
+ while (runif(1) > lambda_this/lambda_star){
+ lambda_star <- lambda_this
+ event_time[i] <- event_time[i] + rexp(1, rate=lambda_star)
+ last_kernel_sum[i] <- (last_kernel_sum[i-1] + alpha)*exp(-beta*(event_time[i] - event_time[i-1]))
+ lambda_this <- mu + last_kernel_sum[i]
+ }
+ }
+ }
Hawkes 过程参数估计
可以将公式转化为代码:
> 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)))
+ -L
+ }
因为后面的函数 nlminb 所做的是使函数的值最小化 , 上面的似然函数中我们输入的是负的似然函数。
> nlminb(c(1, 1, 1), log_likelihood, event=event_time)
$par
[1] 0.4983477 0.3057706 1.2047607
$objective
[1] 136883.5
$convergence
[1] 0
$iterations
[1] 24
$evaluations
function gradient
37 93
$message
[1] "relative convergence (4)"
经过优化得到的参数非常接近我们的真实参数(0.5,0.3,1.2) , 这说明极大似然估计非常有效。
Hawkes 过程的核函数可以指定多种形式 , 除了指数核函数以外 , 还可以使用幂律核函数 ,甚至不指定核函数的具体形式, 使用非参数的方法求取核函数 。
订单流数据描述分析
订单流数据表示
在金融市场上做交易时 , 可以看到一个委托单簿,上面陈列着买价和卖价以及它们对应的量 , 举个例子,比特币市场的订单簿:
可以看到红色代表的是卖价,或者说是 ask , 而绿色代表的是买价 , 或者说是 bid 。 在众多买卖价中 , 最高的买价和最低的卖价是比较重要的两个价格 ,分别被称为最佳买价和最佳卖价 , 即时价格也一般在这两者之间。
这个订单簿在不停地变化 , 有的时候买价和卖价上的量变少了 ,这是由于卖单或买单与它成交,这类即时成交的订单被称为市价单。 另外我们也可以挂单 , 那么订单簿上可能出现新的价格 , 或者使旧价格的量变大 , 这类订单被称为限价单。
这些类型订单一起组成了订单流,本文使用的数据是处理过后的美国标普 500 期货一天的交易数据。
> options(stringsAsFactors=F)
> dat <- readLines("http://labfile.oss.aliyuncs.com/courses/883/pigu.csv")
> dat <- unlist(strsplit(dat, ","))
> dat <- as.data.frame(matrix(dat, ncol=10, byrow=T))
> dat[1,1] <- "X"
> colnames(dat) <- dat[1,]
> dat <- dat[-1,]
> dat$X <- as.factor(dat$X)
> dat$action_item <- as.factor(dat$action_item)
> dat$action_type <- as.factor(dat$action_type)
> dat1 <- dat[,1:3]
> dat2 <- dat[,4:ncol(dat)]
> dat2 <- as.data.frame(sapply(dat2, as.numeric))
> dat <- cbind(dat1, dat2)
> summary(dat[,2:ncol(dat)])
action_item action_type ask_price ask_vol bid_price
ask:1234169 A: 10040 Min. :145675 Min. : 1.0 Min. :145650
bid:1265295 M:2489424 1st Qu.:146050 1st Qu.: 185.0 1st Qu.:146025
trd: 285548 T: 285548 Median :146200 Median : 443.0 Median :146175
Mean :146223 Mean : 602.7 Mean :146198
3rd Qu.:146400 3rd Qu.: 860.0 3rd Qu.:146375
Max. :146825 Max. :3742.0 Max. :146800
bid_vol price time vol
Min. : 1.0 Min. :145425 Min. :1.358e+09 Min. : 1
1st Qu.: 188.0 1st Qu.:146025 1st Qu.:1.358e+09 1st Qu.: 219
Median : 459.0 Median :146200 Median :1.358e+09 Median : 634
Mean : 575.6 Mean :146210 Mean :1.358e+09 Mean : 839
3rd Qu.: 843.0 3rd Qu.:146400 3rd Qu.:1.358e+09 3rd Qu.:1378
Max. :3545.0 Max. :147050 Max. :1.358e+09 Max. :4255
action_itme 的含义是订单类型 , 其中 “M” 代表对 order book 的改变 , 换句话说就是限价单;而 “T” 代表的是 trade ,也可以说是市价单。 ask price 和 ask vol 分别代表最优卖价和量 , bid price 和 bid vol 分别代表最优买价和量 。 price 和 vol 分别代表订单对应的价格和量。 time 是以 1970 年开始计算的秒数。
> head(dat)
X action_item action_type ask_price ask_vol bid_price bid_vol price time vol
2 0 bid M 146500 101 146500 105 146400 1358203997 108
3 1 bid M 146500 101 146500 105 146325 1358203999 106
4 2 bid M 146500 101 146500 105 146300 1358203999 179
5 3 ask M 146500 101 146500 105 146525 1358204012 42
6 4 ask M 146500 101 146500 105 146550 1358204015 147
7 5 ask M 146500 102 146500 105 146500 1358204016 102
> format(head(dat)$time, digits=21)
[1] "1358203996.7088456" "1358203999.3707500" "1358203999.4706881" "1358204012.2595844"
[5] "1358204014.8965719" "1358204016.0976164"
时间的精度 , 达到了10的负7次方 , 也就是达到了微秒级。
> as.POSIXct(head(dat)$time[1], origin="1970-01-01", tz="America/Chicago")
[1] "2013-01-14 16:53:16 CST"
用函数将其转化为 R 语言中的时间格式 , 发现首个数据的时间是下午 5 点。
> library(tidyverse)
> library(lubridate)
>
> bisect_lower_bound <- function(x){
+ date <- as.POSIXct(x[1], origin="1970-01-01", tz="America/Chicago") + days(1)
+ hour(date) <- 0
+ 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){
+ 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,]
由于美国标普 500 的交易时间是上午 8 点半到下午 3 点 , 我们用一个二分搜索把交易时间内 的数据提取出来,并命名为 trade
订单间隔分析
> time <- trade$time
> time_diff <- diff(time)
> time_diff <- time_diff[time_diff>0]
> summary(time_diff)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000011 0.000376 0.001496 0.041396 0.024075 30.228422
75 分位数和均值大约 0.02s , 这可以说明标普 500 期货的交易非常频繁,流动性非常好。
> hist(time_diff[time_diff<0.1], breaks=seq(0, 0.2, 0.001), xlim=c(0, 0.1), probability=T, col="gray", border="white", xlab="empirical")
上面是订单间隔的分布图,由于大于 0.01s 的间隔较少,我们选取小于 0.01s 的间隔进行可视化,可以观察到分布是高度有偏的,绝大多数间隔都非常小。
> library(MASS)
>
> time_diff_p <- time_diff[time_diff<0.04]
> exp_fit <- fitdistr(time_diff_p, densfun="exponential")
> exp_ran <- rexp(length(time_diff_p), rate=exp_fit$estimate)
> plot(density(exp_ran), ylim=c(0, 700), main="inter-arrival time: empirical vs exponential", xlab="inter-arrival time")
> lines(density(time_diff_p), col="red")
用指数分布去拟合小于 0.04 的订单间隔,发现实际数据衰减的速度远远高于对应最优参数模拟指数分布衰减的速度。由于指数分布的衰减速度是非常快的,这更说明小间隔的比例有多么大,可以说这是实实在在的“高频”交易。
高频交易中有一类交易者叫做做市商,它们在市场中挂买单和卖单,如果都成交了就可以赚取 其中的价差。它们一般下限价单,为市场提供流动性,当然它们也会有一些自己的策略,例如在市场上出现市价单时做市商一般会调整自己的订单,所以他们会马上下跟踪的限价单,我们从统计来看一看市场有没有这样的现象。
> action <- trade$action_type
> pre <- action[1:(length(action)-1)]
> aft <- action[2:length(action)]
> market_maker <- intersect(which(pre=="T"), which(aft=="M"))
> mm_diff <- time[(market_maker+1)] - time[market_maker]
> mm_diff <- mm_diff[mm_diff>0]
> plot(density(time_diff), col="red", xlim=c(0, 0.01), ylim=c(0, 5000), main="The evidence of market maker")
> lines(density(mm_diff), col="green")
筛选出了这种特殊的间隔,即前面的事件是市价单,后面的事件是限价单,我们把这种间隔的分布用绿色的线画出来,可以看到这种类型的跟单速度比普通的跟单速度快得多,其峰值小于1毫秒。
信息率的平稳性
我们来看一看订单的到来,或者说是信息率是否是平稳的。我们回忆一下泊松过程,在泊松过程中,对于一个 t 的间隔和 nt 的间隔,它们都服从泊松分布,只是后者的参数是前者的 n 倍 , 所以如果我们统计 t 间隔事件数的分布与 nt 间隔事件数的分布,对应分位数的事件数后者应该是前者的 n 倍,那么在实际数据中是不是这样呢?
由于我们一般关注的是信息率比较高(订单来得比较快)的时间段,这些时候是标的价格变动比较剧烈的时间段,所以我们观察较高的分位数对应的事件数。
> quantile(c(table(cut(time, breaks=seq(min(time)-1, max(time)+1, by=1)))), c(.9, .99, .999, .99999, .99999))
90% 99% 99.9% 99.999% 99.999%
96.00 577.00 1095.00 2154.22 2154.22
> quantile(c(table(cut(time, breaks=seq(min(time)-0.01, max(time)+0.01, by=0.01)))), c(.9, .99, .999, .9999, .99999))
90% 99% 99.9% 99.99% 99.999%
1.0000 7.0000 58.0000 257.0013 380.0001
我们选取的是 1s 和 0.01s , 如果是平稳的那么 0.01s 对应的分位数应该是 1s 的百分之一左右。但是实际上在越高的分位数上这个规律就越不成立 ,在 99.99 分位和 99.999 分位上甚至超过了十分之一。
这说明事件的到来有高度聚集的特征,不能简单地用泊松过程来刻画 。
流动性研究
流动性的含义是当你想交易时,你是否有能力快速地进行大规模交易。它由三个要素组成,速度,深度和宽度。
深度主要与订单量有关,两方的订单量越多,能够承受的买压或卖压就更大,流动性就更好
而宽度主要与价格有关,在市场中我们可以看到一些交易量小的标的,它的 bid 和 ask 的距离非常之大,这样如果我们用市价单一买一卖,会有较大的损失,而对于一些交易量大的标的,bid 和 ask 的距离可能非常小,可以放心下市价单进行即时交易。
由于我们的数据里只有最优买价和最优卖价,没法对深度进行分析,我们只能通过分析 bid 和 ask 的距离(这个距离被称作 spread)来分析一下宽度。
可以看到 bid 和 ask 始终贴合得非常紧,没有出现特别大 spread 的情况,下面从数值上看一看 spread 的分布。
> table(trade$ask_price - trade$bid_price)
-50 0 25 50 75 100 125
1 140 2590741 28779 457 146 29
可以看到绝大多数时候 spread 都是 25(1 个 tick) ,说明标普 500 期货 的流动性非常之好。
限价单相对价格分析
当在准备下限价单时,我们有很多选择,可以当一个保守的人,下一个离最优价格很远的单子,也可以下离最优价格很近的单子,这样很大概率能够快速成交。
我们把相对价格定义为
bid_relative_price = (best bid - 下单价格) / tick
ask_relative_price = (下单价格 - best ask) / tick
这样可以做一个统计,来看一看限价单相对价格的统计分布。
> ask_pre <- trade$ask_price
> ask_idx <- which(trade$action_item=="ask")
> relative_ask <- (trade$price[ask_idx] - trade$ask_price[(ask_idx-1)])/25
> bid_idx <- which(trade$action_item=="bid")
> bid_idx <- bid_idx[2:length(bid_idx)]
> relative_bid <- (trade$price[bid_idx] - trade$bid_price[(bid_idx-1)])/25
> plot(density(relative_ask), col="red", main="relative price for bid and ask", xlab="relative tick")
> lines(density(relative_bid), col="blue")
从图中我们可以看到 , 买单和卖单的相对价格在0之前分布基本相同
> prop.table(table(relative_ask))
relative_ask
-4 -3 -2 -1
8.576388e-07 8.576388e-07 2.572916e-06 2.042038e-03
0 1 2 3
5.266194e-01 1.706710e-01 6.623716e-02 3.770695e-02
4 5 6 7
3.732273e-02 3.446250e-02 3.180125e-02 3.850798e-02
8 9 10 11
2.517170e-02 2.738441e-02 2.061764e-03 6.003472e-06
12
8.576388e-07
> prop.table(table(relative_bid))
relative_bid
-13 -12 -11 -10
8.426240e-07 1.685248e-06 2.527872e-06 2.045048e-03
-9 -8 -7 -6
2.599832e-02 2.254019e-02 3.917106e-02 2.934269e-02
-5 -4 -3 -2
3.606599e-02 3.614267e-02 3.971540e-02 6.508428e-02
-1 0 1 4
1.613136e-01 5.415224e-01 1.052437e-03 8.426240e-07
订单流模型拟合
指数核 hawkes 过程拟合
形象地来看,限价单组建起了买方和卖方的堡垒,而市价单则对对手的堡垒发起冲击。 那么作为进攻部队,市价单对买方卖方力量的博弈是非常关键的。
> market_order <- trade[trade$action_type=="T",]
> head(market_order)
X action_item action_type ask_price ask_vol
113073 113071 trd T 146200 73
113074 113072 trd T 146200 73
113075 113073 trd T 146200 73
113076 113074 trd T 146200 73
113077 113075 trd T 146200 73
113078 113076 trd T 146200 73
bid_price bid_vol price time vol
113073 146175 60 146175 1358229607 23
113074 146175 54 146175 1358229607 6
113075 146175 42 146175 1358229607 12
113076 146175 41 146175 1358229607 1
113077 146175 38 146175 1358229607 3
113078 146175 35 146175 1358229607 3
把时间相同的订单进行合并,并把所有时间减去初始值
> 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 过程的记忆时间 , 超过这个时间的后续事件基本不受这个事件的影响 。 这里我们可以看到影响周期小于一毫秒 , 说明频率确实非常快。
正反馈强度分析
索罗斯在他的“金融炼金术”中提出了一种叫做 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
+ }
>
> 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波动。
考虑订单数量
前面我们把订单视作同质进行分析 ,但是很明显量大的订单对市场的冲击更大,我们应该 把订单的数量考虑进去。
首先我们看一下整个时间段的订单强度:
> plot(time, vol, type="l" , ylab="volume" , xlab="time", main="market order intensity")
然后我们可以开始尝试建模,将量的因素考虑进去:
> 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)))
+ -L
+ }
> nlminb(c(1,1,1), log_likelihood_volume, event=time, vol=vol)
$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)"
订单数量的幂指数
前面我们假设订单量的影响是线性的,可真实是这样的吗,我们尝试在订单数量上加入一个幂指数,那么强度的表达式变为
> 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 , 说明订单的影响大约是数量的根号,并不是线性增长的。 这是非常有趣的一个现象 , 说明订单量的冲击是边际递减的。