bitstampede 0.0.1
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +8 -0
- data/LICENSE.md +22 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/bitstampede.gemspec +30 -0
- data/doc/legitimate_concern.png +0 -0
- data/example/balance.rb +5 -0
- data/example/example.rb +48 -0
- data/example/query_orders.rb +5 -0
- data/lib/bitstampede/client.rb +46 -0
- data/lib/bitstampede/entities/balance.rb +21 -0
- data/lib/bitstampede/entities/base.rb +36 -0
- data/lib/bitstampede/entities/order.rb +34 -0
- data/lib/bitstampede/mapper.rb +37 -0
- data/lib/bitstampede/net.rb +51 -0
- data/lib/bitstampede/version.rb +3 -0
- data/lib/bitstampede.rb +12 -0
- data/spec/integration/client_spec.rb +93 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/bitstampede/client_spec.rb +110 -0
- data/spec/unit/bitstampede/entities/balance_spec.rb +48 -0
- data/spec/unit/bitstampede/entities/order_spec.rb +33 -0
- data/spec/unit/bitstampede/mapper_spec.rb +44 -0
- data/spec/unit/bitstampede/net_spec.rb +43 -0
- data/spec/unit/bitstampede_spec.rb +13 -0
- metadata +218 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3@bitstampede --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Josh Adams
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Bitstampede
|
2
|
+
[](https://travis-ci.org/isotope11/bitstampede) [](https://coveralls.io/r/isotope11/bitstampede?branch=master) [](https://codeclimate.com/github/isotope11/bitstampede)
|
3
|
+
|
4
|
+
|
5
|
+
Bitstampede is a gem for accessing the Bitstamp API
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'bitstampede'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install bitstampede
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
First, look at this picture:
|
24
|
+
|
25
|
+

|
26
|
+
|
27
|
+
Second, stop writing api clients that are configured with class ivars ಠ_ಠ
|
28
|
+
|
29
|
+
Third, this stuff:
|
30
|
+
|
31
|
+
## Actual Usage Without Silly Pictures
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
client = Bitstampede::Client.new
|
35
|
+
|
36
|
+
# I am sad for the following, but such is Bitstamp at present :-\
|
37
|
+
client.key = 'YOUR_USER_ID'
|
38
|
+
client.secret = 'YOUR_PASSWORD'
|
39
|
+
|
40
|
+
# Fetch your balance
|
41
|
+
client.balance
|
42
|
+
# => #<Bitstampede::Entities::Balance:0x0000000259f338 @usd_balance=#<BigDecimal:259e898,'0.0',9(9)>, @btc_balance=#<BigDecimal:2726698,'0.0',9(9)>, @usd_reserved=#<BigDecimal:2726328,'0.0',9(9)>, @btc_reserved=#<BigDecimal:2725fb8,'0.0',9(9)>, @usd_available=#<BigDecimal:2725c48,'0.0',9(9)>, @btc_available=#<BigDecimal:27258b0,'0.0',9(9)>, @fee=#<BigDecimal:2725540,'0.0',9(9)>>
|
43
|
+
|
44
|
+
client.orders
|
45
|
+
#=> [
|
46
|
+
#<Bitstampede::Entities::Order:0x000000027302d8 @id=0, @datetime=0, @type=:buy, @price=#<BigDecimal:272f428,'0.0',9(9)>, @amount=#<BigDecimal:272f130,'0.0',9(9)>>
|
47
|
+
]
|
48
|
+
|
49
|
+
# Place a limit order to buy one bitcoin for $100.00 USD
|
50
|
+
client.buy!(BigDecimal('1'), BigDecimal('100'))
|
51
|
+
|
52
|
+
# Place a limit order to sell one bitcoin for $101.00 USD
|
53
|
+
client.sell!(BigDecimal('1'), BigDecimal('101'))
|
54
|
+
|
55
|
+
# Cancel order #1234
|
56
|
+
client.cancel 1234
|
57
|
+
```
|
58
|
+
|
59
|
+
## Examples
|
60
|
+
|
61
|
+
You can run any of the examples in the `example` dir by just executing the script (except `example/example.rb`, which is the base example class). For instance, to see your balance, do the following:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
ruby example/balance.rb
|
65
|
+
```
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
1. Fork it
|
70
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
71
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
72
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
73
|
+
5. Create new Pull Request
|
74
|
+
|
75
|
+
## License
|
76
|
+
|
77
|
+
This software is licensed under [the MIT License.](./LICENSE.md)
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
require 'rspec/core/rake_task'
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
|
13
|
+
task :default => :spec
|
data/bitstampede.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bitstampede/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bitstampede"
|
8
|
+
spec.version = Bitstampede::VERSION
|
9
|
+
spec.authors = ["Josh Adams"]
|
10
|
+
spec.email = ["josh@isotope11.com"]
|
11
|
+
spec.description = %q{This is a client library for the Bitstamp API that supports instantiating multiple clients in the same process.}
|
12
|
+
spec.summary = %q{Fantastic bitstamp library.}
|
13
|
+
spec.homepage = "http://github.com/isotope11/bitstampede"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'httparty'
|
22
|
+
spec.add_dependency 'multi_json'
|
23
|
+
spec.add_dependency 'yajl-ruby'
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", '2.14.0.rc1'
|
28
|
+
spec.add_development_dependency "fakeweb"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
end
|
Binary file
|
data/example/balance.rb
ADDED
data/example/example.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../lib/bitstampede'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
class Example
|
5
|
+
def initialize &block
|
6
|
+
validate_env_vars
|
7
|
+
client = Bitstampede::Client.new
|
8
|
+
client.key = key
|
9
|
+
client.secret = secret
|
10
|
+
output block.call(client).inspect
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def validate_env_vars
|
15
|
+
print_env_var_message if missing_either_var?
|
16
|
+
end
|
17
|
+
|
18
|
+
def missing_either_var?
|
19
|
+
key.nil? || secret.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def key
|
23
|
+
ENV["BITSTAMP_KEY"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def secret
|
27
|
+
ENV["BITSTAMP_SECRET"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_env_var_message
|
31
|
+
output <<-MSG
|
32
|
+
These examples assume that you have two env vars defined:
|
33
|
+
|
34
|
+
BITSTAMP_KEY (which would be your user id)
|
35
|
+
|
36
|
+
and
|
37
|
+
|
38
|
+
BITSTAMP_SECRET (which would be your password)
|
39
|
+
|
40
|
+
You don't appear to. Rectify that, mnkay?
|
41
|
+
MSG
|
42
|
+
exit(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def output(message)
|
46
|
+
STDOUT.puts message
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'net'
|
2
|
+
require_relative 'mapper'
|
3
|
+
require 'bigdecimal/util'
|
4
|
+
|
5
|
+
module Bitstampede
|
6
|
+
class Client
|
7
|
+
attr_accessor :key
|
8
|
+
attr_accessor :secret
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
end
|
12
|
+
|
13
|
+
def balance
|
14
|
+
mapper.map_balance(net.post("balance"))
|
15
|
+
end
|
16
|
+
|
17
|
+
def orders
|
18
|
+
mapper.map_orders(net.post("open_orders"))
|
19
|
+
end
|
20
|
+
|
21
|
+
def buy!(price, amount)
|
22
|
+
trade!("buy", price, amount)
|
23
|
+
end
|
24
|
+
|
25
|
+
def sell!(price, amount)
|
26
|
+
trade!("sell", price, amount)
|
27
|
+
end
|
28
|
+
|
29
|
+
def cancel(id)
|
30
|
+
mapper.map_cancel(net.post("cancel_order", { id: id.to_s }))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def net
|
35
|
+
@net ||= Net.new(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def mapper
|
39
|
+
@mapper ||= Mapper.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def trade!(type, price, amount)
|
43
|
+
mapper.map_order(net.post(type, { price: price.to_digits, amount: amount.to_digits }))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module Bitstampede
|
4
|
+
module Entities
|
5
|
+
class Balance < Base
|
6
|
+
def self.mappings
|
7
|
+
{
|
8
|
+
usd_balance: map_decimal,
|
9
|
+
btc_balance: map_decimal,
|
10
|
+
usd_reserved: map_decimal,
|
11
|
+
btc_reserved: map_decimal,
|
12
|
+
usd_available: map_decimal,
|
13
|
+
btc_available: map_decimal,
|
14
|
+
fee: map_decimal
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
setup_readers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Bitstampede
|
2
|
+
module Entities
|
3
|
+
class Base
|
4
|
+
def self.setup_readers
|
5
|
+
keys.each {|k| attr_reader k.to_sym }
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.keys
|
9
|
+
self.mappings.keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(balance_hash)
|
13
|
+
self.class.keys.each do |key|
|
14
|
+
instance_variable_set("@#{key}", self.class.mappings[key].call(balance_hash[key.to_s].to_s))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
inspect_string = "#<#{self.class}:#{self.object_id} "
|
20
|
+
self.class.keys.each do |key|
|
21
|
+
inspect_string << "#{key}: #{send(key).inspect} "
|
22
|
+
end
|
23
|
+
inspect_string << " >"
|
24
|
+
inspect_string
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.map_int
|
28
|
+
->(val) { val.to_i }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.map_decimal
|
32
|
+
->(val) { BigDecimal(val) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module Bitstampede
|
4
|
+
module Entities
|
5
|
+
class Order < Base
|
6
|
+
class InvalidTypeError < StandardError; end
|
7
|
+
|
8
|
+
def self.map_type
|
9
|
+
->(val) do
|
10
|
+
case val.to_s
|
11
|
+
when '0'
|
12
|
+
:buy
|
13
|
+
when '1'
|
14
|
+
:sell
|
15
|
+
else
|
16
|
+
raise InvalidTypeError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.mappings
|
22
|
+
{
|
23
|
+
id: map_int,
|
24
|
+
datetime: map_int,
|
25
|
+
type: map_type,
|
26
|
+
price: map_decimal,
|
27
|
+
amount: map_decimal
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
setup_readers
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative './entities/balance'
|
2
|
+
require_relative './entities/order'
|
3
|
+
|
4
|
+
module Bitstampede
|
5
|
+
class Mapper
|
6
|
+
def initialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def map_balance(json)
|
10
|
+
Entities::Balance.new(parsed(json))
|
11
|
+
end
|
12
|
+
|
13
|
+
def map_orders(json)
|
14
|
+
parsed(json).map{|o| map_order(o) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def map_order(order)
|
18
|
+
Entities::Order.new(parsed(order))
|
19
|
+
end
|
20
|
+
|
21
|
+
def map_cancel(result)
|
22
|
+
parsed(result) == 'true'
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# Allow passing either a String or anything else in. If it's not a string,
|
27
|
+
# we assume we've already parsed it and just give it back to you. This
|
28
|
+
# allows us to handle things like collections more easily.
|
29
|
+
def parsed(json)
|
30
|
+
if(json.is_a?(String))
|
31
|
+
Bitstampede::Helpers.json_parse(json)
|
32
|
+
else
|
33
|
+
json
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Bitstampede
|
4
|
+
class Net
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
def secret
|
12
|
+
client.secret
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
client.key
|
17
|
+
end
|
18
|
+
|
19
|
+
def post(endpoint, options={})
|
20
|
+
map_response(raw_post(endpoint, options))
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def url_for(endpoint)
|
25
|
+
base_url + endpoint + '/'
|
26
|
+
end
|
27
|
+
|
28
|
+
def base_url
|
29
|
+
'https://www.bitstamp.net/api/'
|
30
|
+
end
|
31
|
+
|
32
|
+
def auth_options
|
33
|
+
{
|
34
|
+
user: key,
|
35
|
+
password: secret
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# For some crazy reason, bitstamp is returning ruby hash strings rather than
|
40
|
+
# JSON objects right now ಠ_ಠ I'm just going to gsub '=>' to ':' to 'solve'
|
41
|
+
# it for now. Not thrilled with this.
|
42
|
+
def map_response(wish_this_were_reliably_json)
|
43
|
+
wish_this_were_reliably_json.gsub('=>', ':')
|
44
|
+
end
|
45
|
+
|
46
|
+
def raw_post(endpoint, options)
|
47
|
+
HTTParty.post(url_for(endpoint), body: options.merge(auth_options)).to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
data/lib/bitstampede.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe "Integrating a client" do
|
4
|
+
subject{ Client.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
subject.secret = '1'
|
8
|
+
subject.key = '2'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "handles #balance" do
|
12
|
+
example_balance = <<-JSON
|
13
|
+
{
|
14
|
+
"usd_balance": "12.34",
|
15
|
+
"btc_balance": "23.45",
|
16
|
+
"usd_reserved": "1.11",
|
17
|
+
"btc_reserved": "2.22",
|
18
|
+
"usd_available": "11.23",
|
19
|
+
"btc_available": "21.23",
|
20
|
+
"fee": "0.5"
|
21
|
+
}
|
22
|
+
JSON
|
23
|
+
|
24
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/balance/", body: example_balance)
|
25
|
+
|
26
|
+
bal = subject.balance
|
27
|
+
expect(bal.usd_balance).to eq(BigDecimal('12.34'))
|
28
|
+
end
|
29
|
+
|
30
|
+
it "handles #orders" do
|
31
|
+
example_orders = <<-JSON
|
32
|
+
[
|
33
|
+
{
|
34
|
+
"id": "1",
|
35
|
+
"datetime": "1234567",
|
36
|
+
"type": 0,
|
37
|
+
"price": "12.34",
|
38
|
+
"amount": "100"
|
39
|
+
}
|
40
|
+
]
|
41
|
+
JSON
|
42
|
+
|
43
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/open_orders/", body: example_orders)
|
44
|
+
|
45
|
+
orders = subject.orders
|
46
|
+
expect(orders[0].type).to eq(:buy)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "handles #buy!" do
|
50
|
+
example_buy_response = <<-JSON
|
51
|
+
{
|
52
|
+
"id": "1",
|
53
|
+
"datetime": "1234567",
|
54
|
+
"type": 0,
|
55
|
+
"price": "12.34",
|
56
|
+
"amount": "100"
|
57
|
+
}
|
58
|
+
JSON
|
59
|
+
|
60
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/buy/", body: example_buy_response)
|
61
|
+
|
62
|
+
buy = subject.buy!(BigDecimal('1'), BigDecimal('100'))
|
63
|
+
expect(buy.type).to eq(:buy)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "handles #sell!" do
|
67
|
+
example_sell_response = <<-JSON
|
68
|
+
{
|
69
|
+
"id": "1",
|
70
|
+
"datetime": "1234567",
|
71
|
+
"type": 1,
|
72
|
+
"price": "12.34",
|
73
|
+
"amount": "100"
|
74
|
+
}
|
75
|
+
JSON
|
76
|
+
|
77
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/sell/", body: example_sell_response)
|
78
|
+
|
79
|
+
sell = subject.sell!(BigDecimal('1'), BigDecimal('100'))
|
80
|
+
expect(sell.type).to eq(:sell)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "handles #cancel" do
|
84
|
+
example_cancel_response = <<-JSON
|
85
|
+
"true"
|
86
|
+
JSON
|
87
|
+
|
88
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/cancel_order/", body: example_cancel_response)
|
89
|
+
|
90
|
+
cancel = subject.cancel(12345)
|
91
|
+
expect(cancel).to eq(true)
|
92
|
+
end
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
require 'bundler'
|
7
|
+
Bundler.setup
|
8
|
+
|
9
|
+
require 'fakeweb'
|
10
|
+
FakeWeb.allow_net_connect = %r[coveralls\.io] # Only allow the coveralls api call through
|
11
|
+
|
12
|
+
require 'multi_json'
|
13
|
+
def json_parse(string)
|
14
|
+
MultiJson.load(string)
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'pry'
|
18
|
+
|
19
|
+
require_relative '../lib/bitstampede'
|
20
|
+
include Bitstampede
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede::Client do
|
4
|
+
subject { described_class.new }
|
5
|
+
let(:net){ subject.send(:net) }
|
6
|
+
let(:mapper){ subject.send(:mapper) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
subject.key = '1'
|
10
|
+
subject.secret = '2'
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#balance' do
|
14
|
+
let(:api_balance_response){ double }
|
15
|
+
let(:balance_object){ double }
|
16
|
+
|
17
|
+
before do
|
18
|
+
net.stub(:post).and_return(api_balance_response)
|
19
|
+
mapper.stub(:map_balance).and_return(balance_object)
|
20
|
+
subject.balance
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'requests the balance from the API' do
|
24
|
+
expect(net).to have_received(:post).with('balance')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'maps the API response to a Balance object' do
|
28
|
+
expect(mapper).to have_received(:map_balance).with(api_balance_response)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns the mapped object' do
|
32
|
+
expect(subject.balance).to eq(balance_object)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#orders' do
|
37
|
+
let(:api_orders_response){ double }
|
38
|
+
let(:order_object){ double }
|
39
|
+
|
40
|
+
before do
|
41
|
+
net.stub(:post).and_return(api_orders_response)
|
42
|
+
mapper.stub(:map_orders).and_return([order_object])
|
43
|
+
subject.orders
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'requests open orders from the API' do
|
47
|
+
expect(net).to have_received(:post).with('open_orders')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'maps the API response to an array of Order objects' do
|
51
|
+
expect(mapper).to have_received(:map_orders).with(api_orders_response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'buy!' do
|
56
|
+
let(:api_buy_response){ double }
|
57
|
+
let(:order_object){ double }
|
58
|
+
|
59
|
+
before do
|
60
|
+
net.stub(:post).and_return(api_buy_response)
|
61
|
+
mapper.stub(:map_order).and_return(order_object)
|
62
|
+
subject.buy!(BigDecimal('1'), BigDecimal('100'))
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'submits a buy order to the API' do
|
66
|
+
expect(net).to have_received(:post).with('buy', { amount: '100.0', price: '1.0' })
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'maps the API response to an Order object' do
|
70
|
+
expect(mapper).to have_received(:map_order).with(api_buy_response)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'sell!' do
|
75
|
+
let(:api_sell_response){ double }
|
76
|
+
let(:order_object){ double }
|
77
|
+
|
78
|
+
before do
|
79
|
+
net.stub(:post).and_return(api_sell_response)
|
80
|
+
mapper.stub(:map_order).and_return(order_object)
|
81
|
+
subject.sell!(BigDecimal('1'), BigDecimal('100'))
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'submits a sell order to the API' do
|
85
|
+
expect(net).to have_received(:post).with('sell', { amount: '100.0', price: '1.0' })
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'maps the API response to an Order object' do
|
89
|
+
expect(mapper).to have_received(:map_order).with(api_sell_response)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'cancel' do
|
94
|
+
let(:api_cancel_response){ double }
|
95
|
+
|
96
|
+
before do
|
97
|
+
net.stub(:post).and_return(api_cancel_response)
|
98
|
+
mapper.stub(:map_cancel).and_return(true)
|
99
|
+
subject.cancel(1234)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'submits a cancel order to the API' do
|
103
|
+
expect(net).to have_received(:post).with('cancel_order', { id: '1234' })
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'maps the API response to a boolean' do
|
107
|
+
expect(mapper).to have_received(:map_cancel).with(api_cancel_response)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../../../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede::Entities::Balance do
|
4
|
+
let(:balance_hash){
|
5
|
+
{
|
6
|
+
"usd_balance" => "111.12",
|
7
|
+
"btc_balance" => "211.23",
|
8
|
+
"usd_reserved" => "1.20",
|
9
|
+
"btc_reserved" => "2.30",
|
10
|
+
"usd_available" => "5.50",
|
11
|
+
"btc_available" => "6.60",
|
12
|
+
"fee" => "1.11"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
subject{ described_class.new(balance_hash) }
|
16
|
+
|
17
|
+
it "has a usd_balance" do
|
18
|
+
expect(subject.usd_balance).to eq(BigDecimal('111.12'))
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a btc_balance" do
|
22
|
+
expect(subject.btc_balance).to eq(BigDecimal('211.23'))
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has a usd_reserved" do
|
26
|
+
expect(subject.usd_reserved).to eq(BigDecimal('1.20'))
|
27
|
+
end
|
28
|
+
|
29
|
+
it "has a btc_reserved" do
|
30
|
+
expect(subject.btc_reserved).to eq(BigDecimal('2.30'))
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has a usd_available" do
|
34
|
+
expect(subject.usd_available).to eq(BigDecimal('5.50'))
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has a btc_available" do
|
38
|
+
expect(subject.btc_available).to eq(BigDecimal('6.60'))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "has a fee" do
|
42
|
+
expect(subject.fee).to eq(BigDecimal('1.11'))
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can be inspected" do
|
46
|
+
expect { subject.inspect }.to_not raise_error
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../../../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede::Entities::Order do
|
4
|
+
let(:order_hash){
|
5
|
+
{
|
6
|
+
"id" => "1",
|
7
|
+
"datetime" => 1234567,
|
8
|
+
"type" => 0,
|
9
|
+
"price" => "1.23",
|
10
|
+
"amount" => "10"
|
11
|
+
}
|
12
|
+
}
|
13
|
+
subject{ described_class.new(order_hash) }
|
14
|
+
|
15
|
+
it "has an id" do
|
16
|
+
expect(subject.id).to eq(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "type" do
|
20
|
+
it "maps 0 to :buy" do
|
21
|
+
expect(subject.type).to eq(:buy)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "maps 1 to :sell" do
|
25
|
+
order = described_class.new(order_hash.merge({"type" => 1}))
|
26
|
+
expect(order.type).to eq(:sell)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raises InvalidTypeError for other values" do
|
30
|
+
expect { described_class.new(order_hash.merge({"type" => 2})) }.to raise_error(Bitstampede::Entities::Order::InvalidTypeError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede::Mapper do
|
4
|
+
subject { described_class.new }
|
5
|
+
let(:json_object){ '{"foo": "bar"}' }
|
6
|
+
let(:json_array){ '[{"foo": "bar"}]' }
|
7
|
+
|
8
|
+
describe '#map_balance' do
|
9
|
+
let(:balance) { double }
|
10
|
+
|
11
|
+
before do
|
12
|
+
Entities::Balance.stub(:new).and_return(balance)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "maps a balance API response into a Balance entity" do
|
16
|
+
subject.map_balance(json_object)
|
17
|
+
expect(Entities::Balance).to have_received(:new).with(json_parse(json_object))
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns the mapped Balance entity" do
|
21
|
+
expect(subject.map_balance(json_object)).to eq(balance)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#map_orders' do
|
26
|
+
let(:order) { double }
|
27
|
+
|
28
|
+
before do
|
29
|
+
Entities::Order.stub(:new).and_return(order)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "maps an open_orders API response into an array of Order entities" do
|
33
|
+
subject.map_orders(json_array)
|
34
|
+
expect(Entities::Order).to have_received(:new).with(json_parse(json_array)[0])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#map_cancel' do
|
39
|
+
it "maps a cancel API response to a boolean" do
|
40
|
+
expect(subject.map_cancel('"true"')).to eq(true)
|
41
|
+
expect(subject.map_cancel('"false"')).to eq(false)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede::Net do
|
4
|
+
let(:client){ double }
|
5
|
+
subject { described_class.new(client) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
client.stub(:secret).and_return(1)
|
9
|
+
client.stub(:key).and_return(2)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'gets instantiated with a client' do
|
13
|
+
expect(subject.client).to eq(client)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'defers to its client for secret' do
|
17
|
+
expect(subject.secret).to eq(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'defers to its client for key' do
|
21
|
+
expect(subject.key).to eq(2)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#post' do
|
25
|
+
describe 'any_endpoint' do
|
26
|
+
let(:example_balance) do
|
27
|
+
<<-JSON
|
28
|
+
{
|
29
|
+
"foo": "bar"
|
30
|
+
}
|
31
|
+
JSON
|
32
|
+
end
|
33
|
+
|
34
|
+
before do
|
35
|
+
FakeWeb.register_uri(:post, "https://www.bitstamp.net/api/balance/", body: example_balance)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "queries the appropriate endpoint and returns its body as a string" do
|
39
|
+
expect(json_parse(subject.post('balance'))).to eq(json_parse(example_balance))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Bitstampede do
|
4
|
+
it "ought to be an instance rather than a retarded singleton like every freaking btc-related gem in existence apparently" do
|
5
|
+
client = Bitstampede::Client.new
|
6
|
+
expect(client.key).to eq(nil)
|
7
|
+
expect(client.secret).to eq(nil)
|
8
|
+
client.key = '1'
|
9
|
+
client.secret = '2'
|
10
|
+
expect(client.key).to eq('1')
|
11
|
+
expect(client.secret).to eq('2')
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitstampede
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Adams
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yajl-ruby
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bundler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.3'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.3'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.14.0.rc1
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.14.0.rc1
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: fakeweb
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: pry
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: This is a client library for the Bitstamp API that supports instantiating
|
143
|
+
multiple clients in the same process.
|
144
|
+
email:
|
145
|
+
- josh@isotope11.com
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- .rspec
|
152
|
+
- .rvmrc
|
153
|
+
- .travis.yml
|
154
|
+
- Gemfile
|
155
|
+
- LICENSE.md
|
156
|
+
- README.md
|
157
|
+
- Rakefile
|
158
|
+
- bitstampede.gemspec
|
159
|
+
- doc/legitimate_concern.png
|
160
|
+
- example/balance.rb
|
161
|
+
- example/example.rb
|
162
|
+
- example/query_orders.rb
|
163
|
+
- lib/bitstampede.rb
|
164
|
+
- lib/bitstampede/client.rb
|
165
|
+
- lib/bitstampede/entities/balance.rb
|
166
|
+
- lib/bitstampede/entities/base.rb
|
167
|
+
- lib/bitstampede/entities/order.rb
|
168
|
+
- lib/bitstampede/mapper.rb
|
169
|
+
- lib/bitstampede/net.rb
|
170
|
+
- lib/bitstampede/version.rb
|
171
|
+
- spec/integration/client_spec.rb
|
172
|
+
- spec/spec_helper.rb
|
173
|
+
- spec/unit/bitstampede/client_spec.rb
|
174
|
+
- spec/unit/bitstampede/entities/balance_spec.rb
|
175
|
+
- spec/unit/bitstampede/entities/order_spec.rb
|
176
|
+
- spec/unit/bitstampede/mapper_spec.rb
|
177
|
+
- spec/unit/bitstampede/net_spec.rb
|
178
|
+
- spec/unit/bitstampede_spec.rb
|
179
|
+
homepage: http://github.com/isotope11/bitstampede
|
180
|
+
licenses:
|
181
|
+
- MIT
|
182
|
+
post_install_message:
|
183
|
+
rdoc_options: []
|
184
|
+
require_paths:
|
185
|
+
- lib
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
+
none: false
|
188
|
+
requirements:
|
189
|
+
- - ! '>='
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
segments:
|
193
|
+
- 0
|
194
|
+
hash: 4285444086114394616
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
none: false
|
197
|
+
requirements:
|
198
|
+
- - ! '>='
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
segments:
|
202
|
+
- 0
|
203
|
+
hash: 4285444086114394616
|
204
|
+
requirements: []
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 1.8.25
|
207
|
+
signing_key:
|
208
|
+
specification_version: 3
|
209
|
+
summary: Fantastic bitstamp library.
|
210
|
+
test_files:
|
211
|
+
- spec/integration/client_spec.rb
|
212
|
+
- spec/spec_helper.rb
|
213
|
+
- spec/unit/bitstampede/client_spec.rb
|
214
|
+
- spec/unit/bitstampede/entities/balance_spec.rb
|
215
|
+
- spec/unit/bitstampede/entities/order_spec.rb
|
216
|
+
- spec/unit/bitstampede/mapper_spec.rb
|
217
|
+
- spec/unit/bitstampede/net_spec.rb
|
218
|
+
- spec/unit/bitstampede_spec.rb
|