trade-o-matic 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trade-o-matic/adapters/base/raw_account_order.rb +7 -0
  3. data/lib/trade-o-matic/adapters/base/raw_balance.rb +7 -0
  4. data/lib/trade-o-matic/adapters/base/raw_resource.rb +31 -0
  5. data/lib/trade-o-matic/adapters/bitstamp_backend.rb +166 -0
  6. data/lib/trade-o-matic/adapters/fake_backend.rb +235 -0
  7. data/lib/trade-o-matic/adapters/itbit_backend.rb +57 -0
  8. data/lib/trade-o-matic/adapters/surbtc_backend.rb +149 -0
  9. data/lib/trade-o-matic/cli.rb +6 -22
  10. data/lib/trade-o-matic/converters/compound_converter.rb +13 -0
  11. data/lib/trade-o-matic/converters/fixed_converter.rb +0 -7
  12. data/lib/trade-o-matic/converters/inverse_converter.rb +13 -0
  13. data/lib/trade-o-matic/converters/json_api_converter.rb +20 -0
  14. data/lib/trade-o-matic/converters/sync_converter.rb +1 -11
  15. data/lib/trade-o-matic/core/account.rb +75 -0
  16. data/lib/trade-o-matic/core/account_order.rb +99 -0
  17. data/lib/trade-o-matic/core/account_proxy.rb +48 -0
  18. data/lib/trade-o-matic/core/balance.rb +57 -0
  19. data/lib/trade-o-matic/core/exchange.rb +19 -0
  20. data/lib/trade-o-matic/core/market.rb +29 -0
  21. data/lib/trade-o-matic/core/market_loader.rb +29 -0
  22. data/lib/trade-o-matic/services/backend_factory.rb +46 -0
  23. data/lib/trade-o-matic/structs/ask_slope.rb +4 -1
  24. data/lib/trade-o-matic/structs/bid_slope.rb +4 -1
  25. data/lib/trade-o-matic/structs/book.rb +52 -0
  26. data/lib/trade-o-matic/structs/converter.rb +2 -3
  27. data/lib/trade-o-matic/structs/currency.rb +126 -21
  28. data/lib/trade-o-matic/structs/currency_pair.rb +31 -5
  29. data/lib/trade-o-matic/structs/order.rb +53 -1
  30. data/lib/trade-o-matic/structs/price.rb +9 -8
  31. data/lib/trade-o-matic/structs/slope.rb +11 -34
  32. data/lib/trade-o-matic/support/converter_configurator.rb +35 -0
  33. data/lib/trade-o-matic/version.rb +1 -1
  34. data/lib/trade-o-matic.rb +29 -1
  35. metadata +59 -43
  36. data/lib/trade-o-matic/adapters/bitstamp_account.rb +0 -63
  37. data/lib/trade-o-matic/flows/ask_replicator_flow.rb +0 -50
  38. data/lib/trade-o-matic/generators/linear_generator.rb +0 -37
  39. data/lib/trade-o-matic/structs/market.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9300715275fb3f78ca15a6d534d493f61c75ea7e
4
- data.tar.gz: b3f66fd9b2f31736c86260f45c817ea8dc85dc01
3
+ metadata.gz: 9b2ab1e0af6b6c8685d51d92657660229ac270e4
4
+ data.tar.gz: f03f687cbae9e0d8436d5b879166ae186a57bd16
5
5
  SHA512:
