bitex_bot 0.6.1 → 0.9.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/Gemfile +3 -1
  4. data/bitex_bot.gemspec +5 -2
  5. data/lib/bitex_bot/database.rb +2 -2
  6. data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +47 -35
  7. data/lib/bitex_bot/models/api_wrappers/bitex/bitex_api_wrapper.rb +178 -0
  8. data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +62 -45
  9. data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +52 -28
  10. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +61 -28
  11. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +12 -6
  12. data/lib/bitex_bot/models/buy_closing_flow.rb +3 -2
  13. data/lib/bitex_bot/models/buy_opening_flow.rb +31 -6
  14. data/lib/bitex_bot/models/closing_flow.rb +37 -22
  15. data/lib/bitex_bot/models/open_buy.rb +1 -3
  16. data/lib/bitex_bot/models/open_sell.rb +1 -3
  17. data/lib/bitex_bot/models/opening_flow.rb +42 -28
  18. data/lib/bitex_bot/models/order_book_simulator.rb +14 -13
  19. data/lib/bitex_bot/models/sell_closing_flow.rb +3 -2
  20. data/lib/bitex_bot/models/sell_opening_flow.rb +29 -4
  21. data/lib/bitex_bot/robot.rb +28 -43
  22. data/lib/bitex_bot/settings.rb +2 -0
  23. data/lib/bitex_bot/version.rb +1 -1
  24. data/settings.rb.sample +23 -5
  25. data/spec/bitex_bot/settings_spec.rb +13 -6
  26. data/spec/factories/bitex_ask.rb +14 -0
  27. data/spec/factories/bitex_bid.rb +14 -0
  28. data/spec/factories/bitex_buy.rb +7 -7
  29. data/spec/factories/bitex_sell.rb +7 -7
  30. data/spec/factories/buy_opening_flow.rb +10 -10
  31. data/spec/factories/open_buy.rb +8 -8
  32. data/spec/factories/open_sell.rb +8 -8
  33. data/spec/factories/sell_opening_flow.rb +10 -10
  34. data/spec/fixtures/bitstamp/balance.yml +63 -0
  35. data/spec/fixtures/bitstamp/order_book.yml +60 -0
  36. data/spec/fixtures/bitstamp/orders/all.yml +62 -0
  37. data/spec/fixtures/bitstamp/orders/failure_sell.yml +60 -0
  38. data/spec/fixtures/bitstamp/orders/successful_buy.yml +62 -0
  39. data/spec/fixtures/bitstamp/transactions.yml +244 -0
  40. data/spec/fixtures/bitstamp/user_transactions.yml +223 -0
  41. data/spec/models/api_wrappers/bitex_api_wrapper_spec.rb +147 -0
  42. data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +134 -140
  43. data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +9 -3
  44. data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +142 -73
  45. data/spec/models/bitex_api_spec.rb +4 -4
  46. data/spec/models/buy_closing_flow_spec.rb +19 -24
  47. data/spec/models/buy_opening_flow_spec.rb +102 -83
  48. data/spec/models/order_book_simulator_spec.rb +5 -0
  49. data/spec/models/robot_spec.rb +7 -4
  50. data/spec/models/sell_closing_flow_spec.rb +21 -25
  51. data/spec/models/sell_opening_flow_spec.rb +100 -80
  52. data/spec/spec_helper.rb +3 -1
  53. data/spec/support/bitex_stubs.rb +80 -40
  54. data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +2 -2
  55. data/spec/support/bitstamp/bitstamp_stubs.rb +3 -3
  56. data/spec/support/vcr.rb +8 -0
  57. data/spec/support/webmock.rb +8 -0
  58. metadata +77 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc7c52b5a1df2b0bd97b57321bcab7f3590749ec
