bitex 0.3 → 0.4.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +63 -0
  3. data/.rubocop.yml +32 -0
  4. data/.ruby-version +1 -0
  5. data/bitex.gemspec +21 -18
  6. data/lib/bitex.rb +7 -1
  7. data/lib/bitex/api.rb +34 -41
  8. data/lib/bitex/ask.rb +74 -0
  9. data/lib/bitex/base_order.rb +106 -0
  10. data/lib/bitex/bid.rb +72 -0
  11. data/lib/bitex/buy.rb +8 -5
  12. data/lib/bitex/kyc_file.rb +31 -9
  13. data/lib/bitex/kyc_profile.rb +113 -38
  14. data/lib/bitex/{market.rb → market_data.rb} +3 -3
  15. data/lib/bitex/match.rb +30 -15
  16. data/lib/bitex/order.rb +6 -238
  17. data/lib/bitex/payment.rb +30 -18
  18. data/lib/bitex/rates.rb +6 -8
  19. data/lib/bitex/sell.rb +5 -5
  20. data/lib/bitex/specie_deposit.rb +9 -4
  21. data/lib/bitex/specie_withdrawal.rb +29 -28
  22. data/lib/bitex/trade.rb +4 -5
  23. data/lib/bitex/transaction.rb +7 -8
  24. data/lib/bitex/usd_deposit.rb +46 -47
  25. data/lib/bitex/usd_withdrawal.rb +33 -34
  26. data/lib/bitex/version.rb +1 -1
  27. data/spec/ask_spec.rb +17 -5
  28. data/spec/bid_spec.rb +17 -5
  29. data/spec/buy_spec.rb +14 -4
  30. data/spec/kyc_file_spec.rb +34 -18
  31. data/spec/kyc_profile_spec.rb +158 -122
  32. data/spec/order_spec.rb +1 -1
  33. data/spec/payment_spec.rb +51 -45
  34. data/spec/sell_spec.rb +14 -4
  35. data/spec/spec_helper.rb +7 -6
  36. data/spec/specie_deposit_spec.rb +10 -4
  37. data/spec/specie_withdrawal_spec.rb +26 -25
  38. data/spec/support/from_json_shared_examples.rb +20 -22
  39. data/spec/support/order_shared_examples.rb +14 -17
  40. data/spec/support/request_stubs.rb +18 -12
  41. data/spec/trade_spec.rb +5 -5
  42. data/spec/transaction_spec.rb +12 -13
  43. data/spec/usd_deposit_spec.rb +120 -105
  44. data/spec/usd_withdrawal_spec.rb +89 -79
  45. metadata +57 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5585d9b3c87459de35ecbe8c0f6f215597be0976
4
- data.tar.gz: 277eaa74fe7d87546334a7f6fc40c6a9e42df3f0
3
+ metadata.gz: 151ad0a112110c7ce40e5ce09c5f753a10253bcb
4
+ data.tar.gz: aecf7f5c80fe3afba0da32ddb4f820f8d4fb7bc4
5
5
  SHA512:
