binance-sdk 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d6b6fa1ee177c935d5342d2767c82c0c80d3c1befdebf4aaf4bf947fb5f01fbb
4
+ data.tar.gz: 3ea7bcadb01ef35b7d6c74b0849644efc4b5ea77f837a28150cdbbb3bbf0bbb4
5
+ SHA512:
6
+ metadata.gz: 9194ed2de6be9c11529b762588d940d3ba88774c0ff05661b6332cdab111f831cd73c8835717baaeda7165f1bdb5868019a95ab6f8fb9d44e0658d98f2a3aaf9
7
+ data.tar.gz: 62ed6aed957dad77dfd65f734221c79c6fc80713a0142657ffbbad4e2da296e24e1a25ae3f36992c3f3c96b938083355c395d7bf3f5e5b7417a1d974a0e7a068
@@ -0,0 +1,58 @@
1
+ module Binance
2
+ module SDK
3
+ class Account
4
+ class << self
5
+ def info!(recvWindow: 5000)
6
+ timestamp = Configuration.timestamp
7
+
8
+ params = { recvWindow: recvWindow, timestamp: timestamp }
9
+
10
+ response = Request.send!(
11
+ api_key_type: :read_info, path: "/sapi/v1/account/status",
12
+ params: params.delete_if { |key, value| value.nil? },
13
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
14
+ )
15
+
16
+ puts response
17
+ end
18
+
19
+ def balance!(recvWindow: 5000, currency: 'USDT')
20
+ timestamp = Configuration.timestamp
21
+
22
+ params = { recvWindow: recvWindow, timestamp: timestamp }
23
+
24
+ balance = Request.send!(
25
+ api_key_type: :read_info, path: Endpoints.fetch(:account_balance),
26
+ params: params.delete_if { |key, value| value.nil? },
27
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
28
+ )
29
+
30
+ if futures?
31
+ balance.find { |i| i[:asset] == currency }[:balance].to_f
32
+ else
33
+ balance[:balances].find { |i| i[:asset] == currency }[:free]
34
+ end
35
+ end
36
+
37
+ def positions
38
+ params = {
39
+ recvWindow: 60000,
40
+ timestamp: Configuration.timestamp
41
+ }
42
+
43
+ Request.send!(
44
+ api_key_type: :read_info, path: Endpoints.fetch(:account_position_risk),
45
+ params: params.delete_if { |key, value| value.nil? },
46
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ def futures?
53
+ Configuration.futures?
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ module Binance
2
+ module SDK
3
+ class Asset
4
+ class << self
5
+ def change_margin_type(symbol:, type: 'ISOLATED')
6
+ params = {
7
+ recvWindow: 60000,
8
+ timestamp: Configuration.timestamp.to_i - 1000,
9
+ symbol: symbol,
10
+ marginType: type
11
+ }
12
+
13
+ Request.send!(
14
+ method: :post,
15
+ api_key_type: :read_info, path: Endpoints.fetch(:margin_type),
16
+ params: params.delete_if { |_, value| value.nil? },
17
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
18
+ )
19
+ rescue Binance::SDK::Error::NoNeedToChangeMarginType
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ module Binance
2
+ module SDK
3
+ class Configuration
4
+ class << self
5
+ attr_writer :api_key, :locale, :read_info_api_key, :secret_key, :trading_api_key, :withdrawals_api_key, :futures, :testnet
6
+
7
+ def api_key(type: nil)
8
+ raise Error.new(message: "Invalid api_key type: #{type}.") unless type.nil? || API_KEY_TYPES.include?(type)
9
+
10
+ instance_api_key(type: type)
11
+ end
12
+
13
+ def secret_key
14
+ instance_variable_get("@secret_key") || ENV["BINANCE_SECRET_KEY"]
15
+ end
16
+
17
+ def futures?
18
+ (instance_variable_get("@futures") || ENV["BINANCE_FUTURES"]) == true
19
+ end
20
+
21
+ def spot?
22
+ !futures?
23
+ end
24
+
25
+ def testnet?
26
+ (instance_variable_get("@testnet") || ENV["BINANCE_TESTNET"]) == true
27
+ end
28
+
29
+ def signed_request_signature(payload:, api_secret_key: nil)
30
+ raise Error.new(message: "Environment variable 'BINANCE_SECRET_KEY' is required for signed requests.") unless api_secret_key || secret_key
31
+
32
+ digest = OpenSSL::Digest::SHA256.new
33
+ OpenSSL::HMAC.hexdigest(digest, api_secret_key || secret_key, payload)
34
+ end
35
+
36
+ def tld
37
+ tld = ENV["BINANCE_TLD"]&.downcase&.to_sym || :com
38
+ validate_tld!(tld)
39
+
40
+ tld
41
+ end
42
+
43
+ def timestamp
44
+ Time.now.strftime("%s%3N")
45
+ end
46
+
47
+ def validate_tld!(tld)
48
+ error_message = "Invalid tld (top-level-domain): #{tld}. Use one of: #{ALLOWED_TLDS.join(", ")}."
49
+
50
+ raise Error.new(message: error_message) unless ALLOWED_TLDS.include?(tld&.to_sym)
51
+ end
52
+
53
+ private
54
+
55
+ API_KEY_TYPES = [:none, :read_info, :trading, :withdrawals].freeze
56
+ ALLOWED_TLDS = [:com, :us].freeze
57
+
58
+ def instance_api_key(type: nil)
59
+ var = "#{type.to_s.downcase}_api_key".sub(/^\_/, "")
60
+
61
+ instance_variable_get("@#{var}")
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,43 @@
1
+ module Binance
2
+ module SDK
3
+ class Endpoints
4
+ class << self
5
+ def fetch(identifier)
6
+ Configuration.futures? ? future_endpoints[identifier] : spot_endpoints[identifier]
7
+ end
8
+
9
+ private
10
+
11
+ def future_endpoints
12
+ {
13
+ server_time: '/fapi/v1/time',
14
+ account_balance: '/fapi/v2/balance',
15
+ account_position_risk: '/fapi/v2/positionRisk',
16
+ exchange_info: '/fapi/v1/exchangeInfo',
17
+ klines: '/fapi/v1/klines',
18
+ put_order: '/fapi/v1/order',
19
+ cancel_orders: '/fapi/v1/allOpenOrders',
20
+ ticker_price: '/fapi/v1/ticker/price',
21
+ change_leverage: '/fapi/v1/leverage',
22
+ long_short_ratio: '/futures/data/globalLongShortAccountRatio',
23
+ taker_buy_sell_volume: '/futures/data/takerlongshortRatio',
24
+ margin_type: '/fapi/v1/marginType'
25
+ }
26
+ end
27
+
28
+ def spot_endpoints
29
+ {
30
+ server_time: '/sapi/v1/time',
31
+ account_balance: '/sapi/v2/balance',
32
+ account_position_risk: '/sapi/v2/positionRisk',
33
+ exchange_info: '/sapi/v1/exchangeInfo',
34
+ klines: '/sapi/v1/klines',
35
+ put_order: '/sapi/v1/order',
36
+ cancel_orders: '/sapi/v1/allOpenOrders',
37
+ ticker_price: '/sapi/v1/ticker/price',
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,138 @@
1
+ module Binance
2
+ module SDK
3
+ class Error < StandardError
4
+ attr_reader :code, :msg, :symbol
5
+
6
+ class << self
7
+ def is_error_response?(response:)
8
+ response.code >= 400
9
+ end
10
+
11
+ def localized(message)
12
+ code = message#.to_s.match(/\d+/).to_s.to_i
13
+ case code
14
+ when 1000 then Unknown
15
+ when 1001 then Disconnected
16
+ when 1002 then Unauthorized
17
+ when 1003 then TooManyRequests
18
+ when 1006 then UnexpectedResponse
19
+ when 1007 then Timeout
20
+ when 1013 then InvalidQuantity
21
+ when 1014 then UnknownOrderComposition
22
+ when 1015 then TooManyOrders
23
+ when 1016 then ServiceShuttingDown
24
+ when 1020 then UnsupportedOperation
25
+ when 1021 then InvalidTimestamp
26
+ when 1022 then InvalidSignature
27
+ when 1100 then IllegalChars
28
+ when 1101 then TooManyParameters
29
+ when 1102 then MandatoryParamEmptyOrMalformed
30
+ when 1103 then UnknownParam
31
+ when 1104 then UnreadParameters
32
+ when 1105 then ParamEmpty
33
+ when 1106 then ParamNotRequired
34
+ when 1111 then BadPrecision
35
+ when 1112 then NoDepth
36
+ when 1114 then TIFNotRequired
37
+ when 1115 then InvalidTIF
38
+ when 1116 then InvalidOrderType
39
+ when 1117 then InvalidSide
40
+ when 1118 then EmptyNewCLOrderId
41
+ when 1119 then EmptyOrgCLOrderId
42
+ when 1120 then BadInterval
43
+ when 1121 then BadSymbol
44
+ when 1125 then InvalidListenKey
45
+ when 1127 then IntervalTooLarge
46
+ when 1128 then OptionalParamsBadCombo
47
+ when 1130 then InvalidParameter
48
+ when 2007 then InvalidCallbackRate
49
+ when 2010 then NewOrderRejected
50
+ when 2011 then CancelOrderRejected
51
+ when 2013 then NoSuchOrder
52
+ when 2014 then BadAPIKeyFormat
53
+ when 2015 then RejectedAPIKey
54
+ when 2016 then NoTradingWindow
55
+ when -1021 then TimestampOutsideRecvWindow
56
+ when -4131 then CounterPartyBestPrice
57
+ when -4005 then QuantityGreaterThanMaxQuantity
58
+ when -1117 then InvalidSide
59
+ when -4046 then NoNeedToChangeMarginType
60
+ when -4061 then IncorrectOrderPositionSide
61
+ when -1120 then InvalidInterval
62
+ else Binance::SDK::Error
63
+ end
64
+ end
65
+ end
66
+
67
+ def initialize(code: nil, json: {}, message: nil, symbol: nil)
68
+ @code = code || json[:code]
69
+ @msg = message || json[:message]
70
+ @symbol = message || json[:symbol]
71
+ end
72
+
73
+ def inspect
74
+ message = ""
75
+
76
+ message += "(#{code}) " unless code.nil?
77
+ message += "@#{symbol} " unless symbol.nil?
78
+ message += "#{msg}" unless msg.nil?
79
+ end
80
+
81
+ def message
82
+ inspect
83
+ end
84
+
85
+ def to_s
86
+ inspect
87
+ end
88
+
89
+ class Unknown < Error; end
90
+ class Disconnected < Error; end
91
+ class Unauthorized < Error; end
92
+ class TooManyRequests < Error; end
93
+ class UnexpectedResponse < Error; end
94
+ class Timeout < Error; end
95
+ class InvalidQuantity < Error; end
96
+ class UnknownOrderComposition < Error; end
97
+ class TooManyOrders < Error; end
98
+ class ServiceShuttingDown < Error; end
99
+ class UnsupportedOperation < Error; end
100
+ class InvalidTimestamp < Error; end
101
+ class InvalidSignature < Error; end
102
+ class IllegalChars < Error; end
103
+ class TooManyParameters < Error; end
104
+ class MandatoryParamEmptyOrMalformed < Error; end
105
+ class UnknownParam < Error; end
106
+ class UnreadParameters < Error; end
107
+ class ParamEmpty < Error; end
108
+ class ParamNotRequired < Error; end
109
+ class BadPrecision < Error; end
110
+ class NoDepth < Error; end
111
+ class TIFNotRequired < Error; end
112
+ class InvalidTIF < Error; end
113
+ class InvalidOrderType < Error; end
114
+ class InvalidSide < Error; end
115
+ class EmptyNewCLOrderId < Error; end
116
+ class EmptyOrgCLOrderId < Error; end
117
+ class BadInterval < Error; end
118
+ class BadSymbol < Error; end
119
+ class InvalidListenKey < Error; end
120
+ class IntervalTooLarge < Error; end
121
+ class OptionalParamsBadCombo < Error; end
122
+ class InvalidParameter < Error; end
123
+ class NewOrderRejected < Error; end
124
+ class CancelOrderRejected < Error; end
125
+ class NoSuchOrder < Error; end
126
+ class BadAPIKeyFormat < Error; end
127
+ class RejectedAPIKey < Error; end
128
+ class NoTradingWindow < Error; end
129
+ class CounterPartyBestPrice < Error; end
130
+ class InvalidCallbackRate < Error; end
131
+ class QuantityGreaterThanMaxQuantity < Error; end
132
+ class TimestampOutsideRecvWindow < Error; end
133
+ class NoNeedToChangeMarginType < Error; end
134
+ class IncorrectOrderPositionSide < Error; end
135
+ class InvalidInterval < Error; end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,39 @@
1
+ module Binance
2
+ module SDK
3
+ class Exchange
4
+ class << self
5
+ def info!
6
+ timestamp = Configuration.timestamp
7
+
8
+ params = { recvWindow: 5000, timestamp: timestamp }
9
+
10
+ Request.send!(
11
+ api_key_type: :read_info, path: Endpoints.fetch(:exchange_info),
12
+ params: params.delete_if { |key, value| value.nil? },
13
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
14
+ )
15
+ end
16
+
17
+ def long_short_ratio(symbol:, period:, limit: 30)
18
+ params = { recvWindow: 5000, timestamp: Configuration.timestamp, symbol: symbol, period: period, limit: limit }
19
+
20
+ Request.send!(
21
+ api_key_type: :read_info, path: Endpoints.fetch(:long_short_ratio),
22
+ params: params.delete_if { |_, value| value.nil? },
23
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
24
+ )
25
+ end
26
+
27
+ def taker_buy_sell_volume(symbol:, period:, limit: 30)
28
+ params = { recvWindow: 5000, timestamp: Configuration.timestamp, symbol: symbol, period: period, limit: limit }
29
+
30
+ Request.send!(
31
+ api_key_type: :read_info, path: Endpoints.fetch(:taker_buy_sell_volume),
32
+ params: params.delete_if { |_, value| value.nil? },
33
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ module Binance
2
+ module SDK
3
+ class Klines
4
+ class << self
5
+ # [
6
+ # [
7
+ # 1499040000000, // Open time
8
+ # "0.01634790", // Open
9
+ # "0.80000000", // High
10
+ # "0.01575800", // Low
11
+ # "0.01577100", // Close
12
+ # "148976.11427815", // Volume
13
+ # 1499644799999, // Close time
14
+ # "2434.19055334", // Quote asset volume
15
+ # 308, // Number of trades
16
+ # "1756.87402397", // Taker buy base asset volume
17
+ # "28.46694368", // Taker buy quote asset volume
18
+ # "17928899.62484339" // Ignore.
19
+ # ]
20
+ # ]
21
+ def data(symbol, interval = '5m', start_time = nil, end_time = nil, limit = nil, recvWindow: 5000, force_float: false)
22
+ timestamp = Configuration.timestamp
23
+
24
+ params = {
25
+ recvWindow: recvWindow,
26
+ timestamp: timestamp,
27
+ symbol: symbol,
28
+ interval: interval,
29
+ startTime: start_time,
30
+ endTime: end_time,
31
+ limit: limit
32
+ }
33
+
34
+ response = Request.send!(
35
+ api_key_type: :read_info, path: Endpoints.fetch(:klines),
36
+ params: params.delete_if { |key, value| value.nil? },
37
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
38
+ )
39
+
40
+ force_float ? response.map! { |i| i.map(&:to_f) } : response
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,111 @@
1
+ module Binance
2
+ module SDK
3
+ class Orders
4
+ class << self
5
+ def put_order!(symbol:, type: 'TRAILING_STOP_MARKET', side: 'BUY', callback_rate: 4, quantity: 1.0, working_type: nil, position_side: 'BOTH')
6
+ params = {
7
+ recvWindow: 60000,
8
+ timestamp: Configuration.timestamp.to_i - 1000,
9
+ symbol: symbol,
10
+ side: side,
11
+ type: type,
12
+ quantity: quantity,
13
+ callbackRate: callback_rate,
14
+ workingType: working_type,
15
+ positionSide: position_side,
16
+ }
17
+
18
+ Request.send!(
19
+ method: :post,
20
+ api_key_type: :read_info, path: Endpoints.fetch(:put_order),
21
+ params: params.delete_if { |_, value| value.nil? },
22
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
23
+ )
24
+ end
25
+
26
+ def close_position!(symbol:, side:, quantity:)
27
+ params = {
28
+ recvWindow: 60000,
29
+ timestamp: Configuration.timestamp.to_i - 1000,
30
+ symbol: symbol,
31
+ side: side,
32
+ type: 'MARKET',
33
+ quantity: quantity,
34
+ positionSide: side,
35
+ }
36
+
37
+ Request.send!(
38
+ method: :post,
39
+ api_key_type: :read_info, path: Endpoints.fetch(:put_order),
40
+ params: params.delete_if { |_, value| value.nil? },
41
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
42
+ )
43
+ end
44
+
45
+ def stop_loss(symbol:, side:, stop_price:)
46
+ params = {
47
+ recvWindow: 60000,
48
+ timestamp: Configuration.timestamp.to_i - 1000,
49
+ symbol: symbol,
50
+ type: 'STOP',
51
+ side: side,
52
+ stopPrice: stop_price
53
+ }
54
+
55
+ Request.send!(
56
+ method: :post,
57
+ api_key_type: :read_info, path: Endpoints.fetch(:put_order),
58
+ params: params.delete_if { |_, value| value.nil? },
59
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
60
+ )
61
+ end
62
+
63
+ def cancel_order!(symbol:)
64
+ params = {
65
+ recvWindow: 60000,
66
+ timestamp: Configuration.timestamp.to_i - 1000,
67
+ symbol: symbol,
68
+ type: 'MARKET'
69
+ }
70
+
71
+ Request.send!(
72
+ method: :delete,
73
+ api_key_type: :read_info, path: Endpoints.fetch(:put_order),
74
+ params: params.delete_if { |_, value| value.nil? },
75
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
76
+ )
77
+ end
78
+
79
+ def cancel_orders!(symbol:)
80
+ params = {
81
+ recvWindow: 60000,
82
+ timestamp: Configuration.timestamp.to_i - 1000,
83
+ symbol: symbol
84
+ }
85
+
86
+ Request.send!(
87
+ method: :delete,
88
+ api_key_type: :read_info, path: Endpoints.fetch(:cancel_orders),
89
+ params: params.delete_if { |_, value| value.nil? },
90
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
91
+ )
92
+ end
93
+
94
+ def change_leverage(symbol:, leverage:)
95
+ params = {
96
+ symbol: symbol,
97
+ leverage: leverage.to_i,
98
+ timestamp: Configuration.timestamp
99
+ }
100
+
101
+ Request.send!(
102
+ method: :post,
103
+ api_key_type: :read_info, path: Endpoints.fetch(:change_leverage),
104
+ params: params,
105
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,81 @@
1
+ module Binance
2
+ module SDK
3
+ class Request
4
+ include HTTParty
5
+
6
+ class << self
7
+ SECURITY_TYPES = [:none, :trade, :user_data, :user_stream, :market_data, :margin].freeze
8
+
9
+ def send!(api_key_type: :none, headers: {}, method: :get, path: "/", params: {}, security_type: :none, tld: Configuration.tld, api_key: nil, api_secret_key: nil, testnet: false, futures: false)
10
+ Configuration.validate_tld!(tld)
11
+
12
+ if Configuration.testnet?
13
+ self.base_uri 'https://testnet.binancefuture.com'
14
+ else
15
+ if Configuration.futures?
16
+ self.base_uri 'https://fapi.binance.com'
17
+ else
18
+ self.base_uri 'https://api.binance.com'
19
+ end
20
+ end
21
+
22
+ raise Error.new(message: "Invalid security type #{security_type}") unless SECURITY_TYPES.include?(security_type)
23
+ all_headers = default_headers(api_key_type: api_key_type, security_type: security_type, api_key: api_key)
24
+ params.delete_if { |k, v| v.nil? }
25
+
26
+ if %w(trade user_data).include?(security_type&.to_s)
27
+ signature = signed_request_signature(params: params, api_secret_key: api_secret_key)
28
+ params.merge!(signature: signature)
29
+ end
30
+
31
+ case method
32
+ when :get
33
+ response = get(path, headers: all_headers, query: params)
34
+ when :post
35
+ response = post(path, query: params, headers: all_headers)
36
+ when :put
37
+ response = put(path, query: params, headers: all_headers)
38
+ when :delete
39
+ response = delete(path, query: params, headers: all_headers)
40
+ else
41
+ raise Error.new(message: "invalid http method used: #{method}")
42
+ end
43
+
44
+ process!(response: response || "{}")
45
+ end
46
+
47
+ private
48
+
49
+ def default_headers(api_key_type:, security_type:, api_key: nil)
50
+ headers = {}
51
+ headers["Content-Type"] = "application/json; charset=utf-8"
52
+ headers["X-MBX-APIKEY"] = (api_key || Configuration.api_key(type: api_key_type)) unless security_type == :none
53
+ headers
54
+ end
55
+
56
+ def process!(response:)
57
+ json = begin
58
+ JSON.parse(response.body, symbolize_names: true)
59
+ rescue JSON::ParserError => error
60
+ # binance 500 errors are html format
61
+
62
+ raise Error.new(message: "Error: #{error}")
63
+ end
64
+ raise Error.localized(json[:code]).new(json: json) if Error.is_error_response?(response: response)
65
+ json
66
+ end
67
+
68
+ def signed_request_signature(params:, api_secret_key: nil)
69
+ payload = params.map { |key, value| "#{key}=#{value}" }.join("&")
70
+ Configuration.signed_request_signature(payload: payload, api_secret_key: api_secret_key)
71
+ end
72
+
73
+ def base_url
74
+ return 'https://testnet.binancefuture.com' if testnet?
75
+
76
+ 'https://api.binance.com'
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,15 @@
1
+ module Binance
2
+ module SDK
3
+ class Server
4
+ class << self
5
+ def time
6
+ Request.send!(
7
+ api_key_type: :read_info, path: Endpoints.fetch(:server_time),
8
+ params: {},
9
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Binance
2
+ module SDK
3
+ class Ticker
4
+ class << self
5
+ def price(symbol)
6
+ timestamp = Configuration.timestamp
7
+
8
+ params = { recvWindow: 5000, timestamp: timestamp, symbol: symbol }
9
+
10
+ Request.send!(
11
+ api_key_type: :read_info, path: Endpoints.fetch(:ticker_price),
12
+ params: params.delete_if { |_, value| value.nil? },
13
+ security_type: :user_data, api_key: Configuration.api_key, api_secret_key: Configuration.secret_key
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binance
4
+ module SDK
5
+ VERSION = "1.0.4"
6
+ end
7
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binance
4
+ class WebSocket < Faye::WebSocket::Client
5
+ class Error < StandardError; end
6
+
7
+ def initialize(on_open: nil, on_close: nil)
8
+ websocket_api_url = if SDK::Configuration.futures?
9
+ 'wss://fstream.binance.com/stream'
10
+ else
11
+ 'wss://stream.binance.com:9443/stream'
12
+ end
13
+ super websocket_api_url, nil, ping: 180
14
+
15
+ @request_id_inc = 0
16
+ @user_stream_handlers = {}
17
+
18
+ on :open do |event|
19
+ on_open&.call(event)
20
+ end
21
+
22
+ on :message do |event|
23
+ process_data(event.data)
24
+ end
25
+
26
+ on :close do |event|
27
+ on_close&.call(event)
28
+ end
29
+ end
30
+
31
+ # stream name: <symbol>@kline_<interval>
32
+ #
33
+ # {
34
+ # "e": "kline", // Event type
35
+ # "E": 123456789, // Event time
36
+ # "s": "BTCUSDT", // Symbol
37
+ # "k": {
38
+ # "t": 123400000, // Kline start time
39
+ # "T": 123460000, // Kline close time
40
+ # "s": "BTCUSDT", // Symbol
41
+ # "i": "1m", // Interval
42
+ # "f": 100, // First trade ID
43
+ # "L": 200, // Last trade ID
44
+ # "o": "0.0010", // Open price
45
+ # "c": "0.0020", // Close price
46
+ # "h": "0.0025", // High price
47
+ # "l": "0.0015", // Low price
48
+ # "v": "1000", // Base asset volume
49
+ # "n": 100, // Number of trades
50
+ # "x": false, // Is this kline closed?
51
+ # "q": "1.0000", // Quote asset volume
52
+ # "V": "500", // Taker buy base asset volume
53
+ # "Q": "0.500", // Taker buy quote asset volume
54
+ # "B": "123456" // Ignore
55
+ # }
56
+ # }
57
+
58
+ def candlesticks!(symbols, interval, &on_receive)
59
+ symbols_fmt = symbols.is_a?(String) ? [symbols] : symbols
60
+ @candlesticks_handler = on_receive
61
+ subscribe(symbols_fmt.map { |s| "#{s.downcase}@kline_#{interval}" })
62
+ end
63
+
64
+ def user_data_stream!(listen_key, &on_receive)
65
+ @user_stream_handlers[listen_key] = on_receive
66
+ subscribe([listen_key])
67
+ end
68
+
69
+ private
70
+
71
+ def process_data(data)
72
+ json = JSON.parse(data, symbolize_names: true)
73
+ if json[:error]
74
+ raise Error.new("(#{json[:code]}) #{json[:msg]}")
75
+ elsif json.key?(:result)
76
+ # Binance stream connected successfully
77
+ else
78
+ case json[:data][:e]&.to_sym
79
+ when :kline
80
+ @candlesticks_handler&.call(json[:stream], json[:data])
81
+ when :outboundAccountPosition
82
+ when :balanceUpdate
83
+ when :executionReport
84
+ listen_key = json[:stream]
85
+ @user_stream_handlers[listen_key]&.call(listen_key, json[:data])
86
+ end
87
+ end
88
+ end
89
+
90
+ def request_id
91
+ @request_id_inc += 1
92
+ end
93
+
94
+ def subscribe(streams)
95
+ send(
96
+ {
97
+ method: 'SUBSCRIBE',
98
+ params: streams,
99
+ id: request_id
100
+ }.to_json
101
+ )
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require 'pry'
3
+ require 'active_support/core_ext/string'
4
+ require 'awrence'
5
+ require 'httparty'
6
+ require 'faye/websocket'
7
+ require 'dry/inflector'
8
+
9
+ Dry::Inflector.new do |inflections|
10
+ inflections.acronym 'SDK'
11
+ end
12
+
13
+ require_relative 'binance/sdk/version'
14
+ require_relative 'binance/sdk/error'
15
+ require_relative 'binance/sdk/configuration'
16
+ require_relative 'binance/sdk/request'
17
+ require_relative 'binance/sdk/endpoints'
18
+ require_relative 'binance/sdk/account'
19
+ require_relative 'binance/sdk/klines'
20
+ require_relative 'binance/sdk/ticker'
21
+ require_relative 'binance/sdk/exchange'
22
+ require_relative 'binance/sdk/orders'
23
+ require_relative 'binance/sdk/websocket'
24
+ require_relative 'binance/sdk/asset'
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binance-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Andrei Merfu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-01-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: awrence
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httparty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faye-websocket
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: eventmachine
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dry-inflector
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 6.1.4
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 6.1.4
125
+ description: Ruby SDK for Binance API
126
+ email:
127
+ - andrei@amgital.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - lib/binance-sdk.rb
133
+ - lib/binance/sdk/account.rb
134
+ - lib/binance/sdk/asset.rb
135
+ - lib/binance/sdk/configuration.rb
136
+ - lib/binance/sdk/endpoints.rb
137
+ - lib/binance/sdk/error.rb
138
+ - lib/binance/sdk/exchange.rb
139
+ - lib/binance/sdk/klines.rb
140
+ - lib/binance/sdk/orders.rb
141
+ - lib/binance/sdk/request.rb
142
+ - lib/binance/sdk/server.rb
143
+ - lib/binance/sdk/ticker.rb
144
+ - lib/binance/sdk/version.rb
145
+ - lib/binance/sdk/websocket.rb
146
+ homepage: https://github.com/andreimerfu/binance-sdk
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ allowed_push_host: https://rubygems.org
151
+ homepage_uri: https://github.com/andreimerfu/binance-sdk
152
+ source_code_uri: https://github.com/andreimerfu/binance-sdk
153
+ changelog_uri: https://github.com/andreimerfu/binance-sdk/changelog.md
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 3.1.0
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubygems_version: 3.3.3
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: binance-sdk-1.0.4
173
+ test_files: []