package com.xcong.excoin.utils.api;
|
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.xcong.excoin.utils.api.request.CreateOrderRequest;
|
import com.xcong.excoin.utils.api.request.IntrustOrdersDetailRequest;
|
import com.xcong.excoin.utils.api.response.*;
|
import okhttp3.*;
|
import okhttp3.OkHttpClient.Builder;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import javax.crypto.Mac;
|
import javax.crypto.spec.SecretKeySpec;
|
import javax.xml.bind.DatatypeConverter;
|
import java.io.IOException;
|
import java.io.UnsupportedEncodingException;
|
import java.net.MalformedURLException;
|
import java.net.URL;
|
import java.net.URLEncoder;
|
import java.nio.charset.StandardCharsets;
|
import java.security.InvalidKeyException;
|
import java.security.MessageDigest;
|
import java.security.NoSuchAlgorithmException;
|
import java.time.Instant;
|
import java.time.ZoneId;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.stream.Collectors;
|
|
/**
|
* API client.
|
*
|
* @Date 2018/1/14
|
* @Time 16:02
|
*/
|
public class ApiClient {
|
|
static final int CONN_TIMEOUT = 50;
|
static final int READ_TIMEOUT = 50;
|
static final int WRITE_TIMEOUT = 50;
|
|
|
// static final String API_URL = "https://api.huobi.pro:443";
|
// static final String API_URL = "https://api.huobi.pro";
|
// static final String API_URL = "https://api.hbdm.com";
|
// static final String API_URL = "https://api.huobi.br.com";
|
// static final String API_URL = "https://api.btcgateway.pro";
|
static final String API_URL = "https://api.hadax.com";
|
static final String API_HOST = getHost();
|
|
static final MediaType JSON = MediaType.parse("application/json");
|
static final OkHttpClient client = createOkHttpClient();
|
|
final String accessKeyId;
|
final String accessKeySecret;
|
final String assetPassword;
|
|
/**
|
* 创建一个ApiClient实例
|
*
|
* @param accessKeyId AccessKeyId
|
* @param accessKeySecret AccessKeySecret
|
*/
|
public ApiClient(String accessKeyId, String accessKeySecret) {
|
this.accessKeyId = accessKeyId;
|
this.accessKeySecret = accessKeySecret;
|
this.assetPassword = null;
|
}
|
|
/**
|
* 创建一个ApiClient实例
|
*
|
* @param accessKeyId AccessKeyId
|
* @param accessKeySecret AccessKeySecret
|
* @param assetPassword AssetPassword
|
*/
|
public ApiClient(String accessKeyId, String accessKeySecret, String assetPassword) {
|
this.accessKeyId = accessKeyId;
|
this.accessKeySecret = accessKeySecret;
|
this.assetPassword = assetPassword;
|
}
|
|
/**
|
* 查询交易对
|
*
|
* @return List of symbols.
|
*/
|
public List<Symbol> getSymbols() {
|
ApiResponse<List<Symbol>> resp =
|
get("/v1/common/symbols", null, new TypeReference<ApiResponse<List<Symbol>>>() {
|
});
|
return resp.checkAndReturn();
|
}
|
|
/**
|
* 查询所有账户信息
|
*
|
* @return List of accounts.
|
*/
|
public List<Account> getAccounts() {
|
ApiResponse<List<Account>> resp =
|
get("/v1/account/accounts", null, new TypeReference<ApiResponse<List<Account>>>() {
|
});
|
return resp.checkAndReturn();
|
}
|
|
/**
|
* 创建订单
|
*
|
* @param request CreateOrderRequest object.
|
* @return Order id.
|
*/
|
public Long createOrder(CreateOrderRequest request) {
|
ApiResponse<Long> resp =
|
post("/v1/order/orders/place", request, new TypeReference<ApiResponse<Long>>() {
|
});
|
return resp.checkAndReturn();
|
}
|
|
/**
|
* 执行订单
|
*
|
* @param orderId The id of created order.
|
* @return Order id.
|
*/
|
public String placeOrder(long orderId) {
|
ApiResponse<String> resp = post("/v1/order/orders/" + orderId + "/place", null,
|
new TypeReference<ApiResponse<String>>() {
|
});
|
return resp.checkAndReturn();
|
}
|
|
|
// ----------------------------------------行情API-------------------------------------------
|
|
/**
|
* GET /market/history/kline 获取K线数据
|
*
|
* @param symbol
|
* @param period
|
* @param size
|
* @return
|
*/
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
public KlineResponse kline(String symbol, String period, String size) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
map.put("period", period);
|
map.put("size", size);
|
KlineResponse resp = get("/market/history/kline", map, new TypeReference<KlineResponse<List<Kline>>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /market/detail/merged 获取聚合行情(Ticker)
|
*
|
* @param symbol
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public MergedResponse merged(String symbol) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
MergedResponse resp = get("/market/detail/merged", map, new TypeReference<MergedResponse<List<Merged>>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /market/depth 获取 Market Depth 数据
|
*
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public DepthResponse depth(String symbol, String type, String depth) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
map.put("type", type);
|
map.put("depth", depth);
|
DepthResponse resp = get("/market/depth", map, new TypeReference<DepthResponse<List<Depth>>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /market/trade 获取 Trade Detail 数据
|
*
|
* @param symbol
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public TradeResponse trade(String symbol) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
TradeResponse resp = get("/market/trade", map, new TypeReference<TradeResponse>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /market/history/trade 批量获取最近的交易记录
|
*
|
* @param symbol
|
* @param size
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public HistoryTradeResponse historyTrade(String symbol, String size) {
|
// System.out.println("symbol = "+symbol+" size = "+size);
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
map.put("size", size);
|
HistoryTradeResponse resp = get("/market/history/trade", map, new TypeReference<HistoryTradeResponse>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /market/detail 获取 Market Detail 24小时成交量数据
|
*
|
* @param symbol
|
* @return
|
*/
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
public DetailResponse detail(String symbol) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
DetailResponse resp = get("/market/detail", map, new TypeReference<DetailResponse<Details>>() {
|
});
|
return resp;
|
}
|
|
|
/**
|
* GET /v1/common/symbols 查询系统支持的所有交易对及精度
|
*
|
* @param symbol
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public SymbolsResponse symbols(String symbol) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
SymbolsResponse resp = get("/v1/common/symbols", map, new TypeReference<SymbolsResponse<Symbols>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /v1/common/currencys 查询系统支持的所有币种
|
*
|
* @param symbol
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public CurrencysResponse currencys(String symbol) {
|
HashMap map = new HashMap();
|
map.put("symbol", symbol);
|
CurrencysResponse resp = get("/v1/common/currencys", map, new TypeReference<CurrencysResponse>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /v1/common/timestamp 查询系统当前时间
|
*
|
* @return
|
*/
|
public TimestampResponse timestamp() {
|
TimestampResponse resp = get("/v1/common/timestamp", null, new TypeReference<TimestampResponse>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /v1/account/accounts 查询当前用户的所有账户(即account-id)
|
*
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes"})
|
public AccountsResponse accounts() {
|
AccountsResponse resp = get("/v1/account/accounts", null, new TypeReference<AccountsResponse<List<Accounts>>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /v1/account/accounts/{account-id}/balance 查询指定账户的余额
|
*
|
* @param accountId
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes"})
|
public BalanceResponse balance(String accountId) {
|
BalanceResponse resp = get("/v1/account/accounts/" + accountId + "/balance", null, new TypeReference<BalanceResponse<Balance>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* POST /v1/order/orders/{order-id}/submitcancel 申请撤销一个订单请求
|
*
|
* @param orderId
|
* @return
|
*/
|
public SubmitcancelResponse submitcancel(String orderId) {
|
SubmitcancelResponse resp = post("/v1/order/orders/" + orderId + "/submitcancel", null, new TypeReference<SubmitcancelResponse>() {
|
});
|
return resp;
|
}
|
|
/**
|
* POST /v1/order/orders/batchcancel 批量撤销订单
|
*
|
* @param orderList
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public BatchcancelResponse submitcancels(List orderList) {
|
Map<String, List> parameterMap = new HashMap();
|
parameterMap.put("order-ids", orderList);
|
BatchcancelResponse resp = post("/v1/order/orders/batchcancel", parameterMap, new TypeReference<BatchcancelResponse<Batchcancel<List, List<BatchcancelBean>>>>() {
|
});
|
return resp;
|
}
|
|
/**
|
* GET /v1/order/orders/{order-id} 查询某个订单详情
|
*
|
* @param orderId
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes"})
|
public OrdersDetailResponse ordersDetail(String orderId) {
|
OrdersDetailResponse resp = get("/v1/order/orders/" + orderId, null, new TypeReference<OrdersDetailResponse>() {
|
});
|
return resp;
|
}
|
|
|
/**
|
* GET /v1/order/orders/{order-id}/matchresults 查询某个订单的成交明细
|
*
|
* @param orderId
|
* @return
|
*/
|
@SuppressWarnings({"rawtypes"})
|
public MatchresultsOrdersDetailResponse matchresults(String orderId) {
|
MatchresultsOrdersDetailResponse resp = get("/v1/order/orders/" + orderId + "/matchresults", null, new TypeReference<MatchresultsOrdersDetailResponse>() {
|
});
|
return resp;
|
}
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public IntrustDetailResponse intrustOrdersDetail(IntrustOrdersDetailRequest req) {
|
HashMap map = new HashMap();
|
map.put("symbol", req.symbol);
|
map.put("states", req.states);
|
if (req.startDate != null) {
|
map.put("startDate", req.startDate);
|
}
|
if (req.startDate != null) {
|
map.put("start-date", req.startDate);
|
}
|
if (req.endDate != null) {
|
map.put("end-date", req.endDate);
|
}
|
if (req.types != null) {
|
map.put("types", req.types);
|
}
|
if (req.from != null) {
|
map.put("from", req.from);
|
}
|
if (req.direct != null) {
|
map.put("direct", req.direct);
|
}
|
if (req.size != null) {
|
map.put("size", req.size);
|
}
|
IntrustDetailResponse resp = get("/v1/order/orders/", map, new TypeReference<IntrustDetailResponse<List<IntrustDetail>>>() {
|
});
|
return resp;
|
}
|
|
// public IntrustDetailResponse getALlOrdersDetail(String orderId) {
|
// IntrustDetailResponse resp = get("/v1/order/orders/"+orderId, null,new TypeReference<IntrustDetailResponse>() {});
|
// return resp;
|
// }
|
|
|
// send a GET request.
|
<T> T get(String uri, Map<String, String> params, TypeReference<T> ref) {
|
if (params == null) {
|
params = new HashMap<>();
|
}
|
return call("GET", uri, null, params, ref);
|
}
|
|
// send a POST request.
|
<T> T post(String uri, Object object, TypeReference<T> ref) {
|
return call("POST", uri, object, new HashMap<String, String>(), ref);
|
}
|
|
// call api by endpoint.
|
<T> T call(String method, String uri, Object object, Map<String, String> params,
|
TypeReference<T> ref) {
|
ApiSignature sign = new ApiSignature();
|
sign.createSignature(this.accessKeyId, this.accessKeySecret, method, API_HOST, uri, params);
|
try {
|
Request.Builder builder = null;
|
if ("POST".equals(method)) {
|
RequestBody body = RequestBody.create(JSON, JsonUtil.writeValue(object));
|
builder = new Request.Builder().url(API_URL + uri + "?" + toQueryString(params)).post(body);
|
} else {
|
builder = new Request.Builder().url(API_URL + uri + "?" + toQueryString(params)).get();
|
}
|
if (this.assetPassword != null) {
|
builder.addHeader("AuthData", authData());
|
}
|
Request request = builder.build();
|
Response response = client.newCall(request).execute();
|
String s = response.body().string();
|
//System.out.println("-----s:"+JsonUtil.writeValue(s));
|
return JsonUtil.readValue(s, ref);
|
} catch (IOException e) {
|
// throw new ApiException(e);
|
e.printStackTrace();
|
return null;
|
}
|
}
|
|
String authData() {
|
MessageDigest md = null;
|
try {
|
md = MessageDigest.getInstance("MD5");
|
} catch (NoSuchAlgorithmException e) {
|
throw new RuntimeException(e);
|
}
|
md.update(this.assetPassword.getBytes(StandardCharsets.UTF_8));
|
md.update("hello, moto".getBytes(StandardCharsets.UTF_8));
|
Map<String, String> map = new HashMap<>();
|
map.put("assetPwd", DatatypeConverter.printHexBinary(md.digest()).toLowerCase());
|
try {
|
return ApiSignature.urlEncode(JsonUtil.writeValue(map));
|
} catch (IOException e) {
|
throw new RuntimeException("Get json failed: " + e.getMessage());
|
}
|
}
|
|
// Encode as "a=1&b=%20&c=&d=AAA"
|
String toQueryString(Map<String, String> params) {
|
return String.join("&", params.entrySet().stream().map((entry) -> {
|
return entry.getKey() + "=" + ApiSignature.urlEncode(entry.getValue());
|
}).collect(Collectors.toList()));
|
}
|
|
// create OkHttpClient:
|
static OkHttpClient createOkHttpClient() {
|
return new Builder().connectTimeout(CONN_TIMEOUT, TimeUnit.SECONDS)
|
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS).writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
|
.build();
|
}
|
|
static String getHost() {
|
String host = null;
|
try {
|
host = new URL(API_URL).getHost();
|
} catch (MalformedURLException e) {
|
System.err.println("parse API_URL error,system exit!,please check API_URL:" + API_URL);
|
System.exit(0);
|
}
|
return host;
|
}
|
|
}
|
|
|
/**
|
* API签名,签名规范:
|
* <p>
|
* http://docs.aws.amazon.com/zh_cn/general/latest/gr/signature-version-2.html
|
*
|
* @Date 2018/1/14
|
* @Time 16:02
|
*/
|
class ApiSignature {
|
|
final Logger log = LoggerFactory.getLogger(getClass());
|
|
static final DateTimeFormatter DT_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");
|
static final ZoneId ZONE_GMT = ZoneId.of("Z");
|
|
/**
|
* 创建一个有效的签名。该方法为客户端调用,将在传入的params中添加AccessKeyId、Timestamp、SignatureVersion、SignatureMethod、Signature参数。
|
*
|
* @param appKey AppKeyId.
|
* @param appSecretKey AppKeySecret.
|
* @param method 请求方法,"GET"或"POST"
|
* @param host 请求域名,例如"be.huobi.com"
|
* @param uri 请求路径,注意不含?以及后的参数,例如"/v1/api/info"
|
* @param params 原始请求参数,以Key-Value存储,注意Value不要编码
|
*/
|
public void createSignature(String appKey, String appSecretKey, String method, String host,
|
String uri, Map<String, String> params) {
|
StringBuilder sb = new StringBuilder(1024);
|
sb.append(method.toUpperCase()).append('\n') // GET
|
.append(host.toLowerCase()).append('\n') // Host
|
.append(uri).append('\n'); // /path
|
params.remove("Signature");
|
params.put("AccessKeyId", appKey);
|
params.put("SignatureVersion", "2");
|
params.put("SignatureMethod", "HmacSHA256");
|
params.put("Timestamp", gmtNow());
|
// build signature:
|
SortedMap<String, String> map = new TreeMap<>(params);
|
for (Map.Entry<String, String> entry : map.entrySet()) {
|
String key = entry.getKey();
|
String value = entry.getValue();
|
sb.append(key).append('=').append(urlEncode(value)).append('&');
|
}
|
// remove last '&':
|
sb.deleteCharAt(sb.length() - 1);
|
// sign:
|
Mac hmacSha256 = null;
|
try {
|
hmacSha256 = Mac.getInstance("HmacSHA256");
|
SecretKeySpec secKey =
|
new SecretKeySpec(appSecretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
hmacSha256.init(secKey);
|
} catch (NoSuchAlgorithmException e) {
|
throw new RuntimeException("No such algorithm: " + e.getMessage());
|
} catch (InvalidKeyException e) {
|
throw new RuntimeException("Invalid key: " + e.getMessage());
|
}
|
String payload = sb.toString();
|
byte[] hash = hmacSha256.doFinal(payload.getBytes(StandardCharsets.UTF_8));
|
String actualSign = Base64.getEncoder().encodeToString(hash);
|
params.put("Signature", actualSign);
|
|
|
if (log.isDebugEnabled()) {
|
log.debug("Dump parameters:");
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
log.debug(" key: " + entry.getKey() + ", value: " + entry.getValue());
|
}
|
}
|
}
|
|
|
/**
|
* 使用标准URL Encode编码。注意和JDK默认的不同,空格被编码为%20而不是+。
|
*
|
* @param s String字符串
|
* @return URL编码后的字符串
|
*/
|
public static String urlEncode(String s) {
|
try {
|
return URLEncoder.encode(s, "UTF-8").replaceAll("\\+", "%20");
|
} catch (UnsupportedEncodingException e) {
|
throw new IllegalArgumentException("UTF-8 encoding not supported!");
|
}
|
}
|
|
/**
|
* Return epoch seconds
|
*/
|
long epochNow() {
|
return Instant.now().getEpochSecond();
|
}
|
|
String gmtNow() {
|
return Instant.ofEpochSecond(epochNow()).atZone(ZONE_GMT).format(DT_FORMAT);
|
}
|
}
|
|
|
class JsonUtil {
|
|
public static String writeValue(Object obj) throws IOException {
|
return objectMapper.writeValueAsString(obj);
|
}
|
|
public static <T> T readValue(String s, TypeReference<T> ref) throws IOException {
|
return objectMapper.readValue(s, ref);
|
}
|
|
static final ObjectMapper objectMapper = createObjectMapper();
|
|
static ObjectMapper createObjectMapper() {
|
final ObjectMapper mapper = new ObjectMapper();
|
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
|
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
// disabled features:
|
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
return mapper;
|
}
|
|
}
|