bitflyer 0.2.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad1891278833ae56365ff0f8dff53ef02a512c99372e19ae289c7900cd8824fd
4
- data.tar.gz: f018a639ea7a0d9b7a40c35375c672ff34609a2093d5339d17f40e8928b1fc06
3
+ metadata.gz: 0afb7c55d8bf1c0c68fdb043881e446ad9481b932a328c25cc68dadabbd935c4
4
+ data.tar.gz: 0d58c2aebb84b56f15bcc4bca7da2a2ace9f8c39ec6a6b87fa9460d03087d61f
5
5
  SHA512:
6
- metadata.gz: b9dd7721a17208ead1e920543ed1ffe5b0730adb75b24fed09e9ed60b4ad210729adbc9959c60fc806c8bdf6f8cb2c8b3ca68f919849f7cb744aa89eefa5536f
7
- data.tar.gz: 643d7db5e486d3e787e9a4b5563b89b03c90b0732f88b8270837cecd9f8dba2eb68fabf2680ddc476f433cd1b4ff86d5c1e20bdef11e4a76a535b77e323b8923
6
+ metadata.gz: af27099352002ac7983bb1655c6f7d04e606fa06494d76e37943c1c1343c4d6fdf987d235d36a863e63365e667ff7a61a7564fa81800f8ef6f9798b362c6df76
7
+ data.tar.gz: 6e5f176afd23d3c438a1a5ec679a9e5dbd0b1a30784ce91968ce29f2914d7cd5dcd3eec1b5a96e260a203aef4375c4a90b31b2a77b2fb78c3e6fdfe28ec70a19
@@ -0,0 +1,33 @@
1
+ {
2
+ "files": [
3
+ "README.md"
4
+ ],
5
+ "imageSize": 100,
6
+ "commit": false,
7
+ "contributors": [
8
+ {
9
+ "login": "yemartin",
10
+ "name": "Yves-Eric Martin",
11
+ "avatar_url": "https://avatars.githubusercontent.com/u/139002?v=4",
12
+ "profile": "https://github.com/yemartin",
13
+ "contributions": [
14
+ "code"
15
+ ]
16
+ },
17
+ {
18
+ "login": "unhappychoice",
19
+ "name": "Yuji Ueki",
20
+ "avatar_url": "https://avatars.githubusercontent.com/u/5608948?v=4",
21
+ "profile": "http://blog.unhappychoice.com",
22
+ "contributions": [
23
+ "code"
24
+ ]
25
+ }
26
+ ],
27
+ "contributorsPerLine": 7,
28
+ "projectName": "bitflyer",
29
+ "projectOwner": "unhappychoice",
30
+ "repoType": "github",
31
+ "repoHost": "https://github.com",
32
+ "skipCi": true
33
+ }
@@ -0,0 +1,54 @@
1
+ version: 2.1
2
+
3
+ update_bundler: &update_bundler
4
+ run:
5
+ name: update bundler
6
+ command: gem update bundler
7
+
8
+ bundle_install: &bundle_install
9
+ run:
10
+ name: bundle install
11
+ command: bundle update --bundler && bundle install --path vendor/bundle --jobs 4
12
+
13
+ restore_bundle_cache: &restore_bundle_cache
14
+ restore_cache:
15
+ key: cache-bundler-{{ checksum "Gemfile.lock" }}
16
+
17
+ jobs:
18
+ build:
19
+ docker:
20
+ - image: circleci/ruby:2.7.2
21
+ steps:
22
+ - checkout
23
+ - *restore_bundle_cache
24
+ - *update_bundler
25
+ - *bundle_install
26
+ - save_cache:
27
+ key: cache-bundler-{{ checksum "Gemfile.lock" }}
28
+ paths:
29
+ - vendor/bundle
30
+ rubocop:
31
+ docker:
32
+ - image: circleci/ruby:2.7.2
33
+ steps:
34
+ - checkout
35
+ - *restore_bundle_cache
36
+ - *update_bundler
37
+ - *bundle_install
38
+ - run: bundle exec rubocop
39
+ rspec:
40
+ docker:
41
+ - image: circleci/ruby:2.7.2
42
+ steps:
43
+ - checkout
44
+ - *restore_bundle_cache
45
+ - *update_bundler
46
+ - *bundle_install
47
+ - run: bundle exec rspec
48
+ workflows:
49
+ version: 2.1
50
+ rspec:
51
+ jobs:
52
+ - build
53
+ - rubocop
54
+ - rspec
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ NewCops: enable
5
+ Include:
6
+ - 'lib/**/*.rb'
7
+ Style/Documentation:
8
+ Enabled: false
9
+ Metrics/LineLength:
10
+ Max: 120
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,35 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2020-08-08 12:17:16 UTC using RuboCop version 0.89.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: IgnoredMethods.
11
+ Metrics/AbcSize:
12
+ Max: 32
13
+
14
+ # Offense count: 2
15
+ # Configuration parameters: CountComments, CountAsOne.
16
+ Metrics/ClassLength:
17
+ Max: 146
18
+
19
+ # Offense count: 2
20
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
21
+ Metrics/MethodLength:
22
+ Max: 20
23
+
24
+ # Offense count: 3
25
+ # Configuration parameters: CountKeywordArgs.
26
+ Metrics/ParameterLists:
27
+ Max: 7
28
+
29
+ # Offense count: 1
30
+ # Cop supports --auto-correct.
31
+ # Configuration parameters: EnforcedStyle.
32
+ # SupportedStyles: always, always_true, never
33
+ Style/FrozenStringLiteralComment:
34
+ Exclude:
35
+ - 'bin/console'
data/Gemfile.lock CHANGED
@@ -1,36 +1,65 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitflyer (0.2.2)
5
- faraday (~> 0.14.0)
6
- faraday_middleware (~> 0.12.0)
4
+ bitflyer (1.2.0)
5
+ faraday (>= 0.14, < 1.4)
6
+ faraday_middleware (>= 0.12, < 1.1)
7
7
  websocket-client-simple (~> 0.3.0)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- diff-lcs (1.3)
