diff --git a/NAMESPACE b/NAMESPACE index 50167329..275e5071 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -258,6 +258,7 @@ export( getSymbols.SQLite, getSymbols.mysql, getSymbols.FRED, + getSymbols.FXMacroData, getSymbols.yahoo, getSymbols.yahooj, getSymbols.oanda, diff --git a/R/getSymbols.R b/R/getSymbols.R index 7713892f..7d35a8cf 100644 --- a/R/getSymbols.R +++ b/R/getSymbols.R @@ -836,6 +836,91 @@ function(Symbols,env,return.class='xts', return(fr) } #}}} +# getSymbols.FXMacroData {{{ +`getSymbols.FXMacroData` <- function(Symbols, env, + return.class="xts", currency="USD", value.field="val", + api.key=Sys.getenv("FXMACRODATA_API_KEY"), limit=1000, ...) { + importDefaults("getSymbols.FXMacroData") + this.env <- environment() + for(var in names(list(...))) { + assign(var, list(...)[[var]], this.env) + } + if(!hasArg("verbose")) verbose <- FALSE + if(!hasArg("auto.assign")) auto.assign <- TRUE + if(!hasArg("warnings")) warnings <- TRUE + if(!hasArg("from")) from <- "" + if(!hasArg("to")) to <- "" + + API.URL <- "https://fxmacrodata.com/api/v1/announcements" + returnSym <- Symbols + noDataSym <- NULL + + parse_symbol <- function(Symbol) { + parts <- unlist(strsplit(Symbol, "[./:]")) + if(length(parts) >= 2L) { + list(currency=tolower(parts[[1L]]), indicator=tolower(parts[[2L]])) + } else { + list(currency=tolower(currency), indicator=tolower(Symbol)) + } + } + + fetch_json <- function(URL) { + res <- curl::curl_fetch_memory(URL) + txt <- rawToChar(res$content) + if(res$status_code != 200L) { + msg <- tryCatch(jsonlite::fromJSON(txt)$detail, error=function(e) NULL) + stop(if(is.null(msg)) paste0("FXMacroData returned HTTP ", res$status_code) else msg, + call.=FALSE) + } + jsonlite::fromJSON(txt) + } + + for(i in seq_along(Symbols)) { + if(verbose) cat("downloading ", Symbols[[i]], " from FXMacroData.....\n\n") + test <- try({ + parsed <- parse_symbol(Symbols[[i]]) + params <- paste0("?limit=", as.integer(limit)) + if(nzchar(api.key)) params <- paste0(params, "&api_key=", URLencode(api.key, reserved=TRUE)) + if(nzchar(as.character(from))) params <- paste0(params, "&start_date=", URLencode(as.character(from), reserved=TRUE)) + if(nzchar(as.character(to))) params <- paste0(params, "&end_date=", URLencode(as.character(to), reserved=TRUE)) + URL <- paste(API.URL, parsed$currency, parsed$indicator, sep="/") + fxmd <- fetch_json(paste0(URL, params)) + rows <- fxmd$data + if(is.null(rows) || NROW(rows) < 1L) + stop("FXMacroData returned no rows", call.=FALSE) + rows <- utils::head(rows, as.integer(limit)) + if(!value.field %in% colnames(rows)) + stop(paste0("FXMacroData response does not contain value field ", dQuote(value.field)), + call.=FALSE) + date.field <- if("date" %in% colnames(rows)) "date" else "announcement_datetime_local" + fr <- xts(as.numeric(rows[[value.field]]), as.Date(rows[[date.field]]), + src="FXMacroData", updated=Sys.time()) + fr <- fr[order(index(fr))] + colnames(fr) <- as.character(toupper(Symbols[[i]])) + fr <- fr[paste(from, to, sep="/")] + fr <- convert.time.series(fr=fr, return.class=return.class) + Symbols[[i]] <- toupper(gsub("[^[:alnum:]_]", "_", Symbols[[i]])) + if(auto.assign) + assign(Symbols[[i]], fr, env) + if(verbose) cat("done.\n") + }, silent=TRUE) + if(inherits(test, "try-error")) { + msg <- paste0("Unable to import ", dQuote(returnSym[[i]]), + " from FXMacroData.\n", attr(test, "condition")$message) + if(hasArg(".has1sym.") && match.call(expand.dots=TRUE)$.has1sym.) { + stop(msg) + } + if(isTRUE(warnings)) { + warning(msg, call.=FALSE, immediate.=TRUE) + } + noDataSym <- c(noDataSym, returnSym[[i]]) + } + } + if(auto.assign) + return(setdiff(returnSym, noDataSym)) + return(fr) +} #}}} + "getSymbols.cache" <- function() {} # getFX {{{ diff --git a/man/getSymbols.FXMacroData.Rd b/man/getSymbols.FXMacroData.Rd new file mode 100644 index 00000000..9d4863ca --- /dev/null +++ b/man/getSymbols.FXMacroData.Rd @@ -0,0 +1,50 @@ +\name{getSymbols.FXMacroData} +\alias{getSymbols.FXMacroData} +\title{Download Macroeconomic Data from FXMacroData} +\description{ +Download macroeconomic announcement time series from FXMacroData into an +\code{xts} object. +} +\usage{ +getSymbols.FXMacroData(Symbols, env, return.class = "xts", + currency = "USD", value.field = "val", + api.key = Sys.getenv("FXMACRODATA_API_KEY"), limit = 1000, ...) +} +\arguments{ + \item{Symbols}{character vector of FXMacroData series. Use + \code{"USD.INFLATION"}, \code{"USD.UNEMPLOYMENT"}, or pass an indicator such + as \code{"inflation"} with \code{currency = "USD"}.} + \item{env}{where to create objects when \code{auto.assign = TRUE}.} + \item{return.class}{class of returned object, passed to quantmod conversion.} + \item{currency}{default currency used when a symbol omits the currency + prefix.} + \item{value.field}{response field to use as the numeric observation, usually + \code{"val"} or \code{"val_mom"}.} + \item{api.key}{optional FXMacroData API key. Defaults to the + \code{FXMACRODATA_API_KEY} environment variable.} + \item{limit}{maximum rows requested from FXMacroData.} + \item{\dots}{additional arguments, including \code{from}, \code{to}, + \code{auto.assign}, \code{verbose}, and \code{warnings}.} +} +\details{ +The source calls +\url{https://fxmacrodata.com/api/v1/announcements/\{currency\}/\{indicator\}} +and converts the \code{data} array to a dated \code{xts} series. Authentication +is optional for public USD macro series; when provided, the API key is passed as +the \code{api_key} query parameter. +} +\value{ +If \code{auto.assign = TRUE}, the loaded symbol names are returned invisibly and +objects are created in \code{env}. Otherwise, an \code{xts} object is returned. +} +\examples{ +\dontrun{ +getSymbols("USD.INFLATION", src = "FXMacroData") +getSymbols("unemployment", src = "FXMacroData", currency = "USD", + auto.assign = FALSE) +} +} +\seealso{ +\code{\link{getSymbols}}, \code{\link{setSymbolLookup}}, +\code{\link{getSymbols.FRED}} +}