bitex_bot 0.3.7 → 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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +63 -0
  3. data/.rubocop.yml +33 -0
  4. data/Gemfile +1 -1
  5. data/Rakefile +1 -1
  6. data/bin/bitex_bot +1 -1
  7. data/bitex_bot.gemspec +34 -34
  8. data/lib/bitex_bot/database.rb +67 -67
  9. data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
  10. data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
  11. data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
  12. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
  13. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
  14. data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
  15. data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
  16. data/lib/bitex_bot/models/close_buy.rb +2 -2
  17. data/lib/bitex_bot/models/closing_flow.rb +98 -79
  18. data/lib/bitex_bot/models/open_buy.rb +11 -10
  19. data/lib/bitex_bot/models/open_sell.rb +11 -10
  20. data/lib/bitex_bot/models/opening_flow.rb +157 -99
  21. data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
  22. data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
  23. data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
  24. data/lib/bitex_bot/models/store.rb +3 -1
  25. data/lib/bitex_bot/robot.rb +203 -176
  26. data/lib/bitex_bot/settings.rb +71 -12
  27. data/lib/bitex_bot/version.rb +1 -1
  28. data/lib/bitex_bot.rb +40 -16
  29. data/settings.rb.sample +43 -66
  30. data/spec/bitex_bot/settings_spec.rb +87 -15
  31. data/spec/factories/bitex_buy.rb +3 -3
  32. data/spec/factories/bitex_sell.rb +3 -3
  33. data/spec/factories/buy_opening_flow.rb +1 -1
  34. data/spec/factories/open_buy.rb +12 -10
  35. data/spec/factories/open_sell.rb +12 -10
  36. data/spec/factories/sell_opening_flow.rb +1 -1
  37. data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
  38. data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
  39. data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
  40. data/spec/models/bitex_api_spec.rb +1 -1
  41. data/spec/models/buy_closing_flow_spec.rb +140 -71
  42. data/spec/models/buy_opening_flow_spec.rb +126 -56
  43. data/spec/models/order_book_simulator_spec.rb +10 -10
  44. data/spec/models/robot_spec.rb +61 -47
  45. data/spec/models/sell_closing_flow_spec.rb +130 -62
  46. data/spec/models/sell_opening_flow_spec.rb +129 -60
  47. data/spec/spec_helper.rb +19 -16
  48. data/spec/support/bitex_stubs.rb +13 -14
  49. data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
  50. data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
  51. metadata +60 -42
  52. data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
  53. data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
  54. data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
  55. data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
  56. data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
  57. data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
  58. data/spec/models/itbit_api_wrapper_spec.rb +0 -15
  59. data/spec/support/bitstamp_stubs.rb +0 -110
@@ -1,241 +1,268 @@
1
- trap "INT" do
1
+ trap 'INT' do
2
2
  if BitexBot::Robot.graceful_shutdown
3
3
  print "\b"
4
- BitexBot::Robot.logger.info("Ok, ok, I'm out.")
4
+ BitexBot::Robot.log(:info, "Ok, ok, I'm out.")
5
5
  exit 1
6
6
  end
7
7
  BitexBot::Robot.graceful_shutdown = true
8
- BitexBot::Robot.logger.info("Shutting down as soon as I've cleaned up.")
8
+ BitexBot::Robot.log(:info, "Shutting down as soon as I've cleaned up.")
9
9
  end
10
10
 
11
11
  module BitexBot
12
+ # Documentation here!
13
+ # rubocop:disable Metrics/ClassLength
12
14
  class Robot
15
+ extend Forwardable
16
+
17
+ cattr_accessor :taker
18
+
13
19
  cattr_accessor :graceful_shutdown
14
20
  cattr_accessor :cooldown_until