12
+ ast (2.4.2)
13
+ coderay (1.1.3)
14
+ diff-lcs (1.4.4)
13
15
  event_emitter (0.2.6)
14
- faraday (0.14.0)
16
+ faraday (1.3.0)
17
+ faraday-net_http (~> 1.0)
15
18
  multipart-post (>= 1.2, < 3)
16
- faraday_middleware (0.12.2)
17
- faraday (>= 0.7.4, < 1.0)
18
- multipart-post (2.0.0)
19
- rake (12.3.0)
20
- rspec (3.7.0)
21
- rspec-core (~> 3.7.0)
22
- rspec-expectations (~> 3.7.0)
23
- rspec-mocks (~> 3.7.0)
24
- rspec-core (3.7.1)
25
- rspec-support (~> 3.7.0)
26
- rspec-expectations (3.7.0)
19
+ ruby2_keywords
20
+ faraday-net_http (1.0.1)
21
+ faraday_middleware (1.0.0)
22
+ faraday (~> 1.0)
23
+ method_source (1.0.0)
24
+ multipart-post (2.1.1)
25
+ parallel (1.20.1)
26
+ parser (3.0.0.0)
27
+ ast (~> 2.4.1)
28
+ pry (0.14.0)
29
+ coderay (~> 1.1)
30
+ method_source (~> 1.0)
31
+ rainbow (3.0.0)
32
+ rake (13.0.3)
33
+ regexp_parser (2.1.1)
34
+ rexml (3.2.4)
35
+ rspec (3.10.0)
36
+ rspec-core (~> 3.10.0)
37
+ rspec-expectations (~> 3.10.0)
38
+ rspec-mocks (~> 3.10.0)
39
+ rspec-core (3.10.1)
40
+ rspec-support (~> 3.10.0)
41
+ rspec-expectations (3.10.1)
27
42
  diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.7.0)
29
- rspec-mocks (3.7.0)
43
+ rspec-support (~> 3.10.0)
44
+ rspec-mocks (3.10.2)
30
45
  diff-lcs (>= 1.2.0, < 2.0)
31
- rspec-support (~> 3.7.0)
32
- rspec-support (3.7.1)
33
- websocket (1.2.8)
46
+ rspec-support (~> 3.10.0)
47
+ rspec-support (3.10.2)
48
+ rubocop (1.11.0)
49
+ parallel (~> 1.10)
50
+ parser (>= 3.0.0.0)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ regexp_parser (>= 1.8, < 3.0)
53
+ rexml
54
+ rubocop-ast (>= 1.2.0, < 2.0)
55
+ ruby-progressbar (~> 1.7)
56
+ unicode-display_width (>= 1.4.0, < 3.0)
57
+ rubocop-ast (1.4.1)
58
+ parser (>= 2.7.1.5)
59
+ ruby-progressbar (1.11.0)
60
+ ruby2_keywords (0.0.4)
61
+ unicode-display_width (2.0.0)
62
+ websocket (1.2.9)
34
63
  websocket-client-simple (0.3.0)