4
- data.tar.gz: eebb4685c79121101d8432426141cb6f0d011e8d
3
+ metadata.gz: 8a52c57b2cb3f955ac849623421abc8296b1dc9c
4
+ data.tar.gz: b1a88154cb71ef5868c510f9316f0e755b161c33
5
5
  SHA512:
6
- metadata.gz: f281ff61388642e7f5cd810470cf8b2a695deca0306d22ee3fd0f7c5b840f81208df5936c0545a8736c371907d60eb748f5419e08e897c9bdddd83bda2d0ad32
7
- data.tar.gz: 3ac028d9b20737a43e142f83352471c4c85b82ac1950cb52b244811434cceb22fb565b9efc1db124aa13d8e204966ce6bc9373ddf390b175bd36a6ae9dc29d00
6
+ metadata.gz: 1cb5d465e54e321a5d181aa86e988c0183938a70540ffcb5434000abf55627d9e8f875da413d67dd1cfa07892f0198c6be7d7c628d2e46a1a31caed5c6afa17d
7
+ data.tar.gz: 8cef55c3f44533c9b43c7f82bede8a7fb2777e248ad533845a50ac6cc9006cf59bdb95499d94a5f8a6b509fa0a7bff46347d6020bb765f5cd503358a34512dea
data/.rubocop.yml CHANGED
@@ -2,16 +2,16 @@ Style/FrozenStringLiteralComment:
2
2
  EnforcedStyle: never
3
3
 
4
4
  Metrics/AbcSize:
5
- Max: 16
5
+ Max: 18
6
6
 
7
7
  Metrics/ClassLength:
8
- Max: 130
8
+ Enabled: false
9
9
 
10
10
  Metrics/LineLength:
11
11
  Max: 130
12
12
 
13
13
  Metrics/MethodLength:
14
- Max: 25
14
+ Max: 30
15
15
 
16
16
  Metrics/ParameterLists:
17
17
  Max: 7
data/Gemfile CHANGED
@@ -2,4 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in bitex_bot.gemspec
4
4
  gemspec
5
- gem 'bitstamp', github: 'bitex-la/bitstamp'
5
+
6
+ gem 'bitex', github: 'bitex-la/bitex-ruby', branch: 'bitcoin-cash-market'
7
+ gem 'bitstamp', github: 'bitex-la/bitstamp', branch: 'update-lib'
data/bitex_bot.gemspec CHANGED
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = %w[lib]
20
20
 
21
- spec.add_dependency 'activerecord', '~> 4.2'
21
+ spec.add_dependency 'activerecord'
22
22
  spec.add_dependency 'hashie', '~> 3.5.4'
23
23
  spec.add_dependency 'mail'
24
24
  spec.add_dependency 'sqlite3'
25
25
 
26
26
  spec.add_dependency 'bitex', '0.6'
27
27
  spec.add_dependency 'bitstamp'
28
- spec.add_dependency 'itbit', '0.0.6'
28
+ spec.add_dependency 'itbit'
29
29
  spec.add_dependency 'kraken_client', '~> 1.2.1'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
@@ -34,11 +34,14 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'byebug'
35
35
  spec.add_development_dependency 'database_cleaner'
36
36
  spec.add_development_dependency 'factory_bot'
37
+ spec.add_development_dependency 'faker'
37
38
  spec.add_development_dependency 'rspec'
39
+ spec.add_development_dependency 'rspec-its'
38
40
  spec.add_development_dependency 'rspec-mocks'
39
41
  spec.add_development_dependency 'rspec_junit_formatter'
40
42
  spec.add_development_dependency 'rubocop'
41
43
  spec.add_development_dependency 'shoulda-matchers'
42
44
  spec.add_development_dependency 'timecop'
45
+ spec.add_development_dependency 'vcr'
43
46
  spec.add_development_dependency 'webmock'
44
47
  end
@@ -53,7 +53,7 @@ module BitexBot
53
53
  t.decimal :quantity, precision: 30, scale: 15
54
54
  t.decimal :amount, precision: 30, scale: 15
55
55
  t.boolean :done, null: false, default: false
56
- t.decimal :crypto_profit, precision: 30, scale: 15
56
+ t.decimal :crypto_profit, precision: 30, scale: 15
57
57
  t.decimal :fiat_profit, precision: 30, scale: 15
58
58
  t.decimal :fx_rate, precision: 20, scale: 8
59
59
  t.timestamps null: true
@@ -64,7 +64,7 @@ module BitexBot
64
64
  t.decimal :quantity, precision: 30, scale: 15
65
65
  t.decimal :amount, precision: 30, scale: 15
66
66
  t.boolean :done, null: false, default: false
67
- t.decimal :crypto_profit, precision: 30, scale: 15
67
+ t.decimal :crypto_profit, precision: 30, scale: 15
68
68
  t.decimal :fiat_profit, precision: 30, scale: 15
69
69
  t.decimal :fx_rate, precision: 20, scale: 8
70
70
  t.timestamps null: true
@@ -1,12 +1,15 @@
1
1
  # This class represents the general behaviour for trading platform wrappers.
2
2
  class ApiWrapper
3
+ attr_accessor :currency_pair
4
+
3
5
  MIN_AMOUNT = 5
4
6
 
5
7
  Transaction = Struct.new(
6
- :id, # Integer
7
- :price, # Decimal
8
- :amount, # Decimal
9
- :timestamp # Epoch Integer
8
+ :id, # Integer
9
+ :price, # Decimal
10
+ :amount, # Decimal
11
+ :timestamp, # Epoch Integer
12
+ :raw # Actual transaction
10
13
  )
11
14
 
12
15
  Order = Struct.new(
@@ -15,14 +18,14 @@ class ApiWrapper
15
18
  :price, # Decimal
16
19
  :amount, # Decimal
17
20
  :timestamp, # Integer
18
- :raw_order # Actual order object
21
+ :raw # Actual order object
19
22
  ) do
20
23
  def method_missing(method_name, *args, &block)
21
- raw_order.respond_to?(method_name) ? raw_order.send(method_name, *args, &block) : super
24
+ raw.respond_to?(method_name) ? raw.send(method_name, *args, &block) : super
22
25
  end
23
26
 
24
27
  def respond_to_missing?(method_name, include_private = false)
25
- raw_order.respond_to?(method_name) || super
28
+ raw.respond_to?(method_name) || super
26
29
  end
27
30
  end
28
31
 
@@ -50,58 +53,53 @@ class ApiWrapper
50
53
  )
51
54
 
52
55
  UserTransaction = Struct.new(
53
- :order_id, # Integer
54
- :usd, # Decimal
55
- :btc, # Decimal
56
- :btc_usd, # Decimal
57
- :fee, # Decimal
58
- :type, # Integer
59
- :timestamp # Epoch Integer
56
+ :order_id, # Integer
57
+ :fiat, # Decimal
58
+ :crypto, # Decimal
59
+ :crypto_fiat, # Decimal
60
+ :fee, # Decimal
61
+ :type, # Integer
62
+ :timestamp # Epoch Integer
60
63
  )
61
64
 
62
- # @return [Void]
63
- def self.setup(_settings)
64
- raise 'self subclass responsibility'
65
- end
66
-
67
65
  # @return [Array<Transaction>]
68
- def self.transactions
66
+ def transactions
69
67
  raise 'self subclass responsibility'
70
68
  end
71
69
 
72
70
  # @return [OrderBook]
73
- def self.order_book(_retries = 20)
71
+ def order_book(_retries = 20)
74
72
  raise 'self subclass responsibility'
75
73
  end
76
74
 
77
75
  # @return [BalanceSummary]
78
- def self.balance
76
+ def balance
79
77
  raise 'self subclass responsibility'
80
78
  end
81
79
 
82
80
  # @return [nil]
83
- def self.cancel
81
+ def cancel
84
82
  raise 'self subclass responsibility'
