Administrator
2026-08-10 f5ad6890f4dda6f39e8b6fa792603faef6fdc801
feat(gate): 添加加仓启动阈值配置功能

- 在前端表单中新增加仓启动阈值输入字段
- 在GateConfig类中添加addPositionStartThreshold属性及相关getter方法
- 在GateConfigDTO中添加对应的阈值属性
- 更新持久化服务以支持阈值参数存储
- 修改网格交易逻辑实现阈值控制和间隔计算优化
- 默认设置加仓启动阈值为1,支持前N次止损不触发加仓
5 files modified
33 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateConfigDTO.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 11 ●●●● patch | view | raw | blame | history
src/main/resources/static/gate-config.html 7 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -119,6 +119,8 @@
    private final int addPositionQuantity;
    /** 单边最大仓位量:单向持仓张数上限,超出则按上限挂单,0=不限制 */
    private final int maxPositionPerSide;
    /** 加仓启动阈值:前N次止损不触发加仓,默认 1(第1次止损不加仓,第2次起按公式计算) */
    private final int addPositionStartThreshold;
    /** 网格绝对步长(shortBaseEntryPrice × gridRate),运行时由队列生成逻辑设置 */
    private BigDecimal step;
    /** 网格元素列表,由队列初始化时同步填充,包含完整的多空仓挂单状态 */
@@ -158,6 +160,7 @@
        this.addPositionInterval = builder.addPositionInterval;
        this.addPositionQuantity = builder.addPositionQuantity;
        this.maxPositionPerSide = builder.maxPositionPerSide;
        this.addPositionStartThreshold = builder.addPositionStartThreshold;
    }
    // ==================== REST/WS 地址 ====================
@@ -261,6 +264,8 @@
    public int getAddPositionQuantity() { return addPositionQuantity; }
    /** @return 单边最大仓位量:单向持仓张数上限,0=不限制 */
    public int getMaxPositionPerSide() { return maxPositionPerSide; }
    /** @return 加仓启动阈值:前N次止损不触发加仓 */
    public int getAddPositionStartThreshold() { return addPositionStartThreshold; }
    // ==================== 运行时参数 ====================
@@ -369,6 +374,8 @@
        private int addPositionQuantity = 1;
        /** 单边最大仓位量:单向持仓张数上限,超出则按上限挂单,0=不限制 */
        private int maxPositionPerSide = 0;
        /** 加仓启动阈值:前N次止损不触发加仓,调整后的 effectiveCount = max(0, count - threshold)。默认 1 */
        private int addPositionStartThreshold = 1;
        /** 设置 API Key */
        public Builder apiKey(String apiKey) { this.apiKey = apiKey; return this; }
