| | |
| | | return; |
| | | } |
| | | checkProfitAndReset(); |
| | | |
| | | |
| | | if (state == StrategyState.ACTIVE && |
| | | longActive == false && |
| | | longPositionSize.compareTo(BigDecimal.ZERO) == 0){ |
| | | processShortGrid(closePrice); |
| | | } |
| | | |
| | | |
| | | if (state == StrategyState.ACTIVE && |
| | | shortActive == false && |
| | | shortPositionSize.compareTo(BigDecimal.ZERO) == 0){ |
| | | processLongGrid(closePrice); |
| | | } |
| | | } |
| | | |
| | | // ---- 仓位推送回调 ---- |
| | |
| | | } |
| | | } else { |
| | | if (longActive && state == StrategyState.ACTIVE) { |
| | | log.info("[Gate] 多仓持仓归零,重置策略"); |
| | | handlePositionZeroAndReset("多仓"); |
| | | // log.info("[Gate] 多仓持仓归零,重置策略"); |
| | | // handlePositionZeroAndReset("多仓"); |
| | | } |
| | | longActive = false; |
| | | longPositionSize = BigDecimal.ZERO; |
| | |
| | | } |
| | | } else { |
| | | if (shortActive && state == StrategyState.ACTIVE) { |
| | | log.info("[Gate] 空仓持仓归零,重置策略"); |
| | | handlePositionZeroAndReset("空仓"); |
| | | // log.info("[Gate] 空仓持仓归零,重置策略"); |
| | | // handlePositionZeroAndReset("空仓"); |
| | | } |
| | | shortActive = false; |
| | | shortPositionSize = BigDecimal.ZERO; |
| | |
| | | } |
| | | |
| | | private void processShortGrid(BigDecimal currentPrice) { |
| | | int prec = config.getPriceScale(); |
| | | List<BigDecimal> matched = new ArrayList<>(); |
| | | BigDecimal matched = BigDecimal.ZERO; |
| | | synchronized (shortPriceQueue) { |
| | | for (BigDecimal p : shortPriceQueue) { |
| | | if (p.compareTo(currentPrice) >= 0) { |
| | | matched.add(p); |
| | | matched = p; |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (matched.isEmpty()) { |
| | | return; |
| | | } |
| | | log.info("[Gate] 空仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice); |
| | | |
| | | synchronized (shortPriceQueue) { |
| | | shortPriceQueue.removeAll(matched); |
| | | BigDecimal min = shortPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : shortPriceQueue.get(shortPriceQueue.size() - 1); |
| | | BigDecimal gridStep = config.getStep(); |
| | | for (int i = 0; i < matched.size(); i++) { |
| | | min = min.subtract(gridStep).setScale(prec, RoundingMode.HALF_UP); |
| | | shortPriceQueue.add(min); |
| | | if (BigDecimal.ZERO.compareTo( matched) == 0) { |
| | | return; |
| | | } |
| | | shortPriceQueue.sort((a, b) -> b.compareTo(a)); |
| | | } |
| | | log.info("[Gate] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice); |
| | | |
| | | synchronized (longPriceQueue) { |
| | | BigDecimal first = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(0); |
| | | BigDecimal gridStep = config.getStep(); |
| | | for (int i = 1; i <= matched.size(); i++) { |
| | | BigDecimal elem = first.subtract(gridStep.multiply(BigDecimal.valueOf(i))).setScale(prec, RoundingMode.HALF_UP); |
| | | longPriceQueue.add(elem); |
| | | } |
| | | longPriceQueue.sort(BigDecimal::compareTo); |
| | | while (longPriceQueue.size() > config.getGridQueueSize()) { |
| | | longPriceQueue.remove(longPriceQueue.size() - 1); |
| | | } |
| | | } |
| | | GridElement matchedUpGridElement = GridElement.findByPrice(matched); |
| | | if (matchedUpGridElement != null){ |
| | | Integer upId = matchedUpGridElement.getUpId(); |
| | | GridElement newEntryGrid = GridElement.findById(upId); |
| | | |
| | | if (!isMarginSafe()) { |
| | | log.warn("[Gate] 保证金超限,跳过挂条件单"); |
| | | } else { |
| | | if (newEntryGrid != null) { |
| | | /** |
| | | * 看是否有多仓挂单,有就取消 |
| | | */ |
| | | if (!newEntryGrid.isHasLongOrder()) { |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | String size = config.getBaseQuantity(); |
| | | log.info("[Gate] 多仓仓位归零 gridId:{}, 挂{}基础张多单", |
| | | newEntryGrid.getId(), size); |
| | | newEntryGrid.getLongTraderParam().setQuantity(size); |
| | | placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, size); |
| | | } |
| | | |
| | | /** |
| | | * 下一个开仓位置 |
| | | * 获取队列第一个元素的价格对应的网格 |
| | | * 判断网格是否能开空仓,如果不能则跳过 |
| | | * 前进方向挂空仓条件单 |
| | | * 后置方向挂多空条件单 |
| | | */ |
| | | //下一个开仓位置 |
| | | BigDecimal newLongFirst = shortPriceQueue.get(0); |
| | | GridElement UpGridElement = GridElement.findByPrice(newLongFirst); |
| | | |
| | | // 判断网格是否能开空仓,如果不能则跳过 |
| | | if (UpGridElement != null) { |
| | | |
| | | // if (!UpGridElement.isHasShortOrder() && shortEntryPrice.compareTo(newLongFirst) > 0) { |
| | | // |
| | | // TraderParam upShortTraderParam = UpGridElement.getShortTraderParam(); |
| | | // placeEntryOrderWithPreFlag(UpGridElement, false, |
| | | // upShortTraderParam.getEntryPrice(), |
| | | // FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | // negate(upShortTraderParam.getQuantity())); |
| | | // } |
| | | int i = UpGridElement.getId() + 2; |
| | | GridElement downGridElement = GridElement.findById(i); |
| | | if (downGridElement != null){ |
| | | |
| | | BigDecimal downGridPrice = downGridElement.getGridPrice(); |
| | | |
| | | // TraderParam downShortTraderParam = downGridElement.getShortTraderParam(); |
| | | // if ( |
| | | // !downGridElement.isHasShortOrder() && |
| | | // downGridPrice.compareTo(longEntryPrice) <= 0 && |
| | | // downGridPrice.compareTo(shortEntryPrice) >= 0 |
| | | // ){ |
| | | // placeEntryOrderWithPreFlag(downGridElement, false, |
| | | // downShortTraderParam.getEntryPrice(), |
| | | // FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | // negate(downShortTraderParam.getQuantity())); |
| | | // |
| | | // } |
| | | |
| | | TraderParam downLongTraderParam = downGridElement.getLongTraderParam(); |
| | | if ( |
| | | !downGridElement.isHasLongOrder() && |
| | | downGridPrice.compareTo(longEntryPrice) <= 0 |
| | | ){ |
| | | placeEntryOrderWithPreFlag(downGridElement, true, |
| | | downLongTraderParam.getEntryPrice(), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | downLongTraderParam.getQuantity()); |
| | | GridElement cancelGridElement = GridElement.findById(newEntryGrid.getUpId()); |
| | | if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) { |
| | | /** |
| | | * 看是否有多仓挂单,有就取消 |
| | | */ |
| | | executor.cancelConditionalOrder(cancelGridElement.getLongOrderId(), oid -> { |
| | | longEntryTraderIdParam(cancelGridElement, null, false); |
| | | log.info("[Gate] 多仓仓位归零, 取消gridId:{}的多单", cancelGridElement); |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | private void processLongGrid(BigDecimal currentPrice) { |
| | | int prec = config.getPriceScale(); |
| | | List<BigDecimal> matched = new ArrayList<>(); |
| | | BigDecimal matched = BigDecimal.ZERO; |
| | | synchronized (longPriceQueue) { |
| | | for (BigDecimal p : longPriceQueue) { |
| | | if (p.compareTo(currentPrice) <= 0) { |
| | | matched.add(p); |
| | | matched = p; |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (matched.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | log.info("[Gate] 多仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice); |
| | | |
| | | /** |
| | | * 匹配到元素后, |
| | | * 多仓队列更新 |
| | | * 空仓队列更新 |
| | | */ |
| | | synchronized (longPriceQueue) { |
| | | longPriceQueue.removeAll(matched); |
| | | BigDecimal max = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(longPriceQueue.size() - 1); |
| | | BigDecimal gridStep = config.getStep(); |
| | | for (int i = 0; i < matched.size(); i++) { |
| | | max = max.add(gridStep).setScale(prec, RoundingMode.HALF_UP); |
| | | longPriceQueue.add(max); |
| | | if (BigDecimal.ZERO.compareTo( matched) == 0) { |
| | | return; |
| | | } |
| | | longPriceQueue.sort(BigDecimal::compareTo); |
| | | } |
| | | synchronized (shortPriceQueue) { |
| | | BigDecimal first = shortPriceQueue.isEmpty() ? matched.get(0) : shortPriceQueue.get(0); |
| | | BigDecimal gridStep = config.getStep(); |
| | | for (int i = 1; i <= matched.size(); i++) { |
| | | BigDecimal elem = first.add(gridStep.multiply(BigDecimal.valueOf(i))).setScale(prec, RoundingMode.HALF_UP); |
| | | shortPriceQueue.add(elem); |
| | | } |
| | | shortPriceQueue.sort((a, b) -> b.compareTo(a)); |
| | | while (shortPriceQueue.size() > config.getGridQueueSize()) { |
| | | shortPriceQueue.remove(shortPriceQueue.size() - 1); |
| | | } |
| | | } |
| | | |
| | | if (!isMarginSafe()) { |
| | | log.warn("[Gate] 保证金超限,跳过挂条件单"); |
| | | } else { |
| | | log.info("[Gate] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice); |
| | | |
| | | /** |
| | | * 下一个开仓位置 |
| | | * 获取队列第一个元素的价格对应的网格 |
| | | * 判断网格是否能开多仓,如果不能则跳过 |
| | | * 前进方向挂多仓条件单 |
| | | * 后置方向挂多空条件单 |
| | | */ |
| | | //下一个开仓位置 |
| | | BigDecimal newLongFirst = longPriceQueue.get(0); |
| | | GridElement UpGridElement = GridElement.findByPrice(newLongFirst); |
| | | GridElement matchedUpGridElement = GridElement.findByPrice(matched); |
| | | if (matchedUpGridElement != null){ |
| | | Integer downId = matchedUpGridElement.getDownId(); |
| | | GridElement newEntryGrid = GridElement.findById(downId); |
| | | |
| | | // 判断网格是否能开多仓,如果不能则跳过 |
| | | if (UpGridElement != null) { |
| | | if (newEntryGrid != null) { |
| | | |
| | | // if (!UpGridElement.isHasLongOrder() && longEntryPrice.compareTo(newLongFirst) < 0) { |
| | | // TraderParam upLongTraderParam = UpGridElement.getLongTraderParam(); |
| | | // placeEntryOrderWithPreFlag(UpGridElement, true, |
| | | // upLongTraderParam.getEntryPrice(), |
| | | // FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | // config.getQuantity()); |
| | | // } |
| | | |
| | | int i = UpGridElement.getId() - 2; |
| | | GridElement downGridElement = GridElement.findById(i); |
| | | if (downGridElement != null){ |
| | | |
| | | BigDecimal downGridPrice = downGridElement.getGridPrice(); |
| | | |
| | | // TraderParam downLongTraderParam = downGridElement.getLongTraderParam(); |
| | | // if ( |
| | | // !downGridElement.isHasLongOrder() && |
| | | // downGridPrice.compareTo(shortEntryPrice) >= 0 && |
| | | // downGridPrice.compareTo(longEntryPrice) <= 0 |
| | | // ){ |
| | | // placeEntryOrderWithPreFlag(downGridElement, true, |
| | | // downLongTraderParam.getEntryPrice(), |
| | | // FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | // config.getQuantity()); |
| | | // |
| | | // } |
| | | |
| | | TraderParam shortTraderParam = downGridElement.getShortTraderParam(); |
| | | if ( |
| | | !downGridElement.isHasShortOrder() && |
| | | downGridPrice.compareTo(shortEntryPrice) >= 0 |
| | | ){ |
| | | |
| | | placeEntryOrderWithPreFlag(downGridElement, false, |
| | | shortTraderParam.getEntryPrice(), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | negate(config.getQuantity())); |
| | | if (!newEntryGrid.isHasShortOrder()){ |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | String size = config.getBaseQuantity(); |
| | | log.info("[Gate] 空仓仓位归零 gridId:{}, 挂{}基础张多单", |
| | | newEntryGrid.getId(), size); |
| | | newEntryGrid.getShortTraderParam().setQuantity(size); |
| | | placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size)); |
| | | } |
| | | |
| | | GridElement cancelGridElement = GridElement.findById(newEntryGrid.getDownId()); |
| | | /** |
| | | * 看是否有空仓挂单,有就取消 |
| | | */ |
| | | if (cancelGridElement != null && cancelGridElement.isHasShortOrder()) { |
| | | executor.cancelConditionalOrder(cancelGridElement.getShortOrderId(), oid -> { |
| | | shortEntryTraderIdParam(cancelGridElement, null, false); |
| | | log.info("[Gate] 空仓仓位归零, 取消gridId:{}的多单", cancelGridElement); |
| | | }); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | longEntryQty++; |
| | | int entryQty = longEntryQty; |
| | | |
| | | // 最大持仓限制:已持仓+本次挂单 ≤ maxPositionSize |
| | | int maxPos = config.getMaxPositionSize(); |
| | | if (maxPos > 0) { |
| | | int currentPos = longPositionSize.intValue(); |
| | | int maxAllowed = maxPos - currentPos; |
| | | if (maxAllowed <= 0) { |
| | | log.warn("[Gate] 多仓止损触发 gridId:{}, 已达最大持仓{},跳过挂单", gridId, maxPos); |
| | | longEntryQty = 1; |
| | | return; |
| | | } |
| | | if (entryQty > maxAllowed) { |
| | | log.info("[Gate] 多仓止损触发 gridId:{}, 挂单{}张超限, 截断为{}张", gridId, entryQty, maxAllowed); |
| | | entryQty = maxAllowed; |
| | | longEntryQty = 1; |
| | | } |
| | | } |
| | | |
| | | String size = new BigDecimal(String.valueOf(entryQty)).multiply(new BigDecimal(config.getQuantity())).toString(); |
| | | log.info("[Gate] 多仓止损触发 gridId:{}, 在gridId:{}挂{}基础张多单(计数器:{}, size:{})", |
| | | gridId, newEntryGridId, entryQty, longEntryQty, size); |
| | |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | shortEntryQty++; |
| | | int entryQty = shortEntryQty; |
| | | |
| | | // 最大持仓限制:已持仓+本次挂单 ≤ maxPositionSize |
| | | int maxPos = config.getMaxPositionSize(); |
| | | if (maxPos > 0) { |
| | | int currentPos = shortPositionSize.intValue(); |
| | | int maxAllowed = maxPos - currentPos; |
| | | if (maxAllowed <= 0) { |
| | | log.warn("[Gate] 空仓止损触发 gridId:{}, 已达最大持仓{},跳过挂单", gridId, maxPos); |
| | | shortEntryQty = 1; |
| | | return; |
| | | } |
| | | if (entryQty > maxAllowed) { |
| | | log.info("[Gate] 空仓止损触发 gridId:{}, 挂单{}张超限, 截断为{}张", gridId, entryQty, maxAllowed); |
| | | entryQty = maxAllowed; |
| | | shortEntryQty = 1; |
| | | } |
| | | } |
| | | |
| | | String size = new BigDecimal(String.valueOf(entryQty)).multiply(new BigDecimal(config.getQuantity())).toString(); |
| | | log.info("[Gate] 空仓止损触发 gridId:{}, 在gridId:{}挂{}基础张空单(计数器:{}, size:{})", |
| | | gridId, newEntryGridId, entryQty, shortEntryQty, size); |
| | |
| | | } |
| | | } |
| | | if (furthestSlId == 0) { |
| | | furthestSlId = -11; |
| | | furthestSlId = -(Integer.parseInt(config.getBaseQuantity()) + 1); |
| | | } |
| | | log.info("[Gate] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty); |
| | | for (int i = 0; i < filledQty; i++) { |
| | |
| | | } |
| | | } |
| | | if (furthestSlId == 0) { |
| | | furthestSlId = 11; |
| | | furthestSlId = Integer.parseInt(config.getBaseQuantity()) + 1; |
| | | } |
| | | log.info("[Gate] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty); |
| | | for (int i = 0; i < filledQty; i++) { |