bitex_bot 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|