6
- metadata.gz: afcc613f373bba455e4014491d5649f06b748a64eb055d0b3fe0f53534295e42a4d5a82bb2d873891b0837870aa7ad8ffe4c3cded6a123b822be7a3b5c72e80d
7
- data.tar.gz: 0b07c253e3848e571514e75487dc520c5007d7994c1b7cbba146e56e23047a889e5cef753e385d0021975d81492b7e5d74690fb683aee284cc799a99b4b9c8aa
6
+ metadata.gz: d9785a0c2b14f157e6cdef69079c12c4be59c9463bdff76bf865aa1931f98d46eff6d6b376c1392cf2bd0452870296c58e8e76475b0994ca3e0ce8dfaf6b1b26
7
+ data.tar.gz: cb426c0e8d50ddd8982e8505d3696374ab637b0822693cfc98d53145dacd02de0fc220389db4195f38d2b0a9f9327b5072148d4505a2f65a2695f30d0f2f4193
@@ -0,0 +1,63 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+
12
+ # Specify service dependencies here if necessary
13
+ # CircleCI maintains a library of pre-built images
14
+ # documented at https://circleci.com/docs/2.0/circleci-images/
15
+ # - image: circleci/postgres:9.4
16
+
17
+ working_directory: ~/repo
18
+
19
+ steps:
20
+ - checkout
21
+
22
+ # Download and cache dependencies
23
+ - restore_cache:
24
+ keys:
25
+ - v1-dependencies-{{ checksum "bitex.gemspec" }}-{{ checksum "Gemfile" }}
26
+ # fallback to using the latest cache if no exact match is found
27
+ - v1-dependencies-
28
+
29
+ - run:
30
+ name: install dependencies
31
+ command: |
32
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
33
+
34
+ - save_cache:
35
+ paths:
36
+ - ./vendor/bundle
37
+ key: v1-dependencies-{{ checksum "bitex.gemspec" }}-{{ checksum "Gemfile" }}
38
+
39
+ # run tests!
40
+ - run:
41
+ name: run tests
42
+ command: |
43
+ mkdir /tmp/test-results
44
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
45
+
46
+ bundle exec rspec --format progress \
47
+ --format RspecJunitFormatter \
48
+ --out /tmp/test-results/rspec.xml \
49
+ --format progress \
50
+ $TEST_FILES
51
+
52
+ # run lints
53
+ - run:
54
+ name: run lints
55
+ command: |
56
+ bundle exec rubocop
57
+
58
+ # collect reports
59
+ - store_test_results:
60
+ path: /tmp/test-results
61
+ - store_artifacts:
62
+ path: /tmp/test-results
63
+ destination: test-results
@@ -0,0 +1,32 @@
1
+ Style/FrozenStringLiteralComment:
2
+ EnforcedStyle: never
3
+
4
+ Metrics/AbcSize:
5
+ Max: 16
6
+
7
+ Metrics/ClassLength:
8
+ Max: 130
9
+
10
+ Metrics/LineLength:
11
+ Max: 130
12
+
13
+ Metrics/MethodLength:
14
+ Max: 20
15
+
16
+ Metrics/MethodLength:
17
+ Max: 20
18
+
19
+ Metrics/ParameterLists:
20
+ Max: 7
21
+
22
+ AllCops:
23
+ TargetRubyVersion: 2.4
24
+ Exclude:
25
+ - 'Gemfile'
26
+ - 'Gemfile.lock'
27
+ - 'LICENSE.txt'
28
+ - 'README.md'
29
+ - 'Rakefile'
30
+ - 'bitex.gemspec'
31
+ - 'spec/**/*'
32
+ - 'vendor/**/*'
@@ -0,0 +1 @@
1
+ 2.4.1
@@ -4,30 +4,33 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'bitex/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "bitex"
7
+ spec.name = 'bitex'
8
8
  spec.version = Bitex::VERSION
9
- spec.authors = ["Nubis", "Eromirou"]
10
- spec.email = ["nb@bitex.la", "tr@bitex.la"]
11
- spec.description = %q{API client library for bitex.la. Fetch public market
12
- data and build trading robots}
13
- spec.summary = "API client library for bitex.la"
14
- spec.homepage = "http://bitex.la/developers"
15
- spec.license = "MIT"
9
+ spec.authors = ['Nubis', 'Eromirou']
10
+ spec.email = ['nb@bitex.la', 'tr@bitex.la']
11
+ spec.description = %q{API client library for bitex.la. Fetch public market data and build trading robots}
12
+ spec.summary = 'API client library for bitex.la'
13
+ spec.homepage = 'http://bitex.la/developers'
14
+ spec.license = 'MIT'
16
15
 
17
16
  spec.files = `git ls-files`.split($/)
18
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
21
20
 
22
- spec.add_dependency "activesupport"
23
- spec.add_dependency "curb", "~> 0.9.3"
21
+ spec.add_dependency 'activesupport'
22
+ spec.add_dependency 'curb', '~> 0.9.3'
24
23
 
25
24
  spec.required_ruby_version = '>= 1.9.3'
26
- spec.add_development_dependency "bundler", "~> 1.3"
27
- spec.add_development_dependency "rake"
28
- spec.add_development_dependency "rspec"
29
- spec.add_development_dependency "rspec-mocks"
30
- spec.add_development_dependency "timecop"
31
- spec.add_development_dependency "webmock"
32
- spec.add_development_dependency "shoulda-matchers"
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+
28
+ spec.add_development_dependency 'byebug'
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'rspec_junit_formatter'
31
+ spec.add_development_dependency 'rspec-mocks'
32
+ spec.add_development_dependency 'rubocop'
33
+ spec.add_development_dependency 'shoulda-matchers'
34
+ spec.add_development_dependency 'timecop'
35
+ spec.add_development_dependency 'webmock'
33
36
  end
