fix(okx): 修复账户余额获取和API请求处理问题
- 修改账户余额查询逻辑,改为获取USDT合约账户余额
- 更新WebSocket连接地址为业务地址(wss://.../ws/v5/business)
- 添加pong消息处理避免心跳异常
- 修改条件单取消接口调用方式,使用原始请求体格式
- 新增sendSignedRequestRaw方法支持原始请求体签名
| | |
| | | */ |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | 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 地址 */ |
| | |
| | | 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); |
| | | |
| | |
| | | |
| | | 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"); |
| | |
| | | } |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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); |
| | | } |
| | | } |