Pregunta ¿Cuál es la forma correcta de hacer una estrategia de marcos de tiempo múltiples con quantstrat?


Aquí hay un ejemplo de una estrategia de varios períodos de tiempo en la que estoy trabajando con quantstrat. ¿Es esta la forma correcta de hacer una estrategia de varios plazos o estoy haciendo todo mal? No he encontrado ningún otro ejemplo haciendo multi-timeframe en las demostraciones de quantstrat o de googlear.

Para mantener la parte de estrategia simple (esta no es una estrategia que alguien pueda cambiar) y mantener el enfoque en el aspecto de multi-timeframe, demostraré una estrategia simple que usa datos de tick y Datos de OHLC de 5 minutos. La lógica de la estrategia es comprar cuando los datos del tic cruzan por encima del SMA de 30 períodos de los datos de 5 minutos y cierran la posición cuando los datos del tic cruzan por debajo de la misma SMA.

Por ejemplo: si la estrategia es plana, el tiempo es 13:02 y el SMA de 30 periodos observado anteriormente de los datos de 5 minutos es 90.55 (para el período de 12: 55 - finaliza a las 13:00) y los datos de tilde cruza desde menos de 90.55 hasta arriba (90.56) es una compra, y cuando los datos de marcación se cierran debajo de ella nuevamente, sale de la posición.

Para lograr esto, necesito obtener los datos de ticks y SMA de 5 minutos y 30 periodos en el mismo objeto para que quantstrat los procese. Obtengo los OHTS xts de 5 minutos y calculo un SMA de 30 periodos. Luego fusiono esto en el objeto tick data xts, que me dará un objeto con todos los datos de ticks y luego cada 5 minutos obtendré una fila para la última SMA observada de 5 minutos y 30 períodos.

Si hay un valor de SMA de 30 periodos a las 13:00, esto es para los 5 minutos de 12: 55-13: 00. Como la próxima actualización de SMA es 5 minutos después, necesito completar las filas hasta que se observe el siguiente valor (a las 13:05) y así sucesivamente.

Aquí esta la head de los datos de ticks (los datos de ticks que tengo no incluyen milisegundos, pero he hecho que las filas sean únicas usando make.index.unique(clemtick):

head(clemtick)
                    Price Volume
2013-01-15 09:00:00 93.90      1
2013-01-15 09:00:00 93.89      1
2013-01-15 09:00:00 93.89      1
2013-01-15 09:00:00 93.88      2
2013-01-15 09:00:00 93.89      1
2013-01-15 09:00:00 93.89      2

Aquí esta la head de los datos de 1 minuto (cada minuto representa el minuto anterior de datos, por ejemplo, marca de tiempo 09:01:00 == datos de 09:00:00 a 09:01:00):

head(clemin)
                     Open  High   Low Close Volume
2013-01-15 09:01:00 93.90 94.04 93.87 93.97   1631
2013-01-15 09:02:00 93.97 93.98 93.90 93.91    522
2013-01-15 09:03:00 93.91 93.97 93.90 93.96    248
2013-01-15 09:04:00 93.95 93.98 93.93 93.95    138
2013-01-15 09:05:00 93.95 93.96 93.91 93.92    143
2013-01-15 09:06:00 93.93 93.97 93.91 93.91    729

Convierta los datos de 1 minuto a datos de 5 minutos:

cle5min <- to.minutes5(clemin)
                    clemin.Open clemin.High clemin.Low clemin.Close clemin.Volume
2013-01-15 09:04:00       93.90       94.04      93.87        93.95          2539
2013-01-15 09:09:00       93.95       93.97      93.81        93.89          2356
2013-01-15 09:14:00       93.90       94.05      93.86        93.89          4050
2013-01-15 09:19:00       93.90       94.03      93.84        94.00          2351
2013-01-15 09:24:00       93.99       94.21      93.97        94.18          3261
2013-01-15 09:29:00       94.18       94.26      94.18        94.19          1361

Notarás que el primer OHLC es 09:04:00, esto se debe a la forma en que to.minutes5 función funciona, que es discutido en este hilo. Esencialmente, la primera marca de tiempo 09:04:00 == el OHLC 4 minutos de datos de 09:00:00 - 09:04:00. la marca de tiempo 09:09:00 es los próximos 5 minutos completos de 09:04:00 - 09:09:00. Idealmente, me gustaría que cada marca de tiempo sea 5, 10, 15, etc., pero todavía no he resuelto cómo hacerlo.

Para obtener los 30 SMA de los datos de 5 min en los datos de tilde

clemtick$sma30 <- SMA(cle5min$clemin.Close, 30)

Esto crea una nueva columna con SMA. La SMA necesita 30 periodos para calcular el primer valor y el SMA solo aparecerá por cada sello de tiempo de 5 minutos (11:29:00, 11:34:00, 11:39, ...). Parece que:

clemtick["2013-01-15 11:28:59::2013-01-15 11:29:00"]
                    Price Volume    SMA30
2013-01-15 11:28:59 93.87      1       NA
2013-01-15 11:28:59 93.87      1       NA
2013-01-15 11:28:59 93.88      1       NA
2013-01-15 11:29:00 93.87      1 93.92633
2013-01-15 11:29:00 93.87      1       NA
2013-01-15 11:29:00 93.88      1       NA
2013-01-15 11:29:00 93.88      1       NA

Ahora necesito rellenar el SMA30 columna con un valor de repetición. El valor para SMA30 a las 11:29:00 es para el OHLC de 11:24:00 - 11:29:00. La próxima actualización de este valor no será hasta las 11:34:00, así que necesito completar las filas hasta el siguiente valor, ya que esto es a lo que la estrategia hará referencia cuando procese fila por fila.

clemtick  <- na.locf(clemtick)

Ahora si vuelvo a consultar ese objeto,

clemtick["2013-01-15 11:33:58::2013-01-15 11:34:01"]
                    Price Volume    SMA30
2013-01-15 11:33:58 93.84      1 93.92633
2013-01-15 11:34:00 93.84      1 93.92267
2013-01-15 11:34:00 93.85      1 93.92267
2013-01-15 11:34:01 93.84      1 93.92267

Ahora que tenemos el objetivo final aquí, ejecutamos la estrategia:

require(quantstrat)

options("getSymbols.warning4.0"=FALSE)
rm(list=ls(.blotter), envir=.blotter)
Sys.setenv(TZ="UTC")

symbols  <- "clemtick"
currency('USD')
stock(symbols, currency="USD", multiplier=1)

account.st  <- 0
strategy.st  <- portfolio.st <- account.st  <- "multi"
rm.strat(portfolio.st)
rm.strat(strategy.st)

initDate <- "1980-01-01"
tradeSize  <- 1000
initEq  <- tradeSize*length(symbols)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st,
         initDate=initDate, currency='USD', initEq=initEq)
