diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 58143fe..e7a233e 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -8,11 +8,9 @@ on: push: branches: - main - - master pull_request: branches: - main - - master name: R-CMD-check @@ -87,7 +85,8 @@ jobs: - name: Check env: _R_CHECK_CRAN_INCOMING_: false - SIMFIN_API_KEY: ${{ secrets.SIMFIN_API_KEY }} + SFPLUS_API_KEY: ${{ secrets.SFPLUS_API_KEY }} + SF_API_KEY: ${{ secrets.SF_API_KEY }} run: | options(crayon.enabled = TRUE) rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index a9332b6..2bb34c2 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -40,6 +40,7 @@ jobs: - name: Test coverage env: - SIMFIN_API_KEY: ${{ secrets.SIMFIN_API_KEY }} + SFPLUS_API_KEY: ${{ secrets.SFPLUS_API_KEY }} + SF_API_KEY: ${{ secrets.SF_API_KEY }} run: covr::codecov() shell: Rscript {0} diff --git a/DESCRIPTION b/DESCRIPTION index 7bd35f6..5bd16d1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,8 @@ Imports: memoise (>= 1.1.0), RcppSimdJson (>= 0.1.1), utils, - progressr + progressr, + bit64 Suggests: covr (>= 3.5.0), testthat, @@ -34,7 +35,7 @@ Suggests: Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.1.2 VignetteBuilder: knitr Config/testthat/edition: 3 Config/testthat/parallel: true diff --git a/NAMESPACE b/NAMESPACE index 0f2b8f2..9354bb6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,15 +8,20 @@ export(sfa_get_shares) export(sfa_get_statement) export(sfa_set_api_key) export(sfa_set_cache_dir) +export(sfa_set_sfplus) importFrom(RcppSimdJson,fparse) +importFrom(bit64,as.integer64) importFrom(checkmate,assert_character) importFrom(checkmate,assert_choice) importFrom(checkmate,assert_date) importFrom(checkmate,assert_directory) importFrom(checkmate,assert_integerish) +importFrom(checkmate,assert_logical) importFrom(checkmate,assert_string) +importFrom(data.table,CJ) importFrom(data.table,as.data.table) importFrom(data.table,fread) +importFrom(data.table,is.data.table) importFrom(data.table,rbindlist) importFrom(data.table,set) importFrom(data.table,setattr) @@ -26,6 +31,7 @@ importFrom(data.table,setnames) importFrom(data.table,transpose) importFrom(data.table,year) importFrom(future.apply,future_lapply) +importFrom(future.apply,future_mapply) importFrom(httr,GET) importFrom(httr,content) importFrom(memoise,cache_filesystem) diff --git a/R/check_inputs.R b/R/check_inputs.R index 4dea7c1..c318fde 100644 --- a/R/check_inputs.R +++ b/R/check_inputs.R @@ -1,111 +1,155 @@ -#' Generic input checks -#' @description This function covers all kinds of (recurring) input checks in -#' {simfinapi}. This keeps the other functions cleaner. -#' @inheritParams sfa_get_statement -#' @inheritParams sfa_get_prices -#' @inheritParams sfa_get_shares -#' @inheritParams sfa_get_ref -#' @importFrom checkmate assert_string assert_directory assert_character -#' assert_integerish assert_choice assert_date -#' @importFrom data.table year -check_inputs <- function( - api_key = NULL, cache_dir = NULL, ticker = NULL, simfin_id = NULL, - statement = NULL, period = NULL, fyear = NULL, start = NULL, end = NULL, - ttm = NULL, shares = NULL, ratios = NULL, type = NULL, ref_data = NULL -) { - if (!is.null(api_key)) { - checkmate::assert_string(api_key, pattern = "^[[:alnum:]]{32}$") - } - if (!is.null(cache_dir)) { - checkmate::assert_directory(cache_dir, access = "rw") - } - if (!is.null(ticker)) { - checkmate::assert_character( - ticker, - pattern = "^[A-Za-z0-9_\\.\\-]+$", - any.missing = FALSE, - null.ok = TRUE - ) - } - if (!is.null(simfin_id)) { - checkmate::assert_integerish( - simfin_id, - lower = 1L, - any.missing = FALSE, - null.ok = TRUE - ) - } - if (!is.null(statement)) { - checkmate::assert_choice( - statement, - c("pl", "bs", "cf", "derived", "all"), - fmatch = TRUE - ) - } - if (!is.null(period)) { - checkmate::assert_choice( - period, - c("q1", "q2", "q3", "q4", "fy", "h1", "h2", "9m", "6m", "quarters"), - fmatch = TRUE - ) - } - if (!is.null(fyear)) { - checkmate::assert_integerish( - fyear, - lower = 1900L, - upper = data.table::year(Sys.Date()) - ) +msg_sfplus_required <- function(var, verb = "Omitting") { + stop(verb, " '", var, "' is reserved for SimFin+ users.", call. = FALSE) +} + +#' @importFrom checkmate assert_string +check_api_key <- function(api_key) { + checkmate::assert_string(api_key, pattern = "^[[:alnum:]]{32}$") +} + +#' @importFrom checkmate assert_directory +check_cache_dir <- function(cache_dir) { + checkmate::assert_directory(cache_dir, access = "rw") +} + +#' @importFrom checkmate assert_logical +check_sfplus <- function(sfplus) { + checkmate::assert_logical(sfplus, any.missing = FALSE, len = 1L) +} + +#' @importFrom checkmate assert_character +check_ticker <- function(ticker) { + checkmate::assert_character( + ticker, + pattern = "^[A-Za-z0-9_\\.\\-]+$", + any.missing = FALSE, + null.ok = TRUE + ) +} + +#' @importFrom checkmate assert_integerish +check_simfin_id <- function(simfin_id) { + checkmate::assert_integerish( + simfin_id, + lower = 1L, + any.missing = FALSE, + null.ok = TRUE + ) +} + +#' @importFrom checkmate assert_choice +check_statement <- function(statement, sfplus) { + checkmate::assert_choice( + statement, + c("pl", "bs", "cf", "derived", "all"), + fmatch = TRUE + ) + if (statement == "all" & isFALSE(sfplus)) { + stop('statement = "all" is reserved for SimFin+ users.', call. = FALSE) } - if (!is.null(start)) { - checkmate::assert_date( - start, - lower = as.Date("1900-01-01"), - upper = Sys.Date() - ) +} + +#' @importFrom checkmate assert_choice +check_period <- function(period, sfplus) { + if (is.null(period) & isFALSE(sfplus)) { + msg_sfplus_required("period") } - if (!is.null(end)) { - checkmate::assert_date( - end, - lower = as.Date("1900-01-01"), - upper = Sys.Date() - ) + checkmate::assert_choice( + period, + c("q1", "q2", "q3", "q4", "fy", "h1", "h2", "9m", "6m", "quarters"), + null.ok = TRUE, + fmatch = TRUE + ) + if (period == "quarters" & isFALSE(sfplus)) { + stop('period = "quarters" is reserved for SimFin+ users.', call. = FALSE) } - if (!is.null(ttm)) { - checkmate::assert_logical(ttm, any.missing = FALSE, len = 1L) +} + +#' @importFrom checkmate assert_integerish +check_fyear <- function(fyear, sfplus) { + if (is.null(fyear) & isFALSE(sfplus)) { + msg_sfplus_required("fyear") } - if (!is.null(shares)) { - checkmate::assert_logical(shares, any.missing = FALSE, len = 1L) + checkmate::assert_integerish( + fyear, + lower = 1900L, + upper = data.table::year(Sys.Date()), + null.ok = TRUE + ) +} + +#' @importFrom checkmate assert_date +check_start <- function(start, sfplus) { + if (!is.null(start) & isFALSE(sfplus)) { + msg_sfplus_required("start", "Specifying") } - if (!is.null(ratios)) { - checkmate::assert_logical(ratios, any.missing = FALSE, len = 1L) + checkmate::assert_date( + start, + lower = as.Date("1900-01-01"), + upper = Sys.Date(), + null.ok = TRUE + ) +} + +#' @importFrom checkmate assert_date +check_end <- function(end, sfplus) { + if (!is.null(end) & isFALSE(sfplus)) { + msg_sfplus_required("end", "Specifying") } - if (!is.null(type)) { - checkmate::assert_choice( - type, - choices = c("common", "wa-basic", "wa-diluted"), - fmatch = TRUE + checkmate::assert_date( + end, + lower = as.Date("1900-01-01"), + upper = Sys.Date(), + null.ok = TRUE + ) +} + +#' @importFrom checkmate assert_logical +check_ttm <- function(ttm) { + checkmate::assert_logical(ttm, any.missing = FALSE, len = 1L) +} + +#' @importFrom checkmate assert_logical +check_shares <- function(shares, sfplus) { + checkmate::assert_logical(shares, any.missing = FALSE, len = 1L) + + if (isTRUE(shares) & isFALSE(sfplus)) { + stop( + "'shares = TRUE' is reserved to SimFin+ users. As a normal user, please ", + "use 'sfa_get_shares()' with 'type = \"wa-basic\"' or 'type = ", + "\"wa-diluted\".", + call. = FALSE ) } - if (!is.null(ref_data)) { - checkmate::assert_choice( - ref_data, - choices = c("industries", "markets"), - fmatch = TRUE - ) +} + +#' @importFrom checkmate assert_logical +check_ratios <- function(ratios, sfplus) { + if (!is.null(ratios) & isFALSE(sfplus)) { + msg_sfplus_required("ratios", "Specifying") } + checkmate::assert_logical( + ratios, + any.missing = FALSE, + len = 1L, + null.ok = TRUE + ) } +#' @importFrom checkmate assert_choice +check_type <- function(type) { + checkmate::assert_choice( + type, + choices = c("common", "wa-basic", "wa-diluted"), + fmatch = TRUE + ) +} -#' @param api_key See function using this argument. -#' @param cache_dir See function using this argument. -#' @param ticker See function using this argument. -#' @param simfin_id See function using this argument. -#' @param statement See function using this argument. -#' @param period See function using this argument. -#' @param fyear See function using this argument. -#' @param start See function using this argument. -#' @param end See function using this argument. -#' @param ttm See function using this argument. -#' @param shares See function using this argument. -#' @param ratios See function using this argument. -#' @param type See function using this argument. +#' @importFrom checkmate assert_choice +check_ref_data <- function(ref_data) { + checkmate::assert_choice( + ref_data, + choices = c("industries", "markets"), + fmatch = TRUE + ) +} diff --git a/R/param_doc.R b/R/param_doc.R index 48d117b..22ab282 100644 --- a/R/param_doc.R +++ b/R/param_doc.R @@ -7,6 +7,9 @@ #' @param cache_dir [character] Your cache directory. It's recommended to set #' the cache directory globally using [sfa_set_cache_dir]. #' +#' @param sfplus [logical] Set`TRUE` if you have a SimFin+ account. It's +#' recommended to set `sfplus` globally using [sfa_set_sfplus]. +#' #' @param ticker [integer] Ticker of the companies of interest. #' #' @param simfin_id [integer] 'SimFin' IDs of the companies of interest. Any @@ -45,10 +48,26 @@ #' #' @param fyear [integer] Filter for fiscal year. As a non-SimFin+ user, you #' have to provide exactly one fiscal year. As SimFin+ user, this filter can -#' be omitted to retrieve data available for the company. You can also chain -#' this filter with a comma, to retrieve multiple years at once (e.g. `fyear = -#' "2015,2016,2017"` to retrieve the data for 3 years at once). +#' be omitted to retrieve all data available for the company. +#' +#' @param ratios [logical] With `TRUE`, you can display some price related +#' ratios along with the share price data (reserved for SimFin+ users). The +#' ratios that will be displayed are: #' +#' - Market-Cap +#' - Price to Earnings Ratio (quarterly) +#' - Price to Earnings Ratio (ttm) +#' - Price to Sales Ratio (quarterly) +#' - Price to Sales Ratio (ttm) +#' - Price to Book Value (ttm) +#' - Price to Free Cash Flow (quarterly) +#' - Price to Free Cash Flow (ttm) +#' - Enterprise Value (ttm) +#' - EV/EBITDA (ttm) +#' - EV/Sales (ttm) +#' - EV/FCF (ttm) +#' - Book to Market Value (ttm) +#' - Operating Income/EV (ttm). #' #' @section Parallel processing: #' This function supports parallel processing via `future.apply`. If your diff --git a/R/sfa_get_entities.R b/R/sfa_get_entities.R index a3c9c33..b2bfefe 100644 --- a/R/sfa_get_entities.R +++ b/R/sfa_get_entities.R @@ -6,7 +6,8 @@ sfa_get_entities <- function( api_key = getOption("sfa_api_key"), cache_dir = getOption("sfa_cache_dir") ) { - check_inputs(api_key = api_key, cache_dir = cache_dir) + check_api_key(api_key) + check_cache_dir(cache_dir) response_light <- call_api( path = list("api/v2/companies/list/"), diff --git a/R/sfa_get_info.R b/R/sfa_get_info.R index 813b59d..1b1bc38 100644 --- a/R/sfa_get_info.R +++ b/R/sfa_get_info.R @@ -1,12 +1,5 @@ -#' Get basic company information -#' @description Internal function. -#' @param ticker [integer] Ticker of the companies of interest. -#' @param api_key `[character(1)]` Your 'SimFin' API key. For simplicity use -#' `options(sfa_api_key = "yourapikey")`. -#' @param cache_dir [character] Your cache directory. It's recommended to set -#' the cache directory globally using [sfa_set_cache_dir]. #' @importFrom data.table as.data.table -sfa_get_info_ <- function(ticker, api_key, cache_dir) { +sfa_get_info_ <- function(ticker, api_key, cache_dir, sfplus) { response_light <- call_api( path = list("api/v2/companies/general"), @@ -40,46 +33,65 @@ sfa_get_info_ <- function(ticker, api_key, cache_dir) { } #' Get basic company information -#' @param ticker [integer] Ticker of the companies of interest. -#' @param simfin_id [integer] 'SimFin' IDs of the companies of interest. Any -#' `simfin_id` will be internally translated to the respective `ticker`. This -#' reduces the number of queries in case you query the same company via -#' `ticker` *and* `simfin_id`. -#' @param api_key [character] Your 'SimFin' API key. It's recommended to set -#' the API key globally using [sfa_set_api_key]. -#' @param cache_dir [character] Your cache directory. It's recommended to set -#' the cache directory globally using [sfa_set_cache_dir]. +#' @inheritParams param_doc +#' #' @importFrom checkmate assert_character assert_integerish assert_string #' assert_directory #' @importFrom future.apply future_lapply #' @importFrom progressr with_progress progressor +#' #' @export +#' sfa_get_info <- function( ticker = NULL, simfin_id = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) { - check_inputs( + + # input checks + check_sfplus(sfplus) + check_ticker(ticker) + check_simfin_id(simfin_id) + check_api_key(api_key) + check_cache_dir(cache_dir) + + # if (all(is.null(ticker), is.null(simfin_id))) { + # stop("You need to specify at least one 'ticker' or 'simfin_id") + # } + + # translate simfin_id to ticker to simplify API call + ticker <- gather_ticker( ticker = ticker, simfin_id = simfin_id, api_key = api_key, cache_dir = cache_dir ) - if (all(is.null(ticker), is.null(simfin_id))) { - stop("You need to specify at least one 'ticker' or 'simfin_id") - } - # translate simfin_id to ticker to simplify API call - ticker <- gather_ticker(ticker, simfin_id, api_key, cache_dir) + if (isTRUE(sfplus)) { # SimFin+ users make a single API call + results <- sfa_get_info_( + ticker = paste(ticker, collapse = ","), + api_key = api_key, + cache_dir = cache_dir, + sfplus = sfplus + ) - progressr::with_progress({ - prg <- progressr::progressor(along = ticker) - result_list <- future.apply::future_lapply(ticker, function(x) { - prg(x) - sfa_get_info_(ticker = x, api_key, cache_dir) - }, - future.seed = TRUE) - }) - gather_result(result_list) + } else { # normal users make several API calls + progressr::with_progress({ + prg <- progressr::progressor(along = ticker) + results <- future.apply::future_lapply(ticker, function(x) { + prg(x) + sfa_get_info_( + ticker = x, + api_key = api_key, + cache_dir = cache_dir, + sfplus = sfplus + ) + }, + future.seed = TRUE + ) + }) + } + gather_result(results) } diff --git a/R/sfa_get_prices.R b/R/sfa_get_prices.R index 98a9331..6b9b7a2 100644 --- a/R/sfa_get_prices.R +++ b/R/sfa_get_prices.R @@ -1,11 +1,12 @@ #' @importFrom data.table as.data.table setnames set rbindlist setcolorder sfa_get_prices_ <- function( ticker, - ratios, - start, - end, + ratios = NULL, + start = NULL, + end = NULL, api_key, - cache_dir + cache_dir, + sfplus ) { query_list <- list( "ticker" = ticker, @@ -82,25 +83,6 @@ sfa_get_prices_ <- function( #' #' @inheritParams param_doc #' -#' @param ratios [logical] With `TRUE`, you can display some price related -#' ratios along with the share price data (reserved for SimFin+ users). The -#' ratios that will be displayed are: -#' -#' - Market-Cap -#' - Price to Earnings Ratio (quarterly) -#' - Price to Earnings Ratio (ttm) -#' - Price to Sales Ratio (quarterly) -#' - Price to Sales Ratio (ttm) -#' - Price to Book Value (ttm) -#' - Price to Free Cash Flow (quarterly) -#' - Price to Free Cash Flow (ttm) -#' - Enterprise Value (ttm) -#' - EV/EBITDA (ttm) -#' - EV/Sales (ttm) -#' - EV/FCF (ttm) -#' - Book to Market Value (ttm) -#' - Operating Income/EV (ttm). -#' #' @inheritSection param_doc Parallel processing #' #' @importFrom future.apply future_lapply @@ -115,31 +97,36 @@ sfa_get_prices <- function( start = NULL, end = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) { - check_inputs( - ticker = ticker, - simfin_id = simfin_id, - ratios = ratios, - start = start, - end = end, - api_key = api_key, - cache_dir = cache_dir - ) + check_sfplus(sfplus) + check_ticker(ticker) + check_simfin_id(simfin_id) + check_ratios(ratios, sfplus) + check_start(start, sfplus) + check_end(end, sfplus) + check_api_key(api_key) + check_cache_dir(cache_dir) ticker <- gather_ticker(ticker, simfin_id, api_key, cache_dir) - if (length(ticker) == 0L) return(invisible(NULL)) - - progressr::with_progress({ - prg <- progressr::progressor(along = ticker) - result_list <- future.apply::future_lapply(ticker, function(x) { - prg(x) - sfa_get_prices_(ticker = x, ratios, start, end, api_key, cache_dir) - }, - future.seed = TRUE) - }) + if (length(ticker) == 0L) return(invisible(NULL)) # can I delete this since gather_ticker throws an error if there is no valid ticker / simfin_id? - gather_result(result_list) + if (isTRUE(sfplus)) { + results <- sfa_get_prices_( + paste(ticker, collapse = ","), ratios, start, end, api_key, cache_dir, sfplus + ) + } else { + progressr::with_progress({ + prg <- progressr::progressor(along = ticker) + results <- future.apply::future_lapply(ticker, function(x) { + prg(x) + sfa_get_prices_(ticker = x, ratios, start, end, api_key, cache_dir) + }, + future.seed = TRUE) + }) + } + gather_result(results) } diff --git a/R/sfa_get_ref.R b/R/sfa_get_ref.R index 6fcefaa..4e91ca3 100644 --- a/R/sfa_get_ref.R +++ b/R/sfa_get_ref.R @@ -16,7 +16,8 @@ #' sfa_get_ref <- function(ref_data, api_key = getOption("sfa_api_key")) { - check_inputs(ref_data = ref_data) + check_ref_data(ref_data) + check_api_key(api_key) temp_zip <- tempfile(fileext = ".zip") utils::download.file( diff --git a/R/sfa_get_shares.R b/R/sfa_get_shares.R index 4f55614..202d3ec 100644 --- a/R/sfa_get_shares.R +++ b/R/sfa_get_shares.R @@ -1,4 +1,5 @@ #' @importFrom data.table as.data.table setnames set setcolorder rbindlist +#' @importFrom bit64 as.integer64 sfa_get_shares_ <- function( ticker, type, @@ -7,7 +8,8 @@ sfa_get_shares_ <- function( start, end, api_key, - cache_dir + cache_dir, + sfplus ) { response_light <- call_api( @@ -24,6 +26,7 @@ sfa_get_shares_ <- function( cache_dir = cache_dir ) content <- response_light[["content"]] + type_name <- paste0("Shares Outstanding (", type, ")") # lapply necessary for SimFin+, where larger queries are possible DT_list <- lapply(content, function(x) { @@ -34,6 +37,8 @@ sfa_get_shares_ <- function( DT <- as.data.table( matrix(unlist(x[["data"]]), ncol = length(x[["columns"]]), byrow = TRUE) ) + val_col <- + x[["columns"]][x[["columns"]] == "Value"] <- type_name data.table::setnames(DT, x[["columns"]]) }) @@ -44,8 +49,11 @@ sfa_get_shares_ <- function( # prettify DT set_as(DT, "SimFinId", as.integer) - set_as(DT, "Date", as.Date) - set_as(DT, "Value", as.numeric) + if ("Date" %in% names(DT)) set_as(DT, "Date", as.Date) + if ("Fiscal Year" %in% names(DT)) set_as(DT, "Fiscal Year", as.integer) + if ("Report Date" %in% names(DT)) set_as(DT, "Report Date", as.Date) + if ("TTM" %in% names(DT)) set_as(DT, "TTM", as.logical) + set_as(DT, type_name, bit64::as.integer64) return(DT) } @@ -73,8 +81,9 @@ sfa_get_shares_ <- function( #' @inheritSection param_doc Parallel processing #' #' @importFrom checkmate assert_choice -#' @importFrom future.apply future_lapply +#' @importFrom future.apply future_mapply #' @importFrom progressr with_progress progressor +#' @importFrom data.table year CJ #' #' @export #' @@ -87,33 +96,57 @@ sfa_get_shares <- function( start = NULL, end = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) { - check_inputs( - ticker = ticker, - simfin_id = simfin_id, - type = type, - period = period, - fyear = fyear, - start = start, - end = end, - api_key = api_key, - cache_dir = cache_dir - ) + check_sfplus(sfplus) + check_ticker(ticker) + check_simfin_id(simfin_id) + check_type(type) + check_period(period, sfplus) + check_fyear(fyear, sfplus) + check_start(start, sfplus) + check_end(end, sfplus) + check_api_key(api_key) + check_cache_dir(cache_dir) ticker <- gather_ticker(ticker, simfin_id, api_key, cache_dir) - progressr::with_progress({ - prg <- progressr::progressor(along = ticker) - result_list <- future.apply::future_lapply(ticker, function(x) { - prg(x) - sfa_get_shares_( - ticker = x, type, period, fyear, start, end, api_key, cache_dir - ) - }, - future.seed = TRUE) - }) + if (isTRUE(sfplus)) { + results <- sfa_get_shares_( + paste(ticker, collapse = ","), + type, period, + paste(fyear, collapse = ","), + start, end, api_key, cache_dir, sfplus + ) + } else { + progressr::with_progress({ + if (type == "common") { + grid <- data.table::CJ( + ticker = ticker, + fyear = data.table::year(Sys.Date()) + # data.table::year(Sys.Date()) is a placeholder, because fyear is only + # relevant for types "wa-basic" and "wa-diluted" + ) + } else { + grid <- data.table::CJ(ticker = ticker, fyear = fyear) + } - gather_result(result_list) + prg <- progressr::progressor(steps = nrow(grid)) + results <- future.apply::future_mapply( + function(ticker, fyear) { + prg(ticker) + sfa_get_shares_( + ticker, type, period, fyear, start, end, api_key, cache_dir, sfplus + ) + }, + ticker = grid[["ticker"]], + fyear = grid[["fyear"]], + SIMPLIFY = FALSE, + future.seed = TRUE + ) + }) + } + gather_result(results) } diff --git a/R/sfa_get_statement.R b/R/sfa_get_statement.R index d8cdc8e..039941b 100644 --- a/R/sfa_get_statement.R +++ b/R/sfa_get_statement.R @@ -10,7 +10,8 @@ sfa_get_statement_ <- function( ttm, shares, api_key, - cache_dir + cache_dir, + sfplus ) { # hack ttm and statement into the query since GET cannot handle such # parameters (at least I don't know how) @@ -112,8 +113,9 @@ sfa_get_statement_ <- function( #' @inheritSection param_doc Parallel processing #' #' @importFrom checkmate assert_choice -#' @importFrom future.apply future_lapply +#' @importFrom future.apply future_mapply #' @importFrom progressr with_progress progressor +#' @importFrom data.table year CJ #' #' @export sfa_get_statement <- function( @@ -127,34 +129,84 @@ sfa_get_statement <- function( ttm = FALSE, shares = FALSE, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) { - check_inputs( - ticker = ticker, - simfin_id = simfin_id, - statement = statement, - period = period, - fyear = fyear, - start = start, - end = end, - api_key = api_key, - cache_dir = cache_dir - ) + check_sfplus(sfplus) # check sfplus first, since it's needed for other checks + check_ticker(ticker) + check_simfin_id(simfin_id) + check_statement(statement, sfplus) + check_period(period, sfplus) + check_fyear(fyear, sfplus) + check_start(start, sfplus) + check_end(end, sfplus) + check_ttm(ttm) + check_shares(shares, sfplus) + check_api_key(api_key) + check_cache_dir(cache_dir) ticker <- gather_ticker(ticker, simfin_id, api_key, cache_dir) - if (!is.null(fyear)) fyear <- paste(fyear, collapse = ",") - - progressr::with_progress({ - prg <- progressr::progressor(along = ticker) - result_list <- future.apply::future_lapply(ticker, function(x) { - prg(x) - sfa_get_statement_( - ticker = x, statement, period, fyear, start, end, ttm, shares, api_key, - cache_dir + # if (!is.null(fyear)) fyear <- paste(fyear, collapse = ",") + + if (isTRUE(sfplus)) { + results <- sfa_get_statement_( + ticker = paste(ticker, collapse = ","), + statement = statement, + period = ifelse(is.null(period), NULL, paste(period, collapse = ",")), + fyear = {if (is.null(fyear)) NULL else paste(fyear, collapse = ",")}, + start = start, + end = end, + ttm = ttm, + shares = shares, + api_key = api_key, + cache_dir = cache_dir, + sfplus = sfplus + ) + } else { + progressr::with_progress({ + grid <- data.table::CJ( + ticker = ticker, + period = period, + fyear = fyear ) - }, - future.seed = TRUE) - }) - gather_result(result_list) + prg <- progressr::progressor(steps = nrow(grid)) + results <- future.apply::future_mapply( + function(ticker, period, fyear) { + prg(ticker) + sfa_get_statement_( + ticker = ticker, + statement = statement, + period = period, + fyear = fyear, + start = start, + end = end, + ttm = ttm, + shares = shares, + api_key = api_key, + cache_dir = cache_dir, + sfplus = sfplus + ) + }, + ticker = grid[["ticker"]], + period = grid[["period"]], + fyear = grid[["fyear"]], + SIMPLIFY = FALSE, + future.seed = TRUE + ) + }) + } + # progressr::with_progress({ + # prg <- progressr::progressor(along = ticker) + # result_list <- future.apply::future_lapply(ticker, function(x) { + # prg(x) + # sfa_get_statement_( + # ticker = x, statement, period, fyear, start, end, ttm, shares, api_key, + # cache_dir + # ) + # }, + # future.seed = TRUE) + # }) + + gather_result(results) } diff --git a/R/sfa_set_sfplus.R b/R/sfa_set_sfplus.R new file mode 100644 index 0000000..23521fe --- /dev/null +++ b/R/sfa_set_sfplus.R @@ -0,0 +1,21 @@ +#' Specify the type of you SimFin account +#' @description If you have a SimFin+ account, it is highly recommended to +#' specify this globally as it makes specifying the `sfplus` argument of other +#' `sfa_*` functions obsolete. +#' +#' You don't need this function if you don't have a SimFin+ account. +#' @param sfplus [logical] Defaults to `TRUE` to specify that you have a SimFin+ +#' account. +#' @examples +#' \dontrun{ +#' # Tell simfinapi that you have a SimFin+ account +#' sfa_set_sfplus() +#' } +#' @details There is no good reason to use `sfa_set_sfplus(FALSE)` as all +#' functions assume this by default. +#' @importFrom checkmate assert_logical +#' @export +sfa_set_sfplus <- function(sfplus = TRUE) { + checkmate::assert_logical(sfplus, len = 1L) + options(sfa_sfplus = sfplus) +} diff --git a/R/utils.R b/R/utils.R index 4142ed9..4a3f7e8 100644 --- a/R/utils.R +++ b/R/utils.R @@ -11,7 +11,10 @@ gather_ticker <- function(ticker, simfin_id, api_key, cache_dir) { valid_ids <- unique(c(valid_tickers, simfin_id_as_ticker)) if (length(valid_ids) == 0L) { - return(invisible(NULL)) + stop( + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + call. = FALSE + ) } return(valid_ids) } @@ -38,12 +41,16 @@ set_as <- function(DT, vars, as) { } } -#' @importFrom data.table rbindlist setkeyv -gather_result <- function(result_list) { - if (all(vapply(result_list, is.null, FUN.VALUE = logical(1L)))) { +#' @importFrom data.table is.data.table rbindlist setkeyv +gather_result <- function(results) { + if (all(vapply(results, is.null, FUN.VALUE = logical(1L)))) { return(invisible(NULL)) } - result_DT <- data.table::rbindlist(result_list, fill = TRUE) + if (data.table::is.data.table(results)) { + result_DT <- results + } else { + result_DT <- data.table::rbindlist(results, fill = TRUE) + } set_clean_names(result_DT) data.table::setkeyv(result_DT, "ticker") result_DT[] diff --git a/man/check_inputs.Rd b/man/check_inputs.Rd deleted file mode 100644 index d37a4c3..0000000 --- a/man/check_inputs.Rd +++ /dev/null @@ -1,124 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/check_inputs.R -\name{check_inputs} -\alias{check_inputs} -\title{Generic input checks} -\usage{ -check_inputs( - api_key = NULL, - cache_dir = NULL, - ticker = NULL, - simfin_id = NULL, - statement = NULL, - period = NULL, - fyear = NULL, - start = NULL, - end = NULL, - ttm = NULL, - shares = NULL, - ratios = NULL, - type = NULL, - ref_data = NULL -) -} -\arguments{ -\item{api_key}{\link{character} Your 'SimFin' API key. It's recommended to set -the API key globally using \link{sfa_set_api_key}.} - -\item{cache_dir}{\link{character} Your cache directory. It's recommended to set -the cache directory globally using \link{sfa_set_cache_dir}.} - -\item{ticker}{\link{integer} Ticker of the companies of interest.} - -\item{simfin_id}{\link{integer} 'SimFin' IDs of the companies of interest. Any -\code{simfin_id} will be internally translated to the respective \code{ticker}. This -reduces the number of queries in case you query the same company via -\code{ticker} \emph{and} \code{simfin_id}.} - -\item{statement}{\link{character} Statement to be retrieved. One of -\itemize{ -\item \code{"pl"}: Profit & Loss statement -\item \code{"bs"}: Balance Sheet -\item \code{"cf"}: Cash Flow statement -\item \code{"derived"}: Derived figures & fundamental ratios -\item \code{"all"}: Retrieves all 3 statements + ratios. Please note that this -option is reserved for SimFin+ users. -}} - -\item{period}{\link{character} Filter for periods. As a non-SimFin+ user, you have -to provide exactly one period. As SimFin+ user, this filter can be omitted -to retrieve all statements available for the company. -\itemize{ -\item \code{"q1"}: First fiscal quarter. -\item \code{"q2"}: Second fiscal quarter. -\item \code{"q3"}: Third fiscal quarter. -\item \code{"q4"}: Fourth fiscal quarter. -\item \code{"fy"}: Full fiscal year. -\item \code{"h1"}: First 6 months of fiscal year. -\item \code{"h2"}: Last 6 months of fiscal year. -\item \code{"9m"}: First nine months of fiscal year. -\item \code{"6m"}: Any fiscal 6 month period (first + second half years; reserved -for SimFin+ users). -\item \code{"quarters"}: All quarters (q1 + q2 + q3 + q4; reserved for SimFin+ -users). -}} - -\item{fyear}{\link{integer} Filter for fiscal year. As a non-SimFin+ user, you -have to provide exactly one fiscal year. As SimFin+ user, this filter can -be omitted to retrieve data available for the company. You can also chain -this filter with a comma, to retrieve multiple years at once (e.g. \code{fyear = "2015,2016,2017"} to retrieve the data for 3 years at once).} - -\item{start}{\link{Date} Filter for the report dates (reserved for SimFin+ users). -With this filter you can filter the statements by the date on which the -reported period ended ('Report Date'). By specifying a value here, only -statements will be retrieved with report dates ending AFTER the specified -date.} - -\item{end}{\link{Date} Filter for the report dates (reserved for SimFin+ users). -With this filter you can filter the statements by the date on which the -reported period ended ('Report Date'). By specifying a value here, only -statements will be retrieved with report dates ending BEFORE the specified -date.} - -\item{ttm}{\link{logical} If \code{TRUE}, you can return the trailing twelve months -statements for all periods, meaning at every available point in time the -sum of the last 4 available quarterly figures.} - -\item{shares}{\link{logical} If \code{TRUE}, you can display the weighted average basic -& diluted shares outstanding for each period along with the fundamentals. -Reserved for SimFin+ users (as non-SimFin+ user, you can still use the -shares outstanding endpoints).} - -\item{ratios}{\link{logical} With \code{TRUE}, you can display some price related -ratios along with the share price data (reserved for SimFin+ users). The -ratios that will be displayed are: -\itemize{ -\item Market-Cap -\item Price to Earnings Ratio (quarterly) -\item Price to Earnings Ratio (ttm) -\item Price to Sales Ratio (quarterly) -\item Price to Sales Ratio (ttm) -\item Price to Book Value (ttm) -\item Price to Free Cash Flow (quarterly) -\item Price to Free Cash Flow (ttm) -\item Enterprise Value (ttm) -\item EV/EBITDA (ttm) -\item EV/Sales (ttm) -\item EV/FCF (ttm) -\item Book to Market Value (ttm) -\item Operating Income/EV (ttm). -}} - -\item{type}{\link{character} Type of shares outstanding to be retrieved. -\itemize{ -\item \code{"common"}: Common shares outstanding. -\item \code{"wa-basic"}: Weighted average basic shares outstanding for a period. -\item \code{"wa-diluted"}: Weighted average diluted shares outstanding for a period. -}} - -\item{ref_data}{\link{character} Either "industries" or "markets".} -} -\description{ -This function covers all kinds of (recurring) input checks in -{simfinapi}. This keeps the other functions cleaner. -} diff --git a/man/param_doc.Rd b/man/param_doc.Rd index 1783741..f2258bb 100644 --- a/man/param_doc.Rd +++ b/man/param_doc.Rd @@ -10,6 +10,9 @@ the API key globally using \link{sfa_set_api_key}.} \item{cache_dir}{\link{character} Your cache directory. It's recommended to set the cache directory globally using \link{sfa_set_cache_dir}.} +\item{sfplus}{\link{logical} Set\code{TRUE} if you have a SimFin+ account. It's +recommended to set \code{sfplus} globally using \link{sfa_set_sfplus}.} + \item{ticker}{\link{integer} Ticker of the companies of interest.} \item{simfin_id}{\link{integer} 'SimFin' IDs of the companies of interest. Any @@ -49,8 +52,27 @@ users). \item{fyear}{\link{integer} Filter for fiscal year. As a non-SimFin+ user, you have to provide exactly one fiscal year. As SimFin+ user, this filter can -be omitted to retrieve data available for the company. You can also chain -this filter with a comma, to retrieve multiple years at once (e.g. \code{fyear = "2015,2016,2017"} to retrieve the data for 3 years at once).} +be omitted to retrieve all data available for the company.} + +\item{ratios}{\link{logical} With \code{TRUE}, you can display some price related +ratios along with the share price data (reserved for SimFin+ users). The +ratios that will be displayed are: +\itemize{ +\item Market-Cap +\item Price to Earnings Ratio (quarterly) +\item Price to Earnings Ratio (ttm) +\item Price to Sales Ratio (quarterly) +\item Price to Sales Ratio (ttm) +\item Price to Book Value (ttm) +\item Price to Free Cash Flow (quarterly) +\item Price to Free Cash Flow (ttm) +\item Enterprise Value (ttm) +\item EV/EBITDA (ttm) +\item EV/Sales (ttm) +\item EV/FCF (ttm) +\item Book to Market Value (ttm) +\item Operating Income/EV (ttm). +}} } \description{ Parameter documentation diff --git a/man/sfa_get_info.Rd b/man/sfa_get_info.Rd index 2bea57b..621e32f 100644 --- a/man/sfa_get_info.Rd +++ b/man/sfa_get_info.Rd @@ -8,7 +8,8 @@ sfa_get_info( ticker = NULL, simfin_id = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) } \arguments{ @@ -24,6 +25,9 @@ the API key globally using \link{sfa_set_api_key}.} \item{cache_dir}{\link{character} Your cache directory. It's recommended to set the cache directory globally using \link{sfa_set_cache_dir}.} + +\item{sfplus}{\link{logical} Set\code{TRUE} if you have a SimFin+ account. It's +recommended to set \code{sfplus} globally using \link{sfa_set_sfplus}.} } \description{ Get basic company information diff --git a/man/sfa_get_info_.Rd b/man/sfa_get_info_.Rd deleted file mode 100644 index 4753d17..0000000 --- a/man/sfa_get_info_.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sfa_get_info.R -\name{sfa_get_info_} -\alias{sfa_get_info_} -\title{Get basic company information} -\usage{ -sfa_get_info_(ticker, api_key, cache_dir) -} -\arguments{ -\item{ticker}{\link{integer} Ticker of the companies of interest.} - -\item{api_key}{\verb{[character(1)]} Your 'SimFin' API key. For simplicity use -\code{options(sfa_api_key = "yourapikey")}.} - -\item{cache_dir}{\link{character} Your cache directory. It's recommended to set -the cache directory globally using \link{sfa_set_cache_dir}.} -} -\description{ -Internal function. -} diff --git a/man/sfa_get_prices.Rd b/man/sfa_get_prices.Rd index 52fd8af..6865e86 100644 --- a/man/sfa_get_prices.Rd +++ b/man/sfa_get_prices.Rd @@ -11,7 +11,8 @@ sfa_get_prices( start = NULL, end = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) } \arguments{ @@ -59,6 +60,9 @@ the API key globally using \link{sfa_set_api_key}.} \item{cache_dir}{\link{character} Your cache directory. It's recommended to set the cache directory globally using \link{sfa_set_cache_dir}.} + +\item{sfplus}{\link{logical} Set\code{TRUE} if you have a SimFin+ account. It's +recommended to set \code{sfplus} globally using \link{sfa_set_sfplus}.} } \description{ Share price data and ratios can be retrieved here. All share diff --git a/man/sfa_get_shares.Rd b/man/sfa_get_shares.Rd index 9514156..6275bd2 100644 --- a/man/sfa_get_shares.Rd +++ b/man/sfa_get_shares.Rd @@ -13,7 +13,8 @@ sfa_get_shares( start = NULL, end = NULL, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) } \arguments{ @@ -51,8 +52,7 @@ users). \item{fyear}{\link{integer} Filter for fiscal year. As a non-SimFin+ user, you have to provide exactly one fiscal year. As SimFin+ user, this filter can -be omitted to retrieve data available for the company. You can also chain -this filter with a comma, to retrieve multiple years at once (e.g. \code{fyear = "2015,2016,2017"} to retrieve the data for 3 years at once).} +be omitted to retrieve all data available for the company.} \item{start}{\link{Date} Filter for the report dates (reserved for SimFin+ users). With this filter you can filter the statements by the date on which the @@ -71,6 +71,9 @@ the API key globally using \link{sfa_set_api_key}.} \item{cache_dir}{\link{character} Your cache directory. It's recommended to set the cache directory globally using \link{sfa_set_cache_dir}.} + +\item{sfplus}{\link{logical} Set\code{TRUE} if you have a SimFin+ account. It's +recommended to set \code{sfplus} globally using \link{sfa_set_sfplus}.} } \description{ Common shares outstanding (point-in-time) and weighted average diff --git a/man/sfa_get_statement.Rd b/man/sfa_get_statement.Rd index 6ae1d22..24c5bee 100644 --- a/man/sfa_get_statement.Rd +++ b/man/sfa_get_statement.Rd @@ -15,7 +15,8 @@ sfa_get_statement( ttm = FALSE, shares = FALSE, api_key = getOption("sfa_api_key"), - cache_dir = getOption("sfa_cache_dir") + cache_dir = getOption("sfa_cache_dir"), + sfplus = getOption("sfa_sfplus", default = FALSE) ) } \arguments{ @@ -56,8 +57,7 @@ users). \item{fyear}{\link{integer} Filter for fiscal year. As a non-SimFin+ user, you have to provide exactly one fiscal year. As SimFin+ user, this filter can -be omitted to retrieve data available for the company. You can also chain -this filter with a comma, to retrieve multiple years at once (e.g. \code{fyear = "2015,2016,2017"} to retrieve the data for 3 years at once).} +be omitted to retrieve all data available for the company.} \item{start}{\link{Date} Filter for the report dates (reserved for SimFin+ users). With this filter you can filter the statements by the date on which the @@ -85,6 +85,9 @@ the API key globally using \link{sfa_set_api_key}.} \item{cache_dir}{\link{character} Your cache directory. It's recommended to set the cache directory globally using \link{sfa_set_cache_dir}.} + +\item{sfplus}{\link{logical} Set\code{TRUE} if you have a SimFin+ account. It's +recommended to set \code{sfplus} globally using \link{sfa_set_sfplus}.} } \value{ \link{data.table} containing the statement(s) data. diff --git a/man/sfa_set_sfplus.Rd b/man/sfa_set_sfplus.Rd new file mode 100644 index 0000000..f4c3045 --- /dev/null +++ b/man/sfa_set_sfplus.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sfa_set_sfplus.R +\name{sfa_set_sfplus} +\alias{sfa_set_sfplus} +\title{Specify the type of you SimFin account} +\usage{ +sfa_set_sfplus(sfplus = TRUE) +} +\arguments{ +\item{sfplus}{\link{logical} Defaults to \code{TRUE} to specify that you have a SimFin+ +account.} +} +\description{ +If you have a SimFin+ account, it is highly recommended to +specify this globally as it makes specifying the \code{sfplus} argument of other +\verb{sfa_*} functions obsolete. + +You don't need this function if you don't have a SimFin+ account. +} +\details{ +There is no good reason to use \code{sfa_set_sfplus(FALSE)} as all +functions assume this by default. +} +\examples{ +\dontrun{ +# Tell simfinapi that you have a SimFin+ account +sfa_set_sfplus() +} +} diff --git a/tests/testthat/helper-options.R b/tests/testthat/helper-options.R index 6068cf3..386ed68 100644 --- a/tests/testthat/helper-options.R +++ b/tests/testthat/helper-options.R @@ -1,2 +1,3 @@ -options(sfa_api_key = Sys.getenv("SIMFIN_API_KEY")) +options(sfa_api_key = Sys.getenv("SF_API_KEY")) +options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) options(sfa_cache_dir = tempdir()) diff --git a/tests/testthat/test-call_api.R b/tests/testthat/test-call_api.R index 5e6f24f..7a4d99b 100644 --- a/tests/testthat/test-call_api.R +++ b/tests/testthat/test-call_api.R @@ -1,7 +1,11 @@ -test_that("warning is issued if cache is not set yet", { +test_that("setting temporary cache dir works", { expect_warning( - sfa_get_entities(cache_dir = NULL), - "'cache_dir' not set.", + call_api( + path = list("api/v2/companies/list/"), + query = list("api-key" = Sys.getenv("SFPLUS_API_KEY")), + cache_dir = NULL + ), + "'cache_dir' not set. Defaulting to 'tempdir()'. Thus, API results will only be cached during this session. To learn why and how to cache results over the end of this session, see `?sfa_set_cache_dir`.\n\n[This warning appears only once per session.]", fixed = TRUE ) }) diff --git a/tests/testthat/test-sfa_get_info.R b/tests/testthat/test-sfa_get_info.R index 2c24712..5e0fa49 100644 --- a/tests/testthat/test-sfa_get_info.R +++ b/tests/testthat/test-sfa_get_info.R @@ -1,6 +1,4 @@ -library(data.table) - -ref <- data.table( +ref <- data.table::data.table( simfin_id = c(18L, 59265L), ticker = c("GOOG", "MSFT"), company_name = c("Alphabet (Google)", "MICROSOFT CORP"), @@ -17,63 +15,75 @@ for (i in seq_along(ref)) { setattr(ref[[i]], "label", labels[i]) } -test_that("search via tickers works", { - # Restrict columns to those which are in 'ref'. This way, I don't need to - # update the test so often. - expect_identical( - sfa_get_info(ticker = c("GOOG", "MSFT"))[, names(ref), with = FALSE], - ref - ) - expect_identical( - sfa_get_info(ticker = c("MSFT", "GOOG"))[, names(ref), with = FALSE], - ref - ) -}) - -test_that("search via simfin_ids works", { - expect_identical( - sfa_get_info(simfin_id = c(18L, 59265L))[, names(ref), with = FALSE], - ref - ) - expect_identical( - sfa_get_info(simfin_id = c(18, 59265))[, names(ref), with = FALSE], - ref - ) - expect_identical( - sfa_get_info(simfin_id = c(59265L, 18L))[, names(ref), with = FALSE], - ref - ) -}) +for (sfplus in c(TRUE, FALSE)) { + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } -test_that("search via ticker and simfin_id works", { - expect_identical( - sfa_get_info(ticker = "GOOG", simfin_id = 59265L)[, names(ref), with = FALSE], - ref - ) - expect_identical( - sfa_get_info(ticker = "MSFT", simfin_id = 18)[, names(ref), with = FALSE], - ref - ) -}) + test_that("search via tickers works", { + # Restrict columns to those which are in 'ref'. This way, I don't need to + # update the test so often. + expect_identical( + sfa_get_info(ticker = c("GOOG", "MSFT"))[, names(ref), with = FALSE], + ref + ) + expect_identical( + sfa_get_info(ticker = c("MSFT", "GOOG"))[, names(ref), with = FALSE], + ref + ) + }) + test_that("search via simfin_ids works", { + expect_identical( + sfa_get_info(simfin_id = c(18L, 59265L))[, names(ref), with = FALSE], + ref + ) + expect_identical( + sfa_get_info(simfin_id = c(18, 59265))[, names(ref), with = FALSE], + ref + ) + expect_identical( + sfa_get_info(simfin_id = c(59265L, 18L))[, names(ref), with = FALSE], + ref + ) + }) -test_that("search for non-existent ticker / simfin_id yields warning", { - expect_warning( - expect_null(sfa_get_info("does_not_exist")), - 'No company found for ticker `does_not_exist`.', - fixed = TRUE - ) - expect_warning( + test_that("search via ticker and simfin_id works", { expect_identical( - sfa_get_info(simfin_id = c(1L, 18L, 59265L))[, names(ref), with = FALSE], + sfa_get_info(ticker = "GOOG", simfin_id = 59265L)[, names(ref), with = FALSE], ref - ), - "No company found for simfin_id `1`", - fixed = TRUE - ) -}) + ) + expect_identical( + sfa_get_info(ticker = "MSFT", simfin_id = 18)[, names(ref), with = FALSE], + ref + ) + }) -test_that("supplying neiterh ticker / simfin_id yields error", { - expect_error(sfa_get_info()) -}) + test_that("search for non-existent ticker / simfin_id yields warning", { + expect_error( + expect_warning( + sfa_get_info("does_not_exist"), + 'No company found for ticker `does_not_exist`.', + fixed = TRUE + ), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + fixed = TRUE + ) + expect_warning( + expect_identical( + sfa_get_info(simfin_id = c(1L, 18L, 59265L))[, names(ref), with = FALSE], + ref + ), + "No company found for simfin_id `1`", + fixed = TRUE + ) + }) + + test_that("supplying neiterh ticker / simfin_id yields error", { + expect_error(sfa_get_info()) + }) +} diff --git a/tests/testthat/test-sfa_get_prices.R b/tests/testthat/test-sfa_get_prices.R index 8c96a2f..0fbeb7e 100644 --- a/tests/testthat/test-sfa_get_prices.R +++ b/tests/testthat/test-sfa_get_prices.R @@ -7,68 +7,136 @@ ref_classes <- c( names(ref_classes) <- ref_names -ref_1 <- sfa_get_prices("GOOG") -ref_2 <- sfa_get_prices(c("GOOG", "AAPL")) +for (sfplus in c(TRUE, FALSE)) { + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } -test_that("search for single Ticker works", { - checkmate::expect_data_table( - ref_1, - key = "ticker", - types = ref_classes, - ncols = length(ref_names) - ) - expect_named(ref_1, ref_names) - expect_identical(unique(ref_1[["ticker"]]), "GOOG") - expect_identical( - ref_1[["date"]], - `attr<-`(sort(ref_1[["date"]]), "label", "Date") - ) -}) + res_1 <- sfa_get_prices("GOOG") -test_that("search for two Tickers works including correct order", { - checkmate::expect_data_table( - ref_2, - key = "ticker", - types = ref_classes, - ncols = length(ref_names) - ) - expect_named(ref_2, ref_names) - expect_gt(nrow(ref_2), nrow(ref_1)) - expect_identical(unique(ref_2[["ticker"]]), c("AAPL", "GOOG")) - expect_identical( - ref_2[["date"]], - `attr<-`( - Reduce(c, tapply(ref_2[["date"]], ref_2[["ticker"]], sort)), - "label", - "Date" + test_that("search for single Ticker works", { + checkmate::expect_data_table( + res_1, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) ) - ) -}) + expect_named(res_1, ref_names) + expect_identical(unique(res_1[["ticker"]]), "GOOG") + expect_identical( + res_1[["date"]], + `attr<-`(sort(res_1[["date"]]), "label", "Date") + ) + }) + + res_2 <- sfa_get_prices(c("GOOG", "AAPL")) + + test_that("search for two Tickers works including correct order", { + checkmate::expect_data_table( + res_2, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) + ) + expect_named(res_2, ref_names) + expect_gt(nrow(res_2), nrow(res_1)) + expect_identical(unique(res_2[["ticker"]]), c("AAPL", "GOOG")) + expect_identical( + res_2[["date"]], + `attr<-`( + Reduce(c, tapply(res_2[["date"]], res_2[["ticker"]], sort)), + "label", + "Date" + ) + ) + }) + dates <- as.Date(c("2021-01-04", "2021-01-8")) + if (isTRUE(sfplus)) { + res_3 <- sfa_get_prices( + c("GOOG", "AAPL"), + start = dates[1], + end = dates[2] + ) + + test_that("search for two Tickers and start date works", { + checkmate::expect_data_table( + res_3, + key = "ticker", + types = ref_classes, + nrows = 10L, + ncols = length(ref_names) + ) + expect_named(res_3, ref_names) + expect_identical(unique(res_3[["ticker"]]), c("AAPL", "GOOG")) + expect_identical(range(res_3[["date"]]), dates) + expect_identical( + res_3[["date"]], + `attr<-`( + Reduce(c, tapply(res_3[["date"]], res_3[["ticker"]], sort)), + "label", + "Date" + ) + ) + }) + } else { + + expect_error( + sfa_get_prices(c("GOOG", "AAPL"), start = dates[1], end = dates[2]), + "Specifying 'start' is reserved for SimFin+ users.", + fixed = TRUE + ) + } -test_that("sfa_get_price returns null and warnings if ticker not found", { - expect_warning( - expect_null(sfa_get_prices("ZZZZZ")), - "No company found for ticker `ZZZZZ`.", - fixed = TRUE - ) - warnings <- capture_warnings(expect_null(sfa_get_prices(c("ZZZZZ", "ZZZZZZ")))) - expect_identical( - warnings, - paste0("No company found for ticker `", c("ZZZZZ", "ZZZZZZ"), "`.") - ) -}) -test_that("sfa_get_price returns null and warnings if simfin_id not found", { - expect_warning( - expect_null(sfa_get_prices(simfin_id = 1)), - 'No company found for simfin_id `1`.', - fixed = TRUE - ) - warnings <- capture_warnings(expect_null(sfa_get_prices(simfin_id = 1:2))) - expect_identical( - warnings, - paste0('No company found for simfin_id `', 1:2, '`.') - ) -}) + test_that("sfa_get_price returns null and warnings if ticker not found", { + expect_error( + expect_warning( + sfa_get_prices("ZZZZZ"), + "No company found for ticker `ZZZZZ`.", + fixed = TRUE + ), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + fixed = TRUE + ) + warnings <- capture_warnings( + expect_error( + sfa_get_prices(c("ZZZZZ", "ZZZZZZ")), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + fixed = TRUE + )) + expect_identical( + warnings, + paste0("No company found for ticker `", c("ZZZZZ", "ZZZZZZ"), "`.") + ) + }) + + test_that("sfa_get_price returns null and warnings if simfin_id not found", { + expect_error( + expect_warning( + expect_null(sfa_get_prices(simfin_id = 1)), + 'No company found for simfin_id `1`.', + fixed = TRUE + ), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + fixed = TRUE + ) + + warnings <- capture_warnings( + expect_error( + sfa_get_prices(simfin_id = 1:2), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", + fixed = TRUE + ) + ) + expect_identical( + warnings, + paste0('No company found for simfin_id `', 1:2, '`.') + ) + }) + +} diff --git a/tests/testthat/test-sfa_get_shares.R b/tests/testthat/test-sfa_get_shares.R index 8849056..6c3350a 100644 --- a/tests/testthat/test-sfa_get_shares.R +++ b/tests/testthat/test-sfa_get_shares.R @@ -1,3 +1,133 @@ -test_that("multiplication works", { - expect_equal(2 * 2, 4) -}) +for (sfplus in c(TRUE, FALSE)) { + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } + + test_that("sfa_get_prices() works for type = common", { + ref_names <- c("simfin_id", "ticker", "date", "shares_outstanding_common") + ref_classes <- c( + "integer", "character", "Date", "integer64" + ) + names(ref_classes) <- ref_names + + res_1 <- sfa_get_shares("GOOG", type = "common") + + checkmate::expect_data_table( + res_1, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) + ) + expect_named(res_1, ref_names) + expect_identical(unique(res_1[["ticker"]]), "GOOG") + expect_identical( + res_1[["date"]], + `attr<-`(res_1[["date"]], "label", "Date") + ) + + + res_2 <- sfa_get_shares(c("GOOG", "AMZN"), type = "common") + + checkmate::expect_data_table( + res_2, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) + ) + expect_named(res_2, ref_names) + expect_identical(unique(res_2[["ticker"]]), c("AMZN", "GOOG")) + expect_identical( + res_2[["date"]], + `attr<-`(res_2[["date"]], "label", "Date") + ) + + res_3 <- sfa_get_shares(c("GOOG", "AMZN"), type = "common", period = "q1") + expect_identical(res_3, res_2) + # since fyear is only relevant for types "wa-basic" and "wa-diluted" + + res_4 <- sfa_get_shares(c("GOOG", "AMZN"), type = "common", fyear = 2019:2020) + expect_identical(res_4, res_2) + # since fyear is only relevant for types "wa-basic" and "wa-diluted" + + if (isTRUE(sfplus)) { + date <- as.Date("2020-01-01") + res_5 <- sfa_get_shares(c("GOOG", "AMZN"), type = "common", start = date) + checkmate::expect_data_table( + res_5, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) + ) + expect_named(res_5, ref_names) + expect_identical(unique(res_5[["ticker"]]), c("AMZN", "GOOG")) + expect_identical( + res_5[["date"]], + `attr<-`(res_5[["date"]], "label", "Date") + ) + expect_gte(min(res_5[["date"]]), date) + + res_6 <- sfa_get_shares(c("GOOG", "AMZN"), type = "common", end = date) + checkmate::expect_data_table( + res_6, + key = "ticker", + types = ref_classes, + ncols = length(ref_names) + ) + expect_named(res_6, ref_names) + expect_identical(unique(res_6[["ticker"]]), c("AMZN", "GOOG")) + expect_identical( + res_6[["date"]], + `attr<-`(res_6[["date"]], "label", "Date") + ) + expect_lte(max(res_6[["date"]]), date) + + } else { + + date <- as.Date("2020-01-01") + expect_error( + sfa_get_shares(c("GOOG", "AMZN"), type = "common", start = date), + "Specifying 'start' is reserved for SimFin+ users.", + fixed = TRUE + ) + + expect_error( + sfa_get_shares(c("GOOG", "AMZN"), type = "common", end = date), + "Specifying 'end' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + + }) + + for (type in c("wa-basic", "wa-diluted")) { + + # TODO: This needs more tests, but it makes more sense after implementing + # https://github.com/matthiasgomolka/simfinapi/issues/33 + + ref_names <- c( + "simfin_id", "ticker", "fiscal_period", "fiscal_year", "report_date", + "ttm", paste0("shares_outstanding_", sub("-", "_", fixed = TRUE, type)) + ) + ref_classes <- c( + "integer", "character", "character", "integer", "Date", "logical", + "integer64" + ) + names(ref_classes) <- ref_names + + res_7 <- sfa_get_shares("GOOG", type = type) + + checkmate::expect_data_table( + res_7, + key = "ticker", + types = ref_classes, + nrows = 1L, + ncols = length(ref_names) + ) + expect_named(res_7, ref_names) + expect_identical(unique(res_7[["ticker"]]), "GOOG") + } + +} diff --git a/tests/testthat/test-sfa_get_statement.R b/tests/testthat/test-sfa_get_statement.R index 8d50bb5..3c8af49 100644 --- a/tests/testthat/test-sfa_get_statement.R +++ b/tests/testthat/test-sfa_get_statement.R @@ -72,109 +72,148 @@ test_that("getting pl statement works", { ) names(exp_classes) <- clean_names(names(exp_classes)) - ref_1 <- sfa_get_statement("GOOG", statement = "pl", fyear = 2015) - checkmate::expect_data_table( - ref_1, - key = "ticker", - types = exp_classes, - nrows = 1L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1, names(exp_classes)) - - ref_1_plus <- sfa_get_statement("GOOG", statement = "pl") - checkmate::expect_data_table( - ref_1_plus, - key = "ticker", - types = exp_classes, - min.rows = 13L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1_plus, names(exp_classes)) + for (sfplus in c(TRUE, FALSE)) { + exp_classes <- exp_classes[!(names(exp_classes) %in% c("shares_basic", "shares_diluted"))] + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } - ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "pl", fyear = 2015) - checkmate::expect_data_table( - ref_2, - key = "ticker", - types = exp_classes, - nrows = 2L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2, names(exp_classes)) - - ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "pl") - checkmate::expect_data_table( - ref_2_plus, - key = "ticker", - types = exp_classes, - min.rows = 35L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2_plus, names(exp_classes)) + ref_1 <- sfa_get_statement("GOOG", statement = "pl", fyear = 2015) + checkmate::expect_data_table( + ref_1, + key = "ticker", + types = exp_classes, + nrows = 1L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1, names(exp_classes)) - ref_3 <- sfa_get_statement( - c("GOOG", "AAPL"), statement = "pl", ttm = TRUE, fyear = 2015 - ) - checkmate::expect_data_table( - ref_3, - key = "ticker", - types = exp_classes, - nrows = 2L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_3, names(exp_classes)) + if (isTRUE(sfplus)) { - ref_3_plus <- sfa_get_statement( - c("GOOG", "AAPL"), statement = "pl", ttm = TRUE - ) - checkmate::expect_data_table( - ref_3_plus, - key = "ticker", - types = exp_classes, - min.rows = 33L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_3_plus, names(exp_classes)) + ref_1_plus <- sfa_get_statement("GOOG", statement = "pl") + checkmate::expect_data_table( + ref_1_plus, + key = "ticker", + types = exp_classes, + min.rows = 13L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1_plus, names(exp_classes)) - exp_classes <- append( - exp_classes, - c(`Shares (Basic)` = "numeric", `Shares (Diluted)` = "numeric"), - after = which(names(exp_classes) == "currency") - ) - names(exp_classes) <- clean_names(names(exp_classes)) + } else { + expect_error( + sfa_get_statement("GOOG", statement = "pl"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } - ref_4 <- sfa_get_statement( - c("GOOG", "AAPL"), statement = "pl", shares = TRUE, fyear = 2015 - ) - checkmate::expect_data_table( - ref_4, - key = "ticker", - types = exp_classes, - nrows = 2L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_4, names(exp_classes)) - ref_4_plus <- sfa_get_statement( - c("GOOG", "AAPL"), statement = "pl", shares = TRUE - ) - checkmate::expect_data_table( - ref_4_plus, - key = "ticker", - types = exp_classes, - min.rows = 35L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_4_plus, names(exp_classes)) + + ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "pl", fyear = 2015) + checkmate::expect_data_table( + ref_2, + key = "ticker", + types = exp_classes, + nrows = 2L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2, names(exp_classes)) + + + if (isTRUE(sfplus)) { + ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "pl") + checkmate::expect_data_table( + ref_2_plus, + key = "ticker", + types = exp_classes, + min.rows = 35L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2_plus, names(exp_classes)) + } else { + expect_error( + sfa_get_statement(c("GOOG", "AAPL"), statement = "pl"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + + ref_3 <- sfa_get_statement( + c("GOOG", "AAPL"), statement = "pl", ttm = TRUE, fyear = 2015 + ) + checkmate::expect_data_table( + ref_3, + key = "ticker", + types = exp_classes, + nrows = 2L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_3, names(exp_classes)) + + + if (isTRUE(sfplus)) { + + ref_3_plus <- sfa_get_statement( + c("GOOG", "AAPL"), statement = "pl", ttm = TRUE + ) + checkmate::expect_data_table( + ref_3_plus, + key = "ticker", + types = exp_classes, + min.rows = 33L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_3_plus, names(exp_classes)) + + } else { + expect_error( + sfa_get_statement(c("GOOG", "AAPL"), statement = "pl", ttm = TRUE), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + + exp_classes <- append( + exp_classes, + c(`Shares (Basic)` = "numeric", `Shares (Diluted)` = "numeric"), + after = which(names(exp_classes) == "currency") + ) + names(exp_classes) <- clean_names(names(exp_classes)) + + if (isTRUE(sfplus)) { + + ref_4_plus <- sfa_get_statement( + c("GOOG", "AAPL"), statement = "pl", shares = TRUE, fyear = 2015 + ) + checkmate::expect_data_table( + ref_4_plus, + key = "ticker", + types = exp_classes, + nrows = 2L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_4_plus, names(exp_classes)) + + } else { + expect_error( + sfa_get_statement(c("GOOG", "AAPL"), statement = "pl", shares = TRUE, fyear = 2015), + "'shares = TRUE' is reserved to SimFin+ users. As a normal user, please use 'sfa_get_shares()' with 'type = \"wa-basic\"' or 'type = \"wa-diluted\".", + fixed = TRUE + ) + } + } }) @@ -278,51 +317,77 @@ test_that("getting bs statement works", { `Total Liabilities & Equity` = "numeric" ) names(exp_classes) <- clean_names(names(exp_classes)) + exp_classes <- exp_classes[!(names(exp_classes) %in% c("shares_basic", "shares_diluted"))] - ref_1 <- sfa_get_statement("GOOG", statement = "bs", fyear = 2015) - checkmate::expect_data_table( - ref_1, - key = "ticker", - types = exp_classes, - nrows = 1L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1, names(exp_classes)) - - ref_1_plus <- sfa_get_statement("GOOG", statement = "bs") - checkmate::expect_data_table( - ref_1_plus, - key = "ticker", - types = exp_classes, - min.rows = 12L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1_plus, names(exp_classes)) + for (sfplus in c(TRUE, FALSE)) { + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } - ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "bs", fyear = 2015) - checkmate::expect_data_table( - ref_2, - key = "ticker", - types = exp_classes, - nrows = 2L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2, names(exp_classes)) - - ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "bs") - checkmate::expect_data_table( - ref_2_plus, - key = "ticker", - types = exp_classes, - min.rows = 34L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2_plus, names(exp_classes)) + ref_1 <- sfa_get_statement("GOOG", statement = "bs", fyear = 2015) + checkmate::expect_data_table( + ref_1, + key = "ticker", + types = exp_classes, + nrows = 1L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1, names(exp_classes)) + + if (isTRUE(sfplus)) { + ref_1_plus <- sfa_get_statement("GOOG", statement = "bs") + checkmate::expect_data_table( + ref_1_plus, + key = "ticker", + types = exp_classes, + min.rows = 12L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1_plus, names(exp_classes)) + } else { + expect_error( + sfa_get_statement("GOOG", statement = "bs"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + + ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "bs", fyear = 2015) + checkmate::expect_data_table( + ref_2, + key = "ticker", + types = exp_classes, + nrows = 2L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2, names(exp_classes)) + + if (isTRUE(sfplus)) { + ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "bs") + checkmate::expect_data_table( + ref_2_plus, + key = "ticker", + types = exp_classes, + min.rows = 34L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2_plus, names(exp_classes)) + } else { + expect_error( + sfa_get_statement(c("GOOG", "AAPL"), statement = "bs"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + } }) test_that("getting cf statement works", { @@ -393,50 +458,75 @@ test_that("getting cf statement works", { ) names(exp_classes) <- clean_names(names(exp_classes)) - ref_1 <- sfa_get_statement("GOOG", statement = "cf", fyear = 2015) - checkmate::expect_data_table( - ref_1, - key = "ticker", - types = exp_classes, - nrows = 1L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1, names(exp_classes)) - - ref_1_plus <- sfa_get_statement("GOOG", statement = "cf") - checkmate::expect_data_table( - ref_1_plus, - key = "ticker", - types = exp_classes, - min.rows = 12L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_1_plus, names(exp_classes)) + for (sfplus in c(TRUE, FALSE)) { + sfa_set_sfplus(sfplus) + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + } - ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "cf", fyear = 2015) - checkmate::expect_data_table( - ref_2, - key = "ticker", - types = exp_classes, - nrows = 2L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2, names(exp_classes)) - - ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "cf") - checkmate::expect_data_table( - ref_2_plus, - key = "ticker", - types = exp_classes, - min.rows = 36L, - ncols = length(exp_classes), - col.names = "unique" - ) - expect_named(ref_2_plus, names(exp_classes)) + ref_1 <- sfa_get_statement("GOOG", statement = "cf", fyear = 2015) + checkmate::expect_data_table( + ref_1, + key = "ticker", + types = exp_classes, + nrows = 1L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1, names(exp_classes)) + + if (isTRUE(sfplus)) { + ref_1_plus <- sfa_get_statement("GOOG", statement = "cf") + checkmate::expect_data_table( + ref_1_plus, + key = "ticker", + types = exp_classes, + min.rows = 12L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_1_plus, names(exp_classes)) + } else { + expect_error( + sfa_get_statement("GOOG", statement = "cf"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + + ref_2 <- sfa_get_statement(c("GOOG", "AAPL"), statement = "cf", fyear = 2015) + checkmate::expect_data_table( + ref_2, + key = "ticker", + types = exp_classes, + nrows = 2L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2, names(exp_classes)) + + if (isTRUE(sfplus)) { + ref_2_plus <- sfa_get_statement(c("GOOG", "AAPL"), statement = "cf") + checkmate::expect_data_table( + ref_2_plus, + key = "ticker", + types = exp_classes, + min.rows = 36L, + ncols = length(exp_classes), + col.names = "unique" + ) + expect_named(ref_2_plus, names(exp_classes)) + } else { + expect_error( + sfa_get_statement(c("GOOG", "AAPL"), statement = "cf"), + "Omitting 'fyear' is reserved for SimFin+ users.", + fixed = TRUE + ) + } + } }) test_that("warning is triggered if the API did not return any data", { @@ -454,10 +544,34 @@ test_that("warning is triggered if the API did not return any data", { }) # TODO: Test all kinds of statements -test_that("warning is trigged when no company was found", { - expect_warning( - expect_null(sfa_get_statement("doesnotexist", statement = "cf")), - 'No company found for ticker `doesnotexist`.', +test_that("warning is triggered when no company was found", { + expect_error( + expect_warning( + sfa_get_statement("doesnotexist", statement = "cf", fyear = 2015L), + "No company found for ticker `doesnotexist`.", + fixed = TRUE), + "Please provide at least one one valid 'ticker' or 'simfin_id'.", fixed = TRUE ) }) + +test_that("downloading all statements works only for SimFIn+ users", { + for (sfplus in c(TRUE, FALSE)) { + + sfa_set_sfplus(sfplus) + + if (isTRUE(sfplus)) { + options(sfa_api_key = Sys.getenv("SFPLUS_API_KEY")) + checkmate::expect_data_table( + sfa_get_statement("GOOG", statement = "all") + ) + } else { + options(sfa_api_key = Sys.getenv("SF_API_KEY")) + expect_error( + sfa_get_statement("GOOG", statement = "all"), + 'statement = "all" is reserved for SimFin+ users.', + fixed = TRUE + ) + } + } +}) diff --git a/tests/testthat/test-sfa_set_sfplus.R b/tests/testthat/test-sfa_set_sfplus.R new file mode 100644 index 0000000..8ac2266 --- /dev/null +++ b/tests/testthat/test-sfa_set_sfplus.R @@ -0,0 +1,8 @@ +test_that("specifying sfplus works", { + options(sfa_sfplus = NULL) + expect_null(getOption("sfa_sfplus")) + sfa_set_sfplus() + expect_true(getOption("sfa_sfplus")) + sfa_set_sfplus(FALSE) + expect_false(getOption("sfa_sfplus")) +})