Administrator
2025-12-10 ae37093a561a070a23c844c674c0dbe0b41801cf
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package com.xcong.excoin.modules.okxNewPrice.okxWs;
 
import cn.hutool.core.util.StrUtil;
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.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.math.BigDecimal;
import java.util.Optional;
 
/**
 * @author Administrator
 */
@Slf4j
public class PositionsWs {
 
    public static final String POSITIONSWS_CHANNEL = "positions";
    private static final String REDIS_KEY_PREFIX = POSITIONSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode();
 
    public static void subscribePositionChannel(WebSocketClient webSocketClient, String option) {
        try {
            JSONArray argsArray = new JSONArray();
            JSONObject args = new JSONObject();
            args.put("channel", POSITIONSWS_CHANNEL);
            args.put("instType", CoinEnums.INSTTYPE_SWAP.getCode());
            args.put("instId", CoinEnums.HE_YUE.getCode());
            argsArray.add(args);
 
            String connId = MallUtils.getOrderNum(POSITIONSWS_CHANNEL);
            JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray);
            webSocketClient.send(jsonObject.toJSONString());
            log.info("发送持仓频道频道:{}", option);
        } catch (Exception e) {
            log.error("订阅持仓频道频道构建失败", e);
        }
    }
 
    public static void handleEvent(JSONObject response, RedisUtils redisUtils) {
 
 
        log.info("开始执行PositionsWs......");
        try {
            JSONArray dataArray = response.getJSONArray("data");
            if (dataArray == null || dataArray.isEmpty()) {
                log.info("账户持仓频道数据为空,已当前价买入,并且初始化网格");
                JSONObject posData = new JSONObject();
                processPositionData(posData, redisUtils);
                return;
            }
 
            for (int i = 0; i < dataArray.size(); i++) {
                JSONObject posData = dataArray.getJSONObject(i);
                String instId = posData.getString("instId");
                if (CoinEnums.HE_YUE.getCode().equals(instId)) {
                    log.info("查询到账户{}持仓数据",CoinEnums.HE_YUE.getCode());
                    String mgnMode = posData.getString("mgnMode");
                    String posSide = posData.getString("posSide");
                    String pos = posData.getString("pos");
                    String avgPx = posData.getString("avgPx");
                    String upl = posData.getString("upl");
                    String uplRatio = posData.getString("uplRatio");
                    String lever = posData.getString("lever");
                    String liqPx = posData.getString("liqPx");
                    String markPx = posData.getString("markPx");
                    String imr = posData.getString("imr");
                    String mgnRatio = posData.getString("mgnRatio");
                    String mmr = posData.getString("mmr");
                    String notionalUsd = posData.getString("notionalUsd");
                    String ccy = posData.getString("ccy");
                    String last = posData.getString("last");
                    String idxPx = posData.getString("idxPx");
                    String bePx = posData.getString("bePx");
                    String realizedPnl = posData.getString("realizedPnl");
                    String settledPnl = posData.getString("settledPnl");
                    log.info(
                            "账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, "
                                    + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, "
                                    + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, "
                                    + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}"
                                    + "最新标记价格: {}",
                            instId, mgnMode, posSide, pos, avgPx,
                            upl, uplRatio, lever, liqPx, imr,
                            mgnRatio, mmr, notionalUsd, ccy,
                            last, idxPx, bePx, realizedPnl, settledPnl,
                            markPx
                    );
                    processPositionData(posData, redisUtils);
                }
            }
        } catch (Exception e) {
            log.error("处理持仓频道推送数据失败", e);
        }
    }
 
    private static void processPositionData(JSONObject posData, RedisUtils redisUtils) {
        try {
            String avgPx = safeGetString(posData, "avgPx");
            String pos = safeGetString(posData, "pos");
            String upl = safeGetString(posData, "upl");
            String imr = safeGetString(posData, "imr");
            String mgnRatio = safeGetString(posData, "mgnRatio");
            String markPx = safeGetString(posData, "markPx");
            String bePx = safeGetString(posData, "bePx");
            String realizedPnl = safeGetString(posData, "realizedPnl");
 
            boolean setResult = saveToRedis(redisUtils, avgPx, pos, upl, imr, mgnRatio, markPx, bePx,realizedPnl);
 
            if (setResult) {
                calculateAndSaveBuyCount(redisUtils);
            } else {
                log.warn("Redis操作失败");
            }
        } catch (Exception e) {
            log.error("Redis操作异常", e);
        }
    }
 
    private static boolean saveToRedis(RedisUtils redisUtils,
                                       String avgPx, String pos, String upl,
                                       String imr, String mgnRatio, String markPx, String bePx, String realizedPnl) {
        return redisUtils.set(REDIS_KEY_PREFIX + ":avgPx", avgPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":pos", pos, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":upl", upl, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":imr", imr, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":mgnRatio", mgnRatio, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":markPx", markPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":bePx", bePx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":realizedPnl", realizedPnl, 0);
    }
 
    private static void calculateAndSaveBuyCount(RedisUtils redisUtils) {
        try {
            String ctValStr = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":ctVal");
            String markPxStr = (String) redisUtils.get(REDIS_KEY_PREFIX + ":markPx");
            String minSzStr = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":minSz");
            String everyTimeUsdt = (String) redisUtils.get(AccountWs.ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":everyTimeUsdt");
 
            BigDecimal margin = parseBigDecimal(everyTimeUsdt, "0");
            BigDecimal leverage = parseBigDecimal(OrderParamEnums.LEVERAGE.getValue(), "1");
            BigDecimal faceValue = parseBigDecimal(ctValStr, "0");
            BigDecimal markPrice = parseBigDecimal(markPxStr, "0"); // 默认值需谨慎对待
            int minLotSz = parseInt(minSzStr, 0);
 
            BigDecimal buyCnt = buyCnt(margin, leverage, faceValue, markPrice, minLotSz);
            redisUtils.set(REDIS_KEY_PREFIX + ":buyCnt", buyCnt.toString(), 0);
        } catch (NumberFormatException | ArithmeticException e) {
            log.error("计算购买数量时发生数字转换错误", e);
        }
    }
 
    private static String safeGetString(JSONObject obj, String key) {
        return Optional.ofNullable(obj.getString(key)).orElse("0");
    }
 
    private static BigDecimal parseBigDecimal(String value, String defaultValue) {
        return new BigDecimal(Optional.ofNullable(value).filter(s -> !s.isEmpty()).orElse(defaultValue));
    }
 
    private static int parseInt(String value, int defaultValue) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }
 
    /**
     * 计算购买合约的数量
     *
     * USDT 币本位合约
     * 公式:张数 = 保证金 / (面值 * 标记价格 / 杠杆倍数)
     *
     * @param margin 用户的保证金金额
     * @param leverage 杠杆倍数
     * @param faceValue 合约面值
     * @param markPrice 标记价格
     * @param minLotSz 最小下单精度
     * @return 返回用户可以购买的合约数量
     */
    public static BigDecimal buyCnt(BigDecimal margin, BigDecimal leverage, BigDecimal faceValue, BigDecimal markPrice, int minLotSz) {
        if (margin.compareTo(BigDecimal.ZERO) <= 0 ||
            leverage.compareTo(BigDecimal.ZERO) <= 0 ||
            faceValue.compareTo(BigDecimal.ZERO) <= 0 ||
            markPrice.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
 
        BigDecimal divisor = markPrice.divide(leverage, 10, BigDecimal.ROUND_DOWN);
        BigDecimal denominator = faceValue.multiply(divisor);
        return margin.divide(denominator, minLotSz, BigDecimal.ROUND_DOWN);
    }
}