bitflyer 0.3.0 → 1.3.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
- 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