bitex 0.0.2

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.
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