1 package org.oxerr.okcoin.rest.service.web;
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.ArrayList;
8 import java.util.List;
9
10 import org.apache.http.Consts;
11 import org.apache.http.NameValuePair;
12 import org.apache.http.client.entity.UrlEncodedFormEntity;
13 import org.apache.http.client.methods.HttpPost;
14 import org.apache.http.client.utils.URIBuilder;
15 import org.apache.http.client.utils.URIUtils;
16 import org.apache.http.entity.ContentType;
17 import org.apache.http.message.BasicNameValuePair;
18 import org.oxerr.okcoin.rest.dto.Depth;
19 import org.oxerr.okcoin.rest.dto.Funds;
20 import org.oxerr.okcoin.rest.dto.IcebergOrderHistory;
21 import org.oxerr.okcoin.rest.dto.Result;
22 import org.oxerr.okcoin.rest.dto.Ticker;
23 import org.oxerr.okcoin.rest.dto.TickerResponse;
24 import org.oxerr.okcoin.rest.dto.Trade;
25 import org.oxerr.okcoin.rest.dto.TradeParam;
26 import org.oxerr.okcoin.rest.dto.valuereader.IcebergOrdersReader;
27 import org.oxerr.okcoin.rest.dto.valuereader.IndexHtmlPageReader;
28 import org.oxerr.okcoin.rest.dto.valuereader.PlainTextReader;
29 import org.oxerr.okcoin.rest.dto.valuereader.ResultValueReader;
30 import org.oxerr.okcoin.rest.dto.valuereader.VoidValueReader;
31 import org.oxerr.okcoin.rest.service.OKCoinAccountService;
32 import org.oxerr.okcoin.rest.service.OKCoinMarketDataService;
33 import org.oxerr.okcoin.rest.service.OKCoinTradeService;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import com.fasterxml.jackson.core.type.TypeReference;
38
39 public class OKCoinClient implements AutoCloseable {
40
41 public static final String ENCODING = "UTF-8";
42
43 public static final ContentType APPLICATION_FORM_URLENCODED = ContentType.create(
44 "application/x-www-form-urlencoded", Consts.UTF_8);
45
46 private static final URI HTTPS_BASE = URI.create("https://www.okcoin.cn/");
47 private static final URI API_BASE = URIUtils.resolve(HTTPS_BASE, "api/");
48
49 private static final URI TICKER_URI = URIUtils.resolve(API_BASE, "ticker.do");
50 private static final URI DEPTH_URI = URIUtils.resolve(API_BASE, "depth.do");
51 private static final URI TRADES_URI = URIUtils.resolve(API_BASE, "trades.do");
52
53 private static final URI LOGIN_URI = URIUtils.resolve(HTTPS_BASE, "login/index.do");
54 private static final URI LOGOUT_URI = URIUtils.resolve(HTTPS_BASE, "user/logout.do");
55
56 private static final URI BUY_BTC_SUBMIT_URI = URIUtils.resolve(HTTPS_BASE, "trade/buyBtcSubmit.do");
57 private static final URI SELL_BTC_SUBMIT_URI = URIUtils.resolve(HTTPS_BASE, "trade/sellBtcSubmit.do");
58
59 private static final URI SUBMIT_CONTINUOUS_ENTRUST_URI = URIUtils.resolve(HTTPS_BASE, "strategy/submitContinousEntrust.do");
60 private static final URI CANCEL_CONTINUOUS_ENTRUST_URI = URIUtils.resolve(HTTPS_BASE, "strategy/cancelContinuousEntrust.do");
61
62 private static final String TRADE_BTC_REFERER_PREFIX = HTTPS_BASE.toString() + "trade/btc.do?tradeType=";
63
64 private static final BigDecimal MIN_TRADE_AMOUNT = new BigDecimal("0.01");
65
66 private final Logger log = LoggerFactory.getLogger(OKCoinClient.class);
67
68 private final HttpClient httpClient;
69
70 private String loginName;
71
72 private String password;
73
74 private String tradePwd;
75
76 public OKCoinClient(
77 int socketTimeout,
78 int connectTimeout,
79 int connectionRequestTimeout) {
80 this(null, null, null,
81 socketTimeout, connectTimeout, connectionRequestTimeout);
82 }
83
84 public OKCoinClient(String loginName, String password,
85 int socketTimeout,
86 int connectTimeout,
87 int connectionRequestTimeout) {
88 this(loginName, password, null,
89 socketTimeout, connectTimeout, connectionRequestTimeout);
90 }
91
92 public OKCoinClient(String loginName, String password, String tradePwd,
93 int socketTimeout,
94 int connectTimeout,
95 int connectionRequestTimeout) {
96 httpClient = new HttpClient(
97 socketTimeout, connectTimeout, connectionRequestTimeout);
98
99 this.loginName = loginName;
100 this.password = password;
101 this.tradePwd = tradePwd;
102 }
103
104
105
106
107
108
109
110
111 @Deprecated
112 public Ticker getTicker() throws IOException {
113 return httpClient.get(TICKER_URI, TickerResponse.class).getTicker();
114 }
115
116
117
118
119
120
121 @Deprecated
122 public Depth getDepth() throws IOException {
123 return httpClient.get(DEPTH_URI, Depth.class);
124 }
125
126
127
128
129
130
131
132
133 @Deprecated
134 public List<Trade> getTrades() throws IOException {
135 return httpClient.get(TRADES_URI, new TypeReference<List<Trade>>() {
136 });
137 }
138
139
140
141
142
143
144
145
146
147 @Deprecated
148 public List<Trade> getTrades(int since) throws IOException {
149 URIBuilder builder = new URIBuilder(TRADES_URI);
150 builder.addParameter("since", Integer.toString(since));
151
152 final URI uri = toURI(builder);
153
154 return httpClient.get(uri, new TypeReference<List<Trade>>() {
155 });
156 }
157
158 public void login() throws IOException {
159 initLoginPage();
160
161 URIBuilder builder = new URIBuilder(LOGIN_URI);
162 builder.setParameter("random", randomInString());
163 URI uri = toURI(builder);
164
165 Result result = httpClient.post(
166 uri,
167 ResultValueReader.getInstance(),
168 new BasicNameValuePair("loginName", loginName),
169 new BasicNameValuePair("password", password));
170
171 log.debug("Login result: {}", result);
172
173 switch(result.getResultCode()) {
174 case -1:
175 throw new OKCoinClientException("用户名或密码错误");
176 case -2:
177 throw new OKCoinClientException("此ip登录频繁,请2小时后再试");
178 case -3:
179 if (result.getErrorNum() == 0) {
180 throw new OKCoinClientException("此ip登录频繁,请2小时后再试");
181 } else {
182 throw new OKCoinClientException("用户名或密码错误,您还有" + result.getErrorNum() + "次机会");
183 }
184 case -4:
185 throw new OKCoinClientException("请设置启用COOKIE功能");
186 case 1:
187 break;
188 case 2:
189 throw new OKCoinClientException("账户出现安全隐患被冻结,请尽快联系客服。");
190 }
191 }
192
193 public void logout() throws IOException {
194 httpClient.get(LOGOUT_URI, VoidValueReader.getInstance());
195 }
196
197
198
199
200
201
202
203
204 @Deprecated
205 public Funds getBalance() throws IOException {
206 return httpClient.get(URIUtils.resolve(HTTPS_BASE, "/trade/btc.do"),
207 new IndexHtmlPageReader());
208 }
209
210 public BigDecimal getMinTradeAmount() {
211 return MIN_TRADE_AMOUNT;
212 }
213
214
215
216
217
218
219
220
221
222 @Deprecated
223 public void bid(BigDecimal amount, BigDecimal cnyPrice) throws IOException {
224 final int tradeType = 0;
225 final int symbol = 0;
226 final int isopen = 0;
227
228 trade(amount, cnyPrice, tradeType, symbol, isopen);
229 }
230
231
232
233
234
235
236
237
238
239 @Deprecated
240 public void ask(BigDecimal amount, BigDecimal cnyPrice) throws IOException {
241 final int tradeType = 1;
242 final int symbol = 0;
243 final int isopen = 0;
244
245 trade(amount, cnyPrice, tradeType, symbol, isopen);
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public Result submitContinuousEntrust(int symbol, int type,
262 BigDecimal tradeValue, BigDecimal singleAvg, BigDecimal depthRange,
263 BigDecimal protePrice) throws LoginRequiredException, IOException {
264 URI uri = randomUri(SUBMIT_CONTINUOUS_ENTRUST_URI);
265
266 HttpPost post = new HttpPost(uri);
267 post.setHeader("X-Requested-With", "XMLHttpRequest");
268 String referer = URIUtils.resolve(HTTPS_BASE, "trade/btc.do").toString();
269 log.debug("Add referer header: {}", referer);
270 post.setHeader("Referer", referer);
271
272 List<NameValuePair> params = new ArrayList<>(6);
273 params.add(new BasicNameValuePair("symbol", String.valueOf(symbol)));
274 params.add(new BasicNameValuePair("type", String.valueOf(type)));
275 params.add(new BasicNameValuePair("tradeValue", tradeValue.toPlainString()));
276 params.add(new BasicNameValuePair("singleAvg", singleAvg.toPlainString()));
277 params.add(new BasicNameValuePair("depthRange", depthRange.toPlainString()));
278 params.add(new BasicNameValuePair("protePrice", protePrice.toPlainString()));
279 params.add(new BasicNameValuePair("tradePwd", tradePwd == null ? "" : tradePwd));
280
281 post.setEntity(new UrlEncodedFormEntity(params));
282 return httpClient.execute(ResultValueReader.getInstance(), post);
283 }
284
285
286
287
288
289
290
291
292
293
294 public boolean cancelContinuousEntrust(int symbol, long id)
295 throws LoginRequiredException, IOException {
296 URI uri = randomUri(CANCEL_CONTINUOUS_ENTRUST_URI);
297 HttpPost post = new HttpPost(uri);
298 post.setHeader("X-Requested-With", "XMLHttpRequest");
299 String referer = URIUtils.resolve(HTTPS_BASE, "trade/btc.do").toString();
300 log.debug("Add referer header: {}", referer);
301 post.setHeader("Referer", referer);
302
303 List<NameValuePair> params = new ArrayList<>(2);
304 params.add(new BasicNameValuePair("symbol", String.valueOf(symbol)));
305 params.add(new BasicNameValuePair("id", String.valueOf(id)));
306
307 post.setEntity(new UrlEncodedFormEntity(params));
308 String result = httpClient.execute(PlainTextReader.getInstance(), post);
309 log.debug("result: {}", result);
310 if (result.length() == 0) {
311 throw new LoginRequiredException();
312 }
313 return Integer.parseInt(result) == 0;
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327
328 public IcebergOrderHistory getIcebergOrders(int symbol, int type, int sign,
329 int strategyType, Integer page) throws LoginRequiredException, IOException {
330 URIBuilder builder = new URIBuilder(URIUtils.resolve(HTTPS_BASE, "strategy/refrushRecordNew.do"))
331 .setParameter("symbol", String.valueOf(symbol))
332 .setParameter("type", String.valueOf(type))
333 .setParameter("sign", String.valueOf(sign))
334 .setParameter("strategyType", String.valueOf(strategyType));
335 if (page != null) {
336 builder.setParameter("_data_ice", null);
337 builder.setParameter("currentPage", page.toString());
338 builder.setParameter("_", String.valueOf(System.currentTimeMillis()));
339 }
340 URI uri = toURI(builder);
341 log.debug("uri: {}", uri);
342 return httpClient.get(uri, IcebergOrdersReader.getInstance());
343 }
344
345
346
347
348 @Override
349 public void close() throws IOException {
350 logout();
351 httpClient.close();
352 }
353
354
355
356
357
358
359 private void initLoginPage() throws IOException {
360 httpClient.get(HTTPS_BASE, VoidValueReader.getInstance());
361 }
362
363
364
365
366
367
368
369
370
371
372
373 private void trade(BigDecimal tradeAmount, BigDecimal tradeCnyPrice,
374 int tradeType, int symbol, int isopen) throws IOException {
375 if (tradeAmount.compareTo(MIN_TRADE_AMOUNT) < 0) {
376 final String error;
377 if (symbol == 1) {
378 error = "最小交易数量为:0.1LTC!";
379 } else {
380 error = "最小交易数量为:0.01BTC!";
381 }
382 throw new IllegalArgumentException(error);
383 }
384
385 final URI submitUri;
386 if (tradeType == 0) {
387 submitUri = BUY_BTC_SUBMIT_URI;
388 } else {
389 submitUri = SELL_BTC_SUBMIT_URI;
390 }
391
392 final URI uri = randomUri(submitUri);
393
394 TradeParam tradeParam = new TradeParam(
395 tradeAmount, tradeCnyPrice, tradePwd, symbol);
396
397 final HttpPost post = new HttpPost(uri);
398 post.setHeader("X-Requested-With", "XMLHttpRequest");
399 final String referer = TRADE_BTC_REFERER_PREFIX + tradeType;
400 log.debug("Add referer header: {}", referer);
401 post.setHeader("Referer", referer);
402 post.setEntity(new UrlEncodedFormEntity(tradeParam.toNameValurPairs()));
403
404 Result result = httpClient.execute(ResultValueReader.getInstance(), post);
405
406 final String error;
407 if (result != null) {
408 log.debug("Result: {}", result);
409
410 if (result.getResultCode() == -1) {
411 if (tradeType == 0) {
412 if (symbol == 1) {
413 error = "最小购买数量为:0.1LTC!";
414 } else {
415 error = "最小购买数量为:0.01BTC!";
416 }
417 } else {
418 if (symbol == 1) {
419 error = "最小卖出数量为:0.1LTC!";
420 } else {
421 error = "最小卖出数量为:0.01BTC!";
422 }
423 }
424 } else if (result.getResultCode() == -2) {
425 if (result.getErrorNum() == 0) {
426 error = "交易密码错误五次,请2小时后再试!";
427 } else {
428 error = "交易密码不正确!您还有" + result.getErrorNum() + "次机会";
429 }
430 } else if (result.getResultCode() == -3) {
431 error = "出价不能为0!";
432 } else if (result.getResultCode() == -4) {
433 error = "余额不足!";
434 } else if (result.getResultCode() == -5) {
435 error = "您未设置交易密码,请设置交易密码。";
436 } else if (result.getResultCode() == -6) {
437 error = "您输入的价格与最新成交价相差太大,请检查是否输错";
438 } else if (result.getResultCode() == -7) {
439 error = "交易密码免输超时,请刷新页面输入交易密码后重新激活。";
440 } else if (result.getResultCode() == -8) {
441 error = "请输入交易密码";
442 } else if (result.getResultCode() == 0) {
443
444 error = null;
445 } else if (result.getResultCode() == 2) {
446 error = "账户出现安全隐患已被冻结,请尽快联系客服。";
447 } else {
448 error = null;
449 }
450 } else {
451 error = null;
452 }
453
454 if (error != null) {
455 throw new OKCoinClientException(error);
456 }
457 }
458
459 private String randomInString() {
460 return Long.toString(random());
461 }
462
463 private long random() {
464 long random = Math.round(Math.random() * 100);
465 return random;
466 }
467
468
469
470
471
472 private URI randomUri(URI uri) throws IOException {
473 final URI ret = toURI(new URIBuilder(uri)
474 .setParameter("random", randomInString()));
475 return ret;
476 }
477
478 private URI toURI(URIBuilder builder) {
479 try {
480 return builder.build();
481 } catch (URISyntaxException e) {
482 throw new IllegalArgumentException(e);
483 }
484 }
485
486 }