initOrders(portfolio.st, initDate=initDate)

strategy(strategy.st, store=TRUE)

add.signal(strategy.st, name="sigCrossover",
  arguments=list(columns=c("Price", "sma30"), relationship="gt"),
  label="golong") 

add.signal(strategy.st, name="sigCrossover",
  arguments=list(columns=c("Price", "sma30"), relationship="lt"),
  label="exitlong")

#enter rule
add.rule(strategy.st, name="ruleSignal",
  arguments=list(sigcol="golong",
                 sigval=TRUE,
                 ordertype="market",
                 orderside="long",
                 replace=TRUE,
                 prefer="Price",
                 orderqty=1),
  type="enter", path.dep=TRUE, label="long")

#exit rule
add.rule(strategy.st, name = "ruleSignal",
  arguments=list(sigcol="exitlong",
                 sigval=TRUE,
                 ordertype="market",
                 orderside="long",
                 replace=TRUE,
                 prefer="Price",
                 orderqty=-1),
  type="exit", path.dep=TRUE, label="exitlong")

#apply strategy
t1 <- Sys.time()
out2 <- applyStrategy(strategy=strategy.st, portfolios=portfolio.st, debug=TRUE)
t2 <- Sys.time()
print(t2-t1)
head(mktdata)
nrow(mktdata)

Entonces, para resumir, ¿es esta la mejor manera de hacer estrategias de marcos de tiempo múltiples?


8
2017-08-18 05:12


origen


Respuestas:


Aquí hay dos enfoques para incorporar indicadores / señales de marco de tiempo múltiples en su estrategia. Ambos salen de la caja utilizando solo datos de muestra de quantstrat.

Ambos siguen la misma estrategia (y dan resultados idénticos): la estrategia usa una SMA (20) en barras de 1 minuto y una SMA (10) en barras de 30 minutos para generar señales comerciales. Se ingresa una posición larga cuando SMA (barras de 20, 1 min) cruza por encima de SMA (barras de 10, 30 minutos). Salir de un largo posición cuando SMA (20, barras de 1 minuto) cruza debajo de SMA (barras de 10, 30 minutos)

Método 1: compilar datos de precios e indicadores en inferior frecuencias de tiempo dentro una función personalizada llamada por add.indicator. (No puedes ir a frecuencias de tiempo más altas que los datos de mercado bruto para el símbolo).

from <- "2002-10-20"
to <- "2002-10-24"

symbols <- "GBPUSD"
# Load 1 minute data stored in the quantstrat package
getSymbols.FI(Symbols = symbols,
              dir=system.file('extdata',package='quantstrat'),
              from=from, 
              to=to
)

