bitflyer 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +36 -4
- data/lib/bitflyer.rb +2 -2
- data/lib/bitflyer/realtime/client.rb +8 -4
- data/lib/bitflyer/realtime/websocket.rb +63 -26
- data/lib/bitflyer/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0afb7c55d8bf1c0c68fdb043881e446ad9481b932a328c25cc68dadabbd935c4
|
4
|
+
data.tar.gz: 0d58c2aebb84b56f15bcc4bca7da2a2ace9f8c39ec6a6b87fa9460d03087d61f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af27099352002ac7983bb1655c6f7d04e606fa06494d76e37943c1c1343c4d6fdf987d235d36a863e63365e667ff7a61a7564fa81800f8ef6f9798b362c6df76
|
7
|
+
data.tar.gz: 6e5f176afd23d3c438a1a5ec679a9e5dbd0b1a30784ce91968ce29f2914d7cd5dcd3eec1b5a96e260a203aef4375c4a90b31b2a77b2fb78c3e6fdfe28ec70a19
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bitflyer (1.
|
4
|
+
bitflyer (1.2.0)
|
5
5
|
faraday (>= 0.14, < 1.4)
|
6
6
|
faraday_middleware (>= 0.12, < 1.1)
|
7
7
|
websocket-client-simple (~> 0.3.0)
|
@@ -30,7 +30,7 @@ GEM
|
|
30
30
|
method_source (~> 1.0)
|
31
31
|
rainbow (3.0.0)
|
32
32
|
rake (13.0.3)
|
33
|
-
regexp_parser (2.
|
33
|
+
regexp_parser (2.1.1)
|
34
34
|
rexml (3.2.4)
|
35
35
|
rspec (3.10.0)
|
36
36
|
rspec-core (~> 3.10.0)
|
@@ -45,7 +45,7 @@ GEM
|
|
45
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
46
|
rspec-support (~> 3.10.0)
|
47
47
|
rspec-support (3.10.2)
|
48
|
-
rubocop (1.
|
48
|
+
rubocop (1.11.0)
|
49
49
|
parallel (~> 1.10)
|
50
50
|
parser (>= 3.0.0.0)
|
51
51
|
rainbow (>= 2.2.2, < 4.0)
|
data/README.md
CHANGED
@@ -37,20 +37,52 @@ p private_client.positions # will print your positions
|
|
37
37
|
|
38
38
|
### Realtime API
|
39
39
|
|
40
|
+
#### Public events
|
41
|
+
|
40
42
|
Accessor format is like `{event_name}_{product_code}`.
|
41
43
|
You can set lambda to get realtime events.
|
42
44
|
|
43
45
|
`{event_name}` and `{product_code}` is defined at [client.rb](./lib/bitflyer/realtime/client.rb).
|
44
46
|
|
45
|
-
####
|
47
|
+
#### Private events
|
48
|
+
|
49
|
+
To subscribe to the private `child_order_events` and `parent_order_events`, pass your API key and secret when creating the `realtime_client`.
|
50
|
+
|
51
|
+
#### Connection status monitoring
|
46
52
|
|
53
|
+
The `ready` callback is called when the `realtime_client` is ready to receive events (after the socket connection is established, the optional authentication has succeeded, and the channels have been subscribed). If connection is lost, the `disconnected` callback is called, and reconnection is attempted automatically. When connection is restored, `ready` is called again.
|
54
|
+
|
55
|
+
#### Examples
|
56
|
+
|
57
|
+
For public events only:
|
47
58
|
```ruby
|
48
59
|
client = Bitflyer.realtime_client
|
49
|
-
client.ticker_btc_jpy = ->(json){ p json } # will print json object
|
60
|
+
client.ticker_btc_jpy = ->(json){ p json } # will print json object
|
61
|
+
client.executions_btc_jpy = ->(json){ p json }
|
62
|
+
# ...
|
63
|
+
```
|
64
|
+
|
65
|
+
For both public and private events:
|
66
|
+
```ruby
|
67
|
+
client = Bitflyer.realtime_client('YOUR_API_KEY', 'YOUR_API_SECRET')
|
68
|
+
# Private events:
|
69
|
+
client.child_order_events = ->(json){ p json }
|
70
|
+
client.parent_order_events = ->(json){ p json }
|
71
|
+
# Public events:
|
72
|
+
client.ticker_btc_jpy = ->(json){ p json }
|
50
73
|
client.executions_btc_jpy = ->(json){ p json }
|
51
|
-
# ...
|
74
|
+
# ...
|
75
|
+
```
|
76
|
+
|
77
|
+
Connection monitoring:
|
78
|
+
```ruby
|
79
|
+
client = Bitflyer.realtime_client
|
80
|
+
client.ready = -> { p "Client is ready to receive events" }
|
81
|
+
client.disconnected = ->(error) { p "Client got disconnected" }
|
52
82
|
```
|
53
83
|
|
84
|
+
|
85
|
+
|
54
86
|
## Contributing
|
55
87
|
|
56
88
|
Bug reports and pull requests are welcome on GitHub at https://github.com/unhappychoice/bitflyer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
@@ -80,4 +112,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|
80
112
|
|
81
113
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
82
114
|
|
83
|
-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
115
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
data/lib/bitflyer.rb
CHANGED
@@ -4,13 +4,17 @@ require_relative './websocket'
|
|
4
4
|
|
5
5
|
module Bitflyer
|
6
6
|
module Realtime
|
7
|
-
|
7
|
+
PUBLIC_EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
|
8
8
|
MARKETS = %w[BTC_JPY FX_BTC_JPY ETH_BTC BCH_BTC BTCJPY_MAT3M BTCJPY_MAT1WK BTCJPY_MAT2WK].freeze
|
9
|
-
|
9
|
+
PUBLIC_CHANNEL_NAMES = PUBLIC_EVENT_NAMES.product(MARKETS).map { |e, m| "#{e}_#{m}" }.freeze
|
10
|
+
PRIVATE_CHANNEL_NAMES = %w[child_order_events parent_order_events].freeze
|
11
|
+
CHANNEL_NAMES = (PUBLIC_CHANNEL_NAMES + PRIVATE_CHANNEL_NAMES).freeze
|
10
12
|
|
11
13
|
SOCKET_HOST = 'https://io.lightstream.bitflyer.com'
|
12
14
|
|
13
15
|
class Client
|
16
|
+
extend Forwardable
|
17
|
+
def_delegators :@websocket_client, :ready=, :disconnected=
|
14
18
|
attr_accessor :websocket_client, :ping_interval, :ping_timeout, :last_ping_at, :last_pong_at
|
15
19
|
|
16
20
|
Realtime::CHANNEL_NAMES.each do |channel_name|
|
@@ -19,8 +23,8 @@ module Bitflyer
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
def initialize
|
23
|
-
@websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST)
|
26
|
+
def initialize(key = nil, secret = nil)
|
27
|
+
@websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST, key: key, secret: secret)
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end
|
@@ -2,54 +2,55 @@
|
|
2
2
|
|
3
3
|
require 'websocket-client-simple'
|
4
4
|
require 'json'
|
5
|
+
require 'openssl'
|
5
6
|
|
6
7
|
module Bitflyer
|
7
8
|
module Realtime
|
8
9
|
class WebSocketClient
|
9
|
-
attr_accessor :websocket_client, :
|
10
|
-
:last_ping_at, :last_pong_at, :
|
10
|
+
attr_accessor :websocket_client, :channel_names, :channel_callbacks, :ping_interval, :ping_timeout,
|
11
|
+
:last_ping_at, :last_pong_at, :ready, :disconnected
|
11
12
|
|
12
|
-
def initialize(host:, debug: false)
|
13
|
+
def initialize(host:, key:, secret:, debug: false)
|
13
14
|
@host = host
|
15
|
+
@key = key
|
16
|
+
@secret = secret
|
14
17
|
@debug = debug
|
15
|
-
@error = nil
|
16
18
|
@channel_names = []
|
17
19
|
@channel_callbacks = {}
|
18
20
|
connect
|
21
|
+
start_monitoring
|
19
22
|
end
|
20
23
|
|
21
24
|
def subscribe(channel_name:, &block)
|
22
25
|
debug_log "Subscribe #{channel_name}"
|
23
26
|
@channel_names = (@channel_names + [channel_name]).uniq
|
24
27
|
@channel_callbacks[channel_name] = block
|
25
|
-
websocket_client.send "42#{['subscribe', channel_name].to_json}"
|
28
|
+
@websocket_client.send "42#{['subscribe', channel_name].to_json}"
|
26
29
|
end
|
27
30
|
|
28
31
|
def connect
|
29
32
|
@websocket_client = WebSocket::Client::Simple.connect "#{@host}/socket.io/?transport=websocket"
|
30
33
|
this = self
|
34
|
+
@websocket_client.on(:message) { |payload| this.handle_message(payload: payload) }
|
35
|
+
@websocket_client.on(:error) { |error| this.handle_error(error: error) }
|
36
|
+
@websocket_client.on(:close) { |error| this.handle_close(error: error) }
|
37
|
+
rescue SocketError => e
|
38
|
+
puts e
|
39
|
+
puts e.backtrace.join("\n")
|
40
|
+
end
|
31
41
|
|
42
|
+
def start_monitoring
|
32
43
|
Thread.new do
|
33
44
|
loop do
|
34
45
|
sleep 1
|
35
46
|
if @websocket_client&.open?
|
36
47
|
send_ping
|
37
48
|
wait_pong
|
49
|
+
else
|
50
|
+
reconnect
|
38
51
|
end
|
39
52
|
end
|
40
53
|
end
|
41
|
-
|
42
|
-
Thread.new do
|
43
|
-
loop do
|
44
|
-
sleep 1
|
45
|
-
next unless @error
|
46
|
-
|
47
|
-
reconnect
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
@websocket_client.on(:message) { |payload| this.handle_message(payload: payload) }
|
52
|
-
@websocket_client.on(:error) { |error| this.handle_error(error: error) }
|
53
54
|
end
|
54
55
|
|
55
56
|
def send_ping
|
@@ -70,23 +71,19 @@ module Bitflyer
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def reconnect
|
73
|
-
return
|
74
|
+
return if @websocket_client&.open?
|
74
75
|
|
75
76
|
debug_log 'Reconnecting...'
|
76
77
|
|
77
|
-
@error = nil
|
78
78
|
@websocket_client.close if @websocket_client.open?
|
79
79
|
connect
|
80
|
-
@channel_names.each do |channel_name|
|
81
|
-
debug_log "42#{{ subscribe: channel_name }.to_json}"
|
82
|
-
websocket_client.send "42#{['subscribe', channel_name].to_json}"
|
83
|
-
end
|
84
80
|
end
|
85
81
|
|
86
82
|
def handle_error(error:)
|
87
83
|
debug_log error
|
88
84
|
return unless error.is_a? Errno::ECONNRESET
|
89
85
|
|
86
|
+
@disconnected&.call(error)
|
90
87
|
reconnect
|
91
88
|
end
|
92
89
|
|
@@ -101,20 +98,60 @@ module Bitflyer
|
|
101
98
|
when 3 then receive_pong
|
102
99
|
when 41 then disconnect
|
103
100
|
when 42 then emit_message(json: body)
|
101
|
+
when 430 then authenticated(json: body)
|
104
102
|
end
|
105
103
|
rescue StandardError => e
|
106
104
|
puts e
|
107
105
|
puts e.backtrace.join("\n")
|
108
106
|
end
|
109
107
|
|
108
|
+
def handle_close(error:)
|
109
|
+
debug_log error
|
110
|
+
@disconnected&.call(error)
|
111
|
+
end
|
112
|
+
|
110
113
|
def setup_by_response(json:)
|
111
114
|
body = JSON.parse json
|
112
115
|
@ping_interval = body['pingInterval'].to_i || 25_000
|
113
116
|
@ping_timeout = body['pingTimeout'].to_i || 60_000
|
114
117
|
@last_ping_at = Time.now.to_i
|
115
118
|
@last_pong_at = Time.now.to_i
|
116
|
-
|
117
|
-
|
119
|
+
if @key && @secret
|
120
|
+
authenticate
|
121
|
+
else
|
122
|
+
subscribe_channels
|
123
|
+
@ready&.call
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def authenticate
|
128
|
+
debug_log 'Authenticate'
|
129
|
+
timestamp = Time.now.to_i
|
130
|
+
nonce = Random.new.bytes(16).unpack('H*').first
|
131
|
+
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, timestamp.to_s + nonce)
|
132
|
+
auth_params = {
|
133
|
+
api_key: @key,
|
134
|
+
timestamp: timestamp,
|
135
|
+
nonce: nonce,
|
136
|
+
signature: signature
|
137
|
+
}
|
138
|
+
@websocket_client.send "420#{['auth', auth_params].to_json}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def authenticated(json:)
|
142
|
+
if json == '[null]'
|
143
|
+
debug_log 'Authenticated'
|
144
|
+
subscribe_channels
|
145
|
+
@ready&.call
|
146
|
+
else
|
147
|
+
raise "Authentication failed: #{json}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def subscribe_channels
|
152
|
+
@channel_callbacks.each do |channel_name, _|
|
153
|
+
debug_log "42#{{ subscribe: channel_name }.to_json}"
|
154
|
+
@websocket_client.send "42#{['subscribe', channel_name].to_json}"
|
118
155
|
end
|
119
156
|
end
|
120
157
|
|
@@ -125,7 +162,7 @@ module Bitflyer
|
|
125
162
|
|
126
163
|
def disconnect
|
127
164
|
debug_log 'Disconnecting from server...'
|
128
|
-
@
|
165
|
+
@websocket_client.close
|
129
166
|
end
|
130
167
|
|
131
168
|
def emit_message(json:)
|
data/lib/bitflyer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitflyer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuji Ueki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|