From 31d92e21ecfc300039de96e7f6ecf04cd16e8946 Mon Sep 17 00:00:00 2001
From: Serkan Korkmaz <77464572+serkor1@users.noreply.github.com>
Date: Thu, 7 Nov 2024 14:16:40 +0100
Subject: [PATCH] {cryptoQuotes} v1.3.2 :rocket: (#40)
* See commit history or NEWS.md for changes.
---------
Co-authored-by: Stavros Alfieris <98609531+stavralf@users.noreply.github.com>
---
.Rbuildignore | 1 +
.github/workflows/R-CMD-check.yaml | 5 +-
.github/workflows/test-coverage.yaml | 4 +-
.gitignore | 6 +-
CRAN-SUBMISSION | 3 +
DESCRIPTION | 36 +-
NAMESPACE | 2 +
NEWS.Rmd | 112 +++-
NEWS.md | 234 +++++--
R/api_binance.R | 236 ++++---
R/api_bitmart.R | 165 ++---
R/api_bybit.R | 226 +++----
R/api_cryptocom.R | 223 +++++++
R/api_huobi.R | 249 ++++++++
R/api_kraken.R | 150 ++---
R/api_kucoin.R | 126 ++--
R/api_mexc.R | 282 +++++++++
R/available_exchanges.R | 40 +-
R/available_intervals.R | 6 +-
R/available_tickers.R | 13 +-
R/chart.R | 157 ++++-
R/chart_fgi.R | 2 +-
R/chart_lsr.R | 2 +-
R/chart_ma.R | 20 +-
R/chart_macd.R | 2 +-
R/chart_rsi.R | 2 +-
R/chart_smi.R | 2 +-
R/chart_volume.R | 2 +-
R/get_fgi.R | 5 +-
R/get_fundingrate.R | 43 +-
R/get_lsratio.R | 59 +-
R/get_openinterest.R | 46 +-
R/get_quote.R | 130 ++--
R/helper.R | 81 ++-
R/store_xts.R | 101 +++
R/utils.R | 180 +++---
README.Rmd | 272 +++++---
README.md | 599 +++++++-----------
_pkgdown.yml | 16 +-
codemeta.json | 20 +-
cran-comments.md | 14 +-
cryptoQuotes.Rproj | 22 -
man/alma.Rd | 6 +-
man/assert.Rd | 2 +-
man/available_tickers.Rd | 4 +-
man/bollinger_bands.Rd | 4 +-
man/calibrate_window.Rd | 3 +-
man/chart.Rd | 23 +-
man/cryptoQuotes-package.Rd | 4 +-
man/dema.Rd | 6 +-
man/donchian_channel.Rd | 2 +-
man/ema.Rd | 6 +-
man/evwma.Rd | 6 +-
man/fetch.Rd | 2 +-
man/figures/NEWS-unnamed-chunk-2-1.png | Bin 55095 -> 52554 bytes
man/figures/NEWS-unnamed-chunk-3-1.png | Bin 0 -> 46910 bytes
man/figures/NEWS-unnamed-chunk-4-1.png | Bin 52705 -> 54731 bytes
man/figures/NEWS-unnamed-chunk-6-1.png | Bin 0 -> 50538 bytes
man/figures/NEWS-unnamed-chunk-7-1.png | Bin 0 -> 66175 bytes
.../README-chartquote(deficiency)-1.png | Bin 131573 -> 125720 bytes
man/figures/README-chartquote-1.png | Bin 132952 -> 133854 bytes
man/figures/README-unnamed-chunk-1-1.png | Bin 0 -> 85822 bytes
man/figures/README-unnamed-chunk-3-1.png | Bin 86298 -> 81093 bytes
man/get_fgindex.Rd | 2 +-
man/get_fundingrate.Rd | 2 +-
man/get_lsratio.Rd | 2 +-
man/get_openinterest.Rd | 2 +-
man/get_quote.Rd | 2 +-
man/hma.Rd | 6 +-
man/macd.Rd | 2 +-
man/remove_bound.Rd | 3 +-
man/rsi.Rd | 2 +-
man/sma.Rd | 6 +-
man/smi.Rd | 10 +-
man/split_window.Rd | 3 +-
man/vwap.Rd | 6 +-
man/wma.Rd | 6 +-
man/write_xts.Rd | 59 ++
man/zlema.Rd | 6 +-
pkgdown/extra.css | 1 +
pkgdown/index.Rmd | 93 +--
pkgdown/index.md | 277 +-------
playground/playground.R | 53 --
tests/testthat/test-CICD.R | 322 ++++++++++
tests/testthat/test-charting.R | 4 +-
tests/testthat/test-getFundingrate.R | 12 +-
tests/testthat/test-getQuote.R | 91 ++-
tests/testthat/test-infer_interval.R | 36 +-
tests/testthat/test-kraken.R | 237 -------
tests/testthat/test-store_xts.R | 46 ++
vignettes/articles/01-article.Rmd | 8 +-
vignettes/articles/02-article.Rmd | 12 +-
vignettes/articles/03-article.Rmd | 125 ++--
vignettes/articles/04-article.Rmd | 29 +-
vignettes/articles/05-article.Rmd | 17 +-
vignettes/cryptoQuotes.Rmd | 8 +-
vignettes/custom_indicators.Rmd | 465 ++++----------
vignettes/usecase.Rmd | 12 +-
98 files changed, 3373 insertions(+), 2558 deletions(-)
create mode 100644 CRAN-SUBMISSION
create mode 100644 R/api_cryptocom.R
create mode 100644 R/api_huobi.R
create mode 100644 R/api_mexc.R
create mode 100644 R/store_xts.R
delete mode 100644 cryptoQuotes.Rproj
create mode 100644 man/figures/NEWS-unnamed-chunk-3-1.png
create mode 100644 man/figures/NEWS-unnamed-chunk-6-1.png
create mode 100644 man/figures/NEWS-unnamed-chunk-7-1.png
create mode 100644 man/figures/README-unnamed-chunk-1-1.png
create mode 100644 man/write_xts.Rd
delete mode 100644 playground/playground.R
create mode 100644 tests/testthat/test-CICD.R
delete mode 100644 tests/testthat/test-kraken.R
create mode 100644 tests/testthat/test-store_xts.R
diff --git a/.Rbuildignore b/.Rbuildignore
index 9b396e4b..8b088c76 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -17,3 +17,4 @@ playground
^data-raw$
^codemeta\.json$
NEWS.Rmd
+.vdoc.r
\ No newline at end of file
diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml
index 24a7f327..80a15b07 100644
--- a/.github/workflows/R-CMD-check.yaml
+++ b/.github/workflows/R-CMD-check.yaml
@@ -8,6 +8,8 @@ on:
name: R-CMD-check
+permissions: read-all
+
jobs:
R-CMD-check:
runs-on: ${{ matrix.config.os }}
@@ -29,7 +31,7 @@ jobs:
R_KEEP_PKG_SOURCE: yes
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: r-lib/actions/setup-pandoc@v2
@@ -47,3 +49,4 @@ jobs:
- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
+ build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
\ No newline at end of file
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
index cfe937b6..080b236b 100644
--- a/.github/workflows/test-coverage.yaml
+++ b/.github/workflows/test-coverage.yaml
@@ -2,9 +2,9 @@
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
- branches: [development]
+ branches: [development, expand-test]
pull_request:
- branches: [development]
+ branches: [development, expand-test]
name: test-coverage
diff --git a/.gitignore b/.gitignore
index 09bebba5..830332d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,4 +51,8 @@ inst/doc
/doc/
/Meta/
docs
-/playground
+/playground/
+
+/*.html
+.vdoc.r
+*.Rproj
diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION
new file mode 100644
index 00000000..4fa0cc51
--- /dev/null
+++ b/CRAN-SUBMISSION
@@ -0,0 +1,3 @@
+Version: 1.3.2
+Date: 2024-11-07 05:41:59 UTC
+SHA: e12973201b0a1173d20ccc6d89b774a4f68b95cf
diff --git a/DESCRIPTION b/DESCRIPTION
index d5a607f9..db3eac68 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,20 +1,30 @@
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 returned in 'xts'-format. 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)
-RoxygenNote: 7.3.1
+RoxygenNote: 7.3.2
Suggests:
data.table,
knitr,
@@ -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 423df79e..051c5aea 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 34518eb3..341d6292 100644
--- a/NEWS.Rmd
+++ b/NEWS.Rmd
@@ -23,7 +23,97 @@ knitr::opts_chunk$set(
library(cryptoQuotes)
```
-# cryptoQuotes 1.3.1
+# Version 1.3.2
+
+## General
+
+* `bitmart` has updated their futures API. The backend have been updated accordingly.
+
+* Unit-tests have been updated and now all `get_quote()`-functions are being tested for equality in passed and inferred interval.
+
+## 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 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 `width` that is set to 0.9 by default. Sets the overall `linewidth` of the chart. (Thank you @andreltr for the suggestion. See [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/30))
+
+
+Static set to FALSE (Default Palette)
+```{r}
+# static = FALSE
+chart(
+ ticker = BTC,
+ main = kline(),
+ indicator = line(
+ sma(n = 7),
+ sma(n = 14),
+ sma(n = 21)
+ ),
+ options = list(
+ static = FALSE,
+ palette = "hawaii"
+ )
+)
+```
+
+
+
+Static set to TRUE ("Set 3" palette)
+```{r}
+# static = TRUE
+chart(
+ ticker = BTC,
+ main = kline(),
+ indicator = line(
+ sma(n = 7),
+ sma(n = 14),
+ sma(n = 21)
+ ),
+ options = list(
+ static = TRUE,
+ palette = "Set 3"
+ )
+)
+```
+
+
+### Supported Exchanges (Issue [#14](https://github.com/serkor1/cryptoQuotes/issues/14))
+
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) now supports the following exchanges:
+
+* Crypto.com
+* Huobi
+* MEXC
+
+
+## Breaking Changes
+
+## Bugfixes
+
+### Charting
+
+* Fixed a bug in the `chart()`-function where a warning would be given if called using namespace qualified function calls (Issue [#13](https://github.com/serkor1/cryptoQuotes/issues/13))
+* Fixed a bug in the `chart()`-function where a `legend` wouldn't show unless a main-chart indicator
+were included. (Issue [#13](https://github.com/serkor1/cryptoQuotes/issues/13))
+
+### Quotes
+
+* Removed `1s` from *Binance spot*
+* Removed `3m`, `6h` and `3d` in *Bitmart spot*
+
+These intervals have been removed as they have either been discontinued, or were non-existent.
+
+
+# Version 1.3.1
## General
@@ -118,7 +208,7 @@ chart(
* Removed dependency on `conflicted`-package.
-Prior to version `1.3.0` the `get*`-functions were following the syntax of `quantmod` closely, and this goes for the function naming too. With the adoption of the `tidyverse` style guide, there is no conflicts that
+Prior to version `1.3.0` the `get*`-functions were following the syntax of [{quantmod}](https://github.com/joshuaulrich/quantmod) closely, and this goes for the function naming too. With the adoption of the `tidyverse` style guide, there is no conflicts that
needs to be resolved on `stable`- and `experimental`-functions.
### New developper tools
@@ -140,7 +230,7 @@ needs to be resolved on `stable`- and `experimental`-functions.
* Fixed a bug in the `get_quote()`-function where if `to = NULL` and `from != NULL` the returned `quote` would be filtered according to `UTC` and not `Sys.timezone()`
* Fixed a bug in the `chart()`-function where the inferred intervals would be incorrect for leap years, and months different from 30 days.
-# cryptoQuotes 1.3.0
+# Version 1.3.0
## Improvements
@@ -247,10 +337,10 @@ tail(
## Warning
-As the `cryptoQuotes`-package has moved to the `tidyverse` style guide, the `getFoo`-functions are now `deprecated`. These will be permanently deleted, and removed from the `cryptoQuotes`-package, at version 1.4.0!
+As [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) has moved to the `tidyverse` style guide, the `getFoo`-functions are now `deprecated`. These will be permanently deleted, and removed from the [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/), at version 1.4.0!
-# cryptoQuotes 1.2.1
+# Version 1.2.1
### Minor Updates
@@ -263,7 +353,7 @@ As the `cryptoQuotes`-package has moved to the `tidyverse` style guide, the `get
* All returned Quotes are now in `UTC`, again.
* Fixed an error on the `Bitmart` API where weekly candles would throw an error.
-# cryptoQuotes 1.2.0
+# Version 1.2.0
* All `from` and `to` arguments are now more flexible, and supports passing `Sys.Date()` and `Sys.time()` directly into the `get`-functions.
@@ -271,7 +361,7 @@ As the `cryptoQuotes`-package has moved to the `tidyverse` style guide, the `get
The `getQuote()`-function can now be used as follows;
-```
+```r
## Specifying from
## date only;
##
@@ -283,7 +373,7 @@ getQuote(
)
```
-```
+```r
## Specifying to
## date only;
##
@@ -312,7 +402,7 @@ Three new convinience functions are added applicable to some situations,
* `splitWindow()`
* `calibrateWindow()`
-# cryptoQuotes 1.1.0
+# Version 1.1.0
## Frontend
@@ -320,7 +410,7 @@ Three new convinience functions are added applicable to some situations,
## Backend
-* All code has been rewritten so its compatible with `httr2`, the package used `httr` at version `1.0.0`.
+* All code has been rewritten so its compatible with [{httr2}](https://github.com/r-lib/httr2), the package used [{httr}](https://github.com/r-lib/httr) at version `1.0.0`.
## Future releases
@@ -331,6 +421,6 @@ In the next release, three more exchanges will be supported.
The returned `quotes` are in local timezone, this is an unintentional feature and will be fixed in a bugfix.
-# cryptoQuotes 1.0.0
+# Version 1.0.0
* Initial CRAN submission :rocket:
diff --git a/NEWS.md b/NEWS.md
index 35fcb76c..e7fe3430 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,7 +1,125 @@
-# cryptoQuotes 1.3.1
+# Version 1.3.2
+
+## General
+
+- `bitmart` has updated their futures API. The backend have been updated
+ accordingly.
+
+- Unit-tests have been updated and now all `get_quote()`-functions are
+ being tested for equality in passed and inferred interval.
+
+## 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 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 `width` that is set to 0.9
+ by default. Sets the overall `linewidth` of the chart. (Thank you
+ @andreltr for the suggestion. See
+ [Discussion](https://github.com/serkor1/cryptoQuotes/discussions/30))
+
+
+
+Static set to FALSE (Default Palette)
+
+
+``` r
+# static = FALSE
+chart(
+ ticker = BTC,
+ main = kline(),
+ indicator = line(
+ sma(n = 7),
+ sma(n = 14),
+ sma(n = 21)
+ ),
+ options = list(
+ static = FALSE,
+ palette = "hawaii"
+ )
+)
+```
+
+
+
+
+
+Static set to TRUE (“Set 3” palette)
+
+
+``` r
+# static = TRUE
+chart(
+ ticker = BTC,
+ main = kline(),
+ indicator = line(
+ sma(n = 7),
+ sma(n = 14),
+ sma(n = 21)
+ ),
+ options = list(
+ static = TRUE,
+ palette = "Set 3"
+ )
+)
+```
+
+
+
+
+### Supported Exchanges (Issue [\#14](https://github.com/serkor1/cryptoQuotes/issues/14))
+
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) now supports
+the following exchanges:
+
+- Crypto.com
+- Huobi
+- MEXC
+
+## Breaking Changes
+
+## Bugfixes
+
+### Charting
+
+- Fixed a bug in the `chart()`-function where a warning would be given
+ if called using namespace qualified function calls (Issue
+ [\#13](https://github.com/serkor1/cryptoQuotes/issues/13))
+- Fixed a bug in the `chart()`-function where a `legend` wouldn’t show
+ unless a main-chart indicator were included. (Issue
+ [\#13](https://github.com/serkor1/cryptoQuotes/issues/13))
+
+### Quotes
+
+- Removed `1s` from *Binance spot*
+- Removed `3m`, `6h` and `3d` in *Bitmart spot*
+
+These intervals have been removed as they have either been discontinued,
+or were non-existent.
+
+# Version 1.3.1
## General
@@ -49,7 +167,7 @@ chart(
)
```
-
+
### Expanded Support
@@ -72,12 +190,12 @@ tail(
```
#> open_interest
- #> 2024-05-31 15:00:00 3013.342
- #> 2024-05-31 16:00:00 2957.343
- #> 2024-05-31 17:00:00 2960.819
- #> 2024-05-31 18:00:00 2954.668
- #> 2024-05-31 19:00:00 2983.686
- #> 2024-05-31 20:00:00 2996.449
+ #> 2024-11-02 10:00:00 2394.876
+ #> 2024-11-02 11:00:00 2389.595
+ #> 2024-11-02 12:00:00 2396.225
+ #> 2024-11-02 13:00:00 2403.175
+ #> 2024-11-02 14:00:00 2418.193
+ #> 2024-11-02 15:00:00 2398.648
@@ -115,7 +233,7 @@ chart(
)
```
-
+
### Documentation
@@ -134,9 +252,10 @@ chart(
- Removed dependency on `conflicted`-package.
Prior to version `1.3.0` the `get*`-functions were following the syntax
-of `quantmod` closely, and this goes for the function naming too. With
-the adoption of the `tidyverse` style guide, there is no conflicts that
-needs to be resolved on `stable`- and `experimental`-functions.
+of [{quantmod}](https://github.com/joshuaulrich/quantmod) closely, and
+this goes for the function naming too. With the adoption of the
+`tidyverse` style guide, there is no conflicts that needs to be resolved
+on `stable`- and `experimental`-functions.
### New developper tools
@@ -163,7 +282,7 @@ needs to be resolved on `stable`- and `experimental`-functions.
- Fixed a bug in the `chart()`-function where the inferred intervals
would be incorrect for leap years, and months different from 30 days.
-# cryptoQuotes 1.3.0
+# Version 1.3.0
## Improvements
@@ -229,7 +348,7 @@ chart(
)
```
-
+
### Exchange Support
@@ -258,12 +377,12 @@ tail(
```
#> funding_rate
- #> 2024-05-30 02:00:00 0.00010000
- #> 2024-05-30 10:00:00 0.00010000
- #> 2024-05-30 18:00:00 0.00010000
- #> 2024-05-31 02:00:00 0.00014599
- #> 2024-05-31 10:00:00 0.00012268
- #> 2024-05-31 18:00:00 0.00010000
+ #> 2024-10-31 17:00:00 1.730390e+12
+ #> 2024-11-01 01:00:00 1.730419e+12
+ #> 2024-11-01 09:00:00 1.730448e+12
+ #> 2024-11-01 17:00:00 1.730477e+12
+ #> 2024-11-02 01:00:00 1.730506e+12
+ #> 2024-11-02 09:00:00 1.730534e+12
@@ -285,12 +404,12 @@ tail(
```
#> open_interest
- #> 2024-05-26 02:00:00 72347.36
- #> 2024-05-27 02:00:00 71077.10
- #> 2024-05-28 02:00:00 71580.71
- #> 2024-05-29 02:00:00 71880.38
- #> 2024-05-30 02:00:00 76232.59
- #> 2024-05-31 02:00:00 74250.55
+ #> 2024-10-28 01:00:00 82206.35
+ #> 2024-10-29 01:00:00 89115.04
+ #> 2024-10-30 01:00:00 90242.98
+ #> 2024-10-31 01:00:00 89315.49
+ #> 2024-11-01 01:00:00 89544.93
+ #> 2024-11-02 01:00:00 84087.60
@@ -311,11 +430,13 @@ tail(
## Warning
-As the `cryptoQuotes`-package has moved to the `tidyverse` style guide,
-the `getFoo`-functions are now `deprecated`. These will be permanently
-deleted, and removed from the `cryptoQuotes`-package, at version 1.4.0!
+As [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) has moved
+to the `tidyverse` style guide, the `getFoo`-functions are now
+`deprecated`. These will be permanently deleted, and removed from the
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/), at version
+1.4.0!
-# cryptoQuotes 1.2.1
+# Version 1.2.1
### Minor Updates
@@ -331,7 +452,7 @@ deleted, and removed from the `cryptoQuotes`-package, at version 1.4.0!
- Fixed an error on the `Bitmart` API where weekly candles would throw
an error.
-# cryptoQuotes 1.2.0
+# Version 1.2.0
- All `from` and `to` arguments are now more flexible, and supports
passing `Sys.Date()` and `Sys.time()` directly into the
@@ -343,25 +464,29 @@ deleted, and removed from the `cryptoQuotes`-package, at version 1.4.0!
The `getQuote()`-function can now be used as follows;
- ## Specifying from
- ## date only;
- ##
- ## Returns 10 pips
- getQuote(
- ticker = 'BTCUSDT',
- interval = '1d'
- from = as.character(Sys.Date() - 10)
- )
-
- ## Specifying to
- ## date only;
- ##
- ## Returns 100 pips
- getQuote(
- ticker = 'BTCUSDT',
- interval = '1d'
- to = as.character(Sys.Date())
- )
+``` r
+## Specifying from
+## date only;
+##
+## Returns 10 pips
+getQuote(
+ ticker = 'BTCUSDT',
+ interval = '1d'
+ from = as.character(Sys.Date() - 10)
+ )
+```
+
+``` r
+## Specifying to
+## date only;
+##
+## Returns 100 pips
+getQuote(
+ ticker = 'BTCUSDT',
+ interval = '1d'
+ to = as.character(Sys.Date())
+ )
+```
## Market Sentiment
@@ -383,7 +508,7 @@ Three new convinience functions are added applicable to some situations,
- `splitWindow()`
- `calibrateWindow()`
-# cryptoQuotes 1.1.0
+# Version 1.1.0
## Frontend
@@ -391,8 +516,9 @@ Three new convinience functions are added applicable to some situations,
## Backend
-- All code has been rewritten so its compatible with `httr2`, the
- package used `httr` at version `1.0.0`.
+- All code has been rewritten so its compatible with
+ [{httr2}](https://github.com/r-lib/httr2), the package used
+ [{httr}](https://github.com/r-lib/httr) at version `1.0.0`.
## Future releases
@@ -403,6 +529,6 @@ In the next release, three more exchanges will be supported.
The returned `quotes` are in local timezone, this is an unintentional
feature and will be fixed in a bugfix.
-# cryptoQuotes 1.0.0
+# Version 1.0.0
- Initial CRAN submission :rocket:
diff --git a/R/api_binance.R b/R/api_binance.R
index 59e97c57..9c00afb4 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,11 +61,54 @@ binanceIntervals <- function(
# 0) wrap all intercals
# in switch
- all_intervals <- switch(
+ switch(
EXPR = type,
'ohlc' = {
- data.frame(
- labels = c(
+
+ if (futures) {
+
+ # the labels
+ interval_label <- 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',
@@ -88,8 +125,9 @@ binanceIntervals <- function(
'3d',
'1w',
'1M'
- ),
- values = c(
+ )
+
+ interval_actual <- c(
'1s',
'1m',
'3m',
@@ -107,30 +145,50 @@ binanceIntervals <- function(
'1w',
'1M'
)
- )
+
+
+ }
+
},
# 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'
+ )
+
+ interval_actual = c(
+ '5m',
+ '15m',
+ '30m',
+ '1h',
+ '2h',
+ '4h',
+ '6h',
+ '12h',
+ '1d'
+ )
+ }
+
- return(all_intervals$labels)
+ )
- } else {
- # Select the specified interval
- selectedInterval <- all_intervals$values[
- grepl(paste0('^', interval, '$'), all_intervals$values)
- ]
+ if (all) { return(interval_label) }
- return(selectedInterval)
+ interval_actual[
+ interval_label %in% interval
+ ]
- }
}
# 3) define response object and format; ####
@@ -146,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(
@@ -185,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
+
+ )
}
)
@@ -229,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')
}
@@ -265,7 +313,7 @@ binanceParameters <- function(
symbol = ticker,
interval = binanceIntervals(
interval = interval,
- futures = futures,
+ futures = futures,
type = type
),
limit = if (futures) 1500 else 1000
@@ -274,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
)
@@ -302,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 9d6ac225..0961f30a 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-v2.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'
+
+ }
}
@@ -54,8 +49,9 @@ bitmartIntervals <- function(
...) {
# Define all intervals in a data frame
- allIntervals <- data.frame(
- labels = c(
+ if (futures) {
+
+ interval_label <- c(
'1m',
'3m',
'5m',
@@ -69,8 +65,9 @@ bitmartIntervals <- function(
'1d',
'3d',
'1w'
- ),
- values = c(
+ )
+
+ interval_actual <- c(
1,
3,
5,
@@ -85,20 +82,49 @@ bitmartIntervals <- function(
4320,
10080
)
- )
- if (all) {
+ } else {
- return(allIntervals$labels)
+ interval_label <- c(
+ '1m',
+ # '3m',
+ '5m',
+ '15m',
+ '30m',
+ '1h',
+ '2h',
+ '4h',
+ # '6h',
+ # '12h',
+ '1d',
+ # '3d',
+ '1w'
+ )
- } else {
- # Locate and return the chosen interval value
- selectedInterval <- allIntervals$values[
- allIntervals$labels == interval
- ]
+ interval_actual <- c(
+ 1,
+ # 3,
+ 5,
+ 15,
+ 30,
+ 60,
+ 120,
+ 240,
+ # 360,
+ # 720,
+ 1440,
+ # 4320,
+ 10080
+ )
- return(selectedInterval)
}
+
+ if (all) { return(interval_label) }
+
+ interval_actual[
+ interval_label %in% interval
+ ]
+
}
# 3) define response object and format; ####
@@ -107,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
+
+ }
+ )
+
+ }
}
@@ -166,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 ed2ff55b..eb135e20 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
new file mode 100644
index 00000000..e86708e4
--- /dev/null
+++ b/R/api_cryptocom.R
@@ -0,0 +1,223 @@
+# script: new_api_crypto.com
+# date: 2024-06-07
+# author: Serkan Korkmaz, serkor1@duck.com
+# objective: Create all necessary parameters
+# for a proper API call
+# script start;
+# 1) URLs and Endpoint; ####
+crypto.comUrl <- function(
+ futures = TRUE,
+ ...) {
+
+ # 1) define baseURL
+ # for each API
+ if (futures)
+ 'https://api.crypto.com/exchange/v1/public'
+ else
+ 'https://api.crypto.com/exchange/v1/public'
+
+}
+
+crypto.comEndpoint <- function(
+ type = 'ohlc',
+ futures = TRUE,
+ ...) {
+
+ switch(
+ EXPR = type,
+ ticker ={
+ 'get-instruments'
+ },
+ fundingrate = {
+ 'get-valuations'
+ },
+ # default value:
+ # klines
+ {
+ 'get-candlestick'
+ }
+ )
+
+}
+
+# 2) Available intervals; #####
+crypto.comIntervals <- function(
+ interval,
+ futures,
+ all = FALSE,
+ ...) {
+
+ # 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(interval_label) }
+
+ interval_actual[
+ interval_label %in% interval
+ ]
+
+}
+
+# 3) define response object and format; ####
+crypto.comResponse <- function(
+ type = 'ohlc',
+ futures,
+ ...) {
+
+ # mock response
+ # to avoid check error in
+ # unevaluated expressions
+ response <- NULL
+
+ switch(
+ EXPR = type,
+ ticker = {
+ list(
+ foo = function(response, futures) {
+
+ subset(
+ response$result$data,
+ response$result$data$tradable == TRUE &
+ response$result$data$inst_type %in% ifelse(
+ futures,
+ c("PERPETUAL_SWAP", "FUTURE"),
+ "CCY_PAIR"
+
+ )
+ )$symbol
+ }
+ )
+ },
+
+ fundingrate = {
+ list(
+ colum_names = "funding_rate",
+ index_location = c(2),
+ colum_location = c(1)
+ )
+ },
+ {
+ list(
+ colum_names = c('open', 'high', 'low', 'close', 'volume'),
+ colum_location = 1:5,
+ index_location = 6
+ )
+ }
+ )
+
+}
+
+# 4) Dates passed to and from endpoints; ####
+crypto.comDates <- function(
+ futures,
+ dates,
+ is_response = FALSE,
+ ...) {
+
+ # 0) set multiplier based
+ # on market
+ multiplier <- 1e3
+
+ # 1) if its a response
+ if (is_response) {
+
+ dates <- convert_date(
+ x = as.numeric(dates),
+ multiplier = multiplier
+ )
+
+ } else {
+
+ # Convert dates and format
+ dates <- format(
+ convert_date(
+ x = dates,
+ multiplier = multiplier
+ ),
+ scientific = FALSE
+ )
+
+ names(dates) <- c('start_ts', 'end_ts')
+
+ }
+
+ dates
+
+}
+
+# 5) Parameters passed to endpoints; ####
+crypto.comParameters <- function(
+ futures = TRUE,
+ ticker,
+ type = NULL,
+ interval,
+ from = NULL,
+ to = NULL,
+ ...) {
+
+ # Initial parameter setup
+ params <- list(
+ instrument_name = ticker,
+ timeframe = crypto.comIntervals(
+ interval = interval,
+ futures = futures
+ ),
+ count = 5000
+ )
+
+ if (type == "fundingrate"){
+ params$valuation_type <- 'funding_hist'
+ }
+
+ # Add date parameters
+ date_params <- crypto.comDates(
+ futures = futures,
+ dates = c(from = from, to = to)
+ )
+
+ # Combine all parameters
+ params <- c(params, date_params)
+
+ # Return structured list with additional parameters
+ list(
+ query = params,
+ path = NULL,
+ futures = futures,
+ source = 'crypto.com',
+ ticker = ticker,
+ interval = interval
+ )
+}
+
+# script end;
diff --git a/R/api_huobi.R b/R/api_huobi.R
new file mode 100644
index 00000000..50643132
--- /dev/null
+++ b/R/api_huobi.R
@@ -0,0 +1,249 @@
+# script: api_huobi
+# date: 2024-06-07
+# author: Serkan Korkmaz, serkor1@duck.com
+# objective: Create all necessary parameters
+# for a proper API call
+# script start;
+# 1) URLs and Endpoint; ####
+huobiUrl <- function(
+ futures = TRUE,
+ ...) {
+
+ # 1) define baseURL
+ # for each API
+ if (futures)
+ 'https://api.hbdm.com'
+ else
+ 'https://api.huobi.pro'
+
+}
+
+huobiEndpoint <- function(
+ type = 'ohlc',
+ futures = TRUE,
+ top = FALSE) {
+
+ switch(
+ EXPR = type,
+ 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'
+ }
+ )
+}
+
+# 2) Available intervals; #####
+huobiIntervals <- function(
+ futures,
+ interval,
+ all = FALSE,
+ type,
+ ...) {
+
+ # 0) define intervals
+ # NOTE: These are common for
+ # both endpoints
+
+ interval_label <- c(
+ '1m',
+ '5m',
+ '15m',
+ '30m',
+ '1h',
+ '4h',
+ '1d',
+ '1w',
+ '1M'
+ )
+
+ interval_actual <- c(
+ "1min",
+ "5min",
+ "15min",
+ "30min",
+ "60min",
+ "4hour",
+ "1day",
+ "1week",
+ "1mon"
+ )
+
+
+ if (all) { return(interval_label) }
+
+ interval_actual[
+ interval_label %in% interval
+ ]
+
+}
+
+
+# 3) define response object and format; ####
+huobiResponse <- function(
+ type = 'ohlc',
+ futures,
+ ...) {
+
+ # mock response
+ # to avoid check error in
+ # unevaluated expressions
+ response <- NULL
+
+ switch(
+ EXPR = type,
+ 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(
+ 'open',
+ 'close',
+ 'high',
+ 'low',
+ 'volume'
+ )
+
+ else {
+ c(
+ 'open',
+ 'close',
+ 'low',
+ 'high',
+ 'volume'
+ )
+ },
+ colum_location = if (futures) c(2:5,7) else c(2:6),
+ index_location = c(
+ 1
+ )
+
+ )
+
+ }
+
+ )
+
+}
+
+# 4) Dates passed to and from endpoints; ####
+huobiDates <- function(
+ futures,
+ dates,
+ is_response = FALSE,
+ ...) {
+
+ # 0) Set multiplier
+ multiplier <- 1
+
+ # 1) determine wether
+ # its a response or request
+ if (is_response) {
+
+ # NOTE: The API returns
+ # in GMT+8 time and
+ # convert_date assumes that its
+ # UTC
+ dates <- convert_date(
+ x = as.numeric(dates),
+ multiplier = multiplier)
+
+ } else {
+
+ dates <- format(
+ convert_date(
+ x = dates,
+ multiplier = multiplier
+ ),
+ scientific = FALSE
+ )
+
+ names(dates) <- c('from', 'to')
+
+ }
+
+ dates
+
+}
+
+# 5) Parameters passed to endpoints; ####
+huobiParameters <- function(
+ futures = TRUE,
+ type = 'ohlc',
+ ticker,
+ interval,
+ from = NULL,
+ to = NULL,
+ ...) {
+
+ # Basic parameters common to both futures and non-futures
+ params <- list(
+ symbol = ticker,
+ period = huobiIntervals(
+ interval = interval,
+ futures = futures,
+ type = type
+ ),
+ size = 2000
+ )
+
+ if (futures) names(params)[1] <- "contract_code"
+
+ # Add date parameters
+ date_params <- huobiDates(
+ futures = futures,
+ dates = c(
+ from = from,
+ to = to
+ ),
+ is_response = FALSE
+ )
+
+ # Combine all parameters
+ params <- c(params)
+
+ # Return a structured list with additional common parameters
+ return(
+ list(
+ query = params,
+ path = NULL,
+ futures = futures,
+ source = 'huobi',
+ ticker = ticker,
+ interval = interval
+ )
+ )
+}
+
+# script end; ####
diff --git a/R/api_kraken.R b/R/api_kraken.R
index 78e220a7..ac7e4d4e 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) {
+ switch(EXPR = type, 'ohlc' = {
+ if (futures) {
+ # For futures, labels and values are the same
- # 2) return all
- # intervals
- interval <- all_intervals$labels
+ 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
+ interval_label <- c("1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "2w")
+ interval_actual <- c(1 , 5 , 15 , 30 , 60 , 240 , 1440, 10080, 21600)
- } else {
-
- 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) {
@@ -144,16 +116,14 @@ krakenResponse <- function(
} else {
- names(
- lapply(
- response$result,
- function(x) {
- if (x$status == 'online'){
- x$altname
- }
- }
- )
- )
+ unname(
+ obj = sapply(
+ response$result, function(x){
+ x$altname},
+ simplify = TRUE,
+ USE.NAMES = FALSE),
+ force = TRUE)
+
}
}
@@ -175,6 +145,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 +186,7 @@ krakenDates <- function(
yes = 1,
no = 1
)
- )
+ )
if (!futures) {
# Adjust for Spot market
@@ -276,7 +251,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 +290,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 950440af..82fae38e 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
new file mode 100644
index 00000000..d7bd210d
--- /dev/null
+++ b/R/api_mexc.R
@@ -0,0 +1,282 @@
+# script: new_api_mexc
+# date: 2023-12-18
+# author: Serkan Korkmaz, serkor1@duck.com
+# objective: Create all necessary parameters
+# for a proper API call
+# script start;
+# 1) URLs and Endpoint; ####
+mexcUrl <- function(
+ futures = TRUE,
+ ...) {
+
+ # 1) define baseURL
+ # for each API
+ if (futures)
+ 'https://contract.mexc.com'
+ else
+ 'https://api.mexc.com'
+
+}
+
+mexcEndpoint <- function(
+ type = 'ohlc',
+ futures = TRUE,
+ ...) {
+
+ switch(
+ EXPR = type,
+ ticker = {
+ if (futures)
+ 'api/v1/contract/detail'
+ else
+ 'api/v3/exchangeInfo'
+ },
+ fundingrate = {
+ 'api/v1/contract/funding_rate/history'
+ },
+ # Default values is
+ # the ohlc
+ {
+ if (futures)
+ 'api/v1/contract/kline/'
+ else
+ 'api/v3/klines'
+ }
+ )
+
+}
+
+# 2) Available intervals; #####
+mexcIntervals <- function(
+ interval,
+ futures,
+ all = FALSE,
+ ...) {
+
+ if (futures) {
+
+ interval_label <- c(
+ '1m',
+ '5m',
+ '15m',
+ '30m',
+ '1h',
+ '4h',
+ '8h',
+ '1d',
+ '1w',
+ "1M"
+ )
+
+ interval_actual <- c(
+ "Min1",
+ "Min5",
+ "Min15",
+ "Min30",
+ "Min60",
+ "Hour4",
+ "Hour8",
+ "Day1",
+ "Week1",
+ "Month1"
+ )
+
+ } else {
+
+ interval_label <- c(
+ '1m',
+ '5m',
+ '15m',
+ '30m',
+ '1h',
+ '4h',
+ '1d',
+ '1w',
+ '1M'
+ )
+
+ interval_actual <- c(
+ '1m',
+ '5m',
+ '15m',
+ '30m',
+ '60m',
+ '4h',
+ '1d',
+ '1W',
+ '1M'
+ )
+
+ }
+
+ if (all) { return(interval_label) }
+
+ interval_actual[
+ interval_label %in% interval
+ ]
+}
+
+# 3) define response object and format; ####
+mexcResponse <- function(
+ type = 'ohlc',
+ futures,
+ ...) {
+
+ # mock response
+ # to avoid check error in
+ # unevaluated expressions
+ response <- NULL
+
+ switch(
+ EXPR = type,
+ ticker = {
+ list(
+ foo = function(response, futures) {
+ if (futures) {
+ response$data$symbol
+
+ } else {
+ subset(
+ x = response$symbols,
+ response$symbols$status == "1"
+ )$symbol
+
+
+ }
+ }
+ )
+ },
+
+ fundingrate = {
+ list(
+ colum_names = "funding_rate",
+ 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
+ )
+ }
+ )
+
+}
+
+# 4) Dates passed to and from endpoints; ####
+mexcDates <- function(
+ futures,
+ dates,
+ is_response = FALSE,
+ type = "ohlc",
+ ...) {
+ # Set multiplier based on market
+ # and type
+ #
+ # If fundingrate the multiplier
+ # is 1e3
+ multiplier <- 1e3 / if (futures & !grepl("fundingrate", type)) 1e3 else 1
+
+ # Convert and format dates
+ dates <- convert_date(
+ x = if (is_response) as.numeric(dates) else dates,
+ multiplier = multiplier
+ )
+
+ if (!is_response) {
+ # Adjust for mexc spot and set names
+ dates <- as.numeric(dates)
+ dates[2] <- dates[2] + 15 * 60
+
+ if (!futures) {
+
+ names(dates) <- c('startTime', 'endTime')
+
+ } else {
+ # Set names for futures
+ names(dates) <- c('start', 'end')
+ }
+
+ dates <- format(dates, scientific = FALSE)
+
+ }
+
+ dates
+}
+
+
+
+# 5) Parameters passed to endpoints; ####
+mexcParameters <- function(
+ futures = TRUE,
+ ticker,
+ type = NULL,
+ interval,
+ from = NULL,
+ to = NULL,
+ ...) {
+
+ # Set initial parameters with interval and assign appropriate name
+ params <- list(
+ interval = mexcIntervals(
+ interval = interval,
+ futures = futures
+ )
+ )
+
+ # Add date parameters
+ params <- c(
+ params,
+ mexcDates(
+ futures = futures,
+ dates = c(from = from, to = to)
+ )
+ )
+
+ # Handle type-specific parameters
+ if (type == "fundingrate") {
+ params$symbol <- ticker
+ params$page_size <- 100
+ } else {
+ if (futures) {
+ params$path <- list(ticker)
+ } else {
+ params$symbol <- ticker
+ params$limit <- 1000
+ }
+ }
+
+ # Construct the final parameter list based on type
+ params <- switch(
+ EXPR = type,
+ fundingrate = list(
+ query = params,
+ futures = futures,
+ source = 'mexc',
+ interval = interval
+ ),
+ ohlc = list(
+ query = params,
+ path = if (futures) list(ticker) else NULL,
+ futures = futures,
+ source = 'mexc',
+ interval = interval
+ ),
+ # Default case to handle unexpected types
+ list(
+ query = params,
+ futures = futures,
+ source = 'mexc',
+ interval = interval
+ )
+ )
+
+ # Return the final structured parameter list
+ params
+}
+# script end;
diff --git a/R/available_exchanges.R b/R/available_exchanges.R
index 90772d44..507ae209 100644
--- a/R/available_exchanges.R
+++ b/R/available_exchanges.R
@@ -67,14 +67,38 @@ available_exchanges <- function(
)
)
- exchanges <- sort(
- switch(
- type,
- ohlc = c('binance', 'kucoin', 'kraken', 'bitmart', 'bybit'),
- fundingrate = c('binance', 'bybit', 'kucoin'),
- lsratio = c('binance', 'bybit', 'kraken'),
- interest = c('binance', 'bybit', 'kraken')
- )
+ exchanges <- sort(switch(
+ type,
+ fundingrate = c(
+ 'binance',
+ 'bybit',
+ 'kucoin',
+ 'crypto.com',
+ 'mexc'
+ ),
+ lsratio = c(
+ 'binance',
+ 'bybit',
+ 'kraken'
+ ),
+ interest = c(
+ 'binance',
+ 'bybit',
+ 'kraken'
+ ),
+ {
+ c(
+ 'binance',
+ 'kucoin',
+ 'kraken',
+ 'bitmart',
+ 'bybit',
+ 'crypto.com',
+ 'huobi',
+ 'mexc'
+ )
+ }
+ )
)
# 1) retun a message
diff --git a/R/available_intervals.R b/R/available_intervals.R
index 85ea3889..40cd233c 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 09c3ac46..f80af47a 100644
--- a/R/available_tickers.R
+++ b/R/available_tickers.R
@@ -70,17 +70,16 @@ 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'
)
)
-
# 2) get source_response
# objects
source_response <- get(
@@ -88,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/chart.R b/R/chart.R
index 7f7beecf..a5498d27 100644
--- a/R/chart.R
+++ b/R/chart.R
@@ -31,7 +31,8 @@
#'
#' **Sample Output**
#' \if{html}{
-#' \out{}\figure{README-chartquote-1.png}{options: style="width:750px;max-width:75\%;"}\out{}
+#' \out{}
+#' \figure{README-chartquote-1.png}{options: style="width:750px;max-width:75\%;"}\out{}
#' }
#' \if{latex}{
#' \out{\begin{center}}\figure{README-chartquote-1.png}\out{\end{center}}
@@ -42,12 +43,27 @@
#'
#' * \code{dark} A <[logical]>-value of [length] 1. [TRUE] by default.
#' Sets the overall theme of the [chart()]
+#'
#' * \code{slider} A <[logical]>-value of [length] 1. [FALSE] by default.
-#' If [TRUE], a [plotly::rangeslider()] is added
+#' If [TRUE], a [plotly::rangeslider()] is added.
+#'
#' * \code{deficiency} A <[logical]>-value of [length] 1. [FALSE] by default.
#' If [TRUE], all [chart()]-elements are colorblind friendly
+#'
#' * \code{size} A <[numeric]>-value of [length] 1. The relative size of the
-#' main chart. 0.6 by default. Must be between 0 and 1, non-inclusive
+#' main chart. 0.6 by default. Must be between 0 and 1, non-inclusive.
+#'
+#' * \code{scale} A <[numeric]>-value of [length] 1. 1 by default. Scales
+#' all fonts on the chart.
+#'
+#' * \code{width} A <[numeric]>-value of [length] 1. 0.9 by default. Sets
+#' the width of all line elements on the chart.
+#'
+#' * \code{static} A <[logical]>-value of [length] 1. [FALSE] by default. If [FALSE]
+#' the chart can be edited, annotated and explored interactively.
+#'
+#' * \code{palette} A <[character]>-vector of [length] 1. "hawaii" by default. See [hcl.pals()] for
+#' all possible color palettes.
#'
#' ## Charting Events
#'
@@ -66,9 +82,7 @@
#' @export
chart <- function(
ticker,
- main = list(
- kline()
- ),
+ main = kline(),
sub = list(),
indicator = list(),
event_data = NULL,
@@ -132,10 +146,14 @@ chart <- function(
## 1) set chart options
## globally (locally)
default_options <- list(
+ static = FALSE,
dark = TRUE,
slider = FALSE,
deficiency = FALSE,
- size = 0.6
+ palette = "hawaii",
+ scale = 1,
+ size = 0.6,
+ width = 0.9
)
options <- utils::modifyList(
@@ -144,13 +162,68 @@ chart <- function(
keep.null = TRUE
)
- dark <- options$dark
- deficiency <- options$deficiency
- slider <- options$slider
- size <- options$size
+ dark <- options$dark
+ deficiency <- options$deficiency
+ slider <- options$slider
+ size <- options$size
+ palette <- options$palette
+ static <- options$static
+ candle_color <- movement_color(deficiency = deficiency)
+ scale <- options$scale
+ width <- options$width
+
+ if (static) {
+
+ # if the plot is static
+ # then turn off modebar
+ # slider and editable
+
+ modebar <- slider <- editable <- FALSE
+
+ } else {
+
+ # the modebar and editable
+ # part of the plot should
+ # always be set to true
+ # for "real" interactivitiy
+ modebar <- editable <- TRUE
+
+
+ }
+
+ # assert inputs and options
+ assert(
+ any(grepl(pattern = palette,x = grDevices::hcl.pals(),ignore.case = TRUE)),
+ error_message = c(
+ "x" = sprintf(
+ fmt = "Palette {.val %s} is not valid.",
+ palette
+ ),
+ "i" = paste(
+ "Run",
+ cli::code_highlight(
+ code = "hcl.pals()",
+ code_theme = "chaos"
+ ),
+ "for valid values."
+ )
+ )
+ )
+
+ assert(
+ size > 0 & size < 1,
+ error_message = c(
+ "x" = sprintf(
+ fmt = "Got {.arg size} %s.",
+ size
+ ),
+ "i" = sprintf(
+ fmt = "{.arg size} has to be between 0 and 1, non-inclusive."
+ )
+ )
+ )
- candle_color <- movement_color(deficiency = deficiency)
# 1) generate list
# of calls for lazy
@@ -171,6 +244,7 @@ chart <- function(
.f$interval <- interval
.f$candle_color <- candle_color
.f$deficiency <- deficiency
+ .f$scale <- scale
eval(.f)
},
flatten(list(call_list$main, call_list$sub))
@@ -184,7 +258,7 @@ chart <- function(
if (!identical(call_list$indicator, list())) {
- plot_list[1] <- list(Reduce(
+ plot_list[1] <- list(Reduce(
f = function(plot, .f) {
# Modify the call list
.f$data <- ticker
@@ -230,7 +304,11 @@ chart <- function(
# hcl.colors are colorblind friendly. See:
# https://stackoverflow.com/questions/57153428/r-plot-color-combinations-that-are-colorblind-accessible
n_colors <- length(unlist(call_list))
- colorway <- grDevices::hcl.colors(n = n_colors)
+ # colorway <- grDevices::hcl.colors(n = n_colors)
+ colorway <- grDevices::hcl.colors(
+ n = n_colors,
+ palette = palette
+ )
plot_list <- lapply(
X = plot_list,
@@ -266,16 +344,55 @@ chart <- function(
}
)
+
)
- bar(
- dark = dark,
- plot = plot,
- name = name,
- market = market,
- date_range = paste(range(zoo::index(ticker)), collapse = " - ")
+ scatter_indices <- which(
+ sapply(
+ X = plot$x$data,
+ FUN = function(x) {
+ x$type == "scatter"
+ }
+ )
)
+ plot <- plotly::style(
+ p = plot,
+ line.width = width,
+ traces = scatter_indices
+ )
+
+
+ plot <- plotly::config(
+ p = bar(
+ dark = dark,
+ plot = plot,
+ name = name,
+ market = market,
+ date_range = paste(range(zoo::index(ticker)), collapse = " - "),
+ modebar = modebar,
+ scale = scale
+ ),
+ staticPlot = static,
+ editable = editable,
+ responsive = TRUE,
+ displayModeBar = modebar,
+ modeBarButtonsToAdd = c(
+ "drawline",
+ "drawrect",
+ "eraseshape"
+ ),
+ toImageButtonOptions = list(
+ format = "svg",
+ filename = "chart",
+ height = 2160,
+ width = 3840,
+ scale = 1
+ )
+ )
+
+ plot
+
}
# script end;
diff --git a/R/chart_fgi.R b/R/chart_fgi.R
index 6d0aa7c5..79b27c70 100644
--- a/R/chart_fgi.R
+++ b/R/chart_fgi.R
@@ -95,7 +95,7 @@ fgi <- function(
annotations = list(
text = "Fear and Greed Index",
font = list(
- size = 16
+ size = 16 * args$scale
),
showarrow = FALSE,
x = 0,
diff --git a/R/chart_lsr.R b/R/chart_lsr.R
index 601b9873..6513a9c2 100644
--- a/R/chart_lsr.R
+++ b/R/chart_lsr.R
@@ -92,7 +92,7 @@ lsr <- function(
annotations = list(
text = "Long-Short Ratio",
font = list(
- size = 16
+ size = 16 * args$scale
),
showarrow = FALSE,
x = 0,
diff --git a/R/chart_ma.R b/R/chart_ma.R
index a7a60485..432e6aec 100644
--- a/R/chart_ma.R
+++ b/R/chart_ma.R
@@ -41,7 +41,7 @@ chart_ma <- function(
#' `r lifecycle::badge("experimental")`
#'
#' A high-level [plotly::add_lines()]-wrapper function that
-#' interacts with [TTR]'s moving average family of functions.
+#' interacts with \{TTR\}'s moving average family of functions.
#' The function adds moving average indicators to the main [chart()].
#'
#' @usage sma(
@@ -52,7 +52,7 @@ chart_ma <- function(
#'
#' @inheritParams TTR::SMA
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::SMA].
+#' The name of the vector to passed into [TTR::SMA()].
#' @param ... For internal use. Please ignore.
#'
#' @example man/examples/scr_MAindicator.R
@@ -128,7 +128,7 @@ sma <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::EMA].
+#' The name of the vector to passed into [TTR::EMA()].
#' @inheritParams TTR::EMA
#' @param ... For internal use. Please ignore.
#'
@@ -206,7 +206,7 @@ ema <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::DEMA].
+#' The name of the vector to passed into [TTR::DEMA()].
#' @inheritParams TTR::DEMA
#' @param ... For internal use. Please ignore.
#'
@@ -286,7 +286,7 @@ dema <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::WMA].
+#' The name of the vector to passed into [TTR::WMA()].
#' @inheritParams TTR::WMA
#' @param ... For internal use. Please ignore.
#'
@@ -362,7 +362,7 @@ wma <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::EVWMA]
+#' The name of the vector to passed into [TTR::EVWMA()]
#' @inheritParams TTR::EVWMA
#' @param ... For internal use. Please ignore.
#'
@@ -434,7 +434,7 @@ evwma <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::ZLEMA].
+#' The name of the vector to passed into [TTR::ZLEMA()].
#' @inheritParams TTR::ZLEMA
#' @param ... For internal use. Please ignore.
#'
@@ -507,7 +507,7 @@ zlema <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::VWAP]
+#' The name of the vector to passed into [TTR::VWAP()]
#' @inheritParams TTR::VWAP
#' @param ... For internal use. Please ignore.
#'
@@ -583,7 +583,7 @@ vwap <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default.
-#' The name of the vector to passed into [TTR::HMA].
+#' The name of the vector to passed into [TTR::HMA()].
#' @inheritParams TTR::HMA
#' @param ... For internal use. Please ignore.
#'
@@ -654,7 +654,7 @@ hma <- function(
#' )
#'
#' @param price A [character]-vector of [length] 1. "close" by default
-#' The name of the vector to passed into [TTR::ALMA].
+#' The name of the vector to passed into [TTR::ALMA()].
#' @inheritParams TTR::ALMA
#' @param ... For internal use. Please ignore.
#'
diff --git a/R/chart_macd.R b/R/chart_macd.R
index e506def6..a0fc5611 100644
--- a/R/chart_macd.R
+++ b/R/chart_macd.R
@@ -149,7 +149,7 @@ macd <- function(
x = 0,
y = 1,
font = list(
- size = 16
+ size = 16 * args$scale
),
xref = 'paper',
yref = 'paper',
diff --git a/R/chart_rsi.R b/R/chart_rsi.R
index 9ee19906..36b43d25 100644
--- a/R/chart_rsi.R
+++ b/R/chart_rsi.R
@@ -150,7 +150,7 @@ rsi <- function(
list(
text = paste0("RSI(", n, ")"),
font = list(
- size = 16
+ size = 16 * args$scale
),
x = 0,
y = 1,
diff --git a/R/chart_smi.R b/R/chart_smi.R
index fc0d6e7a..11862bf7 100644
--- a/R/chart_smi.R
+++ b/R/chart_smi.R
@@ -176,7 +176,7 @@ smi <- function(
x = 0,
y = 1,
font = list(
- size = 16
+ size = 16 * args$scale
),
xref = 'paper',
yref = 'paper',
diff --git a/R/chart_volume.R b/R/chart_volume.R
index 6f756394..13fb3e94 100644
--- a/R/chart_volume.R
+++ b/R/chart_volume.R
@@ -69,7 +69,7 @@ volume <- function(
annotations = list(
text = "Volume",
font = list(
- size = 16
+ size = 16 * args$scale
),
showarrow = FALSE,
x = 0,
diff --git a/R/get_fgi.R b/R/get_fgi.R
index e56dfeb2..ea801b81 100644
--- a/R/get_fgi.R
+++ b/R/get_fgi.R
@@ -24,7 +24,7 @@
#'
#' @inherit get_quote details
#'
-#' @returns An [xts]-object containing,
+#' @returns An <[\link[xts]{xts}]>-object containing,
#'
#' \item{index}{<[POSIXct]> the time-index}
#' \item{fgi}{<[numeric]> the daily fear and greed index value}
@@ -110,7 +110,8 @@ get_fgindex <- function(
forced_dates <- default_dates(
interval = '1d',
from = from,
- to = to
+ to = to,
+ length = 201
)
# generate from
diff --git a/R/get_fundingrate.R b/R/get_fundingrate.R
index 73d74bb5..c180562f 100644
--- a/R/get_fundingrate.R
+++ b/R/get_fundingrate.R
@@ -24,7 +24,7 @@
#'
#' @inheritParams get_quote
#'
-#' @returns An [xts]-object containing,
+#' @returns An <[\link[xts]{xts}]>-object containing,
#'
#' \item{index}{<[POSIXct]> the time-index}
#' \item{funding_rate}{<[numeric]> the current funding rate}
@@ -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 db950b72..68306310 100644
--- a/R/get_lsratio.R
+++ b/R/get_lsratio.R
@@ -27,7 +27,7 @@
#' If [TRUE] it returns the top traders Long-Short ratios.
#'
#'
-#' @returns An [xts]-object containing,
+#' @returns An [xts::xts]-object containing,
#'
#' \item{index}{<[POSIXct]> the time-index}
#' \item{long}{<[numeric]> the share of longs}
@@ -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 94a75282..18bcc4cc 100644
--- a/R/get_openinterest.R
+++ b/R/get_openinterest.R
@@ -27,7 +27,7 @@
#' @inheritParams get_quote
#'
#' @returns
-#' An [xts]-object containing,
+#' An <[\link[xts]{xts}]>-object containing,
#'
#' \item{index}{<[POSIXct]> the time-index}
#' \item{open_interest}{<[numeric]> open perpetual contracts on both both sides}
@@ -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 8af50a2a..ca3fb42f 100644
--- a/R/get_quote.R
+++ b/R/get_quote.R
@@ -30,7 +30,7 @@
#' @param to An optional [character]-, [date]- or
#' [POSIXct]-vector of [length] 1. [NULL] by default.
#'
-#' @returns An [xts]-object containing,
+#' @returns An <[\link[xts]{xts}]>-object containing,
#'
#' \item{index}{<[POSIXct]> The time-index}
#' \item{open}{<[numeric]> Opening price}
@@ -71,61 +71,53 @@ 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)
- )
- ticker <- toupper(
- trimws(ticker)
- )
-
+ source <- tolower(trimws(source))
+ ticker <- trimws(ticker)
# 1) check wether
# the chosen exchange
@@ -151,6 +143,7 @@ get_quote <- function(
)
)
)
+
# 2) check wether the
# interval is supported by
# the exchange API
@@ -171,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."
)
)
)
@@ -217,28 +206,29 @@ 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)"
)
+ ohlc <- ohlc[
+ ,
+ c("open", "high", "low", "close", "volume")
+ ]
+
ohlc
}
diff --git a/R/helper.R b/R/helper.R
index caa2ff20..9f5cc8f6 100644
--- a/R/helper.R
+++ b/R/helper.R
@@ -60,9 +60,7 @@ indicator <- function(
zoo::fortify.zoo(
x,
- names = c(
- "index"
- )
+ names = "index"
)
}
@@ -237,6 +235,7 @@ infer_interval <- function(
"259200" = "3d",
"604800" = "1w",
"1209600" = "2w",
+ "1296000" = "2w",
"2678400" = "1M",
"2592000" = "1M",
"2419200" = "1M",
@@ -307,7 +306,7 @@ is.date <- function(x){
#' @param ... expressions >= 1. If named the names are used
#' as error messages, otherwise R's internal error-messages are thrown
#'
-#' @param error_message character. An error message, supports [cli]-formatting.
+#' @param error_message character. An error message, supports [cli::cli]-formatting.
#' @example man/examples/scr_assert.R
#' @seealso [stopifnot()], [cli::cli_abort()], [tryCatch()]
#' @keywords internal
@@ -330,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
@@ -339,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
@@ -407,8 +400,6 @@ assert <- function(..., error_message = NULL) {
}
-
-
pull <- function(
from,
what = "Open") {
@@ -550,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)
}
@@ -605,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) {
@@ -943,35 +935,37 @@ bar <- function(
name,
market,
date_range,
+ modebar,
+ scale,
...) {
# 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,
- margin = list(l = 5, r = 5, b = 5, t = 65),
+ margin = list(l = 5, r = 5, b = 5, t = if(modebar) 85 else 55),
paper_bgcolor = theme$paper_bgcolor,
plot_bgcolor = theme$plot_bgcolor,
font = list(
- size = 14,
+ size = 14 * scale,
color = theme$font_color
),
+ showlegend = TRUE,
legend = list(
orientation = 'h',
x = 0,
@@ -980,14 +974,14 @@ bar <- function(
title = list(
text = "Indicators:",
font = list(
- size = 16
+ size = 16 * scale
)
)
),
title = list(
text = title_text,
font = list(
- size = 20
+ size = 20 * scale
),
x = 1,
xref = "paper",
@@ -1099,8 +1093,7 @@ normalize <- function(
check_indicator_call <- function(
system_calls = sys.calls(),
- caller = match.call(envir = parent.frame())
- ) {
+ caller = match.call(envir = parent.frame())) {
# 0) get the entire call stack
# to determine the calling function
@@ -1110,9 +1103,11 @@ check_indicator_call <- function(
# 1) get the calling calling
# function, ie. SMA, EMA etc
+ calling_function <- sys.call(-1)
+
calling_function <- as.character(
- sys.call(-1)[[1]]
- )
+ calling_function[[1]]
+ )[length(calling_function)]
# 2) check the location
# of chart
diff --git a/R/store_xts.R b/R/store_xts.R
new file mode 100644
index 00000000..9314c2fe
--- /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 <[\link[xts]{xts}]>-object.
+#' @inheritParams zoo::write.zoo
+#' @inheritParams zoo::read.zoo
+#'
+#' @details
+#' When reading and writing <[\link[xts]{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 c948d611..42d38f0b 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -57,64 +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
-
-
-
}
@@ -126,8 +90,7 @@ source_parameters <- function(
interval,
from,
to,
- ...
-) {
+ ...) {
get(
paste0(
@@ -157,7 +120,7 @@ baseUrl <- function(
baseUrl <- get(paste0(source, 'Url'))(
futures = futures,
...
- )
+ )
# 2) return the baseUrl
return(
@@ -213,7 +176,7 @@ endPoint <- function(
#'
#' @returns
#'
-#' It returns an [xts]-object from the desired endpoint.
+#' It returns an [xts::xts]-object from the desired endpoint.
#'
#'
#' @author Serkan Korkmaz
@@ -313,18 +276,49 @@ fetch <- function(
#
# This could be done using do.call
# maybe
- switch (source,
- kraken = {
- response <- do.call(
- data.frame,
- response
+ 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)]]
)
},
- response <- response[[which(idx)]]
+ error = function(error) {
+ assert(
+ FALSE,
+ error_message = error_message
+ )
+ }
)
-
# 3) Extract source specific
# response parameters
parameters <- get(
@@ -339,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(
@@ -377,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 dff5a4b6..009519fc 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -13,11 +13,12 @@ knitr::opts_chunk$set(
comment = "#>",
fig.path = "man/figures/README-",
message = FALSE,
- warning = FALSE
+ warning = FALSE,
+ echo = FALSE
)
```
-# cryptoQuotes: Open access to cryptocurrency market data
+# {cryptoQuotes}: Open access to cryptocurrency market data
[![CRAN status](https://www.r-pkg.org/badges/version/cryptoQuotes)](https://CRAN.R-project.org/package=cryptoQuotes)
@@ -30,28 +31,63 @@ knitr::opts_chunk$set(
## :information_source: About
-The `cryptoQuotes`-package 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,
+[{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()
+ )
)
-# Convert the character vector to a single-row data frame
-exchanges_df <- data.frame(t(all_exchanges))
+# 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"
+ )
+ ,
+]
+
+```
-# Set column names to be the exchange names
-colnames(exchanges_df) <- all_exchanges
+
+
+```{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(DT$label, "")
+}
+
+# Convert the character vector to a single-row data frame
+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'
@@ -62,13 +98,15 @@ kableExtra::kable_styling(
```
-All data is returned as [`xts`](https://github.com/joshuaulrich/xts)-objects which enables seamless interaction with with [`quantmod`](https://github.com/joshuaulrich/quantmod) and [`TTR`](https://github.com/joshuaulrich/TTR), for developing and evaluating trading strategies or general purpose cryptocurrency market analysis with a historical or temporal perspective.
+All data is returned as [{xts}](https://github.com/joshuaulrich/xts)-objects which enables seamless interaction with with [{quantmod}](https://github.com/joshuaulrich/quantmod) and [{TTR}](https://github.com/joshuaulrich/TTR), for developing and evaluating trading strategies or general purpose cryptocurrency market analysis with a historical or temporal perspective.
## :information_source: Overview
-The `cryptoQuotes`-package has *two* main features; retrieving cryptocurrency market data, and charting. The market data consists of *OHLC-V* data and sentiment indicators; including, but not limited to, cryptocurrency *fear and greed index*, *long-short ratio* and *open interest*. All market data is retrieved using the family of `get_*`-functions. To get a full overview of the package and functionality please see the documentation on [pkgdown](https://serkor1.github.io/cryptoQuotes/).
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) has *two* main features; retrieving cryptocurrency market data, and charting. The market data consists of *OHLC-V* data and sentiment indicators; including, but not limited to, cryptocurrency *fear and greed index*, *long-short ratio* and *open interest*. All market data is retrieved using the family of `get_*`-functions. To get a full overview of the package and functionality please see the documentation via [{pkgdown}](https://serkor1.github.io/cryptoQuotes/).
-> **Note:** Given the nature of crypotcurrency data and general legislative restrictions, some `exchanges` may not work in your geolocation.
+> [!WARNING]
+>
+> Given the nature of crypotcurrency data and general legislative restrictions, some `exchanges` may not work in your geolocation.
Below is a quick overview of the package and basic usage examples on retrieving and charting Bitcoin (BTC) *OHLC-V* and *long-short ratio* in 30 minute intervals.
@@ -124,7 +162,7 @@ exchange <- firstup(
# 2) calculate available
# intervals
intervals <- sapply(
- exchange,
+ DT$value,
function(x){
suppressMessages(
{
@@ -158,7 +196,7 @@ granularity <- function(
)
{
sapply(
- exchange,
+ DT$value,
function(x){
suppressMessages(
{
@@ -222,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"
+ )
+
+
+ )
)
```
@@ -251,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
@@ -260,7 +305,7 @@ BTC <- cryptoQuotes::get_quote(
source = 'binance',
futures = FALSE,
interval = '30m',
- from = Sys.Date() - 1
+ from = Sys.Date() - 2
)
```
@@ -289,60 +334,85 @@ __________
#### Sentiment indicators
-The sentiment indicators available in the `cryptoQuotes`-package can be divided in two; *derived indicators* and *market indicators*. The former is calculated based on, for example, the price actions such as the *Moving Average Convergence Divergence* (MACD) indicator. The latter are public indicators such as the *long-short ratio* or *fear and greed index*; these are retrieved using the family of `get_*`-functions, while the derived indicators can be created using, for example, [`TTR`](https://github.com/joshuaulrich/TTR).
+The sentiment indicators available in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) can be divided in two; *derived indicators* and *market indicators*. The former is calculated based on, for example, the price actions such as the *Moving Average Convergence Divergence* (MACD) indicator. The latter are public indicators such as the *long-short ratio* or *fear and greed index*; these are retrieved using the family of `get_*`-functions, while the derived indicators can be created using, for example, [{TTR}](https://github.com/joshuaulrich/TTR).
-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 on [pkgdown](https://serkor1.github.io/cryptoQuotes/). All supported *market indicators* by exchange are listed in the table below,
+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:"
- )
+```{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"
+
+ )
+
+ )
)
```
@@ -352,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
@@ -360,7 +430,7 @@ BTC_LS <- cryptoQuotes::get_lsratio(
ticker = 'BTCUSDT',
source = 'binance',
interval = '30m',
- from = Sys.Date() - 1
+ from = Sys.Date() - 2
)
```
@@ -409,7 +479,7 @@ cryptoQuotes::chart(
)
```
-Charting in the `cryptoQuotes`-package is built on [`plotly`](https://github.com/plotly/plotly.R) for interactivity. It supports *light* and *dark* themes, and accounts for *color-deficiency* via the `options`-argument in the `chart()`-function.
+Charting in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) is built on [{plotly}](https://github.com/plotly/plotly.R) for interactivity. It supports *light* and *dark* themes, and accounts for *color-deficiency* via the `options`-argument in the `chart()`-function.
#### Charting with indicators
@@ -432,20 +502,21 @@ cryptoQuotes::chart(
cryptoQuotes::sma(n = 14),
cryptoQuotes::sma(n = 21),
cryptoQuotes::bollinger_bands()
+ ),
+ options = list(
+ static = TRUE
)
)
```
-
-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(),
@@ -460,18 +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',
@@ -480,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/',
@@ -490,4 +560,4 @@ devtools::install_github(
## :information_source: Code of Conduct
-Please note that the `cryptoQuotes` project is released with a [Contributor Code of Conduct](https://serkor1.github.io/cryptoQuotes/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms.
+Please note that the [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) project is released with a [Contributor Code of Conduct](https://serkor1.github.io/cryptoQuotes/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms.
diff --git a/README.md b/README.md
index 9ab287fd..c7534365 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-# cryptoQuotes: Open access to cryptocurrency market data
+# {cryptoQuotes}: Open access to cryptocurrency market data
@@ -19,38 +19,49 @@ stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://
## :information_source: About
-The `cryptoQuotes`-package 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 5 supported
-cryptocurrency exchanges,
+[{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 8 supported cryptocurrency exchanges,
-
+
-Supported exchanges
+All supported exchanges.
-binance
+Binance
|
-bitmart
+BitMart
|
-bybit
+Bybit
|
-kraken
+Crypto.com
|
+
+
-kucoin
+Huobi (HTX)
+ |
+
+Kraken
+ |
+
+KuCoin
+ |
+
+MEXC
|
@@ -59,27 +70,28 @@ kucoin
All data is returned as
-[`xts`](https://github.com/joshuaulrich/xts)-objects which enables
+[{xts}](https://github.com/joshuaulrich/xts)-objects which enables
seamless interaction with with
-[`quantmod`](https://github.com/joshuaulrich/quantmod) and
-[`TTR`](https://github.com/joshuaulrich/TTR), for developing and
+[{quantmod}](https://github.com/joshuaulrich/quantmod) and
+[{TTR}](https://github.com/joshuaulrich/TTR), for developing and
evaluating trading strategies or general purpose cryptocurrency market
analysis with a historical or temporal perspective.
## :information_source: Overview
-The `cryptoQuotes`-package has *two* main features; retrieving
-cryptocurrency market data, and charting. The market data consists of
-*OHLC-V* data and sentiment indicators; including, but not limited to,
-cryptocurrency *fear and greed index*, *long-short ratio* and *open
-interest*. All market data is retrieved using the family of
-`get_*`-functions. To get a full overview of the package and
-functionality please see the documentation on
-[pkgdown](https://serkor1.github.io/cryptoQuotes/).
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) has *two* main
+features; retrieving cryptocurrency market data, and charting. The
+market data consists of *OHLC-V* data and sentiment indicators;
+including, but not limited to, cryptocurrency *fear and greed index*,
+*long-short ratio* and *open interest*. All market data is retrieved
+using the family of `get_*`-functions. To get a full overview of the
+package and functionality please see the documentation via
+[{pkgdown}](https://serkor1.github.io/cryptoQuotes/).
-> **Note:** Given the nature of crypotcurrency data and general
-> legislative restrictions, some `exchanges` may not work in your
-> geolocation.
+> \[!WARNING\]
+>
+> Given the nature of crypotcurrency data and general legislative
+> restrictions, some `exchanges` may not work in your geolocation.
Below is a quick overview of the package and basic usage examples on
retrieving and charting Bitcoin (BTC) *OHLC-V* and *long-short ratio* in
@@ -95,135 +107,72 @@ exchanges,
-
-
-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)
- |
-
-
-
-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)
- |
-
-
+
+
+ Supported exchanges by spot and futures markets with available 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 (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) |
+
+
+
@@ -244,13 +193,13 @@ BTC <- cryptoQuotes::get_quote(
source = 'binance',
futures = FALSE,
interval = '30m',
- from = Sys.Date() - 1
+ from = Sys.Date() - 2
)
```
-
+
Bitcoin (BTC) OHLC-V data
@@ -279,122 +228,122 @@ volume
-2024-05-31 18:00:00
+2024-11-02 13:00:00
|
-67215.58
+69608
|
-67352.41
+69680
|
-66670
+69520
|
-67000.01
+69529.32
|
-2093.77494
+111.56801
|
-2024-05-31 18:30:00
+2024-11-02 13:30:00
|
-67000.01
+69529.32
|
-67221.53
+69626
|
-66876.8
+69390.09
|
-67200.01
+69459.05
|
-788.25693
+373.18153
|
-2024-05-31 19:00:00
+2024-11-02 14:00:00
|
-67200.01
+69459.05
|
-67459.66
+69711.59
|
-67160.3
+69393.22
|
-67417.98
+69679.99
|
-598.68095
+524.68007
|
-2024-05-31 19:30:00
+2024-11-02 14:30:00
|
-67417.98
+69679.99
|
-67455.93
+69680
|
-67287.97
+69358.87
|
-67342.77
+69365.99
|
-287.26257
+391.34893
|
-2024-05-31 20:00:00
+2024-11-02 15:00:00
|
-67342.77
+69366
|
-67444.47
+69437.85
|
-67288.2
+69099.96
|
-67305.5
+69151.19
|
-383.04002
+731.13778
|
-2024-05-31 20:30:00
+2024-11-02 15:30:00
|
-67305.5
+69151.2
|
-67427.68
+69253.16
|
-67170
+69112
|
-67400
+69112.01
|
-468.98276
+260.8502
|
@@ -408,112 +357,72 @@ volume
#### Sentiment indicators
-The sentiment indicators available in the `cryptoQuotes`-package can be
-divided in two; *derived indicators* and *market indicators*. The former
-is calculated based on, for example, the price actions such as the
-*Moving Average Convergence Divergence* (MACD) indicator. The latter are
-public indicators such as the *long-short ratio* or *fear and greed
-index*; these are retrieved using the family of `get_*`-functions, while
-the derived indicators can be created using, for example,
-[`TTR`](https://github.com/joshuaulrich/TTR).
+The sentiment indicators available in
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) can be divided
+in two; *derived indicators* and *market indicators*. The former is
+calculated based on, for example, the price actions such as the *Moving
+Average Convergence Divergence* (MACD) indicator. The latter are public
+indicators such as the *long-short ratio* or *fear and greed index*;
+these are retrieved using the family of `get_*`-functions, while the
+derived indicators can be created using, for example,
+[{TTR}](https://github.com/joshuaulrich/TTR).
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 on
-[pkgdown](https://serkor1.github.io/cryptoQuotes/). All supported
+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,
-
-
-Available sentiment indicators by exchange
-
-
-
-
-Endpoint
- |
-
-Binance
- |
-
-Bitmart
- |
-
-Bybit
- |
-
-Kraken
- |
-
-Kucoin
- |
-
-
-
-
-
-Long-Short Ratio
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-:white_check_mark:
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-
-
-Open Interest
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-:white_check_mark:
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-
-
-Funding Rate
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-:white_check_mark:
- |
-
-:x:
- |
-
-:white_check_mark:
- |
-
-
+
+
+ Available sentiment indicators by exchange
+
+
+ Endpoint |
+ Binance |
+ BitMart |
+ Bybit |
+ Crypto.com |
+ Huobi (HTX) |
+ Kraken |
+ KuCoin |
+ MEXC |
+
+
+
+ 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: |
+
+
+
@@ -533,13 +442,13 @@ BTC_LS <- cryptoQuotes::get_lsratio(
ticker = 'BTCUSDT',
source = 'binance',
interval = '30m',
- from = Sys.Date() - 1
+ from = Sys.Date() - 2
)
```
-
+
Long-Short Ratio on Bitcoin (BTC)
@@ -562,86 +471,86 @@ ls_ratio
-2024-05-31 18:00:00
+2024-11-02 13:00:00
|
-0.679
+0.523
|
-0.321
+0.477
|
-2.114
+1.098
|
-2024-05-31 18:30:00
+2024-11-02 13:30:00
|
-0.687
+0.52
|
-0.313
+0.48
|
-2.199
+1.085
|
-2024-05-31 19:00:00
+2024-11-02 14:00:00
|
-0.696
+0.517
|
-0.304
+0.483
|
-2.289
+1.07
|
-2024-05-31 19:30:00
+2024-11-02 14:30:00
|
-0.699
+0.519
|
-0.301
+0.481
|
-2.323
+1.077
|
-2024-05-31 20:00:00
+2024-11-02 15:00:00
|
-0.696
+0.519
|
-0.304
+0.481
|
-2.288
+1.08
|
-2024-05-31 20:30:00
+2024-11-02 15:30:00
|
-0.696
+0.52
|
-0.304
+0.48
|
-2.293
+1.085
|
@@ -655,51 +564,28 @@ ls_ratio
### :information_source: Charting
-Charting in the `cryptoQuotes`-package is built on
-[`plotly`](https://github.com/plotly/plotly.R) for interactivity. It
-supports *light* and *dark* themes, and accounts for *color-deficiency*
-via the `options`-argument in the `chart()`-function.
+Charting in [{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) is
+built on [{plotly}](https://github.com/plotly/plotly.R) for
+interactivity. It supports *light* and *dark* themes, and accounts for
+*color-deficiency* via the `options`-argument in the `chart()`-function.
#### Charting with indicators
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()
- )
-)
-```
-
-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(),
@@ -714,15 +600,11 @@ cryptoQuotes::chart(
cryptoQuotes::bollinger_bands()
),
options = list(
- deficiency = TRUE
+ static = TRUE
)
)
```
-
-
-------------------------------------------------------------------------
-
## :information_source: Installation
@@ -749,7 +631,8 @@ devtools::install_github(
## :information_source: Code of Conduct
-Please note that the `cryptoQuotes` project is released with a
-[Contributor Code of
+Please note that the
+[{cryptoQuotes}](https://serkor1.github.io/cryptoQuotes/) project is
+released with a [Contributor Code of
Conduct](https://serkor1.github.io/cryptoQuotes/CODE_OF_CONDUCT.html).
By contributing to this project, you agree to abide by its terms.
diff --git a/_pkgdown.yml b/_pkgdown.yml
index 6fc6aeaf..a8b6fcdf 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -5,14 +5,15 @@ home:
# description for google
description: >
- An open and unified access to cryptocurrency market data in R.
+ {cryptoQuotes}: Open access to cryptocurrency market data and
+ charts in R.
navbar:
- type: inverse
+ inverse: true
structure:
left: [intro, usecase, custom-indicators, reference, articles, news]
- right: [github]
+ right: [search, lightswitch, github]
components:
usecase:
@@ -32,14 +33,19 @@ navbar:
href: articles/03-article.html
- text: Converting xts-objects
href: articles/04-article.html
- - text: cryptoQuotes x quantmod and TTR
+ - text: '{cryptoQuotes} x {quantmod} and {TTR}'
href: articles/05-article.html
url: https://serkor1.github.io/cryptoQuotes/
template:
bootstrap: 5
- bootswatch: flatly
+ light-switch: true
+ bslib:
+ primary: "#0054AD"
+ border-radius: 0.5rem
+ btn-border-radius: 0.25rem
+ danger: "#A6081A"
reference:
- title: Cryptocurrency Market Data
diff --git a/codemeta.json b/codemeta.json
index 2e5c5836..d5417ad7 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",
+ "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 returned in 'xts'-format. 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/", "https://CRAN.R-project.org/package=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.2 (2024-10-31)",
"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,10 @@
},
"SystemRequirements": null
},
- "fileSize": "808.095KB",
+ "fileSize": "1455.164KB",
"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"]
+ "contIntegration": ["https://github.com/serkor1/cryptoQuotes/actions/workflows/R-CMD-check.yaml", "https://app.codecov.io/gh/serkor1/cryptoQuotes"],
+ "developmentStatus": "https://lifecycle.r-lib.org/articles/stages.html#stable",
+ "keywords": ["cryptocurrencies", "cryptocurrency", "cryptocurrency-exchanges", "r", "binance", "binance-api", "kucoin", "kucoin-api", "kraken-api", "kraken-exchange-api", "bitmart", "bybit", "bybit-api", "rstats", "rstats-package", "huobi", "huobi-api"]
}
diff --git a/cran-comments.md b/cran-comments.md
index 85b962e4..1d8a8914 100644
--- a/cran-comments.md
+++ b/cran-comments.md
@@ -1,11 +1,15 @@
## R CMD check results
-0 errors ✔ | 0 warnings ✔ | 1 note ✖
+0 errors ✔ | 0 warnings ✔ | 1 notes ✖
❯ checking installed package size ... NOTE
installed size is 13.1Mb
sub-directories of 1Mb or more:
- doc 11.9Mb
-
-This is beyond my technical knowledge, but here is a related SO-post:
-https://stackoverflow.com/questions/38639266/r-cmd-check-unusual-checking-installed-package-size-note
+ doc 11.7Mb
+ help 1.2Mb
+
+
+There is not provided a link to the API in the description as there is (currently) 8 different APIs embedded in the
+package. All API are documented in the wiki, pkgdown and vignettes.
+
+
diff --git a/cryptoQuotes.Rproj b/cryptoQuotes.Rproj
deleted file mode 100644
index 69fafd4b..00000000
--- a/cryptoQuotes.Rproj
+++ /dev/null
@@ -1,22 +0,0 @@
-Version: 1.0
-
-RestoreWorkspace: No
-SaveWorkspace: No
-AlwaysSaveHistory: Default
-
-EnableCodeIndexing: Yes
-UseSpacesForTab: Yes
-NumSpacesForTab: 2
-Encoding: UTF-8
-
-RnwWeave: Sweave
-LaTeX: pdfLaTeX
-
-AutoAppendNewline: Yes
-StripTrailingWhitespace: Yes
-LineEndingConversion: Posix
-
-BuildType: Package
-PackageUseDevtools: Yes
-PackageInstallArgs: --no-multiarch --with-keep.source
-PackageRoxygenize: rd,collate,namespace
diff --git a/man/alma.Rd b/man/alma.Rd
index 9e786902..4f27ccd3 100644
--- a/man/alma.Rd
+++ b/man/alma.Rd
@@ -14,7 +14,7 @@ alma(
}
\arguments{
\item{price}{A \link{character}-vector of \link{length} 1. "close" by default
-The name of the vector to passed into \link[TTR:MovingAverages]{TTR::ALMA}.}
+The name of the vector to passed into \code{\link[TTR:ALMA]{TTR::ALMA()}}.}
\item{n}{Number of periods to average over. Must be between 1 and
\code{nrow(x)}, inclusive.}
@@ -31,8 +31,8 @@ A \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that
-interacts with \link{TTR}'s moving average family of functions.
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that
+interacts with \{TTR\}'s moving average family of functions.
The function adds moving average indicators to the main \code{\link[=chart]{chart()}}.
}
\examples{
diff --git a/man/assert.Rd b/man/assert.Rd
index 6405a6c1..bb8f500a 100644
--- a/man/assert.Rd
+++ b/man/assert.Rd
@@ -10,7 +10,7 @@ assert(..., error_message = NULL)
\item{...}{expressions >= 1. If named the names are used
as error messages, otherwise R's internal error-messages are thrown}
-\item{error_message}{character. An error message, supports \link{cli}-formatting.}
+\item{error_message}{character. An error message, supports \link[cli:cli]{cli::cli}-formatting.}
}
\value{
\link{NULL} if all statements in ... are \link{TRUE}
diff --git a/man/available_tickers.Rd b/man/available_tickers.Rd
index 03197e3e..4c643465 100644
--- a/man/available_tickers.Rd
+++ b/man/available_tickers.Rd
@@ -19,8 +19,8 @@ and the specified market.
\strong{Sample output}
-\if{html}{\out{}}\preformatted{#> [1] "10000000AIDOGEUSDT" "1000000MOGUSDT" "10000COQUSDT"
-#> [4] "10000LADYSUSDT" "10000NFTUSDT" "10000SATSUSDT"
+\if{html}{\out{
}}\preformatted{#> [1] "10000000AIDOGEUSDT" "1000000BABYDOGEUSDT" "1000000MOGUSDT"
+#> [4] "1000000PEIPEIUSDT" "10000COQUSDT" "10000LADYSUSDT"
}\if{html}{\out{
}}
}
\description{
diff --git a/man/bollinger_bands.Rd b/man/bollinger_bands.Rd
index 29d5dbb8..0cf307a8 100644
--- a/man/bollinger_bands.Rd
+++ b/man/bollinger_bands.Rd
@@ -30,8 +30,8 @@ An \link{invisible} \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object.
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that interacts
-with the \code{\link[TTR:bollingerBands]{TTR::BBands()}}-function. The function adds bollinger bands
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that interacts
+with the \code{\link[TTR:BBands]{TTR::BBands()}}-function. The function adds bollinger bands
to the main \code{\link[=chart]{chart()}}.
}
\examples{
diff --git a/man/calibrate_window.Rd b/man/calibrate_window.Rd
index 74392426..e6992d60 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/chart.Rd b/man/chart.Rd
index 0625b19f..29e35ab1 100644
--- a/man/chart.Rd
+++ b/man/chart.Rd
@@ -6,7 +6,7 @@
\usage{
chart(
ticker,
- main = list(kline()),
+ main = kline(),
sub = list(),
indicator = list(),
event_data = NULL,
@@ -21,7 +21,7 @@ can be coerced to a \code{\link[xts:xts]{xts::xts()}}-object.}
\item{sub}{An optional \link{list} of \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-function(s).}
-\item{indicator}{An optional \link{list} of \code{\link[plotly:add_trace]{plotly::add_lines()}}-function(s).}
+\item{indicator}{An optional \link{list} of \code{\link[plotly:add_lines]{plotly::add_lines()}}-function(s).}
\item{event_data}{An optional \link{data.frame} with event line(s) to be added
to the \code{\link[=chart]{chart()}}. See \code{\link[=add_event]{add_event()}} for more details.}
@@ -33,7 +33,8 @@ A \code{\link[plotly:plot_ly]{plotly::plot_ly()}} object.
\strong{Sample Output}
\if{html}{
- \out{
}\figure{README-chartquote-1.png}{options: style="width:750px;max-width:75\%;"}\out{}
+ \out{
}
+ \figure{README-chartquote-1.png}{options: style="width:750px;max-width:75\%;"}\out{}
}
\if{latex}{
\out{\begin{center}}\figure{README-chartquote-1.png}\out{\end{center}}
@@ -54,11 +55,19 @@ indicators like \link{sma} and \link{bollinger_bands}.
\item \code{dark} A <\link{logical}>-value of \link{length} 1. \link{TRUE} by default.
Sets the overall theme of the \code{\link[=chart]{chart()}}
\item \code{slider} A <\link{logical}>-value of \link{length} 1. \link{FALSE} by default.
-If \link{TRUE}, a \code{\link[plotly:rangeslider]{plotly::rangeslider()}} is added
+If \link{TRUE}, a \code{\link[plotly:rangeslider]{plotly::rangeslider()}} is added.
\item \code{deficiency} A <\link{logical}>-value of \link{length} 1. \link{FALSE} by default.
If \link{TRUE}, all \code{\link[=chart]{chart()}}-elements are colorblind friendly
\item \code{size} A <\link{numeric}>-value of \link{length} 1. The relative size of the
-main chart. 0.6 by default. Must be between 0 and 1, non-inclusive
+main chart. 0.6 by default. Must be between 0 and 1, non-inclusive.
+\item \code{scale} A <\link{numeric}>-value of \link{length} 1. 1 by default. Scales
+all fonts on the chart.
+\item \code{width} A <\link{numeric}>-value of \link{length} 1. 0.9 by default. Sets
+the width of all line elements on the chart.
+\item \code{static} A <\link{logical}>-value of \link{length} 1. \link{FALSE} by default. If \link{FALSE}
+the chart can be edited, annotated and explored interactively.
+\item \code{palette} A <\link{character}>-vector of \link{length} 1. "hawaii" by default. See \code{\link[=hcl.pals]{hcl.pals()}} for
+all possible color palettes.
}
}
@@ -99,7 +108,7 @@ cryptoQuotes::chart(
# script end;
}
\seealso{
-Other chart indicators:
+Other chart indicators:
\code{\link{add_event}()},
\code{\link{alma}()},
\code{\link{bollinger_bands}()},
@@ -119,7 +128,7 @@ Other chart indicators:
\code{\link{wma}()},
\code{\link{zlema}()}
-Other price charts:
+Other price charts:
\code{\link{kline}()},
\code{\link{ohlc}()},
\code{\link{pline}()}
diff --git a/man/cryptoQuotes-package.Rd b/man/cryptoQuotes-package.Rd
index 70b8390e..eab101c1 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 returned in 'xts'-format. 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/dema.Rd b/man/dema.Rd
index f15aef88..99773e13 100644
--- a/man/dema.Rd
+++ b/man/dema.Rd
@@ -15,7 +15,7 @@ dema(
}
\arguments{
\item{price}{A \link{character}-vector of \link{length} 1. "close" by default.
-The name of the vector to passed into \link[TTR:MovingAverages]{TTR::DEMA}.}
+The name of the vector to passed into \code{\link[TTR:DEMA]{TTR::DEMA()}}.}
\item{n}{Number of periods to average over. Must be between 1 and
\code{nrow(x)}, inclusive.}
@@ -36,8 +36,8 @@ A \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that
-interacts with \link{TTR}'s moving average family of functions.
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that
+interacts with \{TTR\}'s moving average family of functions.
The function adds moving average indicators to the main \code{\link[=chart]{chart()}}.
}
\examples{
diff --git a/man/donchian_channel.Rd b/man/donchian_channel.Rd
index fcb1929f..106325d1 100644
--- a/man/donchian_channel.Rd
+++ b/man/donchian_channel.Rd
@@ -28,7 +28,7 @@ An \link{invisible} \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object.
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that interacts
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that interacts
with the \code{\link[TTR:DonchianChannel]{TTR::DonchianChannel()}}-function.
The function adds Donchian Channels
to the main \code{\link[=chart]{chart()}}.
diff --git a/man/ema.Rd b/man/ema.Rd
index b72b2a88..2cee2ad8 100644
--- a/man/ema.Rd
+++ b/man/ema.Rd
@@ -14,7 +14,7 @@ ema(
}
\arguments{
\item{price}{A \link{character}-vector of \link{length} 1. "close" by default.
-The name of the vector to passed into \link[TTR:MovingAverages]{TTR::EMA}.}
+The name of the vector to passed into \code{\link[TTR:EMA]{TTR::EMA()}}.}
\item{n}{Number of periods to average over. Must be between 1 and
\code{nrow(x)}, inclusive.}
@@ -33,8 +33,8 @@ A \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that
-interacts with \link{TTR}'s moving average family of functions.
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that
+interacts with \{TTR\}'s moving average family of functions.
The function adds moving average indicators to the main \code{\link[=chart]{chart()}}.
}
\examples{
diff --git a/man/evwma.Rd b/man/evwma.Rd
index cdfbcd98..59d5590f 100644
--- a/man/evwma.Rd
+++ b/man/evwma.Rd
@@ -12,7 +12,7 @@ evwma(
}
\arguments{
\item{price}{A \link{character}-vector of \link{length} 1. "close" by default.
-The name of the vector to passed into \link[TTR:MovingAverages]{TTR::EVWMA}}
+The name of the vector to passed into \code{\link[TTR:EVWMA]{TTR::EVWMA()}}}
\item{n}{Number of periods to average over. Must be between 1 and
\code{nrow(x)}, inclusive.}
@@ -25,8 +25,8 @@ A \code{\link[plotly:plot_ly]{plotly::plot_ly()}}-object
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
-A high-level \code{\link[plotly:add_trace]{plotly::add_lines()}}-wrapper function that
-interacts with \link{TTR}'s moving average family of functions.
+A high-level \code{\link[plotly:add_lines]{plotly::add_lines()}}-wrapper function that
+interacts with \{TTR\}'s moving average family of functions.
The function adds moving average indicators to the main \code{\link[=chart]{chart()}}.
}
\examples{
diff --git a/man/fetch.Rd b/man/fetch.Rd
index ca415c91..f9e0e8e8 100644
--- a/man/fetch.Rd
+++ b/man/fetch.Rd
@@ -40,7 +40,7 @@ contracts on both sides. See the \code{\link[=get_openinterest]{get_openinterest
\item{...}{additional parameters passed down the endpoint}
}
\value{
-It returns an \link{xts}-object from the desired endpoint.
+It returns an \link[xts:xts]{xts::xts}-object from the desired endpoint.
}
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
diff --git a/man/figures/NEWS-unnamed-chunk-2-1.png b/man/figures/NEWS-unnamed-chunk-2-1.png
index 40eb6491a47a921f7b5878aa610c1e8126fd993d..e5f292a57582a71789c7a90d9eaf476de78ba9b9 100644
GIT binary patch
literal 52554
zcmbSzbyQr<(
0avCd
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 s3Gwig>pH{a*XL-au<>#I)yw2ZJ#Br(
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 |