15
- cattr_accessor :test_mode
16
- cattr_accessor :taker do
17
- case Settings.taker
18
- when 'itbit'
19
- ItbitApiWrapper
20
- when 'bitstamp'
21
- BitstampApiWrapper
22
- when 'bitfinex'
23
- BitfinexApiWrapper
24
- when 'kraken'
25
- KrakenApiWrapper
26
- end
27
- end
28
- cattr_accessor :logger do
29
- STDOUT.sync = true unless logdev = Settings.log.try(:file)
30
- Logger.new(logdev || STDOUT, 10, 10240000).tap do |l|
31
- l.level = Logger.const_get(Settings.log.level.upcase)
32
- l.formatter = proc do |severity, datetime, progname, msg|
33
- date = datetime.strftime("%m/%d %H:%M:%S.%L")
34
- "#{ '%-6s' % severity } #{date}: #{msg}\n"
21
+ cattr_accessor(:current_cooldowns) { 0 }
22
+
23
+ cattr_accessor(:logger) do
24
+ logdev = Settings.log.try(:file)
25
+ STDOUT.sync = true unless logdev.present?
26
+ Logger.new(logdev || STDOUT, 10, 10_240_000).tap do |log|
27
+ log.level = Logger.const_get(Settings.log.level.upcase)
28
+ log.formatter = proc do |severity, datetime, _progname, msg|
29
+ date = datetime.strftime('%m/%d %H:%M:%S.%L')
30
+ "#{format('%-6s', severity)} #{date}: #{msg}\n"
35
31
  end
36
32
  end
37
33
  end
38
- cattr_accessor :current_cooldowns do 0 end
39
-
40
- # Trade constantly respecting cooldown times so that we don't get
41
- # banned by api clients.
34
+
35
+ def self.setup
36
+ Bitex.api_key = Settings.maker_settings.api_key
37
+ Bitex.sandbox = Settings.maker_settings.sandbox
38
+ self.taker = Settings.taker_class.tap { |klass| klass.setup(Settings.taker_settings) }
39
+ end
40
+
41
+ # Trade constantly respecting cooldown times so that we don't get banned by api clients.
42
42
  def self.run!
43
- setup
44
- logger.info("Loading trading robot, ctrl+c *once* to exit gracefully.")
43
+ bot = start_robot
45
44
  self.cooldown_until = Time.now
46
- bot = new
47
-
48
- while true
45
+ loop do
49
46
  start_time = Time.now
50
47
  next if start_time < cooldown_until
48
+
51
49
  self.current_cooldowns = 0
52
50
  bot.trade!
53
- # This global sleep is so that we don't stress bitex too much.
54
- sleep 0.3 unless test_mode
55
51
  self.cooldown_until = start_time + current_cooldowns.seconds
56
52
  end
57
53
  end
58
-
59
- def self.setup
60
- Bitex.api_key = Settings.bitex
61
- Bitex.sandbox = Settings.sandbox
62
- taker.setup(Settings)
63
- end
64
-
65
- def self.with_cooldown(&block)
66
- result = block.call
67
- return result if test_mode
54
+
55
+ def self.sleep_for(seconds)
56
+ sleep(seconds)
57
+ end
58
+ def_delegator self, :sleep_for
59
+
60
+ def self.log(level, message)
61
+ logger.send(level, message)
62
+ end
63
+ def_delegator self, :log
64
+
65
+ def self.with_cooldown
66
+ result = yield
68
67
  self.current_cooldowns += 1
69
- sleep 0.1
70
- return result
68
+ sleep_for(0.1)
69
+ result
71
70
  end
72
71
 
73
- def with_cooldown(&block)
74
- self.class.with_cooldown(&block)
72
+ # private class methods
73
+
74
+ def self.start_robot
75
+ setup
76
+ log(:info, 'Loading trading robot, ctrl+c *once* to exit gracefully.')
77
+ new
75
78
  end
76
79
 
80
+ # end: private class methods
81
+
82
+ # rubocop:disable Metrics/AbcSize
77
83
  def trade!
78
84
  sync_opening_flows if active_opening_flows?
79
85
  finalise_some_opening_flows
80
- if(!active_opening_flows? && !open_positions? &&
81
- !active_closing_flows? && self.class.graceful_shutdown)
82
- self.class.logger.info("Shutdown completed")
83
- exit
84
- end
86
+ shutdown! if shutdable?
85
87
  start_closing_flows if open_positions?
