| | |
| | | package com.xcong.excoin.modules.okxNewPrice.okxWs; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | 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 com.xcong.excoin.utils.RedisUtils; |
| | | 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 的账户频道并处理账户信息推送。 |
| | |
| | | @Slf4j |
| | | public class AccountWs { |
| | | |
| | | public static final Map<String,String> ACCOUNTWSMAP = new ConcurrentHashMap<>(); |
| | | /** |
| | | * 账户频道名称常量 |
| | | */ |
| | | public static final String ACCOUNTWS_CHANNEL = "account"; |
| | | |
| | | private static final String CCY_KEY = "ccy"; |
| | | private static final String AVAIL_BAL_KEY = "availBal"; |
| | | private static final String CASH_BAL_KEY = "cashBal"; |
| | | private static final String EQ_KEY = "eq"; |
| | | private static final String DETAILS_KEY = "details"; |
| | | private static final String DATA_KEY = "data"; |
| | | |
| | | // 缓存常用 BigDecimal 常量 |
| | | private static final BigDecimal KANG_CANG_THRESHOLD = new BigDecimal(OrderParamEnums.KANG_CANG.getValue()); |
| | | private static final BigDecimal ZHI_SUN_THRESHOLD = new BigDecimal(OrderParamEnums.ZHI_SUN.getValue()); |
| | | private static final BigDecimal TOTAL_ORDER_USDT_FACTOR = new BigDecimal(OrderParamEnums.TOTAL_ORDER_USDT.getValue()); |
| | | private static final BigDecimal EVERY_TIME_USDT_FACTOR = new BigDecimal(OrderParamEnums.EVERY_TIME_USDT.getValue()); |
| | | |
| | | /** |
| | | * 订阅账户频道 |
| | |
| | | JSONArray argsArray = new JSONArray(); |
| | | JSONObject args = new JSONObject(); |
| | | args.put("channel", ACCOUNTWS_CHANNEL); |
| | | // args.put(CCY_KEY, CoinEnums.USDT.getCode()); |
| | | JSONObject updateInterval = new JSONObject(); |
| | | updateInterval.put("updateInterval",CoinEnums.UPDATEINTERVAL.getCode()); |
| | | args.put("extraParams", updateInterval); |
| | | 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); |
| | | // log.info("发送账户频道:{}", option); |
| | | } catch (Exception e) { |
| | | log.error("订阅账户频道构建失败", e); |
| | | } |
| | | } |
| | | |
| | | public static void initEvent(JSONObject response) { |
| | | // log.info("订阅成功: {}", response.getJSONObject("arg")); |
| | | JSONObject arg = response.getJSONObject("arg"); |
| | | initParam(arg); |
| | | } |
| | | |
| | | /** |
| | | * 处理账户频道推送的数据 |
| | | * |
| | | * @param response 推送的 JSON 数据对象 |
| | | * @param redisUtils Redis 工具类实例,用于存储账户相关信息 |
| | | */ |
| | | public static void handleEvent(JSONObject response, RedisUtils redisUtils) { |
| | | public static void handleEvent(JSONObject response) { |
| | | |
| | | |
| | | log.info("开始执行AccountWs......"); |
| | | // log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL); |
| | | try { |
| | | JSONArray dataArray = response.getJSONArray(DATA_KEY); |
| | | 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_KEY); |
| | | 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); |
| | | |
| | | String ccy = detail.getString(CCY_KEY); |
| | | if (!CoinEnums.USDT.getCode().equals(ccy)) { |
| | | log.warn("账户频道币种不匹配,跳过处理"); |
| | | continue; |
| | | } |
| | | |
| | | String availBalStr = detail.getString(AVAIL_BAL_KEY); |
| | | String cashBalStr = detail.getString(CASH_BAL_KEY); |
| | | String eq = detail.getString(EQ_KEY); |
| | | |
| | | if (StrUtil.isBlank(ccy) || StrUtil.isBlank(availBalStr) || StrUtil.isBlank(cashBalStr)) { |
| | | log.warn("账户频道缺失必要字段,跳过处理"); |
| | | continue; |
| | | } |
| | | |
| | | BigDecimal availBal = parseBigDecimalSafe(availBalStr); |
| | | BigDecimal cashBal = parseBigDecimalSafe(cashBalStr); |
| | | |
| | | if (availBal == null || cashBal == null || cashBal.compareTo(BigDecimal.ZERO) == 0) { |
| | | log.warn("账户频道无效的账户余额数据,跳过处理"); |
| | | continue; |
| | | } |
| | | |
| | | // 可用余额 / 现金余额 比例判断是否允许开仓 |
| | | BigDecimal divide = availBal.divide(cashBal, 4, RoundingMode.DOWN); |
| | | |
| | | String state = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state"); |
| | | String out = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":out"); |
| | | if (OrderParamEnums.STATE_4.getValue().equals(state)){ |
| | | log.info(OrderParamEnums.STATE_4.getName()); |
| | | state = OrderParamEnums.STATE_4.getValue(); |
| | | }else if(OrderParamEnums.STATE_3.getValue().equals(state) && OrderParamEnums.OUT_YES.getValue().equals(out)){ |
| | | log.info(OrderParamEnums.STATE_3.getName()); |
| | | state = OrderParamEnums.STATE_3.getValue(); |
| | | }else{ |
| | | if (divide.compareTo(KANG_CANG_THRESHOLD) > 0) { |
| | | log.info(OrderParamEnums.STATE_1.getName()); |
| | | state = OrderParamEnums.STATE_1.getValue(); |
| | | } else if (divide.compareTo(ZHI_SUN_THRESHOLD) > 0) { |
| | | log.warn(OrderParamEnums.STATE_2.getName()); |
| | | state = OrderParamEnums.STATE_2.getValue(); |
| | | } else { |
| | | log.error(OrderParamEnums.STATE_0.getName()); |
| | | state = OrderParamEnums.STATE_0.getValue(); |
| | | } |
| | | } |
| | | |
| | | // 根据可用余额计算下单总保证金与每次下单金额 |
| | | BigDecimal totalOrderUsdt = availBal.multiply(TOTAL_ORDER_USDT_FACTOR).setScale(4, RoundingMode.DOWN); |
| | | BigDecimal everyTimeUsdt = totalOrderUsdt.divide(EVERY_TIME_USDT_FACTOR, 4, RoundingMode.DOWN); |
| | | |
| | | log.info( |
| | | "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 下单总保证金: {}, 每次下单保证金: {}, 是否允许开仓: {}", |
| | | ccy, availBalStr, cashBalStr, eq, totalOrderUsdt, everyTimeUsdt, state |
| | | ); |
| | | |
| | | saveToRedis(redisUtils, availBalStr, cashBalStr, totalOrderUsdt.toString(), everyTimeUsdt.toString(), state); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode()); |
| | | } |
| | | } catch (Exception innerEx) { |
| | | log.warn("处理账户频道数据失败", innerEx); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 安全地将字符串解析为 BigDecimal 类型 |
| | | * |
| | | * @param value 字符串数值 |
| | | * @return 解析后的 BigDecimal 对象,若解析失败则返回 null |
| | | */ |
| | | private static BigDecimal parseBigDecimalSafe(String value) { |
| | | try { |
| | | return new BigDecimal(value); |
| | | } catch (NumberFormatException e) { |
| | | log.warn("无法转换为 BigDecimal: {}", value); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将账户相关数据保存至 Redis 中 |
| | | * |
| | | * @param redisUtils Redis 工具类实例 |
| | | * @param availBal 可用余额 |
| | | * @param cashBal 现金余额 |
| | | * @param totalOrderUsdt 总下单保证金 |
| | | * @param everyTimeUsdt 每次下单保证金 |
| | | * @param state 当前账户状态(是否可开仓) |
| | | */ |
| | | private static void saveToRedis(RedisUtils redisUtils, String availBal, String cashBal, |
| | | String totalOrderUsdt, String everyTimeUsdt, String state) { |
| | | try { |
| | | boolean setResult = |
| | | redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":availBal", availBal, 0) |
| | | && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":cashBal", cashBal, 0) |
| | | && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":totalOrderUsdt", totalOrderUsdt, 0) |
| | | && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":everyTimeUsdt", everyTimeUsdt, 0) |
| | | && redisUtils.set(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", state, 0); |
| | | 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"; |
| | | private static void initParam(JSONObject detail) { |
| | | |
| | | if (!setResult) { |
| | | log.warn("Redis set operation failed for key: account:{}", CoinEnums.USDT.getCode()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Redis操作异常,key: account:{}, error: {}", CoinEnums.USDT.getCode(), e.getMessage(), e); |
| | | } |
| | | String ccy = WsMapBuild.parseStringSafe( detail.getString(ccyKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, ccyKey, ccy); |
| | | |
| | | String availBal = WsMapBuild.parseStringSafe(detail.getString(availBalKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, availBalKey, availBal); |
| | | |
| | | String cashBal = WsMapBuild.parseStringSafe(detail.getString(cashBalKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, cashBalKey, cashBal); |
| | | |
| | | String eq = WsMapBuild.parseStringSafe(detail.getString(eqKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, eqKey, eq); |
| | | |
| | | String upl = WsMapBuild.parseStringSafe(detail.getString(uplKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, uplKey, upl); |
| | | |
| | | BigDecimal cashBalDecimal = WsMapBuild.parseBigDecimalSafe(cashBal); |
| | | // 根据可用余额计算下单总保证金 |
| | | String total_order_usdtpecent = InstrumentsWs.INSTRUMENTSWSMAP.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(ACCOUNTWSMAP, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt)); |
| | | |
| | | log.info( |
| | | "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}", |
| | | ccy, availBal, cashBal, eq, upl, totalOrderUsdt |
| | | ); |
| | | } |
| | | } |
| | | |