From 782bebb2e734e1782e875fc2f45cbef71cb07712 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 05 Jun 2026 11:56:13 +0800
Subject: [PATCH] fix(okxNewPrice): 解决网格交易价格精度计算问题

---
 src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java |   91 +++++++++++++++++++++++++++++++--------------
 1 files changed, 62 insertions(+), 29 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
index dca76ea..b8a94ce 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
+++ b/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;
+                                }
+                            }
                         }
                     }
                 }
@@ -208,10 +217,19 @@
                 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);
+                        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);
+                                    }
+                                    break;
+                                }
+                            }
                         }
                     }
                 }
@@ -338,7 +356,7 @@
     // ---- 订单/条件单推送回调 ----
 
     public void onOrderUpdate(String algoId, String state, String ordType) {
-        if (!"effective".equals(state) && !"canceled".equals(state)) {
+        if (!"filled".equals(state) && !"canceled".equals(state)) {
             return;
         }
 
@@ -390,31 +408,33 @@
             baseGridElement.setShortOrderId(baseShortTp.getEntryOrderId());
             baseGridElement.setHasShortOrder(true);
 
-            // 挂多仓止损 (id=-2 到 -11)
+            // 挂多仓止损 (id=-2 到 -11),每格 quantity 张
             for (int id = -2; id >= -11; id--) {
                 OkxGridElement elem = OkxGridElement.findById(id);
                 if (elem == null) continue;
                 BigDecimal triggerPrice = elem.getGridPrice();
                 int finalId = id;
-                executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", "1",
+                executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                         profitId -> {
                             elem.setLongStopLossOrderId(profitId);
                             OkxGridElement.refreshIndices();
-                            log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
+                            log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
+                                    finalId, triggerPrice, config.getQuantity(), profitId);
                         });
             }
 
-            // 挂空仓止损 (id=2 到 11)
+            // 挂空仓止损 (id=2 到 11),每格 quantity 张
             for (int id = 2; id <= 11; id++) {
                 OkxGridElement elem = OkxGridElement.findById(id);
                 if (elem == null) continue;
                 BigDecimal triggerPrice = elem.getGridPrice();
                 int finalId = id;
-                executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", "1",
+                executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                         profitId -> {
                             elem.setShortStopLossOrderId(profitId);
                             OkxGridElement.refreshIndices();
-                            log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
+                            log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
+                                    finalId, triggerPrice, config.getQuantity(), profitId);
                         });
             }
 
@@ -543,10 +563,14 @@
 
         BigDecimal triggerPrice = newEntryGrid.getGridPrice();
         BigDecimal priceDiff = longEntryPrice.subtract(triggerPrice).abs();
-        int entryQty = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
-        entryQty = Math.max(1, entryQty);
+        // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题
+        BigDecimal epsilon = new BigDecimal("0.00000001");
+        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
+        count = Math.max(1, count);
+        int entryQty = count * Integer.parseInt(config.getQuantity());
         String size = String.valueOf(entryQty);
-        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单", gridId, newEntryGridId, entryQty);
+        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(价差:{},步长:{},count:{},qty:{})",
+                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
         newEntryGrid.getLongTraderParam().setQuantity(size);
         placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
     }
@@ -583,15 +607,22 @@
 
         BigDecimal triggerPrice = newEntryGrid.getGridPrice();
         BigDecimal priceDiff = shortEntryPrice.subtract(triggerPrice).abs();
-        int entryQty = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
-        entryQty = Math.max(1, entryQty);
+        // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题
+        BigDecimal epsilon = new BigDecimal("0.00000001");
+        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
+        count = Math.max(1, count);
+        int entryQty = count * Integer.parseInt(config.getQuantity());
         String size = String.valueOf(entryQty);
-        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单", gridId, newEntryGridId, entryQty);
+        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(价差:{},步长:{},count:{},qty:{})",
+                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
         newEntryGrid.getShortTraderParam().setQuantity(size);
         placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size);
     }
 
     private void extendLongStopLoss(int filledQty) {
+        // filledQty 为本次新增止损张数 = count * quantity, 需要按 quantity 为粒度拆分为 count 个止损单
+        int qty = Integer.parseInt(config.getQuantity());
+        int stopLossCount = filledQty / qty;
         int furthestSlId = 0;
         for (OkxGridElement e : config.getGridElements()) {
             if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
@@ -599,14 +630,14 @@
             }
         }
         if (furthestSlId == 0) furthestSlId = -11;
-        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
-        for (int i = 0; i < filledQty; i++) {
+        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
+        for (int i = 0; i < stopLossCount; i++) {
             int newSlId = furthestSlId - i - 1;
             OkxGridElement elem = OkxGridElement.findById(newSlId);
             if (elem == null) continue;
             BigDecimal triggerPrice = elem.getGridPrice();
             int finalSlId = newSlId;
-            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", "1",
+            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                     profitId -> {
                         elem.setLongStopLossOrderId(profitId);
                         OkxGridElement.refreshIndices();
@@ -616,6 +647,8 @@
     }
 
     private void extendShortStopLoss(int filledQty) {
+        int qty = Integer.parseInt(config.getQuantity());
+        int stopLossCount = filledQty / qty;
         int furthestSlId = 0;
         for (OkxGridElement e : config.getGridElements()) {
             if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
@@ -623,14 +656,14 @@
             }
         }
         if (furthestSlId == 0) furthestSlId = 11;
-        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
-        for (int i = 0; i < filledQty; i++) {
+        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
+        for (int i = 0; i < stopLossCount; i++) {
             int newSlId = furthestSlId + i + 1;
             OkxGridElement elem = OkxGridElement.findById(newSlId);
             if (elem == null) continue;
             BigDecimal triggerPrice = elem.getGridPrice();
             int finalSlId = newSlId;
-            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", "1",
+            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                     profitId -> {
                         elem.setShortStopLossOrderId(profitId);
                         OkxGridElement.refreshIndices();

--
Gitblit v1.9.1