Convert from R to quantstrat setup for trading strategy backtesting - r

I am trying to backtest a trading strategy with "quantstrat" package.
My strategy is composed by 4 indicators, 3 different EMAs and 1 lagged EMA.
I want to go long when: EMA1 > EMA2 & EMA1 > EMA3 & EMA1_lag < EMA1
I want to exit and go flat when: EMA1 < EMA3
It's pretty simple but I am not able to write it into quantstrat environment.
Here's a data integrity check function used in both examples:
# 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)
}
Here is the blotter code that does what I want:
# 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")
I tried to replicate the above strategy with quantstrat (using add.indicator, add.signal, add.rule), but the results are definitely different. Here the second code with 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")
Could anyone help me to understand why the second code doesn't give identical results? I think my mistakes are within the add.indicator, add.signal, add.rule setup, but I am not able to figure out it precisely.

The quanstrat-based code will not provide identical results for several reasons. One is that your columns are not correct in your first 3 add.signal calls. All the columns need to have an "EMA." prefix:
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")
Another issue, and likely the biggest cause of differences, is the next signal:
add.signal(qs.strategy, name = "sigFormula",
arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag"),
label="longEntry")
That creates a signal for every observation where the formula is true, not just the observations where the formula crosses from false to true. You only want the observations where the formula crosses, so you should use:
add.signal(qs.strategy, name = "sigFormula",
arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag",
cross = TRUE),
label="longEntry")
Another source of differences is that you always use ~100% of your available equity for your opening long transaction in the blotter version, but you always buy 900 shares in the quantstrat version. You can do something similar in quantstrat by using a custom order sizing function (see osNoOp and osMaxPos for examples of how to write a custom order sizing function).

Related

R quantstrat applySignals error - Error in `dimnames<-.xts`(`*tmp*`, value = dn) : length of 'dimnames' [2] not equal to array extent

I am trying to use the applySignals function on my stock data and additional filter rule that I created since the TTR package does not provide a simple filter rule.
When I use the applyIndicators function I can see that an additional column appears with the filter (daily return) so I think this works. However when I add my long and short signal based on this filter and I use the applySignals function I get the following error:
test <- applySignals(strategy = strategy.st, mktdata = test_init)
Error in dimnames<-.xts(*tmp*, value = dn) :
length of 'dimnames' [2] not equal to array extent
In addition: Warning message:
In match.names(column, colnames(data)) :
all columns not located in filter for OTRK.Open OTRK.High OTRK.Low OTRK.Close FILTER.filter longfilter
I used the same example and code from a professional who provided the code but it doesn't seem to work for me. (chapter 9.3 Simple filter rule: https://bookdown.org/kochiuyu/technical-analysis-with-r-second-edition/simple-filter-rule.html)
I am new to R so I don't really understand the error message.
Thanks in advance!
This is my code as far as the error:
install.packages("quantstrat")
install.packages("TTR")
## Library
library("quantstrat")
library("TTR")
## Quantsrat not directly available: solution
install.packages("devtools")
require(devtools)
install_github("braverock/blotter") # dependency
install_github("braverock/quantstrat")
###############################################################################################################################
###############################################################################################################################
initdate <- "2003-12-15"
from <- "2018-12-15"
to <- "2021-04-07"
Sys.setenv(TZ = "UTC")
currency("USD")
getSymbols("OTRK", from = from, to = to, src= "yahoo", adjust = TRUE)
stock("OTRK", currency = "USD", multiplier=1)
tradesize <- 1000
initeq <- 1000
#txnfees <- -10
#orderqty <- 100
#nsamples <- 5
#Parameters indicators#
filterthreshold <- 0.05
#Initialise#
strategy.st <- portfolio.st <- account.st <- "FilterRule"
rm.strat(strategy.st)
initPortf(portfolio.st, symbols = "OTRK", initDate = initdate, currency = "USD")
initAcct(account.st, portfolios = portfolio.st, initDate = initdate, currency = "USD", initEq = initeq)
initOrders(portfolio.st, symbols = "OTRK", initDate = initdate)
strategy(strategy.st, store = TRUE)
#Adding indicators#
FILTER <- function(price) {
lagprice <- lag(price,1)
temp<- price/lagprice - 1
colnames(temp) <- "FILTER"
return(temp)
}
add.indicator(
strategy=strategy.st,
name = "FILTER",
arguments = list(price = quote(Cl(mktdata))),
label= "filter")
#Check test indicators work#
test_init <- applyIndicators(strategy = strategy.st, mktdata = OHLC(OTRK))
head(test_init, n=7)
tail(test_init, n=7)
#Adding signals#
add.signal(strategy.st,
name="sigThreshold",
arguments = list(threshold= filterthreshold,
column="filter",
relationship="gt",
cross=TRUE),
label="longfilter")
add.signal(strategy.st,
name="sigThreshold",
arguments = list(threshold= - filterthreshold,
column="filter",
relationship="lt",
cross=TRUE),
label="shortfilter")
#Test signals on data
test <- applySignals(strategy = strategy.st, mktdata = test_init)

