bitflyer 0.3.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 02cf9dce44723e91ea75d8f9c705c8bec8040f40
4
- data.tar.gz: 0c359f712a2692685e6e7a504f0c05c70edbc468
2
+ SHA256:
3
+ metadata.gz: b190522b3b17c7f99dd4d91d7cba88b399cd1d313e5a4aeba6537ad802daa68b
4
+ data.tar.gz: c53b238a8c84a01ba4ba913a329e00724ef213d6acf6b7e0a216071038a37ff5
5
5
  SHA512:
6
- metadata.gz: 88b4f42f92fc5e8431011030f28a7195c382903da764c22cdf0a0baf15a3f0f04eac6646fc552433dd1d591e39b21c97f774fca0ba16b521747b9c4da6dffb5e
7
- data.tar.gz: 59d59e03775bb6a074f22115803c16d40c674262b1ca5e73b77f2b0dbedc4d3c90fcec0bfd671045b0dd5e6488c557d61a618bc340b26ca5a05672c722150716
6
+ metadata.gz: b41f5b83d91accdac278ed3b38bbff5dc12c2d95e16b2998413661eb817a447543d25afa7e246a22f99860740fcb83af10b31cf7e7a6069674f39b2bfd74517c
7
+ data.tar.gz: 344832975df6df64f311975197c2d5a1f5a8e00b14b9e9ec4f9e02f72937a447304ea3d2b0bed4a922de0d2d0525cd6e8c3b6e4fc4c688570a171f3a96500956
@@ -0,0 +1,42 @@
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
+ "login": "fkshom",
28
+ "name": "Shoma FUKUDA",
29
+ "avatar_url": "https://avatars.githubusercontent.com/u/1889118?v=4",
30
+ "profile": "https://github.com/fkshom",
31
+ "contributions": [
32
+ "code"
33
+ ]
34
+ }
35
+ ],
36
+ "contributorsPerLine": 7,
37
+ "projectName": "bitflyer",
38
+ "projectOwner": "unhappychoice",
39
+ "repoType": "github",
40
+ "repoHost": "https://github.com",
41
+ "skipCi": true
42
+ }
data/.circleci/config.yml CHANGED
@@ -1,5 +1,10 @@
1
1
  version: 2.1
2
2
 
3
+ update_bundler: &update_bundler
4
+ run:
5
+ name: update bundler
6
+ command: gem update bundler
7
+
3
8
  bundle_install: &bundle_install
4
9
  run:
5
10
  name: bundle install
@@ -12,21 +17,32 @@ restore_bundle_cache: &restore_bundle_cache
12
17
  jobs:
13
18
  build:
14
19
  docker:
15
- - image: circleci/ruby
20
+ - image: circleci/ruby:2.7.2
16
21
  steps:
17
22
  - checkout
18
23
  - *restore_bundle_cache
24
+ - *update_bundler
19
25
  - *bundle_install
20
26
  - save_cache:
21
27
  key: cache-bundler-{{ checksum "Gemfile.lock" }}
22
28
  paths:
23
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
24
39
  rspec:
25
40
  docker:
26
- - image: circleci/ruby
41
+ - image: circleci/ruby:2.7.2
27
42
  steps:
28
43
  - checkout
29
44
  - *restore_bundle_cache
45
+ - *update_bundler
30
46
  - *bundle_install
31
47
  - run: bundle exec rspec
32
48
  workflows:
@@ -34,4 +50,5 @@ workflows:
34
50
  rspec:
35
51
  jobs:
36
52
  - build
53
+ - rubocop
37
54
  - rspec
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "12:00"
8
+ timezone: Asia/Tokyo
9
+ open-pull-requests-limit: 20
10
+ reviewers:
11
+ - unhappychoice
12
+ allow:
13
+ - dependency-type: direct
14
+ - dependency-type: indirect
15
+ ignore:
16
+ - dependency-name: rubocop
17
+ versions:
18
+ - 1.12.0
19
+ - 1.12.1
20
+ - dependency-name: regexp_parser
21
+ versions:
22
+ - 2.1.0
23
+ - 2.1.1
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,41 +1,69 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitflyer (0.3.0)
5
- faraday (~> 0.14.0)
6
- faraday_middleware (~> 0.12.0)
4
+ bitflyer (1.3.0)
5
+ faraday (>= 0.14, < 1.5)
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
- coderay (1.1.2)
13
- diff-lcs (1.3)
12
+ ast (2.4.2)
13
+ coderay (1.1.3)
14
+ diff-lcs (1.4.4)
14
15
  event_emitter (0.2.6)
