Administrator
2026-06-02 0f5172865f608869bf16fbe14099f9a2c7269b76
fix(okx): 修复账户余额获取和API请求处理问题

- 修改账户余额查询逻辑,改为获取USDT合约账户余额
- 更新WebSocket连接地址为业务地址(wss://.../ws/v5/business)
- 添加pong消息处理避免心跳异常
- 修改条件单取消接口调用方式,使用原始请求体格式
- 新增sendSignedRequestRaw方法支持原始请求体签名
4 files modified
69 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridWsClient.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxTradeExecutor.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/RequestHandler.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
@@ -104,18 +104,27 @@
     */
    public void init() {
        try {
            // 1. 查询账户获取初始本金
            // 1. 查询账户获取初始本金(仅取 USDT 合约账户余额)
            String balanceResp = executor.getBalance();
            if (balanceResp != null) {
                JSONObject json = JSON.parseObject(balanceResp);
                if ("0".equals(json.getString("code"))) {
                    JSONArray data = json.getJSONArray("data");
                    if (data != null && !data.isEmpty()) {
                        JSONObject detail = data.getJSONObject(0);
                        String totalEq = detail.getString("totalEq");
                        if (totalEq != null) {
                            this.initialPrincipal = new BigDecimal(totalEq);
                            log.info("[OKX] 初始本金: {} USDT", initialPrincipal);
                        JSONObject accountData = data.getJSONObject(0);
                        JSONArray details = accountData.getJSONArray("details");
                        if (details != null) {
                            for (int i = 0; i < details.size(); i++) {
                                JSONObject detail = details.getJSONObject(i);
                                if ("USDT".equals(detail.getString("ccy"))) {
                                    String eq = detail.getString("eq");
                                    if (eq != null) {
                                        this.initialPrincipal = new BigDecimal(eq);
                                        log.info("[OKX] 初始本金(USDT合约): {} USDT", initialPrincipal);
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridWsClient.java
@@ -38,10 +38,10 @@
    private static final int HEARTBEAT_TIMEOUT = 10;
    /** 模拟盘公共 WS 地址 */
    private static final String WS_PUBLIC_URL_SIM = "wss://wspap.okx.com:8443/ws/v5/public";
    /** 实盘公共 WS 地址 */
    private static final String WS_PUBLIC_URL_PROD = "wss://ws.okx.com:8443/ws/v5/public";
    /** 模拟盘业务 WS 地址(K线等行情数据) */
    private static final String WS_BUSINESS_URL_SIM = "wss://wspap.okx.com:8443/ws/v5/business";
    /** 实盘业务 WS 地址(K线等行情数据) */
    private static final String WS_BUSINESS_URL_PROD = "wss://ws.okx.com:8443/ws/v5/business";
    /** 模拟盘私有 WS 地址 */
    private static final String WS_PRIVATE_URL_SIM = "wss://wspap.okx.com:8443/ws/v5/private";
    /** 实盘私有 WS 地址 */
@@ -119,9 +119,9 @@
            System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
            String wsUrl;
            if (account.isAccountType()) {
                wsUrl = isPublic ? WS_PUBLIC_URL_PROD : WS_PRIVATE_URL_PROD;
                wsUrl = isPublic ? WS_BUSINESS_URL_PROD : WS_PRIVATE_URL_PROD;
            } else {
                wsUrl = isPublic ? WS_PUBLIC_URL_SIM : WS_PRIVATE_URL_SIM;
                wsUrl = isPublic ? WS_BUSINESS_URL_SIM : WS_PRIVATE_URL_SIM;
            }
            URI uri = new URI(wsUrl);
@@ -212,6 +212,10 @@
    private void handleMessage(String message) {
        try {
            if ("pong".equals(message)) {
                log.debug("[{}] 收到 pong", logPrefix);
                return;
            }
            JSONObject response = JSON.parseObject(message);
            String event = response.getString("event");
            String op = response.getString("op");
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxTradeExecutor.java
@@ -219,11 +219,9 @@
        }
        executor.execute(() -> {
            try {
                LinkedHashMap<String, Object> params = new LinkedHashMap<>();
                params.put("instId", instId);
                params.put("algoId", algoId);
                String resp = okxAccount.requestHandler.sendSignedRequest(
                        okxAccount.baseUrl, "/api/v5/trade/cancel-algos", params, HttpMethod.POST, okxAccount.isSimluate());
                String body = "[{\"instId\":\"" + instId + "\",\"algoId\":\"" + algoId + "\"}]";
                String resp = okxAccount.requestHandler.sendSignedRequestRaw(
                        okxAccount.baseUrl, "/api/v5/trade/cancel-algos", body, HttpMethod.POST, okxAccount.isSimluate());
                log.info("[OkxExec] 条件单已取消, algoId:{}", algoId);
                if (onSuccess != null) {
                    onSuccess.accept(algoId);
@@ -240,10 +238,9 @@
    public void cancelAllAlgoOrders() {
        executor.execute(() -> {
            try {
                LinkedHashMap<String, Object> params = new LinkedHashMap<>();
                params.put("instId", instId);
                String resp = okxAccount.requestHandler.sendSignedRequest(
                        okxAccount.baseUrl, "/api/v5/trade/cancel-algos", params, HttpMethod.POST, okxAccount.isSimluate());
                String body = "[{\"instId\":\"" + instId + "\",\"instType\":\"SWAP\"}]";
                String resp = okxAccount.requestHandler.sendSignedRequestRaw(
                        okxAccount.baseUrl, "/api/v5/trade/cancel-algos", body, HttpMethod.POST, okxAccount.isSimluate());
                log.info("[OkxExec] 已尝试清除条件单, resp:{}", resp);
            } catch (Exception e) {
                log.error("[OkxExec] 清除条件单失败", e);
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/RequestHandler.java
@@ -118,9 +118,20 @@
        if (null == secretKey || secretKey.isEmpty() || null == apiKey || apiKey.isEmpty()) {
            throw new FebsException("[RequestHandler] Secret key/API key cannot be null or empty!");
        }
        //parameters.put("timestamp", UrlBuilder.buildTimestamp());
        //String queryString = UrlBuilder.joinQueryParameters(parameters);
        //String signature = SignatureGenerator.getSignature(queryString, secretKey);
        return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.SIGNED, isSimluate);
    }
    public String sendSignedRequestRaw(String baseUrl, String urlPath, String rawBody,
                                        HttpMethod httpMethod, boolean isSimluate) {
        if (null == secretKey || secretKey.isEmpty() || null == apiKey || apiKey.isEmpty()) {
            throw new FebsException("[RequestHandler] Secret key/API key cannot be null or empty!");
        }
        String fullUrl = UrlBuilder.buildFullUrl(baseUrl, urlPath, null, null);
        log.debug("{} {}", httpMethod, fullUrl);
        String timestamp = DateUtils.format(DateUtils.FORMAT_UTC_ISO8601, new Date(), 0);
        String queryString = urlPath;
        String sign = SignUtils.signRest(secretKey, timestamp, httpMethod.toString(), queryString, rawBody);
        Request request = RequestBuilder.buildApiKeyRequest(fullUrl, rawBody, passphrase, sign, timestamp, httpMethod, apiKey, isSimluate);
        return ResponseHandler.handleResponse(request, isSimluate);
    }
}