86
88
  sync_closing_flows if active_closing_flows?
87
89
  start_opening_flows_if_needed
88
90
  rescue CannotCreateFlow => e
89
- self.notify("#{e.message}:\n\n#{e.backtrace.join("\n")}")
90
- sleep (60 * 3) unless self.class.test_mode
91
+ notify("#{e.message}:\n\n#{e.backtrace.join("\n")}")
92
+ sleep_for(60 * 3)
91
93
  rescue Curl::Err::TimeoutError => e
92
- self.class.logger.error("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
93
- sleep 15 unless self.class.test_mode
94
+ log(:error, "#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
95
+ sleep_for(15)
96
+ rescue OrderNotFound => e
97
+ notify("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
98
+ rescue ApiWrapperError => e
99
+ notify("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
100
+ rescue OrderArgumentError => e
101
+ notify("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
94
102
  rescue StandardError => e
95
- self.notify("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
96
- sleep 120 unless self.class.test_mode
103
+ notify("#{e.class} - #{e.message}:\n\n#{e.backtrace.join("\n")}")
104
+ sleep_for(60 * 2)
105
+ end
106
+ # rubocop:enable Metrics/AbcSize
107
+
108
+ def active_closing_flows?
109
+ [BuyClosingFlow.active, SellClosingFlow.active].any?(&:exists?)
110
+ end
111
+
112
+ def active_opening_flows?
113
+ [BuyOpeningFlow.active, SellOpeningFlow.active].any?(&:exists?)
114
+ end
115
+
116
+ # The trader has a Store
117
+ def store
118
+ @store ||= Store.first || Store.create
119
+ end
120
+
121
+ private
122
+
123
+ def with_cooldown(&block)
124
+ self.class.with_cooldown(&block)
125
+ end
126
+
127
+ def sync_opening_flows
128
+ [SellOpeningFlow, BuyOpeningFlow].each(&:sync_open_positions)
129
+ end
130
+
131
+ def shutdable?
132
+ !(active_flows? || open_positions?) && turn_off?
133
+ end
134
+
135
+ def shutdown!
136
+ log(:info, 'Shutdown completed')
137
+ exit
138
+ end
139
+
140
+ def active_flows?
141
+ active_opening_flows? || active_closing_flows?
142
+ end
143
+
144
+ def turn_off?
145
+ self.class.graceful_shutdown
97
146
  end
98
-
147
+
99
148
  def finalise_some_opening_flows
100
- [BuyOpeningFlow, SellOpeningFlow].each do |kind|
101
- flows = self.class.graceful_shutdown ? kind.active : kind.old_active
102
- flows.each{|flow| flow.finalise! }
103
- end
149
+ [BuyOpeningFlow, SellOpeningFlow].each { |kind| active_flows(kind).each(&:finalise!) }
150
+ end
151
+
152
+ def active_flows(opening_flow_class)
153
+ turn_off? ? opening_flow_class.active : opening_flow_class.old_active
104
154
  end
105
-
155
+
106
156
  def start_closing_flows
107
- [BuyClosingFlow, SellClosingFlow].each{|kind| kind.close_open_positions}
157
+ [BuyClosingFlow, SellClosingFlow].each(&:close_open_positions)
108
158
  end
109
159
 
110
160
  def open_positions?
111
- OpenBuy.open.exists? || OpenSell.open.exists?
161
+ [OpenBuy.open, OpenSell.open].any?(&:exists?)
112
162
  end
113
-
163
+
114
164
  def sync_closing_flows
115
- orders = with_cooldown{ BitexBot::Robot.taker.orders }
116
- transactions = with_cooldown{ BitexBot::Robot.taker.user_transactions }
165
+ orders = with_cooldown { Robot.taker.orders }
166
+ transactions = with_cooldown { Robot.taker.user_transactions }
117
167
 
118
168
  [BuyClosingFlow, SellClosingFlow].each do |kind|
119
- kind.active.each do |flow|
120
- flow.sync_closed_positions(orders, transactions)
121
- end
169
+ kind.active.each { |flow| flow.sync_closed_positions(orders, transactions) }
122
170
  end
123
171
  end
124
-
125
- def active_closing_flows?
126
- BuyClosingFlow.active.exists? || SellClosingFlow.active.exists?
127
- end
128
-
172
+
173
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
129
174
  def start_opening_flows_if_needed
130
- if store.reload.hold?
131
- BitexBot::Robot.logger.debug("Not placing new orders because of hold")
132
- return
133
- end
134
-
135
- if active_closing_flows?
136
- BitexBot::Robot.logger.debug("Not placing new orders, closing flows.")
137
- return
138
- end
139
-
140
- if self.class.graceful_shutdown
141
- BitexBot::Robot.logger.debug("Not placing new orders, shutting down.")
142
- return
143
- end
144
-
145
- recent_buying, recent_selling =
146
- [BuyOpeningFlow, SellOpeningFlow].collect do |kind|
147
- threshold = (Settings.time_to_live / 2).seconds.ago
148
- kind.active.where('created_at > ?', threshold).first
149
- end
175
+ return log(:debug, 'Not placing new orders because of hold') if store.reload.hold?
176
+ return log(:debug, 'Not placing new orders, closing flows.') if active_closing_flows?
177
+ return log(:debug, 'Not placing new orders, shutting down.') if turn_off?
150
178
 
151
- if recent_buying && recent_selling
152
- BitexBot::Robot.logger.debug("Not placing new orders, recent ones exist.")
153
- return
154
- end
155
-
156
- balances = with_cooldown{ BitexBot::Robot.taker.balance }
179
+ recent_buying, recent_selling = recent_operations
180
+ return log(:debug, 'Not placing new orders, recent ones exist.') if recent_buying && recent_selling
181
+
182
+ taker_balance = with_cooldown { Robot.taker.balance }
157
183
  profile = Bitex::Profile.get
158
-
159
- total_usd = balances['usd_balance'].to_d + profile[:usd_balance]
160
- total_btc = balances['btc_balance'].to_d + profile[:btc_balance]
161
-
162
- last_log = `tail -c 61440 #{Settings.log.try(:file)}` if Settings.log.try(:file)
163
-
164
- store.update_attributes(taker_usd: balances['usd_balance'],
165
- taker_btc: balances['btc_balance'], log: last_log)
166
-
167
- if store.last_warning.nil? || store.last_warning < 30.minutes.ago
168
- if store.usd_warning && total_usd <= store.usd_warning
169
- notify("USD balance is too low, it's #{total_usd},"\
170
- "make it #{store.usd_warning} to stop this warning.")
171
- store.update_attributes(last_warning: Time.now)
172
- end
184
+ total_fiat, total_btc = balances(taker_balance, profile)
173
185
 
174
- if store.btc_warning && total_btc <= store.btc_warning
175
- notify("BTC balance is too low, it's #{total_btc},"\
176
- "make it #{store.btc_warning} to stop this warning.")
177
- store.update_attributes(last_warning: Time.now)
178
- end
179
- end
186
+ sync_log(taker_balance)
187
+ check_balance_warning(total_fiat, total_btc) if expired_last_warning?
188
+ return log(:debug, "Not placing new orders, #{Settings.quote} target not met") if target_met?(:fiat, total_fiat)
189
+ return log(:debug, 'Not placing new orders, BTC target not met') if target_met?(:btc, total_btc)
180
190
 
181
- if store.usd_stop && total_usd <= store.usd_stop
182
- BitexBot::Robot.logger.debug("Not placing new orders, USD target not met")
183
- return
184
- end
185
- if store.btc_stop && total_btc <= store.btc_stop
186
- BitexBot::Robot.logger.debug("Not placing new orders, BTC target not met")
187
- return
188
- end
191
+ order_book = with_cooldown { Robot.taker.order_book }
192
+ transactions = with_cooldown { Robot.taker.transactions }
189
193
 
190
- order_book = with_cooldown{ BitexBot::Robot.taker.order_book }
191
- transactions = with_cooldown{ BitexBot::Robot.taker.transactions }
192
-
193
- unless recent_buying
194
- BuyOpeningFlow.create_for_market(
195
- balances['btc_available'].to_d,
196
- order_book['bids'],
197
- transactions,
198
- profile[:fee],
199
- balances['fee'].to_d,
200
- store)
201
- end
202
- unless recent_selling
203
- SellOpeningFlow.create_for_market(
204
- balances['usd_available'].to_d,
205
- order_book['asks'],
206
- transactions,
207
- profile[:fee],
208
- balances['fee'].to_d,
209
- store)
194
+ create_buy_opening_flow(taker_balance, order_book, transactions, profile) unless recent_buying
195
+ create_sell_opening_flow(taker_balance, order_book, transactions, profile) unless recent_selling
196
+ end
197
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
198
+
199
+ def recent_operations
200
+ [BuyOpeningFlow, SellOpeningFlow].map do |kind|
201
+ threshold = (Settings.time_to_live / 2).seconds.ago
202
+ kind.active.where('created_at > ?', threshold).first
210
203
  end
211
204
  end
212
-
213
- def sync_opening_flows
214
- [SellOpeningFlow, BuyOpeningFlow].each{|o| o.sync_open_positions }
205
+
206
+ def balances(taker_balance, maker_balance)
207
+ total_fiat = maker_balance[:"#{Settings.quote}_balance"] + taker_balance.usd.total * Settings.fx_rate
208
+ total_btc = maker_balance[:btc_balance] + taker_balance.btc.total
209
+
210
+ [total_fiat, total_btc]
215
211
  end
216
-
217
- def active_opening_flows?
218
- BuyOpeningFlow.active.exists? || SellOpeningFlow.active.exists?
219
- end
220
-
221
- def notify(message)
222
- self.class.logger.error(message)
223
- if Settings.mailer
224
- mail = Mail.new do
225
- from Settings.mailer.from
226
- to Settings.mailer.to
227
- subject 'Notice from your robot trader'
228
- body message
229
- end
230
- mail.delivery_method(Settings.mailer.delivery_method.to_sym,
231
- Settings.mailer.options.to_hash)
232
- mail.deliver!
212
+
213
+ def sync_log(taker_balance)
214
+ file = Settings.log.try(:file)
215
+ last_log = `tail -c 61440 #{file}` if file.present?
216
+ store.update(taker_fiat: taker_balance.usd.total * Settings.fx_rate, taker_btc: taker_balance.btc.total, log: last_log)
217
+ end
218
+
219
+ def expired_last_warning?
220
+ store.last_warning.nil? || store.last_warning < 30.minutes.ago
221
+ end
222
+
223
+ def check_balance_warning(total_fiat, total_btc)
224
+ notify_balance_warning(Settings.quote, total_fiat, store.fiat_warning) if balance_warning_notify?(:fiat, total_fiat)
225
+ notify_balance_warning(:btc, total_btc, store.btc_warning) if balance_warning_notify?(:btc, total_btc)
226
+ end
227
+
228
+ def balance_warning_notify?(currency, total)
229
+ store.send("#{currency}_warning").present? && total <= store.send("#{currency}_warning")
230
+ end
231
+
232
+ def target_met?(currency, total)
233
+ store.send("#{currency}_stop").present? && total <= store.send("#{currency}_stop")
234
+ end
235
+
236
+ def notify_balance_warning(currency, total, warning_amount)
237
+ notify("#{currency.upcase} balance is too low, it's #{total}, make it #{warning_amount} to stop this warning.")
238
+ store.update(last_warning: Time.now)
239
+ end
240
+
241
+ def notify(message, subj = 'Notice from your robot trader')
242
+ log(:error, message)
243
+ return unless Settings.mailer.present?
244
+
245
+ mail = new_mail(subj, message)
246
+ mail.delivery_method(Settings.mailer.delivery_method.to_sym, Settings.mailer.options.to_hash)
247
+ mail.deliver!
248
+ end
249
+
250
+ def new_mail(subj, message)
251
+ Mail.new do
252
+ from Settings.mailer.from
253
+ to Settings.mailer.to
254
+ subject subj
255
+ body message
233
256
  end
234
257
  end
235
258
 
236
- # The trader has a Store
237
- def store
238
- @store ||= Store.first || Store.create
259
+ def create_buy_opening_flow(balance, order_book, transactions, profile)
260
+ BuyOpeningFlow.create_for_market(balance.btc.available, order_book.bids, transactions, profile[:fee], balance.fee, store)
261
+ end
262
+
263
+ def create_sell_opening_flow(balance, order_book, transactions, profile)
264
+ SellOpeningFlow.create_for_market(balance.usd.available, order_book.asks, transactions, profile[:fee], balance.fee, store)
239
265
  end
266
+ # rubocop:enable Metrics/ClassLength
240
267
  end
241
268
  end
@@ -1,37 +1,96 @@
1
1
  require 'hashie'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
2
4
 
3
5
  module BitexBot
6
+ # Documentation here!
4
7
  class FileSettings < ::Hashie::Clash
5
- def method_missing(name, *args)
6
- return super unless args.size == 1 && args.none?
8
+ def method_missing(name, *args, &block)
9
+ return super unless args.none? && args.size == 1
7
10
  self[name] = args.first
8
11
  end
12
+
13
+ def respond_to_missing?(method_name, include_private = false)
14
+ respond_to?(method_name) || super
15
+ end
9
16
  end
10
17
 
18
+ # This class load settings file, else write a sample file.
11
19
  class SettingsClass < ::Hashie::Mash
12
20
  include ::Hashie::Extensions::Mash::SymbolizeKeys
13
21
 
22
+ def load_test
23
+ load_settings(sample_path)
24
+ end
25
+
14
26
  def load_default
15
27
  path = ARGV[0] || 'bitex_bot_settings.rb'
16
- unless FileTest.exists?(path)
17
- sample_path = File.expand_path('../../../settings.rb.sample', __FILE__)
18
- FileUtils.cp(sample_path, path)
19
- puts "No settings found, I've created a new one with sample "\
20
- "values at #{path}. Please go ahead and edit it before running this again."
21
- exit 1
22
- end
28
+ show_sample(path) unless FileTest.exists?(path)
23
29
  load_settings(path)
24
30
  end
25
-
26
- def load_test
27
- load_settings File.expand_path('../../../settings.rb.sample', __FILE__)
31
+
32
+ def fx_rate
33
+ Store.first.try(:fx_rate) || foreign_exchange_rate
34
+ end
35
+
36
+ def base
37
+ order_book_currencies[:base]
38
+ end
39
+
40
+ def quote
41
+ order_book_currencies[:quote]
42
+ end
43
+
44
+ def maker_class
45
+ exchange_class(maker)
46
+ end
47
+
48
+ def taker_class
49
+ exchange_class(taker)
50
+ end
51
+
52
+ def maker_settings
53
+ exchange_settings(maker)
54
+ end
55
+
56
+ def taker_settings
57
+ exchange_settings(taker)
28
58
  end
29
59
 
60
+ private
61
+
30
62
  def load_settings(path)
31
63
  file_settings = FileSettings.new
32
64
  file_settings.instance_eval(File.read(path), path, 1)
33
65
  merge!(file_settings)
34
66
  end
67
+
68
+ def sample_path
69
+ File.expand_path('../../settings.rb.sample', __dir__)
70
+ end
71
+
72
+ def show_sample(path)
73
+ FileUtils.cp(sample_path, path)
74
+ puts "No settings found, I've created a new one with sample values at #{path}. "\
75
+ 'Please go ahead and edit it before running this again.'
76
+ exit 1
77
+ end
78
+
79
+ def order_book_currencies
80
+ {}.tap { |currencies| currencies[:base], currencies[:quote] = maker_settings.order_book.to_s.split('_') }
81
+ end
82
+
83
+ def exchange_name(exchange)
84
+ exchange.keys.pop
85
+ end
86
+
87
+ def exchange_class(exchange)
88
+ "#{exchange_name(exchange).capitalize}ApiWrapper".constantize
89
+ end
90
+
91
+ def exchange_settings(exchange)
92
+ exchange.send(exchange_name(exchange))
93
+ end
35
94
  end
36
95
 
37
96
  Settings = SettingsClass.new
@@ -1,3 +1,3 @@
1
1
  module BitexBot
2
- VERSION = "0.3.7"
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/bitex_bot.rb CHANGED
@@ -1,19 +1,32 @@
1
- require "bitex_bot/version"
1
+ require 'bitex_bot/version'
2
+
3
+ # Utilities
4
+ require 'active_record'
5
+ require 'bigdecimal'
6
+ require 'bigdecimal/util'
7
+ require 'forwardable'
2
8
  require 'hashie'
3
- require "active_record"
4
- require "mail"
5
- require "logger"
6
- require "bitex"
7
- require "bitstamp"
8
- require "itbit"
9
- require 'bitfinex'
10
- require "bitex_bot/settings"
11
- require "bitex_bot/database"
12
- require "bitex_bot/models/opening_flow.rb"
13
- require "bitex_bot/models/closing_flow.rb"
14
- Dir[File.dirname(__FILE__) + '/bitex_bot/models/*.rb'].each {|file| require file }
15
- require "bitex_bot/robot"
9
+ require 'logger'
10
+ require 'mail'
11
+
12
+ # Traders Platforms
13
+ require 'bitex'
14
+ require 'bitstamp'
15
+ require 'itbit'
16
+
17
+ # BitexBot Models
18
+ require 'bitex_bot/settings'
19
+ require 'bitex_bot/database'
20
+ require 'bitex_bot/models/api_wrappers/api_wrapper.rb'
21
+ Dir[File.dirname(__FILE__) + '/bitex_bot/models/api_wrappers/**/*.rb'].each { |file| require file }
22
+ require 'bitex_bot/models/opening_flow.rb'
23
+ require 'bitex_bot/models/closing_flow.rb'
24
+ Dir[File.dirname(__FILE__) + '/bitex_bot/models/*.rb'].each { |file| require file }
25
+ require 'bitex_bot/robot'
16
26
 
27
+ # #
28
+ # Get version and bitex-bot as user-agent
29
+ #
17
30
  module BitexBot
18
31
  def self.user_agent
19
32
  "Bitexbot/#{VERSION} (https://github.com/bitex-la/bitex-bot)"
@@ -21,6 +34,9 @@ module BitexBot
21
34
  end
22
35
 
23
36
  module Bitex
37
+ # #
38
+ # Set bitex-bot user-agent on request.
39
+ #
24
40
  module WithUserAgent
25
41
  def grab_curl
26
42
  super.tap do |curl|
@@ -29,6 +45,9 @@ module Bitex
29
45
  end
30
46
  end
31
47
 
48
+ ##
49
+ # Mixing to include request behaviour and set user-agent.
50
+ #
32
51
  class Api
33
52
  class << self
34
53
  prepend WithUserAgent
@@ -36,14 +55,19 @@ module Bitex
36
55
  end
37
56
  end
38
57
 
39
- # Itbit and Bitstamp
40
58
  module RestClient
59
+ # #
60
+ # On Itbit and Bitstamp, the mechanism to set bitex-bot user-agent are different.
61
+ #
41
62
  module WithUserAgent
42
63
  def default_headers
43
- super.merge(:user_agent => BitexBot.user_agent)
64
+ super.merge(user_agent: BitexBot.user_agent)
44
65
  end
45
66
  end
46
67
 
68
+ ##
69
+ # Mixing to include request behaviour and set user-agent.
70
+ #
47
71
  class Request
48
72
  prepend WithUserAgent
49
73
  end