Skip to content
Open
121 changes: 95 additions & 26 deletions R/getFinancials.R
Original file line number Diff line number Diff line change
@@ -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 <-
Expand All @@ -16,40 +40,85 @@ 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',
CF='Cash Flow Statement',
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)
}
21 changes: 14 additions & 7 deletions man/getFinancials.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
...)

Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_getFinancials.R
Original file line number Diff line number Diff line change
@@ -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"))
}