Warning message with blotter / quantmod /quanstrat - Incompatible methods ("Ops.POSIXt", "Ops.Date")

Sorry in advance for the long post, I'm not sure how to reduce it though.
I have started using the blotter / quanstrat package from this tutorial of Guy Yollin. If I use Mr. Yollin code as it is, no worries I'm getting similar results.
library(blotter)
currency("USD")
initDate <- "2000-01-01"
startDate <- "2000-01-02"
endDate <- "2016-07-01"
initEq <- 1e6
The only change I am making is to use data locally stored on a .csv using a slightly modified function given by the Systematic Investor on his Github.
Here is the function.
getSymbols.sit <- function(
Symbols,
env = .GlobalEnv,
auto.assign = TRUE,
stock.folder = 'Google Drive/Software/TechnicalAnalysis/StockData',
stock.date.format = '%Y-%m-%d',
...)
{
require(quantmod)
for(i in 1:length(Symbols)) {
s = Symbols[i]
temp = list()
temp[[ s ]] = list(src='csv', format=stock.date.format, dir=stock.folder)
setSymbolLookup(temp)
temp = quantmod::getSymbols(s, env = env, auto.assign = auto.assign)
if (!auto.assign) {
cat(s, format(range(index(temp)), '%d-%b-%Y'), '\n', sep='\t')
return(temp)
}
if(!is.null(env[[ s ]]))
cat(i, 'out of', length(Symbols), 'Reading', s, format(range(index(env[[ s ]])), '%d-%b-%Y'), '\n', sep='\t')
else
cat(i, 'out of', length(Symbols), 'Missing', s, '\n', sep='\t')
}
}
Then calling it.
getSymbols.sit("SPY", source = "yahoo", from=startDate, to=endDate, adjust=T)
Everything seems to work so far. and now continuing building the back test based on the 10 months SMA from Faber.
stock("SPY", currency = "USD", multiplier = 1)
SPY=to.monthly(SPY, indexAt = 'endof', drop.time = FALSE)
SPY$SMA10m <- SMA(Cl(SPY), n=10)
portfolio.st <- "portf.faber"
account.st <- "acct.faber"
initPortf(portfolio.st, "SPY", initDate = initDate)
initAcct(account.st, portfolios = portfolio.st, initDate = initDate, initEq = initEq)
Now creatingn the strategy and running it.
for(i in 1:nrow(SPY))
{
#set up all the values for the specific date and update them in the loop
actualDate <- time(SPY)[i]
equity = getEndEq(Account = account.st, Date = actualDate)
closePrice <- as.numeric(Cl(SPY[i]))
posn <- getPosQty(Portfolio = portfolio.st, Symbol = "SPY", Date = actualDate)
unitSize = as.numeric(trunc(equity/closePrice))
ma <- as.numeric(SPY$SMA10m[i])
#Take market decision
if( !is.na(ma) ) { #we have to wait to have our first 10sma
if( posn == 0 ) { #if no position then we go long
if( closePrice > ma ) {
addTxn(Portfolio = portfolio.st, Symbol = "SPY", TxnDate = actualDate,
TxnQty = unitSize, TxnPrice = closePrice, TxnFees = 0) }
} else {
if( closePrice < ma ) { #sell share and go cash if closing price < 10sma
addTxn(Portfolio = portfolio.st, Symbol = "SPY", TxnDate = actualDate,
TxnQty = -posn, TxnPrice = closePrice, TxnFees = 0)
} else {
if( i == nrow(SPY) ) #last recorded price, we close the system
addTxn(Portfolio = portfolio.st, Symbol = "SPY", TxnDate = actualDate,
TxnQty = -posn, TxnPrice = closePrice, TxnFees = 0)
}
}
}
updatePortf(portfolio.st, Dates = actualDate)
updateAcct(name = account.st, Dates = actualDate)
updateEndEq(Account = account.st, actualDate)
}
Although the script run through without error, there are 3 things that I am getting concern about.
a warning when using the getSymbols.sit(). and I am not sure what to do about it. Here is the warning.
1 out of 1 Reading SPY 03-Jan-2000 19-Jul-2016
Warning message:
In if (as.character(sc[[1]]) != calling.fun) return() :
the condition has length > 1 and only the first element will be used
a warning when running the last 3 update functions.
There were 50 or more warnings (use warnings() to see the first 50)
1: In updatePortf(portfolio.st, Dates = actualDate) :
Incompatible methods ("Ops.POSIXt", "Ops.Date") for ">="
If I use the checkBlotterUpdate (as given in the tutorial) function I get an error.
checkBlotterUpdate(portfolio.st, account.st)
[1] "portfolio P&L doesn't match sum of symbols P&L" [1] FALSE
So it seems I have to be worry about these warnings but I'm not sure how to fix the problem.
If run the whole thing with just getSymbols("SPY"), there is no issues. But I would really like to be able to backtest using locally stored data. I live in Zambia, Africa and internet / power is not always reliable.
Thanks in advance for any hint.
I've submitted a pull request to the quantmod github. You now have four options with regard to Warning #1:
You can ignore the warning (if you're getting the data)
You can avoid Warning #1 by calling getSymbols (without the namespace designation) instead of quantmod::getSymbols
You can build the source from my github
You can wait for #Joshua-Ulrich to incorporate
my pull request.

From loop to Quantstrat [duplicate]

I am trying to backtest a trading strategy with "quantstrat" package.
My strategy is composed by 4 indicators, 3 different EMAs and 1 lagged EMA.
I want to go long when: EMA1 > EMA2 & EMA1 > EMA3 & EMA1_lag < EMA1
I want to exit and go flat when: EMA1 < EMA3
It's pretty simple but I am not able to write it into quantstrat environment.
Here's a data integrity check function used in both examples:
# 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)
}
Here is the blotter code that does what I want:
# 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")
I tried to replicate the above strategy with quantstrat (using add.indicator, add.signal, add.rule), but the results are definitely different. Here the second code with 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")
Could anyone help me to understand why the second code doesn't give identical results? I think my mistakes are within the add.indicator, add.signal, add.rule setup, but I am not able to figure out it precisely.
The quanstrat-based code will not provide identical results for several reasons. One is that your columns are not correct in your first 3 add.signal calls. All the columns need to have an "EMA." prefix:
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")
Another issue, and likely the biggest cause of differences, is the next signal:
add.signal(qs.strategy, name = "sigFormula",
arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag"),
label="longEntry")
That creates a signal for every observation where the formula is true, not just the observations where the formula crosses from false to true. You only want the observations where the formula crosses, so you should use:
add.signal(qs.strategy, name = "sigFormula",
arguments = list(formula="EMA1.gt.EMA2 & EMA1.gt.EMA3 & EMA1.gt.EMA1_lag",
cross = TRUE),
label="longEntry")
Another source of differences is that you always use ~100% of your available equity for your opening long transaction in the blotter version, but you always buy 900 shares in the quantstrat version. You can do something similar in quantstrat by using a custom order sizing function (see osNoOp and osMaxPos for examples of how to write a custom order sizing function).

