bitex_bot 0.6.1 → 0.9.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/.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
|