bitex 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +89 -0
  6. data/Rakefile +1 -0
  7. data/bitex.gemspec +33 -0
  8. data/lib/bitex.rb +11 -0
  9. data/lib/bitex/api.rb +57 -0
  10. data/lib/bitex/buy.rb +25 -0
  11. data/lib/bitex/market.rb +67 -0
  12. data/lib/bitex/match.rb +24 -0
  13. data/lib/bitex/order.rb +185 -0
  14. data/lib/bitex/profile.rb +10 -0
  15. data/lib/bitex/sell.rb +25 -0
  16. data/lib/bitex/specie_deposit.rb +27 -0
  17. data/lib/bitex/specie_withdrawal.rb +56 -0
  18. data/lib/bitex/transaction.rb +14 -0
  19. data/lib/bitex/usd_deposit.rb +65 -0
  20. data/lib/bitex/usd_withdrawal.rb +56 -0
  21. data/lib/bitex/version.rb +3 -0
  22. data/spec/ask_spec.rb +24 -0
  23. data/spec/bid_spec.rb +24 -0
  24. data/spec/buy_spec.rb +11 -0
  25. data/spec/fixtures/aggregated_data.json +1 -0
  26. data/spec/fixtures/asks_cancel.json +1 -0
  27. data/spec/fixtures/asks_create.json +1 -0
  28. data/spec/fixtures/bids_cancel.json +1 -0
  29. data/spec/fixtures/bids_create.json +1 -0
  30. data/spec/fixtures/market_ticker.json +1 -0
  31. data/spec/fixtures/order_book.json +1 -0
  32. data/spec/fixtures/orders.json +1 -0
  33. data/spec/fixtures/profile.json +1 -0
  34. data/spec/fixtures/transactions.json +1 -0
  35. data/spec/fixtures/user_transactions.json +1 -0
  36. data/spec/market_spec.rb +46 -0
  37. data/spec/order_spec.rb +23 -0
  38. data/spec/profile_spec.rb +23 -0
  39. data/spec/sell_spec.rb +11 -0
  40. data/spec/spec_helper.rb +28 -0
  41. data/spec/specie_deposit_spec.rb +16 -0
  42. data/spec/specie_withdrawal_spec.rb +37 -0
  43. data/spec/support/from_json_shared_examples.rb +52 -0
  44. data/spec/support/order_shared_examples.rb +45 -0
  45. data/spec/support/request_stubs.rb +17 -0
  46. data/spec/transaction_spec.rb +19 -0
  47. data/spec/usd_deposit_spec.rb +45 -0
  48. data/spec/usd_withdrawal_spec.rb +37 -0
  49. metadata +269 -0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bitex.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Nubis
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.
@@ -0,0 +1,89 @@
1
+ # Bitex
2
+
3
+ [Bitex.la](https://bitex.la/developers) API Client library.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'bitex'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install bitex
19
+
20
+
21
+ ## Use Public Market Data
22
+
23
+ Bitex::BitcoinMarketData and Bitex::LitecoinMarketData classes have methods for
24
+ fetching all public market data available.
25
+
26
+ ### Ticker
27
+
28
+ ruby > Bitex::BitcoinMarketData.ticker
29
+ => {:last=>632.0, :high=>648.0, :low=>625.94, :vwap=>633.670289779918,
30
+ :volume=>9.49595029, :bid=>632.0, :ask=>648.0}
31
+
32
+ ### Order Book
33
+
34
+ ruby > Bitex::BitcoinMarketData.order_book
35
+ => {:bids=>[[632.0, 38.910443037975], [630.87, 1.8], ...],
36
+ :asks=>[[634.9, 0.95], [648.0, 0.4809267], ...]}
37
+
38
+ ### Transactions
39
+
40
+ ruby > Bitex::BitcoinMarketData.transactions
41
+ => [[1404501180, 1335, 632.0, 0.95], [1404501159, 1334, 632.0, 0.95], ...]
42
+
43
+ ## Use for Private Trading
44
+
45
+ ### Authentication
46
+
47
+ Sign in to [https://bitex.la/developers](https://bitex.la/developers) and create
48
+ an `api_key`. You can find more information about our authentication security
49
+ on that page. Once done you can start using it as follows:
50
+
51
+ ruby > Bitex.api_key = 'your_api_key'
52
+ => "your_api_key"
53
+
54
+ ### Get your balances, deposit addresses, fee
55
+
56
+ ruby > Bitex::Profile.get
57
+
58
+ ### Place a Bid
59
+
60
+ ruby > Bitex::Bid.create!(:btc, 1000, 500) # Spend 1000 USD in btc, paying up to 500 each
61
+
62
+ ### Place an Ask
63
+
64
+ ruby > Bitex::Ask.create!(:ltc, 2, 500) # Sell 2 LTC
65
+
66
+ ### List your pending or recently active orders
67
+
68
+ ruby > Bitex::Order.all
69
+
70
+ ### List your recent transactions
71
+
72
+ ruby > Bitex::Transaction.all
73
+
74
+ ## Want more?
75
+
76
+ Find the full API description at
77
+ [https://bitex.la/developers](https://bitex.la/developers)
78
+
79
+ Read this gems full documentation at
80
+ [http://rubydoc.info/github/bitex-la/bitex-ruby/master/frames/file/README.md](http://rubydoc.info/github/bitex-la/bitex-ruby/master/frames/file/README.md)
81
+
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bitex/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bitex"
8
+ spec.version = Bitex::VERSION
9
+ spec.authors = ["Nubis", "Eromirou"]
10
+ spec.email = ["nb@bitex.la", "tr@bitex.la"]
11
+ spec.description = %q{API client library for bitex.la. Fetch public market
12
+ data and build trading robots}
13
+ spec.summary = "API client library for bitex.la"
14
+ spec.homepage = "http://bitex.la/developers"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "activesupport"
23
+ spec.add_dependency "curb"
24
+
25
+ spec.required_ruby_version = '>= 1.9.3'
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rspec"
29
+ spec.add_development_dependency "rspec-mocks"
30
+ spec.add_development_dependency "timecop"
31
+ spec.add_development_dependency "webmock"
32
+ spec.add_development_dependency "shoulda-matchers"
33
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_support/core_ext'
2
+ require 'json'
3
+ require 'curl'
4
+ require 'bigdecimal'
5
+ require 'active_support'
6
+ require 'bitex/match'
7
+ Dir[File.expand_path("../bitex/*.rb", __FILE__)].each {|f| require f}
8
+
9
+ module Bitex
10
+ mattr_accessor :api_key
11
+ end
@@ -0,0 +1,57 @@
1
+ module Bitex
2
+ class ApiError < Exception; end
3
+ class Api
4
+ def self.curl(verb, path, options={})
5
+ verb = verb.upcase.to_sym
6
+ query = verb == :GET ? "?#{options.to_query}" : ''
7
+
8
+ curl = Curl::Easy.new("https://bitex.la/api-v1/rest#{path}#{query}")
9
+ curl.post_body = options.to_query if verb == :POST
10
+ curl.http(verb)
11
+ code = curl.response_code
12
+
13
+ unless [200, 201, 202].include?(code)
14
+ raise ApiError.new("Got #{code} fetching #{path} with #{options}")
15
+ end
16
+
17
+ return curl
18
+ end
19
+
20
+ def self.public(path, options={})
21
+ JSON.parse(curl(:GET, path).body)
22
+ end
23
+
24
+ def self.private(verb, path, options={})
25
+ if Bitex.api_key.nil?
26
+ raise Exception.new("No api_key available to make private key calls")
27
+ end
28
+ JSON.parse(curl(verb, path, options.merge(api_key: Bitex.api_key)).body)
29
+ end
30
+
31
+ # Deserialize a single object from a json representation as specified on the
32
+ # bitex API class reference
33
+ # @see https://bitex.la/developers#api-class-reference
34
+ def self.deserialize(object)
35
+ { 1 => Bid,
36
+ 2 => Ask,
37
+ 3 => Buy,
38
+ 4 => Sell,
39
+ 5 => SpecieDeposit,
40
+ 6 => SpecieWithdrawal,
41
+ 7 => UsdDeposit,
42
+ 8 => UsdWithdrawal,
43
+ }[object.first].from_json(object)
44
+ end
45
+
46
+ # @visibility private
47
+ def self.from_json(thing, json, with_specie=false, &block)
48
+ thing.id = json[1]
49
+ thing.created_at = Time.at(json[2])
50
+ if with_specie
51
+ thing.specie = {1 => :btc, 2 => :ltc}[json[3]]
52
+ end
53
+ block.call(thing)
54
+ return thing
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ module Bitex
2
+ # A transaction in which you bought some quantity of specie.
3
+ class Buy < Match
4
+ # @!attribute id
5
+ # @return [Integer] This buy's unique ID.
6
+
7
+ # @!attribute created_at
8
+ # @return [Time] Time when this Buy happened.
9
+
10
+ # @!attribute specie
11
+ # @return [Symbol] :btc or :ltc
12
+
13
+ # @!attribute quantity
14
+ # @return [BigDecimal] Quantity of specie bought
15
+
16
+ # @!attribute amount
17
+ # @return [BigDecimal] Amount of USD spent
18
+
19
+ # @!attribute fee
20
+ # @return [BigDecimal] Quantity of specie paid as transaction fee.
21
+
22
+ # @!attribute price
23
+ # @return [BigDecimal] Price paid per unit
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ module Bitex
2
+ # Public market data for a specie, do not use directly, use
3
+ # {BitcoinMarketData} and {LitecoinMarketData} instead.
4
+ class MarketData
5
+
6
+ # The species currency ticker conveniently formatted as a ruby Hash with
7
+ # symbolized keys.
8
+ # @see https://bitex.la/developers#ticker
9
+ def self.ticker
10
+ api_get('/market/ticker').symbolize_keys
11
+ end
12
+
13
+ # The species order book as a Hash with two keys: bids and asks.
14
+ # Each of them is a list of list consisting of [price, quantity]
15
+ # @see https://bitex.la/developers#order_book
16
+ def self.order_book
17
+ api_get('/market/order_book').symbolize_keys
18
+ end
19
+
20
+ # The species transactions for the past hour as a list of lists,
21
+ # each composed of [unix_timestamp, transaction_id, price, quantity]
22
+ # @see https://bitex.la/developers#transactions
23
+ def self.transactions
24
+ api_get('/market/transactions')
25
+ end
26
+
27
+ # Returns a list of lists with aggregated transaction data for each hour
28
+ # from the last 24 hours.
29
+ # @see https://bitex.la/developers#last_24_hours
30
+ def self.last_24_hours
31
+ api_get('/market/last_24_hours')
32
+ end
33
+
34
+ # Returns a list of lists with aggregated transaction data for each 4 hour
35
+ # period from the last 7 days.
36
+ # @see https://bitex.la/developers#last_7_days
37
+ def self.last_7_days
38
+ api_get('/market/last_7_days')
39
+ end
40
+
41
+ # Returns a list of lists with aggregated transaction data for each day
42
+ # from the last 30 days.
43
+ # @see https://bitex.la/developers#last_30_days
44
+ def self.last_30_days
45
+ api_get('/market/last_30_days')
46
+ end
47
+
48
+ # @visibility private
49
+ def self.api_get(path, options = {})
50
+ Api.public("/#{specie}#{path}", options)
51
+ end
52
+ end
53
+
54
+ # A {MarketData} for Bitcoin.
55
+ class BitcoinMarketData < MarketData
56
+ def self.specie
57
+ 'btc'
58
+ end
59
+ end
60
+
61
+ # A {MarketData} for Litecoin.
62
+ class LitecoinMarketData < MarketData
63
+ def self.specie
64
+ 'ltc'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ module Bitex
2
+ # @visibility private
3
+ # Both Buy and Sell are a kind of Match, they deserialize the same
4
+ # and have very similar fields, although their documentation may differ.
5
+ class Match
6
+ attr_accessor :id
7
+ attr_accessor :created_at
8
+ attr_accessor :specie
9
+ attr_accessor :quantity
10
+ attr_accessor :amount
11
+ attr_accessor :fee
12
+ attr_accessor :price
13
+
14
+ # @visibility private
15
+ def self.from_json(json)
16
+ Api.from_json(new, json, true) do |thing|
17
+ thing.quantity = BigDecimal.new(json[4].to_s)
18
+ thing.amount = BigDecimal.new(json[5].to_s)
19
+ thing.fee = BigDecimal.new(json[6].to_s)
20
+ thing.price = BigDecimal.new(json[7].to_s)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,185 @@
1
+ module Bitex
2
+
3
+ # Base class for Bids and Asks
4
+ class OrderBase
5
+ attr_accessor :id
6
+ attr_accessor :created_at
7
+ attr_accessor :specie
8
+ attr_accessor :price
9
+ attr_accessor :status
10
+
11
+ # Returns an array with all your active orders
12
+ # of this type, and any other order of this type that was active in the
13
+ # last 2 hours. Uses {Order.all} under the hood.
14
+ def self.all
15
+ Order.all.select{|o| o.is_a?(self) }
16
+ end
17
+
18
+ # Returns an array with all your active orders of this type
19
+ # Uses {Order.active} under the hood.
20
+ def self.active
21
+ Order.active.select{|o| o.is_a?(self) }
22
+ end
23
+
24
+ # Find an order in your list of active orders.
25
+ # Uses {Order.active} under the hood.
26
+ def self.find(id)
27
+ all.select{|o| o.id == id}.first
28
+ end
29
+
30
+ def cancel!
31
+ path = "/private#{self.class.base_path}/cancel"
32
+ self.class.from_json(Api.private(:post, path), self)
33
+ end
34
+
35
+ # @visibility private
36
+ def self.from_json(json, order = nil)
37
+ status_lookup = {
38
+ 1 => :received,
39
+ 2 => :executing,
40
+ 3 => :cancelling,
41
+ 4 => :cancelled,
42
+ 5 => :completed,
43
+ }
44
+ Api.from_json(order || new, json, true) do |thing|
45
+ thing.price = BigDecimal.new(json[6].to_s)
46
+ thing.status = status_lookup[json[7]]
47
+ end
48
+ end
49
+ end
50
+
51
+ # A Bid is an order to buy a given specie.
52
+ # @see OrderBase
53
+ class Bid < OrderBase
54
+ # @!attribute id
55
+ # @return [Integer] This Bid's unique ID.
56
+
57
+ # @!attribute created_at
58
+ # @return [Time] Time when this Bid was created.
59
+
60
+ # @!attribute specie
61
+ # @return [Symbol] :btc or :ltc
62
+
63
+ # @!attribute amount
64
+ # @return [BigDecimal] Amount of USD to spend in this Bid.
65
+ attr_accessor :amount
66
+
67
+ # @!attribute remaining_amount
68
+ # @return [BigDecimal] Amount of USD left to be spent in this Bid.
69
+ attr_accessor :remaining_amount
70
+
71
+ # @!attribute price
72
+ # @return [BigDecimal] Maximum price to pay per unit.
73
+
74
+ # @!attribute status
75
+ # The status of this Bid in its lifecycle.
76
+ # * :received queued to check if you have enough funds.
77
+ # * :executing available in our ourderbook waiting to be matched.
78
+ # * :cancelling To be cancelled as soon as our trading engine unlocks it.
79
+ # * :cancelled no further executed. May have some Remaining Amount.
80
+ # * :completed Fully executed, Remaining Amount should be 0.
81
+
82
+ # @visibility private
83
+ def self.base_path
84
+ '/bids'
85
+ end
86
+
87
+ # Create a new Bid for spending Amount USD paying no more than
88
+ # price per unit.
89
+ # @param specie [Symbol] :btc or :ltc, whatever you're buying.
90
+ # @see https://bitex.la/developers#create-bid
91
+ def self.create!(specie, amount, price)
92
+ params = {
93
+ amount: amount,
94
+ price: price,
95
+ specie: {btc: 1, ltc: 2}[specie]
96
+ }
97
+ from_json(Api.private(:post, "/private#{base_path}", params))
98
+ end
99
+
100
+ # @visibility private
101
+ def self.from_json(json, order = nil)
102
+ super(json, order).tap do |thing|
103
+ thing.amount = BigDecimal.new(json[4].to_s)
104
+ thing.remaining_amount = BigDecimal.new(json[5].to_s)
105
+ end
106
+ end
107
+ end
108
+
109
+ # An Ask is an order to sell a given specie.
110
+ # @see OrderBase
111
+ class Ask < OrderBase
112
+ # @!attribute id
113
+ # @return [Integer] This Ask's unique ID.
114
+
115
+ # @!attribute created_at
116
+ # @return [Time] Time when this Ask was created.
117
+
118
+ # @!attribute specie
119
+ # @return [Symbol] :btc or :ltc
120
+
121
+ # @!attribute quantity
122
+ # @return [BigDecimal] Quantity of specie to sell in this Ask.
123
+ attr_accessor :quantity
124
+
125
+ # @!attribute remaining_quantity
126
+ # @return [BigDecimal] Quantity of specie left to sell in this Ask.
127
+ attr_accessor :remaining_quantity
128
+
129
+ # @!attribute price
130
+ # @return [BigDecimal] Minimum price to charge per unit.
131
+
132
+ # @!attribute status
133
+ # The status of this Ask in its lifecycle.
134
+ # * :received queued to check if you have enough funds.
135
+ # * :executing available in our ourderbook waiting to be matched.
136
+ # * :cancelling To be cancelled as soon as our trading engine unlocks it.
137
+ # * :cancelled no further executed. May have some Remaining Quantity.
138
+ # * :completed Fully executed, Remaining Quantity should be 0.
139
+
140
+ # @visibility private
141
+ def self.base_path
142
+ '/asks'
143
+ end
144
+
145
+ # Create a new Ask for selling a Quantity of specie charging no less than
146
+ # Price per each.
147
+ # @param specie [Symbol] :btc or :ltc, whatever you're selling.
148
+ # @see https://bitex.la/developers#create-ask
149
+ def self.create!(specie, quantity, price)
150
+ params = {
151
+ amount: quantity,
152
+ price: price,
153
+ specie: {btc: 1, ltc: 2}[specie]
154
+ }
155
+ from_json(Api.private(:post, "/private#{base_path}", params))
156
+ end
157
+
158
+ # @visibility private
159
+ def self.from_json(json, order = nil)
160
+ super(json, order).tap do |thing|
161
+ thing.quantity = BigDecimal.new(json[4].to_s)
162
+ thing.remaining_quantity = BigDecimal.new(json[5].to_s)
163
+ end
164
+ end
165
+ end
166
+
167
+ # Convenience class for fetching heterogeneous lists with all your Bids and
168
+ # Asks.
169
+ class Order
170
+ # @return [Array<Bitex::Bid, Bitex::Ask>] Returns an heterogeneous array
171
+ # with all your active orders and any other order that was active in the
172
+ # last 2 hours.
173
+ # @see https://bitex.la/developers#orders
174
+ def self.all
175
+ Api.private(:GET, '/private/orders').collect{|o| Api.deserialize(o) }
176
+ end
177
+
178
+ # @return [Array<Bitex::Bid, Bitex::Ask>] Returns an heterogeneous array
179
+ # with all your active orders.
180
+ # @see https://bitex.la/developers#active-orders
181
+ def self.active
182
+ Api.private(:GET, '/private/orders/active').collect{|o| Api.deserialize(o) }
183
+ end
184
+ end
185
+ end