bitstamp-rbtc-arbitrage 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require 'active_support/core_ext'
2
+ require 'active_support/inflector'
3
+ require 'active_model'
4
+ require 'curb'
5
+ require 'hmac-sha2'
6
+
7
+ require 'bitstamp/net'
8
+ require 'bitstamp/helper'
9
+ require 'bitstamp/collection'
10
+ require 'bitstamp/model'
11
+
12
+ require 'bitstamp/orders'
13
+ require 'bitstamp/transactions'
14
+ require 'bitstamp/ticker'
15
+
16
+ String.send(:include, ActiveSupport::Inflector)
17
+
18
+ module Bitstamp
19
+ # API Key
20
+ mattr_accessor :key
21
+
22
+ # Bitstamp secret
23
+ mattr_accessor :secret
24
+
25
+ # Bitstamp client ID
26
+ mattr_accessor :client_id
27
+
28
+ # Currency
29
+ mattr_accessor :currency
30
+ @@currency = :usd
31
+
32
+ def self.orders
33
+ self.sanity_check!
34
+
35
+ @@orders ||= Bitstamp::Orders.new
36
+ end
37
+
38
+ def self.user_transactions
39
+ self.sanity_check!
40
+
41
+ @@transactions ||= Bitstamp::UserTransactions.new
42
+ end
43
+
44
+ def self.transactions
45
+ return Bitstamp::Transactions.from_api
46
+ end
47
+
48
+ def self.balance
49
+ self.sanity_check!
50
+
51
+ JSON.parse Bitstamp::Net.post('/balance').body_str
52
+ end
53
+
54
+ def self.ticker
55
+ return Bitstamp::Ticker.from_api
56
+ end
57
+
58
+ def self.order_book
59
+ return JSON.parse Bitstamp::Net.get('/order_book/').body_str
60
+ end
61
+
62
+ def self.setup
63
+ yield self
64
+ end
65
+
66
+ def self.configured?
67
+ self.key && self.secret && self.client_id
68
+ end
69
+
70
+ def self.sanity_check!
71
+ unless configured?
72
+ raise MissingConfigExeception.new("Bitstamp Gem not properly configured")
73
+ end
74
+ end
75
+
76
+ class MissingConfigExeception<Exception;end;
77
+ end
@@ -0,0 +1,30 @@
1
+ module Bitstamp
2
+ class Collection
3
+ attr_accessor :access_token, :module, :name, :model, :path
4
+
5
+ def initialize(api_prefix="/api")
6
+ self.access_token = Bitstamp.key
7
+
8
+ self.module = self.class.to_s.singularize.underscore
9
+ self.name = self.module.split('/').last
10
+ self.model = self.module.camelize.constantize
11
+ self.path = "#{api_prefix}/#{self.name.pluralize}"
12
+ end
13
+
14
+ def all(options = {})
15
+ Bitstamp::Helper.parse_objects! Bitstamp::Net::get(self.path).body_str, self.model
16
+ end
17
+
18
+ def create(options = {})
19
+ Bitstamp::Helper.parse_object! Bitstamp::Net::post(self.path, options).body_str, self.model
20
+ end
21
+
22
+ def find(id, options = {})
23
+ Bitstamp::Helper.parse_object! Bitstamp::Net::get("#{self.path}/#{id}").body_str, self.model
24
+ end
25
+
26
+ def update(id, options = {})
27
+ Bitstamp::Helper.parse_object! Bitstamp::Net::patch("#{self.path}/#{id}", options).body_str, self.model
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module Bitstamp
2
+ module Helper
3
+ def self.parse_objects!(string, klass)
4
+ # If Bitstamp returned nothing (which it does if the results yield empty) 'cast' it to an array
5
+ string = "[]" if string == ""
6
+
7
+ objects = JSON.parse(string)
8
+ objects.collect do |t_json|
9
+ parse_object!(t_json, klass)
10
+ end
11
+ end
12
+
13
+ def self.parse_object!(object, klass)
14
+ object = JSON.parse(object) if object.is_a? String
15
+
16
+ klass.new(object)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module Bitstamp
2
+ class Model
3
+ attr_accessor :error, :message
4
+
5
+ if ActiveModel::VERSION::MAJOR <= 3
6
+ include ActiveModel::Validations
7
+ include ActiveModel::Conversion
8
+ extend ActiveModel::Naming
9
+
10
+ def initialize(attributes = {})
11
+ self.attributes = attributes
12
+ end
13
+ else
14
+ include ActiveModel::Model
15
+ end
16
+
17
+ # Set the attributes based on the given hash
18
+ def attributes=(attributes = {})
19
+ attributes.each do |name, value|
20
+ begin
21
+ send("#{name}=", value)
22
+ rescue NoMethodError => e
23
+ puts "Unable to assign #{name}. No such method."
24
+ end
25
+ end
26
+ end
27
+
28
+ # Returns a hash with the current instance variables
29
+ def attributes
30
+ Hash[instance_variables.map { |name| [name, instance_variable_get(name)] }]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ module Bitstamp
2
+ module Net
3
+ def self.to_uri(path)
4
+ return "https://www.bitstamp.net/api#{path}/"
5
+ end
6
+
7
+ def self.curl(verb, path, options={})
8
+ verb = verb.upcase.to_sym
9
+
10
+ c = Curl::Easy.new(self.to_uri(path))
11
+
12
+ if Bitstamp.configured?
13
+ options[:key] = Bitstamp.key
14
+ options[:nonce] = (Time.now.to_f*10000).to_i.to_s
15
+ options[:signature] = HMAC::SHA256.hexdigest(Bitstamp.secret, options[:nonce]+Bitstamp.client_id.to_s+options[:key]).upcase
16
+ end
17
+
18
+ c.post_body = options.to_query
19
+
20
+ c.http(verb)
21
+
22
+ return c
23
+ end
24
+
25
+ def self.get(path, options={})
26
+ request = self.curl(:GET, path, options)
27
+
28
+ return request
29
+ end
30
+
31
+ def self.post(path, options={})
32
+ request = self.curl(:POST, path, options)
33
+
34
+ return request
35
+ end
36
+
37
+ def self.patch(path, options={})
38
+ request = self.curl(:PATCH, path, options)
39
+
40
+ return request
41
+ end
42
+
43
+ def self.delete(path, options={})
44
+ request = self.curl(:DELETE, path, options)
45
+
46
+ return request
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module Bitstamp
2
+ class Orders < Bitstamp::Collection
3
+ def all(options = {})
4
+ Bitstamp::Helper.parse_objects! Bitstamp::Net::post('/open_orders').body_str, self.model
5
+ end
6
+
7
+ def create(options = {})
8
+ path = (options[:type] == Bitstamp::Order::SELL ? "/sell" : "/buy")
9
+ Bitstamp::Helper.parse_object! Bitstamp::Net::post(path, options).body_str, self.model
10
+ end
11
+
12
+ def sell(options = {})
13
+ options.merge!({type: Bitstamp::Order::SELL})
14
+ self.create options
15
+ end
16
+
17
+ def buy(options = {})
18
+ options.merge!({type: Bitstamp::Order::BUY})
19
+ self.create options
20
+ end
21
+
22
+ def find(order_id)
23
+ all = self.all
24
+ index = all.index {|order| order.id.to_i == order_id}
25
+
26
+ return all[index] if index
27
+ end
28
+ end
29
+
30
+ class Order < Bitstamp::Model
31
+ BUY = 0
32
+ SELL = 1
33
+
34
+ attr_accessor :type, :amount, :price, :id, :datetime
35
+ attr_accessor :error, :message
36
+
37
+ def cancel!
38
+ Bitstamp::Net::post('/cancel_order', {id: self.id}).body_str
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ module Bitstamp
2
+ class Ticker < Bitstamp::Model
3
+ attr_accessor :last, :high, :low, :volume, :bid, :ask, :timestamp, :vwap
4
+
5
+ def self.from_api
6
+ Bitstamp::Helper.parse_object!(Bitstamp::Net.get('/ticker').body_str, self)
7
+ end
8
+
9
+ def self.method_missing method, *args
10
+ ticker = self.from_api
11
+ return ticker.send(method) if ticker.respond_to? method
12
+
13
+ super
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ module Bitstamp
2
+ class UserTransactions < Bitstamp::Collection
3
+ def all(options = {})
4
+ # Default time delta to an hour
5
+ options[:timedelta] = "3600" unless options[:timedelta]
6
+
7
+ Bitstamp::Helper.parse_objects! Bitstamp::Net::post("/user_transactions", options).body_str, self.model
8
+ end
9
+
10
+ def find(order_id)
11
+ all = self.all
12
+ index = all.index {|order| order.id.to_i == order_id}
13
+
14
+ return all[index] if index
15
+ end
16
+
17
+ def create(options = {})
18
+ end
19
+
20
+ def update(options={})
21
+ end
22
+ end
23
+
24
+ class UserTransaction < Bitstamp::Model
25
+ attr_accessor :datetime, :id, :type, :usd, :btc, :fee, :order_id, :btc_usd, :nonce
26
+ end
27
+
28
+ # adding in methods to pull the last public trades list
29
+ class Transactions < Bitstamp::Model
30
+ attr_accessor :date, :price, :tid, :amount
31
+
32
+ def self.from_api
33
+ Bitstamp::Helper.parse_objects! Bitstamp::Net::get("/transactions").body_str, self
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bitstamp do
4
+
5
+ describe :sanity_check! do
6
+ context 'not properly configured' do
7
+ it { -> { Bitstamp.sanity_check! }.should raise_error }
8
+ end
9
+ context 'properly configured' do
10
+ before {
11
+ Bitstamp.setup do |config|
12
+ config.key = 'test'
13
+ config.secret = 'test'
14
+ config.client_id = 'test'
15
+ end
16
+ }
17
+ it { -> { Bitstamp.sanity_check! }.should_not raise_error }
18
+ end
19
+ end
20
+
21
+ describe :orders do
22
+ it { should respond_to :orders }
23
+ end
24
+
25
+ describe :ticket, vcr: {cassette_name: 'bitstamp/ticker'} do
26
+ subject { Bitstamp.ticker }
27
+ it { should be_kind_of Bitstamp::Ticker }
28
+ its(:high) { should == "124.90" }
29
+ its(:last) { should == "124.55" }
30
+ its(:timestamp) { should == "1380237724" }
31
+ its(:bid) { should == "124.55" }
32
+ its(:volume) { should == "7766.46908740" }
33
+ its(:low) { should == "123.00" }
34
+ its(:ask) { should == "124.56" }
35
+ end
36
+
37
+ describe :balance, vcr: {cassette_name: 'bitstamp/balance'} do
38
+ context "configured" do
39
+ subject { Bitstamp.balance }
40
+ before { setup_bitstamp }
41
+ it { should == {"btc_reserved"=>"0", "fee"=>"0.4000", "btc_available"=>"0", "usd_reserved"=>"1.02", "btc_balance"=>"0", "usd_balance"=>"6953.07", "usd_available"=>"6952.05"} }
42
+ end
43
+ context "not configured" do
44
+ it { expect { Bitstamp.balance }.to raise_exception(Bitstamp::MissingConfigExeception, "Bitstamp Gem not properly configured") }
45
+ end
46
+ end
47
+
48
+ describe :order_book, vcr: {cassette_name: 'bitstamp/order_book'} do
49
+ let(:order_book) { Bitstamp.order_book }
50
+ subject { order_book }
51
+ it { should be_kind_of Hash }
52
+ it { should have_key("asks") }
53
+ it { should have_key("bids") }
54
+ it { order_book["asks"].should be_kind_of Array }
55
+ it { order_book["bids"].should be_kind_of Array }
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ class Bitstamp::Coin < Bitstamp::Model;end
4
+ class Bitstamp::Coins < Bitstamp::Collection;end
5
+
6
+ describe Bitstamp::Coins do
7
+ subject { Bitstamp::Coins.new }
8
+ its(:name) { should eq 'coin' }
9
+ its(:module) { should eq "bitstamp/coin" }
10
+ its(:model) { should be Bitstamp::Coin }
11
+ its(:path) { should eq "/api/coins" }
12
+ end
@@ -0,0 +1,63 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.bitstamp.net/api/balance/
6
+ body:
7
+ encoding: US-ASCII
8
+ string: key=BITSTAMP_KEY&nonce=1380237671&signature=67768DD1BBDF45203840CC8688847A2DF5D224CA15B3ABDDC804EA251AC12211
9
+ headers: {}
10
+ response:
11
+ status:
12
+ code: 200
13
+ message: !binary |-
14
+ T0s=
15
+ headers:
16
+ !binary "U2VydmVy":
17
+ - !binary |-
18
+ Y2xvdWRmbGFyZS1uZ2lueA==
19
+ !binary "RGF0ZQ==":
20
+ - !binary |-
21
+ VGh1LCAyNiBTZXAgMjAxMyAyMzoyMToxOCBHTVQ=
22
+ !binary "Q29udGVudC1UeXBl":
23
+ - !binary |-
24
+ YXBwbGljYXRpb24vanNvbg==
25
+ !binary "VHJhbnNmZXItRW5jb2Rpbmc=":
26
+ - !binary |-
27
+ Y2h1bmtlZA==
28
+ !binary "Q29ubmVjdGlvbg==":
29
+ - !binary |-
30
+ a2VlcC1hbGl2ZQ==
31
+ !binary "U2V0LUNvb2tpZQ==":
32
+ - !binary |-
33
+ X19jZmR1aWQ9ZDg2ZTkzYTIxNzY5Y2UxZDQzODc2YzQ1N2ZjNTU3ZmNjMTM4
34
+ MDIzNzY3Nzk1ODsgZXhwaXJlcz1Nb24sIDIzLURlYy0yMDE5IDIzOjUwOjAw
35
+ IEdNVDsgcGF0aD0vOyBkb21haW49LmJpdHN0YW1wLm5ldA==
36
+ !binary "Q29udGVudC1MYW5ndWFnZQ==":
37
+ - !binary |-
38
+ ZW4=
39
+ !binary "RXhwaXJlcw==":
40
+ - !binary |-
41
+ VGh1LCAyNiBTZXAgMjAxMyAyMzoyMToxOCBHTVQ=
42
+ !binary "VmFyeQ==":
43
+ - !binary |-
44
+ QWNjZXB0LUxhbmd1YWdl
45
+ !binary "Q2FjaGUtQ29udHJvbA==":
46
+ - !binary |-
47
+ bWF4LWFnZT0w
48
+ !binary "TGFzdC1Nb2RpZmllZA==":
49
+ - !binary |-
50
+ VGh1LCAyNiBTZXAgMjAxMyAyMzoyMToxOCBHTVQ=
51
+ !binary "Q2YtUmF5":
52
+ - !binary |-
53
+ YjQzYjA4ZjM5ZDgwMDk3
54
+ body:
55
+ encoding: ASCII-8BIT
56
+ string: !binary |-
57
+ eyJidGNfcmVzZXJ2ZWQiOiAiMCIsICJmZWUiOiAiMC40MDAwIiwgImJ0Y19h
58
+ dmFpbGFibGUiOiAiMCIsICJ1c2RfcmVzZXJ2ZWQiOiAiMS4wMiIsICJidGNf
59
+ YmFsYW5jZSI6ICIwIiwgInVzZF9iYWxhbmNlIjogIjY5NTMuMDciLCAidXNk
60
+ X2F2YWlsYWJsZSI6ICI2OTUyLjA1In0=
61
+ http_version:
62
+ recorded_at: Thu, 26 Sep 2013 23:21:18 GMT
63
+ recorded_with: VCR 2.6.0