bitex_bot 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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