Search code examples
rquantmodquantstratblotter

Quantstrat Multiple Currencies. Possible Bug in Blotter::UpdateAcct?


General info:

R-Version: 3.1.0

blotter: 0.8.19

Problem description:

I am trying to implement a quantstrat account which uses multiple portofolios with different currencies.

So here's my basic setup:

  • 1 account in EUR
  • 1 portfolio in USD

So in order for this to work I have to setup an exchange rate, which I based on data retrieved from yahoo. Then I should run my basic strategy and the conversion will be done automatically in the last step via the updateAcct function.

Now here's the rub... I think the updateAcct function has a bug.

MyCode:

initDate="1990-01-01"
from="2007-01-01"
to="2012-12-31"
options(width=70)

options("getSymbols.warning4.0"=FALSE) 
currency(c('USD','EUR'))
exchange_rate("USDEUR", tick_size = 0.01)
USDEUR <- Cl(getSymbols("EUR=X",src="yahoo", auto.assign = FALSE))
Sys.setenv(TZ="UTC") 
#not sure why this might work
.blotter <- new.env()
.strategy <- new.env()

symbols <- c("^IXIC" #Nasdaq
             ) 
if(!"XLB" %in% ls()) {
  suppressMessages(getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE))  
  } 

#need this to remove index call symbol (yahoo.) from string. I.e. get ^IXIC, but named IXIC
symbols<-gsub("\\^", "", symbols)

stock(symbols, currency="USD", multiplier=1)

#trade sizing and initial equity settings
tradeSize <- 10000
initEq <- tradeSize*length(symbols) 
strategy.st <- portfolio.st <- account.st <- "TradeNasdaq100"

#clear old strategies etc.
suppressWarnings(try(rm.strat(strategy.st), silent=TRUE))

#initialize portfolio and account
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='EUR',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

Then I use some indicators, signals, rules etc....

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1) 
#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(account.st,dateRange)

Everything works until the code reaches the last line.

The last line will be give the error message: Error in isTRUE(invert) : object 'invert' not found

Possible bug: So I decided to check out the updateAcct function try a little debug here... I am pretty sure that there is a mistake in code. The if clause in line 63 queries for isTRUE(invert), but invert is only created if it is actually true (see else clause line 46). But invert is not initialized, thus if it is actually false the code will fail.

Here's the source code blotter (original)

function (name = "default", Dates = NULL) 
{
    Account <- getAccount(name)
    if (!is.null(attr(Account, "currency"))) {
        a.ccy.str <- attr(Account, "currency")
    }
    Portfolios = names(Account$portfolios)
    if (is.null(Dates)) 
        Dates <- unique(do.call(c, c(lapply(Portfolios, function(x) index(.getPortfolio(x)$summary)), 
            use.names = FALSE, recursive = FALSE)))[-1]
    if (!length(Dates)) 
        return(name)
    if (last(index(Account$summary)) > .parseISO8601(Dates)$first.time) {
        whichi <- first(Account$summary[paste(.parseISO8601(Dates)$first.time, 
            "::", sep = ""), which.i = TRUE])
        if (!is.null(whichi)) 
            whichi = whichi - 1
        if (whichi < 1) 
            whichi = 1
        Account$summary = Account$summary[1:whichi, ]
    }
    for (pname in Portfolios) {
        Portfolio = .getPortfolio(pname)
        if (!is.null(attr(Portfolio, "currency"))) {
            p.ccy.str <- attr(Portfolio, "currency")
        }
        psummary = Portfolio$summary[Dates]
        if (a.ccy.str != p.ccy.str) {
            CcyMult <- NA
            port_currency <- try(getInstrument(p.ccy.str), silent = TRUE)
            if (inherits(port_currency, "try-error") | !is.instrument(port_currency)) {
                warning("Currency", p.ccy.str, " not found, using currency multiplier of 1")
                CcyMult <- 1
            }
            else {
                FXrate.str <- paste(p.ccy.str, a.ccy.str, sep = "")
                FXrate <- try(get(FXrate.str), silent = TRUE)
                if (inherits(FXrate, "try-error")) {
                  FXrate.str <- paste(a.ccy.str, p.ccy.str, sep = "")
                  FXrate <- try(get(FXrate.str), silent = TRUE)
                  if (inherits(FXrate, "try-error")) {
                    warning("Exchange Rate", FXrate.str, " not found for symbol,',Symbol,' using currency multiplier of 1")
                    CcyMult <- 1
                  }
                  else {
                    invert = TRUE
                  }
                }
            }
            if (is.na(CcyMult) && !is.na(FXrate)) {
                if (inherits(FXrate, "xts")) {
                  CcyMult <- FXrate[Dates]
                  CcyMult <- na.locf(merge(CcyMult, index(psummary)))
                  CcyMult <- drop(CcyMult[index(psummary)])
                }
                else {
                  CcyMult <- as.numeric(FXrate)
                }
            }
            else {
                CcyMult <- 1
            }
            if (isTRUE(invert)) {
                CcyMult <- 1/CcyMult
            }
            psummary <- psummary * CcyMult
        }
        Account$portfolios[[pname]] = rbind(Account$portfolios[[pname]], 
            psummary)
    }
    summary = NULL
    table = .getByPortf(Account, "Net.Trading.PL", Dates)
    obsLength = length(index(table))
    obsDates = index(table)
    if (obsLength > 1) 
        on = periodicity(table)$units
    else on = "none"
    Attributes = c("Additions", "Withdrawals", "Realized.PL", 
        "Unrealized.PL", "Interest", "Gross.Trading.PL", "Txn.Fees", 
        "Net.Trading.PL", "Advisory.Fees", "Net.Performance", 
        "End.Eq")
    for (Attribute in Attributes) {
        switch(Attribute, Realized.PL = , Unrealized.PL = , Gross.Trading.PL = , 
            Txn.Fees = , Net.Trading.PL = {
                table = .getByPortf(Account, Attribute, Dates)
                result = xts(rowSums(table, na.rm = TRUE), order.by = index(table))
            }, Additions = {
                result = if (on == "none") as.xts(sum(Account$Additions[paste("::", 
                  obsDates, sep = "")]), order.by = index(table)) else {
                  if (length(Account$Additions[obsDates]) > 0) period.apply(Account$Additions[obsDates], 
                    endpoints(Account$Additions[obsDates], on = on), 
                    sum) else xts(rep(0, obsLength), order.by = obsDates)
                }
            }, Withdrawals = {
                result = if (on == "none") as.xts(sum(Account$Withdrawals[paste("::", 
                  obsDates, sep = "")]), order.by = index(table)) else {
                  if (length(Account$Additions[obsDates]) > 0) period.apply(Account$Withdrawals[obsDates], 
                    endpoints(Account$Withdrawals[obsDates], 
                      on = periodicity(table)$units), sum) else xts(rep(0, 
                    obsLength), order.by = obsDates)
                }
            }, Interest = {
                result = if (on == "none") as.xts(sum(Account$Interest[paste("::", 
                  obsDates, sep = "")]), , order.by = index(table)) else {
                  if (length(Account$Additions[obsDates]) > 0) period.apply(Account$Interest[obsDates], 
                    endpoints(Account$Interest[obsDates], on = periodicity(table)$units), 
                    sum) else xts(rep(0, obsLength), order.by = obsDates)
                }
            }, Advisory.Fees = , Net.Performance = , End.Eq = {
                result = xts(rep(0, obsLength), order.by = obsDates)
            })
        colnames(result) = Attribute
        if (is.null(summary)) {
            summary = result
        }
        else {
            summary = cbind(summary, result)
        }
    }
    summary[is.na(summary)] <- 0
    Account$summary <- rbind(Account$summary, summary)
    assign(paste("account", name, sep = "."), Account, envir = .blotter)
    return(name)
}

