binance-connector-ruby 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'binance/websocket_base'
4
+
5
+ module Binance
6
+ class Spot
7
+ # Spot Websocket
8
+ class WebSocket < WebSocketBase
9
+ BASE_URL = 'wss://stream.binance.com:9443'
10
+
11
+ def initialize(options = {})
12
+ @base_url = options[:base_url] || BASE_URL
13
+ options[:base_url] = @base_url
14
+ super(options)
15
+ end
16
+
17
+ # Aggregate Trade Streams
18
+ # The Aggregate Trade Streams push trade information that is aggregated for a single taker order.
19
+ # Stream Name: <symbol>@aggTrade
20
+ # Update Speed: Real-time
21
+ #
22
+ # @param symbol [String]
23
+ # @see https://binance-docs.github.io/apidocs/spot/en/#aggregate-trade-streams
24
+ def agg_trade(symbol:, callbacks:)
25
+ url = "#{@base_url}/ws/#{symbol.downcase}@aggTrade"
26
+ create_connection(url, callbacks)
27
+ end
28
+
29
+ # Trade Streams
30
+ # The Trade Streams push raw trade information; each trade has a unique buyer and seller.
31
+ # Stream Name: <symbol>@trade
32
+ # Update Speed: Real-time
33
+ #
34
+ # @param symbol [String]
35
+ # @see https://binance-docs.github.io/apidocs/spot/en/#trade-streams
36
+ def trade(symbol:, callbacks:)
37
+ url = "#{@base_url}/ws/#{symbol.downcase}@trade"
38
+ create_connection(url, callbacks)
39
+ end
40
+
41
+ # Kline/Candlestick Streams
42
+ # The Kline/Candlestick Stream push updates to the current klines/candlestick every second.
43
+ # Stream Name: <symbol>@kline_<interval>
44
+ # Update Speed: 2000ms
45
+ #
46
+ # @param symbol [String]
47
+ # @param interval [String] 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
48
+ # @see https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-streams
49
+ def kline(symbol:, interval:, callbacks:)
50
+ url = "#{@base_url}/ws/#{symbol.downcase}@kline_#{interval}"
51
+ create_connection(url, callbacks)
52
+ end
53
+
54
+ # Individual Symbol Mini Ticker Stream or All Market Mini Tickers Stream
55
+ # 24hr rolling window mini-ticker statistics. These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs.
56
+ # Stream Name: <symbol>@miniTicker or !miniTicker@arr
57
+ # Update Speed: 1000ms
58
+ #
59
+ # @option symbol [String]
60
+ # @see https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-mini-ticker-stream
61
+ def mini_ticker(callbacks:, symbol: nil)
62
+ url = if symbol.nil?
63
+ "#{@base_url}/ws/!miniTicker@arr"
64
+ else
65
+ "#{@base_url}/ws/#{symbol.downcase}@miniTicker"
66
+ end
67
+ create_connection(url, callbacks)
68
+ end
69
+
70
+ # Individual Symbol Ticker Streams or All Market Tickers Stream
71
+ # 24hr rollwing window ticker statistics for a single symbol. These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs.
72
+ # Stream Name: <symbol>@ticker or !ticker@arr
73
+ # Update Speed: 1000ms
74
+ #
75
+ # @option symbol [String]
76
+ # @see https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-ticker-streams
77
+ def symbol_ticker(callbacks:, symbol: nil)
78
+ url = if symbol.nil?
79
+ "#{@base_url}/ws/!ticker@arr"
80
+ else
81
+ "#{@base_url}/ws/#{symbol.downcase}@ticker"
82
+ end
83
+ create_connection(url, callbacks)
84
+ end
85
+
86
+ # Individual Symbol Book Ticker Streams or All Book Tickers Stream
87
+ # Pushes any update to the best bid or ask's price or quantity in real-time for a specified symbol.
88
+ # Stream Name: <symbol>@bookTicker or !bookTicker
89
+ # Update Speed: Real-time
90
+ #
91
+ # @option symbol [String]
92
+ # @see https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-book-ticker-streams
93
+ def book_ticker(callbacks:, symbol: nil)
94
+ url = if symbol.nil?
95
+ "#{@base_url}/ws/!bookTicker"
96
+ else
97
+ "#{@base_url}/ws/#{symbol.downcase}@bookTicker"
98
+ end
99
+ create_connection(url, callbacks)
100
+ end
101
+
102
+ # Partial Book Depth Streams
103
+ # Top bids and asks, Valid are 5, 10, or 20.
104
+ # Stream Name: <symbol>@depth<levels> OR <symbol>@depth<levels>@100ms.
105
+ # Update Speed: 1000ms or 100ms
106
+ #
107
+ # @param symbol [String]
108
+ # @param levels [Integer] 5, 10, or 20.
109
+ # @param speed [String] 1000ms or 100ms
110
+ # @see https://binance-docs.github.io/apidocs/spot/en/#partial-book-depth-streams
111
+ def partial_book_depth(symbol:, levels:, speed:, callbacks:)
112
+ url = "#{@base_url}/ws/#{symbol.downcase}@depth#{levels}@#{speed}"
113
+ create_connection(url, callbacks)
114
+ end
115
+
116
+ # Diff. Depth Stream
117
+ # Top bids and asks, Valid are 5, 10, or 20.
118
+ # Stream Name: <symbol>@depth<levels> OR <symbol>@depth<levels>@100ms.
119
+ # Update Speed: 1000ms or 100ms
120
+ #
121
+ # @param symbol [String]
122
+ # @param speed [String] 1000ms or 100ms
123
+ # @see https://binance-docs.github.io/apidocs/spot/en/#diff-depth-stream
124
+ def diff_book_depth(symbol:, speed:, callbacks:)
125
+ url = "#{@base_url}/ws/#{symbol.downcase}@depth@#{speed}"
126
+ create_connection(url, callbacks)
127
+ end
128
+
129
+ # Subscribe to a stream manually
130
+ # subscribe(stream: "btcusdt@miniTicker") or
131
+ # subscribe(stream: ["btcusdt@miniTicker", "ethusdt@miniTicker"])
132
+ #
133
+ # @param stream [String|Array]
134
+ def subscribe(stream:, callbacks:)
135
+ url = if stream.is_a?(Array)
136
+ "#{@base_url}/stream?streams=#{stream.join('/')}"
137
+ else
138
+ "#{@base_url}/ws/#{stream}"
139
+ end
140
+
141
+ subscribe_to(url, callbacks)
142
+ end
143
+
144
+ alias user_data subscribe
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'binance/session'
4
+ require 'binance/authentication'
5
+ require 'binance/utils/validation'
6
+ require 'binance/utils/url'
7
+ require 'binance/error'
8
+ require 'binance/spot/blvt'
9
+ require 'binance/spot/bswap'
10
+ require 'binance/spot/c2c'
11
+ require 'binance/spot/fiat'
12
+ require 'binance/spot/futures'
13
+ require 'binance/spot/loan'
14
+ require 'binance/spot/margin'
15
+ require 'binance/spot/market'
16
+ require 'binance/spot/mining'
17
+ require 'binance/spot/savings'
18
+ require 'binance/spot/stream'
19
+ require 'binance/spot/subaccount'
20
+ require 'binance/spot/trade'
21
+ require 'binance/spot/wallet'
22
+ require 'binance/spot/websocket'
23
+
24
+ module Binance
25
+ # Spot class includes the following modules:
26
+ # - Blvt
27
+ # - Bswap
28
+ # - C2C
29
+ # - Fiat
30
+ # - Futures
31
+ # - Loan
32
+ # - Margin
33
+ # - Market
34
+ # - Mining
35
+ # - Saving
36
+ # - Stream
37
+ # - Subaccount
38
+ # - Trade
39
+ # - Wallet
40
+ # @see https://binance-docs.github.io/apidocs/spot/en/
41
+ class Spot
42
+ include Binance::Spot::Blvt
43
+ include Binance::Spot::Bswap
44
+ include Binance::Spot::C2C
45
+ include Binance::Spot::Fiat
46
+ include Binance::Spot::Futures
47
+ include Binance::Spot::Loan
48
+ include Binance::Spot::Margin
49
+ include Binance::Spot::Market
50
+ include Binance::Spot::Mining
51
+ include Binance::Spot::Savings
52
+ include Binance::Spot::Stream
53
+ include Binance::Spot::Subaccount
54
+ include Binance::Spot::Trade
55
+ include Binance::Spot::Wallet
56
+
57
+ def initialize(key: '', secret: '', **kwargs)
58
+ @session = Session.new kwargs.merge(key: key, secret: secret)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Binance
6
+ module Utils
7
+ module Faraday
8
+ # Custom parameter encoder
9
+ module CustomParamsEncoder
10
+ class << self
11
+ extend Forwardable
12
+ def_delegators :'Faraday::Utils', :escape, :unescape
13
+ end
14
+
15
+ def self.encode(params)
16
+ if params.nil?
17
+ nil
18
+ elsif params.is_a?(Array)
19
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
20
+ encode_array params
21
+ elsif params.respond_to?(:to_hash)
22
+ params = params.to_hash.map do |key, value|
23
+ key = key.to_s if key.is_a?(Symbol)
24
+ [key, value]
25
+ end
26
+ encode_array params
27
+ else
28
+ raise TypeError, "Can't encode #{params.class}."
29
+ end
30
+ end
31
+
32
+ def self.encode_array(params)
33
+ buffer = ''.dup
34
+ params.each do |key, value|
35
+ encoded_key = escape(key)
36
+ if Binance::Utils::Validation.invalid?(value)
37
+ buffer << "#{encoded_key}&"
38
+ elsif value.is_a?(Array)
39
+ value.each do |sub_value|
40
+ buffer << "#{encoded_key}=#{escape sub_value}&"
41
+ end
42
+ else
43
+ value = value.to_s if [true, false].include?(value)
44
+ buffer << "#{encoded_key}=#{value}&"
45
+ end
46
+ end
47
+ buffer.chomp '&'
48
+ end
49
+
50
+ # rubocop:disable Metrics/AbcSize
51
+ def self.decode(query)
52
+ return nil if query.nil?
53
+
54
+ param_pairs = split_query query
55
+ param_pairs.each_with_object({}) do |pair, accu|
56
+ key = unescape(pair[0])
57
+ value = pair[1] || true
58
+ value = unescape(value.to_str.gsub(/\+/, ' ')) if value.respond_to?(:to_str)
59
+ if accu[key].is_a?(Array)
60
+ accu[key] << value
61
+ elsif accu[key] # already a value for this key
62
+ accu[key] = [accu[key], value]
63
+ else
64
+ accu[key] = value
65
+ end
66
+ end
67
+ end
68
+ # rubocop:enable Metrics/AbcSize
69
+
70
+ def self.split_query(query)
71
+ (query.split('&').map do |pair|
72
+ pair.split('=', 2) if pair && !pair.empty?
73
+ end).compact
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Binance
6
+ module Utils
7
+ module Faraday
8
+ module Middleware
9
+ Signature = Struct.new(:app, :secret) do
10
+ def call(env)
11
+ hash = OpenSSL::HMAC.hexdigest(
12
+ OpenSSL::Digest.new('sha256'), secret, env.url.query
13
+ )
14
+ env.url.query = Url.add_param(env.url.query, 'signature', hash)
15
+ app.call env
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Binance
6
+ module Utils
7
+ module Faraday
8
+ module Middleware
9
+ Timestamp = Struct.new(:app) do
10
+ def call(env)
11
+ env.url.query = Url.add_param(
12
+ env.url.query, 'timestamp', DateTime.now.strftime('%Q')
13
+ )
14
+ app.call env
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './middleware/timestamp'
4
+ require_relative './middleware/signature'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binance
4
+ module Utils
5
+ # Url Utils
6
+ module Url
7
+ module_function
8
+
9
+ def build_query(params)
10
+ params.map do |key, value|
11
+ if value.is_a?(Array)
12
+ value.map { |v| "#{key}=#{v}" }.join('&')
13
+ else
14
+ "#{key}=#{value}"
15
+ end
16
+ end.join('&')
17
+ end
18
+
19
+ def add_param(query, key, value)
20
+ query = (query || '').dup
21
+ query << "&#{key}=#{value}"
22
+ query.delete_prefix('&')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binance
4
+ module Utils
5
+ # Client side validation
6
+ class Validation
7
+ class << self
8
+ def require_param(param_name, param_value)
9
+ raise Binance::RequiredParameterError.new(param_name, param_value) if invalid?(param_value)
10
+ end
11
+
12
+ def invalid?(param_value)
13
+ param_value.nil? ||
14
+ (array_or_hash?(param_value) && param_value.empty?) ||
15
+ (param_value.respond_to?(:to_str) && param_value.empty?)
16
+ end
17
+
18
+ private
19
+
20
+ def array_or_hash?(param_value)
21
+ param_value.is_a?(Array) || param_value.is_a?(Hash)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binance
4
+ VERSION = '1.0.1'
5
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'websocket-eventmachine-client'
5
+
6
+ module Binance
7
+ # Base Websocket
8
+ class WebSocketBase
9
+ def initialize(options = {})
10
+ @logger = options[:logger] || Logger.new($stdout)
11
+ @base_url = options[:base_url]
12
+ @ws_connection = nil
13
+ end
14
+
15
+ def create_connection(url, cbs)
16
+ @ws_connection = ::WebSocket::EventMachine::Client.connect(uri: url)
17
+ add_callbacks(cbs)
18
+ end
19
+
20
+ def subscribe_to(url, cbs)
21
+ create_connection(url, cbs)
22
+ end
23
+
24
+ private
25
+
26
+ def add_callbacks(cbs)
27
+ # check allowed callbacks
28
+ raise ArgumentError, 'Only :onopen, :onmessage, :onclose, :onping, :onpong, :onerror allowed' if %i[onopen onmessage onping onpong onerror onclose].union(cbs.keys).size > 6
29
+
30
+ onmessage(cbs)
31
+ onerror(cbs)
32
+ onping(cbs)
33
+ onpong(cbs)
34
+ onclose(cbs)
35
+ end
36
+
37
+ def onopen(cbs)
38
+ @ws_connection.onopen do
39
+ @logger.debug('connected to server')
40
+ event_callback(cbs, :onopen)
41
+ end
42
+ end
43
+
44
+ def onmessage(cbs)
45
+ @ws_connection.onmessage do |msg, type|
46
+ cbs[:onmessage].call(msg, type) if cbs.key?(:onmessage) && cbs[:onmessage].respond_to?(:call)
47
+ end
48
+ end
49
+
50
+ def onerror(cbs)
51
+ @ws_connection.onerror do |error|
52
+ @logger.error("Error occured: #{error}")
53
+ event_callback(cbs, :onerror, error)
54
+ end
55
+ end
56
+
57
+ def onping(cbs)
58
+ @ws_connection.onping do
59
+ @logger.info('Received ping from server')
60
+ @ws_connection.pong
61
+ @logger.info('Responded pong to server')
62
+ event_callback(cbs, :onping)
63
+ end
64
+ end
65
+
66
+ def onpong(cbs)
67
+ @ws_connection.onpong do
68
+ @logger.info('Received pong from server')
69
+ event_callback(cbs, :onpong)
70
+ end
71
+ end
72
+
73
+ def onclose(cbs)
74
+ @ws_connection.onclose do
75
+ @logger.info('Closed from server')
76
+ event_callback(cbs, :onclose)
77
+ end
78
+ end
79
+
80
+ def event_callback(cbs, event, data = nil)
81
+ cbs[event].call(data) if cbs.key?(event) && cbs[event].respond_to?(:call)
82
+ end
83
+ end
84
+ end
data/lib/binance.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'binance/version'
5
+ require 'binance/spot'
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binance-connector-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Binance
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: gem-release
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.8'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: websocket-eventmachine-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ description: ''
98
+ email:
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE.txt
105
+ - README.md
106
+ - lib/binance.rb
107
+ - lib/binance/authentication.rb
108
+ - lib/binance/error.rb
109
+ - lib/binance/session.rb
110
+ - lib/binance/spot.rb
111
+ - lib/binance/spot/blvt.rb
112
+ - lib/binance/spot/bswap.rb
113
+ - lib/binance/spot/c2c.rb
114
+ - lib/binance/spot/fiat.rb
115
+ - lib/binance/spot/futures.rb
116
+ - lib/binance/spot/loan.rb
117
+ - lib/binance/spot/margin.rb
118
+ - lib/binance/spot/market.rb
119
+ - lib/binance/spot/mining.rb
120
+ - lib/binance/spot/savings.rb
121
+ - lib/binance/spot/stream.rb
122
+ - lib/binance/spot/subaccount.rb
123
+ - lib/binance/spot/trade.rb
124
+ - lib/binance/spot/wallet.rb
125
+ - lib/binance/spot/websocket.rb
126
+ - lib/binance/utils/faraday/custom_params_encoder.rb
127
+ - lib/binance/utils/faraday/middleware.rb
128
+ - lib/binance/utils/faraday/middleware/signature.rb
129
+ - lib/binance/utils/faraday/middleware/timestamp.rb
130
+ - lib/binance/utils/url.rb
131
+ - lib/binance/utils/validation.rb
132
+ - lib/binance/version.rb
133
+ - lib/binance/websocket_base.rb
134
+ homepage: https://github.com/binance/binance-connector-ruby
135
+ licenses:
136
+ - MIT
137
+ metadata:
138
+ homepage_uri: https://github.com/binance/binance-connector-ruby
139
+ documentation_uri: https://binance-docs.github.io/apidocs/spot/en/
140
+ source_code_uri: https://github.com/binance/binance-connector-ruby
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: 2.5.0
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubygems_version: 3.1.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: This is a lightweight library that works as a connector to the Binance public
160
+ API.
161
+ test_files: []