85
83
  end
86
84
 
87
85
  # @return [Array<Order>]
88
- def self.orders
86
+ def orders
89
87
  raise 'self subclass responsibility'
90
88
  end
91
89
 
92
90
  # @return [UserTransaction]
93
- def self.user_transacitions
91
+ def user_transactions
94
92
  raise 'self subclass responsibility'
95
93
  end
96
94
 
97
95
  # @param type
98
96
  # @param price
99
97
  # @param quantity
100
- def self.place_order(type, price, quantity)
98
+ def place_order(type, price, quantity)
101
99
  order = send_order(type, price, quantity)
102
100
  return order unless order.nil? || order.id.nil?
103
101
 
104
- BitexBot::Robot.log(:debug, "Captured error when placing order on #{self.class.name}")
102
+ BitexBot::Robot.log(:debug, "Captured error when placing order on #{self.class}")
105
103
  # Order may have gone through and be stuck somewhere in Wrapper's pipeline.
106
104
  # We just sleep for a bit and then look for the order.
107
105
  20.times do
@@ -109,32 +107,46 @@ class ApiWrapper
109
107
  order = find_lost(type, price, quantity)
110
108
  return order if order.present?
111
109
  end
112
- raise OrderNotFound, "Closing: #{type} order not found for #{quantity} BTC @ $#{price}. #{order}"
110
+ raise OrderNotFound, "Closing: #{type} order not found for #{quantity} #{base} @ #{quote} #{price}. #{order}"
113
111
  end
114
112
 
115
113
  # Hook Method - arguments could not be used in their entirety by the subclasses
116
- def self.send_order(_type, _price, _quantity)
114
+ def send_order(_type, _price, _quantity)
117
115
  raise 'self subclass responsibility'
118
116
  end
119
117
 
120
- # @param order_method [String] buy|sell
118
+ # @param order_type [String] buy|sell
121
119
  # @param price [Decimal]
120
+ # @param quantity [Decimal]
122
121
  #
123
122
  # Hook Method - arguments could not be used in their entirety by the subclasses
124
- def self.find_lost(_type, _price, _quantity)
123
+ def find_lost(_type, _price, _quantity)
125
124
  raise 'self subclass responsibility'
126
125
  end
127
126
 
127
+ # From an order when you buy or sell, when you place an order and it matches, you can match more than one order.
128
128
  # @param order_id
129
- # @param transactions
129
+ # @param transactions: all matches for a purchase or sale order.
130
130
  #
131
131
  # @return [Array<Decimal, Decimal>]
132
- def self.amount_and_quantity(_order_id, _transactions)
132
+ def amount_and_quantity(_order_id)
133
133
  raise 'self subclass responsibility'
134
134
  end
135
135
 
136
- def self.enough_order_size?(quantity, price)
137
- (quantity * price) > MIN_AMOUNT
136
+ def enough_order_size?(quantity, price)
137
+ quantity * price > MIN_AMOUNT
138
+ end
139
+
140
+ def base_quote
141
+ "#{base}_#{quote}"
142
+ end
143
+
144
+ def base
145
+ currency_pair[:base]
146
+ end
147
+
148
+ def quote
149
+ currency_pair[:quote]
138
150
  end
139
151
  end
140
152
 