@@ -3,9 +3,15 @@ require 'active_support/core_ext'
3
3
  require 'json'
4
4
  require 'curl'
5
5
  require 'bigdecimal'
6
+ require 'bigdecimal/util'
6
7
  require 'bitex/match'
7
- Dir[File.expand_path("../bitex/*.rb", __FILE__)].each {|f| require f}
8
+ require 'bitex/base_order'
8
9
 
10
+ Dir[File.expand_path('bitex/*.rb', __dir__)].each { |f| require f }
11
+
12
+ ##
13
+ # Documentation here!
14
+ #
9
15
  module Bitex
10
16
  mattr_accessor :api_key
11
17
  mattr_accessor :sandbox
@@ -1,5 +1,10 @@
1
1
  module Bitex
2
- class ApiError < StandardError; end
2
+ class ApiError < StandardError
3
+ end
4
+
5
+ ##
6
+ # Documentation here!
7
+ #
3
8
  class Api
4
9
  def self.grab_curl
5
10
  if @curl
@@ -9,34 +14,27 @@ module Bitex
9
14
  end
10
15
 
11
16
  @curl.ssl_version = Curl::CURL_SSLVERSION_TLSv1
12
- if Bitex.debug
13
- @curl.on_debug do |t,d|
14
- if d.to_s.size < 300
15
- puts "DEBUG SSL #{t}, #{d}"
16
- end
17
- end
18
- end
19
-
17
+ @curl.on_debug { |t, d| puts "DEBUG SSL #{t}, #{d}" if d.to_s.size < 300 } if Bitex.debug
20
18
  @curl.connect_timeout = 30
21
19
  @curl.timeout = 30
22
20
  @curl
23
21
  end
24
22
 
25
- def self.curl(verb, path, options={}, files={})
23
+ def self.curl(verb, path, options = {}, files = {})
26
24
  verb = verb.upcase.to_sym
27
25
  query = verb == :GET ? "?#{options.to_query}" : ''
28
26
  prefix = Bitex.sandbox ? 'sandbox.' : ''
29
27
 
30
28
  curl = grab_curl
31
29
  curl.url = "https://#{prefix}bitex.la/api-v1/rest#{path}#{query}"
32
-
30
+
33
31
  if verb == :POST
34
32
  fields = []
35
33
  unless files.empty?
36
- fields += files.collect{|k, v| Curl::PostField.file(k.to_s, v) }
34
+ fields += files.map { |k, v| Curl::PostField.file(k.to_s, v) }
37
35
  curl.multipart_form_post = true
38
36
  end
39
- fields += options.collect do |k,v|
37
+ fields += options.map do |k, v|
40
38
  next unless v
41
39
  Curl::PostField.content(k.to_s, v)
42
40
  end.compact
@@ -48,50 +46,45 @@ module Bitex
48
46
  code = curl.response_code
49
47
 
50
48
  unless [200, 201, 202].include?(code)
