diff --git a/DESCRIPTION b/DESCRIPTION index d5a607f..071b11c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,16 +1,26 @@ Package: cryptoQuotes -Title: A Streamlined Access to Cryptocurrency OHLC-V Market Data and Sentiment Indicators -Version: 1.3.1 +Title: Open Access to Cryptocurrency Market Data, Sentiment Indicators and Interactive Charts +Version: 1.3.2 Authors@R: c( - person("Serkan", "Korkmaz", , "serkor1@duck.com", role = c("cre", "aut", "ctb", "cph"), - comment = c(ORCID = "0000-0002-5052-0982")), - person("Jonas", "Cuzulan Hirani", , "jjh@vive.dk", role = "ctb", - comment = c(ORCID = "0000-0002-9512-1993")) + person( + given = "Serkan", + family = "Korkmaz", + email = "serkor1@duck.com", + role = c("cre", "aut", "ctb", "cph"), + comment = c(ORCID = "0000-0002-5052-0982") + ), + person( + given = "Jonas", + family = "Cuzulan Hirani", + email = "jjh@vive.dk", + role = "ctb", + comment = c(ORCID = "0000-0002-9512-1993") + ) ) -Description: - This high-level API client offers a streamlined access to public cryptocurrency market data and sentiment indicators. It features OHLC-V (Open, High, Low, Close, Volume) that comes - with granularity ranging from seconds to months and essential sentiment indicators to develop and backtest trading strategies, or conduct detailed market analysis. By interacting directly with - the major cryptocurrency exchanges this package ensures a reliable, and stable, flow of market information, eliminating the need for complex, low-level API interactions or webcrawlers. +Description: + This high-level API client provides open access to cryptocurrency market data, sentiment indicators, and interactive charting tools. + The data is sourced from major cryptocurrency exchanges via cURL and processed using the xts package. The data comes in open, high, low, and close (OHLC) format with flexible granularity, ranging from seconds to months. + This flexibility makes it ideal for developing and backtesting trading strategies or conducting detailed market analysis. License: GPL (>= 2) Encoding: UTF-8 Roxygen: list(markdown = TRUE) @@ -24,14 +34,14 @@ Suggests: tidyverse Config/testthat/edition: 3 Imports: - cli (>= 3.6.2), + cli (>= 3.6.3), curl (>= 5.2.1), jsonlite (>= 1.8.8), lifecycle (>= 1.0.4), plotly (>= 4.10.4), TTR (>= 0.24.4), utils, - xts (>= 0.13.2), + xts (>= 0.14.0), zoo (>= 1.8-12) Depends: R (>= 4.0.0) diff --git a/NAMESPACE b/NAMESPACE index 423df79..051c5ae 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -23,6 +23,7 @@ export(lsr) export(macd) export(ohlc) export(pline) +export(read_xts) export(remove_bound) export(rsi) export(sma) @@ -31,6 +32,7 @@ export(split_window) export(volume) export(vwap) export(wma) +export(write_xts) export(zlema) importFrom(TTR,BBands) importFrom(curl,has_internet) diff --git a/NEWS.Rmd b/NEWS.Rmd index 2d17fb1..50034bd 100644 --- a/NEWS.Rmd +++ b/NEWS.Rmd @@ -29,14 +29,17 @@ library(cryptoQuotes) ## Improvements +## Read and Write `xts`-objects + +* `read_xts()` and `write_xts()` reads and stores `xts`-objects. These functions are essentially just wrappers of `zoo::read.zoo()` and `zoo::write.zoo()`. Thank you @gokberkcan7 for the suggestion. + ### Charting * The `chart()`-function are now exported as `.svg`-images in 4k resolution via the `modebar`. -* The `chart()`-function are now more interactive and supports drawing lines and rectangles via the `modebar`. It is also possible to interactively change the `title` and `subtitle` by double clicking these (Thank you @andreltr for the suggestion. See [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/19). +* The `chart()`-function are now more interactive and supports drawing lines and rectangles via the `modebar`. It is also possible to interactively change the `title` and `subtitle` by double clicking these (Thank you @andreltr for the suggestion. See [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/19)). * The `chart()`-function now has a new option `static` that is equal to `FALSE` by default. If `FALSE` the chart can be edited, annotated and explored interactively. -* The `chart()`-function now has a new option `palette` that is set to "hawaii" by default. See [hcl.pals()] for accepted values. -* The `chart()`-function now has a new option `scale` that is set to 1 by default. Scales -#' all fonts on the chart. +* The `chart()`-function now has a new option `palette` that is set to "hawaii" by default. See `hcl.pals()` for accepted values. +* The `chart()`-function now has a new option `scale` that is set to 1 by default. Scales all fonts on the chart.
Static set to FALSE (Default Palette) diff --git a/NEWS.md b/NEWS.md index a0f43e4..4657223 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,12 @@ ## Improvements +## Read and Write `xts`-objects + +- `read_xts()` and `write_xts()` reads and stores `xts`-objects. These + functions are essentially just wrappers of `zoo::read.zoo()` and + `zoo::write.zoo()`. Thank you @gokberkcan7 for the suggestion. + ### Charting - The `chart()`-function are now exported as `.svg`-images in 4k @@ -15,14 +21,14 @@ lines and rectangles via the `modebar`. It is also possible to interactively change the `title` and `subtitle` by double clicking these (Thank you @andreltr for the suggestion. See - [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/19). + [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/19)). - The `chart()`-function now has a new option `static` that is equal to `FALSE` by default. If `FALSE` the chart can be edited, annotated and explored interactively. - The `chart()`-function now has a new option `palette` that is set to - “hawaii” by default. See \[hcl.pals()\] for accepted values. + “hawaii” by default. See `hcl.pals()` for accepted values. - The `chart()`-function now has a new option `scale` that is set to 1 - by default. Scales \#’ all fonts on the chart. + by default. Scales all fonts on the chart.
@@ -174,12 +180,12 @@ tail( ``` #> open_interest - #> 2024-07-01 19:00:00 2517.698 - #> 2024-07-01 20:00:00 2538.052 - #> 2024-07-01 21:00:00 2532.170 - #> 2024-07-01 22:00:00 2523.813 - #> 2024-07-01 23:00:00 2530.148 - #> 2024-07-02 00:00:00 2519.876 + #> 2024-07-07 10:00:00 2474.917 + #> 2024-07-07 11:00:00 2522.876 + #> 2024-07-07 12:00:00 2522.405 + #> 2024-07-07 13:00:00 2520.337 + #> 2024-07-07 14:00:00 2514.162 + #> 2024-07-07 15:00:00 2512.482
@@ -361,12 +367,12 @@ tail( ``` #> funding_rate - #> 2024-06-30 02:00:00 1e-04 - #> 2024-06-30 10:00:00 1e-04 - #> 2024-06-30 18:00:00 1e-04 - #> 2024-07-01 02:00:00 1e-04 - #> 2024-07-01 10:00:00 1e-04 - #> 2024-07-01 18:00:00 1e-04 + #> 2024-07-05 18:00:00 1.762e-05 + #> 2024-07-06 02:00:00 5.729e-05 + #> 2024-07-06 10:00:00 7.512e-05 + #> 2024-07-06 18:00:00 3.250e-05 + #> 2024-07-07 02:00:00 8.164e-05 + #> 2024-07-07 10:00:00 1.000e-04
@@ -388,12 +394,12 @@ tail( ``` #> open_interest - #> 2024-06-26 02:00:00 75584.46 - #> 2024-06-27 02:00:00 74876.90 - #> 2024-06-28 02:00:00 74906.65 - #> 2024-06-29 02:00:00 78522.05 - #> 2024-06-30 02:00:00 80535.81 - #> 2024-07-01 02:00:00 85675.69 + #> 2024-07-02 02:00:00 81856.18 + #> 2024-07-03 02:00:00 84473.52 + #> 2024-07-04 02:00:00 88025.51 + #> 2024-07-05 02:00:00 88552.55 + #> 2024-07-06 02:00:00 83117.83 + #> 2024-07-07 02:00:00 80177.38 diff --git a/R/api_binance.R b/R/api_binance.R index b679066..9c00afb 100644 --- a/R/api_binance.R +++ b/R/api_binance.R @@ -9,17 +9,10 @@ binanceUrl <- function( futures = TRUE, ...) { - # 1) define baseURL - # for each API - baseUrl <- base::ifelse( - test = futures, - yes = 'https://fapi.binance.com', - no = 'https://data-api.binance.vision' - ) - - # 2) return the - # baseURL - baseUrl + if (futures) + 'https://fapi.binance.com' + else + 'https://data-api.binance.vision' } @@ -28,18 +21,18 @@ binanceEndpoint <- function( futures = TRUE, top = FALSE) { - endPoint <- switch( + switch( EXPR = type, - ohlc = { - if (futures) 'fapi/v1/klines' else - 'api/v3/klines' - }, - ticker ={ - if (futures) 'fapi/v1/exchangeInfo' else + ticker = { + if (futures) + 'fapi/v1/exchangeInfo' + else 'api/v3/exchangeInfo' }, lsratio = { - if (top) 'futures/data/topLongShortAccountRatio' else + if (top) + 'futures/data/topLongShortAccountRatio' + else 'futures/data/globalLongShortAccountRatio' }, interest = { @@ -47,14 +40,15 @@ binanceEndpoint <- function( }, fundingrate = { 'fapi/v1/fundingRate' + }, + { + if (futures) + 'fapi/v1/klines' + else + 'api/v3/klines' } ) - # 2) return endPoint url - return( - endPoint - ) - } # 2) Available intervals; ##### @@ -67,90 +61,89 @@ binanceIntervals <- function( # 0) wrap all intercals # in switch - all_intervals <- switch( + switch( EXPR = type, 'ohlc' = { if (futures) { + # the labels + interval_label <- c( + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '8h', + '12h', + '1d', + '3d', + '1w', + '1M' + ) - data.frame( - labels = c( - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '8h', - '12h', - '1d', - '3d', - '1w', - '1M' - ), - values = c( - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '8h', - '12h', - '1d', - '3d', - '1w', - '1M' - ) + # the actual values + interval_actual <- c( + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '8h', + '12h', + '1d', + '3d', + '1w', + '1M' ) + } else { + interval_label <- c( + '1s', + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '8h', + '12h', + '1d', + '3d', + '1w', + '1M' + ) - data.frame( - labels = c( - '1s', - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '8h', - '12h', - '1d', - '3d', - '1w', - '1M' - ), - values = c( - '1s', - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '8h', - '12h', - '1d', - '3d', - '1w', - '1M' - ) + interval_actual <- c( + '1s', + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '8h', + '12h', + '1d', + '3d', + '1w', + '1M' ) @@ -159,26 +152,43 @@ binanceIntervals <- function( }, # default return value - data.frame( - labels = c('5m', '15m', '30m', '1h', '2h', '4h', '6h', '12h', '1d'), - values = c('5m', '15m', '30m', '1h', '2h', '4h', '6h', '12h', '1d') - ) - ) - if (all) { + { + interval_label = c( + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '12h', + '1d' + ) - return(all_intervals$labels) + interval_actual = c( + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '12h', + '1d' + ) + } - } else { - # Select the specified interval - selectedInterval <- all_intervals$values[ - grepl(paste0('^', interval, '$'), all_intervals$values) - ] + ) + - return(selectedInterval) + if (all) { return(interval_label) } + + interval_actual[ + interval_label %in% interval + ] - } } # 3) define response object and format; #### @@ -194,30 +204,11 @@ binanceResponse <- function( switch( EXPR = type, - ohlc = { - list( - colum_names = c( - 'open', - 'high', - 'low', - 'close', - 'volume' - ), - colum_location = c( - 2:6 - ), - index_location = c( - 1 - ) - - ) - }, - ticker = { list( foo = function( response, - futures = NULL){ + futures = NULL) { subset( x = response$symbols, grepl( @@ -233,25 +224,39 @@ binanceResponse <- function( fundingrate = { list( colum_names = "funding_rate", - index_location = c(2), - colum_location = c(3) + index_location = 2, + colum_location = 2 ) }, interest = { list( - colum_names = c("open_interest"), - index_location = c(4), - colum_location = c(2) + colum_names = "open_interest", + index_location = 4, + colum_location = 2 ) }, lsratio = { list( - colum_names = c('long', 'short'), - index_location = c(5), + colum_names = c('long', 'short'), + index_location = 5, colum_location = c(2,4) ) + }, + { + list( + colum_names = c( + 'open', + 'high', + 'low', + 'close', + 'volume' + ), + colum_location = 2:6, + index_location = 1 + + ) } ) @@ -277,20 +282,15 @@ binanceDates <- function( } else { - dates <- convert_date( - x = dates, - multiplier = multiplier) - - dates <- vapply( - dates, - format, - scientific = FALSE, - FUN.VALUE = character(1) + dates <- format( + convert_date( + x = dates, + multiplier = multiplier + ), + scientific = FALSE ) - names(dates) <- c('startTime', 'endTime') - - + names(dates) <- c('startTime','endTime') } @@ -313,7 +313,7 @@ binanceParameters <- function( symbol = ticker, interval = binanceIntervals( interval = interval, - futures = futures, + futures = futures, type = type ), limit = if (futures) 1500 else 1000 @@ -322,9 +322,9 @@ binanceParameters <- function( # Add date parameters date_params <- binanceDates( futures = futures, - dates = c( + dates = c( from = from, - to = to + to = to ), is_response = FALSE ) @@ -350,15 +350,13 @@ binanceParameters <- function( params <- c(params, date_params) # Return a structured list with additional common parameters - return( - list( - query = params, - path = NULL, - futures = futures, - source = 'binance', - ticker = ticker, - interval = interval - ) + list( + query = params, + path = NULL, + futures = futures, + source = 'binance', + ticker = ticker, + interval = interval ) } diff --git a/R/api_bitmart.R b/R/api_bitmart.R index 780e3ed..dcfa328 100644 --- a/R/api_bitmart.R +++ b/R/api_bitmart.R @@ -11,15 +11,10 @@ bitmartUrl <- function( # 1) define baseURL # for each API - baseUrl <- base::ifelse( - test = futures, - yes = 'https://api-cloud.bitmart.com', - no = 'https://api-cloud.bitmart.com' - ) - - # 2) return the - # baseURL - baseUrl + if (futures) + 'https://api-cloud.bitmart.com' + else + 'https://api-cloud.bitmart.com' } @@ -28,21 +23,21 @@ bitmartEndpoint <- function( futures = TRUE, ...) { + if (type == "ohlc") { - endPoint <- switch( - EXPR = type, - ohlc = { - if (futures) 'contract/public/kline' else - 'spot/quotation/v3/lite-klines' - }, - ticker ={ - if (futures) 'contract/public/details' else - 'spot/v1/symbols' - } - ) + if (futures) + 'contract/public/kline' + else + 'spot/quotation/v3/lite-klines' - # 2) return endPoint url - endPoint + } else { + + if (futures) + 'contract/public/details' + else + 'spot/v1/symbols' + + } } @@ -56,90 +51,80 @@ bitmartIntervals <- function( # Define all intervals in a data frame if (futures) { - allIntervals <- data.frame( - labels = c( - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '12h', - '1d', - '3d', - '1w' - ), - values = c( - 1, - 3, - 5, - 15, - 30, - 60, - 120, - 240, - 360, - 720, - 1440, - 4320, - 10080 - ) + interval_label <- c( + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '12h', + '1d', + '3d', + '1w' ) - } else { - - allIntervals <- data.frame( - labels = c( - '1m', - # '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - # '6h', - # '12h', - '1d', - # '3d', - '1w' - ), - values = c( - 1, - # 3, - 5, - 15, - 30, - 60, - 120, - 240, - # 360, - # 720, - 1440, - # 4320, - 10080 - ) + interval_actual <- c( + 1, + 3, + 5, + 15, + 30, + 60, + 120, + 240, + 360, + 720, + 1440, + 4320, + 10080 ) - } + } else { + interval_label <- c( + '1m', + # '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + # '6h', + # '12h', + '1d', + # '3d', + '1w' + ) + interval_actual <- c( + 1, + # 3, + 5, + 15, + 30, + 60, + 120, + 240, + # 360, + # 720, + 1440, + # 4320, + 10080 + ) - if (all) { + } - return(allIntervals$labels) + if (all) { return(interval_label) } - } else { - # Locate and return the chosen interval value - selectedInterval <- allIntervals$values[ - allIntervals$labels == interval - ] + interval_actual[ + interval_label %in% interval + ] - return(selectedInterval) - } } # 3) define response object and format; #### @@ -148,46 +133,45 @@ bitmartResponse <- function( futures, ...) { - response <- NULL - # mock response # to avoid check error in # unevaluated expressions response <- NULL - switch( - EXPR = type, - ohlc = { - list( - - colum_names = if (futures) - c('low', 'high', 'open', 'close', 'volume') - else - c('open', 'high', 'low', 'close', 'volume'), + if (type == "ohlc") { - colum_location = if (futures) - 1:5 - else - c(2:5, 7), + if (futures) { - index_location = if (futures) - 6 - else - 1 - ) - }, - ticker = { list( - foo = function(response, futures){ + colum_names = c('low', 'high', 'open', 'close', 'volume'), + colum_location = 1:5, + index_location = 6 + ) - if (futures) response$data$symbol$symbol else - response$data$symbols + } else { - } + list( + colum_names = c('open', 'high', 'low', 'close', 'volume'), + colum_location = c(2:5,7), + index_location = 1 ) + } - ) + } else { + + list( + foo = function(response, futures){ + + if (futures) + response$data$symbol$symbol + else + response$data$symbols + + } + ) + + } } @@ -207,16 +191,12 @@ bitmartDates <- function( } else { - dates <- convert_date( - x = dates, - multiplier = 1 - ) - - dates <- vapply( - dates, - format, - scientific = FALSE, - FUN.VALUE = character(1) + dates <- format( + convert_date( + x = dates, + multiplier = 1 + ), + scientific = FALSE ) names(dates) <- if (futures) diff --git a/R/api_bybit.R b/R/api_bybit.R index ed2ff55..eb135e2 100644 --- a/R/api_bybit.R +++ b/R/api_bybit.R @@ -9,17 +9,10 @@ bybitUrl <- function( futures = TRUE, ...) { - # 1) define baseURL - # for each API - baseUrl <- base::ifelse( - test = futures, - yes = 'https://api.bybit.com', - no = 'https://api.bybit.com' - ) - - # 2) return the - # baseURL - baseUrl + if (futures) + 'https://api.bybit.com' + else + 'https://api.bybit.com' } @@ -30,34 +23,29 @@ bybitEndpoint <- function( endPoint <- switch( EXPR = type, - - ohlc = { - if (futures) - 'v5/market/kline' - else - 'v5/market/kline' - }, - ticker = { if (futures) 'v5/market/instruments-info?category=linear' else 'v5/market/instruments-info?category=spot' }, - lsratio = { if (top) 'v5/market/account-ratio' else 'v5/market/account-ratio' }, - fundingrate = { 'v5/market/funding/history' }, - interest = { '/v5/market/open-interest' + }, + { + if (futures) + 'v5/market/kline' + else + 'v5/market/kline' } ) @@ -77,79 +65,47 @@ bybitIntervals <- function( ...) { # 0) Define intervals - all_intervals <- switch( - EXPR = type, - - 'lsratio' = { - data.frame( - labels = c('5m', '15m', '30m', '1h', '4h', '1d'), - values = c("5min", "15min", "30min", "1h", "4h", "1d") - ) - }, - - 'interest' = { - data.frame( - labels = c('5m', '15m', '30m', '1h', '4h', '1d'), - values = c("5min", "15min", "30min", "1h" , "4h", "1d") - ) - }, - - data.frame( - labels = c( - '1m', - '3m', - '5m', - '15m', - '30m', - '1h', - '2h', - '4h', - '6h', - '12h', - '1d', - '1M', - '1w' - ), - values = c( - "1" , - "3", - "5", - "15", - "30", - "60", - "120", - "240", - "360", - "720", - "D", - "M", - "W" - ) + if (type == "ohlc") { + interval_label <- c( + '1m', + '3m', + '5m', + '15m', + '30m', + '1h', + '2h', + '4h', + '6h', + '12h', + '1d', + '1M', + '1w' ) - ) - - - - # 2.1) if not ALL - # then return interval - # selected - if (all) { - - # 2) return all - # intervals - interval <- all_intervals$labels - - - - } else { - - interval <- all_intervals$values[ - grepl(pattern = paste0('^', interval, '$'), x = all_intervals$labels) - ] + interval_actual <- c( + "1" , + "3", + "5", + "15", + "30", + "60", + "120", + "240", + "360", + "720", + "D", + "M", + "W" + ) } else { + interval_label <- c('5m', '15m', '30m', '1h', '4h', '1d') + interval_actual <- c("5min", "15min", "30min", "1h", "4h", "1d") } - interval + if (all) { return(interval_label) } + + interval_actual[ + interval_label %in% interval + ] } @@ -176,12 +132,8 @@ bybitResponse <- function( 'close', 'volume' ), - colum_location = c( - 2:6 - ), - index_location = c( - 1 - ) + colum_location = 2:6, + index_location = 1 ) }, @@ -197,25 +149,24 @@ bybitResponse <- function( }, interest = { list( - colum_names = c('open_interest'), - index_location = c(2), - colum_location = c(1) + colum_names = 'open_interest', + index_location = 2, + colum_location = 1 ) }, fundingrate = { list( colum_names = "funding_rate", - index_location = c(3), - colum_location = c(2) + index_location = 3, + colum_location = 2 ) }, - - lsratio <- { + { list( colum_names = c('long', 'short'), - index_location = c(4), - colum_location = c(2:3) + index_location = 4, + colum_location = 2:3 ) } @@ -246,23 +197,16 @@ bybitDates <- function( } else { - dates <- convert_date( - x = dates, - multiplier = multiplier) - - dates <- vapply( - dates, - format, - scientific = FALSE, - FUN.VALUE = character(1) + dates <- format( + convert_date( + x = dates, + multiplier = multiplier + ), + scientific = FALSE ) - - names(dates) <-c('start', 'end') - - } dates @@ -300,32 +244,36 @@ bybitParameters <- function( is_response = FALSE ) + if (type != "ohlc") { - if (type == "fundingrate"){ + switch( + EXPR = type, + "fundingrate" = { - names(date_params) <- c("startTime", "endTime") + names(date_params) <- c("startTime", "endTime") - } + }, + "interest" = { - if (type == "interest") { + names(params)[3] <- 'intervalTime' + names(date_params) <- c("startTime", "endTime") + params$limit <- 200 - names(params)[3] <- 'intervalTime' - names(date_params) <- c("startTime", "endTime") - params$limit <- 200 - } + }, + "lsratio" = { + # 4.1) This is a standalone + # parameter; was called interval + # but is named period in the API calls + names(params)[3] <- 'period' - if (type == 'lsratio') { - # 4.1) This is a standalone - # parameter; was called interval - # but is named period in the API calls - names(params)[3] <- 'period' - - # 4.1) Return only - # 100 such that this function - # aligns with the remaining - # functions which - # also returns 100 - params$limit <- 500 + # 4.1) Return only + # 100 such that this function + # aligns with the remaining + # functions which + # also returns 100 + params$limit <- 500 + } + ) } diff --git a/R/api_cryptocom.R b/R/api_cryptocom.R index 2b94091..e86708e 100644 --- a/R/api_cryptocom.R +++ b/R/api_cryptocom.R @@ -11,11 +11,10 @@ crypto.comUrl <- function( # 1) define baseURL # for each API - baseURL <- 'https://api.crypto.com/exchange/v1/public' - - # 2) return the - # baseURL - baseURL + if (futures) + 'https://api.crypto.com/exchange/v1/public' + else + 'https://api.crypto.com/exchange/v1/public' } @@ -24,21 +23,21 @@ crypto.comEndpoint <- function( futures = TRUE, ...) { - endPoint <- switch( + switch( EXPR = type, - ohlc = { - 'get-candlestick' - }, ticker ={ 'get-instruments' }, fundingrate = { 'get-valuations' + }, + # default value: + # klines + { + 'get-candlestick' } ) - # 2) return endPoint url - endPoint } # 2) Available intervals; ##### @@ -48,51 +47,46 @@ crypto.comIntervals <- function( all = FALSE, ...) { - allIntervals <- data.frame( - labels = c( - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "4h", - "12h", - "1d", - "1w", - "2w", - "1M" - ), - values = c( - "M1", - "M5", - "M15", - "M30", - "H1", - "H2", - "H4", - "H12", - "1D", - "7D", - "14D", - "1M" - ) - + # interval labels + # user-facing + interval_label <- c( + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "4h", + "12h", + "1d", + "1w", + "2w", + "1M" + ) + # API intervals + interval_actual <- c( + "M1", + "M5", + "M15", + "M30", + "H1", + "H2", + "H4", + "H12", + "1D", + "7D", + "14D", + "1M" ) - if (all) { - return(allIntervals$labels) + if (all) { return(interval_label) } - } else { - # Select the specified interval - selectedInterval <- allIntervals$values[ - allIntervals$labels %in% interval - ] + interval_actual[ + interval_label %in% interval + ] - return(selectedInterval) - } } # 3) define response object and format; #### @@ -101,8 +95,6 @@ crypto.comResponse <- function( futures, ...) { - response <- NULL - # mock response # to avoid check error in # unevaluated expressions @@ -110,13 +102,6 @@ crypto.comResponse <- function( switch( EXPR = type, - ohlc = { - list( - colum_names = c('open', 'high', 'low', 'close', 'volume'), - colum_location = 1:5, - index_location = 6 - ) - }, ticker = { list( foo = function(response, futures) { @@ -141,6 +126,13 @@ crypto.comResponse <- function( index_location = c(2), colum_location = c(1) ) + }, + { + list( + colum_names = c('open', 'high', 'low', 'close', 'volume'), + colum_location = 1:5, + index_location = 6 + ) } ) @@ -165,19 +157,14 @@ crypto.comDates <- function( multiplier = multiplier ) - } else { - # Convert dates and format - - dates <- convert_date( - x = dates, - multiplier = multiplier - ) - dates <- format( - dates, + convert_date( + x = dates, + multiplier = multiplier + ), scientific = FALSE ) diff --git a/R/api_huobi.R b/R/api_huobi.R index 6ddda00..5064313 100644 --- a/R/api_huobi.R +++ b/R/api_huobi.R @@ -11,11 +11,10 @@ huobiUrl <- function( # 1) define baseURL # for each API - base::ifelse( - test = futures, - yes = 'https://api.hbdm.com', - no = 'https://api.huobi.pro' - ) + if (futures) + 'https://api.hbdm.com' + else + 'https://api.huobi.pro' } @@ -24,19 +23,19 @@ huobiEndpoint <- function( futures = TRUE, top = FALSE) { - switch( + switch( EXPR = type, - ohlc = { - if (futures) - 'linear-swap-ex/market/history/kline' - else - 'market/history/kline' - }, ticker ={ if (futures) "linear-swap-api/v1/swap_api_state" else "v2/settings/common/symbols" + }, + { + if (futures) + 'linear-swap-ex/market/history/kline' + else + 'market/history/kline' } ) } @@ -52,47 +51,41 @@ huobiIntervals <- function( # 0) define intervals # NOTE: These are common for # both endpoints - all_intervals <- data.frame( - labels = c( - '1m', - '5m', - '15m', - '30m', - '1h', - '4h', - '1d', - '1w', - '1M' - ), - values = c( - "1min", - "5min", - "15min", - "30min", - "60min", - "4hour", - "1day", - "1week", - "1mon" - ) - ) + interval_label <- c( + '1m', + '5m', + '15m', + '30m', + '1h', + '4h', + '1d', + '1w', + '1M' + ) - if (all) { + interval_actual <- c( + "1min", + "5min", + "15min", + "30min", + "60min", + "4hour", + "1day", + "1week", + "1mon" + ) - all_intervals$labels - } else { + if (all) { return(interval_label) } - # Select the specified interval - selectedInterval <- all_intervals$values[ - grepl(paste0('^', interval, '$'), all_intervals$labels) - ] + interval_actual[ + interval_label %in% interval + ] - selectedInterval - } } + # 3) define response object and format; #### huobiResponse <- function( type = 'ohlc', @@ -106,7 +99,32 @@ huobiResponse <- function( switch( EXPR = type, - ohlc = { + ticker = { + list( + foo = function( + response, + futures = NULL){ + + if (futures) { + + subset( + response$data + )$contract_code + + } else { + + subset( + response$data, + response$data$state == "online" & response$data$te == TRUE + )$sc + + } + + } + ) + }, + { + list( colum_names = if (futures) c( @@ -132,32 +150,9 @@ huobiResponse <- function( ) ) - }, - - ticker = { - list( - foo = function( - response, - futures = NULL){ - - if (futures) { - - subset( - response$data - )$contract_code - } else { - - subset( - response$data, - response$data$state == "online" & response$data$te == TRUE - )$sc - - } - - } - ) } + ) } @@ -186,21 +181,16 @@ huobiDates <- function( } else { - dates <- convert_date( - x = dates, - multiplier = multiplier) - - dates <- vapply( - dates, - format, - scientific = FALSE, - FUN.VALUE = character(1) + dates <- format( + convert_date( + x = dates, + multiplier = multiplier + ), + scientific = FALSE ) names(dates) <- c('from', 'to') - - } dates @@ -229,6 +219,7 @@ huobiParameters <- function( ) if (futures) names(params)[1] <- "contract_code" + # Add date parameters date_params <- huobiDates( futures = futures, @@ -245,11 +236,11 @@ huobiParameters <- function( # Return a structured list with additional common parameters return( list( - query = params, - path = NULL, - futures = futures, - source = 'huobi', - ticker = ticker, + query = params, + path = NULL, + futures = futures, + source = 'huobi', + ticker = ticker, interval = interval ) ) diff --git a/R/api_kraken.R b/R/api_kraken.R index 78e220a..1f307e8 100644 --- a/R/api_kraken.R +++ b/R/api_kraken.R @@ -11,15 +11,10 @@ krakenUrl <- function( # 1) define baseURL # for each API - baseUrl <- base::ifelse( - test = futures, - yes = 'https://futures.kraken.com', - no = 'https://api.kraken.com' - ) - - # 2) return the - # baseURL - baseUrl + if (futures) + 'https://futures.kraken.com' + else + 'https://api.kraken.com' } @@ -28,14 +23,12 @@ krakenEndpoint <- function( futures = TRUE, ...) { - endPoint <- switch( + switch( EXPR = type, - ohlc = { - if (futures) 'api/charts/v1/' else - '0/public/OHLC/' - }, ticker ={ - if (futures) 'derivatives/api/v3/instruments/' else + if (futures) + 'derivatives/api/v3/instruments/' + else '0/public/AssetPairs/' }, # Was lsratio @@ -44,12 +37,15 @@ krakenEndpoint <- function( }, interest = { 'api/charts/v1/analytics/' + }, + { + if (futures) + 'api/charts/v1/' + else + '0/public/OHLC/' } ) - # 2) return endPoint url - endPoint - } # 2) Available intervals; ##### @@ -61,49 +57,31 @@ krakenIntervals <- function( ...) { # 0) construct intervals - all_intervals <- switch( - EXPR = type, - 'ohlc' = { - if (futures) { - # For futures, labels and values are the same - data.frame( - labels = c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d", "1w"), - values = c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d", "1w") - ) - } else { - # For non-futures, labels and values are different - data.frame( - labels = c("1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "2w"), - values = c(1 ,5 ,15 ,30 ,60 ,240 , 1440,10080,21600) - ) - } - }, - data.frame( - labels = c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "2d", "8d"), - values = c(60, 300, 900, 1800, 3600, 14400, 43200, 86400, 604800) - ) - ) - - # 2.1) if not ALL - # then return interval - # selected - if (all) { - - # 2) return all - # intervals - interval <- all_intervals$labels + switch(EXPR = type, 'ohlc' = { + if (futures) { + # For futures, labels and values are the same + interval_label <- c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d", "1w") + interval_actual <- c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d", "1w") + } else { + # For non-futures, labels and values are different - } else { + interval_label <- c("1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "2w") + interval_actual <- c(1 , 5 , 15 , 30 , 60 , 240 , 1440, 10080, 21600) - interval <- all_intervals$values[ - grepl(pattern = paste0('^', interval, '$'), x = all_intervals$labels) - ] + } + }, # default values + { + interval_label <- c("1m", "5m", "15m", "30m", "1h", "4h", "12h", "2d", "8d") + interval_actual <- c(60, 300, 900, 1800, 3600, 14400, 43200, 86400, 604800) + }) - } + if (all) { return(interval_label) } - interval + interval_actual[ + interval_label %in% interval + ] } @@ -123,14 +101,8 @@ krakenResponse <- function( ) } - switch( EXPR = type, - ohlc = { - ohlc_structure( - volume_loc = if (!futures) 7 else 6 - ) - }, ticker = { list( foo = function(response, futures) { @@ -175,6 +147,11 @@ krakenResponse <- function( index_location = c(1), colum_location = c(2,3,4,5) ) + }, + { + ohlc_structure( + volume_loc = if (futures) 6 else 7 + ) } ) @@ -211,7 +188,7 @@ krakenDates <- function( yes = 1, no = 1 ) - ) + ) if (!futures) { # Adjust for Spot market @@ -276,7 +253,20 @@ krakenParameters <- function( tick_type = "open-interest" ) }, - ohlc = { + lsratio = { + params$symbol <- params$ticker + params$resolution <- params$interval + params$query <- list( + interval = params$interval, + since = date_params[1], + to = date_params[2] + ) + params$path <- list( + symbol = params$symbol, + tick_type = 'long-short-info' + ) + }, + { # Set specific parameters for futures or non-futures if (futures) { params$symbol <- params$ticker @@ -302,22 +292,6 @@ krakenParameters <- function( ) params$path <- NULL } - - - - }, - lsratio = { - params$symbol <- params$ticker - params$resolution <- params$interval - params$query <- list( - interval = params$interval, - since = date_params[1], - to = date_params[2] - ) - params$path <- list( - symbol = params$symbol, - tick_type = 'long-short-info' - ) } ) diff --git a/R/api_kucoin.R b/R/api_kucoin.R index 950440a..82fae38 100644 --- a/R/api_kucoin.R +++ b/R/api_kucoin.R @@ -11,17 +11,10 @@ kucoinUrl <- function( # 1) define baseURL # for each API - baseUrl <- base::ifelse( - test = futures, - yes = 'https://api-futures.kucoin.com', - no = 'https://api.kucoin.com' - ) - - # 2) return the - # baseURL - return( - baseUrl - ) + if (futures) + 'https://api-futures.kucoin.com' + else + 'https://api.kucoin.com' } @@ -30,25 +23,25 @@ kucoinEndpoint <- function( futures = TRUE, ...) { - endPoint <- switch( + switch( EXPR = type, - ohlc = { - if (futures) 'api/v1/kline/query' else - 'api/v1/market/candles' - }, ticker ={ - if (futures) 'api/v1/contracts/active' else + if (futures) + 'api/v1/contracts/active' + else 'api/v1/market/allTickers' }, fundingrate = { 'api/v1/contract/funding-rates' + }, + { + if (futures) + 'api/v1/kline/query' + else + 'api/v1/market/candles' } ) - # 2) return endPoint url - return( - endPoint - ) } # 2) Available intervals; ##### @@ -59,8 +52,8 @@ kucoinIntervals <- function( ...) { if (futures) { - allIntervals <- data.frame( - labels = c( + + interval_label <- c( '1m', '5m', '15m', @@ -72,12 +65,12 @@ kucoinIntervals <- function( '12h', '1d', '1w' - ), - values = c(1, 5, 15, 30, 60, 120, 240, 480, 720, 1440, 10080) - ) + ) + interval_actual <- c(1, 5, 15, 30, 60, 120, 240, 480, 720, 1440, 10080) + } else { - allIntervals <- data.frame( - labels = c( + + interval_label <- c( '1m', '3m', '5m', @@ -91,8 +84,9 @@ kucoinIntervals <- function( '12h', '1d', '1w' - ), - values = c( + ) + + interval_actual <- c( '1min', '3min', '5min', @@ -107,21 +101,14 @@ kucoinIntervals <- function( '1day', '1week' ) - ) - } - - if (all) { - return(allIntervals$labels) + } - } else { - # Select the specified interval - selectedInterval <- allIntervals$values[ - grepl(paste0('^', interval, '$'), allIntervals$labels, ignore.case = TRUE) - ] + if (all) { return(interval_label) } - return(selectedInterval) - } + interval_actual[ + interval_label %in% interval + ] } # 3) define response object and format; #### @@ -139,16 +126,6 @@ kucoinResponse <- function( switch( EXPR = type, - ohlc = { - list( - colum_names = if (futures) - c('open', 'high', 'low', 'close', 'volume') - else - c('open', 'close', 'high', 'low', 'volume'), - colum_location = 2:6, - index_location = 1 - ) - }, ticker = { list( foo = function(response, futures) { @@ -174,6 +151,16 @@ kucoinResponse <- function( index_location = c(3), colum_location = c(2) ) + }, + { + list( + colum_names = if (futures) + c('open', 'high', 'low', 'close', 'volume') + else + c('open', 'close', 'high', 'low', 'volume'), + colum_location = 2:6, + index_location = 1 + ) } ) @@ -188,7 +175,10 @@ kucoinDates <- function( # 0) set multiplier based # on market - multiplier <- if (futures) 1e3 else 1 + multiplier <- if (futures) + 1e3 + else + 1 # 1) if its a response if (is_response) { @@ -201,7 +191,6 @@ kucoinDates <- function( } else { - # Convert dates and format dates <- convert_date( @@ -209,25 +198,22 @@ kucoinDates <- function( multiplier = multiplier ) - dates <- format( - dates, - scientific = FALSE - ) - - if (!futures) { # Adjust for Kucoin spot and set names dates <- as.numeric(dates) dates[2] <- dates[2] + 15 * 60 names(dates) <- c('startAt', 'endAt') + } else { # Set names for futures names(dates) <- c('from', 'to') } - - + dates <- format( + dates, + scientific = FALSE + ) } dates @@ -254,7 +240,11 @@ kucoinParameters <- function( ) ) # Assign appropriate names based on the futures flag - interval_param_name <- if (futures) 'granularity' else 'type' + interval_param_name <- if (futures) + 'granularity' + else + 'type' + names(params)[2] <- interval_param_name # Add date parameters @@ -269,11 +259,11 @@ kucoinParameters <- function( # Return structured list with additional parameters return( list( - query = params, - path = NULL, - futures = futures, - source = 'kucoin', - ticker = ticker, + query = params, + path = NULL, + futures = futures, + source = 'kucoin', + ticker = ticker, interval = interval ) ) diff --git a/R/api_mexc.R b/R/api_mexc.R index 19f65ed..10479e1 100644 --- a/R/api_mexc.R +++ b/R/api_mexc.R @@ -11,11 +11,10 @@ mexcUrl <- function( # 1) define baseURL # for each API - base::ifelse( - test = futures, - yes = 'https://contract.mexc.com', - no = 'https://api.mexc.com' - ) + if (futures) + 'https://contract.mexc.com' + else + 'https://api.mexc.com' } @@ -26,13 +25,7 @@ mexcEndpoint <- function( switch( EXPR = type, - ohlc = { - if (futures) - 'api/v1/contract/kline/' - else - 'api/v3/klines' - }, - ticker ={ + ticker = { if (futures) 'api/v1/contract/detail' else @@ -40,6 +33,14 @@ mexcEndpoint <- function( }, fundingrate = { 'api/v1/contract/funding_rate/history' + }, + # Default values is + # the ohlc + { + if (futures) + 'api/v1/contract/kline/' + else + 'api/v3/klines' } ) @@ -53,70 +54,66 @@ mexcIntervals <- function( ...) { if (futures) { - allIntervals <- data.frame( - labels = c( - '1m', - '5m', - '15m', - '30m', - '1h', - '4h', - '8h', - '1d', - '1w', - "1M" - ), - values = c( - "Min1", - "Min5", - "Min15", - "Min30", - "Min60", - "Hour4", - "Hour8", - "Day1", - "Week1", - "Month1") + + interval_label <- c( + '1m', + '5m', + '15m', + '30m', + '1h', + '4h', + '8h', + '1d', + '1w', + "1M" ) - } else { - allIntervals <- data.frame( - labels = c( - '1m', - '5m', - '15m', - '30m', - '1h', - # '4h', - '1d', - '1w', - '1M' - ), - values = c( - '1m', - '5m', - '15m', - '30m', - '60m', - # '4h', - '1d', - '1W', - '1M' - ) + + interval_actual <- c( + "Min1", + "Min5", + "Min15", + "Min30", + "Min60", + "Hour4", + "Hour8", + "Day1", + "Week1", + "Month1" ) - } - if (all) { + } else { - return(allIntervals$labels) + interval_label <- c( + '1m', + '5m', + '15m', + '30m', + '1h', + '4h', + '1d', + '1w', + '1M' + ) - } else { - # Select the specified interval - selectedInterval <- allIntervals$values[ - grepl(paste0('^', interval, '$'), allIntervals$labels, ignore.case = FALSE) - ] + interval_actual <- c( + '1m', + '5m', + '15m', + '30m', + '60m', + '4h', + '1d', + '1W', + '1M' + ) - return(selectedInterval) } + + if (all) { return(interval_label) } + + interval_actual[ + interval_label %in% interval + ] } # 3) define response object and format; #### @@ -132,16 +129,6 @@ mexcResponse <- function( switch( EXPR = type, - ohlc = { - list( - colum_names = if (futures) - c('open', 'close', 'high', 'low', 'volume') - else - c('open', 'high', 'low', 'close', 'volume'), - colum_location = c(2:6), - index_location = 1 - ) - }, ticker = { list( foo = function(response, futures) { @@ -166,6 +153,16 @@ mexcResponse <- function( index_location = c(3), colum_location = c(2) ) + }, + { + list( + colum_names = if (futures) + c('open', 'close', 'high', 'low', 'volume') + else + c('open', 'high', 'low', 'close', 'volume'), + colum_location = c(2:6), + index_location = 1 + ) } ) @@ -192,14 +189,11 @@ mexcDates <- function( ) if (!is_response) { - - dates <- format(dates, scientific = FALSE) + # Adjust for mexc spot and set names + dates <- as.numeric(dates) + dates[2] <- dates[2] + 15 * 60 if (!futures) { - # Adjust for mexc spot and set names - dates <- as.numeric(dates) - dates[2] <- dates[2] + 15 * 60 - names(dates) <- c('startTime', 'endTime') @@ -207,6 +201,9 @@ mexcDates <- function( # Set names for futures names(dates) <- c('start', 'end') } + + dates <- format(dates, scientific = FALSE) + } dates diff --git a/R/available_exchanges.R b/R/available_exchanges.R index 00007ab..507ae20 100644 --- a/R/available_exchanges.R +++ b/R/available_exchanges.R @@ -69,16 +69,6 @@ available_exchanges <- function( exchanges <- sort(switch( type, - ohlc = c( - 'binance', - 'kucoin', - 'kraken', - 'bitmart', - 'bybit', - 'crypto.com', - 'huobi', - 'mexc' - ), fundingrate = c( 'binance', 'bybit', @@ -95,7 +85,19 @@ available_exchanges <- function( 'binance', 'bybit', 'kraken' - ) + ), + { + c( + 'binance', + 'kucoin', + 'kraken', + 'bitmart', + 'bybit', + 'crypto.com', + 'huobi', + 'mexc' + ) + } ) ) diff --git a/R/available_intervals.R b/R/available_intervals.R index 85ea388..40cd233 100644 --- a/R/available_intervals.R +++ b/R/available_intervals.R @@ -89,9 +89,9 @@ available_intervals <- function( # 0) extract available # intervals all_intervals <- get(paste0(source, 'Intervals'))( - type = type, - futures = futures, - all = TRUE, + type = type, + futures = futures, + all = TRUE, interval = NULL ) diff --git a/R/available_tickers.R b/R/available_tickers.R index 5577d52..f80af47 100644 --- a/R/available_tickers.R +++ b/R/available_tickers.R @@ -70,13 +70,13 @@ available_tickers <- function( # to ticker-information response <- GET( url = baseUrl( - source = source, + source = source, futures = futures ), endpoint = endPoint( - source = source, + source = source, futures = futures, - type = 'ticker' + type = 'ticker' ) ) @@ -87,19 +87,17 @@ available_tickers <- function( source, 'Response' ) )( - type = 'ticker', + type = 'ticker', futures = futures ) - ticker <- sort( + sort( source_response$foo( response = response, futures = futures ) ) - ticker - } # script end; diff --git a/R/get_fundingrate.R b/R/get_fundingrate.R index 73d74bb..d1d1057 100644 --- a/R/get_fundingrate.R +++ b/R/get_fundingrate.R @@ -87,27 +87,6 @@ get_fundingrate <- function( trimws(ticker) ) - - assert( - source %in% suppressMessages( - available_exchanges(type = "fundingrate") - ), - error_message = c( - "x" = sprintf( - fmt = "Exchange {.val %s} is not supported.", - source - ), - "i" = paste( - "Run", - cli::code_highlight( - code = "cryptoQuotes::available_exchanges(type = 'fundingrate')", - code_theme = "Chaos" - ), - "for supported exhanges" - ) - ) - ) - from <- coerce_date(from); to <- coerce_date(to) # 3) if either of the @@ -136,14 +115,18 @@ get_fundingrate <- function( } - fetch( - ticker = ticker, - source = source, - futures = TRUE, - interval = '1d', - type = "fundingrate", - to = to, - from = from + stats::window( + x = fetch( + ticker = ticker, + source = source, + futures = TRUE, + interval = '1d', + type = "fundingrate", + to = to, + from = from + ), + start = from, + end = to ) } diff --git a/R/get_lsratio.R b/R/get_lsratio.R index db950b7..6e1be63 100644 --- a/R/get_lsratio.R +++ b/R/get_lsratio.R @@ -107,7 +107,7 @@ get_lsratio <- function( available_exchanges( type = 'lsratio' ) - ), + ), error_message = c( "x" = sprintf( fmt = "Exchange {.val %s} is not supported.", @@ -124,28 +124,32 @@ get_lsratio <- function( ) ) + # 2) check wether the + # interval is supported by + # the exchange API assert( interval %in% suppressMessages( available_intervals( - type = 'lsratio', - source = source + source = source, + futures = TRUE, + type = 'lsratio' ) ), error_message = c( "x" = sprintf( - fmt = "Interval {.val %s} is not supported by {.val %s}.", - interval, - source + fmt = "Interval {.val %s} is not supported.", + interval ), "i" = paste( "Run", cli::code_highlight( - code = "cryptoQuotes::available_intervals( - type = 'lsratio', source = source - )", + code = sprintf( + "cryptoQuotes::available_intervals(source = '%s', type = 'lsratio', futures = TRUE)", + source + ), code_theme = "Chaos" ), - "for supported intervals" + "for supported intervals." ) ) ) @@ -201,28 +205,21 @@ get_lsratio <- function( } - response <- fetch( - ticker = ticker, - source = source, - futures= TRUE, - interval = interval, - type = 'lsratio', - to = to, - from = from, - top = top + response <- stats::window( + x = fetch( + ticker = ticker, + source = source, + futures= TRUE, + interval = interval, + type = 'lsratio', + to = to, + from = from, + top = top + ), + start = from, + end = to ) - # Bybit has no to or from - # parameter - so this have to be subsettet - # manually - if (source == "bybit") { - - response <- response[ - paste(c(from, to),collapse = "/") - ] - - } - # Calculate the long # short ratio as not # all APIs provides this by default diff --git a/R/get_openinterest.R b/R/get_openinterest.R index 94a7528..7eddc80 100644 --- a/R/get_openinterest.R +++ b/R/get_openinterest.R @@ -107,28 +107,32 @@ get_openinterest <- function( ) ) + # 2) check wether the + # interval is supported by + # the exchange API assert( interval %in% suppressMessages( available_intervals( - type = 'interest', - source = source - ) + source = source, + futures = TRUE, + type = 'interest' + ) ), error_message = c( "x" = sprintf( - fmt = "Interval {.val %s} is not supported by {.val %s}.", - interval, - source + fmt = "Interval {.val %s} is not supported.", + interval ), "i" = paste( "Run", cli::code_highlight( - code = " - cryptoQuotes::available_intervals(type = 'interest', source = source) - ", + code = sprintf( + "cryptoQuotes::available_intervals(source = '%s', type = 'interest', futures = TRUE)", + source + ), code_theme = "Chaos" ), - "for supported intervals" + "for supported intervals." ) ) ) @@ -172,14 +176,18 @@ get_openinterest <- function( ) } - output <- fetch( - ticker = ticker, - source = source, - futures= TRUE, - interval = interval, - type = "interest", - to = to, - from = from + output <- stats::window( + x = fetch( + ticker = ticker, + source = source, + futures= TRUE, + interval = interval, + type = "interest", + to = to, + from = from + ), + start = from, + end = to ) if (source %in% 'kraken') { diff --git a/R/get_quote.R b/R/get_quote.R index c3ee7b8..26b6e4a 100644 --- a/R/get_quote.R +++ b/R/get_quote.R @@ -71,60 +71,54 @@ get_quote <- function( interval = '1d', from = NULL, to = NULL) { - # This function returns - # the ticker with the desired intervals - # and such - - # 0) check internet connection and passed - # argumnents before anything + # # This function returns + # # the ticker with the desired intervals + # # and such + # + # # 0) check internet connection and passed + # # argumnents before anything check_internet_connection() # 1) check all arguments # what are missing, and are # the classes correct? - {{ - - assert( - " - Argument {.arg ticker} is missing with no default - " = !missing(ticker) & is.character(ticker) & length(ticker) == 1, - - " - Argument {.arg source} has to be {.cls character} of length {1} - " = (is.character(source) & length(source) == 1), - - " - Argument {.arg futures} has to be {.cls logical} of length {1} - " = (is.logical(futures) & length(futures) == 1), - - " - Argument {.arg interval} has to be {.cls character} of length {1} - " = (is.character(interval) & length(interval) == 1), - - " - Valid {.arg from} input is on the form - {.val {paste(as.character(Sys.Date()))}} or - {.val {as.character(format(Sys.time()))}} - " = (is.null(from) || (is.date(from) & length(from) == 1)), - - "Valid {.arg to} input is on the form - {.val {paste(as.character(Sys.Date()))}} or - {.val {as.character(format(Sys.time()))}} - " = (is.null(to) || (is.date(to) & length(to) == 1)) - ) - - }} + assert( + " + Argument {.arg ticker} is missing with no default + " = !missing(ticker) & is.character(ticker) & length(ticker) == 1, + + " + Argument {.arg source} has to be {.cls character} of length {1} + " = (is.character(source) & length(source) == 1), + + " + Argument {.arg futures} has to be {.cls logical} of length {1} + " = (is.logical(futures) & length(futures) == 1), + + " + Argument {.arg interval} has to be {.cls character} of length {1} + " = (is.character(interval) & length(interval) == 1), + + " + Valid {.arg from} input is on the form + {.val {paste(as.character(Sys.Date()))}} or + {.val {as.character(format(Sys.time()))}} + " = (is.null(from) || (is.date(from) & length(from) == 1)), + + " + Valid {.arg to} input is on the form + {.val {paste(as.character(Sys.Date()))}} or + {.val {as.character(format(Sys.time()))}} + " = (is.null(to) || (is.date(to) & length(to) == 1)) + ) # recode the exchange # source to avoid errors # based on capitalization # and whitespace - source <- tolower( - trimws(source) - ) + source <- tolower(trimws(source)) ticker <- trimws(ticker) - # 1) check wether # the chosen exchange # is supported by the library @@ -149,6 +143,7 @@ get_quote <- function( ) ) ) + # 2) check wether the # interval is supported by # the exchange API @@ -169,17 +164,13 @@ get_quote <- function( "Run", cli::code_highlight( code = sprintf( - "cryptoQuotes::available_intervals( - source = '%s', - type = 'ohlc, - futures = '%s' - )", + "cryptoQuotes::available_intervals(source = '%s', type = 'ohlc', futures = %s)", source, futures - ), + ), code_theme = "Chaos" ), - "for supported intervals" + "for supported intervals." ) ) ) @@ -215,23 +206,19 @@ get_quote <- function( } - ohlc <- fetch( - ticker = ticker, - source = source, - futures= futures, - interval = interval, - type = "ohlc", - to = to, - from = from - )[paste(c(from, to), collapse = "/")] - - # Kraken doesnt have a to - # parameter on spot market - if (source == "kraken") { - - ohlc <- ohlc[paste(c(from, to), collapse = "/")] - - } + ohlc <- stats::window( + x = fetch( + ticker = ticker, + source = source, + futures = futures, + interval = interval, + type = "ohlc", + to = to, + from = from + ), + start = from, + end = to + ) attributes(ohlc)$source <- paste0( to_title(source), if (futures) " (PERPETUALS)" else " (SPOT)" diff --git a/R/helper.R b/R/helper.R index a8197c3..0772051 100644 --- a/R/helper.R +++ b/R/helper.R @@ -60,9 +60,7 @@ indicator <- function( zoo::fortify.zoo( x, - names = c( - "index" - ) + names = "index" ) } @@ -331,7 +329,7 @@ assert <- function(..., error_message = NULL) { # 2.1) store all conditions # in a list alongside its # names - conditions <- list(...) + conditions <- c(...) # 2.2) if !is.null(condition_names) the # above condition never gets evaluated and @@ -340,32 +338,26 @@ assert <- function(..., error_message = NULL) { # The condition is the names(list()), and is # the error messages written on lhs of the the assert # function - for (condition in named_expressions) { + if (all(conditions)) { - # if TRUE abort - # function - if (!eval.parent(conditions[[condition]])) { + # Stop the funciton + # here if all conditions + # are [TRUE] + return(NULL) - cli::cli_abort( - c("x" = condition), + } else { - # the call will reference the caller - # by default, so we need the second - # topmost caller - call = sys.call( - 1 - length(sys.calls()) - ) + cli::cli_abort( + message = c( + "x" = named_expressions[which.min(conditions)] + ), + call = sys.call( + 1 - length(sys.calls()) ) - - - } + ) } - # stop the function - # here - return(NULL) - } # 3) if there length(...) == 1 then @@ -408,8 +400,6 @@ assert <- function(..., error_message = NULL) { } - - pull <- function( from, what = "Open") { @@ -551,8 +541,9 @@ coerce_date <- function(x){ flatten <- function(x) { if (!inherits(x, "list")) - return(list(x)) else - return(unlist(c(lapply(x, flatten)), recursive = FALSE)) + list(x) + else + unlist(c(lapply(x, flatten)), recursive = FALSE) } @@ -606,7 +597,7 @@ convert_date <- function( # If the values are numeric # it is returned from the # API - scale_factor <- multiplier ** ifelse(is_numeric, -1, 1) + scale_factor <- multiplier ** if (is_numeric) -1 else 1 if (is_numeric) { @@ -951,20 +942,19 @@ bar <- function( # 0) chart theme theme <- chart_theme(dark = dark) - title_text <- ifelse( - !is.null(market), - yes = sprintf( + title_text <- if (!is.null(market)) + sprintf( "Ticker: %s Market: %s
Period: %s", name, market, date_range - ), - no = sprintf( + ) + else + sprintf( "Ticker: %s
Period: %s", name, date_range ) - ) plot <- plotly::layout( p = plot, diff --git a/R/store_xts.R b/R/store_xts.R new file mode 100644 index 0000000..6bdfd46 --- /dev/null +++ b/R/store_xts.R @@ -0,0 +1,101 @@ +# script: Read and Write XTS +# objects +# author: Serkan Korkmaz, serkor1@duck.com +# date: 2024-07-04 +# objective: These convience functions makes it +# easy to read and write XTS objects. +# script start; + +#' @title +#' Read and Write `xts`-objects +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' The [write_xts()]- and [read_xts()]-functions are [zoo::write.zoo()]- and [zoo::read.zoo()]-wrapper functions. +#' +#' @usage +#' # write XTS-object +#' write_xts( +#' x, +#' file, +#' ... +#' ) +#' +#' @param x An <[xts]>-object. +#' @inheritParams zoo::write.zoo +#' @inheritParams zoo::read.zoo +#' +#' @details +#' When reading and writing <[xts]>-objects the [attributes] does not follow the object. +#' +#' +#' @author +#' Serkan Korkmaz +#' +#' @family utility +#' +#' @export +write_xts <- function( + x, + file, + ...) { + + assert( + inherits(x = x, what = "xts"), + error_message = c( + "x" = "{.arg x} must be a {.cls xts}-object" + ) + ) + + assert( + is.character(file) & length(file == 1), + error_message = c( + "x" = "{.arg file} has to be a {.cls character} of length 1." + ) + ) + + zoo::write.zoo( + x = x, + file = file, + index.name = "index", + col.names = TRUE, + row.names = FALSE, + ... + ) + +} + +#' @rdname +#' write_xts +#' +#' @usage +#' # read XTS-object +#' read_xts( +#' file +#' ) +#' +#' @family utility +#' @export +read_xts <- function( + file) { + + assert( + is.character(file) & length(file == 1), + error_message = c( + "x" = "{.arg file} has to be a {.cls character} of length 1." + ) + ) + + xts::as.xts( + x = zoo::read.zoo( + file = file, + index.column = 1, + header = TRUE + ) + ) + + +} + +# script end; diff --git a/R/utils.R b/R/utils.R index dd8c70b..374cd3b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -57,61 +57,28 @@ GET <- function( # 4) set URL # to handle - curl::handle_setopt( - handle = handle, - url = url - ) + # curl::handle_setopt( + # handle = handle, + # url = url + # ) # 5) store # response in memory # and catch connection # errors - response <- tryCatch( - expr = { - curl::curl_fetch_memory( - url = url, - handle = handle - ) - }, - error = function(error) { - - cli::cli_abort( - message = error$message, - call = sys.call( - 1 - length(sys.calls()) - ) - ) - - } + response <- curl::curl_fetch_memory( + url = url, + handle = handle ) - response <- tryCatch( - expr = { - jsonlite::fromJSON( - txt = rawToChar( - x = response$content - ), - simplifyVector = TRUE, - flatten = TRUE - ) - }, - error = function(error) { - - cli::cli_abort( - message = c( - "x" = rawToChar( - x = response$content - ) - ), - call = sys.call( - 1 - length(sys.calls()) - ) - ) - - } + jsonlite::fromJSON( + txt = rawToChar( + x = response$content + ), + simplifyVector = TRUE, + flatten = TRUE ) - response } @@ -309,37 +276,47 @@ fetch <- function( # # This could be done using do.call # maybe - response <- switch (source, - kraken = { - do.call( - data.frame, - response - ) - }, - mexc = { - - tryCatch( - response[[which(idx)]], - error = function(error) { - do.call( - data.frame, - response[ - grepl( - pattern = "data", - x = names(response), - ignore.case = TRUE - ) - ] - ) - - } - ) - - - }, - response[[which(idx)]] - ) + response <- tryCatch( + expr = { + switch ( + source, + kraken = { + do.call( + data.frame, + response + ) + }, + mexc = { + + tryCatch( + response[[which(idx)]], + error = function(error) { + do.call( + data.frame, + response[ + grepl( + pattern = "data", + x = names(response), + ignore.case = TRUE + ) + ] + ) + + } + ) + + }, + response[[which(idx)]] + ) + }, + error = function(error) { + assert( + FALSE, + error_message = error_message + ) + } + ) # 3) Extract source specific @@ -356,36 +333,49 @@ fetch <- function( # 3.1) wrap parameters # in tryCatch to check wether # the columns exists - column_list <- tryCatch( - expr = { - list( - index = response[, parameters$index_location], - core = rbind(response[,parameters$colum_location]) - ) - }, - error = function(error) { - - assert( - FALSE, - error_message = error_message - ) + # column_list <- tryCatch( + # expr = { + # list( + # index = response[, parameters$index_location], + # core = rbind(response[,parameters$colum_location]) + # ) + # }, + # error = function(error) { + # + # assert( + # FALSE, + # error_message = error_message + # ) + # + # } + # ) - } - ) + # column_list <- list( + # index = response[, parameters$index_location], + # core = rbind(response[,parameters$colum_location]) + # ) # 3.1.1) extract the values # from the list - index <- column_list$index - core <- column_list$core + + # core <- vapply( + # X = column_list$core, + # FUN = as.numeric, + # FUN.VALUE = numeric(1) + # ) # 3.1.2) convert # all to as.numeric - core <- apply( - X = core, - MARGIN = 2, - FUN = as.numeric + core <- zoo::as.zoo( + apply( + X = rbind(response[,parameters$colum_location]), + MARGIN = 2, + FUN = as.numeric + ) ) + + # 3.1.3) convert dates # to positxct index <- get( @@ -394,23 +384,18 @@ fetch <- function( ) )( futures = futures, - dates = index, + dates = response[, parameters$index_location], is_response = TRUE, type = type ) - - - # 4) convert to xts # from data.frame # NOTE: This throws an error # for KRAKEN no idea why response <- xts::as.xts( - zoo::as.zoo( - core - ), - index + x = core, + order.by = index ) # 4.1) set column # names diff --git a/README.Rmd b/README.Rmd index e18c843..009519f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -13,7 +13,8 @@ knitr::opts_chunk$set( comment = "#>", fig.path = "man/figures/README-", message = FALSE, - warning = FALSE + warning = FALSE, + echo = FALSE ) ``` @@ -32,28 +33,61 @@ knitr::opts_chunk$set( [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) is a high-level API client for accessing public market data endpoints on major cryptocurrency exchanges. It supports open, high, low, close and volume (OHLC-V) data and a variety of sentiment indicators; the market data is high quality and can be retrieved in intervals ranging from *seconds* to *months*. All the market data is accessed and processed without relying on crawlers, or API keys, ensuring an open, and reliable, access for researchers, traders and students alike. There are currently `r length(invisible(cryptoQuotes::available_exchanges(type = "ohlc")))` supported cryptocurrency exchanges, -
-```{r, echo=FALSE} -## available exchanges -invisible( - all_exchanges <- cryptoQuotes::available_exchanges() +```{r Exchanges (Raw),include=FALSE, echo=FALSE} +# 1) create a data.table of available +# exchanges and store as dat.table +DT <- data.table::data.table( + value = suppressMessages( + cryptoQuotes::available_exchanges() + ) ) -if (length(all_exchanges) %% 2 != 0) { +# 2) add labels +# to the data data +DT[ + , + label := data.table::fcase( + value == "binance", "Binance", + value == "kraken", "Kraken", + value == "crypto.com", "Crypto.com", + value == "kucoin", "KuCoin", + value == "bitmart", "BitMart", + value == "huobi", "Huobi (HTX)", + value == "mexc", "MEXC", + value == "bybit", "Bybit" + ) + , +] + +``` + + +
+```{r Exchanges (Table), echo=FALSE} +# 1) define all available +# exchanges +all_exchanges <- DT$label + +if (length(DT$label) %% 2 != 0) { # If odd, append an empty string to make it even - all_exchanges <- c(all_exchanges, "") + all_exchanges <- c(DT$label, "") } # Convert the character vector to a single-row data frame -exchanges_df <- data.frame(matrix(data = all_exchanges, ncol = 4,byrow = TRUE)) +all_exchanges <- data.table::data.table( + matrix( + data = all_exchanges, + ncol = 4, + byrow = TRUE) +) # Create a horizontal table kableExtra::kable_styling( knitr::kable( - x = exchanges_df, + x = all_exchanges, digits = 2, col.names = NULL, - caption = 'Supported exchanges', + caption = 'All supported exchanges.', align = 'c', table.attr = "style='width:100%;'", format = 'html' @@ -128,7 +162,7 @@ exchange <- firstup( # 2) calculate available # intervals intervals <- sapply( - exchange, + DT$value, function(x){ suppressMessages( { @@ -162,7 +196,7 @@ granularity <- function( ) { sapply( - exchange, + DT$value, function(x){ suppressMessages( { @@ -226,26 +260,33 @@ granularity <- function( # 2) create # table -kableExtra::kable_styling( - knitr::kable( - digits = 2, - caption = 'Supported exchanges, markets and intervals.', - align = 'lccccc', - table.attr = "style='width:100%;'", - col.names = c('Exchange', 'Spot', 'Futures', 'Available Intervals' ,'Smallest Interval', 'Biggest Interval'), - x = data.frame( - row.names = NULL, - Exchange = exchange, - Spot = rep(x = ":white_check_mark:",length(exchange)), - Futures = rep(x = ":white_check_mark:",length(exchange)), - `Available Intervals` = c(intervals), - `Smallest Interval` = c(granularity(TRUE)), - `Biggest Interval` = c(granularity(FALSE)) +gt::as_raw_html( + gt::fmt_markdown( + gt::cols_label( + gt::cols_align( + data = gt::gt( + data.frame( + row.names = NULL, + Exchange = DT$label, + Spot = rep(x = ":white_check_mark:",length(exchange)), + Futures = rep(x = ":white_check_mark:",length(exchange)), + `Available Intervals` = c(intervals), + `Smallest Interval` = c(granularity(TRUE)), + `Biggest Interval` = c(granularity(FALSE)) + ), + auto_align = FALSE, + caption = 'Supported exchanges by spot and futures markets with available intervals.'), + align = "left", + columns = "Exchange" + ), - format = 'html' - ), - full_width = TRUE, - position = 'center' + Available.Intervals = "Available Intervals", + Smallest.Interval = "Smallest Interval", + Biggest.Interval = "Biggest Interval" + ) + + + ) ) ```
@@ -255,7 +296,7 @@ kableExtra::kable_styling( Get USDT denominated Bitcoin (BTC) on the spot market from Binance in `30m`-intervals using the `get_quote()`-function, -```{r cryptocurrency market data in R, eval=TRUE} +```{r cryptocurrency market data in R, eval=TRUE, echo=TRUE} ## BTC OHLC prices ## from Binance spot market ## in 30 minute intervals @@ -264,7 +305,7 @@ BTC <- cryptoQuotes::get_quote( source = 'binance', futures = FALSE, interval = '30m', - from = Sys.Date() - 1 + from = Sys.Date() - 2 ) ``` @@ -298,64 +339,80 @@ The sentiment indicators available in [{cryptoQuotes}](https://serkor1.github.io In this overview we are focusing on *market indicators* made public by the cryptocurrency exchanges. For a full overview of sentiment indicators please refer to the documentation via [{pkgdown}](https://serkor1.github.io/cryptoQuotes/). All supported *market indicators* by exchange are listed in the table below,
-```{r, echo = FALSE} -## 1) list all available -## exchanges and the links -available_endpoint <- data.frame( - Endpoint = c( - "Long-Short Ratio", - "Open Interest", - "Funding Rate" - ), - Binance = c( - ":white_check_mark:", - ":white_check_mark:", - ":white_check_mark:" - ), - Bitmart = c( - ":x:", - ":x:", - ":x:" - ), - Bybit = c( - ":white_check_mark:", - ":white_check_mark:", - ":white_check_mark:" - ), - Kraken = c( - ":white_check_mark:", - ":white_check_mark:", - ":x:" - ), - Kucoin = c( - ":x:", - ":x:", - ":white_check_mark:" - ), - `Crypto.com` = c( - ":x:", ":x:", ":white_check_mark:" - ), - MEXC = c( - ":x:", ":x:", ":white_check_mark:" - ), - Huobi = c( - ":x:", ":x:", ":x:" +```{r Available Endpoints by Exchange, echo = FALSE} +## 0) Get all available +## endpoints +endpoint_label <- c("Long-Short Ratio", "Open Interest", "Fundingrate") +endpoint_value <- c( "lsratio", "interest", "fundingrate") +all_exchanges <- DT$value + +DT_ <- data.table::rbindlist( + lapply( + seq_along(all_exchanges), + FUN = function(i) { + + # 0) extract exchange + exchange <- all_exchanges[i] + + data.table::rbindlist( + lapply( + seq_along(endpoint_value), + FUN = function(j) { + + + # 0) generate table + DT <- data.table::data.table( + order = j, + Exchange = DT$label[i], + Endpoint = endpoint_label[j] + ) + + DT[ + , + available := data.table::fifelse( + test = exchange %in% suppressMessages(cryptoQuotes::available_exchanges(endpoint_value[j])), + yes = ":white_check_mark:", + no = ":x:" + ) + , + ] + + DT + + + } + ) + ) + + + + + } ) ) +DT_ <- data.table::dcast( + data = DT_, + formula = order + Endpoint ~ Exchange, + value.var = "available" +) +DT_$order <- NULL + ## 4) present table ## for as is -kableExtra::kable_styling( - knitr::kable( - digits = 0, - caption = 'Available sentiment indicators by exchange', - align = 'lccccc', - table.attr = "style='width:100%;'", - x = available_endpoint, - format = 'markdown' - ), - full_width = TRUE, - position = 'center' +gt::as_raw_html( + gt::fmt_markdown( + gt::cols_align( + data = gt::gt( + DT_, + auto_align = FALSE, + caption = 'Available sentiment indicators by exchange'), + align = "left", + columns = "Endpoint" + + ) + + ) ) ```
@@ -365,7 +422,7 @@ kableExtra::kable_styling( Get the *long-short ratio* on Bitcoin (BTC) using the `get_lsratio()`-function, -```{r cryptocurrency sentiment indicators in R, eval=TRUE} +```{r cryptocurrency sentiment indicators in R, eval=TRUE, echo=TRUE} ## BTC OHLC prices ## from Binance spot market ## in 30 minute intervals @@ -373,7 +430,7 @@ BTC_LS <- cryptoQuotes::get_lsratio( ticker = 'BTCUSDT', source = 'binance', interval = '30m', - from = Sys.Date() - 1 + from = Sys.Date() - 2 ) ``` @@ -452,16 +509,14 @@ cryptoQuotes::chart( ) ``` -
-Colorblind friendly version -#### Charting with indicators (colorblind friendly) +Source -```{r chartquote(deficiency), fig.align='center', fig.dpi=180, fig.alt="cryptocurrency charts in R"} +```{r chartquote (Source),echo=TRUE, fig.align='center',fig.dpi=180, fig.alt="cryptocurrency charts in R", eval=FALSE} ## Chart BTC ## using klines, SMA -## Bollinger Bands and -## ling-short ratio with color-deficiency +## Bollinger Bands and +## long-short ratio cryptoQuotes::chart( ticker = BTC, main = cryptoQuotes::kline(), @@ -476,19 +531,17 @@ cryptoQuotes::chart( cryptoQuotes::bollinger_bands() ), options = list( - deficiency = TRUE, static = TRUE ) ) ``` -__________
## :information_source: Installation ### :shield: Stable version -```{r stable version guide, eval = FALSE} +```{r stable version guide, eval = FALSE, echo=TRUE} ## install from CRAN install.packages( pkgs = 'cryptoQuotes', @@ -497,7 +550,7 @@ install.packages( ``` ### :hammer_and_wrench: Development version -```{r development version guide, eval = FALSE} +```{r development version guide, eval = FALSE,echo=TRUE} ## install from github devtools::install_github( repo = 'https://github.com/serkor1/cryptoQuotes/', diff --git a/README.md b/README.md index fe8a71d..d1344a1 100644 --- a/README.md +++ b/README.md @@ -33,35 +33,35 @@ currently 8 supported cryptocurrency exchanges, @@ -107,195 +107,120 @@ exchanges,
-
-Supported exchanges +All supported exchanges.
-binance +Binance -bitmart +BitMart -bybit +Bybit -crypto.com +Crypto.com
-huobi +Huobi (HTX) -kraken +Kraken -kucoin +KuCoin -mexc +MEXC
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
-Supported exchanges, markets and intervals. -
-Exchange - -Spot - -Futures - -Available Intervals - -Smallest Interval - -Biggest Interval -
-Binance - -:white_check_mark: - -:white_check_mark: - -16 - -1 second(s) - -1 month(s) -
-Bitmart - -:white_check_mark: - -:white_check_mark: - -13 - -1 minute(s) - -1 week(s) -
-Bybit - -:white_check_mark: - -:white_check_mark: - -13 - -1 minute(s) - -1 month(s) -
-Crypto.com - -:white_check_mark: - -:white_check_mark: - -12 - -1 minute(s) - -1 month(s) -
-Huobi - -:white_check_mark: - -:white_check_mark: - -9 - -1 minute(s) - -1 month(s) -
-Kraken - -:white_check_mark: - -:white_check_mark: - -10 - -1 minute(s) - -2 week(s) -
-Kucoin - -:white_check_mark: - -:white_check_mark: - -13 - -1 minute(s) - -1 week(s) -
-Mexc - -:white_check_mark: - -:white_check_mark: - -10 - -1 minute(s) - -1 month(s) -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Supported exchanges by spot and futures markets with available intervals.
ExchangeSpotFuturesAvailable IntervalsSmallest IntervalBiggest Interval

Binance

+

:white_check_mark:

+

:white_check_mark:

+

16

+

1 second(s)

+

1 month(s)

+

BitMart

+

:white_check_mark:

+

:white_check_mark:

+

13

+

1 minute(s)

+

1 week(s)

+

Bybit

+

:white_check_mark:

+

:white_check_mark:

+

13

+

1 minute(s)

+

1 month(s)

+

Crypto.com

+

:white_check_mark:

+

:white_check_mark:

+

12

+

1 minute(s)

+

1 month(s)

+

Huobi (HTX)

+

:white_check_mark:

+

:white_check_mark:

+

9

+

1 minute(s)

+

1 month(s)

+

Kraken

+

:white_check_mark:

+

:white_check_mark:

+

10

+

1 minute(s)

+

2 week(s)

+

KuCoin

+

:white_check_mark:

+

:white_check_mark:

+

13

+

1 minute(s)

+

1 week(s)

+

MEXC

+

:white_check_mark:

+

:white_check_mark:

+

10

+

1 minute(s)

+

1 month(s)

+
+
@@ -316,7 +241,7 @@ BTC <- cryptoQuotes::get_quote( source = 'binance', futures = FALSE, interval = '30m', - from = Sys.Date() - 1 + from = Sys.Date() - 2 ) ``` @@ -351,122 +276,122 @@ volume -2024-07-01 21:30:00 +2024-07-07 10:00:00 -63392.07 +57369.99 -63411.41 +57540 -63071.36 +57369.98 -63226.78 +57485.12 -854.33693 +352.10478 -2024-07-01 22:00:00 +2024-07-07 10:30:00 -63226.78 +57485.12 -63423.48 +57661.58 -63162.06 +57439.99 -63348.01 +57650.45 -364.18394 +355.56531 -2024-07-01 22:30:00 +2024-07-07 11:00:00 -63348.01 +57650.45 -63380 +57720.35 -63290 +57565.94 -63292.53 +57702.01 -179.12302 +265.19989 -2024-07-01 23:00:00 +2024-07-07 11:30:00 -63292.53 +57702 -63292.53 +57733.99 -63111.25 +57602 -63138 +57733.4 -180.11508 +143.65146 -2024-07-01 23:30:00 +2024-07-07 12:00:00 -63138 +57733.41 -63198.26 +57783.8 -63036.82 +57584.01 -63130.01 +57746 -162.41002 +210.40092 -2024-07-02 +2024-07-07 12:30:00 -63130.01 +57745.99 -63162.5 +57911.71 -63019.38 +57745.99 -63121.2 +57751.47 -146.10718 +186.96287 @@ -498,131 +423,81 @@ indicators please refer to the documentation via
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
-Available sentiment indicators by exchange -
-Endpoint - -Binance - -Bitmart - -Bybit - -Kraken - -Kucoin - -Crypto.com - -MEXC - -Huobi -
-Long-Short Ratio - -:white_check_mark: - -:x: - -:white_check_mark: - -:white_check_mark: - -:x: - -:x: - -:x: - -:x: -
-Open Interest - -:white_check_mark: - -:x: - -:white_check_mark: - -:white_check_mark: - -:x: - -:x: - -:x: - -:x: -
-Funding Rate - -:white_check_mark: - -:x: - -:white_check_mark: - -:x: - -:white_check_mark: - -:white_check_mark: - -:white_check_mark: - -:x: -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available sentiment indicators by exchange
EndpointBinanceBitMartBybitCrypto.comHuobi (HTX)KrakenKuCoinMEXC

Long-Short Ratio

+

:white_check_mark:

+

:x:

+

:white_check_mark:

+

:x:

+

:x:

+

:white_check_mark:

+

:x:

+

:x:

+

Open Interest

+

:white_check_mark:

+

:x:

+

:white_check_mark:

+

:x:

+

:x:

+

:white_check_mark:

+

:x:

+

:x:

+

Fundingrate

+

:white_check_mark:

+

:x:

+

:white_check_mark:

+

:white_check_mark:

+

:x:

+

:x:

+

:white_check_mark:

+

:white_check_mark:

+
+
@@ -642,7 +517,7 @@ BTC_LS <- cryptoQuotes::get_lsratio( ticker = 'BTCUSDT', source = 'binance', interval = '30m', - from = Sys.Date() - 1 + from = Sys.Date() - 2 ) ``` @@ -671,86 +546,86 @@ ls_ratio -2024-07-01 21:30:00 +2024-07-07 10:00:00 -0.65 +0.669 -0.35 +0.331 -1.855 +2.021 -2024-07-01 22:00:00 +2024-07-07 10:30:00 -0.653 +0.666 -0.346 +0.334 -1.886 +1.997 -2024-07-01 22:30:00 +2024-07-07 11:00:00 -0.654 +0.668 -0.346 +0.332 -1.893 +2.013 -2024-07-01 23:00:00 +2024-07-07 11:30:00 -0.651 +0.669 -0.349 +0.331 -1.868 +2.018 -2024-07-01 23:30:00 +2024-07-07 12:00:00 -0.649 +0.67 -0.351 +0.33 -1.851 +2.028 -2024-07-02 +2024-07-07 12:30:00 -0.65 +0.67 -0.35 +0.33 -1.857 +2.028 @@ -774,44 +649,18 @@ interactivity. It supports *light* and *dark* themes, and accounts for The OHLC-V data and the sentiment indicator can be charted using the `chart()`-function, -``` r -## Chart BTC -## using klines, SMA -## Bollinger Bands and -## long-short ratio -cryptoQuotes::chart( - ticker = BTC, - main = cryptoQuotes::kline(), - sub = list( - cryptoQuotes::lsr(ratio = BTC_LS), - cryptoQuotes::volume() - ), - indicator = list( - cryptoQuotes::sma(n = 7), - cryptoQuotes::sma(n = 14), - cryptoQuotes::sma(n = 21), - cryptoQuotes::bollinger_bands() - ), - options = list( - static = TRUE - ) -) -``` - cryptocurrency charts in R
-Colorblind friendly version +Source -#### Charting with indicators (colorblind friendly) - ``` r ## Chart BTC ## using klines, SMA -## Bollinger Bands and -## ling-short ratio with color-deficiency +## Bollinger Bands and +## long-short ratio cryptoQuotes::chart( ticker = BTC, main = cryptoQuotes::kline(), @@ -826,16 +675,11 @@ cryptoQuotes::chart( cryptoQuotes::bollinger_bands() ), options = list( - deficiency = TRUE, static = TRUE ) ) ``` -cryptocurrency charts in R - ------------------------------------------------------------------------- -
## :information_source: Installation diff --git a/codemeta.json b/codemeta.json index 2e5c583..4fe28c2 100644 --- a/codemeta.json +++ b/codemeta.json @@ -2,19 +2,19 @@ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "identifier": "cryptoQuotes", - "description": " This high-level API client offers a streamlined access to public cryptocurrency market data and sentiment indicators. It features OHLC-V (Open, High, Low, Close, Volume) that comes with granularity ranging from seconds to months and essential sentiment indicators to develop and backtest trading strategies, or conduct detailed market analysis. By interacting directly with the major cryptocurrency exchanges this package ensures a reliable, and stable, flow of market information, eliminating the need for complex, low-level API interactions or webcrawlers.", - "name": "cryptoQuotes: A Streamlined Access to Cryptocurrency OHLC-V Market Data and Sentiment Indicators", - "relatedLink": ["https://serkor1.github.io/cryptoQuotes/", "https://CRAN.R-project.org/package=cryptoQuotes"], + "description": " This high-level API client provides open access to cryptocurrency market data, sentiment indicators, and interactive charting tools. The data is sourced from major cryptocurrency exchanges via cURL and processed using the xts package. The data comes in open, high, low, and close (OHLC) format with flexible granularity, ranging from seconds to months. This flexibility makes it ideal for developing and backtesting trading strategies or conducting detailed market analysis.", + "name": "cryptoQuotes: Open Access to Cryptocurrency Market Data, Sentiment Indicators and Interactive Charts", + "relatedLink": "https://serkor1.github.io/cryptoQuotes/", "codeRepository": "https://github.com/serkor1/cryptoQuotes", "issueTracker": "https://github.com/serkor1/cryptoQuotes/issues", "license": "https://spdx.org/licenses/GPL-2.0", - "version": "1.3.1", + "version": "1.3.2", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", "url": "https://r-project.org" }, - "runtimePlatform": "R version 4.4.0 (2024-04-24)", + "runtimePlatform": "R version 4.4.1 (2024-06-14)", "provider": { "@id": "https://cran.r-project.org", "@type": "Organization", @@ -144,7 +144,7 @@ "@type": "SoftwareApplication", "identifier": "cli", "name": "cli", - "version": ">= 3.6.2", + "version": ">= 3.6.3", "provider": { "@id": "https://cran.r-project.org", "@type": "Organization", @@ -227,7 +227,7 @@ "@type": "SoftwareApplication", "identifier": "xts", "name": "xts", - "version": ">= 0.13.2", + "version": ">= 0.14.0", "provider": { "@id": "https://cran.r-project.org", "@type": "Organization", @@ -257,10 +257,5 @@ }, "SystemRequirements": null }, - "fileSize": "808.095KB", - "releaseNotes": "https://github.com/serkor1/cryptoQuotes/blob/master/NEWS.md", - "readme": "https://github.com/serkor1/cryptoQuotes/blob/main/README.md", - "contIntegration": ["https://github.com/serkor1/cryptoQuotes/actions/workflows/R-CMD-check.yaml", "https://codecov.io/gh/serkor1/cryptoQuotes"], - "developmentStatus": "https://lifecycle.r-lib.org/articles/stages.html#experimental", - "keywords": ["cryptocurrencies", "cryptocurrency", "cryptocurrency-exchanges", "r", "binance", "binance-api", "kucoin", "kucoin-api", "kraken-api", "kraken-exchange-api", "bitmart", "bybit", "bybit-api", "rstats", "rstats-package"] + "fileSize": "1504.773KB" } diff --git a/man/available_tickers.Rd b/man/available_tickers.Rd index 35b6f78..4c64346 100644 --- a/man/available_tickers.Rd +++ b/man/available_tickers.Rd @@ -20,7 +20,7 @@ and the specified market. \strong{Sample output} \if{html}{\out{
}}\preformatted{#> [1] "10000000AIDOGEUSDT" "1000000BABYDOGEUSDT" "1000000MOGUSDT" -#> [4] "10000COQUSDT" "10000LADYSUSDT" "10000NFTUSDT" +#> [4] "1000000PEIPEIUSDT" "10000COQUSDT" "10000LADYSUSDT" }\if{html}{\out{
}} } \description{ diff --git a/man/calibrate_window.Rd b/man/calibrate_window.Rd index 7439242..e6992d6 100644 --- a/man/calibrate_window.Rd +++ b/man/calibrate_window.Rd @@ -81,6 +81,7 @@ stopifnot( \seealso{ Other utility: \code{\link{remove_bound}()}, -\code{\link{split_window}()} +\code{\link{split_window}()}, +\code{\link{write_xts}()} } \concept{utility} diff --git a/man/cryptoQuotes-package.Rd b/man/cryptoQuotes-package.Rd index 70b8390..7c52e9a 100644 --- a/man/cryptoQuotes-package.Rd +++ b/man/cryptoQuotes-package.Rd @@ -4,11 +4,11 @@ \name{cryptoQuotes-package} \alias{cryptoQuotes} \alias{cryptoQuotes-package} -\title{cryptoQuotes: A Streamlined Access to Cryptocurrency OHLC-V Market Data and Sentiment Indicators} +\title{cryptoQuotes: Open Access to Cryptocurrency Market Data, Sentiment Indicators and Interactive Charts} \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} -This high-level API client offers a streamlined access to public cryptocurrency market data and sentiment indicators. It features OHLC-V (Open, High, Low, Close, Volume) that comes with granularity ranging from seconds to months and essential sentiment indicators to develop and backtest trading strategies, or conduct detailed market analysis. By interacting directly with the major cryptocurrency exchanges this package ensures a reliable, and stable, flow of market information, eliminating the need for complex, low-level API interactions or webcrawlers. +This high-level API client provides open access to cryptocurrency market data, sentiment indicators, and interactive charting tools. The data is sourced from major cryptocurrency exchanges via cURL and processed using the xts package. The data comes in open, high, low, and close (OHLC) format with flexible granularity, ranging from seconds to months. This flexibility makes it ideal for developing and backtesting trading strategies or conducting detailed market analysis. } \seealso{ Useful links: diff --git a/man/figures/README-chartquote(deficiency)-1.png b/man/figures/README-chartquote(deficiency)-1.png index b6743ec..352b0cd 100644 Binary files a/man/figures/README-chartquote(deficiency)-1.png and b/man/figures/README-chartquote(deficiency)-1.png differ diff --git a/man/figures/README-chartquote-1.png b/man/figures/README-chartquote-1.png index 532a641..7595d1e 100644 Binary files a/man/figures/README-chartquote-1.png and b/man/figures/README-chartquote-1.png differ diff --git a/man/figures/README-unnamed-chunk-1-1.png b/man/figures/README-unnamed-chunk-1-1.png new file mode 100644 index 0000000..5512af8 Binary files /dev/null and b/man/figures/README-unnamed-chunk-1-1.png differ diff --git a/man/remove_bound.Rd b/man/remove_bound.Rd index 644ac99..fa6864e 100644 --- a/man/remove_bound.Rd +++ b/man/remove_bound.Rd @@ -81,6 +81,7 @@ stopifnot( \seealso{ Other utility: \code{\link{calibrate_window}()}, -\code{\link{split_window}()} +\code{\link{split_window}()}, +\code{\link{write_xts}()} } \concept{utility} diff --git a/man/split_window.Rd b/man/split_window.Rd index 5f150f0..1965503 100644 --- a/man/split_window.Rd +++ b/man/split_window.Rd @@ -82,6 +82,7 @@ stopifnot( \seealso{ Other utility: \code{\link{calibrate_window}()}, -\code{\link{remove_bound}()} +\code{\link{remove_bound}()}, +\code{\link{write_xts}()} } \concept{utility} diff --git a/man/write_xts.Rd b/man/write_xts.Rd new file mode 100644 index 0000000..23eb753 --- /dev/null +++ b/man/write_xts.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/store_xts.R +\name{write_xts} +\alias{write_xts} +\alias{read_xts} +\title{Read and Write \code{xts}-objects} +\usage{ +# write XTS-object +write_xts( + x, + file, + ... +) + +# read XTS-object +read_xts( +file +) +} +\arguments{ +\item{x}{An <\link{xts}>-object.} + +\item{file}{character string or strings giving the name of the file(s) + which the data + are to be read from/written to. See \code{\link{read.table}} and + \code{\link{write.table}} for more information. Alternatively, + in \code{read.zoo}, \code{file} can be a \code{connection} or a + \code{data.frame} (e.g., + resulting from a previous \code{read.table} call) that + is subsequently processed to a \code{"zoo"} series.} + +\item{...}{further arguments passed to other functions. In the \code{read.*.zoo} + the arguments are passed to the function specified in \code{read} + (unless \code{file} is a \code{data.frame} already). In \code{write.zoo} the + arguments are passed to \code{\link{write.table}}.} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +The \code{\link[=write_xts]{write_xts()}}- and \code{\link[=read_xts]{read_xts()}}-functions are \code{\link[zoo:read.zoo]{zoo::write.zoo()}}- and \code{\link[zoo:read.zoo]{zoo::read.zoo()}}-wrapper functions. +} +\details{ +When reading and writing <\link{xts}>-objects the \link{attributes} does not follow the object. +} +\seealso{ +Other utility: +\code{\link{calibrate_window}()}, +\code{\link{remove_bound}()}, +\code{\link{split_window}()} + +Other utility: +\code{\link{calibrate_window}()}, +\code{\link{remove_bound}()}, +\code{\link{split_window}()} +} +\author{ +Serkan Korkmaz +} +\concept{utility} diff --git a/playground/playground.R b/playground/playground.R index 72a4243..91bcd03 100644 --- a/playground/playground.R +++ b/playground/playground.R @@ -10,4 +10,66 @@ rm(list = ls()); gc(); devtools::load_all() +# lapply vs for loop +foo <- function() { + + lapply( + X = 1:1000, + FUN = function(x) { + x * 2 + } + ) + +} + +bar <- function() { + + for (i in 1:1000) { + + i * 2 + + } + +} + +baz <- function() { + + vapply( + X = 1:1000, + FUN = function(x) { + + x * 2 + + }, + FUN.VALUE = as.numeric(1000) + ) + +} + +microbenchmark::microbenchmark( + foo(), + bar(), + baz() +) + +response <- list( + a = 1, + b = 2, + c = data.frame( + a = 1 + ) +) + +which( + +) + +idx <- vapply( + X = response, + FUN.VALUE = logical(1), + FUN = inherits, + what = c('array', 'matrix', 'data.frame') +) + + # script end; diff --git a/tests/testthat/test-store_xts.R b/tests/testthat/test-store_xts.R new file mode 100644 index 0000000..f4cae71 --- /dev/null +++ b/tests/testthat/test-store_xts.R @@ -0,0 +1,42 @@ +# script: Test Read and Write +# author: Serkan Korkmaz, serkor1@duck.com +# date: 2024-07-04 +# objective: Unittests for reading and writing +# objects +# script start; + +testthat::test_that( + desc = "Read and Write -objects", + code = { + + # 0) create temporary + # file location and name + temp_file <- paste0( + tempdir(), "BTC.csv" + ) + + # 1) store in temporary file + write_xts( + x = BTC, + file = temp_file + ) + + # 2) check if its equal + # to the original BTC + # object. + # + # NOTE: expect_equal will not + # work as attributes are missing + testthat::expect_true( + setequal( + x = BTC, + y = read_xts( + file = temp_file + ) + ) + ) + + } +) +# script end; + diff --git a/vignettes/custom_indicators.Rmd b/vignettes/custom_indicators.Rmd index 457ec7d..792cdfe 100644 --- a/vignettes/custom_indicators.Rmd +++ b/vignettes/custom_indicators.Rmd @@ -1,9 +1,8 @@ --- -title: "A Guide on Custom Indicators" -subtitle: "How to build and chart it" +title: "A Guide on Charting and Custom Indicators" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{A Guide on Custom Indicators} + %\VignetteIndexEntry{A Guide on Charting and Custom Indicators} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -12,6 +11,7 @@ vignette: > knitr::opts_chunk$set( collapse = TRUE, message = FALSE, + warning = FALSE, comment = "#>", out.width = "100%", out.height = "620px" @@ -22,394 +22,168 @@ knitr::opts_chunk$set( library(cryptoQuotes) ``` -Trading indicators comes in various forms; from the alignment of the moon relative to the sun, to sophisticated trading rules based on neural networks which incorporates classified features - It is not possible to cover them all in an `R` package. +Trading indicators comes in various forms; from the alignment of the moon relative to the sun, to sophisticated trading rules based on neural networks which incorporates classified features; It is not possible to cover them all in an `R` package. -In this `vignette` an introduction to the construction of charting indicators are given, and is recommended for those who would want to chart indicators not otherwise found in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/). +In this `vignette` an introduction to the construction of charts and chart indicators are given, and is recommended for those who would want to chart indicators not otherwise found in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/). The `vignette` uses the built-in `BTC`-object. > **Note:** Feel free to make a `PR` with your indicators that you wish to share with the > rest of the community. -## Charting indicators +As the charts in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) uses [{plotly}](https://github.com/plotly) as backend, the `chart`-objects complies with it's syntax. -Below is a chart, with the indicators `macd()` and `bollinger_bands()`. Each indicator is created using [{TTR}](https://github.com/joshuaulrich/TTR). +## Custom Indicators + +We start by creating a simple `chart`-object with `volume` as it's only indicator, ```{r} -chart( - ticker = BTC, - main = kline(), - sub = list( - macd() - ), - indicator = list( - bollinger_bands() - ) +# 1) create a simple chart +# object +chart_object <- chart( + ticker = BTC, + main = kline(), + sub = list( + volume() + ), + options = list( + dark = FALSE + ) ) -``` -### The anatomy of indicators - -Each indicator is either a *main chart*- or *subchart*-indicator, lets call them `classes` for consistency. The source code for each `class` of indicator is given below, - -
-Main chart indicator (Bollinger Bands) -```{r, echo=FALSE} -bollinger_bands +# 2) display the chart +chart_object ``` -
-
-Subchart indicator (MACD) -```{r, echo=FALSE} -macd -``` -
+### Sinus-oscillator -Common for both indicator `classes` is that they are wrapped in `structure`, with `class = c("plotly", "htmlwidget")`, +Assume a trading strategy that follows a `sin`-curve throughout the period of interest. The starting point is generating the indicator, -```R -structure( - .Data = { - - # Indicator Logic +```{r} +# 1) generate sin-indicator +sin_indicator <- data.frame( + index = zoo::index(BTC), + sin_indicator = sin(seq(0,8*pi,length.out=nrow(BTC))) - }, - class = c( - yourclass, - "plotly", - "htmlwidget" - ) ) ``` -What differentiates the two `classes` of indicators, is the addition of `indicator` or `subchart` in the `yourclass`-placeholder. - -The indicator logic is important for the correct charting of your custom indicator. As -[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) uses -[{plotly}](hhttps://github.com/plotly/plotly.R) as backend for charting, your `class` of indicator has to be consistent with the use of [{plotly}](hhttps://github.com/plotly/plotly.R)-functions. More specifically; `subchart`-indicators uses `plotly::plot_ly()`-functions, while main chart `indicator` uses the `plotly::add_*`-family of functions. - -When creating the custom indicators there is a couple of additional steps needed which will be covered in the examples. - -## Donchian Channels (Example) - -Assume a trading strategy based on Donchian Channels (`TTR::DonchianChannel()`) is needed to optimize your profits. This indicator is a main chart indicator, similar to that of `TTR::BBands()`, +The `sin_indicator` in it's basic form can be charted as follows, ```{r} -tail( - TTR::DonchianChannel( - HL = BTC[,c("high", "low")] +# 1) create a plotly-object +# with the sin-indicator +sin_indicator <- plotly::layout( + margin= list(l = 5, r = 5, b = 5), + p = plotly::plot_ly( + data = sin_indicator, + y = ~sin_indicator, + x = ~index, + type = "scatter", + mode = "lines", + name = "sin" + ), + yaxis = list( + title = NA + ), + xaxis = list( + title = NA ) ) +# 2) display the +# indicator +sin_indicator ``` -This indicator has three features; `high`, `mid` and `low`. To chart this indicator, we would need to call the `plotly::add_lines()`-function three times to chart it properly. Each of these features are defined as `layers` in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/). All layers get built with the `cryptoQuotes:::build()`-function. - -```{r} -## define custom TA -## donchian_channel -donchian_channel <- function( - ## these arguments are the - ## available arguments in the TTR::DonchianChannel - ## function - n = 10, - include.lag = FALSE, - ## the ellipsis - ## is needed to interact with - ## the chart-function - ... -) { - - structure( - .Data = { - - ## 1) define args - ## as a list from the ellipsis - ## which is how the chart-function - ## communicates with the indicators - args <- list( - ... - ) - - ## 2) define the data, which in this - ## case is the indicator. The indicator - ## function streamlines the data so it works - ## with plotly - data <- cryptoQuotes:::indicator( - ## this is just the ticker - ## that is passed into the chart-function - x = args$data, - - ## columns are the columns of the ohlc - ## which the indicator is calculated on - columns = c("high", "low"), - - ## the function itself - ## can be a custom function - ## too. - .f = TTR::DonchianChannel, - - ## all other arguments - ## passed into .f - n = n, - include.lag = FALSE - ) - - ## each layer represents - ## each output from the indicator - ## in this case we have - ## high, mid and low. - ## - ## The lists represents a plotly-function - ## and its associated parameters. - layers <- list( - ## high - list( - type = "add_lines", - params = list( - showlegend = FALSE, - legendgroup = "DC", - name = "high", - inherit = FALSE, - data = data, - x = ~index, - y = ~high, - line = list( - color = "#d38b68", - width = 0.9 - ) - ) - ), - - ## mid - list( - type = "add_lines", - params = list( - showlegend = FALSE, - legendgroup = "DC", - name = "mid", - inherit = FALSE, - data = data, - x = ~index, - y = ~mid, - line = list( - color = "#d38b68", - dash ='dot', - width = 0.9 - ) - ) - ), - - ## low - list( - type = "add_lines", - params = list( - showlegend = FALSE, - legendgroup = "DC", - name = "low", - inherit = FALSE, - data = data, - x = ~index, - y = ~low, - line = list( - color = "#d38b68", - width = 0.9 - ) - ) - ) - ) - - ## we can add ribbons - ## to the main plot to give - ## it a more structured look. - plot <- plotly::add_ribbons( - showlegend = TRUE, - legendgroup = 'DC', - p = args$plot, - inherit = FALSE, - x = ~index, - ymin = ~low, - ymax = ~high, - data = data, - fillcolor = cryptoQuotes:::as_rgb(alpha = 0.1, hex_color = "#d38b68"), - line = list( - color = "transparent" - ), - name = paste0("DC(", paste(c(n), collapse = ", "), ")") - ) - - ## the plot has to be build - ## using the cryptoQuotes::build-function - invisible( - cryptoQuotes:::build( - plot, - layers = layers - ) - ) - - } - ) - -} -``` - -The indicator function can be passed into the appropriate argument in the `chart()`-function, which will handle everything else, +The `sin_indicator` can be added to the `chart_object` using `plotly::subplot` which also handles the theming, ```{r} -chart( - ticker = BTC, - main = kline(), - sub = list( - volume() +# 1) append the sin_indicator +# to the chart object +chart_object <- plotly::subplot( + # ensures that plots are + # vertically aligned + nrows = 2, + heights = c( + 0.7, + 0.2 ), - indicator = list( - bollinger_bands(), - donchian_channel() - ) + chart_object, + sin_indicator, + shareX = FALSE, + titleY = FALSE ) + +# 2) display the chart +# object +chart_object ``` -## Commodity Channel Index (Example) +### Linear Regression Line -Assume a trading strategy based on Commodity Channel Indices (`TTR::CCI()`) is needed to optimize your profits. This indicator is subchart indicator similar to that of `TTR::RSI()`, +Assume a trading strategy that goes long (short) every time the price is below (above) the linear regression line. This indicator can be defined as follows, ```{r} -tail( - TTR::CCI( - HLC = BTC[,c("high", "low", "close")] - ) +# 1) linear regression +# line +lm_indicator <- data.frame( + y = fitted( + lm( + close ~ time, + data = data.frame( + time = 1:nrow(BTC), + close = BTC$close + ) + ) + ), + index = zoo::index(BTC) ) ``` -This indicator has a single feature; `cci`. As this indicator is a subchart indicator with a single feature, we only need a single `layer` built with `plot_ly()`, - +The `lm_indicator` in it's basic form can be charted as follows, ```{r} -## define custom TA -## Commodity Channel Index (CCI) -cc_index <- function( - ## these arguments are the - ## available arguments in the TTR::CCI - ## function - n = 20, - maType, - c = 0.015, - ## the ellipsis - ## is needed to interact with - ## the chart-function - ... -) { - - structure( - .Data = { - - ## 1) define args - ## as a list from the ellipsis - ## which is how the chart-function - ## communicates with the indicators - args <- list( - ... - ) - - ## 2) define the data, which in this - ## case is the indicator. The indicator - ## function streamlines the data so it works - ## with plotly - data <- cryptoQuotes:::indicator( - ## this is just the ticker - ## that is passed into the chart-function - x = args$data, - - ## columns are the columns of the ohlc - ## which the indicator is calculated on - columns = c("high", "low", "close"), - - ## the function itself - ## can be a custom function - ## too. - .f = TTR::CCI, - - ## all other arguments - ## passed into .f - n = n, - maType = maType, - c = c - ) - - - layer <- list( - list( - type = "plot_ly", - params = list( - name = paste0("CCI(", n,")"), - data = data, - showlegend = TRUE, - x = ~index, - y = ~cci, - type = "scatter", - mode = "lines", - line = list( - color = cryptoQuotes:::as_rgb(alpha = 1, hex_color = "#d38b68"), - width = 0.9 - ) - ) - - ) - ) - - cryptoQuotes:::build( - plot = args$plot, - layers = layer, - annotations = list( - list( - text = "Commodity Channel Index", - x = 0, - y = 1, - font = list( - size = 18 - ), - xref = 'paper', - yref = 'paper', - showarrow = FALSE - ) - ) - ) - - - - } - ) - -} +# 1) display the linear +# regression line on +# an empty chart +plotly::add_lines( + p = plotly::plotly_empty(), + data = lm_indicator, + y = ~y, + x = ~index, + inherit = FALSE, + xaxis = "x1", + yaxis = "y2", + name = "regression" +) ``` +The `lm_indicator` can be added to the `chart_object` using `plotly::add_lines` which also handles the theming, ```{r} -chart( - ticker = BTC, - main = kline(), - sub = list( - volume(), - cc_index() +# 1) add the regression +# line to the chart_object +plotly::layout( + margin = list(l = 5, r = 5, b = 5, t = 65), + plotly::add_lines( + p = chart_object, + data = lm_indicator, + y = ~y, + x = ~index, + inherit = FALSE, + xaxis = "x1", + yaxis = "y2", + name = "regression" ), - indicator = list( - bollinger_bands(), - donchian_channel() + yaxis = list( + title = NA + ), + xaxis = list( + title = NA ) ) ``` ## Summary -Creating custom indicators for the `chart()`-functions can be daunting. Two examples of how these are developed in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) have been covered. +Creating custom indicators for the `chart()`-functions follows standard [{plotly}](https://github.com/plotly) syntax. Two examples of how these are charted in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) have been covered. > **Note:** A full pipeline of charting indicators, custom and built-in, will be released sometime in the future. - -To summarise the example, - -1. Define the `indicator`-function (e.g `TTR::CCI()`) -2. Define the `chart`-function for the indicator (e.g `cc_index()`) - 1. Wrap the `indicator`-function in `cryptoQuotes:::indicator()` - 2. Define the `layers` according to features, and wether its a subchart or main chart indicator - 3. Build the chart using `cryptoQuotes:::build()` -3. Add the `chart`-function for the indicator in the appropriate argument in the `chart()`-function - -