@@ -0,0 +1,178 @@
1
+ # Wrapper implementation for Bitex API.
2
+ # https://bitex.la/developers
3
+ class BitexApiWrapper < ApiWrapper
4
+ Order = Struct.new(
5
+ :id, # String
6
+ :type, # Symbol
7
+ :price, # Decimal
8
+ :amount, # Decimal
9
+ :timestamp, # Integer
10
+ :raw # Actual order object
11
+ ) do
12
+ def method_missing(method_name, *args, &block)
13
+ raw.respond_to?(method_name) ? raw.send(method_name, *args, &block) : super
14
+ end
15
+
16
+ def respond_to_missing?(method_name, include_private = false)
17
+ raw.respond_to?(method_name) || super
18
+ end
19
+ end
20
+
21
+ attr_accessor :api_key, :ssl_version, :debug, :sandbox
22
+
23
+ def initialize(settings)
24
+ self.api_key = settings.api_key
25
+ self.ssl_version = settings.ssl_version
26
+ self.debug = settings.debug
27
+ self.sandbox = settings.sandbox
28
+ currency_pair(settings.order_book)
29
+ setup
30
+ end
31
+
32
+ def setup
33
+ Bitex.api_key = api_key
34
+ Bitex.ssl_version = ssl_version
35
+ Bitex.debug = debug
36
+ Bitex.sandbox = sandbox
37
+ end
38
+
39
+ def profile
40
+ Bitex::Profile.get
41
+ end
42
+
43
+ # rubocop:disable Metrics/AbcSize
44
+ def amount_and_quantity(order_id)
45
+ closes = user_transactions.select { |t| t.order_id.to_s == order_id }
46
+ amount = closes.map { |c| c.send(currency[:quote]).to_d }.sum.abs
47
+ quantity = closes.map { |c| c.send(currency[:base]).to_d }.sum.abs
48
+
49
+ [amount, quantity]
50
+ end
51
+ # rubocop:enable Metrics/AbcSize
52
+
53
+ def balance
54
+ balance_summary_parser(profile)
55
+ end
56
+
57
+ def find_lost(type, price, _quantity)
58
+ orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
59
+ end
60
+
61
+ def order_book
62
+ order_book_parser(market.order_book)
63
+ end
64
+
65
+ def orders
66
+ Bitex::Order.all.map { |o| order_parser(o) }
67
+ end
68
+
69
+ def send_order(type, price, quantity, wait = false)
70
+ { sell: Bitex::Ask, buy: Bitex::Bid }[type].create!(base_quote.to_sym, quantity, price, wait)
71
+ end
72
+
73
+ def transactions
74
+ Bitex::Trade.all.map { |t| transaction_parser(t) }
75
+ end
76
+
77
+ def user_transactions
78
+ Bitex::Trade.all.map { |trade| user_transaction_parser(trade) }
79
+ end
80
+
81
+ # {
82
+ # usd_balance: 10000.00, # Total USD balance.
83
+ # usd_reserved: 2000.00, # USD reserved in open orders.
84
+ # usd_available: 8000.00, # USD available for trading.
85
+ # btc_balance: 20.00000000, # Total BTC balance.
86
+ # btc_reserved: 5.00000000, # BTC reserved in open orders.
87
+ # btc_available: 15.00000000, # BTC available for trading.
88
+ # fee: 0.5, # Your trading fee (0.5 means 0.5%).
89
+ # btc_deposit_address: "1XXXXXXXX..." # Your BTC deposit address.
90
+ # }
91
+ def balance_summary_parser(balances)
92
+ BalanceSummary.new(
93
+ balance_parser(balances, currency_pair[:base]),
94
+ balance_parser(balances, currency_pair[:quote]),
95
+ balances[:fee].to_d
96
+ )
97
+ end
98
+
99
+ def balance_parser(balances, currency)
100
+ Balance.new(
101
+ balances["#{currency}_balance".to_sym].to_d,
102
+ balances["#{currency}_reserved".to_sym].to_d,
103
+ balances["#{currency}_available".to_sym].to_d
104
+ )
105
+ end
106
+
107
+ def last_order_by(price)
108
+ orders.select { |o| o.price == price && (o.timestamp - Time.now.to_i).abs < 500 }.first
109
+ end
110
+
111
+ # {
112
+ # bids: [[0.63921e3, 0.195e1], [0.637e3, 0.47e0], [0.63e3, 0.158e1]],
113
+ # asks: [[0.6424e3, 0.4e0], [0.6433e3, 0.95e0], [0.6443e3, 0.25e0]]
114
+ # }
115
+ def order_book_parser(book)
116
+ OrderBook.new(Time.now.to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
117
+ end
118
+
119
+ def order_summary_parser(orders)
120
+ orders.map { |order| OrderSummary.new(order[0].to_d, order[1].to_d) }
121
+ end
122
+
123
+ # <Bitex::Bid
124
+ # @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @price=0.1e4, @status=:executing, @reason=nil,
125
+ # @issuer=nil, @amount=0.1e3, @remaining_amount=0.1e2, @produced_quantity=0.0
126
+ # >
127
+ def order_parser(order)
128
+ Order.new(order.id.to_s, order_type(order), order.price, order_amount(order), order.created_at.to_i, order)
129
+ end
130
+
131
+ def order_type(order)
132
+ order.is_a?(Bitex::Bid) ? :buy : :sell
133
+ end
134
+
135
+ def order_amount(order)
136
+ order.is_a?(Bitex::Bid) ? order.amount : order.quantity
137
+ end
138
+
139
+ # [
140
+ # [1492795215, 80310, 1243.51657154, 4.60321971],
141
+ # [UNIX timestamp, Transaction ID, Price Paid, Amound Sold]
142
+ # ]
143
+ def transaction_parser(transaction)
144
+ Transaction.new(transaction.id, transaction.price.to_d, transaction.amount.to_d, transaction.created_at.to_i, transaction)
145
+ end
146
+
147
+ # <Bitex::Buy:0x007ff9a2979390
148
+ # @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @quantity=0.2e1, @amount=0.6e3, @fee=0.5e-1,
149
+ # @price=0.3e3, @bid_id=123
150
+ # >
151
+ #
152
+ # <Bitex::Sell:0x007ff9a2978710
153
+ # @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @quantity=0.2e1, @amount=0.6e3, @fee=0.5e-1,
154
+ # @price=0.3e3, @ask_id=456i
155
+ # >
156
+ def user_transaction_parser(trade)
157
+ UserTransaction.new(
158
+ trade.id, trade.amount, trade.quantity, trade.price, trade.fee, trade_type(trade), trade.created_at.to_i
159
+ )
160
+ end
161
+
162
+ def trade_type(trade)
163
+ # ask: 0, bid: 1
164
+ trade.is_a?(Bitex::Buy) ? 1 : 0
165
+ end
166
+
167
+ def market
168
+ @market ||= { btc: Bitex::BitcoinMarketData, bch: Bitex::BitcoinCashMarketData }[currency_pair[:base].to_sym]
169
+ end
170
+
171
+ def currency_pair(order_book = '_')
172
+ @currency_pair ||= {
173
+ name: order_book,
174
+ base: order_book.split('_').first,
175
+ quote: order_book.split('_').last
176
+ }
177
+ end
178
+ end
@@ -1,41 +1,45 @@
1
1
  # Wrapper implementation for Bitstamp API.
2
2
  # https://www.bitstamp.net/api/
3
3
  class BitstampApiWrapper < ApiWrapper
4
- def self.setup(settings)
4
+ attr_accessor :key, :secret, :client_id
5
+
6
+ def initialize(settings)
7
+ self.key = settings.api_key
8
+ self.secret = settings.secret
9
+ self.client_id = settings.client_id
10
+ currency_pair(settings.order_book)
11
+ setup
12
+ end
13
+
14
+ def setup
5
15
  Bitstamp.setup do |config|
6
- config.key = settings.api_key
7
- config.secret = settings.secret
8
- config.client_id = settings.client_id
16
+ config.key = key
17
+ config.secret = secret
18
+ config.client_id = client_id
9
19
  end
10
20
  end
11
21
 
12
- def self.amount_and_quantity(order_id, transactions)
13
- closes = transactions.select { |t| t.order_id.to_s == order_id }
14
- amount = closes.map { |c| c.usd.to_d }.sum.abs
15
- quantity = closes.map { |c| c.btc.to_d }.sum.abs
22
+ def amount_and_quantity(order_id)
23
+ closes = user_transactions.select { |t| t.order_id.to_s == order_id }
24
+ amount = closes.sum(&:fiat).abs
25
+ quantity = closes.sum(&:crypto).abs
16
26
 
17
27
  [amount, quantity]
18
28
  end
19
29
 
20
- def self.balance
21
- balance_summary_parser(Bitstamp.balance.symbolize_keys)
30
+ def balance
31
+ balance_summary_parser(Bitstamp.balance(currency_pair[:name]).symbolize_keys)
22
32
  rescue StandardError => e
23
33
  raise ApiWrapperError, "Bitstamp balance failed: #{e.message}"
24
34
  end
25
35
 
26
- def self.cancel(order)
27
- Bitstamp::Order.new(id: order.id).cancel!
28
- rescue StandardError => e
29
- raise ApiWrapperError, "Bitstamp cancel! failed: #{e.message}"
30
- end
31
-
32
- def self.find_lost(type, price, _quantity)
36
+ def find_lost(type, price, _quantity)
33
37
  orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
34
38
  end
35
39
 
36
40
  # rubocop:disable Metrics/AbcSize
37
- def self.order_book(retries = 20)
38
- book = Bitstamp.order_book.deep_symbolize_keys
41
+ def order_book(retries = 20)
42
+ book = Bitstamp.order_book(currency_pair[:name]).deep_symbolize_keys
39
43
  age = Time.now.to_i - book[:timestamp].to_i
40
44
  return order_book_parser(book) if age <= 300
41
45
 
@@ -50,24 +54,25 @@ class BitstampApiWrapper < ApiWrapper
50
54
  end
51
55
  # rubocop:enable Metrics/AbcSize
52
56
 
53
- def self.orders
54
- Bitstamp.orders.all.map { |o| order_parser(o) }
57
+ def orders
58
+ Bitstamp.orders.all(currency_pair: currency_pair[:name]).map { |o| order_parser(o) }
55
59
  rescue StandardError => e
56
60
  raise ApiWrapperError, "Bitstamp orders failed: #{e.message}"
57
61
  end
58
62
 
59
- def self.send_order(type, price, quantity)
60
- Bitstamp.orders.send(type, amount: quantity.round(4), price: price.round(2))
63
+ def send_order(type, price, quantity)
64
+ order = Bitstamp.orders.send(type, currency_pair: currency_pair[:name], amount: quantity.round(4), price: price.round(2))
65
+ order_parser(order) unless order.error.present?
61
66
  end
62
67
 
63
- def self.transactions
64
- Bitstamp.transactions.map { |t| transaction_parser(t) }
68
+ def transactions
69
+ Bitstamp.transactions(currency_pair[:name]).map { |t| transaction_parser(t) }
65
70
  rescue StandardError => e
66
71
  raise ApiWrapperError, "Bitstamp transactions failed: #{e.message}"
67
72
  end
68
73
 
69
- def self.user_transactions
70
- Bitstamp.user_transactions.all.map { |ut| user_transaction_parser(ut) }
74
+ def user_transactions
75
+ Bitstamp.user_transactions.all(currency_pair: currency_pair[:name]).map { |ut| user_transaction_parser(ut) }
71
76
  rescue StandardError => e
72
77
  raise ApiWrapperError, "Bitstamp user_transactions failed: #{e.message}"
73
78
  end
@@ -77,11 +82,15 @@ class BitstampApiWrapper < ApiWrapper
77
82
  # usd_reserved: '1.02, usd_available: '6952.05', usd_balance: '6953.07',
78
83
  # fee: '0.4000'
79
84
  # }
80
- def self.balance_summary_parser(balances)
81
- BalanceSummary.new(balance_parser(balances, :btc), balance_parser(balances, :usd), balances[:fee].to_d)
85
+ def balance_summary_parser(balances)
86
+ BalanceSummary.new(
87
+ balance_parser(balances, currency_pair[:base]),
88
+ balance_parser(balances, currency_pair[:quote]),
89
+ balances[:fee].to_d
90
+ )
82
91
  end
83
92
 
84
- def self.balance_parser(balances, currency)
93
+ def balance_parser(balances, currency)
85
94
  Balance.new(
86
95
  balances["#{currency}_balance".to_sym].to_d,
87
96
  balances["#{currency}_reserved".to_sym].to_d,
@@ -94,42 +103,50 @@ class BitstampApiWrapper < ApiWrapper
94
103
  # bids: [['124.55', '1.58057006'], ['124.40', '14.91779125']],
95
104
  # asks: [['124.56', '0.81888247'], ['124.57', '0.81078911']]
96
105
  # }
97
- def self.order_book_parser(book)
106
+ def order_book_parser(book)
98
107
  OrderBook.new(book[:timestamp].to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
99
108
  end
100
109
 
101
- def self.order_is_done?(order)
110
+ def order_is_done?(order)
102
111
  order.nil?
103
112
  end
104
113
 
105
- # <Bitstamp::Order @id=76, @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>
106
- def self.order_parser(order)
107
- type = order.type.zero? ? :buy : :sell
114
+ # <Bitstamp::Order @id='76', @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>
115
+ def order_parser(order)
116
+ type = order.type == '0' ? :buy : :sell
108
117
  Order.new(order.id.to_s, type, order.price.to_d, order.amount.to_d, order.datetime.to_datetime.to_i, order)
109
118
  end
110
119
 
111
- def self.order_summary_parser(orders)
120
+ def order_summary_parser(orders)
112
121
  orders.map { |order| OrderSummary.new(order[0].to_d, order[1].to_d) }
113
122
  end
114
123
 
115
- # <Bitstamp::Transactions: @tid=1469074, @price='126.95', @amount='1.10000000', @date='1380648951'>
116
- def self.transaction_parser(transaction)
117
- Transaction.new(transaction.tid, transaction.price.to_d, transaction.amount.to_d, transaction.date.to_i)
124
+ # <Bitstamp::Transactions: @tid='1469074', @price='126.95', @amount='1.10000000', @date='1380648951'>
125
+ def transaction_parser(transaction)
126
+ Transaction.new(transaction.tid, transaction.price.to_d, transaction.amount.to_d, transaction.date.to_i, transaction)
118
127
  end
119
128
 
120
129
  # <Bitstamp::UserTransaction:
121
130
  # @usd='-373.51', @btc='3.00781124', @btc_usd='124.18', @order_id=7623942, @fee='1.50', @type=2, @id=1444404,
122
131
  # @datetime='2013-09-26 13:28:55'
123
132
  # >
124
- def self.user_transaction_parser(user_transaction)
133
+ def user_transaction_parser(user_transaction)
125
134
  UserTransaction.new(
126
135
  user_transaction.order_id,
127
- user_transaction.usd.to_d,
128
- user_transaction.btc.to_d,
129
- user_transaction.btc_usd.to_d,
136
+ user_transaction.send(quote).to_d,
137
+ user_transaction.send(base).to_d,
138
+ user_transaction.send(base_quote).to_d,
130
139
  user_transaction.fee.to_d,
131
- user_transaction.type,
132
- Time.new(user_transaction.datetime).to_i
140
+ user_transaction.type.to_i,
141
+ Time.parse(user_transaction.datetime).to_i
133
142
  )
134
143
  end
144
+
145
+ def currency_pair(order_book = '')
146
+ @currency_pair ||= {
147
+ name: order_book,
148
+ base: order_book.slice(0..2),
149
+ quote: order_book.slice(3..5)
150
+ }
151
+ end
135
152
  end