rbtc_arbitrage 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Rakefile +9 -0
- data/lib/rbtc_arbitrage.rb +1 -2
- data/lib/rbtc_arbitrage/cli.rb +3 -88
- data/lib/rbtc_arbitrage/trader.rb +107 -0
- data/lib/rbtc_arbitrage/version.rb +1 -1
- data/spec/spec_helper.rb +19 -0
- data/spec/trader_spec.rb +40 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b73f2bb929386db4483f985d6e4ad1e1e630c0f
|
4
|
+
data.tar.gz: 09877353240e3fad0adaa5a14f7a9d9b15951f76
|
5
5
|
!binary "U0hBNTEy":
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ea1ba33fd9e0cefc968e9af9c4a96b71af16d0ae2d8a8952b9928ba065f83f5f489576e902b20c2b55e2658f446b089fadf16d558e7a3574e52e0dc0d20d422
|
7
|
+
data.tar.gz: 3267b9bf783cec825d6564b86910ee67715ab5c6aeca56d1fb3d9a90afe1d0a0e17c7f54659495a36f2188e4c92bd67220d1e1c95267533c7e17efc582810cc2
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Rakefile
CHANGED
@@ -1 +1,10 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc "Run specs"
|
5
|
+
RSpec::Core::RakeTask.new do |t|
|
6
|
+
# t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
7
|
+
# Put spec opts in a file named .rspec in root
|
8
|
+
end
|
9
|
+
|
10
|
+
task default: :spec
|
data/lib/rbtc_arbitrage.rb
CHANGED
data/lib/rbtc_arbitrage/cli.rb
CHANGED
@@ -1,101 +1,16 @@
|
|
1
1
|
module RbtcArbitrage
|
2
2
|
class CLI < Thor
|
3
|
-
attr_accessor :stamp, :mtgox
|
4
3
|
|
5
4
|
desc "arbitrage", "Get information about the current arbitrage levels."
|
6
5
|
option :live, type: :boolean, default: false, desc: "Execute live trades."
|
7
6
|
option :cutoff, type: :numeric, default: 2, desc: "The minimum profit level required to execute a trade."
|
8
7
|
option :volume, type: :numeric, default: 0.01, desc: "The amount of bitcoins to trade per transaction."
|
8
|
+
option :verbose, type: :boolean, default: true, desc: "Whether you wish to log information."
|
9
9
|
def trade
|
10
|
-
|
11
|
-
|
12
|
-
if options[:live]
|
13
|
-
validate_env
|
14
|
-
get_balance
|
15
|
-
end
|
16
|
-
|
17
|
-
# puts "#{Time.now.strftime("%m/%d/%Y at %I:%M%p")}"
|
18
|
-
# puts "Retrieving market information and balances"
|
19
|
-
amount_to_buy = options[:volume]
|
20
|
-
stamp[:price] = Bitstamp.ticker.ask.to_f
|
21
|
-
mtgox[:price] = MtGox.ticker.buy
|
22
|
-
prices = [stamp[:price], mtgox[:price]]
|
23
|
-
paid = prices.min * 1.005 * amount_to_buy
|
24
|
-
received = prices.max * 0.994 * amount_to_buy
|
25
|
-
|
26
|
-
puts "Bitstamp: $#{stamp[:price].round(2)}"
|
27
|
-
puts "MtGox: $#{mtgox[:price].round(2)}"
|
28
|
-
lower_ex, higher_ex = stamp[:price] < mtgox[:price] ? [Bitstamp, MtGox] : [MtGox, Bitstamp]
|
29
|
-
puts "buying #{amount_to_buy} btc from #{lower_ex} for $#{paid.round(2)}"
|
30
|
-
puts "selling #{amount_to_buy} btc on #{higher_ex} for $#{received.round(2)}"
|
31
|
-
percent = ((received/paid - 1) * 100).round(2)
|
32
|
-
puts "profit: $#{(received - paid).round(2)} (#{percent.round(2)}%)"
|
33
|
-
|
34
|
-
if options[:cutoff] > percent
|
35
|
-
puts "Exiting because real profit (#{percent.round(2)}%) is less than cutoff (#{options[:cutoff].round(2)}%)"
|
36
|
-
return 0
|
37
|
-
end
|
38
|
-
|
39
|
-
if options[:live]
|
40
|
-
puts "Balances:"
|
41
|
-
puts "stamp usd: $#{stamp[:usd].round(2)} btc: #{stamp[:usd].round(2)}"
|
42
|
-
puts "mtgox usd: $#{mtgox[:usd].round(2)} btc: #{mtgox[:usd].round(2)}"
|
43
|
-
if stamp[:price] < mtgox[:price]
|
44
|
-
if paid > stamp[:usd] || amount_to_buy > mtgox[:btc]
|
45
|
-
puts "Not enough funds. Exiting."
|
46
|
-
else
|
47
|
-
puts "Trading live!"
|
48
|
-
Bitstamp.orders.buy amount_to_buy, stamp[:price] + 0.001
|
49
|
-
MtGox.sell! amount_to_buy, :market
|
50
|
-
Bitstamp.transfer amount_to_buy, ENV['MTGOX_ADDRESS']
|
51
|
-
end
|
52
|
-
else
|
53
|
-
if paid > mtgox[:usd] || amount_to_buy > stamp[:btc]
|
54
|
-
puts "Not enough funds. Exiting."
|
55
|
-
else
|
56
|
-
puts "Trading live!"
|
57
|
-
MtGox.buy! amount_to_buy, :market
|
58
|
-
Bitstamp.orders.sell amount_to_buy, stamp[:price] - 0.001
|
59
|
-
MtGox.withdraw amount_to_buy, ENV['BITSTAMP_ADDRESS']
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
0
|
10
|
+
RbtcArbitrage::Trader.new(options).trade
|
64
11
|
end
|
65
12
|
|
66
13
|
default_task :trade
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def validate_env
|
71
|
-
["KEY", "SECRET", "ADDRESS"].each do |suffix|
|
72
|
-
["MTGOX", "BITSTAMP"].each do |prefix|
|
73
|
-
key = "#{prefix}_#{suffix}"
|
74
|
-
if ENV[key].blank?
|
75
|
-
puts "Exiting because missing required ENV variable $#{key}."
|
76
|
-
return 0
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
MtGox.configure do |config|
|
82
|
-
config.key = ENV["MTGOX_KEY"]
|
83
|
-
config.secret = ENV["MTGOX_SECRET"]
|
84
|
-
end
|
85
|
-
|
86
|
-
Bitstamp.setup do |config|
|
87
|
-
config.key = ENV["BITSTAMP_KEY"]
|
88
|
-
config.secret = ENV["BITSTAMP_SECRET"]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def get_balance
|
93
|
-
balances = MtGox.balance
|
94
|
-
@mtgox[:btc] = balances[0].amount.to_f
|
95
|
-
@mtgox[:usd] = balances[1].amount.to_f
|
96
|
-
balances = Bitstamp.balance if options[:live]
|
97
|
-
@stamp[:usd] = balances["usd_available"].to_f
|
98
|
-
@stamp[:btc] = balances["btc_available"].to_f
|
99
|
-
end
|
14
|
+
|
100
15
|
end
|
101
16
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
class Trader
|
3
|
+
attr_accessor :stamp, :mtgox, :paid, :received, :options, :percent, :amount_to_buy
|
4
|
+
|
5
|
+
def initialize options={}
|
6
|
+
@stamp = {}
|
7
|
+
@mtgox = {}
|
8
|
+
@options = options
|
9
|
+
@options[:volume] ||= 0.01
|
10
|
+
@options[:cutoff] ||= 2
|
11
|
+
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def trade
|
16
|
+
fetch_prices
|
17
|
+
log_info if options[:verbose]
|
18
|
+
|
19
|
+
if options[:cutoff] > percent
|
20
|
+
raise SecurityError, "Exiting because real profit (#{percent.round(2)}%) is less than cutoff (#{options[:cutoff].round(2)}%)"
|
21
|
+
end
|
22
|
+
|
23
|
+
execute_trade if options[:live]
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute_trade
|
29
|
+
validate_env
|
30
|
+
get_balance
|
31
|
+
raise SecurityError, "--live flag is false. Not executing trade." unless options[:live]
|
32
|
+
if options[:verbose]
|
33
|
+
puts "Balances:"
|
34
|
+
puts "stamp usd: $#{stamp[:usd].round(2)} btc: #{stamp[:usd].round(2)}"
|
35
|
+
puts "mtgox usd: $#{mtgox[:usd].round(2)} btc: #{mtgox[:usd].round(2)}"
|
36
|
+
end
|
37
|
+
if stamp[:price] < mtgox[:price]
|
38
|
+
if paid > stamp[:usd] || amount_to_buy > mtgox[:btc]
|
39
|
+
raise SecurityError, "Not enough funds. Exiting."
|
40
|
+
else
|
41
|
+
puts "Trading live!" if options[:verbose]
|
42
|
+
Bitstamp.orders.buy amount_to_buy, stamp[:price] + 0.001
|
43
|
+
MtGox.sell! amount_to_buy, :market
|
44
|
+
Bitstamp.transfer amount_to_buy, ENV['MTGOX_ADDRESS']
|
45
|
+
end
|
46
|
+
else
|
47
|
+
if paid > mtgox[:usd] || amount_to_buy > stamp[:btc]
|
48
|
+
raise SecurityError, "Not enough funds. Exiting."
|
49
|
+
else
|
50
|
+
puts "Trading live!" if options[:verbose]
|
51
|
+
MtGox.buy! amount_to_buy, :market
|
52
|
+
Bitstamp.orders.sell amount_to_buy, stamp[:price] - 0.001
|
53
|
+
MtGox.withdraw amount_to_buy, ENV['BITSTAMP_ADDRESS']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def fetch_prices
|
59
|
+
@amount_to_buy = options[:volume]
|
60
|
+
stamp[:price] = Bitstamp.ticker.ask.to_f
|
61
|
+
mtgox[:price] = MtGox.ticker.buy
|
62
|
+
prices = [stamp[:price], mtgox[:price]]
|
63
|
+
@paid = prices.min * 1.005 * amount_to_buy
|
64
|
+
@received = prices.max * 0.994 * amount_to_buy
|
65
|
+
@percent = ((received/paid - 1) * 100).round(2)
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_info
|
69
|
+
puts "Bitstamp: $#{stamp[:price].round(2)}"
|
70
|
+
puts "MtGox: $#{mtgox[:price].round(2)}"
|
71
|
+
lower_ex, higher_ex = stamp[:price] < mtgox[:price] ? %w{Bitstamp, MtGox} : %w{MtGox, Bitstamp}
|
72
|
+
puts "buying #{amount_to_buy} btc from #{lower_ex} for $#{paid.round(2)}"
|
73
|
+
puts "selling #{amount_to_buy} btc on #{higher_ex} for $#{received.round(2)}"
|
74
|
+
puts "profit: $#{(received - paid).round(2)} (#{percent.round(2)}%)"
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_env
|
78
|
+
["KEY", "SECRET", "ADDRESS"].each do |suffix|
|
79
|
+
["MTGOX", "BITSTAMP"].each do |prefix|
|
80
|
+
key = "#{prefix}_#{suffix}"
|
81
|
+
if ENV[key].blank?
|
82
|
+
raise ArgumentError, "Exiting because missing required ENV variable $#{key}."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
MtGox.configure do |config|
|
88
|
+
config.key = ENV["MTGOX_KEY"]
|
89
|
+
config.secret = ENV["MTGOX_SECRET"]
|
90
|
+
end
|
91
|
+
|
92
|
+
Bitstamp.setup do |config|
|
93
|
+
config.key = ENV["BITSTAMP_KEY"]
|
94
|
+
config.secret = ENV["BITSTAMP_SECRET"]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_balance
|
99
|
+
balances = MtGox.balance
|
100
|
+
@mtgox[:btc] = balances[0].amount.to_f
|
101
|
+
@mtgox[:usd] = balances[1].amount.to_f
|
102
|
+
balances = Bitstamp.balance if options[:live]
|
103
|
+
@stamp[:usd] = balances["usd_available"].to_f
|
104
|
+
@stamp[:btc] = balances["btc_available"].to_f
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require 'rbtc_arbitrage'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
config.filter_run :focus
|
13
|
+
|
14
|
+
# Run specs in random order to surface order dependencies. If you find an
|
15
|
+
# order dependency and want to debug it, you can fix the order by providing
|
16
|
+
# the seed, which is printed after each run.
|
17
|
+
# --seed 1234
|
18
|
+
config.order = 'random'
|
19
|
+
end
|
data/spec/trader_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe RbtcArbitrage::Trader do
|
3
|
+
before :each do
|
4
|
+
@trader = RbtcArbitrage::Trader.new
|
5
|
+
|
6
|
+
#clear env variables
|
7
|
+
["KEY", "SECRET", "ADDRESS"].each do |suffix|
|
8
|
+
["MTGOX", "BITSTAMP"].each do |prefix|
|
9
|
+
key = "#{prefix}_#{suffix}"
|
10
|
+
ENV[key] = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#validate_env" do
|
16
|
+
it "should raise errors when missing env variable" do
|
17
|
+
["KEY", "SECRET", "ADDRESS"].each do |suffix|
|
18
|
+
["MTGOX", "BITSTAMP"].each do |prefix|
|
19
|
+
key = "#{prefix}_#{suffix}"
|
20
|
+
expect { @trader.validate_env }.to raise_error(ArgumentError, "Exiting because missing required ENV variable $#{key}.")
|
21
|
+
ENV[key] = "some value"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#fetch_prices" do
|
28
|
+
it "gets the right price set" do
|
29
|
+
stamp_price = Bitstamp.ticker.ask.to_f
|
30
|
+
mtgox_price = MtGox.ticker.buy
|
31
|
+
|
32
|
+
@trader.fetch_prices
|
33
|
+
|
34
|
+
#allow for recent price changes
|
35
|
+
@trader.mtgox[:price].should be_within(0.02).of(mtgox_price)
|
36
|
+
@trader.stamp[:price].should be_within(0.02).of(stamp_price)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbtc_arbitrage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hank Stoever
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -117,6 +117,8 @@ extensions: []
|
|
117
117
|
extra_rdoc_files: []
|
118
118
|
files:
|
119
119
|
- .gitignore
|
120
|
+
- .rspec
|
121
|
+
- .travis.yml
|
120
122
|
- Gemfile
|
121
123
|
- LICENSE.txt
|
122
124
|
- README.md
|
@@ -124,8 +126,11 @@ files:
|
|
124
126
|
- bin/rbtc
|
125
127
|
- lib/rbtc_arbitrage.rb
|
126
128
|
- lib/rbtc_arbitrage/cli.rb
|
129
|
+
- lib/rbtc_arbitrage/trader.rb
|
127
130
|
- lib/rbtc_arbitrage/version.rb
|
128
131
|
- rbtc_arbitrage.gemspec
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/trader_spec.rb
|
129
134
|
homepage: ''
|
130
135
|
licenses:
|
131
136
|
- MIT
|
@@ -150,5 +155,7 @@ rubygems_version: 2.0.3
|
|
150
155
|
signing_key:
|
151
156
|
specification_version: 4
|
152
157
|
summary: A gem for conducting arbitrage with Bitcoin.
|
153
|
-
test_files:
|
158
|
+
test_files:
|
159
|
+
- spec/spec_helper.rb
|
160
|
+
- spec/trader_spec.rb
|
154
161
|
has_rdoc:
|