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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +63 -0
- data/.rubocop.yml +33 -0
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/bin/bitex_bot +1 -1
- data/bitex_bot.gemspec +34 -34
- data/lib/bitex_bot/database.rb +67 -67
- data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
- data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
- data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
- data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
- data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
- data/lib/bitex_bot/models/close_buy.rb +2 -2
- data/lib/bitex_bot/models/closing_flow.rb +98 -79
- data/lib/bitex_bot/models/open_buy.rb +11 -10
- data/lib/bitex_bot/models/open_sell.rb +11 -10
- data/lib/bitex_bot/models/opening_flow.rb +157 -99
- data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
- data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
- data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
- data/lib/bitex_bot/models/store.rb +3 -1
- data/lib/bitex_bot/robot.rb +203 -176
- data/lib/bitex_bot/settings.rb +71 -12
- data/lib/bitex_bot/version.rb +1 -1
- data/lib/bitex_bot.rb +40 -16
- data/settings.rb.sample +43 -66
- data/spec/bitex_bot/settings_spec.rb +87 -15
- data/spec/factories/bitex_buy.rb +3 -3
- data/spec/factories/bitex_sell.rb +3 -3
- data/spec/factories/buy_opening_flow.rb +1 -1
- data/spec/factories/open_buy.rb +12 -10
- data/spec/factories/open_sell.rb +12 -10
- data/spec/factories/sell_opening_flow.rb +1 -1
- data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
- data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
- data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
- data/spec/models/bitex_api_spec.rb +1 -1
- data/spec/models/buy_closing_flow_spec.rb +140 -71
- data/spec/models/buy_opening_flow_spec.rb +126 -56
- data/spec/models/order_book_simulator_spec.rb +10 -10
- data/spec/models/robot_spec.rb +61 -47
- data/spec/models/sell_closing_flow_spec.rb +130 -62
- data/spec/models/sell_opening_flow_spec.rb +129 -60
- data/spec/spec_helper.rb +19 -16
- data/spec/support/bitex_stubs.rb +13 -14
- data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
- data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
- metadata +60 -42
- data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
- data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
- data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
- data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
- data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
- data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
- data/spec/models/itbit_api_wrapper_spec.rb +0 -15
- data/spec/support/bitstamp_stubs.rb +0 -110
data/lib/bitex_bot/robot.rb
CHANGED
@@ -1,241 +1,268 @@
|
|
1
|
-
trap
|
1
|
+
trap 'INT' do
|
2
2
|
if BitexBot::Robot.graceful_shutdown
|
3
3
|
print "\b"
|
4
|
-
BitexBot::Robot.
|
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.
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
logger.info("Loading trading robot, ctrl+c *once* to exit gracefully.")
|
43
|
+
bot = start_robot
|
45
44
|
self.cooldown_until = Time.now
|
46
|
-
|
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.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
68
|
+
sleep_for(0.1)
|
69
|
+
result
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
91
|
+
notify("#{e.message}:\n\n#{e.backtrace.join("\n")}")
|
92
|
+
sleep_for(60 * 3)
|
91
93
|
rescue Curl::Err::TimeoutError => e
|
92
|
-
|
93
|
-
|
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
|
-
|
96
|
-
|
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
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
157
|
+
[BuyClosingFlow, SellClosingFlow].each(&:close_open_positions)
|
108
158
|
end
|
109
159
|
|
110
160
|
def open_positions?
|
111
|
-
OpenBuy.open
|
161
|
+
[OpenBuy.open, OpenSell.open].any?(&:exists?)
|
112
162
|
end
|
113
|
-
|
163
|
+
|
114
164
|
def sync_closing_flows
|
115
|
-
orders = with_cooldown{
|
116
|
-
transactions = with_cooldown{
|
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
|
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
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
182
|
-
|
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
|
191
|
-
transactions
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
214
|
-
[
|
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
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
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
|
data/lib/bitex_bot/settings.rb
CHANGED
@@ -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
|
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
|
27
|
-
|
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
|
data/lib/bitex_bot/version.rb
CHANGED
data/lib/bitex_bot.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
|
-
require
|
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
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
require
|
13
|
-
require
|
14
|
-
|
15
|
-
|
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(:
|
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
|