Aprendizaje automático en el trading: teoría, práctica, operaciones y más - página 213

 

Otro ejemplo para una simulación.

Construimos 20.000 modelos lineales (1.000 observaciones en todas partes, número de predictores de 1 a 20 (1.000 modelos para cada número), más una variable independiente). Datos i.i.d., N(0,1).

El objetivo de la simulación es asegurarse de que el estadístico F no supera el valor crítico cuando la regresión MNA se construye sobre datos independientes (que no contienen dependencias), satisfaciendo los requisitos del modelo lin. Por lo tanto, puede utilizarse como indicador de la formación del modelo.

############### simulate lm f-stats with random vars


rm(list=ls());gc()


library(data.table)

library(ggplot2)


start <- Sys.time()


set.seed(1)


x <- as.data.table(matrix(rnorm(21000000, 0, 1), ncol = 21))

x[, sampling:= sample(1000, nrow(x), replace = T)]


lm_models <- x[, 

 {

  lapply(c(1:20), function(x) summary(lm(data = .SD[, c(1:x, 21), with = F], formula = V21 ~ . -1))$'fstatistic'[[1]])

 }

 , by = sampling

]


lm_models_melted <- melt(lm_models, measure.vars = paste0('V', c(1:20)))


crtitical_f_stats <- qf(p = 0.99, df1 = c(1:20), df2 = 1000, lower.tail = TRUE, log.p = FALSE)


boxplot(data = lm_models_melted, value ~ variable); lines(crtitical_f_stats, type = 's', col = 'red')


Sys.time() - start


gc()

Tiempo de ejecución del código: 1,35 minutos.

 

Código útil. Visualiza las secuencias de transacciones en tres hipóstasis.

##########################


rm(list=ls());gc()


library(data.table)

library(ggplot2)

library(gridExtra)

library(tseries)


start <- Sys.time()


set.seed(1)


x <- as.data.table(matrix(rnorm(1000000, 0.1, 1), ncol = 1)) #random normal value with positive expectation

x[, variable:= rep(1:1000, times = 1000)]

x[, trade:= 1:.N, by = variable]


x.cast = dcast.data.table(x, variable ~ trade, value.var = 'V1', fun.aggregate = sum)

x_cum <- x.cast[, as.list(cumsum(unlist(.SD))), by = variable]


monte_trades <- melt(x_cum, measure.vars = names(x_cum)[-1], variable.name = "trade", value.name = 'V1')

setorder(monte_trades, variable, trade)

monte_trades_last <- as.data.table(monte_trades[trade == '1000', V1])

quantile_trade <- monte_trades[, quantile(V1, probs = 0.05), by = trade]

RF_last <- monte_trades[, V1[.N] / maxdrawdown(V1)[[1]], by = variable]



p1 <- ggplot(data = monte_trades, aes(x = trade, y = V1, group = variable)) +

geom_line(size = 2, color = 'blue', alpha = 0.01) + 

geom_line(data = quantile_trade, aes(x = trade, y = V1, group = 1), size = 2, alpha = 0.5, colour = 'blue') +

ggtitle('Simulated Trade Sequences of Length 1000')


p2 <- ggplot(data = monte_trades_last, aes(V1)) +

geom_density(alpha = 0.1, size = 1, color = 'blue', fill = 'blue') + 

scale_x_continuous(limits = c(min(monte_trades$V1), max(monte_trades$V1))) +

coord_flip() +

ggtitle('Cumulative Profit Density')


p3 <- ggplot(data = RF_last, aes(V1)) +

geom_density(alpha = 0.1, size = 1, color = 'blue', fill = 'blue') + 

geom_vline(xintercept = mean(RF_last$V1), colour = "blue", linetype = 2, size = 1) +

geom_vline(xintercept = median(RF_last$V1), colour = "red", linetype = 2, size = 1) +

ggtitle('Recovery Factor Density + Mean (blue) and Median (red)')