51
- raise ApiError.new("Got #{code} fetching #{path} with
52
- #{options}\n\n#{curl.head}\n\n#{curl.body}")
49
+ raise ApiError, "Got #{code} fetching #{path} with #{options}\n\n#{curl.head}\n\n#{curl.body}"
53
50
  end
54
51
 
55
- return curl
52
+ curl
56
53
  end
57
-
58
- def self.public(path, options={})
59
- c = curl(:GET, path)
60
- JSON.parse(c.body)
54
+
55
+ def self.public(path, _options = {})
56
+ response = curl(:GET, path)
57
+ JSON.parse(response.body)
61
58
  end
62
-
63
- def self.private(verb, path, options={}, files={})
64
- if Bitex.api_key.nil?
65
- raise StandardError.new("No api_key available to make private key calls")
66
- end
67
- c = curl(verb, path, options.merge(api_key: Bitex.api_key), files)
68
- JSON.parse(c.body)
59
+
60
+ def self.private(verb, path, options = {}, files = {})
61
+ raise StandardError, 'No api_key available to make private key calls' if Bitex.api_key.nil?
62
+ response = curl(verb, path, options.merge(api_key: Bitex.api_key), files)
63
+ JSON.parse(response.body)
69
64
  end
70
-
71
- # Deserialize a single object from a json representation as specified on the
72
- # bitex API class reference
65
+
66
+ # Deserialize a single object from a json representation as specified on the bitex API class reference
73
67
  # @see https://bitex.la/developers#api-class-reference
74
68
  def self.deserialize(object)
75
- { 1 => Bid,
69
+ {
70
+ 1 => Bid,
76
71
  2 => Ask,
77
72
  3 => Buy,
78
73
  4 => Sell,
79
74
  5 => SpecieDeposit,
80
75
  6 => SpecieWithdrawal,
81
76
  7 => UsdDeposit,
82
- 8 => UsdWithdrawal,
83
- }[object.first].from_json(object)
77
+ 8 => UsdWithdrawal
78
+ }[object[0]].from_json(object)
84
79
  end
85
-
80
+
86
81
  # @visibility private
87
- def self.from_json(thing, json, with_specie=false, &block)
88
- thing.id = json[1]
89
- thing.created_at = Time.at(json[2])
90
- if with_specie
91
- thing.specie = {1 => :btc, 2 => :ltc}[json[3]]
82
+ def self.from_json(thing, json, &block)
83
+ thing.tap do |t|
84
+ t.id = json[1]
85
+ t.created_at = Time.at(json[2])
86
+ block.call(t)
92
87
  end
93
- block.call(thing)
94
- return thing
95
88
  end
96
89
  end
97
90
  end
@@ -0,0 +1,74 @@
1
+ module Bitex
2
+ # An Ask is an order to sell a given orderbook.
3
+ # @see BaseOrder
4
+ class Ask < BaseOrder
5
+ # @!attribute id
6
+ # @return [Integer] This Ask's unique ID.
7
+
8
+ # @!attribute created_at
9
+ # @return [Time] Time when this Ask was created.
10
+
11
+ # @!attribute orderbook
12
+ # @return [Symbol] :btc_usd or :btc_ars
13
+
14
+ # @!attribute quantity
15
+ # TODO: rever esta documentacion
16
+ # @return [BigDecimal] Quantity of specie to sell in this Ask.
17
+ attr_accessor :quantity
18
+
19
+ # @!attribute remaining_quantity
20
+ # TODO: rever esta documentacion
21
+ # @return [BigDecimal] Quantity of specie left to sell in this Ask.
22
+ attr_accessor :remaining_quantity
23
+
24
+ # @!attribute price
25
+ # @return [BigDecimal] Minimum price to charge per unit.
26
+
27
+ # @!attribute status
28
+ # The status of this Ask in its lifecycle.
29
+ # * :received queued to check if you have enough funds.
30
+ # * :executing available in our ourderbook waiting to be matched.
31
+ # * :cancelling To be cancelled as soon as our trading engine unlocks it.
32
+ # * :cancelled no further executed. May have some Remaining Quantity.
33
+ # * :completed Fully executed, Remaining Quantity should be 0.
34
+
35
+ # @!attribute reason
36
+ # The cancellation reason of this Ask.
37
+ # * :not_cancelled Has not been cancelled.
38
+ # * :not_enough_funds Not enough funds to place this order.
39
+ # * :user_cancelled Cancelled per user's request.
40
+ # * :system_cancelled Bitex cancelled this order, for a good reason.
41
+
42
+ # @!attribute produced_amount
43
+ # @return [BigDecimal] Amount of USD produced from this sale
44
+ attr_accessor :produced_amount
45
+
46
+ # @!attribute issuer
47
+ # @return [String] The issuer of this order, helps you tell apart orders created from the web UI and the API.
48
+
49
+ # @visibility private
50
+ def self.base_path
51
+ '/asks'
52
+ end
53
+
54
+ # TODO: rever esta documentacion
55
+ # Create a new Ask for selling a Quantity of specie charging no less than Price per each.
56
+ # @param orderbook [Symbol] :btc_usd or :btc_ars, whatever you're selling.
57
+ # @param quantity [BigDecimal] Quantity to sell.
58
+ # @param price [BigDecimal] Minimum price to charge when selling.
59
+ # @param wait [Boolean] Block the process and wait until this ask moves out of the :received state, defaults to false.
60
+ # @see https://bitex.la/developers#create-ask
61
+ def self.create!(orderbook, quantity, price, wait = false)
62
+ super
63
+ end
64
+
65
+ # @visibility private
66
+ def self.from_json(json, order = nil)
67
+ super(json, order).tap do |thing|
68
+ thing.quantity = (json[4].presence || 0).to_d
69
+ thing.remaining_quantity = (json[5].presence || 0).to_d
70
+ thing.produced_amount = (json[9].presence || 0).to_d
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,106 @@
1
+ module Bitex
2
+ # Base class for Bids and Asks
3
+ class BaseOrder
4
+ # @!attribute id
5
+ # @return [Integer] This Bid's unique ID.
6
+ attr_accessor :id
7
+
8
+ # @!attribute created_at
9
+ # @return [Time] Time when this Bid was created.
10
+ attr_accessor :created_at
11
+
12
+ # @!attribute orderbook
13
+ # @return [Symbol] :btc_usd or :btc_ars
14
+ attr_accessor :orderbook
15
+
16
+ # @!attribute price
17
+ # @return [BigDecimal] Maximum price to pay per unit.
18
+ attr_accessor :price
19
+
20
+ # @!attribute status
21
+ # The status in its lifecycle, was defined into Ask/Bid subclass.
22
+ attr_accessor :status
23
+
24
+ # @!attribute reason
25
+ # The cancellation reason for your Ask/Bid subclass, if any.
26
+ # * :not_cancelled Has not been cancelled.
27
+ # * :not_enough_funds Not enough funds to place this order.
28
+ # * :user_cancelled Cancelled per user's request
29
+ # * :system_cancelled Bitex cancelled this order, for a good reason.
30
+ attr_accessor :reason
31
+
32
+ # @!attribute issuer
33
+ # @return [String] The issuer of this order, helps you tell apart orders created from the web UI and the API.
34
+ attr_accessor :issuer
35
+
36
+ # Returns an array with all your active orders of this type and another order of this type that was active the last 2 hours.
37
+ # Uses {Order.all} under the hood.
38
+ def self.all
39
+ Order.all.select { |o| o.is_a?(self) }
40
+ end
41
+
42
+ # Returns an array with all your active orders of this type.
43
+ # Uses {Order.active} under the hood.
44
+ def self.active
45
+ Order.active.select { |o| o.is_a?(self) }
46
+ end
47
+
48
+ # Find an order in your list of active orders.
49
+ # Uses {Order.active} under the hood.
50
+ def self.find(id)
51
+ from_json(Api.private(:get, "/private#{base_path}/#{id}"))
52
+ end
53
+
54
+ # @visibility private
55
+ def self.create!(orderbook, amount, price, wait = false)
56
+ params = { amount: amount, price: price, orderbook: { btc_usd: 1, btc_ars: 5 }[orderbook] }
57
+
58
+ order = from_json(Api.private(:post, "/private#{base_path}", params))
59
+ retries = 0
60
+ while wait && order.status == :received
61
+ sleep(0.2)
62
+ order = find_order(order)
63
+ retries += 1
64
+ # Wait 15 minutes for the order to be accepted.
65
+ raise StandardError, "Timed out waiting for #{base_path} ##{order.id}" if retries > 5000
66
+ end
67
+ order
68
+ end
69
+
70
+ def cancel!
71
+ path = "/private#{self.class.base_path}/#{id}/cancel"
72
+ self.class.from_json(Api.private(:post, path), self)
73
+ end
74
+
75
+ # @visibility private
76
+ def self.from_json(json, order = nil)
77
+ Api.from_json(order || new, json) do |thing|
78
+ thing.orderbook = orderbooks[json[3]]
79
+ thing.price = json[6].to_s.to_d
80
+ thing.status = statuses[json[7]]
81
+ thing.reason = reasons[json[8]]
82
+ thing.issuer = json[10]
83
+ end
84
+ end
85
+
86
+ private_class_method
87
+
88
+ def self.find_order(order)
89
+ find(order.id)
90
+ rescue StandardError
91
+ order
92
+ end
93
+
94
+ def self.orderbooks
95
+ { 1 => :btc_usd, 5 => :btc_ars }
96
+ end
97
+
98
+ def self.reasons
99
+ { 0 => :not_cancelled, 1 => :not_enough_funds, 2 => :user_cancelled, 3 => :system_cancelled }
100
+ end
101
+
102
+ def self.statuses
103
+ { 1 => :received, 2 => :executing, 3 => :cancelling, 4 => :cancelled, 5 => :completed }
104
+ end
105
+ end
106
+ end