Преобразование из R в настройку quantstrat для тестирования торговой стратегии на исторических данных

Я пытаюсь протестировать торговую стратегию с помощью пакета «quantstrat». Моя стратегия состоит из 4 индикаторов, 3 разных EMA и 1 запаздывающей EMA.

Я хочу открывать длинную позицию, когда: EMA1 > EMA2 и EMA1 > EMA3 и EMA1_lag ‹ EMA1 Я хочу выйти и оставаться без изменений, когда: EMA1 ‹ EMA3

Это довольно просто, но я не могу написать это в среде quantstrat.

Вот функция проверки целостности данных, используемая в обоих примерах:

# Data integrity check
checkBlotterUpdate <- function(port.st,account.st,verbose=TRUE)
{
    ok <- TRUE
    p <- getPortfolio(port.st)
    a <- getAccount(account.st)
    syms <- names(p$symbols)
    port.tot <- sum(sapply(syms,FUN = function(x) eval(parse(
        text=paste("sum(p$symbols",x,"posPL.USD$Net.Trading.PL)",sep="$")))))
    port.sum.tot <- sum(p$summary$Net.Trading.PL)
    if( !isTRUE(all.equal(port.tot,port.sum.tot)) ) {
        ok <- FALSE
        if( verbose )
            print("portfolio P&L doesn't match sum of symbols P&L")
    }
    initEq <- as.numeric(first(a$summary$End.Eq))
    endEq <- as.numeric(last(a$summary$End.Eq))
    if( !isTRUE(all.equal(port.tot,endEq-initEq)) ) {
        ok <- FALSE
        if( verbose )
            print("portfolio P&L doesn't match account P&L")
    }
    if( sum(duplicated(index(p$summary))) ) {
        ok <- FALSE
        if( verbose )
            print("duplicate timestamps in portfolio summary")
    }
    if( sum(duplicated(index(a$summary))) ) {
        ok <- FALSE
        if( verbose )
            print("duplicate timestamps in account summary")
    }
    return(ok)
}

Вот промокод, который делает то, что я хочу:

# Working Strategy
# it works well only with one portfolio
library(quantstrat)
suppressWarnings({
  try(rm(list=ls(FinancialInstrument:::.instrument),
         pos=FinancialInstrument:::.instrument), silent=TRUE)
  try(rm(list=c("account.bGiulio","portfolio.bGiulio","order_book"),
         pos=.blotter), silent=TRUE)
  try(rm(list=c("b.strategy","myTheme","SPY",".getSymbols")), silent=TRUE)
})

#### all currency instruments must be defined
#### before instruments of other types can be defined
# Initialize a currency and a stock instrument
currency("USD")
stock("SPY",currency="USD",multiplier=1)

#Fetch historic data
# system settings
initDate <- '1997-12-31'
startDate <- '1998-01-01'
endDate <- '2014-06-30'
initEq <- 1e6
Sys.setenv(TZ="UTC")
getSymbols('SPY', from=startDate, to=endDate, index.class="POSIXct", adjust=T)

# convert data to weekly
SPY=to.weekly(SPY, indexAt='endof', drop.time=FALSE)

SPY$EMA_1<-EMA(na.locf(Cl(SPY)),10) # 10 o 3
SPY$EMA_2<-EMA(na.locf(Cl(SPY)),25) # 50 o 10
SPY$EMA_3<-EMA(na.locf(Cl(SPY)),30) # 200 o 50
SPY$EMA_1_lag<-lag(EMA(na.locf(Cl(SPY)),10),1) # 200 o 50

# inizialization on both 
b.strategy <- "bGiulio"
initPortf(b.strategy, 'SPY', initDate=initDate)
initAcct(b.strategy, portfolios=b.strategy, initDate=initDate, initEq=initEq)
initOrders(portfolio=b.strategy,initDate=initDate)

