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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -3
- data/Gemfile +3 -1
- data/bitex_bot.gemspec +5 -2
- data/lib/bitex_bot/database.rb +2 -2
- data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +47 -35
- data/lib/bitex_bot/models/api_wrappers/bitex/bitex_api_wrapper.rb +178 -0
- data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +62 -45
- data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +52 -28
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +61 -28
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +12 -6
- data/lib/bitex_bot/models/buy_closing_flow.rb +3 -2
- data/lib/bitex_bot/models/buy_opening_flow.rb +31 -6
- data/lib/bitex_bot/models/closing_flow.rb +37 -22
- data/lib/bitex_bot/models/open_buy.rb +1 -3
- data/lib/bitex_bot/models/open_sell.rb +1 -3
- data/lib/bitex_bot/models/opening_flow.rb +42 -28
- data/lib/bitex_bot/models/order_book_simulator.rb +14 -13
- data/lib/bitex_bot/models/sell_closing_flow.rb +3 -2
- data/lib/bitex_bot/models/sell_opening_flow.rb +29 -4
- data/lib/bitex_bot/robot.rb +28 -43
- data/lib/bitex_bot/settings.rb +2 -0
- data/lib/bitex_bot/version.rb +1 -1
- data/settings.rb.sample +23 -5
- data/spec/bitex_bot/settings_spec.rb +13 -6
- data/spec/factories/bitex_ask.rb +14 -0
- data/spec/factories/bitex_bid.rb +14 -0
- data/spec/factories/bitex_buy.rb +7 -7
- data/spec/factories/bitex_sell.rb +7 -7
- data/spec/factories/buy_opening_flow.rb +10 -10
- data/spec/factories/open_buy.rb +8 -8
- data/spec/factories/open_sell.rb +8 -8
- data/spec/factories/sell_opening_flow.rb +10 -10
- data/spec/fixtures/bitstamp/balance.yml +63 -0
- data/spec/fixtures/bitstamp/order_book.yml +60 -0
- data/spec/fixtures/bitstamp/orders/all.yml +62 -0
- data/spec/fixtures/bitstamp/orders/failure_sell.yml +60 -0
- data/spec/fixtures/bitstamp/orders/successful_buy.yml +62 -0
- data/spec/fixtures/bitstamp/transactions.yml +244 -0
- data/spec/fixtures/bitstamp/user_transactions.yml +223 -0
- data/spec/models/api_wrappers/bitex_api_wrapper_spec.rb +147 -0
- data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +134 -140
- data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +9 -3
- data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +142 -73
- data/spec/models/bitex_api_spec.rb +4 -4
- data/spec/models/buy_closing_flow_spec.rb +19 -24
- data/spec/models/buy_opening_flow_spec.rb +102 -83
- data/spec/models/order_book_simulator_spec.rb +5 -0
- data/spec/models/robot_spec.rb +7 -4
- data/spec/models/sell_closing_flow_spec.rb +21 -25
- data/spec/models/sell_opening_flow_spec.rb +100 -80
- data/spec/spec_helper.rb +3 -1
- data/spec/support/bitex_stubs.rb +80 -40
- data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +2 -2
- data/spec/support/bitstamp/bitstamp_stubs.rb +3 -3
- data/spec/support/vcr.rb +8 -0
- data/spec/support/webmock.rb +8 -0
- metadata +77 -10
@@ -3,6 +3,8 @@ module BitexBot
|
|
3
3
|
# The OpeningFlow stage places an order on bitex, detecting and storing all transactions spawn from that order as
|
4
4
|
# Open positions.
|
5
5
|
class OpeningFlow < ActiveRecord::Base
|
6
|
+
extend Forwardable
|
7
|
+
|
6
8
|
self.abstract_class = true
|
7
9
|
|
8
10
|
# The updated config store as passed from the robot
|
@@ -29,26 +31,31 @@ module BitexBot
|
|
29
31
|
# #safest_price
|
30
32
|
# #value_to_use
|
31
33
|
# rubocop:disable Metrics/AbcSize
|
32
|
-
def self.create_for_market(
|
34
|
+
def self.create_for_market(taker_balance, taker_orders, taker_transactions, maker_fee, taker_fee, store)
|
33
35
|
self.store = store
|
34
36
|
|
35
|
-
remote_value, safest_price = calc_remote_value(maker_fee, taker_fee,
|
36
|
-
|
37
|
-
|
37
|
+
remote_value, safest_price = calc_remote_value(maker_fee, taker_fee, taker_orders, taker_transactions)
|
38
|
+
unless enough_remote_funds?(taker_balance, remote_value)
|
39
|
+
raise CannotCreateFlow,
|
40
|
+
"Needed #{remote_value} but you only have #{taker_specie_to_spend} #{taker_balance} on your taker market."
|
41
|
+
end
|
42
|
+
|
43
|
+
price = maker_price(remote_value)
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
order = create_order!(price)
|
46
|
+
unless enough_funds?(order)
|
47
|
+
raise CannotCreateFlow,
|
48
|
+
"You need to have #{maker_specie_to_spend} #{value_per_order} on Bitex to place this #{order_class}."
|
49
|
+
end
|
43
50
|
|
44
51
|
Robot.log(
|
45
52
|
:info,
|
46
|
-
"Opening: Placed #{order_class
|
47
|
-
" (#{remote_value})"
|
53
|
+
"Opening: Placed #{order_class} ##{order.id} #{value_per_order} @ #{Robot.maker.quote.upcase} #{price}"\
|
54
|
+
" (#{maker_specie_to_obtain} #{remote_value})"
|
48
55
|
)
|
49
56
|
|
50
57
|
create!(
|
51
|
-
price:
|
58
|
+
price: price,
|
52
59
|
value_to_use: value_to_use,
|
53
60
|
suggested_closing_price: safest_price,
|
54
61
|
status: 'executing',
|
@@ -60,16 +67,16 @@ module BitexBot
|
|
60
67
|
# rubocop:enable Metrics/AbcSize
|
61
68
|
|
62
69
|
# create_for_market helpers
|
63
|
-
def self.calc_remote_value(maker_fee, taker_fee,
|
70
|
+
def self.calc_remote_value(maker_fee, taker_fee, taker_orders, taker_transactions)
|
64
71
|
value_to_use_needed = (value_to_use + maker_plus(maker_fee)) / (1 - taker_fee / 100)
|
65
|
-
safest_price = safest_price(
|
72
|
+
safest_price = safest_price(taker_transactions, taker_orders, value_to_use_needed)
|
66
73
|
remote_value = remote_value_to_use(value_to_use_needed, safest_price)
|
67
74
|
|
68
75
|
[remote_value, safest_price]
|
69
76
|
end
|
70
77
|
|
71
|
-
def self.create_order!(
|
72
|
-
|
78
|
+
def self.create_order!(maker_price)
|
79
|
+
Robot.maker.send_order(order_type, maker_price, value_per_order, true)
|
73
80
|
rescue StandardError => e
|
74
81
|
raise CannotCreateFlow, e.message
|
75
82
|
end
|
@@ -78,8 +85,8 @@ module BitexBot
|
|
78
85
|
!order.reason.to_s.inquiry.not_enough_funds?
|
79
86
|
end
|
80
87
|
|
81
|
-
def self.enough_remote_funds?(
|
82
|
-
|
88
|
+
def self.enough_remote_funds?(taker_balance, remote_value)
|
89
|
+
taker_balance >= remote_value
|
83
90
|
end
|
84
91
|
|
85
92
|
def self.maker_plus(fee)
|
@@ -93,7 +100,7 @@ module BitexBot
|
|
93
100
|
# #open_position_class
|
94
101
|
def self.sync_open_positions
|
95
102
|
threshold = open_position_class.order('created_at DESC').first.try(:created_at)
|
96
|
-
|
103
|
+
Robot.maker.transactions.map do |transaction|
|
97
104
|
next unless sought_transaction?(threshold, transaction)
|
98
105
|
|
99
106
|
flow = find_by_order_id(transaction_order_id(transaction))
|
@@ -104,25 +111,28 @@ module BitexBot
|
|
104
111
|
end
|
105
112
|
|
106
113
|
# sync_open_positions helpers
|
114
|
+
# rubocop:disable Metrics/AbcSize
|
107
115
|
def self.create_open_position!(transaction, flow)
|
108
116
|
Robot.log(
|
109
117
|
:info,
|
110
|
-
"Opening: #{name} ##{flow.id} was hit for #{transaction.quantity} #{
|
111
|
-
" #{transaction.price}"
|
118
|
+
"Opening: #{name} ##{flow.id} was hit for #{transaction.raw.quantity} #{Robot.maker.base.upcase}"\
|
119
|
+
" @ #{Robot.maker.quote.upcase} #{transaction.price}"
|
112
120
|
)
|
121
|
+
|
113
122
|
open_position_class.create!(
|
114
123
|
transaction_id: transaction.id,
|
115
124
|
price: transaction.price,
|
116
125
|
amount: transaction.amount,
|
117
|
-
quantity: transaction.quantity,
|
126
|
+
quantity: transaction.raw.quantity,
|
118
127
|
opening_flow: flow
|
119
128
|
)
|
120
129
|
end
|
130
|
+
# rubocop:enable Metrics/AbcSize
|
121
131
|
|
122
132
|
# This use hooks methods, these must be defined in the subclass:
|
123
133
|
# #transaction_class
|
124
134
|
def self.sought_transaction?(threshold, transaction)
|
125
|
-
|
135
|
+
expected_kind_transaction?(transaction) &&
|
126
136
|
!active_transaction?(transaction, threshold) &&
|
127
137
|
!open_position?(transaction) &&
|
128
138
|
expected_order_book?(transaction)
|
@@ -130,16 +140,20 @@ module BitexBot
|
|
130
140
|
# end: sync_open_positions helpers
|
131
141
|
|
132
142
|
# sought_transaction helpers
|
143
|
+
def self.expected_kind_transaction?(transaction)
|
144
|
+
transaction.raw.is_a?(transaction_class)
|
145
|
+
end
|
146
|
+
|
133
147
|
def self.active_transaction?(transaction, threshold)
|
134
|
-
threshold.present? && transaction.
|
148
|
+
threshold.present? && transaction.timestamp < (threshold - 30.minutes).to_i
|
135
149
|
end
|
136
150
|
|
137
151
|
def self.open_position?(transaction)
|
138
152
|
open_position_class.find_by_transaction_id(transaction.id)
|
139
153
|
end
|
140
154
|
|
141
|
-
def self.expected_order_book?(
|
142
|
-
|
155
|
+
def self.expected_order_book?(maker_transaction)
|
156
|
+
maker_transaction.raw.order_book.to_s == Robot.maker.base_quote
|
143
157
|
end
|
144
158
|
# end: sought_transaction helpers
|
145
159
|
|
@@ -157,7 +171,7 @@ module BitexBot
|
|
157
171
|
end
|
158
172
|
|
159
173
|
def finalise!
|
160
|
-
order =
|
174
|
+
order = order_class.find(order_id)
|
161
175
|
canceled_or_completed?(order) ? do_finalize : do_cancel(order)
|
162
176
|
end
|
163
177
|
|
@@ -169,12 +183,12 @@ module BitexBot
|
|
169
183
|
end
|
170
184
|
|
171
185
|
def do_finalize
|
172
|
-
Robot.log(:info, "Opening: #{
|
186
|
+
Robot.log(:info, "Opening: #{order_class} ##{order_id} finalised.")
|
173
187
|
finalised!
|
174
188
|
end
|
175
189
|
|
176
190
|
def do_cancel(order)
|
177
|
-
Robot.log(:info, "Opening: #{
|
191
|
+
Robot.log(:info, "Opening: #{order_class} ##{order_id} canceled.")
|
178
192
|
order.cancel!
|
179
193
|
settling! unless settling?
|
180
194
|
end
|
@@ -17,35 +17,36 @@ module BitexBot
|
|
17
17
|
# quantity.
|
18
18
|
#
|
19
19
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
20
|
-
def self.run(volatility,
|
21
|
-
to_skip = estimate_quantity_to_skip(volatility,
|
22
|
-
Robot.log(:debug, "Skipping #{to_skip}
|
20
|
+
def self.run(volatility, taker_transactions, taker_orderbook, amount_target, quantity_target, fx_rate = 1)
|
21
|
+
to_skip = estimate_quantity_to_skip(volatility, taker_transactions)
|
22
|
+
Robot.log(:debug, "Skipping #{to_skip} #{Robot.taker.base.upcase}")
|
23
23
|
seen = 0
|
24
24
|
|
25
|
-
|
25
|
+
taker_orderbook.each do |order_summary|
|
26
26
|
price = order_summary.price
|
27
27
|
quantity = order_summary.quantity
|
28
28
|
|
29
29
|
# An order may be partially or completely skipped due to volatility.
|
30
30
|
if to_skip.positive?
|
31
|
-
[quantity, to_skip].min
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
31
|
+
dropped = [quantity, to_skip].min
|
32
|
+
to_skip -= dropped
|
33
|
+
quantity -= dropped
|
34
|
+
Robot.log(:debug, "Skipped #{dropped} #{Robot.taker.base.upcase} @ #{Robot.taker.quote.upcase} #{price}")
|
36
35
|
next if quantity.zero?
|
37
36
|
end
|
38
37
|
|
39
38
|
if quantity_target.present?
|
40
|
-
return best_price(
|
39
|
+
return best_price(Robot.maker.base.upcase, quantity_target, price) if best_price?(quantity, quantity_target, seen)
|
40
|
+
|
41
41
|
seen += quantity
|
42
42
|
elsif amount_target.present?
|
43
43
|
amount = price * quantity
|
44
|
-
return best_price(
|
44
|
+
return best_price(Robot.maker.quote.upcase, amount_target * fx_rate, price) if best_price?(amount, amount_target, seen)
|
45
|
+
|
45
46
|
seen += amount
|
46
47
|
end
|
47
48
|
end
|
48
|
-
|
49
|
+
taker_orderbook.last.price
|
49
50
|
end
|
50
51
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
51
52
|
|
@@ -64,7 +65,7 @@ module BitexBot
|
|
64
65
|
end
|
65
66
|
|
66
67
|
def self.best_price(currency, target, price)
|
67
|
-
price.tap { Robot.log(:debug, "Best price to get #{currency} #{target} is
|
68
|
+
price.tap { Robot.log(:debug, "Best price to get #{currency} #{target} is #{Robot.taker.quote.upcase} #{price}") }
|
68
69
|
end
|
69
70
|
|
70
71
|
# end: private class methods
|
@@ -10,9 +10,10 @@ module BitexBot
|
|
10
10
|
OpenSell
|
11
11
|
end
|
12
12
|
|
13
|
-
def fx_rate
|
13
|
+
def self.fx_rate
|
14
14
|
Settings.selling_fx_rate
|
15
15
|
end
|
16
|
+
def_delegator self, :fx_rate
|
16
17
|
|
17
18
|
private
|
18
19
|
|
@@ -37,7 +38,7 @@ module BitexBot
|
|
37
38
|
# end: create_or_cancel! helpers
|
38
39
|
|
39
40
|
# create_order_and_close_position helpers
|
40
|
-
def
|
41
|
+
def order_type
|
41
42
|
:buy
|
42
43
|
end
|
43
44
|
# end: create_order_and_close_position helpers
|
@@ -28,13 +28,13 @@ module BitexBot
|
|
28
28
|
# @return [SellOpeningFlow] The newly created flow.
|
29
29
|
# @raise [CannotCreateFlow] If there's any problem creating this flow, for example when you run out of BTC on bitex or out
|
30
30
|
# of USD on the other exchange.
|
31
|
-
def self.create_for_market(
|
31
|
+
def self.create_for_market(taker_fiat_balance, taker_asks, taker_transactions, maker_fee, taker_fee, store)
|
32
32
|
super
|
33
33
|
end
|
34
34
|
|
35
35
|
# sync_open_positions helpers
|
36
36
|
def self.transaction_order_id(transaction)
|
37
|
-
transaction.ask_id
|
37
|
+
transaction.raw.ask_id
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.open_position_class
|
@@ -56,6 +56,11 @@ module BitexBot
|
|
56
56
|
def self.order_class
|
57
57
|
Bitex::Ask
|
58
58
|
end
|
59
|
+
def_delegator self, :order_class
|
60
|
+
|
61
|
+
def self.order_type
|
62
|
+
:sell
|
63
|
+
end
|
59
64
|
|
60
65
|
def self.profit
|
61
66
|
store.selling_profit || Settings.selling.profit
|
@@ -65,8 +70,8 @@ module BitexBot
|
|
65
70
|
value_to_use_needed * safest_price
|
66
71
|
end
|
67
72
|
|
68
|
-
def self.safest_price(transactions,
|
69
|
-
OrderBookSimulator.run(Settings.time_to_live, transactions,
|
73
|
+
def self.safest_price(transactions, taker_asks, bitcoins_to_use)
|
74
|
+
OrderBookSimulator.run(Settings.time_to_live, transactions, taker_asks, nil, bitcoins_to_use, nil)
|
70
75
|
end
|
71
76
|
|
72
77
|
def self.value_to_use
|
@@ -77,5 +82,25 @@ module BitexBot
|
|
77
82
|
def self.fx_rate
|
78
83
|
Settings.selling_fx_rate
|
79
84
|
end
|
85
|
+
|
86
|
+
def self.value_per_order
|
87
|
+
value_to_use
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.maker_specie_to_spend
|
91
|
+
Robot.maker.base.upcase
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.maker_specie_to_obtain
|
95
|
+
Robot.maker.quote.upcase
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.taker_specie_to_spend
|
99
|
+
Robot.taker.quote.upcase
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.taker_specie_to_obtain
|
103
|
+
Robot.taker.base.upcase
|
104
|
+
end
|
80
105
|
end
|
81
106
|
end
|
data/lib/bitex_bot/robot.rb
CHANGED
@@ -10,11 +10,11 @@ end
|
|
10
10
|
|
11
11
|
module BitexBot
|
12
12
|
# Documentation here!
|
13
|
-
# rubocop:disable Metrics/ClassLength
|
14
13
|
class Robot
|
15
14
|
extend Forwardable
|
16
15
|
|
17
16
|
cattr_accessor :taker
|
17
|
+
cattr_accessor :maker
|
18
18
|
|
19
19
|
cattr_accessor :graceful_shutdown
|
20
20
|
cattr_accessor :cooldown_until
|
@@ -33,9 +33,8 @@ module BitexBot
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def self.setup
|
36
|
-
|
37
|
-
|
38
|
-
self.taker = Settings.taker_class.tap { |klass| klass.setup(Settings.taker_settings) }
|
36
|
+
self.maker = Settings.maker_class.new(Settings.maker_settings)
|
37
|
+
self.taker = Settings.taker_class.new(Settings.taker_settings)
|
39
38
|
end
|
40
39
|
|
41
40
|
# Trade constantly respecting cooldown times so that we don't get banned by api clients.
|
@@ -68,6 +67,7 @@ module BitexBot
|
|
68
67
|
sleep_for(0.1)
|
69
68
|
end
|
70
69
|
end
|
70
|
+
def_delegator self, :with_cooldown
|
71
71
|
|
72
72
|
def self.start_robot
|
73
73
|
setup
|
@@ -116,10 +116,6 @@ module BitexBot
|
|
116
116
|
|
117
117
|
private
|
118
118
|
|
119
|
-
def with_cooldown(&block)
|
120
|
-
self.class.with_cooldown(&block)
|
121
|
-
end
|
122
|
-
|
123
119
|
def sync_opening_flows
|
124
120
|
[SellOpeningFlow, BuyOpeningFlow].each(&:sync_open_positions)
|
125
121
|
end
|
@@ -158,53 +154,45 @@ module BitexBot
|
|
158
154
|
end
|
159
155
|
|
160
156
|
def sync_closing_flows
|
161
|
-
|
162
|
-
transactions = with_cooldown { Robot.taker.user_transactions }
|
163
|
-
|
164
|
-
[BuyClosingFlow, SellClosingFlow].each do |kind|
|
165
|
-
kind.active.each { |flow| flow.sync_closed_positions(orders, transactions) }
|
166
|
-
end
|
157
|
+
[BuyClosingFlow, SellClosingFlow].each { |kind| kind.active.each(&:sync_closed_positions) }
|
167
158
|
end
|
168
159
|
|
169
160
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
170
161
|
def start_opening_flows_if_needed
|
171
|
-
return log(:debug, 'Not placing new orders because
|
172
|
-
return log(:debug, 'Not placing new orders, closing flows.') if active_closing_flows?
|
162
|
+
return log(:debug, 'Not placing new orders, because Store is held') if store.reload.hold?
|
163
|
+
return log(:debug, 'Not placing new orders, has active closing flows.') if active_closing_flows?
|
173
164
|
return log(:debug, 'Not placing new orders, shutting down.') if turn_off?
|
174
165
|
|
175
166
|
recent_buying, recent_selling = recent_operations
|
176
167
|
return log(:debug, 'Not placing new orders, recent ones exist.') if recent_buying && recent_selling
|
177
168
|
|
178
|
-
|
179
|
-
taker_balance = with_cooldown {
|
180
|
-
sync_log_and_store(
|
169
|
+
maker_balance = with_cooldown { maker.balance }
|
170
|
+
taker_balance = with_cooldown { taker.balance }
|
171
|
+
sync_log_and_store(maker_balance, taker_balance)
|
181
172
|
|
182
173
|
check_balance_warning if expired_last_warning?
|
174
|
+
return if stop_opening_flows?
|
183
175
|
|
184
|
-
|
185
|
-
|
176
|
+
order_book = with_cooldown { taker.order_book }
|
177
|
+
transactions = with_cooldown { taker.transactions }
|
186
178
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
create_buy_opening_flow(taker_balance, order_book, transactions, profile) unless recent_buying
|
191
|
-
create_sell_opening_flow(taker_balance, order_book, transactions, profile) unless recent_selling
|
179
|
+
args = [transactions, maker_balance.fee, taker_balance.fee, store]
|
180
|
+
BuyOpeningFlow.create_for_market(*[taker_balance.crypto.available, order_book.bids] + args) unless recent_buying
|
181
|
+
SellOpeningFlow.create_for_market(*[taker_balance.fiat.available, order_book.asks] + args) unless recent_selling
|
192
182
|
end
|
193
183
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
194
184
|
|
195
185
|
def recent_operations
|
196
|
-
|
197
|
-
|
198
|
-
kind.active.where('created_at > ?', threshold).first
|
199
|
-
end
|
186
|
+
threshold = (Settings.time_to_live / 2).seconds.ago
|
187
|
+
[BuyOpeningFlow, SellOpeningFlow].map { |kind| kind.active.where('created_at > ?', threshold).first }
|
200
188
|
end
|
201
189
|
|
202
|
-
def sync_log_and_store(
|
190
|
+
def sync_log_and_store(maker_balance, taker_balance)
|
203
191
|
file = Settings.log.try(:file)
|
204
192
|
last_log = `tail -c 61440 #{file}` if file.present?
|
205
193
|
|
206
194
|
store.update(
|
207
|
-
maker_fiat: maker_balance
|
195
|
+
maker_fiat: maker_balance.fiat.total, maker_crypto: maker_balance.crypto.total,
|
208
196
|
taker_fiat: taker_balance.fiat.total, taker_crypto: taker_balance.crypto.total,
|
209
197
|
log: last_log
|
210
198
|
)
|
@@ -214,13 +202,19 @@ module BitexBot
|
|
214
202
|
store.last_warning.nil? || store.last_warning < 30.minutes.ago
|
215
203
|
end
|
216
204
|
|
205
|
+
def stop_opening_flows?
|
206
|
+
(log(:debug, "Not placing new orders, #{maker.quote.upcase} target not met") if alert?(:fiat, :stop)) ||
|
207
|
+
(log(:debug, "Not placing new orders, #{maker.base.upcase} target not met") if alert?(:crypto, :stop))
|
208
|
+
end
|
209
|
+
|
217
210
|
def check_balance_warning
|
218
|
-
notify_balance_warning(
|
219
|
-
notify_balance_warning(
|
211
|
+
notify_balance_warning(maker.base, balance(:crypto), store.crypto_warning) if alert?(:crypto, :warning)
|
212
|
+
notify_balance_warning(maker.quote, balance(:fiat), store.fiat_warning) if alert?(:fiat, :warning)
|
220
213
|
end
|
221
214
|
|
222
215
|
def alert?(currency, flag)
|
223
216
|
return unless store.send("#{currency}_#{flag}").present?
|
217
|
+
|
224
218
|
balance(currency) <= store.send("#{currency}_#{flag}")
|
225
219
|
end
|
226
220
|
|
@@ -251,14 +245,5 @@ module BitexBot
|
|
251
245
|
body message
|
252
246
|
end
|
253
247
|
end
|
254
|
-
|
255
|
-
def create_buy_opening_flow(balance, order_book, transactions, profile)
|
256
|
-
BuyOpeningFlow.create_for_market(balance.crypto.available, order_book.bids, transactions, profile[:fee], balance.fee, store)
|
257
|
-
end
|
258
|
-
|
259
|
-
def create_sell_opening_flow(balance, order_book, transactions, profile)
|
260
|
-
SellOpeningFlow.create_for_market(balance.fiat.available, order_book.asks, transactions, profile[:fee], balance.fee, store)
|
261
|
-
end
|
262
|
-
# rubocop:enable Metrics/ClassLength
|
263
248
|
end
|
264
249
|
end
|
data/lib/bitex_bot/settings.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'hashie'
|
2
|
+
require 'fileutils'
|
2
3
|
require 'bigdecimal'
|
3
4
|
require 'bigdecimal/util'
|
4
5
|
|
@@ -7,6 +8,7 @@ module BitexBot
|
|
7
8
|
class FileSettings < ::Hashie::Clash
|
8
9
|
def method_missing(name, *args, &block)
|
9
10
|
return super unless args.none? && args.size == 1
|
11
|
+
|
10
12
|
self[name] = args.first
|
11
13
|
end
|
12
14
|
|
data/lib/bitex_bot/version.rb
CHANGED
data/settings.rb.sample
CHANGED
@@ -46,19 +46,37 @@ buying_foreign_exchange_rate 1.to_d
|
|
46
46
|
# api_key: it's passed in to the bitex gem: https://github.com/bitex-la/bitex-ruby.
|
47
47
|
# sandbox: Use sandbox environments instead of live environments.
|
48
48
|
# order_book: order book with which you will operate.
|
49
|
-
maker bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe',
|
49
|
+
maker bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe', sandbox: false, ssl_version: nil, debug: false, order_book: 'btc_usd' }
|
50
50
|
|
51
51
|
# These are the configurations we need for the markets currently supported.
|
52
|
-
# Turn on and off the chosen Bitstamp, Itbit or Kraken market with comments.
|
52
|
+
# Turn on and off the chosen Bitex, Bitstamp, Itbit or Kraken market with comments.
|
53
53
|
|
54
54
|
# These are passed in to the bitstamp gem: see https://github.com/kojnapp/bitstamp for more info.
|
55
|
-
|
55
|
+
# Supported values for order_book: 'btcusd', 'btceur', 'eurusd', 'xrpusd', 'xrpeur', 'xrpbtc', 'ltcusd', 'ltceur', 'ltcbtc',
|
56
|
+
# 'ethusd', 'etheur', 'ethbtc', 'bchusd', 'bcheur', 'bchbtc'
|
57
|
+
#
|
58
|
+
taker bitstamp: { api_key: 'YOUR_API_KEY', secret: 'YOUR_API_SECRET', client_id: 'YOUR_BITSTAMP_USERNAME', order_book: 'btcusd' }
|
56
59
|
|
57
60
|
# These are passed in to the itbit gem: see https://github.com/bitex-la/itbit for more info.
|
58
|
-
#
|
61
|
+
# Choices: 'xbtusd', 'xbtsgd', 'xbteur', https://api.itbit.com/docs#market-data-get-order-book-get.
|
62
|
+
#
|
63
|
+
# taker itbit: { client_key: 'client-key', secret: 'secret', user_id: 'user-id', default_wallet_id: 'wallet-000', sandbox: false, order_book: 'xbtusd' }
|
59
64
|
|
60
65
|
# These are passed in to the kraken gem: see https://github.com/shideneyu/kraken_client for more info.
|
61
|
-
#
|
66
|
+
# Supported values for order_book https://api.kraken.com/0/public/AssetPairs:
|
67
|
+
# 'bcheur', 'bchusd', 'bchxbt', 'dasheur', 'dashusd', 'dashxbt', 'eoseth', 'eoseur', 'eosusd', 'eosxbt', 'etceth', 'etceur',
|
68
|
+
# 'etcusd', 'etcxbt', 'ethcad', '"ethcad.d"', 'etheur', '"etheur.d"', 'ethgbp', ''ethgbp.d'', 'ethjpy', ''ethjpy.d'', 'ethusd',
|
69
|
+
# 'ethusd.d', 'ethxbt', ''ethxbt.d'', 'gnoeth', 'gnoeur', 'gnousd', 'gnoxbt', 'icneth', 'icnxbt', 'ltceur', 'ltcusd', 'ltcxbt',
|
70
|
+
# 'mlneth', 'mlnxbt', 'repeth', 'repeur', 'repusd', 'repxbt', 'usdtusd', 'xbtcad', ''xbtcad.d'', 'xbteur', ''xbteur.d'', 'xbtgbp',
|
71
|
+
# 'xbtgbp.d', 'xbtjpy', ''xbtjpy.d'', 'xbtusd', ''xbtusd.d'', 'xdgxbt', 'xlmeur', 'xlmusd', 'xlmxbt', 'xmreur', 'xmrusd',
|
72
|
+
# 'xmrxbt', 'xrpcad', 'xrpeur', 'xrpjpy', 'xrpusd', 'xrpxbt', 'zeceur', 'zecjpy', 'zecusd', 'zecxbt']
|
73
|
+
#
|
74
|
+
# taker kraken: { api_key: 'your_api_key', api_secret: 'your_api_secret', order_book: 'xbtusd' }
|
75
|
+
|
76
|
+
# These are passed in to the Bitex gem: see https://github.com/bitex-la/bitex-ruby.
|
77
|
+
# Supported values for order_book: 'btc_usd', 'bch_usd', 'bct_ars', 'bct_clp', 'bct_pyg', 'btc_uyu'
|
78
|
+
#
|
79
|
+
# taker bitex: { api_key: 'taker_api_key', sandbox: false, ssl_version: nil, debug: false, order_book: 'btc_usd' }
|
62
80
|
|
63
81
|
# Settings for the ActiveRecord Database to use.
|
64
82
|
# sqlite is just fine. Check this link for more options:
|
@@ -11,9 +11,16 @@ describe BitexBot::Settings do
|
|
11
11
|
buying_foreign_exchange_rate: 1,
|
12
12
|
selling_foreign_exchange_rate: 1,
|
13
13
|
|
14
|
-
maker: { bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe', order_book:
|
14
|
+
maker: { bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe', order_book: 'btc_usd', sandbox: false, ssl_version: nil, debug: false } },
|
15
15
|
# By default Bitstamp is taker market.
|
16
|
-
taker: {
|
16
|
+
taker: {
|
17
|
+
bitstamp: {
|
18
|
+
api_key: 'YOUR_API_KEY',
|
19
|
+
secret: 'YOUR_API_SECRET',
|
20
|
+
client_id: 'YOUR_BITSTAMP_USERNAME',
|
21
|
+
order_book: 'btcusd'
|
22
|
+
}
|
23
|
+
},
|
17
24
|
|
18
25
|
database: { adapter: :sqlite3, database: 'bitex_bot.db' },
|
19
26
|
mailer: {
|
@@ -32,13 +39,13 @@ describe BitexBot::Settings do
|
|
32
39
|
)
|
33
40
|
end
|
34
41
|
|
35
|
-
context 'fx rate' do
|
36
|
-
context '
|
42
|
+
context 'fx rate, when Store' do
|
43
|
+
context 'isn´t loaded, by default' do
|
37
44
|
it { described_class.buying_fx_rate.should eq(1) }
|
38
45
|
it { described_class.selling_fx_rate.should eq(1) }
|
39
46
|
end
|
40
47
|
|
41
|
-
context '
|
48
|
+
context 'is loaded, take rate from' do
|
42
49
|
before(:each) { BitexBot::Store.stub(first: BitexBot::Store.new) }
|
43
50
|
let(:fx_rate) { rand(10) }
|
44
51
|
|
@@ -58,7 +65,7 @@ describe BitexBot::Settings do
|
|
58
65
|
|
59
66
|
context 'maker' do
|
60
67
|
{
|
61
|
-
bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe',
|
68
|
+
bitex: { api_key: 'your_bitex_api_key_which_should_be_kept_safe', ssl_version: nil, debug: false, sandbox: false, order_book: 'btc_usd' }
|
62
69
|
}.each do |market, market_settings|
|
63
70
|
before(:each) { described_class.stub(taker: BitexBot::SettingsClass.new(taker_hash)) }
|
64
71
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :bitex_ask, class: Bitex::Ask do
|
3
|
+
id { 12_345_678 }
|
4
|
+
created_at { Time.now }
|
5
|
+
order_book { :btc_usd }
|
6
|
+
price { 1_000.to_d }
|
7
|
+
status { :received }
|
8
|
+
reason { :not_cancelled }
|
9
|
+
issuer { 'User#1' }
|
10
|
+
quantity { 100.to_d }
|
11
|
+
remaining_quantity { 100.to_d }
|
12
|
+
produced_amount { 10.to_d }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :bitex_bid, class: Bitex::Bid do
|
3
|
+
id { 12_345_678 }
|
4
|
+
created_at { Time.now }
|
5
|
+
order_book { :btc_usd }
|
6
|
+
price { 1_000.to_d }
|
7
|
+
status { :received }
|
8
|
+
reason { :not_cancelled }
|
9
|
+
issuer { 'User#1' }
|
10
|
+
amount { 100.to_d }
|
11
|
+
remaining_amount { 100.to_d }
|
12
|
+
produced_quantity { 10.to_d }
|
13
|
+
end
|
14
|
+
end
|
data/spec/factories/bitex_buy.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
factory :bitex_buy, class: Bitex::Buy do
|
3
|
-
id
|
3
|
+
id { 12_345_678 }
|
4
4
|
created_at { Time.now }
|
5
|
-
order_book :btc_usd
|
6
|
-
quantity 2.0
|
7
|
-
amount 600.0
|
8
|
-
fee 0.05
|
9
|
-
price 300.0
|
10
|
-
bid_id
|
5
|
+
order_book { :btc_usd }
|
6
|
+
quantity { 2.0.to_d }
|
7
|
+
amount { 600.0.to_d }
|
8
|
+
fee { 0.05.to_d }
|
9
|
+
price { 300.0.to_d }
|
10
|
+
bid_id { 12_345 }
|
11
11
|
end
|
12
12
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
factory :bitex_sell, class: Bitex::Sell do
|
3
|
-
id
|
3
|
+
id { 12_345_678 }
|
4
4
|
created_at { Time.now }
|
5
|
-
order_book :btc_usd
|
6
|
-
quantity 2.0
|
7
|
-
amount 600.0
|
8
|
-
fee 0.05
|
9
|
-
price 300.0
|
10
|
-
ask_id
|
5
|
+
order_book { :btc_usd }
|
6
|
+
quantity { 2.0.to_d }
|
7
|
+
amount { 600.0.to_d }
|
8
|
+
fee { 0.05.to_d }
|
9
|
+
price { 300.0.to_d }
|
10
|
+
ask_id { 12_345 }
|
11
11
|
end
|
12
12
|
end
|