bitex_bot 0.3.7 → 0.4.0

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