currency(c('GBP', 'USD'))
exchange_rate('GBPUSD', tick_size=0.0001)

strategy.st <- "multiFrame"
portfolio.st <- "multiFrame"
account.st <- "multiFrame"

initEq <- 50000

rm.strat(strategy.st)
initPortf(portfolio.st, symbols = symbols)
initAcct(account.st, portfolios = portfolio.st, initEq = initEq)
initOrders(portfolio.st)
strategy(strategy.st, store = TRUE)

# Create an SMA on 20 1 minute bars:
add.indicator(strategy.st, name = "SMA", 
              arguments = list(x = quote(Cl(mktdata)),
                                n = 20), 
              label = "MA20")

# Define the function that add.indicator will use to create an SMA(10) on 30 minute bars:
ind30minMA <- function(x, n30min = 10) {

  if (!is.OHLC(x)) 
    stop("Must pass in OHLC data")
  x.h <- to.period(x[, 1:4], period = "minutes", k = 30, indexAt = "endof") 
  #^ Ensure that the timestamp on the lower frequency data is at the END of the bar/candle, to avoid look forward bias.

  # If you need to know what symbol you are currently processing:
  # symbol <- parent.frame(n = 2)$symbol
  sma.h <- SMA(Cl(x.h), n = n30min)
  r <- merge(sma.h, xts(, index(x)), fill= na.locf) 
  #^ Carry forward the last value, no lookforward bias introduced

  r <- r[index(x)]
  # if you don't return the same # of rows in the argument x, then quantstrat won't work correctly. So let's check the data is OK after the merge above:
  stopifnot(NROW(r) == NROW(x))
  r
}

add.indicator(strategy.st, name = "ind30minMA", 
              arguments = list(x = quote(mktdata),
                               n30min = 10), 
              label = "MA30minbar")

add.signal(strategy.st, name = "sigCrossover", 
              arguments = list(columns = c("SMA.MA20", "SMA.MA30minbar"),
                               relationship = "gt"),
              label = "FastCrossUp")

add.signal(strategy.st, name = "sigCrossover", 
           arguments = list(columns = c("SMA.MA20", "SMA.MA30minbar"),
                            relationship = "lt"),
           label = "FastCrossDn")

add.rule(strategy.st,name='ruleSignal', 
         arguments = list(sigcol="FastCrossUp",
                          sigval=TRUE, 
                          orderqty= 100, 
                          ordertype='market', 
                          orderside='long', 
                          threshold=NULL),
         type='enter',
         label='enterL',
         storefun=FALSE
)

add.rule(strategy.st,name='ruleSignal',
         arguments = list(sigcol="FastCrossDn",
                          sigval=TRUE,
                          orderqty='all',
                          ordertype='market',
                          orderside='long',
                          threshold=NULL,
                          orderset='default',
                          replace = TRUE),
         type='exit',
         label='exitL'
)


applyStrategy(strategy.st, portfolio.st)


tail(mktdata)
# Open   High    Low  Close Volume SMA.MA20 SMA.MA30minbar FastCrossUp FastCrossDn
# 2002-10-24 17:54:00 1.5552 1.5552 1.5552 1.5552      0 1.555115        1.55467          NA          NA
# 2002-10-24 17:55:00 1.5552 1.5552 1.5551 1.5551      0 1.555120        1.55467          NA          NA
# 2002-10-24 17:56:00 1.5551 1.5551 1.5551 1.5551      0 1.555125        1.55467          NA          NA
# 2002-10-24 17:57:00 1.5551 1.5551 1.5551 1.5551      0 1.555130        1.55467          NA          NA
# 2002-10-24 17:58:00 1.5551 1.5551 1.5551 1.5551      0 1.555130        1.55467          NA          NA
# 2002-10-24 17:59:00 1.5551 1.5551 1.5551 1.5551      0 1.555135        1.55478          NA          NA

tx <- getTxns(portfolio.st, "GBPUSD")
# Record total PL earned.  This number should be identical to the result from the second approach listed below:
sum(tx$Net.Txn.Realized.PL)
# -0.03

Enfoque 2: la idea es que ya hemos calculado los datos diarios del mercado con nombres en el espacio de nombres global de [symbol].d (ver más abajo para lo que quiero decir). Esta información diaria podría cargarse desde el disco en la memoria también, obviamente. Usamos estos conjuntos de datos precalculados en diferentes frecuencias de tiempo, en lugar de calcular los datos de barras dentro de la función de indicador (como se hace en indDailyMA arriba):

Se puede decir que este enfoque es más avanzado y eficiente en cuanto a memoria, ya que no estamos computando las agregaciones (que pueden ser computacionalmente costosas cuando se trabaja con datos de tics, por ejemplo).

library(quantstrat)

