R/prices.R

Defines functions retrieve_crypto_price_col_ordering riingo_crypto_prices riingo_iex_prices_single riingo_iex_prices_single_safely riingo_iex_prices riingo_prices_single riingo_prices_single_safely riingo_prices

Documented in riingo_crypto_prices riingo_iex_prices riingo_prices

# ------------------------------------------------------------------------------
# Tiingo prices

#' Get stock or ETF prices from the Tiingo API
#'
#' The Tiingo API provides a large feed of historical data at the
#' daily (and weekly, monthly, or annual) level.
#'
#' @param ticker One or more tickers to download data for from Tiingo. Can be a
#' stock, mutual fund, or ETF. A character vector.
#' @param start_date The first date to download data for.
#' A character in the form YYYY-MM-DD, or a `Date` variable. The default is to
#' download 1 year's worth of data.
#' @param end_date The last date to download data for.
#' A character in the form YYYY-MM-DD, or a `Date` variable.
#' @param resample_frequency For Tiingo data, a character specified as one of:
#' `"daily"`, `"weekly"`, `"monthly"` or `"annually"`.
#'
#' For IEX data, a character specified at the `"min"` or `"hour"` frequencies
#' in the form: `"1min"`, `"5min"`, or `"2hour"`.
#'
#' For Crypto data, a character specified at the `"min"`, `"hour"` or `"day"`
#' frequencies similar to IEX.
#'
#' @details
#'
#' Multiple downloads are done _sequentially_, meaning that downloading 5 tickers
#' costs 5 requests against your usage limits. Sadly Tiingo does not support
#' batch downloads at the moment.
#'
#' __Tiingo supplied documentation regarding the resample frequency:__
#'  * daily: Values returned as daily periods, with a holiday calendar
#'  * weekly: Values returned as weekly data, with days ending on Friday
#'  * monthly: Values returned as monthly data, with days ending on the last standard business day (Mon-Fri) of each month
#'  * annually: Values returned as annual data, with days ending on the last standard business day (Mon-Fri) of each year
#'
#'  * Note, that if you choose a value in-between the resample period for weekly,
#' monthly, and daily, the start date rolls back to consider the entire period.
#' For example, if you choose to resample weekly, but your "start_date" parameter
#' is set to Wednesday of that week, the start_date will be adjusted to Monday,
#' so the entire week is captured. Another example is if you send a start_date
#' mid-month, we roll back the start_date to the beginning of the month.
#'
#'  * Similarly, if you provide an end_date, and it's midway through the period,
#' we roll-forward the date to capture the whole period. In the above example,
#' if the end date is set to a wednesday with a weekly resample, the end date is
#' rolled forward to the Friday of that week.
#'
#'
#' @examples
#'
#' \dontrun{
#'
#' # Downloading 1 year's worth of prices for AAPL
#' riingo_prices("AAPL")
#'
#' # Downloading a range of data, using 2 tickers
#' riingo_prices(c("AAPL", "MSFT"), "1999-01-01", "2005-01-01")
#'
#' # Monthly data
#' riingo_prices(c("AAPL", "MSFT"), "1999-01-01", "2005-01-01", "monthly")
#'
#' }
#'
#' @export
riingo_prices <- function(ticker, start_date = NULL, end_date = NULL, resample_frequency = "daily") {

  assert_valid_argument_inheritance(ticker, start_date, end_date, resample_frequency)
  assert_resample_freq_is_granular(resample_frequency)

  results <- purrr::map(
    .x = ticker,
    .f = riingo_prices_single_safely,
    start_date = start_date,
    end_date = end_date,
    resample_frequency = resample_frequency
  )

  validate_not_all_null(results)

  vctrs::vec_rbind(!!!results)
}

riingo_prices_single_safely <- function(ticker, start_date, end_date, resample_frequency) {
  riingo_single_safely(
    .f = riingo_prices_single,
    ticker = ticker,
    start_date = start_date,
    end_date = end_date,
    resample_frequency = resample_frequency
  )
}

riingo_prices_single <- function(ticker, start_date, end_date, resample_frequency) {

  type <- "tiingo"
  endpoint <- "prices"

  # URL construction
  riingo_url <- construct_url(
    type, endpoint, ticker,
    startDate = start_date,
    endDate = end_date,
    resampleFreq = resample_frequency
  )

  # Download
  cont_df <- content_downloader(riingo_url, ticker)

  # Clean
  riingo_data <- clean_json_df(cont_df, type, endpoint)

  # Add ticker
  riingo_data_with_ticker <- tibble::add_column(riingo_data, ticker = ticker, .before = 1L)

  riingo_data_with_ticker
}

# ------------------------------------------------------------------------------
# IEX prices