# trading algo 
for( i in 1:nrow(SPY) )
{
    # update values for this date
    CurrentDate <- time(SPY)[i]
    equity = getEndEq(b.strategy, CurrentDate)
    ClosePrice <- as.numeric(Cl(SPY[i,]))
    Posn <- getPosQty(b.strategy, Symbol='SPY', Date=CurrentDate)
    UnitSize = as.numeric(trunc(equity/ClosePrice))
    EMA1 <- as.numeric(SPY[i,'EMA_1'])
    EMA2 <- as.numeric(SPY[i,'EMA_2'])
    EMA3 <- as.numeric(SPY[i,'EMA_3'])
    EMA1_lag <- as.numeric(SPY[i,'EMA_1_lag'])
    # change market position if necessary
    if( !is.na(EMA1)  & # if the moving average has begun
        !is.na(EMA2)  & # if the moving average has begun
        !is.na(EMA3) &
        !is.na(EMA1_lag) ) # if the moving average has begun
    {
        if( Posn == 0 ) { # No position, test to go Long
            if( EMA1 > EMA2 & EMA1 > EMA3 & EMA1_lag<EMA1) {
                # enter long position
                addTxn(b.strategy, Symbol='SPY', TxnDate=CurrentDate,
                       TxnPrice=ClosePrice, TxnQty = UnitSize , TxnFees=0)
            }
        } else { # Have a position, so check exit
            if( EMA1 < EMA3) {
                # exit position
                addTxn(b.strategy, Symbol='SPY', TxnDate=CurrentDate,
                       TxnPrice=ClosePrice, TxnQty = -Posn , TxnFees=0)
            } else {
                if( i==nrow(SPY) ) # exit on last day
                    addTxn(b.strategy, Symbol='SPY', TxnDate=CurrentDate,
                           TxnPrice=ClosePrice, TxnQty = -Posn , TxnFees=0)
            }
        }
    }
    updatePortf(b.strategy,Dates=CurrentDate)
    updateAcct(b.strategy,Dates=CurrentDate)
    updateEndEq(b.strategy,CurrentDate)
} # End dates loop

# transactions
#getTxns(Portfolio=b.strategy, Symbol="SPY")
checkBlotterUpdate(b.strategy,b.strategy)
## [1] TRUE

tstats <- t(tradeStats(b.strategy))
perTradeStats(b.strategy)

library(lattice)
a <- getAccount(b.strategy)
xyplot(a$summary,type="h",col=4)

equity <- a$summary$End.Eq
plot(equity,main="Giulio Strategy Equity Curve")
ret <- Return.calculate(equity,method="log")
charts.PerformanceSummary(ret, colorset = bluefocus,
                          main="Giulio Strategy Performance")

Я попытался воспроизвести описанную выше стратегию с помощью quantstrat (используя add.indicator, add.signal, add.rule), но результаты определенно отличаются. Вот второй код с quantstrat:

# Here the code that does not work
library(quantstrat)

#Initialize a currency and a stock instrument
currency("USD")
stock("SPY",currency="USD",multiplier=1)

# system settings
initDate <- '1997-12-31'
startDate <- '1998-01-01'
endDate <- '2014-06-30'
initEq <- 1e6
Sys.setenv(TZ="UTC")

getSymbols('SPY', from=startDate, to=endDate, index.class="POSIXct", adjust=T)
SPY <- to.weekly(SPY, indexAt='endof', drop.time=FALSE)
SPY$EMA1<-EMA(na.locf(Cl(SPY)),10) # 10 o 3
SPY$EMA2<-EMA(na.locf(Cl(SPY)),25) # 50 o 10
SPY$EMA3<-EMA(na.locf(Cl(SPY)),30) # 200 o 50
SPY$EMA1_lag<-lag(EMA(na.locf(Cl(SPY)),10)) # 200 o 50

# initialize portfolio/account
qs.strategy <- "qsGiulio"
rm.strat(qs.strategy) # remove strategy etc. if this is a re-run
initPortf(qs.strategy,'SPY', initDate=initDate)
initAcct(qs.strategy,portfolios=qs.strategy, initDate=initDate, initEq=initEq)

# initialize orders container
initOrders(portfolio=qs.strategy,initDate=initDate)
# instantiate a new strategy object
strategy(qs.strategy,store=TRUE)
strat <-getStrategy(qs.strategy)

add.indicator(strategy = qs.strategy, name = "EMA",
              arguments = list(x = quote(na.locf(Cl(mktdata))), n=10), label="EMA1")
add.indicator(strategy = qs.strategy, name = "EMA",
              arguments = list(x = quote(na.locf(Cl(mktdata))), n=25), label="EMA2")
add.indicator(strategy = qs.strategy, name = "EMA",
              arguments = list(x = quote(na.locf(Cl(mktdata))), n=30), label="EMA3")
add.indicator(strategy = qs.strategy, name = "EMA",
              arguments = list(x = quote(lag(na.locf(Cl(mktdata)))), n=10), label="EMA1_lag")

# entry signals
add.signal(qs.strategy,name="sigComparison",
           arguments = list(columns=c("EMA1","EMA2"),relationship="gt"),
           label="EMA1.gt.EMA2")
