rbtc_arbitrage 0.0.2 → 0.1.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/.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:
|