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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a52c57b2cb3f955ac849623421abc8296b1dc9c
|
4
|
+
data.tar.gz: b1a88154cb71ef5868c510f9316f0e755b161c33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cb5d465e54e321a5d181aa86e988c0183938a70540ffcb5434000abf55627d9e8f875da413d67dd1cfa07892f0198c6be7d7c628d2e46a1a31caed5c6afa17d
|
7
|
+
data.tar.gz: 8cef55c3f44533c9b43c7f82bede8a7fb2777e248ad533845a50ac6cc9006cf59bdb95499d94a5f8a6b509fa0a7bff46347d6020bb765f5cd503358a34512dea
|
data/.rubocop.yml
CHANGED
@@ -2,16 +2,16 @@ Style/FrozenStringLiteralComment:
|
|
2
2
|
EnforcedStyle: never
|
3
3
|
|
4
4
|
Metrics/AbcSize:
|
5
|
-
Max:
|
5
|
+
Max: 18
|
6
6
|
|
7
7
|
Metrics/ClassLength:
|
8
|
-
|
8
|
+
Enabled: false
|
9
9
|
|
10
10
|
Metrics/LineLength:
|
11
11
|
Max: 130
|
12
12
|
|
13
13
|
Metrics/MethodLength:
|
14
|
-
Max:
|
14
|
+
Max: 30
|
15
15
|
|
16
16
|
Metrics/ParameterLists:
|
17
17
|
Max: 7
|
data/Gemfile
CHANGED
@@ -2,4 +2,6 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in bitex_bot.gemspec
|
4
4
|
gemspec
|
5
|
-
|
5
|
+
|
6
|
+
gem 'bitex', github: 'bitex-la/bitex-ruby', branch: 'bitcoin-cash-market'
|
7
|
+
gem 'bitstamp', github: 'bitex-la/bitstamp', branch: 'update-lib'
|
data/bitex_bot.gemspec
CHANGED
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = %w[lib]
|
20
20
|
|
21
|
-
spec.add_dependency 'activerecord'
|
21
|
+
spec.add_dependency 'activerecord'
|
22
22
|
spec.add_dependency 'hashie', '~> 3.5.4'
|
23
23
|
spec.add_dependency 'mail'
|
24
24
|
spec.add_dependency 'sqlite3'
|
25
25
|
|
26
26
|
spec.add_dependency 'bitex', '0.6'
|
27
27
|
spec.add_dependency 'bitstamp'
|
28
|
-
spec.add_dependency 'itbit'
|
28
|
+
spec.add_dependency 'itbit'
|
29
29
|
spec.add_dependency 'kraken_client', '~> 1.2.1'
|
30
30
|
|
31
31
|
spec.add_development_dependency 'bundler'
|
@@ -34,11 +34,14 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency 'byebug'
|
35
35
|
spec.add_development_dependency 'database_cleaner'
|
36
36
|
spec.add_development_dependency 'factory_bot'
|
37
|
+
spec.add_development_dependency 'faker'
|
37
38
|
spec.add_development_dependency 'rspec'
|
39
|
+
spec.add_development_dependency 'rspec-its'
|
38
40
|
spec.add_development_dependency 'rspec-mocks'
|
39
41
|
spec.add_development_dependency 'rspec_junit_formatter'
|
40
42
|
spec.add_development_dependency 'rubocop'
|
41
43
|
spec.add_development_dependency 'shoulda-matchers'
|
42
44
|
spec.add_development_dependency 'timecop'
|
45
|
+
spec.add_development_dependency 'vcr'
|
43
46
|
spec.add_development_dependency 'webmock'
|
44
47
|
end
|
data/lib/bitex_bot/database.rb
CHANGED
@@ -53,7 +53,7 @@ module BitexBot
|
|
53
53
|
t.decimal :quantity, precision: 30, scale: 15
|
54
54
|
t.decimal :amount, precision: 30, scale: 15
|
55
55
|
t.boolean :done, null: false, default: false
|
56
|
-
t.decimal :crypto_profit,
|
56
|
+
t.decimal :crypto_profit, precision: 30, scale: 15
|
57
57
|
t.decimal :fiat_profit, precision: 30, scale: 15
|
58
58
|
t.decimal :fx_rate, precision: 20, scale: 8
|
59
59
|
t.timestamps null: true
|
@@ -64,7 +64,7 @@ module BitexBot
|
|
64
64
|
t.decimal :quantity, precision: 30, scale: 15
|
65
65
|
t.decimal :amount, precision: 30, scale: 15
|
66
66
|
t.boolean :done, null: false, default: false
|
67
|
-
t.decimal :crypto_profit,
|
67
|
+
t.decimal :crypto_profit, precision: 30, scale: 15
|
68
68
|
t.decimal :fiat_profit, precision: 30, scale: 15
|
69
69
|
t.decimal :fx_rate, precision: 20, scale: 8
|
70
70
|
t.timestamps null: true
|
@@ -1,12 +1,15 @@
|
|
1
1
|
# This class represents the general behaviour for trading platform wrappers.
|
2
2
|
class ApiWrapper
|
3
|
+
attr_accessor :currency_pair
|
4
|
+
|
3
5
|
MIN_AMOUNT = 5
|
4
6
|
|
5
7
|
Transaction = Struct.new(
|
6
|
-
:id,
|
7
|
-
:price,
|
8
|
-
:amount,
|
9
|
-
:timestamp # Epoch Integer
|
8
|
+
:id, # Integer
|
9
|
+
:price, # Decimal
|
10
|
+
:amount, # Decimal
|
11
|
+
:timestamp, # Epoch Integer
|
12
|
+
:raw # Actual transaction
|
10
13
|
)
|
11
14
|
|
12
15
|
Order = Struct.new(
|
@@ -15,14 +18,14 @@ class ApiWrapper
|
|
15
18
|
:price, # Decimal
|
16
19
|
:amount, # Decimal
|
17
20
|
:timestamp, # Integer
|
18
|
-
:
|
21
|
+
:raw # Actual order object
|
19
22
|
) do
|
20
23
|
def method_missing(method_name, *args, &block)
|
21
|
-
|
24
|
+
raw.respond_to?(method_name) ? raw.send(method_name, *args, &block) : super
|
22
25
|
end
|
23
26
|
|
24
27
|
def respond_to_missing?(method_name, include_private = false)
|
25
|
-
|
28
|
+
raw.respond_to?(method_name) || super
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
@@ -50,58 +53,53 @@ class ApiWrapper
|
|
50
53
|
)
|
51
54
|
|
52
55
|
UserTransaction = Struct.new(
|
53
|
-
:order_id,
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:fee,
|
58
|
-
:type,
|
59
|
-
:timestamp
|
56
|
+
:order_id, # Integer
|
57
|
+
:fiat, # Decimal
|
58
|
+
:crypto, # Decimal
|
59
|
+
:crypto_fiat, # Decimal
|
60
|
+
:fee, # Decimal
|
61
|
+
:type, # Integer
|
62
|
+
:timestamp # Epoch Integer
|
60
63
|
)
|
61
64
|
|
62
|
-
# @return [Void]
|
63
|
-
def self.setup(_settings)
|
64
|
-
raise 'self subclass responsibility'
|
65
|
-
end
|
66
|
-
|
67
65
|
# @return [Array<Transaction>]
|
68
|
-
def
|
66
|
+
def transactions
|
69
67
|
raise 'self subclass responsibility'
|
70
68
|
end
|
71
69
|
|
72
70
|
# @return [OrderBook]
|
73
|
-
def
|
71
|
+
def order_book(_retries = 20)
|
74
72
|
raise 'self subclass responsibility'
|
75
73
|
end
|
76
74
|
|
77
75
|
# @return [BalanceSummary]
|
78
|
-
def
|
76
|
+
def balance
|
79
77
|
raise 'self subclass responsibility'
|
80
78
|
end
|
81
79
|
|
82
80
|
# @return [nil]
|
83
|
-
def
|
81
|
+
def cancel
|
84
82
|
raise 'self subclass responsibility'
|
85
83
|
end
|
86
84
|
|
87
85
|
# @return [Array<Order>]
|
88
|
-
def
|
86
|
+
def orders
|
89
87
|
raise 'self subclass responsibility'
|
90
88
|
end
|
91
89
|
|
92
90
|
# @return [UserTransaction]
|
93
|
-
def
|
91
|
+
def user_transactions
|
94
92
|
raise 'self subclass responsibility'
|
95
93
|
end
|
96
94
|
|
97
95
|
# @param type
|
98
96
|
# @param price
|
99
97
|
# @param quantity
|
100
|
-
def
|
98
|
+
def place_order(type, price, quantity)
|
101
99
|
order = send_order(type, price, quantity)
|
102
100
|
return order unless order.nil? || order.id.nil?
|
103
101
|
|
104
|
-
BitexBot::Robot.log(:debug, "Captured error when placing order on #{self.class
|
102
|
+
BitexBot::Robot.log(:debug, "Captured error when placing order on #{self.class}")
|
105
103
|
# Order may have gone through and be stuck somewhere in Wrapper's pipeline.
|
106
104
|
# We just sleep for a bit and then look for the order.
|
107
105
|
20.times do
|
@@ -109,32 +107,46 @@ class ApiWrapper
|
|
109
107
|
order = find_lost(type, price, quantity)
|
110
108
|
return order if order.present?
|
111
109
|
end
|
112
|
-
raise OrderNotFound, "Closing: #{type} order not found for #{quantity}
|
110
|
+
raise OrderNotFound, "Closing: #{type} order not found for #{quantity} #{base} @ #{quote} #{price}. #{order}"
|
113
111
|
end
|
114
112
|
|
115
113
|
# Hook Method - arguments could not be used in their entirety by the subclasses
|
116
|
-
def
|
114
|
+
def send_order(_type, _price, _quantity)
|
117
115
|
raise 'self subclass responsibility'
|
118
116
|
end
|
119
117
|
|
120
|
-
# @param
|
118
|
+
# @param order_type [String] buy|sell
|
121
119
|
# @param price [Decimal]
|
120
|
+
# @param quantity [Decimal]
|
122
121
|
#
|
123
122
|
# Hook Method - arguments could not be used in their entirety by the subclasses
|
124
|
-
def
|
123
|
+
def find_lost(_type, _price, _quantity)
|
125
124
|
raise 'self subclass responsibility'
|
126
125
|
end
|
127
126
|
|
127
|
+
# From an order when you buy or sell, when you place an order and it matches, you can match more than one order.
|
128
128
|
# @param order_id
|
129
|
-
# @param transactions
|
129
|
+
# @param transactions: all matches for a purchase or sale order.
|
130
130
|
#
|
131
131
|
# @return [Array<Decimal, Decimal>]
|
132
|
-
def
|
132
|
+
def amount_and_quantity(_order_id)
|
133
133
|
raise 'self subclass responsibility'
|
134
134
|
end
|
135
135
|
|
136
|
-
def
|
137
|
-
|
136
|
+
def enough_order_size?(quantity, price)
|
137
|
+
quantity * price > MIN_AMOUNT
|
138
|
+
end
|
139
|
+
|
140
|
+
def base_quote
|
141
|
+
"#{base}_#{quote}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def base
|
145
|
+
currency_pair[:base]
|
146
|
+
end
|
147
|
+
|
148
|
+
def quote
|
149
|
+
currency_pair[:quote]
|
138
150
|
end
|
139
151
|
end
|
140
152
|
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Wrapper implementation for Bitex API.
|
2
|
+
# https://bitex.la/developers
|
3
|
+
class BitexApiWrapper < ApiWrapper
|
4
|
+
Order = Struct.new(
|
5
|
+
:id, # String
|
6
|
+
:type, # Symbol
|
7
|
+
:price, # Decimal
|
8
|
+
:amount, # Decimal
|
9
|
+
:timestamp, # Integer
|
10
|
+
:raw # Actual order object
|
11
|
+
) do
|
12
|
+
def method_missing(method_name, *args, &block)
|
13
|
+
raw.respond_to?(method_name) ? raw.send(method_name, *args, &block) : super
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(method_name, include_private = false)
|
17
|
+
raw.respond_to?(method_name) || super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :api_key, :ssl_version, :debug, :sandbox
|
22
|
+
|
23
|
+
def initialize(settings)
|
24
|
+
self.api_key = settings.api_key
|
25
|
+
self.ssl_version = settings.ssl_version
|
26
|
+
self.debug = settings.debug
|
27
|
+
self.sandbox = settings.sandbox
|
28
|
+
currency_pair(settings.order_book)
|
29
|
+
setup
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup
|
33
|
+
Bitex.api_key = api_key
|
34
|
+
Bitex.ssl_version = ssl_version
|
35
|
+
Bitex.debug = debug
|
36
|
+
Bitex.sandbox = sandbox
|
37
|
+
end
|
38
|
+
|
39
|
+
def profile
|
40
|
+
Bitex::Profile.get
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/AbcSize
|
44
|
+
def amount_and_quantity(order_id)
|
45
|
+
closes = user_transactions.select { |t| t.order_id.to_s == order_id }
|
46
|
+
amount = closes.map { |c| c.send(currency[:quote]).to_d }.sum.abs
|
47
|
+
quantity = closes.map { |c| c.send(currency[:base]).to_d }.sum.abs
|
48
|
+
|
49
|
+
[amount, quantity]
|
50
|
+
end
|
51
|
+
# rubocop:enable Metrics/AbcSize
|
52
|
+
|
53
|
+
def balance
|
54
|
+
balance_summary_parser(profile)
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_lost(type, price, _quantity)
|
58
|
+
orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
|
59
|
+
end
|
60
|
+
|
61
|
+
def order_book
|
62
|
+
order_book_parser(market.order_book)
|
63
|
+
end
|
64
|
+
|
65
|
+
def orders
|
66
|
+
Bitex::Order.all.map { |o| order_parser(o) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def send_order(type, price, quantity, wait = false)
|
70
|
+
{ sell: Bitex::Ask, buy: Bitex::Bid }[type].create!(base_quote.to_sym, quantity, price, wait)
|
71
|
+
end
|
72
|
+
|
73
|
+
def transactions
|
74
|
+
Bitex::Trade.all.map { |t| transaction_parser(t) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def user_transactions
|
78
|
+
Bitex::Trade.all.map { |trade| user_transaction_parser(trade) }
|
79
|
+
end
|
80
|
+
|
81
|
+
# {
|
82
|
+
# usd_balance: 10000.00, # Total USD balance.
|
83
|
+
# usd_reserved: 2000.00, # USD reserved in open orders.
|
84
|
+
# usd_available: 8000.00, # USD available for trading.
|
85
|
+
# btc_balance: 20.00000000, # Total BTC balance.
|
86
|
+
# btc_reserved: 5.00000000, # BTC reserved in open orders.
|
87
|
+
# btc_available: 15.00000000, # BTC available for trading.
|
88
|
+
# fee: 0.5, # Your trading fee (0.5 means 0.5%).
|
89
|
+
# btc_deposit_address: "1XXXXXXXX..." # Your BTC deposit address.
|
90
|
+
# }
|
91
|
+
def balance_summary_parser(balances)
|
92
|
+
BalanceSummary.new(
|
93
|
+
balance_parser(balances, currency_pair[:base]),
|
94
|
+
balance_parser(balances, currency_pair[:quote]),
|
95
|
+
balances[:fee].to_d
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def balance_parser(balances, currency)
|
100
|
+
Balance.new(
|
101
|
+
balances["#{currency}_balance".to_sym].to_d,
|
102
|
+
balances["#{currency}_reserved".to_sym].to_d,
|
103
|
+
balances["#{currency}_available".to_sym].to_d
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def last_order_by(price)
|
108
|
+
orders.select { |o| o.price == price && (o.timestamp - Time.now.to_i).abs < 500 }.first
|
109
|
+
end
|
110
|
+
|
111
|
+
# {
|
112
|
+
# bids: [[0.63921e3, 0.195e1], [0.637e3, 0.47e0], [0.63e3, 0.158e1]],
|
113
|
+
# asks: [[0.6424e3, 0.4e0], [0.6433e3, 0.95e0], [0.6443e3, 0.25e0]]
|
114
|
+
# }
|
115
|
+
def order_book_parser(book)
|
116
|
+
OrderBook.new(Time.now.to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
|
117
|
+
end
|
118
|
+
|
119
|
+
def order_summary_parser(orders)
|
120
|
+
orders.map { |order| OrderSummary.new(order[0].to_d, order[1].to_d) }
|
121
|
+
end
|
122
|
+
|
123
|
+
# <Bitex::Bid
|
124
|
+
# @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @price=0.1e4, @status=:executing, @reason=nil,
|
125
|
+
# @issuer=nil, @amount=0.1e3, @remaining_amount=0.1e2, @produced_quantity=0.0
|
126
|
+
# >
|
127
|
+
def order_parser(order)
|
128
|
+
Order.new(order.id.to_s, order_type(order), order.price, order_amount(order), order.created_at.to_i, order)
|
129
|
+
end
|
130
|
+
|
131
|
+
def order_type(order)
|
132
|
+
order.is_a?(Bitex::Bid) ? :buy : :sell
|
133
|
+
end
|
134
|
+
|
135
|
+
def order_amount(order)
|
136
|
+
order.is_a?(Bitex::Bid) ? order.amount : order.quantity
|
137
|
+
end
|
138
|
+
|
139
|
+
# [
|
140
|
+
# [1492795215, 80310, 1243.51657154, 4.60321971],
|
141
|
+
# [UNIX timestamp, Transaction ID, Price Paid, Amound Sold]
|
142
|
+
# ]
|
143
|
+
def transaction_parser(transaction)
|
144
|
+
Transaction.new(transaction.id, transaction.price.to_d, transaction.amount.to_d, transaction.created_at.to_i, transaction)
|
145
|
+
end
|
146
|
+
|
147
|
+
# <Bitex::Buy:0x007ff9a2979390
|
148
|
+
# @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @quantity=0.2e1, @amount=0.6e3, @fee=0.5e-1,
|
149
|
+
# @price=0.3e3, @bid_id=123
|
150
|
+
# >
|
151
|
+
#
|
152
|
+
# <Bitex::Sell:0x007ff9a2978710
|
153
|
+
# @id=12345678, @created_at=1999-12-31 21:10:00 -0300, @order_book=:btc_usd, @quantity=0.2e1, @amount=0.6e3, @fee=0.5e-1,
|
154
|
+
# @price=0.3e3, @ask_id=456i
|
155
|
+
# >
|
156
|
+
def user_transaction_parser(trade)
|
157
|
+
UserTransaction.new(
|
158
|
+
trade.id, trade.amount, trade.quantity, trade.price, trade.fee, trade_type(trade), trade.created_at.to_i
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
162
|
+
def trade_type(trade)
|
163
|
+
# ask: 0, bid: 1
|
164
|
+
trade.is_a?(Bitex::Buy) ? 1 : 0
|
165
|
+
end
|
166
|
+
|
167
|
+
def market
|
168
|
+
@market ||= { btc: Bitex::BitcoinMarketData, bch: Bitex::BitcoinCashMarketData }[currency_pair[:base].to_sym]
|
169
|
+
end
|
170
|
+
|
171
|
+
def currency_pair(order_book = '_')
|
172
|
+
@currency_pair ||= {
|
173
|
+
name: order_book,
|
174
|
+
base: order_book.split('_').first,
|
175
|
+
quote: order_book.split('_').last
|
176
|
+
}
|
177
|
+
end
|
178
|
+
end
|
@@ -1,41 +1,45 @@
|
|
1
1
|
# Wrapper implementation for Bitstamp API.
|
2
2
|
# https://www.bitstamp.net/api/
|
3
3
|
class BitstampApiWrapper < ApiWrapper
|
4
|
-
|
4
|
+
attr_accessor :key, :secret, :client_id
|
5
|
+
|
6
|
+
def initialize(settings)
|
7
|
+
self.key = settings.api_key
|
8
|
+
self.secret = settings.secret
|
9
|
+
self.client_id = settings.client_id
|
10
|
+
currency_pair(settings.order_book)
|
11
|
+
setup
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
5
15
|
Bitstamp.setup do |config|
|
6
|
-
config.key =
|
7
|
-
config.secret =
|
8
|
-
config.client_id =
|
16
|
+
config.key = key
|
17
|
+
config.secret = secret
|
18
|
+
config.client_id = client_id
|
9
19
|
end
|
10
20
|
end
|
11
21
|
|
12
|
-
def
|
13
|
-
closes =
|
14
|
-
amount = closes.
|
15
|
-
quantity = closes.
|
22
|
+
def amount_and_quantity(order_id)
|
23
|
+
closes = user_transactions.select { |t| t.order_id.to_s == order_id }
|
24
|
+
amount = closes.sum(&:fiat).abs
|
25
|
+
quantity = closes.sum(&:crypto).abs
|
16
26
|
|
17
27
|
[amount, quantity]
|
18
28
|
end
|
19
29
|
|
20
|
-
def
|
21
|
-
balance_summary_parser(Bitstamp.balance.symbolize_keys)
|
30
|
+
def balance
|
31
|
+
balance_summary_parser(Bitstamp.balance(currency_pair[:name]).symbolize_keys)
|
22
32
|
rescue StandardError => e
|
23
33
|
raise ApiWrapperError, "Bitstamp balance failed: #{e.message}"
|
24
34
|
end
|
25
35
|
|
26
|
-
def
|
27
|
-
Bitstamp::Order.new(id: order.id).cancel!
|
28
|
-
rescue StandardError => e
|
29
|
-
raise ApiWrapperError, "Bitstamp cancel! failed: #{e.message}"
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.find_lost(type, price, _quantity)
|
36
|
+
def find_lost(type, price, _quantity)
|
33
37
|
orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
|
34
38
|
end
|
35
39
|
|
36
40
|
# rubocop:disable Metrics/AbcSize
|
37
|
-
def
|
38
|
-
book = Bitstamp.order_book.deep_symbolize_keys
|
41
|
+
def order_book(retries = 20)
|
42
|
+
book = Bitstamp.order_book(currency_pair[:name]).deep_symbolize_keys
|
39
43
|
age = Time.now.to_i - book[:timestamp].to_i
|
40
44
|
return order_book_parser(book) if age <= 300
|
41
45
|
|
@@ -50,24 +54,25 @@ class BitstampApiWrapper < ApiWrapper
|
|
50
54
|
end
|
51
55
|
# rubocop:enable Metrics/AbcSize
|
52
56
|
|
53
|
-
def
|
54
|
-
Bitstamp.orders.all.map { |o| order_parser(o) }
|
57
|
+
def orders
|
58
|
+
Bitstamp.orders.all(currency_pair: currency_pair[:name]).map { |o| order_parser(o) }
|
55
59
|
rescue StandardError => e
|
56
60
|
raise ApiWrapperError, "Bitstamp orders failed: #{e.message}"
|
57
61
|
end
|
58
62
|
|
59
|
-
def
|
60
|
-
Bitstamp.orders.send(type, amount: quantity.round(4), price: price.round(2))
|
63
|
+
def send_order(type, price, quantity)
|
64
|
+
order = Bitstamp.orders.send(type, currency_pair: currency_pair[:name], amount: quantity.round(4), price: price.round(2))
|
65
|
+
order_parser(order) unless order.error.present?
|
61
66
|
end
|
62
67
|
|
63
|
-
def
|
64
|
-
Bitstamp.transactions.map { |t| transaction_parser(t) }
|
68
|
+
def transactions
|
69
|
+
Bitstamp.transactions(currency_pair[:name]).map { |t| transaction_parser(t) }
|
65
70
|
rescue StandardError => e
|
66
71
|
raise ApiWrapperError, "Bitstamp transactions failed: #{e.message}"
|
67
72
|
end
|
68
73
|
|
69
|
-
def
|
70
|
-
Bitstamp.user_transactions.all.map { |ut| user_transaction_parser(ut) }
|
74
|
+
def user_transactions
|
75
|
+
Bitstamp.user_transactions.all(currency_pair: currency_pair[:name]).map { |ut| user_transaction_parser(ut) }
|
71
76
|
rescue StandardError => e
|
72
77
|
raise ApiWrapperError, "Bitstamp user_transactions failed: #{e.message}"
|
73
78
|
end
|
@@ -77,11 +82,15 @@ class BitstampApiWrapper < ApiWrapper
|
|
77
82
|
# usd_reserved: '1.02, usd_available: '6952.05', usd_balance: '6953.07',
|
78
83
|
# fee: '0.4000'
|
79
84
|
# }
|
80
|
-
def
|
81
|
-
BalanceSummary.new(
|
85
|
+
def balance_summary_parser(balances)
|
86
|
+
BalanceSummary.new(
|
87
|
+
balance_parser(balances, currency_pair[:base]),
|
88
|
+
balance_parser(balances, currency_pair[:quote]),
|
89
|
+
balances[:fee].to_d
|
90
|
+
)
|
82
91
|
end
|
83
92
|
|
84
|
-
def
|
93
|
+
def balance_parser(balances, currency)
|
85
94
|
Balance.new(
|
86
95
|
balances["#{currency}_balance".to_sym].to_d,
|
87
96
|
balances["#{currency}_reserved".to_sym].to_d,
|
@@ -94,42 +103,50 @@ class BitstampApiWrapper < ApiWrapper
|
|
94
103
|
# bids: [['124.55', '1.58057006'], ['124.40', '14.91779125']],
|
95
104
|
# asks: [['124.56', '0.81888247'], ['124.57', '0.81078911']]
|
96
105
|
# }
|
97
|
-
def
|
106
|
+
def order_book_parser(book)
|
98
107
|
OrderBook.new(book[:timestamp].to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
|
99
108
|
end
|
100
109
|
|
101
|
-
def
|
110
|
+
def order_is_done?(order)
|
102
111
|
order.nil?
|
103
112
|
end
|
104
113
|
|
105
|
-
# <Bitstamp::Order @id=76, @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>
|
106
|
-
def
|
107
|
-
type = order.type
|
114
|
+
# <Bitstamp::Order @id='76', @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>
|
115
|
+
def order_parser(order)
|
116
|
+
type = order.type == '0' ? :buy : :sell
|
108
117
|
Order.new(order.id.to_s, type, order.price.to_d, order.amount.to_d, order.datetime.to_datetime.to_i, order)
|
109
118
|
end
|
110
119
|
|
111
|
-
def
|
120
|
+
def order_summary_parser(orders)
|
112
121
|
orders.map { |order| OrderSummary.new(order[0].to_d, order[1].to_d) }
|
113
122
|
end
|
114
123
|
|
115
|
-
# <Bitstamp::Transactions: @tid=1469074, @price='126.95', @amount='1.10000000', @date='1380648951'>
|
116
|
-
def
|
117
|
-
Transaction.new(transaction.tid, transaction.price.to_d, transaction.amount.to_d, transaction.date.to_i)
|
124
|
+
# <Bitstamp::Transactions: @tid='1469074', @price='126.95', @amount='1.10000000', @date='1380648951'>
|
125
|
+
def transaction_parser(transaction)
|
126
|
+
Transaction.new(transaction.tid, transaction.price.to_d, transaction.amount.to_d, transaction.date.to_i, transaction)
|
118
127
|
end
|
119
128
|
|
120
129
|
# <Bitstamp::UserTransaction:
|
121
130
|
# @usd='-373.51', @btc='3.00781124', @btc_usd='124.18', @order_id=7623942, @fee='1.50', @type=2, @id=1444404,
|
122
131
|
# @datetime='2013-09-26 13:28:55'
|
123
132
|
# >
|
124
|
-
def
|
133
|
+
def user_transaction_parser(user_transaction)
|
125
134
|
UserTransaction.new(
|
126
135
|
user_transaction.order_id,
|
127
|
-
user_transaction.
|
128
|
-
user_transaction.
|
129
|
-
user_transaction.
|
136
|
+
user_transaction.send(quote).to_d,
|
137
|
+
user_transaction.send(base).to_d,
|
138
|
+
user_transaction.send(base_quote).to_d,
|
130
139
|
user_transaction.fee.to_d,
|
131
|
-
user_transaction.type,
|
132
|
-
Time.
|
140
|
+
user_transaction.type.to_i,
|
141
|
+
Time.parse(user_transaction.datetime).to_i
|
133
142
|
)
|
134
143
|
end
|
144
|
+
|
145
|
+
def currency_pair(order_book = '')
|
146
|
+
@currency_pair ||= {
|
147
|
+
name: order_book,
|
148
|
+
base: order_book.slice(0..2),
|
149
|
+
quote: order_book.slice(3..5)
|
150
|
+
}
|
151
|
+
end
|
135
152
|
end
|