package com.xcong.excoin.modules.okxNewPrice.okxWs; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums; import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils; import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild; import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild; import io.micrometer.core.instrument.util.JsonUtils; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 账户 WebSocket 处理类,用于订阅 OKX 的账户频道并处理账户信息推送。 * 包含账户资金状态判断、计算下单保证金及保存到 Redis 的逻辑。 * * @author Administrator */ @Slf4j public class AccountWs { // 使用双层Map,第一层key为账号名称,第二层key为数据key private static final Map> ACCOUNTWSMAP = new ConcurrentHashMap<>(); // 获取指定账号的Map,如果不存在则创建 public static Map getAccountMap(String accountName) { return ACCOUNTWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); } /** * 账户频道名称常量 */ public static final String ACCOUNTWS_CHANNEL = "account"; /** * 订阅账户频道 * * @param webSocketClient WebSocket 客户端实例 * @param option 请求选项(如 unsubscribe 或 subscribe) */ public static void subscribeAccountChannel(WebSocketClient webSocketClient, String option) { try { JSONArray argsArray = new JSONArray(); JSONObject args = new JSONObject(); args.put("channel", ACCOUNTWS_CHANNEL); args.put("ccy", CoinEnums.USDT.getCode()); argsArray.add(args); String connId = MallUtils.getOrderNum(ACCOUNTWS_CHANNEL); JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray); webSocketClient.send(jsonObject.toJSONString()); // log.info("发送账户频道:{}", option); } catch (Exception e) { log.error("订阅账户频道构建失败", e); } } public static void initEvent(JSONObject response, String accountName) { // log.info("订阅成功: {}", response.getJSONObject("arg")); JSONObject arg = response.getJSONObject("arg"); initParam(arg, accountName); } /** * 处理账户频道推送的数据 * * @param response 推送的 JSON 数据对象 * @param accountName 账号名称 */ public static void handleEvent(JSONObject response, String accountName) { // log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL); try { JSONArray dataArray = response.getJSONArray("data"); if (dataArray == null || dataArray.isEmpty()) { log.warn("账户频道数据为空"); return; } for (int i = 0; i < dataArray.size(); i++) { try { JSONObject accountData = dataArray.getJSONObject(i); JSONArray detailsArray = accountData.getJSONArray("details"); if (detailsArray == null || detailsArray.isEmpty()) { log.warn("账户频道{}数据为空",CoinEnums.USDT.getCode()); continue; } for (int j = 0; j < detailsArray.size(); j++) { JSONObject detail = detailsArray.getJSONObject(j); initParam(detail, accountName); Map accountMap = getAccountMap(accountName); WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode()); } } catch (Exception innerEx) { log.warn("处理账户频道数据失败", innerEx); } } } catch (Exception e) { log.error("处理账户频道推送数据失败", e); } } public static final String ccyKey = "ccy"; public static final String availBalKey = "availBal"; public static final String cashBalKey = "cashBal"; public static final String eqKey = "eq"; public static final String uplKey = "upl"; public static final String imrKey = "imr"; private static void initParam(JSONObject detail, String accountName) { Map accountMap = getAccountMap(accountName); String ccy = WsMapBuild.parseStringSafe( detail.getString(ccyKey)); WsMapBuild.saveStringToMap(accountMap, ccyKey, ccy); String availBal = WsMapBuild.parseStringSafe(detail.getString(availBalKey)); WsMapBuild.saveStringToMap(accountMap, availBalKey, availBal); String cashBal = WsMapBuild.parseStringSafe(detail.getString(cashBalKey)); WsMapBuild.saveStringToMap(accountMap, cashBalKey, cashBal); String eq = WsMapBuild.parseStringSafe(detail.getString(eqKey)); WsMapBuild.saveStringToMap(accountMap, eqKey, eq); String upl = WsMapBuild.parseStringSafe(detail.getString(uplKey)); WsMapBuild.saveStringToMap(accountMap, uplKey, upl); String imr = WsMapBuild.parseStringSafe(detail.getString(imrKey)); WsMapBuild.saveStringToMap(accountMap, imrKey, imr); BigDecimal cashBalDecimal = WsMapBuild.parseBigDecimalSafe(cashBal); // 根据可用余额计算下单总保证金 String total_order_usdtpecent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDTPECENT.name()); BigDecimal total_order_usdt_factor = WsMapBuild.parseBigDecimalSafe(total_order_usdtpecent); BigDecimal totalOrderUsdt = cashBalDecimal.multiply(total_order_usdt_factor).setScale(2, RoundingMode.DOWN); WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt)); /** * 当前账户未满仓,并且账户余额不为0,才更新为已就绪 */ BigDecimal imrDecimal = WsMapBuild.parseBigDecimalSafe(imr); if (BigDecimal.ZERO.compareTo(cashBalDecimal) < 0 && imrDecimal.compareTo(totalOrderUsdt) < 0){ WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode()); } log.info( "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {},已使用保证金:{}", accountName, ccy, availBal, cashBal, eq, upl, totalOrderUsdt,imr ); } }