feat(gate): 添加加仓启动阈值配置功能
- 在前端表单中新增加仓启动阈值输入字段
- 在GateConfig类中添加addPositionStartThreshold属性及相关getter方法
- 在GateConfigDTO中添加对应的阈值属性
- 更新持久化服务以支持阈值参数存储
- 修改网格交易逻辑实现阈值控制和间隔计算优化
- 默认设置加仓启动阈值为1,支持前N次止损不触发加仓
| | |
| | | private final int addPositionQuantity; |
| | | /** 单边最大仓位量:单向持仓张数上限,超出则按上限挂单,0=不限制 */ |
| | | private final int maxPositionPerSide; |
| | | /** 加仓启动阈值:前N次止损不触发加仓,默认 1(第1次止损不加仓,第2次起按公式计算) */ |
| | | private final int addPositionStartThreshold; |
| | | /** 网格绝对步长(shortBaseEntryPrice × gridRate),运行时由队列生成逻辑设置 */ |
| | | private BigDecimal step; |
| | | /** 网格元素列表,由队列初始化时同步填充,包含完整的多空仓挂单状态 */ |
| | |
| | | this.addPositionInterval = builder.addPositionInterval; |
| | | this.addPositionQuantity = builder.addPositionQuantity; |
| | | this.maxPositionPerSide = builder.maxPositionPerSide; |
| | | this.addPositionStartThreshold = builder.addPositionStartThreshold; |
| | | } |
| | | |
| | | // ==================== REST/WS 地址 ==================== |
| | |
| | | public int getAddPositionQuantity() { return addPositionQuantity; } |
| | | /** @return 单边最大仓位量:单向持仓张数上限,0=不限制 */ |
| | | public int getMaxPositionPerSide() { return maxPositionPerSide; } |
| | | /** @return 加仓启动阈值:前N次止损不触发加仓 */ |
| | | public int getAddPositionStartThreshold() { return addPositionStartThreshold; } |
| | | |
| | | // ==================== 运行时参数 ==================== |
| | | |
| | |
| | | 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; } |
| | |
| | | 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); |
| | |
| | | private int addPositionQuantity; |
| | | /** 单边最大仓位量:单向持仓张数上限,超出则按上限挂单,0=不限制 */ |
| | | private int maxPositionPerSide; |
| | | /** 加仓启动阈值:前N次止损不触发加仓,默认 1 */ |
| | | private int addPositionStartThreshold; |
| | | |
| | | /** |
| | | * 从 GateConfig 构建 DTO(不暴露 apiSecret)。 |
| | |
| | | .addPositionInterval(config.getAddPositionInterval()) |
| | | .addPositionQuantity(config.getAddPositionQuantity()) |
| | | .maxPositionPerSide(config.getMaxPositionPerSide()) |
| | | .addPositionStartThreshold(config.getAddPositionStartThreshold()) |
| | | .build(); |
| | | } |
| | | } |
| | |
| | | .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(); |
| | | } |
| | | |
| | |
| | | * 根据加仓配置计算止损追单时的实际下单量。 |
| | | * <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> |
| | |
| | | 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; |
| | | |
| | |
| | | <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> |
| | | |
| | |
| | | // 文件不存在 → 用默认值填充,用户可编辑后保存 |
| | | 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; |
| | |
| | | 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() { |
| | |
| | | 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 |
| | | }; |
| | | } |
| | | |