grid.arrange(p1, p2, p3, ncol = 3)


Sys.time() - start


gc()

Funciona durante unos 45 segundos. Se dibuja durante aproximadamente 1,5 minutos.

 
Alexey Burnakov:


Hermoso, gracias.
 
Alexey Burnakov:

El objetivo de la simulación es garantizar que el estadístico F no supere un valor crítico cuando la regresión MNA se construye sobre datos independientes (que no contienen dependencias), satisfaciendo los requisitos de un modelo lineal. Por lo tanto, puede utilizarse como indicador de la formación del modelo.

No he entendido bien lo de fstatistic. Los datos aquí son aleatorios, pero el modelo está entrenado para algo, por lo que se puede concluir que el modelo está ajustado y sobreentrenado. Lo que significa que la evaluación del modelo debe ser mala. Es decir, esperaba un fstatistis negativo, o alguna otra indicación de que las cosas están mal en el gráfico.
¿Cómo puedo interpretar correctamente el resultado de ese ejemplo?
Entiendo que el primer predictor puede considerarse más cualitativo que el primero + el segundo. Y 1+2 es mejor que 1+2+3. ¿Es así? ¿Es razonable seleccionar por genética el conjunto de predictores que dará el mayor fstatistic?
 
Dr.Trader:
No entendí bien lo del fstatistic. Los datos aquí son aleatorios, pero el modelo ha aprendido algo, por lo que se puede concluir que el modelo está ajustado y sobreentrenado. Lo que significa que la evaluación del modelo debe ser mala. Es decir, esperaba un fstatistis negativo, o alguna otra indicación de que las cosas están mal en el gráfico.
¿Cómo puedo interpretar correctamente el resultado de ese ejemplo?
Entiendo que el primer predictor puede considerarse más cualitativo que el primero + el segundo. Y 1+2 es mejor que 1+2+3. ¿Es así? ¿Es razonable seleccionar por genética el conjunto de predictores que dará el mayor fstatistic?

Mira la tabla de distribución F.http://www.socr.ucla.edu/applets.dir/f_table.html

El estadístico F es un valor que depende de los grados de libertad. Siempre es positivo, porque tenemos una distribución unilateral.

Pero el modelo no aprende nada, porque un modelo entrenado debe tener un elevado estadístico F (mayor o igual que el crítico a un determinado alfa - como suena al probar la hipótesis nula).

En todos los casos, no se supera el valor crítico en alfa = 0,01, pero se podría fijar en 0,0001, por ejemplo.

Dicho esto, quería asegurarme (no estudié esto en la universidad) de que al añadir variables de ruido el modelo lineal no mostraría un aumento del aprendizaje. Como puedes ver...

F-Distribution Tables
  • Ivo Dinov: www.SOCR.ucla.edu
  • www.socr.ucla.edu
Statistics Online Computational Resource
 
Alexey Burnakov:

Código útil. Visualiza las secuencias de transacciones en tres hipóstasis.

Con respecto al código anterior. Por favor, escriba al menos un breve comentario en el código. Especialmente cuando se utilizan expresiones complejas. No todo el mundo conoce y utiliza el paquete "data.table", no está de más explicar qué hace dcast.data.table, fundir qué es .N, .SD. No publicas el código para demostrar lo mucho que sabes del tema. En mi opinión, el código publicado debería ayudar a otros usuarios (incluso con un nivel de formación elemental) a entender el script.

Está muy bien que R permita programar la acción de varias maneras, pero es deseable que no se pierda la legibilidad del código.

Algunas sugerencias sobre el código:

- las variables intermedias x, x.cast, x.cum no son necesarias en los cálculos y sólo ocupan memoria. Todos los cálculos que no requieran guardar resultados intermedios deben realizarse preferentemente a través de la tubería

Por ejemplo