Error in .xts(e, .index(e1) in quantstrat

I get an error when running my quantmod code:
´Error in .xts(e, .index(e1), .indexCLASS = indexClass(e1), .indexFORMAT = indexFormat(e1), :
index length must match number of observations´
My code (partly from http://rbresearch.wordpress.com/2013/02/19/momentum-in-r-part-4-with-quantstrat/) is:
# qstratRank.R
qstratRank <- function(symbols, init.equity=100000, top.N=1,
max.size=1000, max.levels=1) {
# The qstratRank function uses the quantstrat framework to backtest a
# ranking or relative strength strategy
#
# args
# symbols : character vector of symbols
# init.equity : initial equity
# top.N : trade the top N ranked assets
# max.size : maximum position size
# max.levels : maximum levels to scale in a trade
# max.size and max.levels are passed to addPosLimit
#
# return value
# returns a list: end.eq, returns, book, stats
# remove variables
suppressWarnings(rm("order_book.Rank", pos=.strategy))
suppressWarnings(rm("account.Rank", "portfolio.Rank", pos=.blotter))
suppressWarnings(rm("account.st", "port.st", "stock.str", "stratRank",
"initDate", "initEq", 'start_t', 'end_t'))
# set initial variables
initDate <- "1900-01-01"
initEq <- init.equity
port.st <- "Rank"
account.st <- "Rank"
# trade the top "N" ranked symbols
N <- top.N
# initialize quantstrat objects
initPortf(port.st, symbols=symbols, initDate=initDate)
initAcct(account.st, portfolios=port.st, initDate=initDate,initEq=initEq)
initOrders(portfolio=port.st, initDate=initDate)
# initialize a strategy object
stratRank <- strategy("Rank")
# there are two signals
# the first signal is when Rank is less than or equal to N
# (i.e. trades the #1 ranked symbol if N=1)
stratRank <- add.indicator(strategy=stratRank, name="SMA",
arguments=list(x = quote(Cl(mktdata)), n=50), label="SMA50")
stratRank <- add.signal(stratRank, name="sigComparison",
arguments=list(columns=c("Close", "SMA50"), relationship="gt"), label="Cl.gt.SMA50")
# the second signal is when Rank is greter than or equal to N
# (i.e. trades the #1 ranked symbol if N=1)
stratRank <- add.signal(strategy=stratRank, name="sigThreshold",
arguments=list(threshold=N, column="Rank",
relationship="gt", cross=FALSE),
label="Rank.gt.N")
# add buy rule
stratRank <- add.rule(strategy=stratRank, name='ruleSignal',
arguments = list(sigcol="Cl.gt.SMA50", sigval=TRUE,
orderqty=max.size, ordertype='market',
orderside='long', pricemethod='market',
replace=FALSE, osFUN=osMaxPos),
type='enter', path.dep=TRUE)
stratRank <- add.rule(strategy=stratRank, name='ruleSignal',
arguments = list(sigcol="Rank.lte.N", sigval=TRUE,
orderqty=max.size, ordertype='market',
orderside='long', pricemethod='market',
replace=FALSE, osFUN=osMaxPos),
type='enter', path.dep=TRUE)
# add exit rule
stratRank <- add.rule(strategy = stratRank, name='ruleSignal',
arguments = list(sigcol="Rank.gt.N", sigval=TRUE,
orderqty='all', ordertype='market',
orderside='long', pricemethod='market',
replace=FALSE),
type='exit', path.dep=TRUE)
#set max position size and levels
for(symbol in symbols){ addPosLimit(port.st, symbol, initDate, max.size, max.levels) }
print("setup completed")
# apply the strategy to the portfolio
start_t <- Sys.time()
out <- try(applyStrategy(strategy=stratRank, portfolios=port.st))
end_t <- Sys.time()
print(end_t-start_t)
# update Portfolio
start_t <- Sys.time()
updatePortf(Portfolio=port.st, Dates=paste('::', as.Date(Sys.time()), sep=''))
end_t <- Sys.time()
print("trade blotter portfolio update:")
print(end_t - start_t)
# update account
updateAcct(account.st)
# update ending equity
updateEndEq(account.st)
# get ending equity
eq <- getEndEq(account.st, Sys.Date()) + initEq
# view order book to confirm trades
order.book <- getOrderBook(port.st)
# get trade statistics
stats <- tradeStats(port.st)
# portfolio returns
ret1 <- PortfReturns(port.st)
ret1$total <- rowSums(ret1, na.rm=TRUE)
return(list(end.eq=eq, returns=ret1, book=order.book, stats=stats))
}
I then run:
bt <- qstratRank(symbols=symbols, init.equity=100000, top.N=2,
max.size=1000, max.levels=1)
The issue might be with my ´SMA50´ indicator, and signal and rule.
Any ideas?
Best Regards

R: Quantstrat how to make a transaction for complete equity in portfolio?

I'm still playing around with Guy Yollins quantstrat example. In this example he buys 1000 shares of the SPY when it crosses its 10 day MA. Since we define an initial equity, is it possible to always buy for the whole portfolio amount and not just 900 shares? 'all' didn't work for the enter, just the exit..
if (!exists('.blotter')) .blotter <- new.env()
if (!exists('.strategy')) .strategy <- new.env()
if (!exists('.instrument')) .instrument <- new.env()
currency("USD")
stock("SPY",currency="USD",multiplier=1)
ls(envir=FinancialInstrument:::.instrument)
initDate <- '1997-12-31'
startDate <- '1998-01-01'
endDate <- '2013-07-31'
initEq <- 1e6
Sys.setenv(TZ="UTC")
getSymbols('SPY', from=startDate, to=endDate, adjust=T)
SPY=to.monthly(SPY, indexAt='endof')
SPY$SMA10m <- SMA(Cl(SPY), 10)
# inz portfolio, account
qs.strategy <- "qsFaber"
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)
initOrders(portfolio=qs.strategy,initDate=initDate)
# instantiate a new strategy object
strategy(qs.strategy,store=TRUE)
add.indicator(strategy = qs.strategy, name = "SMA",
arguments = list(x = quote(Cl(mktdata)), n=10), label="SMA10")
add.signal(qs.strategy,name="sigCrossover",
arguments = list(columns=c("Close","SMA10"),relationship="gt"),
label="Cl.gt.SMA")
add.signal(qs.strategy,name="sigCrossover",
arguments = list(columns=c("Close","SMA10"),relationship="lt"),
label="Cl.lt.SMA")
add.rule(qs.strategy, name='ruleSignal',
arguments = list(sigcol="Cl.gt.SMA", sigval=TRUE, orderqty=900,
ordertype='market', orderside='long', pricemethod='market'),
type='enter', path.dep=TRUE)
add.rule(qs.strategy, name='ruleSignal',
arguments = list(sigcol="Cl.lt.SMA", sigval=TRUE, orderqty='all',
ordertype='market', orderside='long', pricemethod='market'),
type='exit', path.dep=TRUE)
out <- applyStrategy(strategy=qs.strategy , portfolios=qs.strategy)
updatePortf(qs.strategy)
updateAcct(qs.strategy)
updateEndEq(qs.strategy)
myTheme<-chart_theme()
myTheme$col$dn.col<-'lightblue'
myTheme$col$dn.border <- 'lightgray'
myTheme$col$up.border <- 'lightgray'
# plot performance
chart.Posn(qs.strategy, Symbol = 'SPY', Dates = '1998::',theme=myTheme)
plot(add_SMA(n=10,col=4, on=1, lwd=2))
You cannot use orderqty="all" on entries because "all" refers to the current position size (i.e., when you want to exit the entire position).
It's possible to purchase an amount equal to the total available portfolio equity, but you have to define a custom order sizing function. And that function would necessarily have to mark the book (using updatePortf) in order to determine the amount of available equity.
Here is a toy example that achieves what you want.
You need to introduce an order sizing function.
Take a look at the arguments to the function ruleSignal (e.g. formals(ruleSignal) and ?ruleSignal).
You'll see there is an argument osFUN, which is where you can write your custom function that will determine how you order size.
You modify the appropriate parameters in add.rule to introduce ordersizing (on entry trades).
osFUN_all_eq <- function (data, timestamp, orderqty, ordertype, orderside, equity, portfolio, symbol, ruletype, ..., initEq) {
datePos <- format(timestamp,"%Y-%m-%d")
updatePortf(Portfolio = portfolio, Symbol = symbol, Dates = paste0(start(data), "/", datePos))
trading_pl <- sum(.getPortfolio(portfolio)$summary$Net.Trading.PL)
# The total equity in the strategy for this symbol (and this symbol only in isolation always, as this is how quantstrat by default works with applyStrategy)
equity <- initEq + trading_pl
ClosePrice <- getPrice(data, prefer = "Close")[datePos]
UnitSize <- as.numeric(trunc(equity / ClosePrice))
UnitSize <- osMaxPos(data, timestamp, UnitSize, ordertype, orderside, portfolio, symbol, ruletype, digits=0)
UnitSize
}
library(quantstrat)
currency("USD")
stock("SPY",currency="USD",multiplier=1)
initDate <- '1997-12-31'
startDate <- '1998-01-01'
endDate <- '2013-07-31'
initEq <- 1e6
Sys.setenv(TZ="UTC")
getSymbols('SPY', from=startDate, to=endDate, adjust=T)
SPY=to.monthly(SPY, indexAt='endof')
SPY$SMA10m <- SMA(Cl(SPY), 10)
qs.strategy <- "qsFaber"
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)
initOrders(portfolio=qs.strategy,initDate=initDate)
# instantiate a new strategy object
strategy(qs.strategy,store=TRUE)
# Specify the max quantity you could hold in the SPY instrument. Here we simply assume 1e5 units. You could reduce this number to limit the exposure
max_qty_traded <- 1e5
addPosLimit(qs.strategy, "SPY", timestamp = startDate, maxpos = max_qty_traded)
add.indicator(strategy = qs.strategy, name = "SMA",
arguments = list(x = quote(Cl(mktdata)), n=10), label="SMA10")
add.signal(qs.strategy,name="sigCrossover",
arguments = list(columns=c("Close","SMA10"),relationship="gt"),
label="Cl.gt.SMA")
add.signal(qs.strategy,name="sigCrossover",
arguments = list(columns=c("Close","SMA10"),relationship="lt"),
label="Cl.lt.SMA")
add.rule(qs.strategy, name='ruleSignal',
arguments = list(sigcol="Cl.gt.SMA",
sigval=TRUE,
orderqty = 1, # the acutal orderqty size becomes redundant when supplying a function to the argument `osFUN`
osFUN = osFUN_all_eq,
ordertype='market', orderside='long', pricemethod='market'),
type='enter', path.dep=TRUE)
add.rule(qs.strategy, name='ruleSignal',
arguments = list(sigcol="Cl.lt.SMA",
sigval=TRUE,
orderqty='all', # flatten all open long positions
ordertype='market',
orderside='long',
pricemethod='market'),
type='exit',
path.dep=TRUE)
# supply initEq parameter and its value, which pass through to `osFUN`
out <- applyStrategy(strategy=qs.strategy , portfolios=qs.strategy, initEq=initEq)
updatePortf(qs.strategy)
updateAcct(qs.strategy)
updateEndEq(qs.strategy)
myTheme<-chart_theme()
myTheme$col$dn.col<-'lightblue'
myTheme$col$dn.border <- 'lightgray'
myTheme$col$up.border <- 'lightgray'
# plot performance
chart.Posn(qs.strategy, Symbol = 'SPY', Dates = '1998::',theme=myTheme)
plot(add_SMA(n=10,col=4, on=1, lwd=2))
There are a few things you should consider in an order sizing function:
1) If you already have a position open, do you want to permit "stacking"/pyramidying of positions on a particular side? For example, if you want to just have one position on, which you don't contribute further to, you could include getPosQty(qs.strategy, "SPY", timestamp) in your osFUN and return 0 if the current position held is not 0.
2) Do you want a max trade size? This can be handled using addPosLimit() as was done in this example above.
I know this question was posted long ago, but check out package "IKTrading," and the order sizing function "osMaxDollar." Here's a blog post on the topic; when you use this order sizing function you can set the dollar value of each trade and the total position.
https://quantstrattrader.wordpress.com/2014/08/29/comparing-atr-order-sizing-to-max-dollar-order-sizing/

Resources