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

Fetch data from the Tiingo API with a getSymbols.tiingo() method. #220

Merged
merged 3 commits into from
Apr 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Authors@R: c(
person(given=c("Jeffrey","A."), family="Ryan", role=c("aut","cph")),
person(given=c("Joshua","M."), family="Ulrich", role=c("cre","aut"), email="josh.m.ulrich@gmail.com"),
person(given="Wouter", family="Thielen", role="ctb"),
person(given="Paul", family="Teetor", role="ctb")
person(given="Paul", family="Teetor", role="ctb"),
person(given="Steve", family="Bronder", role="ctb")
)
Depends: R (>= 3.2.0), xts(>= 0.9-0), zoo, TTR(>= 0.2), methods
Imports: curl
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export(
getSymbols.yahoo,
getSymbols.yahooj,
getSymbols.oanda,
getSymbols.tiingo,
#getSymbols.Bloomberg,
#getSymbols.IBrokers,
getSymbols.csv,
Expand Down
119 changes: 119 additions & 0 deletions R/getSymbols.R
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,125 @@ getSymbols.av <- function(Symbols, env, api.key,
# Mnemonic alias, letting callers use getSymbols("IBM", src="alphavantage")
getSymbols.alphavantage <- getSymbols.av

#
# Download OHLC Data From Tiingo
#
# Meant to be called internally by getSymbols().
#
getSymbols.tiingo <- function(Symbols, env, api.key,
return.class="xts",
periodicity="daily",
adjust=FALSE,
from='2007-01-01',
to=Sys.Date(),
data.type="json",
...) {

importDefaults("getSymbols.tiingo")
this.env <- environment()
for (var in names(list(...))) {
assign(var, list(...)[[var]], this.env)
}

if (!hasArg("api.key")) {
stop("getSymbols.tiingo: An API key is required (api.key). Register",
" at https://api.tiingo.com.", call.=FALSE)
}
if (!hasArg("auto.assign")) auto.assign <- TRUE
if (!hasArg("verbose")) verbose <- FALSE
if (!hasArg("warnings")) warnings <- TRUE

valid.periodicity <- c("daily", "weekly", "monthly", "annually")
periodicity <- match.arg(periodicity, valid.periodicity)
default.return.class <- return.class
default.periodicity <- periodicity

if (!requireNamespace("jsonlite", quietly=TRUE)) {
stop("getSymbols.tiingo: Package", dQuote("jsonlite"), "is required but",
" cannot be loaded.", call.=FALSE)
}

tmp <- tempfile()
on.exit(file.remove(tmp))

downloadOne <- function(sym, default.return.class, default.periodicity) {

return.class <- getSymbolLookup()[[sym]]$return.class
return.class <- if (is.null(return.class)) default.return.class else return.class
periodicity <- getSymbolLookup()[[sym]]$periodicity
periodicity <- if (is.null(periodicity)) default.periodicity else periodicity
periodicity <- match.arg(periodicity, valid.periodicity)
sym.name <- getSymbolLookup()[[sym]]$name
sym.name <- if (is.null(sym.name)) sym else sym.name

if (verbose) cat("loading", sym.name, ".....")
from.strftime <- strftime(from, format = "%Y-%m-%d")
to.strftime <- strftime(to, format = "%Y-%m-%d")

tiingo.names <- c("open", "high", "low", "close", "volume",
"adjClose", "adjHigh", "adjLow", "adjOpen",
"adjVolume", "divCash", "splitFactor")
qm.names <- paste(sym, c("Open", "High", "Low", "Close", "Volume",
"Open", "High", "Low", "Close", "Volume",
"DivCash", "SplitFactor"), sep=".")
if (isTRUE(adjust)) {
return.columns <- tiingo.names[6:10]
} else {
return.columns <- tiingo.names[1:5]
}
URL <- paste0("https://api.tiingo.com/tiingo/",
periodicity, "/",
sym.name, "/prices",
"?startDate=", from.strftime,
"&endDate=", to.strftime,
"&format=", data.type,
"&token=", api.key,
"&columns=", paste0(return.columns, collapse=","))
# If rate limit is hit, the csv API returns HTTP 200 (OK), while json API
# returns HTTP 429. The latter caused download.file() to error, but the
# contents of 'tmp' still contain the error message.
response <- curl::curl_fetch_disk(URL, tmp)

if (data.type == "json") {
stock.data <- jsonlite::fromJSON(tmp)
if (verbose) cat("done.\n")
} else {
stock.data <- read.csv(tmp, as.is=TRUE)
}
# check for error
if (!all(return.columns %in% names(stock.data))) {
if (data.type == "json") {
msg <- jsonlite::fromJSON(tmp)$detail
} else {
msg <- readLines(tmp, warn=FALSE)
}
msg <- sub("Error: ", "", msg)
stop(msg, call. = FALSE)
}
tm.stamps <- as.POSIXct(stock.data[, "date"], ...)
stock.data[, "date"] <- NULL
colnames(stock.data) <- qm.names[match(colnames(stock.data), tiingo.names)]
# convert data to xts
xts.data <- xts(stock.data, tm.stamps, src="tiingo", updated=Sys.time())
xts.data <- convert.time.series(xts.data, return.class=return.class)
# order columns
xts.data <- OHLCV(xts.data)
if (auto.assign)
assign(sym, xts.data, env)
return(xts.data)
}

matrices <- lapply(Symbols, FUN=downloadOne,
default.return.class=default.return.class,
default.periodicity=default.periodicity)

if (auto.assign) {
return(Symbols)
} else {
return(matrices[[1]])
}
}

# convert.time.series {{{
`convert.time.series` <- function(fr,return.class) {
if('quantmod.OHLC' %in% return.class) {
Expand Down
84 changes: 84 additions & 0 deletions man/getSymbols.tiingo.Rd
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
\name{getSymbols.tiingo}
\alias{getSymbols.tiingo}
\title{ Download OHLC Data from Tiingo }
\description{
Downloads historical or realtime equity price data
from \url{https://api.tiingo.com/}.
Registration is required.
}
\usage{
getSymbols.tiingo(Symbols, env, api.key,
return.class="xts",
periodicity="daily",
adjust=FALSE,
from='2007-01-01',
to=Sys.Date(),
data.type="json",
...)
}
\arguments{
\item{Symbols}{ a character vector specifying the names
of the symbols to be loaded}
\item{env}{ where to create objects (environment) }
\item{api.key}{ the API key issued by Tiingo when you registered (character)}
\item{return.class}{ class of returned object, see Value (character) }
\item{periodicity}{ one of \code{"daily"}, \code{"weekly"}, \code{"monthly"}, or \code{"Annually"} }
\item{adjust}{ adjust for dividends and splits? (FALSE) }
\item{from}{ Retrieve data no earlier than this date. (2007-01-01)}
\item{to}{ Retrieve data through this date (Sys.Date())}
\item{data.type}{ either \code{"json"} or \code{"csv"} }
\item{\dots}{ additional parameters as per \code{\link{getSymbols}} }
}
\details{
Meant to be called internally by \code{getSymbols} only.
This method is not meant to be called directly, instead
a call to \code{getSymbols("x", src="tiingo")} will
in turn call this method. It is documented for the
sole purpose of highlighting the arguments accepted.

You must register with Tiingo in order to download their data.
Register at their web site, \url{https://api.tiingo.com},
and you will receive an \emph{API key}:
a short string of alphanumeric characters (e.g., "FU4U").
Provide the API key every time you call \code{getSymbols};
or set it globally using \code{setDefaults(getSymbols.tiingo, api.key="yourKey")}.

Tiingo provides daily, weekly, monthly, and annual data.
Use \code{periodicity} to select one.
This API accessor will return adjusted or unadjusted OHLC as well as split and dividend information.

For daily, weekly, and monthly data, Tiingo says the available data is up to 30 years;

Tiingo provides access to data via two APIs. You can choose the API via
the \code{data.type} argument. \code{data.type="json"}, the default, will
import data using the JSON API. This API includes additional metadata (e.g.
last updated time, timezone, etc) that is not provided via the CSV API.
}
\value{
A call to \code{getSymbols(Symbols, src="tiingo")} will create objects
in the specified environment,
one object for each \code{Symbol} specified.
The object class of the object(s) is determined by \code{return.class}.
Presently this may be \code{"ts"}, \code{"zoo"}, \code{"xts"}, or \code{"timeSeries"}.
}
% \note{
% [TBD]
% }
\references{ Tiingo documentation available at \url{https://www.tiingo.com} }
\author{ Steve Bronder }
\seealso{
\code{\link{getSymbols}},
\code{\link{getSymbols.yahoo}},
\code{\link{getSymbols.av}}
}
\examples{
\dontrun{
# You'll need the API key given when you registered
getSymbols("IBM", src="tiingo", api.key="yourKey")

# Repeating your API key every time is tedious.
# Fortunately, you can set a global default.
setDefaults(getSymbols.tiingo, api.key="yourKey")
getSymbols("IBM", src="tiingo")
}
}
10 changes: 10 additions & 0 deletions tests/tests.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ options(warn = 2)
quantmod::getSymbols("SPY", src = "google", from = Sys.Date() - 10)
options(warn = op.warn)

# should throw an error
errorKey <- "d116c846835e633aacedb1a31959dd2724cd67b8"
x <- try(
quantmod::getSymbols("AAPL", src = "tiingo", data.type = "csv", api.key = errorKey)
, silent = TRUE)
stopifnot(inherits(x, "try-error"))
x <- try(
quantmod::getSymbols("AAPL", src = "tiingo", data.type = "json", api.key = errorKey)
, silent = TRUE)
stopifnot(inherits(x, "try-error"))