Encontré a Lee & Mykland (2012, https://econpapers.repec.org/article/eeeeconom/v_3a168_3ay_3a2012_3ai_3a2_3ap_3a396-406.htm ) para ser una buena aplicación para el prepromedio de un proceso de precios ruidoso.
Intentaré explicarlo de forma no demasiado rigurosa, así que, por favor, corregidme si soy chapucero.
En aras de la exhaustividad, fijemos un espacio de probabilidad completo $(\Omega , \mathcal{F}_t, \mathcal{P})$ donde $\Omega$ es el conjunto de eventos en un mercado financiero, $\left \{ \mathcal{F}_t:t\in\left [ 0,T \right ] \right \}$ es la filtración de información continua para los participantes en el mercado, y $\mathcal{P}$ es una medida generadora de datos. Hasta aquí la base teórica.
Ahora, supongamos que nuestro proceso de precios es una variable aleatoria, por ejemplo \begin {align}dX_{t} = \sigma dW_{t} \end {align}
Tenemos $X_t\in \mathbb {R}$ como nuestro precio logarítmico real no observado, $\sigma\in \mathbb {R}^+$ la volatilidad, y $W_t\in \mathbb {R}$ algunos $\mathcal{F}_t$ -movimiento browniano adaptado. Esta ecuación simplemente nos dice que nuestro precio observado se mueve aleatoriamente hacia arriba y hacia abajo dentro de un corredor. En el documento original, esta ecuación también incluye un componente de salto, pero como la OP no preguntó específicamente sobre esto, lo omití.
Ahora, denotemos el precio observado y ruidoso con componente de ruido $\epsilon_t \in \mathbb {R} $ como \begin {align}P_{t} = X_{t} + \epsilon_t\end {align}
Es decir, el precio real está difuminado por el ruido de la microestructura del mercado, y no podemos observar el precio real. Cuando se trata de datos de ticks, todas nuestras observaciones en el horizonte temporal registrado $[0,T]$ se encuentran en la red $\mathcal{G}_n$ : \begin {align} \mathcal {G}_n = \left \N - 0 = t_{n,0} < t_{n,1} < \cdots < t_{n,n} = T \right \} \end {align}
Esto puede parecer un poco extenso, pero nos permite decir que observamos las operaciones siempre que se producen, no sólo en períodos regulares como 1 segundo, 5 segundos, etc.
Para aproximar el precio real, tenemos que hacer algunas suposiciones sobre el proceso de ruido. En este caso suponemos que el ruido es estacionario y con media cero. También suponemos que el ruido es $(k-1)$ -dependiente. En otras palabras, sólo cada $k$ a observación es independiente. Por lo tanto, submuestreamos nuestras observaciones en una cuadrícula reducida $\mathcal{G}_n^{k}$ : \begin {align} \mathcal {G}_n^{k} = \left \t_n < t_{n,k} < t_{n,2k} < \cdots \right \} \end {align}
Los precios submuestreados según esta cuadrícula se denominan $\tilde{P}(t_{ik})$ . Para denotar este precio, tomamos los promedios de $\tilde{P}(t_{ik})$ sobre algunos bloques de tamaño $M \in N^+$ . Esta parte es un poco complicada, los autores del artículo dieron recomendaciones basadas en datos y en simulaciones, así que me saltaré esta parte, pero incluiré un ejemplo. El precio preprocesado con (esperemos que menos ruido de microestructura) es \begin {align} \hat {P}(t_j) = \frac {1}{M} \sum_ {i = \left \lfloor j/k \right \rfloor }^{ \left \lfloor j/k \right \rfloor + M-1} \tilde {P}(t_{ik}) \end {align}
Como todo esto es muy teórico, he aquí un ejemplo en R:
## install and load packages ##
libraries = c("data.table") # needed for shift function
lapply(libraries, function(x) if (!(x %in% installed.packages())) {install.packages(x)} )
invisible(lapply(libraries, library, quietly = TRUE, character.only = TRUE))
## ##
## Get data ##
set.seed(234)
P <- rnorm(100000, 100, 3)
P_tilde <- log(P)
## ##
## Determine k ##
# acf #
bacf <- acf(diff(P_tilde), plot = FALSE) # get ACF data
bacfdf <- with(bacf, data.frame(lag, acf))
# CI # https://stackoverflow.com/questions/42753017/adding-confidence-intervals-to-plotted-acf-in-ggplot2 #
alpha <- 0.90
conf.lims <- c(-1,1)*qnorm((1 + alpha)/2)/sqrt(bacf$n.used)
##
lag_outside_conf.lim <- sort(c(which(bacfdf$acf < conf.lims[1]), which(bacfdf$acf > conf.lims[2])))
# Specify k = maximum lag value + 1 #
k <- max(lag_outside_conf.lim) + 1
# note that the choice of k is very important, so if you aren't dealing with a lot of data you may to this visually,
# also, there may be more advanced approaches for handling this #
## ##
# Get n #
n <- length(P_tilde)
# Get n-k #
n_diff <- n - k
# Vector with P_tilde_m+k values
P_tilde_shift <- shift(P_tilde, n = k, type = "lead")
## Calculate block size M ##
# Calculate q hat (this is from the paper, see table 5) #
q_hat <- sqrt(1/(2 * n_diff) * sum((P_tilde[1:n_diff] - P_tilde_shift[1:n_diff])^2)) # *100 for percentage value (but this will cause trouble in further calculations)
# Determine C w.r.t. q_hat #
C <- NaN
if (q_hat * 100 > 1) {C <- 1/8; print("Q_Hat is larger than 1. C defaulted to 1/8. Choose better value.")}
if (q_hat * 100 <= 1) C <- 1/8
if (q_hat * 100 <= (0.9 + 0.8)/2) C <- 1/9
if (q_hat * 100 <= (0.3 + 0.4)/2) C <- 1/16
if (q_hat * 100 <= (0.1 + 0.2)/2) C <- 1/18
if (q_hat * 100 <= (0.05 + 0.07)/2) C <- 1/19
if (q_hat * 100 < 0.01) print("Q_Hat is smaller than 0.01. C defaulted to 1/19. Choose better value.")
# Calculate M #
if (floor(C*sqrt(n/k)) == 0) {M <- 1
} else {
M <- floor(C*sqrt(n/k))}
# if testing different values for C #
C_vector <- c(1/8, 1/9, 1/16, 1/18, 1/19)
M_vector <- floor(C_vector*sqrt(n/k))
M_vector <- unique(M_vector)
# Calculate P_hat_tj (first iteration) #
G_n_k <- seq(from = 0, to = n, by = k) # Grid for first subsampling
G_n_k[1] <- 1
P_tilde_t_ik <- P_tilde[G_n_k]
P_hat_tj <- array(dim = (length(G_n_k) - 1)) # preallocate
# Calculate P_hat_tj (second iteration) #
for (i in G_n_k) P_hat_tj[[i]] <- mean(P_tilde_t_ik[(floor(i/k)):(floor(i/k)+M-1)])
G_n_kM <- seq(from = 0, to = n, by = k*M) # Grid for second subsampling
G_n_kM[1] <- 1
P_hat_tj <- P_hat_tj[G_n_kM] # keep only those times t_j where they lie in the grid G_n_kM
Espero que se permita este tipo de publicidad, pero implementé la metodología completa de este trabajo aquí: https://github.com/YalDan/JumpDetectR