Administrator
5 days ago 23ece6103fd890655f0eef79331d3d73921611a2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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<String, Map<String, String>> ACCOUNTWSMAP = new ConcurrentHashMap<>();
    
    // 获取指定账号的Map,如果不存在则创建
    public static Map<String, String> 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<String, String> 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<String, String> 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);
        totalOrderUsdt = totalOrderUsdt.divide(new BigDecimal("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
        );
    }
}