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
@@ -1,77 +1,72 @@
|
|
1
|
-
|
2
|
-
# assumed to get executed completely.
|
3
|
-
# It essentially drops the start of the order book, to account for price
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def self.run(volatility, transactions, order_book,
|
25
|
-
amount_target, quantity_target)
|
1
|
+
module BitexBot
|
2
|
+
# Simulates hitting an order-book to find a price at which an order can be assumed to get executed completely.
|
3
|
+
# It essentially drops the start of the order book, to account for price volatility (assuming those orders may be taken by
|
4
|
+
# someone else), and then digs until the given USD amount or BTC quantity are reached, finally returning the last price seen,
|
5
|
+
# which is the 'safest' price at which we can expect this order to get executed quickly.
|
6
|
+
#
|
7
|
+
class OrderBookSimulator
|
8
|
+
# @param volatility [Integer] How many seconds of recent volume we need to skip from the start of the order book to be more
|
9
|
+
# certain that our order will get executed.
|
10
|
+
# @param transactions [Hash] a list of hashes representing all transactions in the other exchange:
|
11
|
+
# Each hash contains 'date', 'tid', 'price' and 'amount', where 'amount' is the BTC transacted.
|
12
|
+
# @param order_book [[price, quantity]] a list of lists representing the order book to dig in.
|
13
|
+
# @param amount_target [BigDecimal] stop when this amount has been reached, leave as nil if looking for a quantity_target.
|
14
|
+
# @param quantity_target [BigDecimal] stop when this quantity has been reached, leave as nil if looking for an
|
15
|
+
# amount_target.
|
16
|
+
# @return [Decimal] Returns the price that we're more likely to get when executing an order for the given amount or
|
17
|
+
# quantity.
|
18
|
+
#
|
19
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
20
|
+
def self.run(volatility, transactions, order_book, amount_target, quantity_target)
|
21
|
+
to_skip = estimate_quantity_to_skip(volatility, transactions)
|
22
|
+
Robot.log(:debug, "Skipping #{to_skip} BTC")
|
23
|
+
seen = 0
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
dropped = [quantity, to_skip].min
|
39
|
-
to_skip -= dropped
|
40
|
-
quantity -= dropped
|
41
|
-
BitexBot::Robot.logger.debug("Skipped #{dropped} BTC @ $#{price}")
|
42
|
-
next if quantity == 0
|
43
|
-
end
|
44
|
-
|
45
|
-
if quantity_target
|
46
|
-
if quantity >= (quantity_target - seen)
|
47
|
-
BitexBot::Robot.logger.debug("Best price to get "\
|
48
|
-
"#{quantity_target} BTC is $#{price}")
|
49
|
-
return price
|
50
|
-
else
|
51
|
-
seen += quantity
|
25
|
+
order_book.each do |order_summary|
|
26
|
+
price = order_summary.price
|
27
|
+
quantity = order_summary.quantity
|
28
|
+
|
29
|
+
# An order may be partially or completely skipped due to volatility.
|
30
|
+
if to_skip.positive?
|
31
|
+
dropped = [quantity, to_skip].min
|
32
|
+
to_skip -= dropped
|
33
|
+
quantity -= dropped
|
34
|
+
Robot.log(:debug, "Skipped #{dropped} BTC @ $#{price}")
|
35
|
+
next if quantity.zero?
|
52
36
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
37
|
+
|
38
|
+
if quantity_target.present?
|
39
|
+
return best_price('BTC', quantity_target, price) if best_price?(quantity, quantity_target, seen)
|
40
|
+
seen += quantity
|
41
|
+
elsif amount_target.present?
|
42
|
+
amount = price * quantity
|
43
|
+
return best_price('$', amount_target, price) if best_price?(amount, amount_target, seen)
|
60
44
|
seen += amount
|
61
45
|
end
|
62
46
|
end
|
47
|
+
order_book.last.price
|
63
48
|
end
|
64
|
-
|
65
|
-
return order_book.last.first.to_d
|
66
|
-
end
|
49
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
67
50
|
|
68
|
-
private
|
51
|
+
# private class methods
|
52
|
+
|
53
|
+
def self.estimate_quantity_to_skip(volatility, transactions)
|
54
|
+
threshold = transactions.first.timestamp - volatility
|
55
|
+
transactions
|
56
|
+
.select { |t| t.timestamp > threshold }
|
57
|
+
.map { |t| t.amount.to_d }
|
58
|
+
.sum
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.best_price?(volume, target, seen)
|
62
|
+
volume >= (target - seen)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.best_price(currency, target, price)
|
66
|
+
Robot.log(:debug, "Best price to get #{currency} #{target} is $#{price}")
|
67
|
+
price
|
68
|
+
end
|
69
69
|
|
70
|
-
|
71
|
-
threshold = transactions.first.date.to_i - volatility
|
72
|
-
transactions
|
73
|
-
.select{|t| t.date.to_i > threshold}
|
74
|
-
.collect{|t| t.amount.to_d }
|
75
|
-
.sum
|
70
|
+
# end: private class methods
|
76
71
|
end
|
77
72
|
end
|
@@ -1,36 +1,41 @@
|
|
1
1
|
module BitexBot
|
2
|
+
# It bought at Bitex and needs to close (sell) in the other market.
|
2
3
|
class SellClosingFlow < ClosingFlow
|
3
4
|
has_many :open_positions, class_name: 'OpenSell', foreign_key: :closing_flow_id
|
4
5
|
has_many :close_positions, class_name: 'CloseSell', foreign_key: :closing_flow_id
|
5
|
-
|
6
|
-
|
6
|
+
|
7
|
+
scope :active, -> { where(done: false) }
|
8
|
+
|
7
9
|
def self.open_position_class
|
8
10
|
OpenSell
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def get_usd_profit
|
18
|
-
open_positions.sum(:amount) - close_positions.sum(:amount)
|
13
|
+
private
|
14
|
+
|
15
|
+
# create_or_cancel! helpers
|
16
|
+
# The amount received when selling initially, minus the amount spent re-buying the sold coins.
|
17
|
+
def estimate_fiat_profit
|
18
|
+
open_positions.sum(:amount) - positions_balance_amount
|
19
19
|
end
|
20
|
-
|
21
|
-
# The coins we actually bought minus the coins we were supposed
|
22
|
-
|
23
|
-
def get_btc_profit
|
20
|
+
|
21
|
+
# The coins we actually bought minus the coins we were supposed to re-buy.
|
22
|
+
def estimate_btc_profit
|
24
23
|
close_positions.sum(:quantity) - quantity
|
25
24
|
end
|
26
|
-
|
27
|
-
def
|
25
|
+
|
26
|
+
def next_price_and_quantity
|
28
27
|
closes = close_positions
|
29
|
-
next_price =
|
30
|
-
|
31
|
-
|
32
|
-
((quantity * desired_price) - closes.sum(:amount)) / next_price
|
28
|
+
next_price = desired_price + price_variation(closes.count)
|
29
|
+
next_quantity = ((quantity * desired_price) - closes.sum(:amount)) / next_price
|
30
|
+
|
33
31
|
[next_price, next_quantity]
|
34
32
|
end
|
33
|
+
# end: create_or_cancel! helpers
|
34
|
+
|
35
|
+
# create_order_and_close_position helpers
|
36
|
+
def order_method
|
37
|
+
:buy
|
38
|
+
end
|
39
|
+
# end: create_order_and_close_position helpers
|
35
40
|
end
|
36
41
|
end
|
@@ -1,83 +1,76 @@
|
|
1
1
|
module BitexBot
|
2
|
-
# A workflow for selling bitcoin in Bitex and buying on another exchange.
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# orderbook and the recent operated volume.
|
2
|
+
# A workflow for selling bitcoin in Bitex and buying on another exchange. The SellOpeningFlow factory function estimates how
|
3
|
+
# much you could buy on the other exchange and calculates a reasonable price taking into account the remote order book and the
|
4
|
+
# recent operated volume.
|
6
5
|
#
|
7
|
-
# When created, a SellOpeningFlow places an Ask on Bitex for the calculated
|
8
|
-
#
|
9
|
-
# created to buy the same quantity for a lower price on the other exchange.
|
6
|
+
# When created, a SellOpeningFlow places an Ask on Bitex for the calculated quantity and price, when the Ask is matched on
|
7
|
+
# Bitex an OpenSell is created to buy the same quantity for a lower price on the other exchange.
|
10
8
|
#
|
11
|
-
# A SellOpeningFlow can be cancelled at any point, which will cancel the Bitex
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# @attr order_id The first thing a SellOpeningFlow does is placing an Ask on Bitex,
|
15
|
-
# this is its unique id.
|
9
|
+
# A SellOpeningFlow can be cancelled at any point, which will cancel the Bitex order and any orders on the remote exchange
|
10
|
+
# created from its OpenSell's
|
11
|
+
#
|
12
|
+
# @attr order_id The first thing a SellOpeningFlow does is placing an Ask on Bitex, this is its unique id.
|
16
13
|
class SellOpeningFlow < OpeningFlow
|
17
|
-
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# @param
|
28
|
-
# order book in the other exchange.
|
29
|
-
# @param transactions [Hash] a list of hashes representing
|
30
|
-
# all transactions in the other exchange. Each hash contains 'date', 'tid',
|
31
|
-
# 'price' and 'amount', where 'amount' is the BTC transacted.
|
32
|
-
# @param bitex_fee [BigDecimal] the transaction fee to pay on bitex.
|
33
|
-
# @param other_fee [BigDecimal] the transaction fee to pay on the other
|
34
|
-
# exchange.
|
14
|
+
# Start a workflow for selling bitcoin on bitex and buying on the other exchange. The quantity to be sold on bitex is
|
15
|
+
# retrieved from Settings, if there is not enough BTC on bitex or USD on the other exchange then no order will be placed and
|
16
|
+
# an exception will be raised instead.
|
17
|
+
# The amount a SellOpeningFlow will try to sell and the price it will try to charge are derived from these parameters:
|
18
|
+
#
|
19
|
+
# @param usd_balance [BigDecimal] amount of usd available in the other exchange that can be spent to balance this sale.
|
20
|
+
# @param order_book [[price, quantity]] a list of lists representing an ask order book in the other exchange.
|
21
|
+
# @param transactions [Hash] a list of hashes representing all transactions in the other exchange:
|
22
|
+
# Each hash contains 'date', 'tid', 'price' and 'amount', where 'amount' is the BTC transacted.
|
23
|
+
# @param maker_fee [BigDecimal] the transaction fee to pay on our maker exchange.
|
24
|
+
# @param taker_fee [BigDecimal] the transaction fee to pay on the taker exchange.
|
35
25
|
# @param store [Store] An updated config for this robot, mainly to use for profit.
|
36
26
|
#
|
37
27
|
# @return [SellOpeningFlow] The newly created flow.
|
38
|
-
# @raise [CannotCreateFlow] If there's any problem creating this flow, for
|
39
|
-
#
|
40
|
-
|
41
|
-
def self.create_for_market(usd_balance, order_book, transactions,
|
42
|
-
bitex_fee, other_fee, store)
|
28
|
+
# @raise [CannotCreateFlow] If there's any problem creating this flow, for example when you run out of BTC on bitex or out
|
29
|
+
# of USD on the other exchange.
|
30
|
+
def self.create_for_market(usd_balance, order_book, transactions, maker_fee, taker_fee, store)
|
43
31
|
super
|
44
32
|
end
|
45
|
-
|
33
|
+
|
34
|
+
# sync_open_positions helpers
|
35
|
+
def self.transaction_order_id(transaction)
|
36
|
+
transaction.ask_id
|
37
|
+
end
|
38
|
+
|
46
39
|
def self.open_position_class
|
47
40
|
OpenSell
|
48
41
|
end
|
49
|
-
|
42
|
+
# end: sync_open_positions helpers
|
43
|
+
|
44
|
+
# sought_transaction helpers
|
50
45
|
def self.transaction_class
|
51
46
|
Bitex::Sell
|
52
47
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
# end: sought_transaction helpers
|
49
|
+
|
50
|
+
# create_for_market helpers
|
51
|
+
def self.maker_price(usd_to_spend_re_buying)
|
52
|
+
usd_to_spend_re_buying / value_to_use * (1 + profit / 100)
|
56
53
|
end
|
57
54
|
|
58
55
|
def self.order_class
|
59
56
|
Bitex::Ask
|
60
57
|
end
|
61
58
|
|
62
|
-
def self.
|
63
|
-
store.
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.get_safest_price(transactions, order_book, bitcoins_to_use)
|
67
|
-
OrderBookSimulator.run(Settings.time_to_live, transactions,
|
68
|
-
order_book, nil, bitcoins_to_use)
|
59
|
+
def self.profit
|
60
|
+
store.selling_profit || Settings.selling.profit
|
69
61
|
end
|
70
|
-
|
71
|
-
def self.
|
62
|
+
|
63
|
+
def self.remote_value_to_use(value_to_use_needed, safest_price)
|
72
64
|
value_to_use_needed * safest_price
|
73
65
|
end
|
74
66
|
|
75
|
-
def self.
|
76
|
-
|
67
|
+
def self.safest_price(transactions, order_book, bitcoins_to_use)
|
68
|
+
OrderBookSimulator.run(Settings.time_to_live, transactions, order_book, nil, bitcoins_to_use)
|
77
69
|
end
|
78
|
-
|
79
|
-
def self.
|
80
|
-
|
70
|
+
|
71
|
+
def self.value_to_use
|
72
|
+
store.selling_quantity_to_sell_per_order || Settings.selling.quantity_to_sell_per_order
|
81
73
|
end
|
74
|
+
# end: create_for_market helpers
|
82
75
|
end
|
83
76
|
end
|