6
- metadata.gz: 5208ae28f7c8d4da98d1a65bfdef0f1f9b2b1a09e4744ee79b87c32f104a6c9e71ceca7ea4045af8cba160c7b4ffca7f2b338b694cb514310957791e3fd24897
7
- data.tar.gz: 4efe51fef441d0a483e38a766743268f87bf4fb9a968496763c85befd879aa6e05902b5732e24e6e525199e83c4e2435019ca16130341f5ed25f9e18cd7e0b5a
6
+ metadata.gz: c524eb2a14bf4d3258e98356ed20493b2ce7943488833137c53fc338ba9a4181c082a4d8a63e0eb08d46cc0a4d0f8e3d400d6ce38c1909cba93f493d75d1db92
7
+ data.tar.gz: 3e5696eafbdcf0e0022166540eace85363ae4f88fb28704d14ea27cf1c1596c5868e8f97c5a2d2ee8756f54f993f2f95b72b0ced2bbdf1da9bb4f64da90adac6
@@ -0,0 +1,7 @@
1
+ require 'trade-o-matic/adapters/base/raw_resource'
2
+
3
+ module Trader
4
+ class RawAccountOrder < RawResource
5
+ enforce_attr :id, :status, :price, :volume, :executed_volume, :instruction, :limit?
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'trade-o-matic/adapters/base/raw_resource'
2
+
3
+ module Trader
4
+ class RawBalance < RawResource
5
+ enforce_attr :amount, :available_amount
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ module Trader
2
+ class RawResource
3
+
4
+ def self.enforce_attr(*_attrs)
5
+ _attrs.each do |att|
6
+ define_method(att) do
7
+ raise NotImplementedError, "#{att} was not implemented by backend"
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.attr_mapped(_attr, _path=nil, &_block)
13
+ define_method(_attr) do
14
+ if _block
15
+ _block.call(raw)
16
+ elsif _path
17
+ raw[_path]
18
+ else
19
+ raw[_attr.to_s]
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_reader :raw
25
+
26
+ def initialize(_raw)
27
+ @raw = _raw
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,166 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'trade-o-matic/adapters/base/raw_account_order'
4
+ require 'trade-o-matic/adapters/base/raw_balance'
5
+
6
+ module Trader
7
+ class BitstampBackend
8
+ BASE_CUR = Currency.for_code(:BTC)
9
+ QUOTE_CUR = Currency.for_code(:BITSTAMP_USD)
10
+ MAIN_MARKET = CurrencyPair.new BASE_CUR, QUOTE_CUR
11
+
12
+ TYPE_MAP = {
13
+ 0 => Order::BID,
14
+ 1 => Order::ASK
15
+ }
16
+
17
+ class BackendBalance < Struct.new(:amount, :available_amount)
18
+ # nothing for now
19
+ end
20
+
21
+ class BitstampOrder < RawAccountOrder
22
+ attr_mapped(:id, 'raw') # use original order information as id
23
+ attr_mapped(:pair) { |r| MAIN_MARKET }
24
+ attr_mapped(:price) { |r| r['raw']['price'].to_f }
25
+ attr_mapped(:volume) { |r| r['raw']['amount'].to_f }
26
+ attr_mapped(:executed_volume)
27
+ attr_mapped(:instruction) { |r| r['raw']['type'] == 0 ? Order::BID : Order::ASK }
28
+
29
+ attr_mapped(:status) do |r|
30
+ case r['status']
31
+ when 'Open'
32
+ AccountOrder::OPEN
33
+ when 'Finished'
34
+ r['executed_volume'] < r['raw']['amount'].to_f ? AccountOrder::CANCELED : AccountOrder::CLOSED
35
+ else
36
+ AccountOrder::PENDING
37
+ end
38
+ end
39
+
40
+ def limit?
41
+ true
42
+ end
43
+ end
44
+
45
+ def initialize(_session=nil)
46
+ @session = _session
47
+ end
48
+
49
+ def get_available_markets
50
+ [MAIN_MARKET]
51
+ end
52
+
53
+ def fill_book(_book)
54
+ # TODO: consider book pair
55
+
56
+ _book.prepare Time.now
57
+
58
+ ob = execute_request(nil, 'order_book')
59
+ ob['bids'].each { |o| _book.add_bid(o[0].to_f, o[1].to_f) }
60
+ ob['asks'].each { |o| _book.add_ask(o[0].to_f, o[1].to_f) }
61
+
62
+ tx = execute_request(nil, 'transactions')
63
+ tx.each do |t|
64
+ _book.add_transaction t['price'].to_f, t['amount'].to_f, Time.at(t['date'].to_i)
65
+ end
66
+ end
67
+
68
+ def get_session(_credentials)
69
+ _credentials
70
+ end
71
+
72
+ def get_balance(_session, _currency)
73
+ raise "#{_currency} not supported" unless _currency == BASE_CUR || _currency == QUOTE_CUR
74
+
75
+ raw = execute_request(_session || session, 'balance')
76
+
77
+ if _currency == BASE_CUR
78
+ return BackendBalance.new raw['btc_balance'].to_f, raw['btc_available'].to_f
79
+ else
80
+ return BackendBalance.new raw['usd_balance'].to_f, raw['usd_available'].to_f
81
+ end
82
+ end
83
+
84
+ def get_orders(_session, _pair)
85
+ raise 'market not supported' unless _pair == MAIN_MARKET
86
+
87
+ raw_orders = execute_request(_session || session, 'open_orders')
88
+ raw_orders.map { |o| normalize_raw_order o }
89
+ end
90
+
91
+ def create_order(_session, _pair, _volume, _price, _type)
92
+ raise 'market not supported' unless _pair == MAIN_MARKET
93
+ raise 'market orders not supported' if _price.nil?
94
+
95
+ normalize_raw_order execute_request(
96
+ _session || session,
97
+ _type == Order::BID ? 'buy' : 'sell',
98
+ { amount: _volume, price: _price }
99
+ )
100
+ end
101
+
102
+ def fetch_order(_session, _id)
103
+ normalize_raw_order_status _id, execute_request(
104
+ _session || session,
105
+ 'order_status',
106
+ { id: _id['id'] }
107
+ )
108
+ end
109
+
110
+ def cancel_order(_session, _id)
111
+ # TODO
112
+ end
113
+
114
+ private
115
+
116
+ attr_reader :session
117
+
118
+ def execute_request(_signing, _resource, _data=nil)
119
+ _data = sign_params _signing, _data if _signing
120
+
121
+ JSON.parse(if _data.nil?
122
+ RestClient.get "https://www.bitstamp.net/api/#{_resource}/"
123
+ else
124
+ RestClient.post "https://www.bitstamp.net/api/#{_resource}/", _data
125
+ end)
126
+ end
127
+
128
+ def sign_params(_keys, _params)
129
+ nonce = (Time.now.to_f*10000).to_i.to_s
130
+
131
+ customer_id = _keys[:customer_id].to_s
132
+ api_key = _keys[:api_key]
133
+ api_secret = _keys[:api_secret]
134
+
135
+ digest = OpenSSL::Digest.new('sha256')
136
+ signature_data = "#{nonce}#{customer_id}#{api_key}"
137
+ signature = OpenSSL::HMAC.hexdigest(digest, api_secret, signature_data)
138
+
139
+ (_params || {}).merge({
140
+ nonce: nonce,
141
+ signature: signature.upcase,
142
+ key: api_key
143
+ })
144
+ end
145
+
146
+ def normalize_raw_order(_order)
147
+ BitstampOrder.new({
148
+ 'raw' => _order,
149
+ 'executed_volume' => 0.0,
150
+ 'status' => 'In Queue'
151
+ })
152
+ end
153
+
154
+ def normalize_raw_order_status(_id, _status)
155
+ executed_volume = _status['transactions'].inject(0.0) { |r, t| r + t['btc'].to_f }
156
+ executed_price = _status['transactions'].inject(0.0) { |r, t| r + (t['price'].to_f * t['btc'].to_f) }
157
+
158
+ BitstampOrder.new({
159
+ 'raw' => _id,
160
+ 'executed_volume' => executed_volume,
161
+ 'executed_price' => executed_price / executed_volume,
162
+ 'status' => _status['status']
163
+ })
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,235 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'trade-o-matic/adapters/base/raw_account_order'
4
+ require 'rainbow'
5
+ require 'rainbow/ext/string'
6
+
7
+ module Trader
8
+ class FakeBackend
9
+ class FakeBalance < Struct.new(:amount, :available_amount);
10
+ end
11
+
12
+ class FakeOrder < RawAccountOrder
13
+ attr_mapped(:id, :id)
14
+ attr_mapped(:pair, :pair)
15
+ attr_mapped(:price, :price)
16
+ attr_mapped(:volume) { |r| r[:pend_volume] + r[:traded_volume] }
17
+ attr_mapped(:executed_volume, :traded_volume)
18
+ attr_mapped(:instruction, :instruction)
19
+ attr_mapped(:status, :status)
20
+
21
+ def limit?
22
+ true
23
+ end
24
+ end
25
+
26
+ attr_reader :pair, :asks, :bids, :verbose
27
+
28
+ def open_bids
29
+ bids.select { |o| o[:status] == AccountOrder::OPEN }
30
+ end
31
+
32
+ def open_asks
33
+ asks.select { |o| o[:status] == AccountOrder::OPEN }
34
+ end
35
+
36
+ def open_orders
37
+ open_bids + open_asks
38
+ end
39
+
40
+ def self.instance
41
+ @@instance ||= self.new
42
+ end
43
+
44
+ def self.reset_instance
45
+ @@instance = nil
46
+ end
47
+
48
+ def initialize(_config=nil)
49
+ get_session(_config) unless _config.nil?
50
+ end
51
+
52
+ def get_available_markets
53
+ [@pair]
54
+ end
55
+
56
+ def fill_book(_book)
57
+ # TODO
58
+ end
59
+
60
+ def available_base_balance
61
+ @base_balance
62
+ end
63
+
64
+ def available_quote_balance
65
+ @quote_balance
66
+ end
67
+
68
+ def total_base_balance
69
+ @base_balance + frozen_base
70
+ end
71
+
72
+ def total_quote_balance
73
+ @quote_balance + frozen_quote
74
+ end
75
+
76
+ def get_session(_config)
77
+ raise ArgumentError, 'must provide login information' if _config.nil?
78
+
79
+ if @config.nil?
80
+ @pair = CurrencyPair.for_code(_config[:base], _config[:quote])
81
+ @base_balance = _config[:base_balance]
82
+ @quote_balance = _config[:quote_balance]
83
+ @verbose = !!_config[:verbose]
84
+ @config = _config
85
+ @bids = []
86
+ @asks = []
87
+ @id = 1
88
+ elsif @config != _config
89
+ raise ArgumentError, 'invalid credentials'
90
+ end
91
+
92
+ nil
93
+ end
94
+
95
+ def get_balance(_session, _currency)
96
+ if _currency == pair.base
97
+ FakeBalance.new(total_base_balance, available_base_balance)
98
+ elsif _currency == pair.quote
99
+ FakeBalance.new(total_quote_balance, available_quote_balance)
100
+ else
101
+ raise 'currency not supported'
102
+ end
103
+ end
104
+
105
+ def get_orders(_session, _pair)
106
+ return [] unless _pair == pair
107
+ (@bids + @asks).map { |o| FakeOrder.new o.clone }
108
+ end
109
+
110
+ def create_order(_session, _pair, _volume, _price, _instruction)
111
+ raise 'market not supported' unless _pair == pair
112
+ raise 'market orders not supported' if _price.nil?
113
+
114
+ if _instruction == Order::ASK
115
+ raise 'Not enough funds' if _volume > @base_balance
116
+ @base_balance -= _volume
117
+ else
118
+ raise 'Not enough funds' if _volume * _price > @quote_balance
119
+ @quote_balance -= _volume * _price
120
+ end
121
+
122
+ info "Creating #{_instruction} order for #{_volume} #{pair.base} @ #{_price} #{pair.quote}", :green
123
+
124
+ raw_order = {
125
+ id: @id.to_s,
126
+ pair: pair,
127
+ instruction: _instruction,
128
+ status: AccountOrder::OPEN,
129
+ price: _price,
130
+ pend_volume: _volume,
131
+ traded_volume: 0.0
132
+ }
133
+
134
+ @id += 1
135
+ (_instruction == Order::ASK ? @asks : @bids) << raw_order
136
+
137
+ FakeOrder.new raw_order.clone
138
+ end
139
+
140
+ def fetch_order(_session, _order_id)
141
+ FakeOrder.new find_order_by_id(_order_id).clone
142
+ end
143
+
144
+ def cancel_order(_session, _order_id)
145
+ order = find_order_by_id(_order_id)
146
+
147
+ info "Closing #{order[:instruction]} order for #{order[:pend_volume] + order[:traded_volume]} #{pair.base} @ #{order[:price]} #{pair.quote}", :red
148
+
149
+ if order[:status] == AccountOrder::OPEN
150
+ if order[:instruction] == Order::ASK
151
+ @base_balance += order[:pend_volume]
152
+ else
153
+ @quote_balance += order[:pend_volume] * order[:price]
154
+ end
155
+ order[:status] = AccountOrder::CANCELED
156
+ end
157
+
158
+ FakeOrder.new order.clone
159
+ end
160
+
161
+ def simulate_buy(_price, _volume)
162
+ ordered_asks = @asks.sort { |b| b[:price] * -1 }
163
+ ordered_asks.each do |order|
164
+ if order[:status] == AccountOrder::OPEN and order[:price] <= _price and _volume > 0
165
+ if order[:pend_volume] > _volume
166
+ @quote_balance += _volume * order[:price]
167
+ order[:pend_volume] -= _volume
168
+ order[:traded_volume] += _volume
169
+ _volume = 0
170
+ else
171
+ @quote_balance += order[:pend_volume] * order[:price]
172
+ _volume -= order[:pend_volume]
173
+ order[:traded_volume] += order[:pend_volume]
174
+ order[:pend_volume] = 0
175
+ end
176
+
177
+ if order[:pend_volume] == 0
178
+ order[:status] = AccountOrder::CLOSED
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ def simulate_sell(_price, _volume)
185
+ ordered_bids = @bids.sort { |b| b[:price] }
186
+ ordered_bids.each do |order|
187
+ if order[:status] == AccountOrder::OPEN and order[:price] >= _price and _volume > 0
188
+ if order[:pend_volume] > _volume
189
+ @base_balance += _volume
190
+ order[:pend_volume] -= _volume
191
+ order[:traded_volume] += _volume
192
+ _volume = 0
193
+ else
194
+ @base_balance += order[:pend_volume]
195
+ _volume -= order[:pend_volume]
196
+ order[:traded_volume] += order[:pend_volume]
197
+ order[:pend_volume] = 0
198
+ end
199
+
200
+ if order[:pend_volume] == 0
201
+ order[:status] = AccountOrder::CLOSED
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def info(_msg, _color=:white)
210
+ if verbose
211
+ puts "FakeEx: #{_msg}".color(_color)
212
+ end
213
+ end
214
+
215
+ def frozen_base
216
+ @asks.inject(0) do |r, order|
217
+ next r if order[:status] != AccountOrder::OPEN
218
+ r + order[:pend_volume]
219
+ end
220
+ end
221
+
222
+ def frozen_quote
223
+ @bids.inject(0) do |r, order|
224
+ next r if order[:status] != AccountOrder::OPEN
225
+ r + (order[:pend_volume] * order[:price])
226
+ end
227
+ end
228
+
229
+ def find_order_by_id(_id)
230
+ order = @bids.find { |o| o[:id] == _id }
231
+ order = @asks.find { |o| o[:id] == _id } if order.nil?
232
+ order
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,57 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module Trader
6
+ class ItbitBackend
7
+ def fill_book(_book)
8
+ # TODO: consider book pair
9
+
10
+ _book.prepare Time.now
11
+
12
+ ob = fetch_raw_order_book _book.pair
13
+ ob['bids'].each { |o| _book.add_bid(o[0].to_f, o[1].to_f) }
14
+ ob['asks'].each { |o| _book.add_ask(o[0].to_f, o[1].to_f) }
15
+
16
+ tx = fetch_raw_transactions _book.pair
17
+ tx['recentTrades'].each do |t|
18
+ _book.add_transaction t['price'].to_f, t['amount'].to_f, Time.parse(t['timestamp'])
19
+ end
20
+ end
21
+
22
+ def get_available_markets
23
+ # TODO.
24
+ end
25
+
26
+ def get_session(_credentials)
27
+ _credentials
28
+ end
29
+
30
+ def get_balance(_session, _currency)
31
+ return Price.new(_currency, 0.0) if _currency.code == :BTC
32
+ return Price.new(_currency, 20000.0) if _currency.code == :Bitstamp_USD
33
+ end
34
+
35
+ def get_orders(_session, _pair)
36
+ end
37
+
38
+ def create_order(_session, _pair, _volume, _price, _type)
39
+ end
40
+
41
+ def fetch_order(_session, _order)
42
+ end
43
+
44
+ def cancel_order(_session, _order)
45
+ end
46
+
47
+ private
48
+
49
+ def fetch_raw_order_book(_pair)
50
+ JSON.parse(RestClient.get "https://api.itbit.com/v1/markets/XBTUSD/order_book")
51
+ end
52
+
53
+ def fetch_raw_transactions(_pair)
54
+ JSON.parse(RestClient.get "https://api.itbit.com/v1/markets/XBTUSD/trades")
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'trade-o-matic/adapters/base/raw_account_order'
4
+ require 'trade-o-matic/adapters/base/raw_balance'
5
+
6
+ module Trader
7
+ class SurbtcBackend
8
+ API_PREFIX = 'https://www.surbtc.com/api/v1'
9
+
10
+ TYPE_MAP = {
11
+ 'ask' => Order::ASK,
12
+ 'bid' => Order::BID
13
+ }
14
+
15
+ STATUS_MAP = {
16
+ 'received' => AccountOrder::PENDING,
17
+ 'pending' => AccountOrder::OPEN,
18
+ 'traded' => AccountOrder::CLOSED,
19
+ 'canceling' => AccountOrder::OPEN,
20
+ 'canceled' => AccountOrder::CANCELED
21
+ }
22
+
23
+ MAIN_MARKET = CurrencyPair.new(:SATOSHI, :SURBTC_CLP)
24
+
25
+ class SurbtcBalance < RawBalance
26
+ attr_mapped :amount
27
+ attr_mapped :available_amount
28
+ end
29
+
30
+ class SurbtcBalanceBTC < RawBalance
31
+ attr_mapped(:amount) { |r| r['amount'].to_f }
32
+ attr_mapped(:available_amount) { |r| r['available_amount'].to_f }
33
+ end
34
+
35
+ class SurbtcOrder < RawAccountOrder
36
+ attr_mapped(:id)
37
+ attr_mapped(:pair) { |r| MAIN_MARKET }
38
+ attr_mapped(:price) { |r| r['limit'] }
39
+ attr_mapped(:volume) { |r| r['original_amount'].to_f }
40
+ attr_mapped(:executed_volume) { |r| r['total_exchanged'].to_f }
41
+ attr_mapped(:instruction) { |r| TYPE_MAP[r['type']] }
42
+ attr_mapped(:status) { |r| STATUS_MAP[r['state']] }
43
+ attr_mapped(:limit?) { |r| r['price_type'] == 'limit' }
44
+ end
45
+
46
+ def get_available_markets
47
+ [MAIN_MARKET]
48
+ end
49
+
50
+ def fill_book(_book)
51
+ # TODO
52
+ end
53
+
54
+ def get_session(_credentials)
55
+ _credentials
56
+ end
57
+
58
+ def get_balance(_session, _currency)
59
+ res = resource_for "balances/#{currency_code_for(_currency)}", _session
60
+ raw_balance = postprocess res.get
61
+
62
+ if _currency == MAIN_MARKET.base
63
+ SurbtcBalanceBTC.new raw_balance['balance']
64
+ else
65
+ SurbtcBalance.new raw_balance['balance']
66
+ end
67
+ end
68
+
69
+ def get_orders(_session, _pair)
70
+ res = resource_for "markets/#{market_code_for(_pair)}/orders", _session
71
+
72
+ raw_orders = postprocess res.get
73
+ raw_orders['orders'].map { |o| SurbtcOrder.new o }
74
+ end
75
+
76
+ def create_order(_session, _pair, _volume, _price, _instruction)
77
+ raise 'market not supported' unless _pair == MAIN_MARKET
78
+
79
+ res = resource_for "markets/#{market_code_for(_pair)}/orders", _session
80
+
81
+ raw_order = postprocess res.post({
82
+ order: build_order_json(_price, _volume, _instruction)
83
+ }, :content_type => 'application/json')
84
+
85
+ SurbtcOrder.new raw_order['order']
86
+ end
87
+
88
+ def fetch_order(_session, _order_id)
89
+ res = resource_for "orders/#{_order_id}", _session
90
+
91
+ raw_order = postprocess res.get
92
+ SurbtcOrder.new raw_order['order']
93
+ end
94
+
95
+ def cancel_order(_session, _order_id)
96
+ res = resource_for "orders/#{_order_id}", _session
97
+
98
+ raw_order = postprocess res.put({
99
+ state: 'canceling'
100
+ }, :content_type => 'application/json')
101
+
102
+ while raw_order['order']['state'] == 'canceling'
103
+ raw_order = postprocess res.get
104
+ end
105
+
106
+ SurbtcOrder.new raw_order['order']
107
+ end
108
+
109
+ private
110
+
111
+ def resource_for(_url, _session=nil)
112
+ res = RestClient::Resource.new "#{API_PREFIX}/#{_url}"
113
+ res.options[:headers] = { params: { 'api_key' => _session[:api_key] } } if _session
114
+ res
115
+ end
116
+
117
+ def postprocess(_response)
118
+ # TODO: handle error responses
119
+ JSON.parse _response
120
+ end
121
+
122
+ def currency_code_for(_currency)
123
+ return 'clp' if _currency.code == :SURBTC_CLP
124
+ return 'btc' if _currency.code == :SATOSHI
125
+ _currency.to_s.downcase
126
+ end
127
+
128
+ def market_code_for(_pair)
129
+ "#{currency_code_for(_pair.base)}-#{currency_code_for(_pair.quote)}"
130
+ end
131
+
132
+ def build_order_json(_price, _volume, _instruction)
133
+ if _price.nil?
134
+ {
135
+ amount: _volume,
136
+ type: _instruction == Order::BID ? 'bid' : 'ask',
137
+ price_type: 'market'
138
+ }
139
+ else
140
+ {
141
+ limit: _price,
142
+ amount: _volume,
143
+ type: _instruction == Order::BID ? 'bid' : 'ask',
144
+ price_type: 'limit'
145
+ }
146
+ end
147
+ end
148
+ end
149
+ end