@@ -428,6 +435,8 @@
        public Builder addPositionQuantity(int addPositionQuantity) { this.addPositionQuantity = addPositionQuantity; return this; }
        /** 设置单边最大仓位量:单向持仓张数上限,0=不限制 */
        public Builder maxPositionPerSide(int maxPositionPerSide) { this.maxPositionPerSide = maxPositionPerSide; return this; }
        /** 设置加仓启动阈值:前N次止损不触发加仓,0=立即启动 */
        public Builder addPositionStartThreshold(int addPositionStartThreshold) { this.addPositionStartThreshold = addPositionStartThreshold; return this; }
        public GateConfig build() {
            return new GateConfig(this);
src/main/java/com/xcong/excoin/modules/gateApi/GateConfigDTO.java
@@ -65,6 +65,8 @@
    private int addPositionQuantity;
    /** 单边最大仓位量:单向持仓张数上限,超出则按上限挂单,0=不限制 */
    private int maxPositionPerSide;
    /** 加仓启动阈值:前N次止损不触发加仓,默认 1 */
    private int addPositionStartThreshold;
    /**
     * 从 GateConfig 构建 DTO(不暴露 apiSecret)。
@@ -96,6 +98,7 @@
                .addPositionInterval(config.getAddPositionInterval())
                .addPositionQuantity(config.getAddPositionQuantity())
                .maxPositionPerSide(config.getMaxPositionPerSide())
                .addPositionStartThreshold(config.getAddPositionStartThreshold())
                .build();
    }
}
src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java
@@ -107,9 +107,10 @@
                .priceDriveEnabled(nvl(dto.getPriceDriveEnabled(), true))
                .rounds(dto.getRounds())
                .stopLossCountMode(nvl(dto.getStopLossCountMode(), "dual"))
                .addPositionInterval(dto.getAddPositionInterval() > 0 ? dto.getAddPositionInterval() : 3)
                .addPositionInterval(dto.getAddPositionInterval() >= 0 ? dto.getAddPositionInterval() : 3)
                .addPositionQuantity(dto.getAddPositionQuantity() > 0 ? dto.getAddPositionQuantity() : 1)
                .maxPositionPerSide(dto.getMaxPositionPerSide())
                .addPositionStartThreshold(dto.getAddPositionStartThreshold() >= 0 ? dto.getAddPositionStartThreshold() : 1)
                .build();
    }
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -1295,7 +1295,8 @@
     * 根据加仓配置计算止损追单时的实际下单量。
     * <p>公式:</p>
     * <pre>
     * addMultiplier = floor(effectiveStopLossCount / addPositionInterval)
     * divisor = addPositionInterval + 1
     * addMultiplier = floor(effectiveStopLossCount / divisor)
     * addQty = addMultiplier × addPositionQuantity
     * finalQty = min(baseQuantity + addQty, maxPositionPerSide > 0 ? maxPositionPerSide : ∞)
     * </pre>
@@ -1310,7 +1311,13 @@
        int maxPerSide = config.getMaxPositionPerSide();
        int effectiveCount = getEffectiveStopLossCount(isLong);
        int addMultiplier = interval > 0 ? effectiveCount / interval : 0;
        int startThreshold = config.getAddPositionStartThreshold();
        if (startThreshold > 0) {
            effectiveCount = Math.max(0, effectiveCount - startThreshold);
        }
        // divisor = interval + 1:interval=0→每次加仓, interval=1→每2次加仓(2,4,6...), interval=3→每4次加仓(4,8,12...)
        int divisor = interval + 1;
        int addMultiplier = interval >= 0 ? effectiveCount / divisor : 0;
        int addQty = addMultiplier * addQtyPerUnit;
        int totalQty = baseQty + addQty;
src/main/resources/static/gate-config.html
@@ -131,6 +131,7 @@
            <div class="form-group"><label>加仓间隔(次)</label><input id="addPositionInterval" type="number" placeholder="3"></div>
            <div class="form-group"><label>加仓数量(张)</label><input id="addPositionQuantity" type="number" placeholder="1"></div>
            <div class="form-group"><label>单边最大仓位量(张)</label><input id="maxPositionPerSide" type="number" placeholder="0(不限制)"></div>
            <div class="form-group"><label>加仓启动阈值(次)</label><input id="addPositionStartThreshold" type="number" placeholder="1(前N次止损不加仓)"></div>
        </form>
    </div>
@@ -207,7 +208,7 @@
        // 文件不存在 → 用默认值填充,用户可编辑后保存
        fillForm({ gridRate:0.005, expectedProfit:0.15, maxLoss:1.5,
            baseQuantity:'2', quantity:'2', maxPositionSize:4, stopLossCount:0, takeProfitGridSpan:2, priceDriveEnabled: true, rounds: 0,
            stopLossCountMode:'dual', addPositionInterval:3, addPositionQuantity:1, maxPositionPerSide:0 });
            stopLossCountMode:'dual', addPositionInterval:3, addPositionQuantity:1, maxPositionPerSide:0, addPositionStartThreshold:1 });
        toast('未找到配置,已加载默认值,编辑后请保存', 'success');
    }
    currentApiKey = key;
@@ -231,6 +232,7 @@
    byId('addPositionInterval').value = d.addPositionInterval ?? 3;
    byId('addPositionQuantity').value = d.addPositionQuantity ?? 1;
    byId('maxPositionPerSide').value = d.maxPositionPerSide ?? 0;
    byId('addPositionStartThreshold').value = d.addPositionStartThreshold ?? 1;
}
function collectForm() {
@@ -249,7 +251,8 @@
        stopLossCountMode: byId('stopLossCountMode').value,
        addPositionInterval: parseInt(byId('addPositionInterval').value) || 3,
        addPositionQuantity: parseInt(byId('addPositionQuantity').value) || 1,
        maxPositionPerSide: parseInt(byId('maxPositionPerSide').value) || 0
        maxPositionPerSide: parseInt(byId('maxPositionPerSide').value) || 0,
        addPositionStartThreshold: parseInt(byId('addPositionStartThreshold').value) ?? 1
    };
}