Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

301 tiingo financial statements #352

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
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"))
}