Here's what I think it should look like (snippet line 28-50)...

if (a.ccy.str != p.ccy.str) {
  CcyMult <- NA
  port_currency <- try(getInstrument(p.ccy.str), silent = TRUE)
  if (inherits(port_currency, "try-error") | !is.instrument(port_currency)) {
    warning("Currency", p.ccy.str, " not found, using currency multiplier of 1")
    CcyMult <- 1
  }
  else {
    FXrate.str <- paste(p.ccy.str, a.ccy.str, sep = "")
    FXrate <- try(get(FXrate.str), silent = TRUE)
    invert=FALSE #THIS IS THE LINE NEEDED FOR FIXING
    if (inherits(FXrate, "try-error")) {
      FXrate.str <- paste(a.ccy.str, p.ccy.str, sep = "")
      FXrate <- try(get(FXrate.str), silent = TRUE)
      if (inherits(FXrate, "try-error")) {
        warning("Exchange Rate", FXrate.str, " not found for symbol,',Symbol,' using currency multiplier of 1")
        CcyMult <- 1
      }
      else {
        invert = TRUE
      }
    }
  }

TL;DR

I think there's bug in blotter:updateAcct which occurs when the currency conversion does not need to invert the exchange rate...

Question: Am I right is this a bug? Or am I missing something?

P.S.:

I would normally file this as a bug but A) I don't know how to file the bug with the authors B) I still a newbie with quantstrat, blotter and Co. and I think somebody else should check this out as well (and the authors are hanging out here quite frequently as well)...


Solution

  • Thanks for the reproducible example. For future reference, it's better to provide a diff than 20-30 lines of code. It took me awhile to notice that you just added one line.

    > svn diff blotter/R/updateAcct.R 
    Index: blotter/R/updateAcct.R
    ===================================================================
    --- blotter/R/updateAcct.R  (revision 1681)
    +++ blotter/R/updateAcct.R  (working copy)
    @@ -51,6 +51,7 @@
                     FXrate.str<-paste(p.ccy.str,a.ccy.str,sep='') # currency quote convention is EURUSD which reads as "USD per EUR"
                     FXrate<-try(get(FXrate.str), silent=TRUE)
                     #TODO FIXME: this uses convention to sort out the rate, we should check $currency and $counter_currency and make sure directionality is correct 
    +                invert=FALSE
                     if(inherits(FXrate,"try-error")){
                         FXrate.str<-paste(a.ccy.str,p.ccy.str,sep='')
                         FXrate<-try(get(FXrate.str), silent=TRUE)
    

    Fixed in revision 1682. Thanks for the report!