#' Get stock or ETF prices from IEX through Tiingo
#'
#' The Tiingo API provides a way to access data from IEX,
#' The Investors Exchange. This data is supplied at a much lower (intraday!)
#' frequency than the data from Tiingo's native API.
#'
#' @inheritParams riingo_prices
#'
#' @param after_hours A single logical. Should pre and post market data be
#'   returned if available?
#'
#' @param force_fill A single logical. Some tickers do not have a trade/quote
#'   update for a given time period. If `force_fill` is set to `TRUE`, then the
#'   previous OHLC will be used to fill the current OHLC.
#'
#' @details
#'
#' This feed returns the most recent 2000 ticks of data at the specified
#' frequency. For example, `"5min"` would return the 2000 most recent data
#' points spaced 5 minutes apart. You can subset the returned range with
#' `start_date` and `end_date`, but __you cannot request data older than
#' today's date minus 2000 data points.__
#'
#' Because the default attempts to pull 1 year's worth of data, at a 5 minute
#' frequency, all available data will be pulled so there is no need to use
#' `start_date` and `end_date`. Only use them if you set the frequency to
#' hourly.
#'
#' @examples
#'
#' \dontrun{
#'
#' # Pulling all available minute level data for Apple
#' riingo_iex_prices("AAPL", resample_frequency = "1min")
#'
#' # This would result in an error, as you are pulling outside the available range
#' # riingo_iex_prices("AAPL", "1990-01-01", "2000-01-01", resample_frequency = "5min")
#'
#' }
#'
#' @export
riingo_iex_prices <- function(ticker,
                              start_date = NULL,
                              end_date = NULL,
                              resample_frequency = "5min",
                              after_hours = FALSE,
                              force_fill = FALSE) {

  assert_valid_argument_inheritance(ticker, start_date, end_date, resample_frequency)
  assert_resample_freq_is_fine(resample_frequency)
  assert_x_inherits(after_hours, "after_hours", "logical")
  assert_x_inherits(force_fill, "force_fill", "logical")

  results <- purrr::map(
    .x = ticker,
    .f = riingo_iex_prices_single_safely,
    start_date = start_date,
    end_date = end_date,
    resample_frequency = resample_frequency,
    after_hours = after_hours,
    force_fill = force_fill
  )

  validate_not_all_null(results)

  vctrs::vec_rbind(!!!results)
}

riingo_iex_prices_single_safely <- function(ticker,
                                            start_date,
                                            end_date,
                                            resample_frequency,
                                            after_hours,
                                            force_fill) {
  riingo_single_safely(
    .f = riingo_iex_prices_single,
    ticker = ticker,
    start_date = start_date,
    end_date = end_date,
    resample_frequency = resample_frequency,
    after_hours = after_hours
  )
}

riingo_iex_prices_single <- function(ticker,
                                     start_date = NULL,
                                     end_date = NULL,
                                     resample_frequency = "5min",
                                     after_hours = FALSE,
                                     force_fill = FALSE) {

  type <- "iex"
  endpoint <- "prices"

  # URL construction
  riingo_url <- construct_url(
    type, endpoint, ticker,
    startDate = start_date,
    endDate = end_date,
    resampleFreq = resample_frequency,
    afterHours = after_hours,
    forceFill = force_fill
  )

  # Download
  cont_df <- content_downloader(riingo_url, ticker)

  # Clean
  riingo_data <- clean_json_df(cont_df, type, endpoint)

  # Add ticker
  riingo_data_with_ticker <- tibble::add_column(riingo_data, ticker = ticker, .before = 1L)

  riingo_data_with_ticker
}

# ------------------------------------------------------------------------------
# Crypto prices