#---variant-------------
rm(list=ls());gc()
library(data.table)
library(ggplot2)
library(gridExtra)
library(tseries)
#----
require(magrittr)
require(dplyr)
start <- Sys.time()
monte_trades <- as.data.table(matrix(rnorm(1000000, 0.1, 1), ncol = 1)) %>%
        .[, variable := rep(1:1000, times = 1000)]%>%
        .[, trade := 1:.N, by = variable] %>%
        dcast.data.table(., variable ~ trade, value.var = 'V1', fun.aggregate = sum)%>%
        .[, as.list(cumsum(unlist(.SD))), by = variable]%>%
        melt(., measure.vars = names(.)[-1], variable.name = "trade", value.name = 'V1')%>%
        setorder(., variable, trade)
monte_trades_last <- as.data.table(monte_trades[trade == '1000', V1])
quantile_trade <- monte_trades[, quantile(V1, probs = 0.05), by = trade]
RF_last <- monte_trades[, V1[.N] / maxdrawdown(V1)[[1]], by = variable]
Sys.time() - start
#Time difference of 2.247022 secs

Por supuesto, se necesita mucho tiempo para construir gráficos.

No es una crítica.

Buena suerte

 
Dr.Trader:
No entendí bien lo del fstatistic. Los datos aquí son aleatorios, pero el modelo ha aprendido algo, por lo que se puede concluir que el modelo está ajustado y sobreentrenado. Lo que significa que la evaluación del modelo debe ser mala. Es decir, esperaba un fstatistis negativo, o alguna otra indicación de que las cosas están mal en el gráfico.
¿Cómo puedo interpretar correctamente el resultado de ese ejemplo?
Entiendo que el primer predictor puede considerarse más cualitativo que el primero + el segundo. Y 1+2 es mejor que 1+2+3. ¿Es así? ¿Es razonable seleccionar por genética el conjunto de predictores que dará el mayor fstatistic?

Y aquí tenemos un ejemplo en el que suponemos que el modelo completamente entrenado incluirá 20 variables, con pesos crecientes (1 variable - peso 1, la 20ª variable - peso 20). Y veamos cómo cambiará la distribución de los estadísticos F tras la adición sucesiva de predictores al modelo:

############### simulate lm f-stats with non-random vars


rm(list=ls());gc()


library(data.table)

library(ggplot2)


start <- Sys.time()


set.seed(1)


x <- as.data.table(matrix(rnorm(20000000, 0, 1), ncol = 20))

x[, (paste0('coef', c(1:20))):= lapply(1:20, function(x) rnorm(.N, x, 1))]

x[, output:= Reduce(`+`, Map(function(x, y) (x * y), .SD[, (1:20), with = FALSE], .SD[, (21:40), with = FALSE])), .SDcols = c(1:40)]

x[, sampling:= sample(1000, nrow(x), replace = T)]


lm_models <- x[, 

 {

  lapply(c(1:20), function(x) summary(lm(data = .SD[, c(1:x, 41), with = F], formula = output ~ . -1))$'fstatistic'[[1]])

 }

 , by = sampling

]


lm_models_melted <- melt(lm_models, measure.vars = paste0('V', c(1:20)))


crtitical_f_stats <- qf(p = 0.99, df1 = c(1:20), df2 = 1000, lower.tail = TRUE, log.p = FALSE)


boxplot(data = lm_models_melted, value ~ variable, log = 'y'); lines(crtitical_f_stats, type = 's', col = 'red')


summary(lm(data = x[sample(1000000, 1000, replace = T), c(1:20, 41), with = F], formula = output ~ . -1))


Sys.time() - start


gc()

Gráfico con eje Y logarítmico:

Aparentemente, sí...

> summary(lm(data = x[sample(1000000, 1000, replace = T), c(1:20, 41), with = F], formula = output ~ . -1))


Call:

lm(formula = output ~ . - 1, data = x[sample(1e+06, 1000, replace = T), 

    c(1:20, 41), with = F])


Residuals:

     Min       1Q   Median       3Q      Max 

-19.6146  -2.8252   0.0192   3.0659  15.8853 