35
64
  event_emitter
36
65
  websocket
@@ -40,9 +69,11 @@ PLATFORMS
40
69
 
41
70
  DEPENDENCIES
42
71
  bitflyer!
43
- bundler (~> 1.12)
72
+ bundler (~> 2.0)
73
+ pry
44
74
  rake
45
75
  rspec
76
+ rubocop
46
77
 
47
78
  BUNDLED WITH
48
- 1.17.2
79
+ 2.2.4
data/README.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # bitflyer
2
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3
+ [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
4
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
2
5
  [![Gem Version](https://badge.fury.io/rb/bitflyer.svg)](https://badge.fury.io/rb/bitflyer)
3
6
  [![Circle CI](https://circleci.com/gh/unhappychoice/bitflyer.svg?style=shield)](https://circleci.com/gh/unhappychoice/bitflyer)
4
7
  [![Code Climate](https://codeclimate.com/github/unhappychoice/bitflyer/badges/gpa.svg)](https://codeclimate.com/github/unhappychoice/bitflyer)
5
- [![Dependency Status](https://gemnasium.com/badges/github.com/unhappychoice/bitflyer.svg)](https://gemnasium.com/github.com/unhappychoice/bitflyer)
8
+ [![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/unhappychoice/bitflyer.svg)](https://libraries.io/github/unhappychoice/bitflyer)
6
9
  ![](http://ruby-gem-downloads-badge.herokuapp.com/bitflyer?type=total)
10
+ ![GitHub](https://img.shields.io/github/license/unhappychoice/bitflyer.svg)
7
11
 
8
12
  bitflyer is a wrapper interface of [Bitflyer lightning API](https://lightning.bitflyer.jp/docs)
9
13
 
@@ -19,6 +23,8 @@ See https://lightning.bitflyer.jp/docs for details.
19
23
 
20
24
  ### HTTP API
21
25
 
26
+ See [public.rb](./lib/bitflyer/http/public.rb) / [private.rb](./lib/bitflyer/http/private.rb) for method definition.
27
+
22
28
  #### Example
23
29
 
24
30
  ```ruby
@@ -31,30 +37,52 @@ p private_client.positions # will print your positions
31
37
 
32
38
  ### Realtime API
33
39
 
40
+ #### Public events
41
+
34
42
  Accessor format is like `{event_name}_{product_code}`.
35
43
  You can set lambda to get realtime events.
36
44
 
37
- #### `event_name`
38
- - board_snapshot
39
- - board
40
- - ticker
41
- - executions
45
+ `{event_name}` and `{product_code}` is defined at [client.rb](./lib/bitflyer/realtime/client.rb).
42
46
 
43
- #### `product_code`
44
- - btc_jpy
45
- - fx_btc_jpy
46
- - eth_btc
47
- - bch_btc
47
+ #### Private events
48
48
 
49
- #### Example
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
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
50
56
 
57
+ For public events only:
51
58
  ```ruby
52
59
  client = Bitflyer.realtime_client
53
- client.ticker_btc_jpy = ->(json){ p json } # will print json object
60
+ client.ticker_btc_jpy = ->(json){ p json } # will print json object
54
61
  client.executions_btc_jpy = ->(json){ p json }
55
- # ...
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 }
73
+ client.executions_btc_jpy = ->(json){ p json }
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" }
56
82
  ```
57
83
 
84
+
85
+
58
86
  ## Contributing
59
87
 
60
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.
@@ -64,3 +92,24 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/unhapp
64
92
 
65
93
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
66
94
 
95
+
96
+ ## Contributors ✨
97
+
98
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
99
+
100
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
101
+ <!-- prettier-ignore-start -->
102
+ <!-- markdownlint-disable -->
103
+ <table>
104
+ <tr>
105
+ <td align="center"><a href="http://blog.unhappychoice.com"><img src="https://avatars.githubusercontent.com/u/5608948?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yuji Ueki</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=unhappychoice" title="Code">💻</a></td>
106
+ <td align="center"><a href="https://github.com/yemartin"><img src="https://avatars.githubusercontent.com/u/139002?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yves-Eric Martin</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=yemartin" title="Code">💻</a></td>
107
+ </tr>
108
+ </table>
109
+
110
+ <!-- markdownlint-restore -->
111
+ <!-- prettier-ignore-end -->
112
+
113
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
114
+
115
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
data/bitflyer.gemspec CHANGED
@@ -17,10 +17,12 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_dependency 'faraday', '~> 0.14.0'
21
- spec.add_dependency 'faraday_middleware', '~> 0.12.0'
20
+ spec.add_dependency 'faraday', '>= 0.14', '< 1.4'
21
+ spec.add_dependency 'faraday_middleware', '>= 0.12', '< 1.1'
22
22
  spec.add_dependency 'websocket-client-simple', '~> 0.3.0'
23
- spec.add_development_dependency 'bundler', '~> 1.12'
23
+ spec.add_development_dependency 'bundler', '~> 2.0'
24
+ spec.add_development_dependency 'pry'
24
25
  spec.add_development_dependency 'rake'
25
26
  spec.add_development_dependency 'rspec'
27
+ spec.add_development_dependency 'rubocop'
26
28
  end
data/lib/bitflyer.rb CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bitflyer/version'
2
4
  require 'bitflyer/http'
3
5
  require 'bitflyer/realtime'
4
6
 
5
7
  module Bitflyer
6
- def realtime_client
7
- Bitflyer::Realtime::Client.new
8
+ def realtime_client(key = nil, secret = nil)
9
+ Bitflyer::Realtime::Client.new(key, secret)
8
10
  end
9
11
 
10
12
  def http_public_client
data/lib/bitflyer/http.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bitflyer'
2
4
  require 'bitflyer/http/public'
3
5
  require 'bitflyer/http/private'
@@ -13,7 +15,7 @@ module Bitflyer
13
15
  def_delegators :@connection, :get, :post
14
16
 
15
17
  def initialize(key, secret)
16
- @connection = Faraday::Connection.new(:url => 'https://api.bitflyer.jp') do |f|
18
+ @connection = Faraday::Connection.new(url: 'https://api.bitflyer.jp') do |f|
17
19
  f.request :json
18
20
  f.response :json
19
21
  f.use Authentication, key, secret
@@ -34,9 +36,9 @@ module Bitflyer
34
36
 
35
37
  timestamp = Time.now.to_i.to_s
36
38
  method = env[:method].to_s.upcase
37
- path = env[:url].path + (env[:url].query ? '?' + env[:url].query : '')
39
+ path = env[:url].path + (env[:url].query ? "?#{env[:url].query}" : '')
38
40
  body = env[:body] || ''
39
- signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, timestamp + method + path + body)
41
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, timestamp + method + path + body)
40
42
  env[:request_headers]['ACCESS-KEY'] = @key if @key
41
43
  env[:request_headers]['ACCESS-TIMESTAMP'] = timestamp
42
44
  env[:request_headers]['ACCESS-SIGN'] = signature
@@ -44,4 +46,4 @@ module Bitflyer
44
46
  end
45
47
  end
46
48
  end
47
- end
49
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bitflyer
2
4
  module HTTP
3
5
  module Private
@@ -40,10 +42,10 @@ module Bitflyer
40
42
 
41
43
  def withdraw(currency_code: 'JPY', bank_account_id: nil, amount: nil, code: nil)
42
44
  body = {
43
- currency_code: currency_code,
44
- bank_account_id: bank_account_id,
45
- amount: amount,
46
- code: code
45
+ currency_code: currency_code,
46
+ bank_account_id: bank_account_id,
47
+ amount: amount,
48
+ code: code
47
49
  }.delete_if { |_, v| v.nil? }
48
50
  @connection.post('/v1/me/withdraw', body).body
49
51
  end
@@ -52,102 +54,124 @@ module Bitflyer
52
54
  @connection.get('/v1/me/getwithdrawals').body
53
55
  end
54
56
 
55
- def send_child_order(product_code: 'BTC_JPY', child_order_type: nil, side: nil, price: nil, size: nil, minute_to_expire: nil, time_in_force: 'GTC')
57
+ def send_child_order(
58
+ product_code: 'BTC_JPY',
59
+ child_order_type: nil,
60
+ side: nil,
61
+ price: nil,
62
+ size: nil,
63
+ minute_to_expire: nil,
64
+ time_in_force: 'GTC'
65
+ )
56
66
  body = {
57
- product_code: product_code,
58
- child_order_type: child_order_type,
59
- side: side,
60
- price: price,
61
- size: size,
62
- minute_to_expire: minute_to_expire,
63
- time_in_force: time_in_force
67
+ product_code: product_code,
68
+ child_order_type: child_order_type,
69
+ side: side,
70
+ price: price,
71
+ size: size,
72
+ minute_to_expire: minute_to_expire,
73
+ time_in_force: time_in_force
64
74
  }.delete_if { |_, v| v.nil? }
65
75
  @connection.post('/v1/me/sendchildorder', body).body
66
76
  end
67
77
 
68
78
  def cancel_child_order(product_code: 'BTC_JPY', child_order_id: nil, child_order_acceptance_id: nil)
69
79
  body = {
70
- product_code: product_code,
71
- child_order_id: child_order_id,
72
- child_order_acceptance_id: child_order_acceptance_id
80
+ product_code: product_code,
81
+ child_order_id: child_order_id,
82
+ child_order_acceptance_id: child_order_acceptance_id
73
83
  }.delete_if { |_, v| v.nil? }
74
84
  @connection.post('/v1/me/cancelchildorder', body).body
75
85
  end
76
86
 
77
87
  def send_parent_order(order_method: nil, minute_to_expire: nil, time_in_force: 'GTC', parameters: {})
78
88
  body = {
79
- order_method: order_method,
80
- minute_to_expire: minute_to_expire,
81
- time_in_force: time_in_force,
82
- parameters: parameters
89
+ order_method: order_method,
90
+ minute_to_expire: minute_to_expire,
91
+ time_in_force: time_in_force,
92
+ parameters: parameters
83
93
  }.delete_if { |_, v| v.nil? }
84
94
  @connection.post('/v1/me/sendparentorder', body).body
85
95
  end
86
96
 
87
97
  def cancel_parent_order(product_code: 'BTC_JPY', parent_order_id: nil, parent_order_acceptance_id: nil)
88
98
  body = {
89
- product_code: product_code,
90
- parent_order_id: parent_order_id,
91
- parent_order_acceptance_id: parent_order_acceptance_id
99
+ product_code: product_code,
100
+ parent_order_id: parent_order_id,
101
+ parent_order_acceptance_id: parent_order_acceptance_id
92
102
  }.delete_if { |_, v| v.nil? }
93
103
  @connection.post('/v1/me/cancelparentorder', body).body
94
104
  end
95
105
 
96
106
  def cancel_all_child_orders(product_code: 'BTC_JPY')
97
- @connection.post('/v1/me/cancelallchildorders', { product_code: product_code }).body
107
+ @connection.post('/v1/me/cancelallchildorders', product_code: product_code).body
98
108
  end
99
109
 
100
- def child_orders(product_code: 'BTC_JPY', count: nil, before: nil, after: nil, child_order_state: nil, parent_order_id: nil)
110
+ def child_orders(
111
+ product_code: 'BTC_JPY',
112
+ count: nil,
113
+ before: nil,
114
+ after: nil,
115
+ child_order_state: nil,
116
+ parent_order_id: nil
117
+ )
101
118
  query = {
102
- product_code: product_code,
103
- count: count,
104
- before: before,
105
- after: after,
106
- child_order_state: child_order_state,
107
- parent_order_id: parent_order_id
119
+ product_code: product_code,
120
+ count: count,
121
+ before: before,
122
+ after: after,
123
+ child_order_state: child_order_state,
124
+ parent_order_id: parent_order_id
108
125
  }.delete_if { |_, v| v.nil? }
109
126
  @connection.get('/v1/me/getchildorders', query).body
110
127
  end
111
128
 
112
129
  def parent_orders(product_code: 'BTC_JPY', count: nil, before: nil, after: nil, parent_order_state: nil)
113
130
  query = {
114
- product_code: product_code,
115
- count: count,
116
- before: before,
117
- after: after,
118
- parent_order_state: parent_order_state
131
+ product_code: product_code,
132
+ count: count,
133
+ before: before,
134
+ after: after,
135
+ parent_order_state: parent_order_state
119
136
  }.delete_if { |_, v| v.nil? }
120
137
  @connection.get('/v1/me/getparentorders', query).body
121
138
  end
122
139
 
123
140
  def parent_order(parent_order_id: nil, parent_order_acceptance_id: nil)
124
141
  query = {
125
- parent_order_id: parent_order_id,
126
- parent_order_acceptance_id: parent_order_acceptance_id
142
+ parent_order_id: parent_order_id,
143
+ parent_order_acceptance_id: parent_order_acceptance_id
127
144
  }.delete_if { |_, v| v.nil? }
128
145
  @connection.get('/v1/me/getparentorder', query).body
129
146
  end
130
147
 
131
- def executions(product_code: 'BTC_JPY', count: nil, before: nil, after: nil, child_order_id: nil, child_order_acceptance_id: nil)
148
+ def executions(
149
+ product_code: 'BTC_JPY',
150
+ count: nil,
151
+ before: nil,
152
+ after: nil,
153
+ child_order_id: nil,
154
+ child_order_acceptance_id: nil
155
+ )
132
156
  query = {
133
- product_code: product_code,
134
- count: count,
135
- before: before,
136
- after: after,
137
- child_order_id: child_order_id,
138
- child_order_acceptance_id: child_order_acceptance_id,
157
+ product_code: product_code,
158
+ count: count,
159
+ before: before,
160
+ after: after,
161
+ child_order_id: child_order_id,
162
+ child_order_acceptance_id: child_order_acceptance_id
139
163
  }.delete_if { |_, v| v.nil? }
140
164
  @connection.get('/v1/me/getexecutions', query).body
141
165
  end
142
166
 
143
167
  def positions(product_code: 'FX_BTC_JPY')
144
- @connection.get('/v1/me/getpositions', { product_code: product_code }).body
168
+ @connection.get('/v1/me/getpositions', product_code: product_code).body
145
169
  end
146
170
 
147
171
  def trading_commission(product_code: 'BTC_JPY')
148
- @connection.get('v1/me/gettradingcommission', { product_code: product_code }).body
172
+ @connection.get('v1/me/gettradingcommission', product_code: product_code).body
149
173
  end
150
174
  end
151
175
  end
152
176
  end
153
- end
177
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bitflyer
2
4
  module HTTP
3
5
  module Public
@@ -14,22 +16,28 @@ module Bitflyer
14
16
  @connection.get('/v1/markets').body
15
17
  end
16
18
 
17
- def board(product_code = 'BTC_JPY')
18
- @connection.get('/v1/board', { product_code: product_code }).body
19
+ def board(product_code: 'BTC_JPY')
20
+ @connection.get('/v1/board', product_code: product_code).body
19
21
  end
20
22
 
21
- def ticker(product_code = 'BTC_JPY')
22
- @connection.get('/v1/ticker', { product_code: product_code }).body
23
+ def ticker(product_code: 'BTC_JPY')
24
+ @connection.get('/v1/ticker', product_code: product_code).body
23
25
  end
24
26
 
25
- def executions(product_code = 'BTC_JPY')
26
- @connection.get('/v1/executions', { product_code: product_code }).body
27
+ def executions(product_code: 'BTC_JPY', count: nil, before: nil, after: nil)
28
+ query = {
29
+ product_code: product_code,
30
+ count: count,
31
+ before: before,
32
+ after: after
33
+ }.delete_if { |_, v| v.nil? }
34
+ @connection.get('/v1/executions', query).body
27
35
  end
28
36
 
29
- def chats(from_date = (Time.now - 5 * 24 * 60 * 60))
30
- @connection.get('/v1/getchats', { from_date: from_date }).body
37
+ def chats(from_date: (Time.now - 5 * 24 * 60 * 60))
38
+ @connection.get('/v1/getchats', from_date: from_date).body
31
39
  end
32
40
  end
33
41
  end
34
42
  end
35
- end
43
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bitflyer'
2
4
  require 'bitflyer/realtime/client'
3
5
 
4
6
  module Bitflyer
5
7
  module Realtime
6
8
  end
7
- end
9
+ end
@@ -1,29 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative './websocket'
2
4
 
3
5
  module Bitflyer
4
6
  module Realtime
5
- CHANNEL_NAMES = [
6
- 'lightning_board_snapshot_BTC_JPY',
7
- 'lightning_board_snapshot_FX_BTC_JPY',
8
- 'lightning_board_snapshot_ETH_BTC',
9
- 'lightning_board_snapshot_BCH_BTC',
10
- 'lightning_board_BTC_JPY',
11
- 'lightning_board_FX_BTC_JPY',
12
- 'lightning_board_ETH_BTC',
13
- 'lightning_board_BCH_BTC',
14
- 'lightning_ticker_BTC_JPY',
15
- 'lightning_ticker_FX_BTC_JPY',
16
- 'lightning_ticker_ETH_BTC',
17
- 'lightning_ticker_BCH_BTC',
18
- 'lightning_executions_BTC_JPY',
19
- 'lightning_executions_FX_BTC_JPY',
20
- 'lightning_executions_ETH_BTC',
21
- 'lightning_executions_BCH_BTC'
22
- ].freeze
7
+ PUBLIC_EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
8
+ MARKETS = %w[BTC_JPY FX_BTC_JPY ETH_BTC BCH_BTC BTCJPY_MAT3M BTCJPY_MAT1WK BTCJPY_MAT2WK].freeze
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
23
12
 
24
13
  SOCKET_HOST = 'https://io.lightstream.bitflyer.com'
25
14
 
26
15
  class Client
16
+ extend Forwardable
17
+ def_delegators :@websocket_client, :ready=, :disconnected=
27
18
  attr_accessor :websocket_client, :ping_interval, :ping_timeout, :last_ping_at, :last_pong_at
28
19
 
29
20
  Realtime::CHANNEL_NAMES.each do |channel_name|
@@ -32,8 +23,8 @@ module Bitflyer
32
23
  end
33
24
  end
34
25
 
35
- def initialize
36
- @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)
37
28
  end
38
29
  end
39
30
  end
@@ -1,52 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'websocket-client-simple'
2
4
  require 'json'
5
+ require 'openssl'
3
6
 
4
7
  module Bitflyer
5
8
  module Realtime
6
9
  class WebSocketClient
7
- attr_accessor :websocket_client, :channel_name, :channel_callbacks, :ping_interval, :ping_timeout,
8
- :last_ping_at, :last_pong_at, :error
10
+ attr_accessor :websocket_client, :channel_names, :channel_callbacks, :ping_interval, :ping_timeout,
11
+ :last_ping_at, :last_pong_at, :ready, :disconnected
9
12
 
10
- def initialize(host:, debug: false)
13
+ def initialize(host:, key:, secret:, debug: false)
11
14
  @host = host
15
+ @key = key
16
+ @secret = secret
12
17
  @debug = debug
13
- @error = nil
14
18
  @channel_names = []
15
19
  @channel_callbacks = {}
16
20
  connect
21
+ start_monitoring
17
22
  end
18
23
 
19
24
  def subscribe(channel_name:, &block)
20
25
  debug_log "Subscribe #{channel_name}"
21
26
  @channel_names = (@channel_names + [channel_name]).uniq
22
27
  @channel_callbacks[channel_name] = block
23
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
28
+ @websocket_client.send "42#{['subscribe', channel_name].to_json}"
24
29
  end
25
30
 
26
31
  def connect
27
32
  @websocket_client = WebSocket::Client::Simple.connect "#{@host}/socket.io/?transport=websocket"
28
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
29
41
 
42
+ def start_monitoring
30
43
  Thread.new do
31
44
  loop do
32
45
  sleep 1
33
- if @websocket_client && @websocket_client.open?
46
+ if @websocket_client&.open?
34
47
  send_ping
35
48
  wait_pong
49
+ else
50
+ reconnect
36
51
  end
37
52
  end
38
53
  end
39
-
40
- Thread.new do
41
- loop do
42
- sleep 1
43
- next unless @error
44
- reconnect
45
- end
46
- end
47
-
48
- @websocket_client.on(:message) { |payload| this.handle_message(payload: payload) }
49
- @websocket_client.on(:error) { |error| this.handle_error(error: error) }
50
54
  end
51
55
 
52
56
  def send_ping
@@ -54,40 +58,39 @@ module Bitflyer
54
58
  return unless Time.now.to_i - @last_ping_at > @ping_interval / 1000
55
59
 
56
60
  debug_log 'Sent ping'
57
- @websocket_client.send "2"
61
+ @websocket_client.send '2'
58
62
  @last_ping_at = Time.now.to_i
59
63
  end
60
64
 
61
65
  def wait_pong
62
66
  return unless @last_pong_at && @ping_timeout
63
- return unless Time.now.to_i - @last_pong_at > @ping_timeout / 1000
67
+ return unless Time.now.to_i - @last_pong_at > (@ping_interval + @ping_timeout) / 1000
64
68
 
65
69
  debug_log 'Timed out waiting pong'
66
70
  @websocket_client.close
67
71
  end
68
72
 
69
73
  def reconnect
70
- return unless @error
74
+ return if @websocket_client&.open?
75
+
71
76
  debug_log 'Reconnecting...'
72
77
 
73
- @error = nil
74
78
  @websocket_client.close if @websocket_client.open?
75
79
  connect
76
- @channel_names.each do |channel_name|
77
- debug_log "42#{{ subscribe: channel_name }.to_json}"
78
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
79
- end
80
80
  end
81
81
 
82
82
  def handle_error(error:)
83
83
  debug_log error
84
- return unless error.kind_of? Errno::ECONNRESET
84
+ return unless error.is_a? Errno::ECONNRESET
85
+
86
+ @disconnected&.call(error)
85
87
  reconnect
86
88
  end
87
89
 
88
90
  def handle_message(payload:)
89
91
  debug_log payload.data
90
92
  return unless payload.data =~ /^\d+/
93
+
91
94
  code, body = payload.data.scan(/^(\d+)(.*)$/)[0]
92
95
 
93
96
  case code.to_i
@@ -95,20 +98,60 @@ module Bitflyer
95
98
  when 3 then receive_pong
96
99
  when 41 then disconnect
97
100
  when 42 then emit_message(json: body)
101
+ when 430 then authenticated(json: body)
98
102
  end
99
- rescue => e
103
+ rescue StandardError => e
100
104
  puts e
101
105
  puts e.backtrace.join("\n")
102
106
  end
103
107
 
108
+ def handle_close(error:)
109
+ debug_log error
110
+ @disconnected&.call(error)
111
+ end
112
+
104
113
  def setup_by_response(json:)
105
114
  body = JSON.parse json
106
- @ping_interval = body["pingInterval"].to_i || 25000
107
- @ping_timeout = body["pingTimeout"].to_i || 60000
115
+ @ping_interval = body['pingInterval'].to_i || 25_000
116
+ @ping_timeout = body['pingTimeout'].to_i || 60_000
108
117
  @last_ping_at = Time.now.to_i
109
118
  @last_pong_at = Time.now.to_i
110
- channel_callbacks.each do |channel_name, _|
111
- websocket_client.send "42#{['subscribe', channel_name].to_json}"
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}"
112
155
  end
113
156
  end
114
157
 
@@ -119,17 +162,19 @@ module Bitflyer
119
162
 
120
163
  def disconnect
121
164
  debug_log 'Disconnecting from server...'
122
- @error = true
165
+ @websocket_client.close
123
166
  end
124
167
 
125
168
  def emit_message(json:)
126
169
  channel_name, *messages = JSON.parse json
127
170
  return unless channel_name
171
+
128
172
  messages.each { |message| @channel_callbacks[channel_name.to_sym]&.call(message) }
129
173
  end
130
174
 
131
175
  def debug_log(message)
132
176
  return unless @debug
177
+
133
178
  p message
134
179
  end
135
180
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bitflyer
2
- VERSION = '0.2.2'
4
+ VERSION = '1.2.0'
3
5
  end
metadata CHANGED
@@ -1,43 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitflyer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
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: 2019-01-06 00:00:00.000000000 Z
11
+ date: 2021-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: 0.14.0
22
+ version: '1.4'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: 0.14.0
29
+ version: '0.14'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.4'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: faraday_middleware
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: 0.12.0
39
+ version: '0.12'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.1'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - "~>"
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0.12'
50
+ - - "<"
39
51
  - !ruby/object:Gem::Version
40
- version: 0.12.0
52
+ version: '1.1'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: websocket-client-simple
43
55
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +70,28 @@ dependencies:
58
70
  requirements:
59
71
  - - "~>"
60
72
  - !ruby/object:Gem::Version
61
- version: '1.12'
73
+ version: '2.0'
62
74
  type: :development
63
75
  prerelease: false
64
76
  version_requirements: !ruby/object:Gem::Requirement
65
77
  requirements:
66
78
  - - "~>"
67
79
  - !ruby/object:Gem::Version
68
- version: '1.12'
80
+ version: '2.0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: pry
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
69
95
  - !ruby/object:Gem::Dependency
70
96
  name: rake
71
97
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +120,20 @@ dependencies:
94
120
  - - ">="
95
121
  - !ruby/object:Gem::Version
96
122
  version: '0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: rubocop
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
97
137
  description: Bitflyer API wrapper
98
138
  email:
99
139
  - unhappychoice@gmail.com
@@ -101,6 +141,10 @@ executables: []
101
141
  extensions: []
102
142
  extra_rdoc_files: []
103
143
  files:
144
+ - ".all-contributorsrc"
145
+ - ".circleci/config.yml"
146
+ - ".rubocop.yml"
147
+ - ".rubocop_todo.yml"
104
148
  - CODE_OF_CONDUCT.md
105
149
  - Gemfile
106
150
  - Gemfile.lock
@@ -135,8 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
179
  - !ruby/object:Gem::Version
136
180
  version: '0'
137
181
  requirements: []
138
- rubyforge_project:
139
- rubygems_version: 2.7.6
182
+ rubygems_version: 3.0.3
140
183
  signing_key:
141
184
  specification_version: 4
142
185
  summary: Bitflyer API wrapper