diff --git a/R/getFinancials.R b/R/getFinancials.R index a9fe3699..59b5150e 100644 --- a/R/getFinancials.R +++ b/R/getFinancials.R @@ -1,10 +1,34 @@ -`getFinancials` <- -getFin <- function(Symbol, env=parent.frame(), src="google", auto.assign=TRUE, ...) { - src <- match.arg(src, "google") - if (src != "google") { +`getFinancials` <- getFin <- + function(Symbols, env=parent.frame(), src="tiingo", auto.assign=TRUE, from=Sys.Date()-720, to=Sys.Date(), ...) { + #As much generic functionality and error handlign has been moved into the master function + #source specific fucnimplementations should just fetch data for a single symbol and be as lightweight as possible + importDefaults("getFinancials") + + call.name <- paste("getFinancials", src, sep = ".") + Symbols <- strsplit(Symbols, ";")[[1]] + if (length(find(call.name, mode = "function")) < 1) stop("src = ", sQuote(src), " is not implemented") + if(is.null(env)) + auto.assign <- FALSE + if(!auto.assign && length(Symbols) > 1) + stop("must use auto.assign=TRUE for multiple Symbols requests") + + ret.sym <- list() + for (sym in Symbols) { + args <- list(Symbol = sym, from = from, to = to, ...) + fin <- try(structure(do.call(call.name, args = args), + symbol = sym, class = "financials", src = src, updated = Sys.time())) + if (auto.assign) { + if (inherits(fin, "financials")) { + new.sym <- paste(gsub(":", ".", sym), "f", sep = ".") + assign(new.sym, fin, env) + ret.sym[[length(ret.sym) + 1]] <- new.sym + } + } else { + return(fin) + } } - getFinancials.google(Symbol, env, auto.assign = auto.assign, ...) + return(unlist(ret.sym)) } getFinancials.google <- @@ -16,20 +40,18 @@ function(Symbol, env=parent.frame(), src="google", auto.assign=TRUE, ...) { .Defunct("Quandl", "quantmod", msg = msg) } - `print.financials` <- function(x, ...) { cat('Financial Statement for',attr(x,'symbol'),'\n') cat('Retrieved from',attr(x,'src'),'at',format(attr(x,'updated')),'\n') cat('Use "viewFinancials" or "viewFin" to view\n') } -`viewFin` <- -`viewFinancials` <- function(x, type=c('BS','IS','CF'), period=c('A','Q'), - subset = NULL) { +`viewFinancials` <- `viewFin` <- + function(x, type=c('BS','IS','CF'), period=c('A','Q'), subset = NULL) { + importDefaults("viewFinancials") if(!inherits(x,'financials')) stop(paste(sQuote('x'),'must be of type',sQuote('financials'))) type <- match.arg(toupper(type[1]),c('BS','IS','CF')) - period <- match.arg(toupper(period[1]),c('A','Q')) - + period <- match.arg(toupper(period[1]),c('A','Q')) statements <- list(BS='Balance Sheet', IS='Income Statement', @@ -37,19 +59,66 @@ function(Symbol, env=parent.frame(), src="google", auto.assign=TRUE, ...) { A='Annual', Q='Quarterly') - if(is.null(subset)) { - message(paste(statements[[period]],statements[[type]],'for',attr(x,'symbol'))) - return(x[[type]][[period]]) - } else { - tmp.table <- as.matrix(as.xts(t(x[[type]][[period]]),dateFormat='Date')[subset]) - dn1 <- rownames(tmp.table) - dn2 <- colnames(tmp.table) - tmp.table <- t(tmp.table)[, NROW(tmp.table):1] - if(is.null(dim(tmp.table))) { - dim(tmp.table) <- c(NROW(tmp.table),1) - dimnames(tmp.table) <- list(dn2,dn1) - } - message(paste(statements[[period]],statements[[type]],'for',attr(x,'symbol'))) - return(tmp.table) - } + message(paste(statements[[period]],statements[[type]],'for',attr(x,'symbol'))) + r <- x[[type]][[period]] + if (is.null(r) || is.null(subset)) + return(r) + else + return(t(as.xts(t(r))[subset])) +} + +as.data.frame.financials <- function(x) { + #reshape nested wide matrices to a long data.frame, adding columns for each nesting level + do.call("rbind", args = lapply(c("BS", "IS", "CF"), function(st) { #statement type loop + if(is.null(x[[st]])) return(NULL) + r <- do.call("rbind", lapply(c("A","Q"), function(p) { #period loop + #convert wide matrix to long dataframe + p.df <- as.data.frame(x[[st]][[p]]) + if (is.null(p.df) || nrow(p.df) < 1 || ncol(p.df) < 1 ) return(NULL) + cn <- colnames(p.df) + p.df <- reshape(p.df, direction = "long", varying = cn, times = cn, v.names = "value", ids = rownames(p.df)) + rownames(p.df) <- NULL + p.df$time <- as.Date(p.df$time) + p.df$period <- p + p.df <- p.df[, c("time", "id", "period", "value")] + colnames(p.df) <- c("date", "entry", "period", "value") + return(p.df) + })) + r$type <- st + return(r) + })) +} + +getFinancials.tiingo <- function(Symbol, from, to, as.reported=FALSE, api.key, ...) { + #API Documentation: https://api.tiingo.com/documentation/fundamentals + importDefaults("getFinancials.tiingo") + URL <- sprintf("https://api.tiingo.com/tiingo/fundamentals/%s/statements?format=csv&startDate=%s&endDate=%s&asReported=%s&token=%s", + Symbol, from, to, tolower(as.reported), api.key) + d <- suppressWarnings(read.csv(URL)) + if (ncol(d) == 1 && colnames(d) == "None") stop("No data returned for Symbol: ", Symbol) + + #reshape long dataframe to nested wide matrices, moving column into list elemnt names + stypes <- c(balanceSheet = "BS", incomeStatement = "IS", cashFlow = "CF") + d <- d[d$statementType %in% names(stypes) & d$quarter %in% (0:4),] + d$period <- ifelse(d$quarter == 0, "A", "Q") + d$statementType <- stypes[d$statementType] + + #partition and format output + tsubs <- split(d[, c("date", "dataCode","value", "period")], d$statementType) + r <- lapply(tsubs, function(tsub) { + dsubs <- split(tsub, tsub$period) + #partition by period (Q or A), pivot and convert to a matrix + lapply(dsubs, function(dsub) { + if (NROW(dsub) < 1) return(NULL) + pivot <- reshape(dsub[, c("date", "dataCode", "value")], + timevar = "date", idvar = "dataCode", direction = "wide") + rownames(pivot) <- pivot[[1]] #row names should be unique at this point. an assumption has been violated if not + pivot <- pivot[, -1, drop = FALSE] + colnames(pivot) <- gsub("^value\\.", "", colnames(pivot)) + return(as.matrix(pivot)) + }) + }) + + r$periods <- unique(d[, c("date", "year", "quarter", "period")]) + return(r) } diff --git a/man/getFinancials.Rd b/man/getFinancials.Rd index c2e2f981..f1792016 100644 --- a/man/getFinancials.Rd +++ b/man/getFinancials.Rd @@ -3,12 +3,13 @@ \alias{viewFinancials} \alias{getFin} \alias{viewFin} +\alias{as.data.frame.financials} \title{ Download and View Financial Statements } \description{ Download Income Statement, Balance Sheet, and Cash Flow Statements. } \usage{ -getFinancials(Symbol, env = parent.frame(), src = "google", +getFinancials(Symbol, env = parent.frame(), src = "tiingo", auto.assign = TRUE, ...) @@ -43,24 +44,30 @@ sheet, IS for income statement, and CF for cash flow statement. The period argument is used to identify which statements to view - (A) for annual and (Q) for quarterly. + +\code{as.data.frame.financials} unpacks the finacials list into a wide formate data.frame } \value{ - Six individual matrices organized in a list of class \sQuote{financials}: + Six individual matrices and a data.frame organized in a list of class \sQuote{financials}: \item{ IS }{ a list containing (Q)uarterly and (A)nnual Income Statements } \item{ BS }{ a list containing (Q)uarterly and (A)nnual Balance Sheets } \item{ CF }{ a list containing (Q)uarterly and (A)nnual Cash Flow Statements } + \item{periods}{a data.frame containing Fiscal year and quarter corresponding to each statement contained in the results. + The \code{date} column matches the column name in the statement matrix corresponding to period. Some sources moy not include this in the output} + + Only the data structure itself is normalized. Individual statement matrixes use field names from the underlying source. + Please refer to the source documentation for definitions and explanations. } -\author{ Jeffrey A. Ryan } +\author{ Jeffrey A. Ryan, Ethan Smith } \note{ -As with all free data, you may be getting exactly what you pay for. -Sometimes that may be absolutely nothing. +currently only implemented for tiingo, which requres a paid license. } \examples{ \dontrun{ -getFinancials('JAVA') # returns JAVA.f to "env" +getFinancials('BA') # returns BA.f to "env" getFin('AAPL') # returns AAPL.f to "env" -viewFin(JAVA.f, "IS", "Q") # Quarterly Income Statement +viewFin(BA.f, "IS", "Q") # Quarterly Income Statement viewFin(AAPL.f, "CF", "A") # Annual Cash Flows str(AAPL.f) diff --git a/tests/test_getFinancials.R b/tests/test_getFinancials.R new file mode 100644 index 00000000..55b7f1f3 --- /dev/null +++ b/tests/test_getFinancials.R @@ -0,0 +1,21 @@ +library(quantmod) + +# Tests for getFinancials + +# Checks for tiingo +# tiingo allows access to dow30 symbols for testing/development +apikey <- Sys.getenv("QUANTMOD_TIINGO_API_KEY") +if (nzchar(apikey)) { + aapl <- getFinancials("AAPL", src = "tiingo", api.key = apikey, + auto.assign = FALSE) + stopifnot(inherits(aapl, "financials")) + aapl.df <- as.data.frame(aapl) + stopifnot(is.data.frame(aapl.df)) + #conversion to df shoudl test unpacing nested elements + stopifnot(names(aapl.df) == c("date", "entry", "period", "value", "type")) + + #test multisymbol path with bad symbols + retsym <- getFinancials("AAPL;BA;UNKNOWNSYMBOL", src = "tiingo", + api.key = apikey, auto.assign = TRUE) + stopifnot(retsym == c("AAPL.f", "BA.f")) +}