Coefficients:

    Estimate Std. Error t value Pr(>|t|)    

V1    0.9528     0.1427   6.676  4.1e-11 ***

V2    1.7771     0.1382  12.859  < 2e-16 ***

V3    2.7344     0.1442  18.968  < 2e-16 ***

V4    4.0195     0.1419  28.325  < 2e-16 ***

V5    5.2817     0.1479  35.718  < 2e-16 ***

V6    6.2776     0.1509  41.594  < 2e-16 ***

V7    6.9771     0.1446  48.242  < 2e-16 ***

V8    7.9722     0.1469  54.260  < 2e-16 ***

V9    9.0349     0.1462  61.806  < 2e-16 ***

V10  10.1372     0.1496  67.766  < 2e-16 ***

V11  10.8783     0.1487  73.134  < 2e-16 ***

V12  11.9129     0.1446  82.386  < 2e-16 ***

V13  12.8079     0.1462  87.588  < 2e-16 ***

V14  14.2017     0.1487  95.490  < 2e-16 ***

V15  14.9080     0.1458 102.252  < 2e-16 ***

V16  15.9893     0.1428 111.958  < 2e-16 ***

V17  17.4997     0.1403 124.716  < 2e-16 ***

V18  17.8798     0.1448 123.470  < 2e-16 ***

V19  18.9317     0.1470 128.823  < 2e-16 ***

V20  20.1143     0.1466 137.191  < 2e-16 ***

---

Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1


Residual standard error: 4.581 on 980 degrees of freedom

Multiple R-squared:  0.9932, Adjusted R-squared:  0.993 

F-statistic:  7123 on 20 and 980 DF,  p-value: < 2.2e-16

 
Vladimir Perervenko:

Gracias. Todavía no sabía cómo hacerlo. Pero, en realidad, hay que tener en cuenta los cálculos en la medida de lo posible. Será más rápido. Buen kung fu...

Con sus retoques:

#---variant-------------

rm(list=ls());gc()

library(data.table)

library(ggplot2)

library(gridExtra)

library(tseries)

#----

require(magrittr)

require(dplyr)

start <- Sys.time()

monte_trades <- as.data.table(matrix(rnorm(1000000, 0.1, 1), ncol = 1)) %>%

.[, variable := rep(1:1000, times = 1000)]%>%

.[, trade := 1:.N, by = variable] %>%

dcast.data.table(., variable ~ trade, value.var = 'V1', fun.aggregate = sum)%>%

.[, as.list(cumsum(unlist(.SD))), by = variable]%>%

melt(., measure.vars = names(.)[-1], variable.name = "trade", value.name = 'V1')%>% 

setorder(., variable, trade)

monte_trades_last <- as.data.table(monte_trades[trade == '1000', V1])

quantile_trade <- monte_trades[, quantile(V1, probs = 0.05), by = trade]

RF_last <- monte_trades[, V1[.N] / maxdrawdown(V1)[[1]], by = variable]


p1 <- ggplot(data = monte_trades, aes(x = trade, y = V1, group = variable)) +

geom_line(size = 2, color = 'blue', alpha = 0.01) + 

geom_line(data = quantile_trade, aes(x = trade, y = V1, group = 1), size = 2, alpha = 0.5, colour = 'blue') +

ggtitle('Simulated Trade Sequences of Length 1000')


p2 <- ggplot(data = monte_trades_last, aes(V1)) +

geom_density(alpha = 0.1, size = 1, color = 'blue', fill = 'blue') + 

scale_x_continuous(limits = c(min(monte_trades$V1), max(monte_trades$V1))) +

coord_flip() +

ggtitle('Cumulative Profit Density')


p3 <- ggplot(data = RF_last, aes(V1)) +

geom_density(alpha = 0.1, size = 1, color = 'blue', fill = 'blue') + 

geom_vline(xintercept = mean(RF_last$V1), colour = "blue", linetype = 2, size = 1) +