from <- "2002-10-20"
to <- "2002-10-24"

symbols <- "GBPUSD"
# Load 1 minute data stored in the quantstrat package
getSymbols.FI(Symbols = symbols,
              dir=system.file('extdata',package='quantstrat'),
              from=from, 
              to=to
)

currency(c('GBP', 'USD'))
exchange_rate('GBPUSD', tick_size=0.0001)

strategy.st <- "multiFrame"
portfolio.st <- "multiFrame"
account.st <- "multiFrame"

# Parameters:

initEq <- 50000



rm.strat(strategy.st)
initPortf(portfolio.st, symbols = symbols)
initAcct(account.st, portfolios = portfolio.st, initEq = initEq)
initOrders(portfolio.st)
strategy(strategy.st, store = TRUE)


GBPUSD <- GBPUSD[, colnames(GBPUSD) != "Volume"]

# Before running the backtest, create the lower frequency market data
GBPUSD.30m <- to.period(OHLC(GBPUSD), period = "minutes", k = 30, indexAt = "endof", name = "GBPUSD") 

GBPUSD.1m.idx <- index(GBPUSD)

NROW(GBPUSD)
# 5276

# Add the lower frequency data indicators to the higher frequency data that will be processed in quantstrat.  Fill forward the lower frequency moving average

GBPUSD <- merge(GBPUSD, setNames(SMA(Cl(GBPUSD.30m), n = 10), "SMA.MA30minbar"))
GBPUSD$SMA.MA30minbar <- na.locf(GBPUSD$SMA.MA30minbar)

# Note: Short hand for the above will the fill argument, which can be helpful in special cases where NAs only exist in the new data to be added:
# GBPUSD <- merge(GBPUSD, setNames(SMA(Cl(GBPUSD.30m), n = 10), "SMA.MA30minbar"),  fill = na.locf)

NROW(GBPUSD)
# 5276

# After doing this merge, sometimes extra rows will appear beyond what GBPUSD (based on the original 1 min bar data) 
GBPUSD <- GBPUSD[GBPUSD.1m.idx, ]

# Now GBPUSD, which will be the raw data used in applyStrategy, already contains the 30 min bar indicators.

add.indicator(strategy.st, name = "SMA", 
              arguments = list(x = quote(Cl(mktdata)),
                               n = 20), 
              label = "MA20")



add.signal(strategy.st, name = "sigCrossover", 
           arguments = list(columns = c("SMA.MA20", "SMA.MA30minbar"),
                            relationship = "gt"),
           label = "FastCrossUp")

add.signal(strategy.st, name = "sigCrossover", 
           arguments = list(columns = c("SMA.MA20", "SMA.MA30minbar"),
                            relationship = "lt"),
           label = "FastCrossDn")

add.rule(strategy.st,name='ruleSignal', 
         arguments = list(sigcol="FastCrossUp",
                          sigval=TRUE, 
                          orderqty= 100, 
                          ordertype='market', 
                          orderside='long', 
                          threshold=NULL),
         type='enter',
         label='enterL',
         storefun=FALSE
)

add.rule(strategy.st,name='ruleSignal',
         arguments = list(sigcol="FastCrossDn",
                          sigval=TRUE,
                          orderqty='all',
                          ordertype='market',
                          orderside='long',
                          threshold=NULL,
                          orderset='sysMACD',
                          replace = TRUE),
         type='exit',
         label='exitL'
)


applyStrategy(strategy.st, portfolio.st)


tail(mktdata)
# Open   High    Low  Close SMA.MA30minbar SMA.MA20 FastCrossUp FastCrossDn
# 2002-10-24 17:54:00 1.5552 1.5552 1.5552 1.5552        1.55467 1.555115          NA          NA
# 2002-10-24 17:55:00 1.5552 1.5552 1.5551 1.5551        1.55467 1.555120          NA          NA
# 2002-10-24 17:56:00 1.5551 1.5551 1.5551 1.5551        1.55467 1.555125          NA          NA
# 2002-10-24 17:57:00 1.5551 1.5551 1.5551 1.5551        1.55467 1.555130          NA          NA
# 2002-10-24 17:58:00 1.5551 1.5551 1.5551 1.5551        1.55467 1.555130          NA          NA
# 2002-10-24 17:59:00 1.5551 1.5551 1.5551 1.5551        1.55478 1.555135          NA          NA

tx <- getTxns(portfolio.st, "GBPUSD")
sum(tx$Net.Txn.Realized.PL)
# -0.03

# Same result as the first approach, as we would expect

También puede encontrar útiles otras referencias sobre este tema:

Generando indicadores de diferente periodicidad en quantstrat

http://r.789695.n4.nabble.com/R-Quantstrat-package-question-td3772989.html


2
2018-05-16 12:06