|
|
@@ -1,5 +1,7 @@
|
|
|
package top.lvzhiqiang.service.impl;
|
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.xxl.job.core.context.XxlJobHelper;
|
|
|
@@ -10,22 +12,28 @@ import org.springframework.http.HttpHeaders;
|
|
|
import org.springframework.http.HttpMethod;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
import top.lvzhiqiang.config.InitRunner;
|
|
|
import top.lvzhiqiang.entity.CoinGateSpotOrders;
|
|
|
import top.lvzhiqiang.mapper.CoinGateMapper;
|
|
|
import top.lvzhiqiang.service.CoinGateService;
|
|
|
+import top.lvzhiqiang.util.DateUtils;
|
|
|
import top.lvzhiqiang.util.StringUtils;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
import javax.crypto.Mac;
|
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.math.MathContext;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.net.URI;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.security.MessageDigest;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
+import java.util.concurrent.ForkJoinPool;
|
|
|
|
|
|
/**
|
|
|
* Coin Gate ServiceImpl
|
|
|
@@ -43,6 +51,9 @@ public class CoinGateServiceImpl implements CoinGateService {
|
|
|
private final RestTemplate restTemplate;
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
|
|
+ private final ForkJoinPool forkJoinPool = new ForkJoinPool(16);
|
|
|
+ private final ForkJoinPool forkJoinPool2 = new ForkJoinPool(16);
|
|
|
+
|
|
|
public CoinGateServiceImpl(RestTemplate restTemplate, ObjectMapper objectMapper) {
|
|
|
this.restTemplate = restTemplate;
|
|
|
this.objectMapper = objectMapper;
|
|
|
@@ -144,6 +155,219 @@ public class CoinGateServiceImpl implements CoinGateService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void mainSearch4AllPositionv2(JSONObject params, JSONArray result) {
|
|
|
+ String apiKey = InitRunner.dicCodeMap.get("gateio_api_key").getCodeValue();
|
|
|
+ String apiSecret = InitRunner.dicCodeMap.get("gateio_api_secret").getCodeValue();
|
|
|
+ String host = InitRunner.dicCodeMap.get("gateio_host").getCodeValue();
|
|
|
+ String prefix = InitRunner.dicCodeMap.get("gateio_prefix").getCodeValue();
|
|
|
+
|
|
|
+ String path = "/futures/usdt/positions";
|
|
|
+ // 构建 Query 参数
|
|
|
+ String query = "holding=true";
|
|
|
+
|
|
|
+ String url = host + prefix + path + "?" + query;
|
|
|
+ try {
|
|
|
+ // 1. 生成鉴权 Headers
|
|
|
+ HttpHeaders headers = generateAuthHeaders("GET", prefix + path, query, "", apiKey, apiSecret);
|
|
|
+
|
|
|
+ // 2. 发起 HTTP 请求
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(null, headers);
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(URI.create(url), HttpMethod.GET, entity, String.class);
|
|
|
+
|
|
|
+ // 3. 解析 JSON 响应
|
|
|
+ JSONArray jsonArray = JSONArray.parseArray(response.getBody());
|
|
|
+ Integer unrealizedPLSort = params.getInteger("unrealizedPLSort");
|
|
|
+ if (!CollectionUtils.isEmpty(jsonArray)) {
|
|
|
+ forkJoinPool.submit(() -> jsonArray.parallelStream().forEach(e -> {
|
|
|
+ JSONObject jsonObject = (JSONObject) e;
|
|
|
+
|
|
|
+ // 币对名称
|
|
|
+ String symbol = jsonObject.getString("contract");
|
|
|
+ jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol.replace("_USDT", "") + "</font></strong>_USDT");
|
|
|
+
|
|
|
+ // 持仓方向
|
|
|
+ String holdSide = jsonObject.getString("mode");
|
|
|
+ jsonObject.put("holdSide", InitRunner.publicParamsMap.get("gateHoldSide").getString(holdSide));
|
|
|
+ if (holdSide.equals("dual_long")) {
|
|
|
+ jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
|
|
|
+ } else if (holdSide.equals("dual_short")) {
|
|
|
+ jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\"");
|
|
|
+ }
|
|
|
+ // 保证金模式
|
|
|
+ jsonObject.put("marginMode", InitRunner.publicParamsMap.get("gateMarginMode").getString(jsonObject.getString("pos_margin_mode")));
|
|
|
+ // 持仓模式
|
|
|
+ jsonObject.put("holdMode", holdSide.contains("dual") ? "双向持仓" : "单向持仓");
|
|
|
+
|
|
|
+ // 最近更新时间 保证金数量 (保证金币种) 平均开仓价 未实现盈亏 预估强平价
|
|
|
+ jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("update_time") * 1000L));
|
|
|
+ jsonObject.put("margin", new BigDecimal(jsonObject.getString("margin")).setScale(4, RoundingMode.HALF_UP));
|
|
|
+ jsonObject.put("marginCoin", symbol.split("_")[1]);
|
|
|
+ jsonObject.put("averageOpenPrice", new BigDecimal(jsonObject.getString("entry_price")).divide(BigDecimal.ONE, new MathContext(4)));
|
|
|
+ jsonObject.put("unrealizedPL", new BigDecimal(jsonObject.getString("unrealised_pnl")).setScale(4, RoundingMode.HALF_UP));
|
|
|
+ jsonObject.put("liquidationPrice", new BigDecimal(jsonObject.getString("liq_price")).divide(BigDecimal.ONE, new MathContext(4)));
|
|
|
+
|
|
|
+ // 未实现盈亏
|
|
|
+ if (jsonObject.getBigDecimal("unrealizedPL").compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回报率=未实现盈亏/保证金
|
|
|
+ jsonObject.put("returnRate", "--");
|
|
|
+
|
|
|
+ // 获取当前资金费率
|
|
|
+ String requestUrl = host + prefix + "/futures/usdt/contracts/" + symbol;
|
|
|
+ try {
|
|
|
+ HttpHeaders fundingRateHeaders = generateAuthHeaders("GET", prefix + path, "", "", apiKey, apiSecret);
|
|
|
+ HttpEntity<String> fundingRateEntity = new HttpEntity<>(null, fundingRateHeaders);
|
|
|
+ ResponseEntity<String> fundingRateResponse = restTemplate.exchange(URI.create(requestUrl), HttpMethod.GET, fundingRateEntity, String.class);
|
|
|
+ String fundingRate = JSONObject.parseObject(fundingRateResponse.getBody()).getString("funding_rate");
|
|
|
+
|
|
|
+ if (new BigDecimal(fundingRate).compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
|
|
|
+ }
|
|
|
+ jsonObject.put("fundingRate", new BigDecimal(fundingRate).multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP).toPlainString() + "%");
|
|
|
+ } catch (Exception ex) {
|
|
|
+ throw new RuntimeException(ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 标记价格
|
|
|
+ jsonObject.put("marketPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
|
|
|
+
|
|
|
+ // 其他
|
|
|
+ jsonObject.put("marketPrice", new BigDecimal(jsonObject.getString("mark_price")).divide(BigDecimal.ONE, new MathContext(4)));
|
|
|
+ jsonObject.put("leverage", jsonObject.getString("leverage"));
|
|
|
+ jsonObject.put("openDelegateCount", jsonObject.getLong("pending_orders"));
|
|
|
+ jsonObject.put("available", Math.abs(jsonObject.getLong("size")) - Math.abs(jsonObject.getLong("pending_orders")));
|
|
|
+ jsonObject.put("locked", jsonObject.getLong("pending_orders"));
|
|
|
+ jsonObject.put("total", Math.abs(jsonObject.getLong("size")));
|
|
|
+ jsonObject.put("keepMarginRate", jsonObject.getString("maintenance_rate"));
|
|
|
+
|
|
|
+ })).join();
|
|
|
+
|
|
|
+ result.addAll(jsonArray);
|
|
|
+
|
|
|
+ if (unrealizedPLSort != 0) {
|
|
|
+ Collections.sort(result, (o1, o2) -> unrealizedPLSort * (((JSONObject) o1).getBigDecimal("unrealizedPL").compareTo(((JSONObject) o2).getBigDecimal("unrealizedPL"))));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mainSearch4CurrentPlan(JSONObject params, JSONArray result) {
|
|
|
+ String apiKey = InitRunner.dicCodeMap.get("gateio_api_key").getCodeValue();
|
|
|
+ String apiSecret = InitRunner.dicCodeMap.get("gateio_api_secret").getCodeValue();
|
|
|
+ String host = InitRunner.dicCodeMap.get("gateio_host").getCodeValue();
|
|
|
+ String prefix = InitRunner.dicCodeMap.get("gateio_prefix").getCodeValue();
|
|
|
+
|
|
|
+ String path = "/futures/usdt/price_orders";
|
|
|
+ // 构建 Query 参数
|
|
|
+ String query = "status=open";
|
|
|
+
|
|
|
+ String url = host + prefix + path + "?" + query;
|
|
|
+ try {
|
|
|
+ // 1. 生成鉴权 Headers
|
|
|
+ HttpHeaders headers = generateAuthHeaders("GET", prefix + path, query, "", apiKey, apiSecret);
|
|
|
+
|
|
|
+ // 2. 发起 HTTP 请求
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(null, headers);
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(URI.create(url), HttpMethod.GET, entity, String.class);
|
|
|
+
|
|
|
+ // 3. 解析 JSON 响应
|
|
|
+ JSONArray jsonArray = JSONArray.parseArray(response.getBody());
|
|
|
+ Integer chaRateSort = params.getInteger("chaRateSort");
|
|
|
+ if (!CollectionUtils.isEmpty(jsonArray)) {
|
|
|
+ forkJoinPool2.submit(() -> jsonArray.parallelStream().forEach(e -> {
|
|
|
+ JSONObject jsonObject = (JSONObject) e;
|
|
|
+
|
|
|
+ // 币对名称
|
|
|
+ JSONObject initial = jsonObject.getJSONObject("initial");
|
|
|
+ String symbol = initial.getString("contract");
|
|
|
+ jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol.replace("_USDT", "") + "</font></strong>_USDT");
|
|
|
+ // 订单状态
|
|
|
+ jsonObject.put("status", InitRunner.publicParamsMap.get("gateOrderStatus").getString(jsonObject.getString("status")));
|
|
|
+ // 交易类型
|
|
|
+ jsonObject.put("orderType", StringUtils.isEmpty(jsonObject.getString("order_type")) ? "--" : InitRunner.publicParamsMap.get("gateOrderType").getString(jsonObject.getString("order_type")));
|
|
|
+ // 订单类型
|
|
|
+ if ("0".equals(initial.getString("price"))) {
|
|
|
+ jsonObject.put("planType", "市价单");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("planType", "限价单");
|
|
|
+ }
|
|
|
+ // 开单方向
|
|
|
+ String side = jsonObject.getString("direction");
|
|
|
+ jsonObject.put("side", InitRunner.publicParamsMap.get("gateSide").getString(side));
|
|
|
+ if (side.equals("long")) {
|
|
|
+ jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
|
|
|
+ } else if (side.equals("short")) {
|
|
|
+ jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\"");
|
|
|
+ }
|
|
|
+ // 触发类型
|
|
|
+ JSONObject trigger = jsonObject.getJSONObject("trigger");
|
|
|
+ jsonObject.put("triggerType", InitRunner.publicParamsMap.get("gateTriggerType").getString(trigger.getString("price_type")));
|
|
|
+
|
|
|
+ jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("create_time") * 1000L));
|
|
|
+ jsonObject.put("uTime", null == jsonObject.getLong("finish_time") ? "--" : DateUtils.longToString(jsonObject.getLong("finish_time") * 1000L));
|
|
|
+
|
|
|
+ // 其他
|
|
|
+ jsonObject.put("triggerPrice", trigger.getBigDecimal("price").divide(BigDecimal.ONE, new MathContext(4)));
|
|
|
+ if ("0".equals(initial.getString("price"))) {
|
|
|
+ jsonObject.put("executePrice", "市价");
|
|
|
+ } else {
|
|
|
+ jsonObject.put("executePrice", jsonObject.getString("price"));
|
|
|
+ }
|
|
|
+ jsonObject.put("presetTakeProfitPrice", jsonObject.getString("stop_profit_price"));
|
|
|
+ jsonObject.put("presetTakeLossPrice", jsonObject.getString("stop_loss_price"));
|
|
|
+ jsonObject.put("marginCoin", symbol.split("_")[1]);
|
|
|
+ jsonObject.put("size", Math.abs(initial.getLong("size")));
|
|
|
+ jsonObject.put("reduceOnly", initial.getBooleanValue("is_reduce_only"));
|
|
|
+
|
|
|
+ // 获取合约标记价格
|
|
|
+ String requestUrl = host + prefix + "/futures/usdt/contracts/" + symbol;
|
|
|
+ try {
|
|
|
+ HttpHeaders fundingRateHeaders = generateAuthHeaders("GET", prefix + path, "", "", apiKey, apiSecret);
|
|
|
+ HttpEntity<String> fundingRateEntity = new HttpEntity<>(null, fundingRateHeaders);
|
|
|
+ ResponseEntity<String> fundingRateResponse = restTemplate.exchange(URI.create(requestUrl), HttpMethod.GET, fundingRateEntity, String.class);
|
|
|
+ String markPrice = JSONObject.parseObject(fundingRateResponse.getBody()).getString("mark_price");
|
|
|
+
|
|
|
+ BigDecimal chaRate = BigDecimal.ZERO;
|
|
|
+ BigDecimal triggerPriceDecimal = new BigDecimal(jsonObject.getString("triggerPrice"));
|
|
|
+ BigDecimal markPriceDecimal = new BigDecimal(markPrice);
|
|
|
+ if (markPriceDecimal.compareTo(triggerPriceDecimal) < 0) {
|
|
|
+ chaRate = markPriceDecimal.divide(triggerPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ } else if (markPriceDecimal.compareTo(triggerPriceDecimal) > 0) {
|
|
|
+ chaRate = triggerPriceDecimal.divide(markPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ jsonObject.put("markPrice", markPrice);
|
|
|
+ jsonObject.put("markPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
|
|
|
+ jsonObject.put("chaRate", chaRate);
|
|
|
+ jsonObject.put("chaRateStyle", " style=\"color:#FFFFFF;background-color:#5EA294;\"");
|
|
|
+ } catch (Exception ex) {
|
|
|
+ throw new RuntimeException(ex);
|
|
|
+ }
|
|
|
+ })).join();
|
|
|
+
|
|
|
+ result.addAll(jsonArray);
|
|
|
+
|
|
|
+ if (chaRateSort != 0) {
|
|
|
+ Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate"))));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 核心鉴权逻辑:生成 Gate.io APIv4 要求的签名 Header
|
|
|
*/
|