add.signal(qs.strategy,name="sigComparison",
           arguments = list(columns=c("EMA1","EMA3"),relationship="gt"),
           label="EMA1.gt.EMA3")
add.signal(qs.strategy,name="sigComparison",
           arguments = list(columns=c("EMA1","EMA1_lag"),relationship="gt"),
           label="EMA1.gt.EMA1_lag")
add.signal(qs.strategy, name = "sigFormula",
           arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag"),
           label="longEntry")

# exit signals
add.signal(qs.strategy,name="sigComparison",
           arguments = list(columns=c("EMA1","EMA3"),relationship="lt"),
           label="EMA1.lt.EMA3")

# RULES
# go long when 3 condition
add.rule(qs.strategy, name='ruleSignal',
         arguments = 
                 list(sigcol="longEntry", sigval=TRUE, orderqty=900,
                      ordertype='market', orderside='long'), 
         type='enter')

# exit when 1 condition
add.rule(qs.strategy, name='ruleSignal',
         arguments = list(sigcol="EMA1.lt.EMA3", sigval=TRUE, orderqty='all',
                          ordertype='market', orderside='long'),
         type='exit')

applyStrategy(strategy=qs.strategy , portfolios=qs.strategy)

# transactions
#getTxns(Portfolio=qs.strategy, Symbol="SPY")
checkBlotterUpdate(b.strategy,b.strategy)
## [1] TRUE

# update portfolio/account
updatePortf(qs.strategy)
updateAcct(qs.strategy)
updateEndEq(qs.strategy)

tstats <- t(tradeStats(qs.strategy))
perTradeStats(qs.strategy)

library(lattice)
a <- getAccount(qs.strategy)
xyplot(a$summary,type="h",col=4)

equity <- a$summary$End.Eq
plot(equity,main="Giulio Strategy Equity Curve")
ret <- Return.calculate(equity,method="log")
charts.PerformanceSummary(ret, colorset = bluefocus,
                          main="Giulio Strategy Performance")

Может ли кто-нибудь помочь мне понять, почему второй код не дает идентичных результатов? Я думаю, что мои ошибки в настройке add.indicator, add.signal, add.rule, но я не могу точно понять это.


person gcats    schedule 23.08.2015    source источник
comment
#Джошуа Ульрих, отмечу, что ваш ответ здесь был написан в 2015 году. Поскольку я недавно изучаю квантстрат, я не замечаю ссылки на необходимость префикса (ваш второй пункт). Это больше не требуется, или, может быть, я просто пропустил это?   -  person W Barker    schedule 11.07.2019


Ответы (1)


Код на основе quanstrat не даст идентичных результатов по нескольким причинам. Во-первых, ваши столбцы неверны в ваших первых 3 add.signal вызовах. Все столбцы должны иметь префикс "EMA.":

add.signal(qs.strategy,name="sigComparison",
  arguments = list(columns=c("EMA.EMA1","EMA.EMA2"),relationship="gt"),
  label="EMA1.gt.EMA2")
add.signal(qs.strategy,name="sigComparison",
  arguments = list(columns=c("EMA.EMA1","EMA.EMA3"),relationship="gt"),
  label="EMA1.gt.EMA3")
add.signal(qs.strategy,name="sigComparison",
  arguments = list(columns=c("EMA.EMA1","EMA.EMA1_lag"),relationship="gt"),
  label="EMA1.gt.EMA1_lag")

Другой проблемой и, вероятно, самой большой причиной разногласий является следующий сигнал:

add.signal(qs.strategy, name = "sigFormula",
  arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag"),
  label="longEntry")

Это создает сигнал для каждого наблюдения, где формула верна, а не только для наблюдений, где формула переходит от ложного к истинному. Вам нужны только наблюдения, где формула пересекается, поэтому вы должны использовать:

add.signal(qs.strategy, name = "sigFormula",
  arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag",
  cross = TRUE),
  label="longEntry")

Другой источник различий заключается в том, что вы всегда используете ~ 100% доступного капитала для открытия длинной сделки в блоттерной версии, но вы всегда покупаете 900 акций в версии Quantstrat. Вы можете сделать что-то подобное в quantstrat, используя пользовательскую функцию определения размера ордера (см. osNoOp и osMaxPos примеры того, как написать пользовательскую функцию определения размера ордера).

person Joshua Ulrich    schedule 10.09.2015