#' Get cryptocurrency prices aggregated through Tiingo
#'
#'
#' @inheritParams riingo_prices
#' @param ticker One or more cryptocurrency tickers.
#' Specified as `"btcusd"` for bitcoin quoted in USD. A character vector.
#' @param base_currency _Instead_ of `ticker` you may pass a base currency.
#' This selects all currencies with that base currency.
#' For example if `base_currency="btc"`` tickers _btcusd_, _btcjpy_, _btceur_, etc..
#' will all be returned.
#' @param exchanges If you would like to limit the query to a subset of exchanges,
#' pass a comma-separated list of exchanges to select. Example) `"POLONIEX, GDAX"`
#' @param convert_currency This parameter will convert the return data into another
#' fx rate. For example if querying `BTCUSD` and convert_currency is `'cure'`,
#' the bitcoin prices will be converted to CureCoin prices.
#' Setting this to a value will add `fxOpen`, `fxHigh`, `fxLow`, `fxClose`, `fxVolumeNotional`,
#' and `fxRate` accordingly. `fxRate` is the rate used to perform the currency calculation.
#' If `exchanges` is specified, the conversion rate will be calculated using the exchanges passed.
#' @param raw If `TRUE`, the raw underlying data from multiple exchanges will be
#' returned, rather than the clean prices. This is the data that calculates the aggregated prices and quotes.
#'
#' @export
#'
#' @examples
#'
#' \dontrun{
#'
#' # Bitcoin prices
#' riingo_crypto_prices("btcusd")
#'
#' # Bitcoin in USD and EUR
#' riingo_crypto_prices(c("btcusd", "btceur"), start_date = "2018-01-01", resample_frequency = "5min")
#'
#' # Bitcoin raw data
#' riingo_crypto_prices("btcusd", raw = TRUE)
#'
#' # Only use the POLONIEX exchange
#' riingo_crypto_prices("btcusd", raw = TRUE, exchanges = "POLONIEX")
#'
#' # All btc___ crypotcurrency pairs
#' riingo_crypto_prices(base_currency = "btc")
#'
#' }
#'
riingo_crypto_prices <- function(ticker, start_date = NULL, end_date = NULL,
                                 resample_frequency = "1day", base_currency = NULL,
                                 exchanges = NULL, convert_currency = NULL, raw = FALSE) {

  if(!is.null(base_currency)) {
    if(!missing(ticker)) {
      stop("Cannot specify both the ticker and the base_currency arguments.", call. = FALSE)
    } else {
      ticker <- NA_character_
    }
  }

  if(!is.null(convert_currency)) {
    if(length(ticker) > 1L) {
      warning("The Tiingo API only uses the first ticker when convert_currency is specified.", call. = FALSE)
    }
  }

  # Assertions
  assert_x_inherits(ticker, "ticker", class = "character")
  assert_x_inherits_one_of(start_date, "start_date", c("NULL", "character", "Date", "POSIXct"))
  assert_x_inherits_one_of(end_date, "end_date", c("NULL", "character", "Date", "POSIXct"))
  assert_x_inherits(resample_frequency, "resample_frequency", "character")
  assert_x_inherits_one_of(base_currency, "base_currency", c("NULL", "character"))
  assert_x_inherits_one_of(exchanges, "exchanges", c("NULL", "character"))
  assert_x_inherits_one_of(convert_currency, "convert_currency", c("NULL", "character"))
  assert_resample_freq_is_crypto(resample_frequency)

  type <- "crypto"
  endpoint <- "prices"

  # For crypto, tickers are passed as a comma separated parameter
  ticker <- glue::glue_collapse(ticker, ",")

  # URL construction
  riingo_url <- construct_url(
    type, endpoint, ticker = NULL,
    tickers = ticker,
    startDate = start_date,
    endDate = end_date,
    resampleFreq = resample_frequency,
    baseCurrency = base_currency,
    exchanges = exchanges,
    convertCurrency = convert_currency
  )

  if(raw) {

    riingo_url <- glue::glue(riingo_url, "&includeRawExchangeData=true")

    # Download
    cont_df <- content_downloader(riingo_url, ticker)

    # We are only going to return exchange data, ignore price data
    exch_data_idx <- which(colnames(cont_df) == "exchangeData")
    pd_idx <- which(colnames(cont_df) == "priceData")

    # Meta data section
    meta <- tibble::as_tibble(cont_df[, -c(exch_data_idx, pd_idx)])
    meta_rows <- split(meta, meta$ticker)

    # The actual raw data
    exch_data <- cont_df[, exch_data_idx]

    seq_ticker <- seq_along(meta$ticker)

    # Extract "nested" data frames
    exch_data_rows <- purrr::map(seq_ticker, ~{
      i <- .x
      tibble::as_tibble(riingo_map_dfr(exch_data, ~.x[[i]]))
    })

    # Bind each row of meta to an exchange data frame
    riingo_df <- riingo_map2_dfr(meta_rows, exch_data_rows, ~cbind(.x, .y))

  } else {

    # Download
    cont_df <- content_downloader(riingo_url, ticker)

    # Have to convert to tibble here, otherwise warnings in the map
    cont_tbl <- tibble::as_tibble(cont_df)

    # Coerce to tidy format (for each row, unnest the priceData)
    cont_tbl_split <- split(cont_tbl, cont_tbl$ticker)

    riingo_df <- riingo_map_dfr(cont_tbl_split, ~{
      pd_idx <- which(colnames(.x) == "priceData")
      meta <- .x[, -pd_idx]
      pd   <- riingo_flatten_dfr(.x[, pd_idx])
      cbind(meta, pd)
    })
  }

  # cbind() returned as df
  riingo_tbl <- tibble::as_tibble(riingo_df)

  # type convert date columns
  date_cols <- retrieve_date_col_names(type, endpoint)

  riingo_data <- purrr::modify_at(
    .x  = riingo_tbl,
    .at = date_cols,
    .f  = ~as.POSIXct(.x, tz = "UTC", format = retrieve_date_col_format(endpoint))
  )

  # Reorder columns
  riingo_data <- riingo_data[, retrieve_crypto_price_col_ordering(raw, convert_currency)]

  riingo_data
}

retrieve_crypto_price_col_ordering <- function(raw = FALSE, convert_currency = NULL) {
  if(raw) {
    cols <- c("ticker", "exchangeCode", "baseCurrency", "quoteCurrency", "date", "open", "high", "low", "close", "volume", "volumeNotional", "tradesDone")
  } else {
    cols <- c("ticker", "baseCurrency", "quoteCurrency", "date", "open", "high", "low", "close", "volume", "volumeNotional", "tradesDone")
  }

  if(!is.null(convert_currency)) {
    cols <- c(cols, "fxOpen", "fxHigh", "fxLow", "fxClose", "fxRate", "fxVolumeNotional")
  }

  cols
}

Try the riingo package in your browser

Any scripts or data that you put into this service are public.

riingo documentation built on Sept. 13, 2020, 5:15 p.m.