1 package org.oxerr.chbtc;
2
3 import java.io.IOException;
4 import java.math.BigDecimal;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Map;
11
12 import org.apache.http.NameValuePair;
13 import org.apache.http.client.utils.URIBuilder;
14 import org.apache.http.client.utils.URIUtils;
15 import org.apache.http.message.BasicNameValuePair;
16 import org.oxerr.chbtc.dto.AccountInfo;
17 import org.oxerr.chbtc.dto.CHBTCError;
18 import org.oxerr.chbtc.dto.Depth;
19 import org.oxerr.chbtc.dto.Order;
20 import org.oxerr.chbtc.dto.OrderResponse;
21 import org.oxerr.chbtc.dto.Ticker;
22 import org.oxerr.chbtc.dto.TickerResponse;
23 import org.oxerr.chbtc.dto.Trade;
24 import org.oxerr.chbtc.dto.Type;
25 import org.oxerr.chbtc.dto.valuereader.DepthReader;
26 import org.oxerr.chbtc.dto.valuereader.ErrorableJsonValueTypeRefReader;
27 import org.oxerr.chbtc.dto.valuereader.ValueReader;
28 import org.oxerr.chbtc.service.polling.CHBTCMarketDataService;
29 import org.oxerr.chbtc.util.EncryDigestUtil;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import com.fasterxml.jackson.core.type.TypeReference;
34 import com.fasterxml.jackson.databind.ObjectMapper;
35 import com.xeiam.xchange.currency.Currencies;
36 import com.xeiam.xchange.currency.CurrencyPair;
37
38 public class CHBTCClient implements AutoCloseable {
39
40 public static final String ENCODING = "UTF-8";
41
42 @Deprecated
43 private static final URI API_BASE_URI = URI.create("http://api.chbtc.com/");
44
45 @Deprecated
46 private static final URI DATA_BASE_URI = URIUtils.resolve(API_BASE_URI, "data/");
47
48 @Deprecated
49 private static final URI TICKER_URI = URIUtils.resolve(DATA_BASE_URI, "ticker");
50
51 @Deprecated
52 private static final URI DEPTH_URI = URIUtils.resolve(DATA_BASE_URI, "depth");
53
54 @Deprecated
55 private static final URI TRADES_URI = URIUtils.resolve(DATA_BASE_URI, "trades");
56
57 @Deprecated
58 private static final URI LTC_DATA_BASE_URI = URIUtils.resolve(DATA_BASE_URI, "ltc");
59
60 @Deprecated
61 private static final URI LTC_TICKER_URI = URIUtils.resolve(LTC_DATA_BASE_URI, "ticker");
62
63 @Deprecated
64 private static final URI LTC_DEPTH_URI = URIUtils.resolve(LTC_DATA_BASE_URI, "depth");
65
66 @Deprecated
67 private static final URI LTC_TRADES_URI = URIUtils.resolve(LTC_DATA_BASE_URI, "trades");
68
69 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
70
71 @Deprecated
72 private static final DepthReader DEPTH_READER = new DepthReader(OBJECT_MAPPER);
73
74 @Deprecated
75 private static final TypeReference<List<Trade>> TRADE_LIST_TYPE_REFERENCE = new TypeReference<List<Trade>>() {
76 };
77
78 private static final TypeReference<List<Order>> ORDER_LIST_TYPE_REFERENCE = new TypeReference<List<Order>>() {
79 };
80
81 private static final ValueReader<List<Order>> ORDER_LIST_READER = new ErrorableJsonValueTypeRefReader<>(OBJECT_MAPPER, ORDER_LIST_TYPE_REFERENCE);
82
83 private final Logger log = LoggerFactory.getLogger(CHBTCClient.class);
84
85 private final Map<String, Long> lastReqTime = new HashMap<>(8);
86
87 private final HttpClient httpClient;
88
89 private final String tradeApiUrl;
90
91 private final String accessKey;
92
93 private final String secret;
94
95 public CHBTCClient(
96 final String accessKey,
97 final String secretKey,
98 final int socketTimeout,
99 final int connectTimeout,
100 final int connectionRequestTimeout) {
101 this("https://trade.chbtc.com/api", accessKey, secretKey,
102 socketTimeout, connectTimeout, connectionRequestTimeout);
103 }
104
105 public CHBTCClient(
106 final String tradeApiUrl,
107 final String accessKey,
108 final String secretKey,
109 final int socketTimeout,
110 final int connectTimeout,
111 final int connectionRequestTimeout) {
112 this.tradeApiUrl = tradeApiUrl;
113
114 httpClient = new HttpClient(
115 socketTimeout,
116 connectTimeout,
117 connectionRequestTimeout);
118
119 this.accessKey = accessKey;
120 this.secret = EncryDigestUtil.digest(secretKey);
121 }
122
123
124
125
126
127
128
129
130 @Deprecated
131 public Ticker getTicker() throws IOException {
132 return httpClient.get(TICKER_URI, TickerResponse.class).getTicker();
133 }
134
135
136
137
138
139
140
141
142
143 @Deprecated
144 public Ticker getTicker(String currency) throws IOException {
145 if (currency.equalsIgnoreCase(Currencies.LTC)) {
146 return httpClient.get(LTC_TICKER_URI, TickerResponse.class)
147 .getTicker();
148 } else {
149 return getTicker();
150 }
151 }
152
153
154
155
156
157
158
159
160 @Deprecated
161 public Depth getDepth() throws IOException {
162 return httpClient.get(DEPTH_URI, DEPTH_READER);
163 }
164
165
166
167
168
169
170
171
172
173 @Deprecated
174 public Depth getDepth(String currency) throws IOException {
175 if (currency.equalsIgnoreCase(Currencies.LTC)) {
176 return httpClient.get(LTC_DEPTH_URI, DEPTH_READER);
177 } else {
178 return getDepth();
179 }
180 }
181
182
183
184
185
186
187
188
189 @Deprecated
190 public List<Trade> getTrades() throws IOException {
191 return httpClient.get(TRADES_URI, TRADE_LIST_TYPE_REFERENCE);
192 }
193
194
195
196
197
198
199
200
201
202 @Deprecated
203 public List<Trade> getTrades(int since) throws IOException {
204 final URI uri;
205 try {
206 uri = new URIBuilder(TRADES_URI)
207 .setParameter("since", String.valueOf(since))
208 .build();
209 } catch (URISyntaxException e) {
210 throw new IllegalArgumentException(e);
211 }
212
213 return httpClient.get(uri, TRADE_LIST_TYPE_REFERENCE);
214 }
215
216
217
218
219
220
221
222
223
224 @Deprecated
225 public List<Trade> getTrades(String currency) throws IOException {
226 if (currency.equalsIgnoreCase(Currencies.LTC)) {
227 return httpClient.get(LTC_TRADES_URI, TRADE_LIST_TYPE_REFERENCE);
228 } else {
229 return getTrades();
230 }
231 }
232
233
234
235
236
237
238
239
240
241
242 @Deprecated
243 public List<Trade> getTrades(
244 String currency,
245 int since
246 ) throws IOException {
247 if (currency.equalsIgnoreCase(Currencies.LTC)) {
248 final URI uri;
249 try {
250 uri = new URIBuilder(LTC_TRADES_URI)
251 .setParameter("since", String.valueOf(since))
252 .build();
253 } catch (URISyntaxException e) {
254 throw new IllegalArgumentException(e);
255 }
256
257 return httpClient.get(uri, TRADE_LIST_TYPE_REFERENCE);
258 } else {
259 return getTrades(since);
260 }
261 }
262
263
264
265
266 @Override
267 public void close() throws IOException {
268 log.debug("Closing HTTP Client...");
269 httpClient.close();
270 }
271
272
273
274
275
276
277
278
279
280
281
282 public long order(
283 final BigDecimal price,
284 final BigDecimal amount,
285 final Type tradeType,
286 final String currency
287 ) throws IOException {
288 OrderResponse orderResponse = get(
289 CHBTC.METHOD_ORDER,
290 OrderResponse.class,
291 new BasicNameValuePair("price", price.toPlainString()),
292 new BasicNameValuePair("amount", amount.toPlainString()),
293 new BasicNameValuePair("tradeType",
294 String.valueOf(tradeType.getTradeType())),
295 new BasicNameValuePair("currency", currency));
296 if (orderResponse.getCode() == CHBTCError.SUCCESS) {
297 return orderResponse.getId();
298 } else {
299 throw new CHBTCClientException(orderResponse);
300 }
301 }
302
303
304
305
306
307
308
309
310 public void cancelOrder(
311 final long id,
312 final String currency
313 ) throws IOException {
314 CHBTCError error = get(
315 CHBTC.METHOD_CANCEL_ORDER,
316 CHBTCError.class,
317 new BasicNameValuePair("id", String.valueOf(id)),
318 new BasicNameValuePair("currency", currency));
319
320 if (error.getCode() != CHBTCError.SUCCESS) {
321 throw new CHBTCClientException(error);
322 }
323 }
324
325
326
327
328
329
330
331
332
333 public Order getOrder(
334 final long id,
335 final String currency
336 ) throws IOException {
337 return get(
338 CHBTC.METHOD_GET_ORDER,
339 Order.class,
340 new BasicNameValuePair("id", String.valueOf(id)),
341 new BasicNameValuePair("currency", currency));
342 }
343
344
345
346
347
348
349
350
351
352
353 public List<Order> getOrders(
354 final Type tradeType,
355 final String currency,
356 final int pageIndex
357 ) throws IOException {
358 return get(
359 CHBTC.METHOD_GET_ORDERS,
360 ORDER_LIST_READER,
361 new BasicNameValuePair("tradeType",
362 String.valueOf(tradeType.getTradeType())),
363 new BasicNameValuePair("currency", currency),
364 new BasicNameValuePair("pageIndex", String.valueOf(pageIndex)));
365 }
366
367
368
369
370
371
372
373
374
375
376
377 public List<Order> getOrdersNew(
378 final Type tradeType,
379 final String currency,
380 final int pageIndex,
381 final int pageSize
382 ) throws IOException {
383 return get(
384 CHBTC.METHOD_GET_ORDERS_NEW,
385 ORDER_LIST_READER,
386 new BasicNameValuePair("tradeType",
387 String.valueOf(tradeType.getTradeType())),
388 new BasicNameValuePair("currency", currency),
389 new BasicNameValuePair("pageIndex", String.valueOf(pageIndex)),
390 new BasicNameValuePair("pageSize", String.valueOf(pageSize)));
391 }
392
393
394
395
396
397
398
399
400
401
402 public List<Order> getOrdersIgnoreTradeType(
403 final String currency,
404 final int pageIndex,
405 final int pageSize
406 ) throws IOException {
407 return get(
408 CHBTC.METHOD_GET_ORDERS_IGNORE_TRADE_TYPE,
409 ORDER_LIST_READER,
410 new BasicNameValuePair("currency", currency),
411 new BasicNameValuePair("pageIndex", String.valueOf(pageIndex)),
412 new BasicNameValuePair("pageSize", String.valueOf(pageSize)));
413 }
414
415
416
417
418
419
420
421
422
423
424 public List<Order> getUnfinishedOrdersIgnoreTradeType(
425 final String currency,
426 final int pageIndex,
427 final int pageSize
428 ) throws IOException {
429 try {
430 return getUnfinishedOrdersIgnoreTradeTypeInternal(currency,
431 pageIndex, pageSize);
432 } catch (CHBTCClientException e) {
433 if (e.getErrorCode() == CHBTCError.NOT_FOUND_ORDER) {
434 return Collections.emptyList();
435 } else {
436 throw e;
437 }
438 }
439 }
440
441
442
443
444
445
446 public AccountInfo getAccountInfo() throws IOException {
447 return get(CHBTC.METHOD_GET_ACCOUNT_INFO, AccountInfo.class);
448 }
449
450 private List<Order> getUnfinishedOrdersIgnoreTradeTypeInternal(
451 final String currency, final int pageIndex, final int pageSize)
452 throws IOException {
453 return get(
454 CHBTC.METHOD_GET_UNFINISHED_ORDERS_IGNORE_TRADE_TYPE,
455 ORDER_LIST_READER,
456 new BasicNameValuePair("currency", currency),
457 new BasicNameValuePair("pageIndex", String.valueOf(pageIndex)),
458 new BasicNameValuePair("pageSize", String.valueOf(pageSize)));
459 }
460
461 private <T> T get(
462 String method,
463 Class<T> valueType,
464 NameValuePair... params
465 ) throws IOException {
466 sleepIfRequired(method);
467
468 URI uri = buildTradeUri(method, params);
469 T t = httpClient.get(uri, valueType);
470
471 this.lastReqTime.put(method, System.currentTimeMillis());
472
473 return t;
474 }
475
476 private <T> T get(
477 String method,
478 ValueReader<T> valueReader,
479 NameValuePair... params
480 ) throws IOException {
481 sleepIfRequired(method);
482
483 URI uri = buildTradeUri(method, params);
484 T t = httpClient.get(uri, valueReader);
485
486 this.lastReqTime.put(method, System.currentTimeMillis());
487
488 return t;
489 }
490
491 private URI buildTradeUri(String method, NameValuePair... nameValuePairs) {
492 final StringBuilder paramsBuilder = new StringBuilder("method=")
493 .append(method)
494 .append("&accesskey=").append(accessKey);
495 for (NameValuePair pair : nameValuePairs) {
496 paramsBuilder.append("&")
497 .append(pair.getName()).append("=").append(pair.getValue());
498 }
499
500 final String params = paramsBuilder.toString();
501
502 final String sign = EncryDigestUtil.hmacSign(params, secret);
503
504 final String uri = new StringBuilder(tradeApiUrl)
505 .append("/")
506 .append(method)
507 .append("?")
508 .append(params)
509 .append("&sign=").append(sign)
510 .append("&reqTime=").append(System.currentTimeMillis())
511 .toString();
512
513 return URI.create(uri);
514 }
515
516
517
518
519 private void sleepIfRequired(String method) {
520 Long last = lastReqTime.get(method);
521 long now = System.currentTimeMillis();
522 if (last != null && now - last.longValue() < 1000) {
523 sleepQuietly(1000 - (now - last.longValue()));
524 }
525 }
526
527 private void sleepQuietly(long millis) {
528 try {
529 log.debug("Sleeping {} ms.", millis);
530 Thread.sleep(millis);
531 } catch (InterruptedException e) {
532
533 }
534 }
535
536 }