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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
- metadata.gz: e874d7529495e986f3fbbc823f984fed0e20c58b
4
- data.tar.gz: def649cf14a6228879ff94970e93ae5b6bd80dbf
3
+ metadata.gz: 8b73f2bb929386db4483f985d6e4ad1e1e630c0f
4
+ data.tar.gz: 09877353240e3fad0adaa5a14f7a9d9b15951f76
5
5
  !binary "U0hBNTEy":
6
- metadata.gz: 676c016c98bbb4ae2b982c5bdbb67be81def5f671f2bba8d0c26c3b496deeb1e2fed0f121b8ec88b32dd628206321141e63c742e2a7fbfff5dbe000cf562fdf2
7
- data.tar.gz: fd15694a96a4cd93abe39768988f2ef444671deadeccb4cb4e36202473b6e93993e4e674bc0f22435a5ff979278faa42e7498b2891e3b8afb6d8841ffa2ccd5a
6
+ metadata.gz: 3ea1ba33fd9e0cefc968e9af9c4a96b71af16d0ae2d8a8952b9928ba065f83f5f489576e902b20c2b55e2658f446b089fadf16d558e7a3574e52e0dc0d20d422
7
+ data.tar.gz: 3267b9bf783cec825d6564b86910ee67715ab5c6aeca56d1fb3d9a90afe1d0a0e17c7f54659495a36f2188e4c92bd67220d1e1c95267533c7e17efc582810cc2
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
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
@@ -1,8 +1,7 @@
1
- require "rbtc_arbitrage/version"
2
1
  require 'thor'
3
- require 'rbtc_arbitrage/cli'
4
2
  require 'mtgox'
5
3
  require 'bitstamp'
4
+ Dir[File.expand_path('../rbtc_arbitrage/*', __FILE__)].each { |f| require f }
6
5
 
7
6
  module RbtcArbitrage
8
7
  end
@@ -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
- @stamp = {}; @mtgox = {}
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
@@ -1,3 +1,3 @@
1
1
  module RbtcArbitrage
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
@@ -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.2
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-08 00:00:00.000000000 Z
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: