Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
1f45c5c
Use testthat
siegfried Dec 17, 2021
9e1577e
Use vcr
siegfried Dec 17, 2021
7985d76
Test spot klines
siegfried Dec 17, 2021
c9e6010
Move api_key and secret out
siegfried Dec 17, 2021
81f4f10
Rename fixtures
siegfried Dec 17, 2021
c1ea8d0
Test binance_exchange_info()
siegfried Dec 17, 2021
d6e5bf7
Test binance_sign
siegfried Dec 17, 2021
b9ef2fc
Move spot specific tests to test-spot.R
siegfried Dec 17, 2021
041d806
Test binance_ticks()
siegfried Dec 17, 2021
6c0dbcd
Test binance_trades()
siegfried Dec 17, 2021
24e8d64
Test binance_depth()
siegfried Dec 17, 2021
67aebbc
Test binance_ticker_price()
siegfried Dec 17, 2021
5a07086
Test binance_ticker_book()
siegfried Dec 17, 2021
97b1447
Test binance_avg_price()
siegfried Dec 17, 2021
1971762
Improve test
siegfried Dec 17, 2021
1b7d2cf
Move test helper as_time()
siegfried Dec 17, 2021
c824d1a
Fix binance_sign()
siegfried Dec 17, 2021
ae5fd01
Add base endpoint option of USDM
siegfried Dec 17, 2021
ef56b02
Add usdm_v1_ping()
siegfried Dec 17, 2021
61f6157
Add usdm_v1_time()
siegfried Dec 17, 2021
07b2727
Update RoxygenNote and the docs
siegfried Dec 17, 2021
7cb3992
Add usdm_v1_exhange_info()
siegfried Dec 17, 2021
655a57c
Rename as_time() to as_timestamp()
siegfried Dec 18, 2021
b4e1855
Improve constant structure
siegfried Dec 18, 2021
8eefa5c
Add more test for usdm_v1_exchange_info()
siegfried Dec 18, 2021
636d61d
Check base in binance_query() instead
siegfried Dec 18, 2021
eecd66c
Add usdm_v1_premium_index()
siegfried Dec 18, 2021
00c5e7f
Add scale_filter
siegfried Dec 18, 2021
bb7473e
Implement usdm_filter_check.PRICE_FILTER
siegfried Dec 18, 2021
f9248fc
Rename scale_filter to validate_scale
siegfried Dec 19, 2021
5f7ad6d
Check input
siegfried Dec 19, 2021
0f52361
Add usdm_filter_check.LOT_SIZE
siegfried Dec 19, 2021
e2e2ad6
Add usdm_filter_check.MARKET_LOT_SIZE
siegfried Dec 19, 2021
35aa331
Add filters of USDM order number
siegfried Dec 19, 2021
8318270
Add usdm_filter_check.PERCENT_PRICE
siegfried Dec 19, 2021
924173e
Add usdm_filter_check.MIN_NOTIONAL
siegfried Dec 19, 2021
7a56aea
Add log to USDM filters
siegfried Dec 19, 2021
f05ad9b
Move spot tests back to test-binance.R
siegfried Dec 19, 2021
45a661d
Add usdm_v1_new_order()
siegfried Dec 19, 2021
98e1018
Add usdm_limit_order()
siegfried Dec 19, 2021
3ca34e5
Update usdm_filter_check()
siegfried Dec 19, 2021
701de73
Test is_algo_order()
siegfried Dec 19, 2021
65a892f
Add usdm_v1_open_orders()
siegfried Dec 19, 2021
e1119a9
Add usdm_v1_filters()
siegfried Dec 19, 2021
f1f76a4
Add is_valid_usdm_order method
siegfried Dec 20, 2021
349aab4
Rename usdm_filter_types
siegfried Dec 20, 2021
be9a541
Implement open_long_limit() and open_short_limit()
siegfried Dec 20, 2021
abe4e9b
Add usdm_v2_position_risks
siegfried Dec 20, 2021
6685dfb
Improve documentation
siegfried Dec 20, 2021
7ba597a
Update document
siegfried Dec 20, 2021
4315076
Fix usdm_v1_open_orders result
siegfried Dec 20, 2021
dfbe308
Add close_long_limit()
siegfried Dec 20, 2021
78e992a
Fix document
siegfried Dec 20, 2021
299d856
Add close_short_limit()
siegfried Dec 20, 2021
6ede8d0
Improve usdm_v1_premium_index()
siegfried Dec 20, 2021
ac365a1
Improve usdm_order_context()
siegfried Dec 20, 2021
fd46a52
Add convert_position_risks()
siegfried Dec 20, 2021
ccde301
Remove warning
siegfried Dec 20, 2021
02dc732
Simplify execute_usdm_order()
siegfried Dec 20, 2021
d6b11ac
Make usdm_limit_order() simpler
siegfried Dec 20, 2021
79f01a5
Implement market orders
siegfried Dec 20, 2021
c1ca6a0
Implement usdm_v2_account()
siegfried Dec 21, 2021
15e2510
Add usdm_v1_change_initial_leverage()
siegfried Dec 21, 2021
18b3ec3
Implement usdm_v1_change_margin_type()
siegfried Dec 21, 2021
1f5c723
Export format() of orders
siegfried Dec 24, 2021
1523953
Implement TAKE_PROFIT_MARKET order with closePosition=true
siegfried Dec 27, 2021
0d951c9
Make symbol of usdm_v1_open_orders() optional
siegfried Dec 27, 2021
d31101c
Implement usdm_v1_cancel_order_by_id()
siegfried Dec 27, 2021
4373010
Implement stop market order with closePosition=true
siegfried Dec 27, 2021
3de6583
Implement usdm_v1_cancel_order_by_client_order_id()
siegfried Dec 30, 2021
b9a6084
Add binancer.recv_window option
siegfried Jan 12, 2022
398cb12
Change document
siegfried Jan 12, 2022
f343d4d
Remove assertive
siegfried Nov 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto
tests/fixtures/**/* -diff
7 changes: 6 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ Authors@R: c(
Encoding: UTF-8
Description: R client to the 'Binance' Public Rest API for data collection on cryptocurrencies, portfolio management and trading: <https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md>.
License: AGPL
RoxygenNote: 7.1.1
RoxygenNote: 7.2.3
Imports:
data.table,
httr,
digest,
snakecase,
stringr,
logger,
jsonlite
URL: https://daroczig.github.io/binancer/
Suggests:
vcr (>= 0.6.0),
testthat (>= 3.0.0)
Config/testthat/edition: 3
38 changes: 38 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Generated by roxygen2: do not edit by hand

S3method(execute_usdm_order,LIMIT)
S3method(execute_usdm_order,MARKET)
S3method(execute_usdm_order,STOP_MARKET)
S3method(execute_usdm_order,TAKE_PROFIT_MARKET)
S3method(format,LIMIT)
S3method(format,MARKET)
S3method(format,STOP_MARKET)
S3method(format,TAKE_PROFIT_MARKET)
export(binance_account)
export(binance_all_orders)
export(binance_avg_price)
Expand All @@ -26,6 +34,34 @@ export(binance_ticker_price)
export(binance_ticks)
export(binance_time)
export(binance_trades)
export(close_long_limit)
export(close_long_market)
export(close_short_limit)
export(close_short_market)
export(convert_position_risks)
export(execute_usdm_order)
export(long_stop_market_all)
export(long_take_profit_market_all)
export(open_long_limit)
export(open_long_market)
export(open_short_limit)
export(open_short_market)
export(short_stop_market_all)
export(short_take_profit_market_all)
export(usdm_v1_cancel_order_by_client_order_id)
export(usdm_v1_cancel_order_by_id)
export(usdm_v1_change_initial_leverage)
export(usdm_v1_change_margin_type)
export(usdm_v1_exchange_info)
export(usdm_v1_filters)
export(usdm_v1_new_order)
export(usdm_v1_open_orders)
export(usdm_v1_ping)
export(usdm_v1_premium_index)
export(usdm_v1_time)
export(usdm_v2_account)
export(usdm_v2_position_risks)
importFrom(data.table,"%chin%")
importFrom(data.table,":=")
importFrom(data.table,as.data.table)
importFrom(data.table,data.table)
Expand All @@ -42,6 +78,8 @@ importFrom(httr,content)
importFrom(httr,headers)
importFrom(jsonlite,fromJSON)
importFrom(logger,log_error)
importFrom(logger,log_warn)
importFrom(snakecase,to_snake_case)
importFrom(stringr,str_glue)
importFrom(utils,assignInMyNamespace)
importFrom(utils,getFromNamespace)
69 changes: 55 additions & 14 deletions R/binance.R
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
BINANCE <- list(
TYPE = c('LIMIT', 'MARKET',
'STOP_LOSS', 'STOP_LOSS_LIMIT',
'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT',
'LIMIT_MAKER'),
BASE = list(
SPOT = 'https://api.binance.com',
USDM = 'https://fapi.binance.com'
),
SPOT = list(
TIMEINFORCE = c('GTC', 'IOC', 'FOK'),
TYPE = c('LIMIT', 'MARKET',
'STOP_LOSS', 'STOP_LOSS_LIMIT',
'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT',
'LIMIT_MAKER')
),
USDM = list(
FILTER = c("PRICE_FILTER", "LOT_SIZE", "MARKET_LOT_SIZE",
"MAX_NUM_ORDERS", "MAX_NUM_ALGO_ORDERS",
"MIN_NOTIONAL", "PERCENT_PRICE"),
TIMEINFORCE = c('GTC', 'IOC', 'FOK', 'GTX'),
POSITION_SIDE = c('LONG', 'SHORT'),
TYPE = c('LIMIT', 'MARKET',
'STOP', 'STOP_MARKET',
'TAKE_PROFIT', 'TAKE_PROFIT_MARKET',
'TRAILING_STOP_MARKET')
),
SIDE = c('BUY', 'SELL'),
TIMEINFORCE = c('GTC', 'IOC', 'FOK'),
INTERVALS = c(
'1m', '3m', '5m', '15m', '30m',
'1h', '2h', '4h', '6h', '8h', '12h',
Expand Down Expand Up @@ -63,14 +80,23 @@ binance_check_credentials <- function() {

#' Sign the query string for Binance
#' @param params list
#' @return string
#' @param time optional string
#' @return list
#' @keywords internal
#' @importFrom digest hmac
#' @examples \dontrun{
#' signature(list(foo = 'bar', z = 4))
#' binance_sign(list(foo = 'bar', z = 4))
#' }
binance_sign <- function(params) {
params$timestamp <- timestamp()
binance_sign <- function(params,
time = timestamp()) {
params$timestamp <- time

recv_window <- getOption("binancer.recv_window")

if (!is.null(recv_window)) {
params$recvWindow <- recv_window
}

params$signature <- hmac(
key = binance_secret(),
object = paste(
Expand All @@ -80,6 +106,13 @@ binance_sign <- function(params) {
params
}

# Workaround: USDM Ping returns neither weight header
get_weight <- function(response) {
header <- headers(response)
w1 <- as.integer(header$`x-mbx-used-weight`)
w2 <- as.integer(header$`x-mbx-used-weight-1m`)
c(w1, w2, 1L)[1]
}

#' Request the Binance API
#' @param endpoint string
Expand All @@ -90,7 +123,7 @@ binance_sign <- function(params) {
#' @keywords internal
#' @importFrom httr headers add_headers content
#' @importFrom utils assignInMyNamespace
binance_query <- function(endpoint, method = 'GET',
binance_query <- function(endpoint, base = BINANCE$BASE$SPOT, method = 'GET',
params = list(), body = NULL, sign = FALSE,
retry = method == 'GET', content_as = 'parsed') {

Expand All @@ -99,6 +132,7 @@ binance_query <- function(endpoint, method = 'GET',
Sys.sleep(61 - as.integer(format(Sys.time(), "%S")))
}

base <- match.arg(base)
method <- match.arg(method)

if (isTRUE(sign)) {
Expand All @@ -109,13 +143,13 @@ binance_query <- function(endpoint, method = 'GET',
}

res <- query(
base = 'https://api.binance.com',
base = base,
path = endpoint,
method = method,
params = params,
config = config)

assignInMyNamespace('BINANCE_WEIGHT', as.integer(headers(res)$`x-mbx-used-weight`))
assignInMyNamespace('BINANCE_WEIGHT', get_weight(res))
res <- content(res, as = content_as)

if (content_as == 'parsed' & length(res) == 2 & !is.null(names(res))) {
Expand All @@ -126,6 +160,7 @@ binance_query <- function(endpoint, method = 'GET',

res
}
formals(binance_query)$base <- unlist(BINANCE$BASE, use.names = FALSE)
formals(binance_query)$method <- BINANCE$METHODS


Expand Down Expand Up @@ -379,6 +414,9 @@ binance_depth <- function(symbol, limit) {
#' @param symbol optional string
#' @return \code{data.table}
#' @export
#' @examples \dontrun{
#' binance_ticker_price('ETHUSDT')
#' }
binance_ticker_price <- function(symbol) {

# silence "no visible global function/variable definition" R CMD check
Expand All @@ -403,6 +441,9 @@ binance_ticker_price <- function(symbol) {
#' @return \code{data.table}
#' @export
#' @importFrom snakecase to_snake_case
#' @examples \dontrun{
#' binance_ticker_book('ETHUSDT')
#' }
binance_ticker_book <- function(symbol) {

if (!missing(symbol)) {
Expand Down Expand Up @@ -860,8 +901,8 @@ binance_new_order <- function(symbol, side, type, time_in_force, quantity, price
data.table(ord)
}
formals(binance_new_order)$side <- BINANCE$SIDE
formals(binance_new_order)$type <- BINANCE$TYPE
formals(binance_new_order)$time_in_force <- BINANCE$TIMEINFORCE
formals(binance_new_order)$type <- BINANCE$SPOT$TYPE
formals(binance_new_order)$time_in_force <- BINANCE$SPOT$TIMEINFORCE


#' Query order on the Binance account
Expand Down
144 changes: 144 additions & 0 deletions R/filter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#' @importFrom data.table %chin%
#' @importFrom logger log_warn
#' @importFrom stringr str_glue

# Validate both of price and quantity
# This should resolve the problem such as 200.1 %% 0.1 == 0.1
validate_scale <- function(x, min, max, step, digits = 8) {
a <- x - min
b <- a / step
round(b - round(b), digits) == 0 &&
x >= min && x <= max
}

usdm_order_context <- function(open_orders = usdm_v1_open_orders(),
premium_index = usdm_v1_premium_index()) {
structure(
list(
open_orders = open_orders,
premium_index = premium_index
),
class = "usdm_order_context"
)
}

order_number <- function(context) {
nrow(context$open_orders)
}

algo_order_number <- function(context) {
type <- NULL
nrow(context$open_orders[type %chin% algo_order_type, ])
}

mark_price <- function(context, symbol) {
markPrice <- NULL
symbol_q <- symbol
context$premium_index[symbol == symbol_q, markPrice]
}

binance_filter <- function(type, params) {
structure(
params,
class = type
)
}

usdm_filter_check <- function(self, order, context) {
UseMethod("usdm_filter_check")
}

log_if_false <- function(self, order, f) {
result <- f()

if (isFALSE(result)) {
log_warn("{format(self)} failed: {format(order)}")
}

result
}

format.PRICE_FILTER <- function(self) {
str_glue("PRICE_FILTER({self$minPrice}, {self$maxPrice}, {self$tickSize})")
}

usdm_filter_check.PRICE_FILTER <- function(self, order, context) {
log_if_false(self, order, function() {
validate_scale(order$price, self$minPrice, self$maxPrice, self$tickSize)
})
}

format.LOT_SIZE <- function(self) {
str_glue("LOT_SIZE({self$minQty}, {self$maxQty}, {self$stepSize})")
}

usdm_filter_check.LOT_SIZE <- function(self, order, context) {
log_if_false(self, order, function() {
validate_scale(order$quantity, self$minQty, self$maxQty, self$stepSize)
})
}

format.MARKET_LOT_SIZE <- function(self) {
str_glue("MARKET_LOT_SIZE({self$minQty}, {self$maxQty}, {self$stepSize})")
}

usdm_filter_check.MARKET_LOT_SIZE <- usdm_filter_check.LOT_SIZE

format.MAX_NUM_ORDERS <- function(self) {
str_glue("MAX_NUM_ORDERS({self$limit})")
}

order_number_check <- function(self, number) {
limit <- self$limit

number < limit
}

usdm_filter_check.MAX_NUM_ORDERS <- function(self, order, context) {
log_if_false(self, order, function() {
order_number_check(self, order_number(context))
})
}

format.MAX_NUM_ALGO_ORDERS <- function(self) {
str_glue("MAX_NUM_ALGO_ORDERS({self$limit})")
}

usdm_filter_check.MAX_NUM_ALGO_ORDERS <- function(self, order, context) {
log_if_false(self, order, function() {
order_number_check(self, algo_order_number(context))
})
}

format.PERCENT_PRICE <- function(self) {
str_glue("PERCENT_PRICE({self$multiplierDown}, {self$multiplierUp})")
}

usdm_filter_check.PERCENT_PRICE <- function(self, order, context) {
down <- self$multiplierDown
up <- self$multiplierUp

mark_price <- mark_price(context, order$symbol)

log_if_false(self, order, function() {
(is_buy(order) && order$price <= mark_price * up) ||
(is_sell(order) && order$price >= mark_price * down)
})
}

format.MIN_NOTIONAL <- function(self) {
str_glue("MIN_NOTIONAL({self$notional})")
}

usdm_filter_check.MIN_NOTIONAL <- function(self, order, context) {
notional <- self$notional
ref_price <- if (inherits(order, "LIMIT")) {
order$price
} else {
mark_price(context, order$symbol)
}

log_if_false(self, order, function() {
ref_price * order$quantity >= notional
})
}
Loading