15
- faraday (0.14.0)
16
+ faraday (1.4.1)
17
+ faraday-excon (~> 1.1)
18
+ faraday-net_http (~> 1.0)
19
+ faraday-net_http_persistent (~> 1.1)
16
20
  multipart-post (>= 1.2, < 3)
17
- faraday_middleware (0.12.2)
18
- faraday (>= 0.7.4, < 1.0)
19
- method_source (0.9.2)
20
- multipart-post (2.0.0)
21
- pry (0.12.2)
22
- coderay (~> 1.1.0)
23
- method_source (~> 0.9.0)
24
- rake (12.3.0)
25
- rspec (3.7.0)
26
- rspec-core (~> 3.7.0)
27
- rspec-expectations (~> 3.7.0)
28
- rspec-mocks (~> 3.7.0)
29
- rspec-core (3.7.1)
30
- rspec-support (~> 3.7.0)
31
- rspec-expectations (3.7.0)
21
+ ruby2_keywords (>= 0.0.4)
22
+ faraday-excon (1.1.0)
23
+ faraday-net_http (1.0.1)
24
+ faraday-net_http_persistent (1.1.0)
25
+ faraday_middleware (1.0.0)
26
+ faraday (~> 1.0)
27
+ method_source (1.0.0)
28
+ multipart-post (2.1.1)
29
+ parallel (1.20.1)
30
+ parser (3.0.1.1)
31
+ ast (~> 2.4.1)
32
+ pry (0.14.1)
33
+ coderay (~> 1.1)
34
+ method_source (~> 1.0)
35
+ rainbow (3.0.0)
36
+ rake (13.0.3)
37
+ regexp_parser (2.1.1)
38
+ rexml (3.2.5)
39
+ rspec (3.10.0)
40
+ rspec-core (~> 3.10.0)
41
+ rspec-expectations (~> 3.10.0)
42
+ rspec-mocks (~> 3.10.0)
43
+ rspec-core (3.10.1)
44
+ rspec-support (~> 3.10.0)
45
+ rspec-expectations (3.10.1)
32
46
  diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.7.0)
34
- rspec-mocks (3.7.0)
47
+ rspec-support (~> 3.10.0)
48
+ rspec-mocks (3.10.2)
35
49
  diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.7.0)
37
- rspec-support (3.7.1)
38
- websocket (1.2.8)
50
+ rspec-support (~> 3.10.0)
51
+ rspec-support (3.10.2)
52
+ rubocop (1.14.0)
53
+ parallel (~> 1.10)
54
+ parser (>= 3.0.0.0)
55
+ rainbow (>= 2.2.2, < 4.0)
56
+ regexp_parser (>= 1.8, < 3.0)
57
+ rexml
58
+ rubocop-ast (>= 1.5.0, < 2.0)
59
+ ruby-progressbar (~> 1.7)
60
+ unicode-display_width (>= 1.4.0, < 3.0)
61
+ rubocop-ast (1.5.0)
62
+ parser (>= 3.0.1.1)
63
+ ruby-progressbar (1.11.0)
64
+ ruby2_keywords (0.0.4)
65
+ unicode-display_width (2.0.0)
66
+ websocket (1.2.9)
39
67
  websocket-client-simple (0.3.0)
40
68
  event_emitter
41
69
  websocket
@@ -45,10 +73,11 @@ PLATFORMS
45
73
 
46
74
  DEPENDENCIES
47
75
  bitflyer!
48
- bundler (~> 1.12)
76
+ bundler (~> 2.0)
49
77
  pry
50
78
  rake
51
79
  rspec
80
+ rubocop
52
81
 
53
82
  BUNDLED WITH
