bitex_bot 0.1.6 → 0.2.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.
- data/bitex_bot.gemspec +2 -0
- data/lib/bitex_bot/database.rb +4 -4
- data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +65 -0
- data/lib/bitex_bot/models/closing_flow.rb +8 -8
- data/lib/bitex_bot/models/itbit_api_wrapper.rb +51 -0
- data/lib/bitex_bot/robot.rb +12 -12
- data/lib/bitex_bot/version.rb +1 -1
- data/lib/bitex_bot.rb +2 -0
- data/settings.yml.sample +16 -0
- data/spec/models/buy_closing_flow_spec.rb +3 -3
- data/spec/models/robot_spec.rb +7 -3
- data/spec/models/sell_closing_flow_spec.rb +3 -3
- data/spec/spec_helper.rb +2 -0
- metadata +36 -2
data/bitex_bot.gemspec
CHANGED
@@ -27,7 +27,9 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency "sqlite3"
|
28
28
|
spec.add_dependency "bitstamp"
|
29
29
|
spec.add_dependency "bitex", "0.1.4"
|
30
|
+
spec.add_dependency "itbit", "0.0.2"
|
30
31
|
spec.add_dependency "mail"
|
32
|
+
spec.add_dependency "hashie"
|
31
33
|
|
32
34
|
spec.add_development_dependency "bundler", "~> 1.3"
|
33
35
|
spec.add_development_dependency "rake"
|
data/lib/bitex_bot/database.rb
CHANGED
@@ -73,7 +73,7 @@ module BitexBot
|
|
73
73
|
t.belongs_to :closing_flow
|
74
74
|
t.decimal :amount, precision: 30, scale: 15
|
75
75
|
t.decimal :quantity, precision: 30, scale: 15
|
76
|
-
t.
|
76
|
+
t.string :order_id
|
77
77
|
t.timestamps
|
78
78
|
end
|
79
79
|
add_index :close_buys, :order_id
|
@@ -82,7 +82,7 @@ module BitexBot
|
|
82
82
|
t.belongs_to :closing_flow
|
83
83
|
t.decimal :amount, precision: 30, scale: 15
|
84
84
|
t.decimal :quantity, precision: 30, scale: 15
|
85
|
-
t.
|
85
|
+
t.string :order_id
|
86
86
|
t.timestamps
|
87
87
|
end
|
88
88
|
add_index :close_sells, :order_id
|
@@ -90,8 +90,8 @@ module BitexBot
|
|
90
90
|
|
91
91
|
unless ActiveRecord::Base.connection.table_exists?('stores')
|
92
92
|
create_table "stores", force: true do |t|
|
93
|
-
t.decimal "
|
94
|
-
t.decimal "
|
93
|
+
t.decimal "taker_usd", precision: 20, scale: 8
|
94
|
+
t.decimal "taker_btc", precision: 20, scale: 8
|
95
95
|
t.boolean "hold", default: false
|
96
96
|
t.text "log"
|
97
97
|
t.decimal "usd_stop", precision: 20, scale: 8
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class BitstampApiWrapper
|
2
|
+
def self.setup(settings)
|
3
|
+
Bitstamp.setup do |config|
|
4
|
+
config.key = settings.bitstamp.key
|
5
|
+
config.secret = settings.bitstamp.secret
|
6
|
+
config.client_id = settings.bitstamp.client_id.to_s
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
#{
|
11
|
+
# tid:i,
|
12
|
+
# date: (i+1).seconds.ago.to_i.to_s,
|
13
|
+
# price: price.to_s,
|
14
|
+
# amount: amount.to_s
|
15
|
+
#}
|
16
|
+
def self.transactions
|
17
|
+
Bitstamp.transactions
|
18
|
+
end
|
19
|
+
|
20
|
+
# { 'timestamp' => DateTime.now.to_i.to_s,
|
21
|
+
# 'bids' =>
|
22
|
+
# [['30', '3'], ['25', '2'], ['20', '1.5'], ['15', '4'], ['10', '5']],
|
23
|
+
# 'asks' =>
|
24
|
+
# [['10', '2'], ['15', '3'], ['20', '1.5'], ['25', '3'], ['30', '3']]
|
25
|
+
# }
|
26
|
+
def self.order_book
|
27
|
+
Bitstamp.order_book
|
28
|
+
end
|
29
|
+
|
30
|
+
# {"btc_balance"=> "10.0", "btc_reserved"=> "0", "btc_available"=> "10.0",
|
31
|
+
# "usd_balance"=> "100.0", "usd_reserved"=>"0", "usd_available"=> "100.0",
|
32
|
+
# "fee"=> "0.5000"}
|
33
|
+
def self.balance
|
34
|
+
Bitstamp.balance
|
35
|
+
end
|
36
|
+
|
37
|
+
# ask = double(amount: args[:amount], price: args[:price],
|
38
|
+
# type: 1, id: remote_id, datetime: DateTime.now.to_s)
|
39
|
+
# ask.stub(:cancel!) do
|
40
|
+
def self.orders
|
41
|
+
Bitstamp.orders.all
|
42
|
+
end
|
43
|
+
|
44
|
+
# double(usd: (usd * ratio).to_s, btc: (btc * ratio).to_s,
|
45
|
+
# btc_usd: o.price.to_s, order_id: o.id, fee: "0.5", type: 2,
|
46
|
+
# datetime: DateTime.now.to_s)
|
47
|
+
def self.user_transactions
|
48
|
+
Bitstamp.user_transactions.all
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.order_is_done?(order)
|
52
|
+
order.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.amount_and_quantity(order_id, transactions)
|
56
|
+
closes = transactions.select{|t| t.order_id.to_s == order_id}
|
57
|
+
amount = closes.collect{|x| x.usd.to_d }.sum.abs
|
58
|
+
quantity = closes.collect{|x| x.btc.to_d }.sum.abs
|
59
|
+
[amount, quantity]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.place_order(type, price, quantity)
|
63
|
+
Bitstamp.orders.send(type, amount: quantity, price: price)
|
64
|
+
end
|
65
|
+
end
|
@@ -34,8 +34,8 @@ module BitexBot
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def create_order_and_close_position(quantity, price)
|
37
|
-
order =
|
38
|
-
|
37
|
+
order = BitexBot::Robot.taker.place_order(
|
38
|
+
order_method, price.round(2), quantity.round(4))
|
39
39
|
if order.nil? || order.id.nil?
|
40
40
|
Robot.logger.error("Closing: Error on #{order_method} for "\
|
41
41
|
"#{self.class.name} ##{id} #{quantity} BTC @ $#{price}."\
|
@@ -44,7 +44,7 @@ module BitexBot
|
|
44
44
|
end
|
45
45
|
Robot.logger.info("Closing: Going to #{order_method} ##{order.id} for"\
|
46
46
|
"#{self.class.name} ##{id} #{quantity} BTC @ $#{price}")
|
47
|
-
close_positions.create!(order_id: order.id
|
47
|
+
close_positions.create!(order_id: order.id)
|
48
48
|
end
|
49
49
|
|
50
50
|
def sync_closed_positions(orders, transactions)
|
@@ -57,14 +57,14 @@ module BitexBot
|
|
57
57
|
return
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
order_id = latest_close.order_id.to_s
|
61
|
+
order = orders.find{|x| x.id.to_s == order_id }
|
62
|
+
|
62
63
|
# When ask is nil it means the other exchange is done executing it
|
63
64
|
# so we can now have a look of all the sales that were spawned from it.
|
64
65
|
if order.nil?
|
65
|
-
|
66
|
-
|
67
|
-
latest_close.quantity = closes.collect{|x| x.btc.to_d }.sum.abs
|
66
|
+
latest_close.amount, latest_close.quantity =
|
67
|
+
BitexBot::Robot.taker.amount_and_quantity(order_id, transactions)
|
68
68
|
latest_close.save!
|
69
69
|
|
70
70
|
next_price, next_quantity = get_next_price_and_quantity
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class ItbitApiWrapper
|
2
|
+
def self.setup(settings)
|
3
|
+
Itbit.client_key = settings.itbit.client_key
|
4
|
+
Itbit.secret = settings.itbit.secret
|
5
|
+
Itbit.user_id = settings.itbit.user_id
|
6
|
+
Itbit.default_wallet_id = settings.itbit.default_wallet_id
|
7
|
+
Itbit.sandbox = settings.sandbox
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.transactions
|
11
|
+
Itbit::XBTUSDMarketData.trades.collect{|t| Hashie::Mash.new(t) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.order_book
|
15
|
+
Itbit::XBTUSDMarketData.orders.stringify_keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.balance
|
19
|
+
balances = Itbit::Wallet.all
|
20
|
+
.find{|i| i[:id] == Itbit.default_wallet_id }[:balances]
|
21
|
+
usd = balances.find{|x| x[:currency] == :usd }
|
22
|
+
btc = balances.find{|x| x[:currency] == :xbt }
|
23
|
+
{ "btc_balance" => btc[:total_balance],
|
24
|
+
"btc_reserved" => btc[:total_balance] - btc[:available_balance],
|
25
|
+
"btc_available" => btc[:available_balance],
|
26
|
+
"usd_balance" => usd[:total_balance],
|
27
|
+
"usd_reserved" => usd[:total_balance] - usd[:available_balance],
|
28
|
+
"usd_available" => usd[:available_balance],
|
29
|
+
"fee" => 0.5
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.orders
|
34
|
+
Itbit::Order.all(status: :open)
|
35
|
+
end
|
36
|
+
|
37
|
+
# We don't need to fetch the list of transactions
|
38
|
+
# for itbit since we wont actually use them later.
|
39
|
+
def self.user_transactions
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.amount_and_quantity(order_id, transactions)
|
44
|
+
order = Itbit::Order.find(order_id)
|
45
|
+
[order.volume_weighted_average_price * order.amount_filled, order.amount_filled]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.place_order(type, price, quantity)
|
49
|
+
Itbit::Order.create!(type, :xbtusd, quantity, price)
|
50
|
+
end
|
51
|
+
end
|
data/lib/bitex_bot/robot.rb
CHANGED
@@ -13,6 +13,9 @@ module BitexBot
|
|
13
13
|
cattr_accessor :graceful_shutdown
|
14
14
|
cattr_accessor :cooldown_until
|
15
15
|
cattr_accessor :test_mode
|
16
|
+
cattr_accessor :taker do
|
17
|
+
Settings.taker == 'itbit' ? ItbitApiWrapper : BitstampApiWrapper
|
18
|
+
end
|
16
19
|
cattr_accessor :logger do
|
17
20
|
Logger.new(Settings.log.try(:file) || STDOUT, 10, 10240000).tap do |l|
|
18
21
|
l.level = Logger.const_get(Settings.log.level.upcase)
|
@@ -45,11 +48,8 @@ module BitexBot
|
|
45
48
|
|
46
49
|
def self.setup
|
47
50
|
Bitex.api_key = Settings.bitex
|
48
|
-
|
49
|
-
|
50
|
-
config.secret = Settings.bitstamp.secret
|
51
|
-
config.client_id = Settings.bitstamp.client_id.to_s
|
52
|
-
end
|
51
|
+
Bitex.sandbox = Settings.sandbox
|
52
|
+
taker.setup(Settings)
|
53
53
|
end
|
54
54
|
|
55
55
|
def self.with_cooldown(&block)
|
@@ -99,8 +99,8 @@ module BitexBot
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def sync_closing_flows
|
102
|
-
orders = with_cooldown{
|
103
|
-
transactions = with_cooldown{
|
102
|
+
orders = with_cooldown{ BitexBot::Robot.taker.orders }
|
103
|
+
transactions = with_cooldown{ BitexBot::Robot.taker.user_transactions }
|
104
104
|
|
105
105
|
[BuyClosingFlow, SellClosingFlow].each do |kind|
|
106
106
|
kind.active.each do |flow|
|
@@ -140,14 +140,14 @@ module BitexBot
|
|
140
140
|
return
|
141
141
|
end
|
142
142
|
|
143
|
-
balances = with_cooldown{
|
143
|
+
balances = with_cooldown{ BitexBot::Robot.taker.balance }
|
144
144
|
profile = Bitex::Profile.get
|
145
145
|
|
146
146
|
total_usd = balances['usd_balance'].to_d + profile[:usd_balance]
|
147
147
|
total_btc = balances['btc_balance'].to_d + profile[:btc_balance]
|
148
148
|
|
149
|
-
store.update_attributes(
|
150
|
-
|
149
|
+
store.update_attributes(taker_usd: balances['usd_balance'],
|
150
|
+
taker_btc: balances['btc_balance'])
|
151
151
|
|
152
152
|
if store.last_warning.nil? || store.last_warning < 30.minutes.ago
|
153
153
|
if store.usd_warning && total_usd <= store.usd_warning
|
@@ -172,8 +172,8 @@ module BitexBot
|
|
172
172
|
return
|
173
173
|
end
|
174
174
|
|
175
|
-
order_book = with_cooldown{
|
176
|
-
transactions = with_cooldown{
|
175
|
+
order_book = with_cooldown{ BitexBot::Robot.taker.order_book }
|
176
|
+
transactions = with_cooldown{ BitexBot::Robot.taker.transactions }
|
177
177
|
|
178
178
|
unless recent_buying
|
179
179
|
BuyOpeningFlow.create_for_market(
|
data/lib/bitex_bot/version.rb
CHANGED
data/lib/bitex_bot.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "bitex_bot/version"
|
2
|
+
require 'hashie'
|
2
3
|
require "active_support"
|
3
4
|
require "active_record"
|
4
5
|
require "active_model"
|
@@ -6,6 +7,7 @@ require "mail"
|
|
6
7
|
require "logger"
|
7
8
|
require "bitex"
|
8
9
|
require "bitstamp"
|
10
|
+
require "itbit"
|
9
11
|
require "bitex_bot/settings"
|
10
12
|
require "bitex_bot/database"
|
11
13
|
require "bitex_bot/models/opening_flow.rb"
|
data/settings.yml.sample
CHANGED
@@ -12,6 +12,14 @@ log:
|
|
12
12
|
# competitive. About 20 seconds is a good number here.
|
13
13
|
time_to_live: 20
|
14
14
|
|
15
|
+
# Use sandbox environments instead of live environments
|
16
|
+
# only available for bitex and itbit
|
17
|
+
sandbox: false
|
18
|
+
|
19
|
+
# Which market to use for taking (we're always makers on bitex)
|
20
|
+
# itbit or bitstamp
|
21
|
+
taker: bitstamp
|
22
|
+
|
15
23
|
# Settings for buying on bitex and selling on bitstamp.
|
16
24
|
buying:
|
17
25
|
# Dollars to spend on each initial bitex bid.
|
@@ -58,6 +66,14 @@ bitstamp:
|
|
58
66
|
secret: YOUR_API_SECRET
|
59
67
|
client_id: YOUR_BITSTAMP_USERNAME
|
60
68
|
|
69
|
+
# These are passed in to the itbit gem:
|
70
|
+
# see https://github.com/bitex-la/itbit for more info.
|
71
|
+
itbit:
|
72
|
+
client_key = 'the-client-key'
|
73
|
+
secret = 'the-secret'
|
74
|
+
user_id = 'the-user-id'
|
75
|
+
default_wallet_id = 'wallet-000'
|
76
|
+
|
61
77
|
# Settings for the ActiveRecord Database to use.
|
62
78
|
# sqlite is just fine. Check this link for more options:
|
63
79
|
# http://apidock.com/rails/ActiveRecord/Base/establish_connection/class
|
@@ -12,7 +12,7 @@ describe BitexBot::BuyClosingFlow do
|
|
12
12
|
flow.btc_profit.should be_nil
|
13
13
|
flow.usd_profit.should be_nil
|
14
14
|
close = flow.close_positions.first
|
15
|
-
close.order_id.should == 1
|
15
|
+
close.order_id.should == '1'
|
16
16
|
close.amount.should be_nil
|
17
17
|
close.quantity.should be_nil
|
18
18
|
end
|
@@ -30,7 +30,7 @@ describe BitexBot::BuyClosingFlow do
|
|
30
30
|
flow.quantity.should == 2.01
|
31
31
|
flow.btc_profit.should be_nil
|
32
32
|
flow.usd_profit.should be_nil
|
33
|
-
close.order_id.should == 1
|
33
|
+
close.order_id.should == '1'
|
34
34
|
close.amount.should be_nil
|
35
35
|
close.quantity.should be_nil
|
36
36
|
end
|
@@ -54,7 +54,7 @@ describe BitexBot::BuyClosingFlow do
|
|
54
54
|
stub_bitstamp_sell
|
55
55
|
flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
|
56
56
|
close = flow.close_positions.first
|
57
|
-
close.order_id.should == 1
|
57
|
+
close.order_id.should == '1'
|
58
58
|
close.amount.should be_nil
|
59
59
|
close.quantity.should be_nil
|
60
60
|
end
|
data/spec/models/robot_spec.rb
CHANGED
@@ -167,10 +167,10 @@ describe BitexBot::Robot do
|
|
167
167
|
end.to change{ Mail::TestMailer.deliveries.count }.by(1)
|
168
168
|
end
|
169
169
|
|
170
|
-
it 'updates
|
170
|
+
it 'updates taker_usd and taker_btc' do
|
171
171
|
bot.trade!
|
172
|
-
bot.store.
|
173
|
-
bot.store.
|
172
|
+
bot.store.taker_usd.should_not be_nil
|
173
|
+
bot.store.taker_btc.should_not be_nil
|
174
174
|
end
|
175
175
|
|
176
176
|
it 'notifies exceptions and sleeps' do
|
@@ -182,4 +182,8 @@ describe BitexBot::Robot do
|
|
182
182
|
end.to change{ Mail::TestMailer.deliveries.count }.by(1)
|
183
183
|
end
|
184
184
|
|
185
|
+
it 'knows how to setup sandbox mode for both gems' do
|
186
|
+
pending
|
187
|
+
fail
|
188
|
+
end
|
185
189
|
end
|
@@ -13,7 +13,7 @@ describe BitexBot::SellClosingFlow do
|
|
13
13
|
flow.btc_profit.should be_nil
|
14
14
|
flow.usd_profit.should be_nil
|
15
15
|
close = flow.close_positions.first
|
16
|
-
close.order_id.should == 1
|
16
|
+
close.order_id.should == '1'
|
17
17
|
close.amount.should be_nil
|
18
18
|
close.quantity.should be_nil
|
19
19
|
end
|
@@ -32,7 +32,7 @@ describe BitexBot::SellClosingFlow do
|
|
32
32
|
flow.amount.should == 604
|
33
33
|
flow.btc_profit.should be_nil
|
34
34
|
flow.usd_profit.should be_nil
|
35
|
-
close.order_id.should == 1
|
35
|
+
close.order_id.should == '1'
|
36
36
|
close.amount.should be_nil
|
37
37
|
close.quantity.should be_nil
|
38
38
|
end
|
@@ -56,7 +56,7 @@ describe BitexBot::SellClosingFlow do
|
|
56
56
|
stub_bitstamp_buy
|
57
57
|
flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
|
58
58
|
close = flow.close_positions.first
|
59
|
-
close.order_id.should == 1
|
59
|
+
close.order_id.should == '1'
|
60
60
|
close.amount.should be_nil
|
61
61
|
close.quantity.should be_nil
|
62
62
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitex_bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-10
|
13
|
+
date: 2014-11-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: settingslogic
|
@@ -108,6 +108,22 @@ dependencies:
|
|
108
108
|
- - '='
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 0.1.4
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: itbit
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - '='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 0.0.2
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - '='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 0.0.2
|
111
127
|
- !ruby/object:Gem::Dependency
|
112
128
|
name: mail
|
113
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +140,22 @@ dependencies:
|
|
124
140
|
- - ! '>='
|
125
141
|
- !ruby/object:Gem::Version
|
126
142
|
version: '0'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: hashie
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
type: :runtime
|
152
|
+
prerelease: false
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
154
|
+
none: false
|
155
|
+
requirements:
|
156
|
+
- - ! '>='
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
127
159
|
- !ruby/object:Gem::Dependency
|
128
160
|
name: bundler
|
129
161
|
requirement: !ruby/object:Gem::Requirement
|
@@ -289,11 +321,13 @@ files:
|
|
289
321
|
- bitex_bot.gemspec
|
290
322
|
- lib/bitex_bot.rb
|
291
323
|
- lib/bitex_bot/database.rb
|
324
|
+
- lib/bitex_bot/models/bitstamp_api_wrapper.rb
|
292
325
|
- lib/bitex_bot/models/buy_closing_flow.rb
|
293
326
|
- lib/bitex_bot/models/buy_opening_flow.rb
|
294
327
|
- lib/bitex_bot/models/close_buy.rb
|
295
328
|
- lib/bitex_bot/models/close_sell.rb
|
296
329
|
- lib/bitex_bot/models/closing_flow.rb
|
330
|
+
- lib/bitex_bot/models/itbit_api_wrapper.rb
|
297
331
|
- lib/bitex_bot/models/open_buy.rb
|
298
332
|
- lib/bitex_bot/models/open_sell.rb
|
299
333
|
- lib/bitex_bot/models/opening_flow.rb
|