package top.lvzhiqiang.service.impl; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpGroupRobotService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.message.WxCpMessage; import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; import org.jsoup.Connection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StopWatch; import top.lvzhiqiang.config.InitRunner; import top.lvzhiqiang.config.WorkWeixinProperties; import top.lvzhiqiang.entity.CoinHistoryOrder; import top.lvzhiqiang.entity.CoinTrader; import top.lvzhiqiang.mapper.CoinMapper; import top.lvzhiqiang.service.CoinService; import top.lvzhiqiang.util.*; import javax.annotation.Resource; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.time.Duration; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * Coin ServiceImpl * * @author lvzhiqiang * 2023/9/5 15:23 */ @Service @Slf4j public class CoinServiceImpl implements CoinService { /** * 任务告警方式-应用文本卡片 */ public static final String JOB_ALARM_MODE_APP_TEXT_CARD = "1"; /** * 任务告警方式-群聊机器人 */ public static final String JOB_ALARM_MODE_CHAT_BOT = "2"; /** * 任务告警方式(1:应用文本卡片,2:群聊机器人文本消息) */ public static String JOB_ALARM_MODE = "1"; // 所有REST请求的header都必须包含以下key: private static final Map basicHeaderMap = new HashMap<>(); // 主域名 URL private static final String mainUrl = "https://api.bitget.com"; // 私钥,由系统随机生成,用于签名的生成。 private static final String secretKey = "1fdd0fc2976bea80189ba13710e12825ca3ef6c5e25a0d76fd03f8f6cd4a61d9"; @Resource private CoinMapper coinMapper; @Resource private WxCpService wxCpService; @Autowired(required = false) private WorkWeixinProperties properties; private final Map orderMap = new ConcurrentHashMap<>(); private final Map mixMap = new ConcurrentHashMap<>(); private final static ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(10); private final ForkJoinPool forkJoinPool = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool2 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool3 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool4 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool5 = new ForkJoinPool(16); private static final DecimalFormat df1 = new DecimalFormat("#,##0.00"); static { // API KEY作为一个字符串。 basicHeaderMap.put("ACCESS-KEY", "bg_433d37306df0e8901c6d107c6d9e9111"); // 使用base64编码签名(请参阅签名消息)。 basicHeaderMap.put("ACCESS-SIGN", ""); // 您请求的时间戳。 basicHeaderMap.put("ACCESS-TIMESTAMP", ""); // 您在创建API KEY时设置的口令。 basicHeaderMap.put("ACCESS-PASSPHRASE", "7f934f62f2701bee932204580d115228"); // 统一设置为application/json。 basicHeaderMap.put("Content-Type", "application/json"); // 支持多语言, 如:中文(zh-CN),英语(en-US) basicHeaderMap.put("locale", "zh-CN"); df1.setRoundingMode(RoundingMode.HALF_UP); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void syncData(String startTime, String endTime, String pageSize) { // 获取全部历史委托 Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("startTime", startTime); paramMap.put("endTime", endTime); paramMap.put("pageSize", pageSize); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList"); if (orderList.size() > 0) { coinMapper.insertHistoryOrderList(JSONArray.parseArray(orderList.toJSONString(), CoinHistoryOrder.class)); log.warn("syncData->insertHistoryOrderList,startTime={},endTime={},size={}", startTime, endTime, orderList.size()); } } @Override public void syncData4TraderList() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 获取交易员列表 Map paramMap = new LinkedHashMap<>(); paramMap.put("sortRule", "composite"); paramMap.put("sortFlag", "desc"); paramMap.put("languageType", "en-US"); paramMap.put("pageSize", "20"); int i = 0; String url = "/api/mix/v1/trace/traderList"; JSONObject response; int totalNum = 0; for (; ; ) { paramMap.put("pageNo", String.valueOf(++i)); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); response = requestApi4Common(url, signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray dataList = response.getJSONArray("data"); if (dataList.size() == 0) { break; } try { syncData4TraderListSub(dataList); } catch (Exception e) { log.error("syncData4TraderListSub error,paramMap={}", paramMap, e); } totalNum += dataList.size(); } log.warn("syncData4TraderList 结束:time={},totalNum={}", stopWatch.getTotalTimeSeconds(), totalNum); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void syncData4TraderListSub(JSONArray dataList) { coinMapper.insertMixTradeList(parseMixTradeList(dataList)); } private List parseMixTradeList(JSONArray dataList) { List mixTraderList = JSONArray.parseArray(dataList.toJSONString(), CoinTrader.class); mixTraderList.stream().forEach(e -> { Map columnMap = e.getColumnList().stream().filter(Objects::nonNull) .collect(Collectors.toMap( object -> { JSONObject item = (JSONObject) object; return item.getString("describe"); }, object -> { JSONObject item = (JSONObject) object; return item.getString("value"); } )); e.setRoi(columnMap.get("ROI")); e.setTotalProfit(columnMap.get("Total PnL")); e.setTotalFollowersProfit(columnMap.get("Total followers PnL")); e.setAum(columnMap.get("AUM")); e.setMaxCallbackRate(columnMap.get("Max drawdown")); e.setLast3wWinRate(columnMap.get("Last 3W win rate")); e.setAverageWinRate(StringUtils.isNotEmpty(e.getAverageWinRate()) ? new BigDecimal(e.getAverageWinRate()).setScale(2, RoundingMode.HALF_UP).toPlainString() : "0.00"); }); return mixTraderList; } @Override public String orderDetail(String trackingNo) { Map paramMap = new LinkedHashMap<>(); paramMap.put("traderId", "b1b5467f8bb73f53ac97"); paramMap.put("pageSize", "20"); StringBuffer sb = new StringBuffer(); // 交易员当前带单列表筛选 for (int j = 1; j < 5; j++) { try { paramMap.put("pageNo", j + ""); JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); String trackingNo1 = order.getString("trackingNo"); if (trackingNo.equals(trackingNo1)) { sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append("
交易对").append(order.getString("symbol")).append("
持仓方向").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("
杠杆倍数").append(order.getString("leverage")).append("
开仓均价").append(order.getString("openPrice")).append("
开仓时间").append(DateUtils.longToString(order.getLong("openTime"))).append("
此笔订单跟单人数").append(order.getString("followerNum")).append("
保证金").append(order.getString("marginAmount")).append("
止盈价").append(order.getString("takeProfitPrice")).append("
止损价").append(order.getString("stopLossPrice")).append("
交易员").append("hale").append("
"); break; } } } catch (Exception e) { } } // 交易员历史带单列表筛选 if (sb.length() == 0) { for (int j = 1; j < 5; j++) { try { paramMap.put("pageNo", j + ""); JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/historyList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); String trackingNo1 = order.getString("trackingNo"); if (trackingNo.equals(trackingNo1)) { sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append("
交易对").append(order.getString("symbol")).append("
持仓方向").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("
杠杆倍数").append(order.getString("leverage")).append("
开仓均价").append(order.getString("openPrice")).append("
开仓时间").append(DateUtils.longToString(order.getLong("openTime"))).append("
此笔订单跟单人数").append(order.getString("followerNum")).append("
保证金").append(order.getString("marginAmount")).append("
平仓均价").append(order.getString("closePrice")).append("
平仓时间").append(DateUtils.longToString(order.getLong("closeTime"))).append("
平仓数量").append(order.getString("closeAmount")).append("
交易员").append("hale").append("
"); break; } } } catch (Exception e) { } } } return sb.toString(); } @Override public String orderDetail2(String orderId, String symbol) { Map paramMap = new LinkedHashMap<>(); paramMap.put("symbol", symbol); paramMap.put("orderId", orderId); StringBuffer sb = new StringBuffer(""); // 获取订单详情 try { String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common("/api/mix/v1/order/detail", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONObject order = response.getJSONObject("data"); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); } catch (Exception e) { log.error("orderDetail2 error,orderId={},symbol={}", orderId, symbol, e); } sb.append("
交易对").append(order.getString("symbol")).append("
交易方向").append(InitRunner.publicParamsMap.get("side").getString(order.getString("side"))).append("
杠杆倍数").append(order.getString("leverage")).append("
成交均价").append(order.getString("priceAvg")).append("
委托价格").append(order.getString("price")).append("
手续费").append(order.getString("fee")).append("
订单状态").append(InitRunner.publicParamsMap.get("state").getString(order.getString("state"))).append("
交易类型").append(InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType"))).append("
总盈亏").append(order.getString("totalProfits")).append("
预设止盈价格").append(order.getString("presetTakeProfitPrice")).append("
预设止损价格").append(order.getString("presetStopLossPrice")).append("
创建时间").append(DateUtils.longToString(order.getLong("cTime"))).append("
更新时间").append(DateUtils.longToString(order.getLong("uTime"))).append("
"); return sb.toString(); } @Override public String monitorJob() { // 开仓平仓监控报警 scheduler.scheduleWithFixedDelay(() -> { LocalDateTime endTime = LocalDateTime.now(); // 全部历史委托列表 Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("startTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime.minusMinutes(1)))); paramMap.put("endTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime))); paramMap.put("pageSize", "100"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); try { JSONObject response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); LocalDateTime cTime = DateUtils.longToLocalDateTime(order.getLong("cTime")); String orderId = order.getString("orderId"); String symbol = order.getString("symbol"); if (Duration.between(cTime, endTime).getSeconds() < 50 && !orderMap.containsKey(orderId)) { orderMap.put(orderId, "1"); String content = "
交易对:" + order.getString("symbol") + "
" + "
交易方向:" + InitRunner.publicParamsMap.get("side").getString(order.getString("side")) + "
" + "
杠杆倍数:" + order.getString("leverage") + "
" + "
成交均价:" + order.getString("priceAvg") + "
" + "
委托价格:" + order.getString("price") + "
" + "
订单状态:" + InitRunner.publicParamsMap.get("state").getString(order.getString("state")) + "
" + "
订单类型:" + InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType")) + "
" + "
订单时间:" + DateUtils.longToString(order.getLong("cTime")) + "
"; JSONObject params = new JSONObject(); params.put("title", (order.getString("side").contains("open") ? "合约开单" : "合约平单") + "监控报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail2/" + orderId + "/" + symbol); params.put("btnTxt", "订单详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params); } } } catch (Exception e) { } }, 0, 2, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(() -> { // 全部合约仓位信息V2 Map paramMap = new HashMap<>(); paramMap.put("productType", "umcbl"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); try { JSONObject response = requestApi4Common("/api/mix/v1/position/allPosition-v2", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray mixList = response.getJSONArray("data"); for (int i = 0; i < mixList.size(); i++) { JSONObject mixData = mixList.getJSONObject(i); String symbol = mixData.getString("symbol"); String margin = mixData.getString("margin"); String averageOpenPrice = mixData.getString("averageOpenPrice"); String key = symbol + margin + averageOpenPrice; // 回报率=未实现盈亏/保证金 // 持仓方向 long:多头 short:空头 String holdSide = mixData.getString("holdSide"); BigDecimal returnRate = new BigDecimal(mixData.getString("unrealizedPL")).divide(new BigDecimal(margin), 4, RoundingMode.HALF_UP); for (int j = 1; j <= 10; j++) { BigDecimal grid = BigDecimal.valueOf(0.5).multiply(BigDecimal.valueOf(j)); if (returnRate.compareTo(grid) < 0) { if (mixMap.containsKey(key)) { mixMap.get(key).put("returnRate", returnRate); } else { JSONObject jsonObject = new JSONObject(); jsonObject.put("returnRate", returnRate); mixMap.put(key, jsonObject); } break; } if (returnRate.compareTo(grid) > 0) { if (mixMap.containsKey(key)) { mixMap.get(key).put("returnRate", returnRate); if (mixMap.get(key).containsKey(grid.toPlainString())) { continue; } else { mixMap.get(key).put(grid.toPlainString(), true); String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol; String last = "--"; try { Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last"); } catch (Exception e) { } String content = "币对名称:" + symbol + "\n" + "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" + "杠杆倍数:" + mixData.getString("leverage") + "\n" + "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" + "当前价格:" + last + "\n" + "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString(); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null); } } else { JSONObject jsonObject = new JSONObject(); jsonObject.put("returnRate", returnRate); jsonObject.put(grid.toPlainString(), true); mixMap.put(key, jsonObject); String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol; String last = "--"; try { Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last"); } catch (Exception e) { } String content = "币对名称:" + symbol + "\n" + "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" + "杠杆倍数:" + mixData.getString("leverage") + "\n" + "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" + "当前价格:" + last + "\n" + "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString(); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null); //break; } } } } } catch (Exception e) { e.printStackTrace(); } }, 0, 5, TimeUnit.SECONDS); // 跟单员监控报警 scheduler.scheduleWithFixedDelay(() -> { List monitorTraderList = coinMapper.findMonitorTraderList(); forkJoinPool5.submit(() -> monitorTraderList.parallelStream().forEach(e -> { LocalDateTime endTime = LocalDateTime.now(); // 交易员当前带单列表 Map paramMap = new LinkedHashMap<>(); String[] split = e.split("\\|"); paramMap.put("traderId", split[0]); paramMap.put("pageNo", "1"); paramMap.put("pageSize", "20"); try { JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); LocalDateTime openTime = DateUtils.longToLocalDateTime(order.getLong("openTime")); String trackingNo = order.getString("trackingNo"); if (Duration.between(openTime, endTime).getSeconds() < 50 && !orderMap.containsKey(trackingNo)) { orderMap.put(trackingNo, "1"); String content = "
交易对:" + order.getString("symbol") + "
" + "
持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide")) + "
" + "
杠杆倍数:" + order.getString("leverage") + "
" + "
开仓均价:" + order.getString("openPrice") + "
" + "
止盈价:" + order.getString("takeProfitPrice") + "
" + "
止损价:" + order.getString("stopLossPrice") + "
" + "
交易员:" + split[1] + "
" + "
开仓时间:" + DateUtils.longToString(order.getLong("openTime")) + "
"; JSONObject params = new JSONObject(); params.put("title", "交易员开单监控报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail/" + order.getString("trackingNo")); params.put("btnTxt", "跟单详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params); } } } catch (Exception ex) { } })).join(); }, 0, 3, TimeUnit.SECONDS); return null; } @Override @Async("coinTaskExecutor") public void monitorAlarm4APP_TEXT_CARD(String content, JSONObject params) { // 文本卡片模式发消息 String title = "监控告警明细"; if (params.containsKey("title")) { title = params.getString("title"); } String logUrl = "https://lvzhiqiang.top"; if (params.containsKey("logUrl")) { logUrl = params.getString("logUrl"); } String btnTxt = "日志详情"; if (params.containsKey("btnTxt")) { btnTxt = params.getString("btnTxt"); } String user = "LvZhiQiang"; if (params.containsKey("user")) { user = params.getString("user"); } String party = ""; if (params.containsKey("party")) { party = params.getString("party"); } String tag = ""; if (params.containsKey("tag")) { tag = params.getString("tag"); } WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(properties.getAgentId()) .toUser(user) .toParty(party) .toTag(tag) .title(title).description(content) .url(logUrl).btnTxt(btnTxt) .build(); try { log.info("企业微信推送消息,send message: {}", wxCpMessage); WxCpMessageSendResult sendResult = wxCpService.getMessageService().send(wxCpMessage); log.info("企业微信推送消息成功,send result: {}", sendResult); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm4CHAT_BOT(String content, JSONObject params) { // 调用企业微信群聊机器人发消息 WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService(); String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940"; List userList = Collections.singletonList("LvZhiQiang"); if (params != null && params.containsKey("user")) { userList = Arrays.asList(params.getString("user").split("[|,]")); } try { log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, userList); groupRobotService.sendText(webhookUrl, content, userList, Collections.emptyList()); log.info("企业微信推送消息成功"); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm(String content, String jobAlarmMode) { // 判断告警模式 if (StringUtils.isEmpty(JOB_ALARM_MODE)) { jobAlarmMode = JOB_ALARM_MODE; } // 文本卡片模式发消息 if (JOB_ALARM_MODE_APP_TEXT_CARD.equals(jobAlarmMode)) { String title = "监控告警明细"; String logUrl = "https://lvzhiqiang.top"; String btnTxt = "日志详情"; WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(properties.getAgentId()) .toUser("LvZhiQiang") .toParty("") .toTag("") .title(title).description(content) .url(logUrl).btnTxt(btnTxt) .build(); try { log.info("企业微信推送消息,send message: {}", wxCpMessage); WxCpMessageSendResult sendResult = wxCpService.getMessageService().send(wxCpMessage); log.info("企业微信推送消息成功,send result: {}", sendResult); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } // 调用企业微信群聊机器人发消息 if (JOB_ALARM_MODE_CHAT_BOT.equals(jobAlarmMode)) { WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService(); String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940"; try { log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, "LvZhiQiang"); groupRobotService.sendText(webhookUrl, content, Collections.singletonList("LvZhiQiang"), Collections.emptyList()); log.info("企业微信推送消息成功"); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } } /** * 请求通用API方法 */ private JSONObject requestApi4Common(String requestPath, String signQueryString, String signBody, String httpMethod, Map paramMap) { String timestamp = String.valueOf(System.currentTimeMillis()); Map headerMap = new HashMap<>(); headerMap.putAll(basicHeaderMap); try { String accessSign = CheckSign4Bitget.generate(timestamp, httpMethod, requestPath, signQueryString, signBody, secretKey); headerMap.put("ACCESS-TIMESTAMP", timestamp); headerMap.put("ACCESS-SIGN", accessSign); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } try { String requestUrl = mainUrl + requestPath; if (httpMethod.equals(JsoupUtil.HTTP_GET)) { Connection.Response response = JsoupUtil.requestBody(requestUrl, httpMethod, InitRunner.proxy, headerMap, paramMap); return JSONObject.parseObject(response.body()); } else { Connection.Response response = JsoupUtil.requestBodyJSON(requestUrl, httpMethod, InitRunner.proxy, null, headerMap, paramMap); return JSONObject.parseObject(response.body()); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public Object mainSearch(JSONObject params) throws Exception { JSONArray result = new JSONArray(); if (params.getString("nameEn").equals("allPositionv2")) { Map paramMap = new HashMap<>(); paramMap.put("productType", "umcbl"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4AllPositionv2(result, params.getInteger("unrealizedPLSort")); } else if (params.getString("nameEn").equals("orderMarginCoinCurrent")) { Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("marginCoin", "USDT"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4OrderMarginCoinCurrent(result, params.getInteger("chaRateSort")); } else if (params.getString("nameEn").equals("orderHistoryProductType")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List historyOrderList = coinMapper.findHistoryOrderList(params.toJavaObject(Map.class)); PageInfo historyOrderPageInfo = new PageInfo<>(historyOrderList); renderMainSearch4OrderHistoryProductType(historyOrderList); //result = (JSONArray) JSON.toJSON(historyOrderList); return historyOrderPageInfo; } else if (params.getString("nameEn").equals("traderList")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List mixTraderList = coinMapper.findMixTraderList(params.toJavaObject(Map.class)); PageInfo coinTraderPageInfo = new PageInfo<>(mixTraderList); renderMainSearch4TraderList(mixTraderList); //result = (JSONArray) JSON.toJSON(mixTraderList); return coinTraderPageInfo; } else if (params.getString("nameEn").equals("monitorCurrency")) { List monitorCurrencyList = coinMapper.findMonitorCurrencyList(); String requestUrl = mainUrl + params.getString("url"); Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); result = JSONObject.parseObject(response.body()).getJSONArray("data"); result = renderMainSearch4MonitorCurrency(result, monitorCurrencyList, params.getInteger("changeUtcSort")); } else if (params.getString("nameEn").equals("currentPlan")) { Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("isPlan", "plan"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4CurrentPlan(result, params.getInteger("chaRateSort")); } return result; } private void renderMainSearch4TraderList(List mixTraderList) { for (CoinTrader mixTrader : mixTraderList) { mixTrader.setLastTradeTime(DateUtils.longToString(Long.valueOf(mixTrader.getLastTradeTime()))); } } /** * 渲染获取当前计划委托(止盈止损)列表 * * @param result */ private void renderMainSearch4CurrentPlan(JSONArray result, Integer chaRateSort) { forkJoinPool3.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 订单状态 jsonObject.put("status", InitRunner.publicParamsMap.get("status").getString(jsonObject.getString("status"))); // 交易类型 jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType"))); // 订单类型 jsonObject.put("planType", InitRunner.publicParamsMap.get("planType").getString(jsonObject.getString("planType"))); // 开单方向 String side = jsonObject.getString("side"); jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side)); if (side.equals("open_long")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (side.equals("open_short")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\""); } // 触发类型 jsonObject.put("triggerType", InitRunner.publicParamsMap.get("triggerType").getString(jsonObject.getString("triggerType"))); jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("uTime", StringUtils.isEmpty(jsonObject.getString("uTime")) ? "--" : DateUtils.longToString(jsonObject.getLong("uTime"))); // 获取合约标记价格 String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice"); BigDecimal chaRate = BigDecimal.ZERO; if ("open_long".equals(side)) { chaRate = new BigDecimal(markPrice).divide(new BigDecimal(jsonObject.getString("triggerPrice")), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } else if ("open_short".equals(side)) { chaRate = new BigDecimal(jsonObject.getString("triggerPrice")).divide(new BigDecimal(markPrice), 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(); if (chaRateSort != 0) { Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate")))); } } /** * 渲染获取监控币种列表 * * @param monitorCurrencyList */ private JSONArray renderMainSearch4MonitorCurrency(JSONArray result, List monitorCurrencyList, Integer changeUtcSort) { JSONArray array = result.stream() .filter(iter -> monitorCurrencyList.contains(((JSONObject) iter).getString("symbol"))) .collect(Collectors.toCollection(JSONArray::new)); forkJoinPool.submit(() -> array.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; jsonObject.put("changeUtc", jsonObject.getBigDecimal("changeUtc").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("change", jsonObject.getBigDecimal("change").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("ts", DateUtils.longToString(jsonObject.getLong("ts"))); // UTC0时涨跌幅 if (jsonObject.getBigDecimal("changeUtc").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 24小时涨跌幅 if (jsonObject.getBigDecimal("change").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 币对名称 String symbol = jsonObject.getString("symbol").replace("USDT", ""); jsonObject.put("symbol", "" + symbol + "USDT"); // 标记价格 jsonObject.put("closeStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); // 基础币量 计价币量 usdt币量 jsonObject.put("baseVol", readableFileSize(jsonObject.getDouble("baseVol"))); jsonObject.put("quoteVol", readableFileSize(jsonObject.getDouble("quoteVol"))); jsonObject.put("usdtVol", readableFileSize(jsonObject.getDouble("usdtVol"))); })).join(); if (changeUtcSort != 0) { Collections.sort(array, (o1, o2) -> changeUtcSort * (((JSONObject) o1).getBigDecimal("changeUtc").compareTo(((JSONObject) o2).getBigDecimal("changeUtc")))); } return array; } /** * 渲染获取全部历史委托 * * @param historyOrderList */ private void renderMainSearch4OrderHistoryProductType(List historyOrderList) { for (CoinHistoryOrder coinHistoryOrder : historyOrderList) { // 币种名称 coinHistoryOrder.setSymbol(coinHistoryOrder.getSymbol().replace("USDT_UMCBL", "")); // 订单状态 coinHistoryOrder.setState(InitRunner.publicParamsMap.get("state").getString(coinHistoryOrder.getState())); // 开单方向 coinHistoryOrder.setSide(InitRunner.publicParamsMap.get("side").getString(coinHistoryOrder.getSide())); // 总盈亏 String TotalProfits = "0E-8"; if (!coinHistoryOrder.getTotalProfits().contains("0E-8")) { TotalProfits = new BigDecimal(coinHistoryOrder.getTotalProfits()).setScale(2, RoundingMode.HALF_UP).toPlainString(); } coinHistoryOrder.setTotalProfits(TotalProfits); // 手续费 String fee = "0E-8"; if (!coinHistoryOrder.getFee().contains("0E-8")) { fee = new BigDecimal(coinHistoryOrder.getFee()).setScale(2, RoundingMode.HALF_UP).toPlainString(); } coinHistoryOrder.setFee(fee); // 持仓方向 coinHistoryOrder.setPosSide(InitRunner.publicParamsMap.get("posSide").getString(coinHistoryOrder.getPosSide())); // 仓位模式 coinHistoryOrder.setMarginMode(InitRunner.publicParamsMap.get("marginMode").getString(coinHistoryOrder.getMarginMode())); // 交易类型 coinHistoryOrder.setOrderType(InitRunner.publicParamsMap.get("orderType").getString(coinHistoryOrder.getOrderType())); // 交易方向 coinHistoryOrder.setTradeSide(InitRunner.publicParamsMap.get("tradeSide").getString(coinHistoryOrder.getTradeSide())); // 持仓模式 coinHistoryOrder.setHoldMode(InitRunner.publicParamsMap.get("holdMode").getString(coinHistoryOrder.getHoldMode())); // orderSource coinHistoryOrder.setOrderSource(InitRunner.publicParamsMap.get("orderSource").getString(coinHistoryOrder.getOrderSource())); coinHistoryOrder.setCTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getCTime()))); coinHistoryOrder.setUTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getUTime()))); } } /** * 渲染获取全部当前委托 * * @param result */ private void renderMainSearch4OrderMarginCoinCurrent(JSONArray result, Integer chaRateSort) { forkJoinPool2.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 订单状态 jsonObject.put("state", InitRunner.publicParamsMap.get("state").getString(jsonObject.getString("state"))); // 开单方向 String side = jsonObject.getString("side"); jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side)); if (side.equals("open_long")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (side.equals("open_short")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\""); } // 交易类型 jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType"))); // 止盈止损 jsonObject.put("presetTakeProfitPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice")); jsonObject.put("presetStopLossPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice")); // 持仓模式 jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode"))); // orderSource jsonObject.put("orderSource", InitRunner.publicParamsMap.get("orderSource").getString(jsonObject.getString("orderSource"))); // 仓位模式 jsonObject.put("marginMode", InitRunner.publicParamsMap.get("marginMode").getString(jsonObject.getString("marginMode"))); jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("uTime", DateUtils.longToString(jsonObject.getLong("uTime"))); // 获取合约标记价格 String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice"); BigDecimal chaRate = BigDecimal.ZERO; if ("open_short".equals(side)) { chaRate = new BigDecimal(markPrice).divide(new BigDecimal(jsonObject.getString("price")), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } else if ("open_long".equals(side)) { chaRate = new BigDecimal(jsonObject.getString("price")).divide(new BigDecimal(markPrice), 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(); if (chaRateSort != 0) { Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate")))); } } /** * 渲染获取全部合约仓位信息V2 * * @param result */ private void renderMainSearch4AllPositionv2(JSONArray result, Integer unrealizedPLSort) { forkJoinPool4.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 持仓方向 String holdSide = jsonObject.getString("holdSide"); jsonObject.put("holdSide", InitRunner.publicParamsMap.get("holdSide").getString(holdSide)); if (holdSide.equals("long")) { jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (holdSide.equals("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("marginMode").getString(jsonObject.getString("marginMode"))); // 持仓模式 jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode"))); // 最近更新时间 保证金数量 (保证金币种) 平均开仓价 未实现盈亏 预估强平价 jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("margin", new BigDecimal(jsonObject.getString("margin")).setScale(4, RoundingMode.HALF_UP)); jsonObject.put("averageOpenPrice", new BigDecimal(jsonObject.getString("averageOpenPrice")).setScale(4, RoundingMode.HALF_UP)); jsonObject.put("unrealizedPL", new BigDecimal(jsonObject.getString("unrealizedPL")).setScale(4, RoundingMode.HALF_UP)); jsonObject.put("liquidationPrice", new BigDecimal(jsonObject.getString("liquidationPrice")).setScale(4, RoundingMode.HALF_UP)); // 未实现盈亏 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;\""); } // 回报率=未实现盈亏/保证金 BigDecimal returnRate = jsonObject.getBigDecimal("unrealizedPL").divide(jsonObject.getBigDecimal("margin"), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); jsonObject.put("returnRate", returnRate); if (returnRate.compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 获取当前资金费率 String requestUrl = mainUrl + "/api/mix/v1/market/current-fundRate?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String fundingRate = JSONObject.parseObject(response.body()).getJSONObject("data").getString("fundingRate"); 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;\""); })).join(); if (unrealizedPLSort != 0) { Collections.sort(result, (o1, o2) -> unrealizedPLSort * (((JSONObject) o1).getBigDecimal("unrealizedPL").compareTo(((JSONObject) o2).getBigDecimal("unrealizedPL")))); } } /** * Java实现字节转换,可以自动转换为B、KB、MB、GB、TB * * @param size * @return */ private String readableFileSize(double size) { if (size <= 0) { return "0"; } final String[] units = new String[]{"B", "K", "M", "G", "T"}; int digitGroups = (int) (Math.log10(size) / Math.log10(1000)); return df1.format(size / Math.pow(1000, digitGroups)) + units[digitGroups]; } }