54
- 1.17.3
83
+ 2.2.4
data/README.md CHANGED
@@ -1,8 +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-3-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)
8
+ [![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/unhappychoice/bitflyer.svg)](https://libraries.io/github/unhappychoice/bitflyer)
5
9
  ![](http://ruby-gem-downloads-badge.herokuapp.com/bitflyer?type=total)
10
+ ![GitHub](https://img.shields.io/github/license/unhappychoice/bitflyer.svg)
6
11
 
7
12
  bitflyer is a wrapper interface of [Bitflyer lightning API](https://lightning.bitflyer.jp/docs)
8
13
 
@@ -18,6 +23,8 @@ See https://lightning.bitflyer.jp/docs for details.
18
23
 
19
24
  ### HTTP API
20
25
 
26
+ See [public.rb](./lib/bitflyer/http/public.rb) / [private.rb](./lib/bitflyer/http/private.rb) for method definition.
27
+
21
28
  #### Example
22
29
 
23
30
  ```ruby
@@ -30,30 +37,52 @@ p private_client.positions # will print your positions
30
37
 
31
38
  ### Realtime API
32
39
 
40
+ #### Public events
41
+
33
42
  Accessor format is like `{event_name}_{product_code}`.
34
43
  You can set lambda to get realtime events.
35
44
 
36
- #### `event_name`
37
- - board_snapshot
38
- - board
39
- - ticker
40
- - executions
45
+ `{event_name}` and `{product_code}` is defined at [client.rb](./lib/bitflyer/realtime/client.rb).
41
46
 
42
- #### `product_code`
43
- - btc_jpy
44
- - fx_btc_jpy
45
- - eth_btc
46
- - bch_btc
47
+ #### Private events
47
48
 
48
- #### 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
49
56
 
57
+ For public events only:
50
58
  ```ruby
51
59
  client = Bitflyer.realtime_client
52
- client.ticker_btc_jpy = ->(json){ p json } # will print json object
60
+ client.ticker_btc_jpy = ->(json){ p json } # will print json object
53
61
  client.executions_btc_jpy = ->(json){ p json }
54
- # ...
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" }
55
82
  ```
56
83
 
84
+
85
+
57
86
  ## Contributing
58
87
 
59
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.
@@ -63,3 +92,25 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/unhapp
63
92
 
64
93
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
65
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="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>
106
+ <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>
107
+ <td align="center"><a href="https://github.com/fkshom"><img src="https://avatars.githubusercontent.com/u/1889118?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shoma FUKUDA</b></sub></a><br /><a href="https://github.com/unhappychoice/bitflyer/commits?author=fkshom" title="Code">💻</a></td>
108
+ </tr>
109
+ </table>
110
+
111
+ <!-- markdownlint-restore -->
112
+ <!-- prettier-ignore-end -->
113
+
114
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
115
+
116
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
data/bitflyer.gemspec CHANGED
@@ -17,11 +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.5'
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'
26
- spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'rubocop'
27
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,14 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative './websocket'
2
4
 
3
5
  module Bitflyer
4
6
  module Realtime
5
- EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
6
- MARKETS = %w[BTC_JPY FX_BTC_JPY ETH_BTC BCH_BTC BTCJPY_MAT3M BTCJPY_MAT1WK BTCJPY_MAT2WK].freeze
7
- CHANNEL_NAMES = EVENT_NAMES.product(MARKETS).map { |e, m| e + '_' + m }
7
+ PUBLIC_EVENT_NAMES = %w[lightning_board_snapshot lightning_board lightning_ticker lightning_executions].freeze
8
+ MARKETS = %w[BTC_JPY XRP_JPY ETH_JPY XLM_JPY MONA_JPY ETH_BTC BCH_BTC FX_BTC_JPY BTCJPY_MAT1WK BTCJPY_MAT2WK
9
+ BTCJPY_MAT3M].freeze
10
+ PUBLIC_CHANNEL_NAMES = PUBLIC_EVENT_NAMES.product(MARKETS).map { |e, m| "#{e}_#{m}" }.freeze
11
+ PRIVATE_CHANNEL_NAMES = %w[child_order_events parent_order_events].freeze
12
+ CHANNEL_NAMES = (PUBLIC_CHANNEL_NAMES + PRIVATE_CHANNEL_NAMES).freeze
8
13
 
9
14
  SOCKET_HOST = 'https://io.lightstream.bitflyer.com'
10
15
 
11
16
  class Client
17
+ extend Forwardable
18
+ def_delegators :@websocket_client, :ready=, :disconnected=
12
19
  attr_accessor :websocket_client, :ping_interval, :ping_timeout, :last_ping_at, :last_pong_at
13
20
 
14
21
  Realtime::CHANNEL_NAMES.each do |channel_name|
@@ -17,8 +24,8 @@ module Bitflyer
17
24
  end
18
25
  end
19
26
 
20
- def initialize
21
- @websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST)
27
+ def initialize(key = nil, secret = nil)
28
+ @websocket_client = Bitflyer::Realtime::WebSocketClient.new(host: SOCKET_HOST, key: key, secret: secret)
22
29
  end
23
30
  end
24
31
  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
- def handle_message(payload:)
90
+ def handle_message(payload:) # rubocop:disable Metrics/CyclomaticComplexity
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,58 @@ 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).unpack1('H*')
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
+ raise "Authentication failed: #{json}" if json != '[null]'
143
+
144
+ debug_log 'Authenticated'
145
+ subscribe_channels
146
+ @ready&.call
147
+ end
148
+
149
+ def subscribe_channels
150
+ @channel_callbacks.each do |channel_name, _|
151
+ debug_log "42#{{ subscribe: channel_name }.to_json}"
152
+ @websocket_client.send "42#{['subscribe', channel_name].to_json}"
112
153
  end
113
154
  end
114
155
 
@@ -119,17 +160,19 @@ module Bitflyer
119
160
 
120
161
  def disconnect
121
162
  debug_log 'Disconnecting from server...'
122
- @error = true
163
+ @websocket_client.close
123
164
  end
124
165
 
125
166
  def emit_message(json:)
126
167
  channel_name, *messages = JSON.parse json
127
168
  return unless channel_name
169
+
128
170
  messages.each { |message| @channel_callbacks[channel_name.to_sym]&.call(message) }
129
171
  end
130
172
 
131
173
  def debug_log(message)
132
174
  return unless @debug
175
+
133
176
  p message
134
177
  end
135
178
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bitflyer
2
- VERSION = '0.3.0'
4
+ VERSION = '1.3.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.3.0
4
+ version: 1.3.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-07-07 00:00:00.000000000 Z
11
+ date: 2021-05-10 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
18
  - !ruby/object:Gem::Version
19
- version: 0.14.0
19
+ version: '0.14'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.5'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.14'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: 0.14.0
32
+ version: '1.5'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: faraday_middleware
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ - - "<"
32
41
  - !ruby/object:Gem::Version
33
- version: 0.12.0
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
+ - - ">="
39
48
  - !ruby/object:Gem::Version
40
- version: 0.12.0
49
+ version: '0.12'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
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
@@ -95,7 +121,7 @@ dependencies:
95
121
  - !ruby/object:Gem::Version
96
122
  version: '0'
97
123
  - !ruby/object:Gem::Dependency
98
- name: pry
124
+ name: rubocop
99
125
  requirement: !ruby/object:Gem::Requirement
100
126
  requirements:
101
127
  - - ">="
@@ -115,7 +141,11 @@ executables: []
115
141
  extensions: []
116
142
  extra_rdoc_files: []
117
143
  files:
144
+ - ".all-contributorsrc"
118
145
  - ".circleci/config.yml"
146
+ - ".github/dependabot.yml"
147
+ - ".rubocop.yml"
148
+ - ".rubocop_todo.yml"
119
149
  - CODE_OF_CONDUCT.md
120
150
  - Gemfile
121
151
  - Gemfile.lock
@@ -150,8 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
180
  - !ruby/object:Gem::Version
151
181
  version: '0'
152
182
  requirements: []
153
- rubyforge_project:
154
- rubygems_version: 2.6.14
183
+ rubygems_version: 3.0.3
155
184
  signing_key:
156
185
  specification_version: 4
157
186
  summary: Bitflyer API wrapper