geom_vline(xintercept = median(RF_last$V1), colour = "red", linetype = 2, size = 1) +

ggtitle('Recovery Factor Density + Mean (blue) and Median (red)')


grid.arrange(p1, p2, p3, ncol = 3)


Sys.time() - start

La duración es de 47 segundos. Es decir, el código es más bonito y más compacto, pero no hay diferencia en la velocidad... Dibujo, sí, muy largo. 1000 líneas con transparencia - por ellos...

 
Alexey Burnakov:

Gracias. Todavía no sabía cómo hacerlo. Pero, en realidad, hay que tener en cuenta los cálculos en la medida de lo posible. Será más rápido. Buen kung fu...

Duración 47 segundos. Es decir, el código es más bonito y más compacto, pero no hay diferencia en la velocidad... El dibujo, sí, es muy largo. 1000 líneas con transparencia - por ellos...

Mi cálculo toma

# tiempo de ejecución en segundos

# min lq mean median uq max neval

# 2.027561 2.253354 2.254134 2.275785 2.300051 2.610649 100

Pero no es tan importante. Se trataba de la legibilidad del código.

Buena suerte

PS. Y paraleliza el cálculo de lm(). Este es exactamente el caso cuando se necesita

 
Vladimir Perervenko:

Tengo un cálculo que toma

#Tiempo de ejecución en segundos

# min lq mean median uq max neval

# 2.027561 2.253354 2.254134 2.275785 2.300051 2.610649 100

Pero no es tan importante. Se trataba de la legibilidad del código.

Buena suerte

PS. Y paraleliza el cálculo de lm(). Esto es exactamente el caso cuando es necesario.

No. Estás dando el tiempo de una parte del código antes de los gráficos. Lo he indicado, junto con los gráficos.

Tengo 1,5 segundos antes de los gráficos. Su método es de 1,15 segundos.

rm(list=ls());gc()


library(data.table)

library(ggplot2)

library(gridExtra)

library(tseries)


start <- Sys.time()


set.seed(1)


x <- as.data.table(matrix(rnorm(1000000, 0.1, 1), ncol = 1)) #random normal value with positive expectation

x[, variable:= rep(1:1000, times = 1000)]

x[, trade:= 1:.N, by = variable]


x.cast = dcast.data.table(x, variable ~ trade, value.var = 'V1', fun.aggregate = sum)

x_cum <- x.cast[, as.list(cumsum(unlist(.SD))), by = variable]


monte_trades <- melt(x_cum, measure.vars = names(x_cum)[-1], variable.name = "trade", value.name = 'V1')

setorder(monte_trades, variable, trade)

monte_trades_last <- as.data.table(monte_trades[trade == '1000', V1])

quantile_trade <- monte_trades[, quantile(V1, probs = 0.05), by = trade]

RF_last <- monte_trades[, V1[.N] / maxdrawdown(V1)[[1]], by = variable]


Sys.time() - start

rm(list=ls());gc()

library(data.table)

library(ggplot2)

library(gridExtra)

library(tseries)

#----

require(magrittr)

require(dplyr)


start <- Sys.time()


monte_trades <- as.data.table(matrix(rnorm(1000000, 0.1, 1), ncol = 1)) %>%

.[, variable := rep(1:1000, times = 1000)]%>%

.[, trade := 1:.N, by = variable] %>%

dcast.data.table(., variable ~ trade, value.var = 'V1', fun.aggregate = sum)%>%

.[, as.list(cumsum(unlist(.SD))), by = variable]%>%

melt(., measure.vars = names(.)[-1], variable.name = "trade", value.name = 'V1')%>% 

setorder(., variable, trade)

monte_trades_last <- as.data.table(monte_trades[trade == '1000', V1])

quantile_trade <- monte_trades[, quantile(V1, probs = 0.05), by = trade]

RF_last <- monte_trades[, V1[.N] / maxdrawdown(V1)[[1]], by = variable]


Sys.time() - start

Resulta que su método es más rápido...