网格卖单改为挂单成交

This commit is contained in:
tony 2026-07-11 00:43:35 +08:00
parent 8e7e1075f7
commit 08c5559cf6
2 changed files with 265 additions and 121 deletions

View File

@ -5,7 +5,7 @@
<groupId>com.sample</groupId> <groupId>com.sample</groupId>
<artifactId>trend-grid-strategy</artifactId> <artifactId>trend-grid-strategy</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>1.0.1-SNAPSHOT</version>
<name>trend-grid-strategy</name> <name>trend-grid-strategy</name>
<description>趋势网格策略</description> <description>趋势网格策略</description>

View File

@ -36,8 +36,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
* 网格间距 = atr_multiplier × ATR,并限制在 [min_gap, max_gap] 区间。 * 网格间距 = atr_multiplier × ATR,并限制在 [min_gap, max_gap] 区间。
* 下方买单始终最多挂 1 笔:成交后再挂下一格,不会批量铺满网格。 * 下方买单始终最多挂 1 笔:成交后再挂下一格,不会批量铺满网格。
* 若现价相对挂单价上行超过间距 × 1.5,撤单并按现价重新挂靠下一格,避免买单悬空。 * 若现价相对挂单价上行超过间距 × 1.5,撤单并按现价重新挂靠下一格,避免买单悬空。
* 采用限价挂单买入,市价止盈卖出。 * 采用限价挂单买入,买单成交后在「入场价 + 间距」挂限价卖单止盈。
* 网格间距变化时不撤销已有挂单,已成交仓位按最新间距计算止盈价。 * 网格间距变化时不撤销已有挂单,已挂卖单价格保持不变。
*/ */
public class AutoGridStrategy extends BaseStrategy { public class AutoGridStrategy extends BaseStrategy {
@ -85,14 +85,14 @@ public class AutoGridStrategy extends BaseStrategy {
* 待成交买单:key = 网格价格字符串 * 待成交买单:key = 网格价格字符串
*/ */
private final Map<String, GridBuyOrder> pendingBuyOrders = new ConcurrentHashMap<>(); private final Map<String, GridBuyOrder> pendingBuyOrders = new ConcurrentHashMap<>();
/**
* 待成交卖单:key = clientPositionId
*/
private final Map<String, GridSellOrder> pendingSellOrders = new ConcurrentHashMap<>();
/** /**
* 已成交待止盈的多头仓位 * 已成交待止盈的多头仓位
*/ */
private final List<GridPosition> openPositions = new CopyOnWriteArrayList<>(); private final List<GridPosition> openPositions = new CopyOnWriteArrayList<>();
/**
* 正在平仓中的仓位,防止重复触发
*/
private final Map<String, Boolean> closingPositions = new ConcurrentHashMap<>();
/** /**
* 已处理过的买单成交 orderId,避免 fill / orders 双推重复开仓补单 * 已处理过的买单成交 orderId,避免 fill / orders 双推重复开仓补单
*/ */
@ -123,19 +123,35 @@ public class AutoGridStrategy extends BaseStrategy {
} }
} }
private static class GridSellOrder {
private final String clientOrderId;
private volatile String exchangeOrderId;
private final String clientPositionId;
private final BigDecimal sellPrice;
private final BigDecimal quantity;
GridSellOrder(String clientOrderId, String clientPositionId,
BigDecimal sellPrice, BigDecimal quantity) {
this.clientOrderId = clientOrderId;
this.clientPositionId = clientPositionId;
this.sellPrice = sellPrice;
this.quantity = quantity;
}
}
private static class GridPosition { private static class GridPosition {
private final String clientPositionId; private final String clientPositionId;
private final BigDecimal entryPrice; private final BigDecimal entryPrice;
private final BigDecimal quantity; private final BigDecimal quantity;
/** 开仓时锁定的止盈价,不随 gridGap 变化 */
private final BigDecimal takeProfitPrice;
GridPosition(String clientPositionId, BigDecimal entryPrice, BigDecimal quantity) { GridPosition(String clientPositionId, BigDecimal entryPrice, BigDecimal quantity,
BigDecimal takeProfitPrice) {
this.clientPositionId = clientPositionId; this.clientPositionId = clientPositionId;
this.entryPrice = entryPrice; this.entryPrice = entryPrice;
this.quantity = quantity; this.quantity = quantity;
} this.takeProfitPrice = takeProfitPrice;
BigDecimal takeProfitPrice(BigDecimal gridGap) {
return entryPrice.add(gridGap);
} }
} }
@ -287,6 +303,7 @@ public class AutoGridStrategy extends BaseStrategy {
refreshGridGap(true); refreshGridGap(true);
syncOpenOrdersFromExchange(); syncOpenOrdersFromExchange();
pruneExcessPendingBuys(); pruneExcessPendingBuys();
replenishMissingSellOrders();
placeInitialBuyGrids(); placeInitialBuyGrids();
createTask(this::syncAccount, 5, 600); createTask(this::syncAccount, 5, 600);
@ -307,7 +324,7 @@ public class AutoGridStrategy extends BaseStrategy {
try { try {
checkPriceBounds(); checkPriceBounds();
refreshGridGap(false); refreshGridGap(false);
processTakeProfit(); replenishMissingSellOrders();
rebaseStalePendingBuy(); rebaseStalePendingBuy();
replenishBuyGrids(); replenishBuyGrids();
updateDashboard(); updateDashboard();
@ -382,7 +399,7 @@ public class AutoGridStrategy extends BaseStrategy {
if (oldGap != null && oldGap.compareTo(gap) != 0) { if (oldGap != null && oldGap.compareTo(gap) != 0) {
logger.info("网格间距调整:{} -> {}(ATR={})", oldGap, gap, atr); logger.info("网格间距调整:{} -> {}(ATR={})", oldGap, gap, atr);
submitAlarm("网格间距调整", submitAlarm("网格间距调整",
String.format("间距 %s -> %s,ATR=%s,已有挂单保持不变,止盈按新间距计算", String.format("间距 %s -> %s,ATR=%s,已有挂单及卖单价格保持不变",
formatPrice(oldGap), formatPrice(gap), formatPrice(atr)), formatPrice(oldGap), formatPrice(gap), formatPrice(atr)),
AlarmLevel.INFO); AlarmLevel.INFO);
} }
@ -589,53 +606,60 @@ public class AutoGridStrategy extends BaseStrategy {
} }
} }
// ======================== 止盈(市价平仓) ======================== // ======================== 限价卖单止盈 ========================
private void processTakeProfit() { /**
if (currentGridGap == null || currentPrice == null) { * 为缺少卖单的持仓补挂限价止盈单(启动恢复或卖单被撤时)。
*/
private void replenishMissingSellOrders() {
synchronized (tradeLock) {
for (GridPosition position : openPositions) {
if (!hasSellOrderFor(position.clientPositionId)) {
placeLimitSellOrder(position);
}
}
}
}
private void placeLimitSellOrder(GridPosition position) {
if (hasSellOrderFor(position.clientPositionId)) {
return;
}
BigDecimal sellPrice = position.takeProfitPrice;
if (sellPrice.compareTo(upperBound) > 0) {
logger.warn("止盈价 {} 超过上限 {},跳过挂卖单", sellPrice, upperBound);
return; return;
} }
for (GridPosition position : openPositions) { String clientOrderId = buildClientOrderId("SELL", position.entryPrice);
BigDecimal tpPrice = position.takeProfitPrice(currentGridGap);
if (currentPrice.compareTo(tpPrice) < 0) {
continue;
}
if (closingPositions.putIfAbsent(position.clientPositionId, Boolean.TRUE) != null) {
continue;
}
marketClosePosition(position, tpPrice);
}
}
private void marketClosePosition(GridPosition position, BigDecimal tpPrice) {
synchronized (tradeLock) {
try { try {
JSONObject orderInfo = buildOrderRequest("sell", "close", "market", null, JSONObject orderInfo = buildOrderRequest("sell", "close", "limit", sellPrice,
position.quantity, true); position.quantity, true);
orderInfo.put("clientOid", buildClientOrderId("SELL", position.entryPrice)); orderInfo.put("clientOid", clientOrderId);
JSONObject result = bitGetClient.placeOrder(orderInfo); JSONObject result = bitGetClient.placeOrder(orderInfo);
String exchangeOrderId = result.getString("orderId"); String exchangeOrderId = result.getString("orderId");
logger.info("市价止盈:入场={},止盈价={},当前价={},订单号={}", GridSellOrder sellOrder = new GridSellOrder(clientOrderId, position.clientPositionId,
position.entryPrice, tpPrice, currentPrice, exchangeOrderId); sellPrice, position.quantity);
sellOrder.exchangeOrderId = exchangeOrderId;
pendingSellOrders.put(position.clientPositionId, sellOrder);
submitAlarm("网格止盈", logger.info("挂卖单成功:入场={},止盈价={},数量={},订单号={}",
String.format("入场 %s,止盈目标 %s,当前价 %s,市价平仓已提交", position.entryPrice, sellPrice, position.quantity, exchangeOrderId);
formatPrice(position.entryPrice), formatPrice(tpPrice), formatPrice(currentPrice)), submitOrderSync(exchangeOrderId, clientOrderId, sellPrice, position.quantity,
AlarmLevel.INFO);
submitOrderSync(exchangeOrderId, position.clientPositionId, currentPrice, position.quantity,
OrderSide.SELL, OrderStatus.NEW); OrderSide.SELL, OrderStatus.NEW);
} catch (Exception e) { } catch (Exception e) {
closingPositions.remove(position.clientPositionId); logger.error("挂卖单失败,入场价={},止盈价={}", position.entryPrice, sellPrice, e);
logger.error("市价止盈失败,入场价={}", position.entryPrice, e); submitAlarm("挂卖单失败",
submitAlarm("止盈失败", String.format("入场 %s,止盈 %s:%s",
String.format("入场 %s:%s", formatPrice(position.entryPrice), e.getMessage()), formatPrice(position.entryPrice), formatPrice(sellPrice), e.getMessage()),
AlarmLevel.ERROR); AlarmLevel.ERROR);
} }
} }
private boolean hasSellOrderFor(String clientPositionId) {
return pendingSellOrders.containsKey(clientPositionId);
} }
// ======================== WebSocket 事件处理 ======================== // ======================== WebSocket 事件处理 ========================
@ -671,7 +695,7 @@ public class AutoGridStrategy extends BaseStrategy {
if (isOpenBuy(side, tradeSide)) { if (isOpenBuy(side, tradeSide)) {
onBuyFilled(price, volume, orderId, clientOid, price); onBuyFilled(price, volume, orderId, clientOid, price);
} else if (isCloseSell(side, tradeSide)) { } else if (isCloseSell(side, tradeSide)) {
onSellFilled(price, volume, orderId); onSellFilled(price, volume, orderId, clientOid);
} }
} }
@ -695,6 +719,8 @@ public class AutoGridStrategy extends BaseStrategy {
if ("cancelled".equals(status) || "canceled".equals(status)) { if ("cancelled".equals(status) || "canceled".equals(status)) {
if ("buy".equals(side)) { if ("buy".equals(side)) {
removePendingBuy(orderId, clientOid, orderPrice); removePendingBuy(orderId, clientOid, orderPrice);
} else if ("sell".equals(side)) {
removePendingSell(orderId, clientOid, orderPrice);
} }
return; return;
} }
@ -715,7 +741,7 @@ public class AutoGridStrategy extends BaseStrategy {
if (isOpenBuy(side, tradeSide)) { if (isOpenBuy(side, tradeSide)) {
onBuyFilled(fillPrice, volume, orderId, clientOid, orderPrice); onBuyFilled(fillPrice, volume, orderId, clientOid, orderPrice);
} else if (isCloseSell(side, tradeSide)) { } else if (isCloseSell(side, tradeSide)) {
onSellFilled(fillPrice, volume, orderId); onSellFilled(fillPrice, volume, orderId, clientOid);
} }
} }
@ -741,17 +767,26 @@ public class AutoGridStrategy extends BaseStrategy {
return; return;
} }
synchronized (tradeLock) {
BigDecimal gridPrice = pending.buyPrice; BigDecimal gridPrice = pending.buyPrice;
String clientPositionId = "POS_" + gridPrice.toPlainString(); String clientPositionId = "POS_" + gridPrice.toPlainString();
GridPosition position = new GridPosition(clientPositionId, gridPrice, volume); if (currentGridGap == null) {
if (orderId != null) {
processedBuyFillOrderIds.remove(orderId);
}
logger.warn("网格间距未就绪,无法处理买单成交,orderId={}", orderId);
return;
}
BigDecimal tpPrice = normalizePrice(gridPrice.add(currentGridGap));
GridPosition position = new GridPosition(clientPositionId, gridPrice, volume, tpPrice);
openPositions.add(position); openPositions.add(position);
BigDecimal tpPrice = currentGridGap != null ? position.takeProfitPrice(currentGridGap) : null; logger.info("买单成交:网格价={},成交价={},数量={},止盈价={},orderId={}",
logger.info("买单成交:网格价={},成交价={},数量={},止盈目标={},orderId={}",
gridPrice, fillPrice, volume, tpPrice, orderId); gridPrice, fillPrice, volume, tpPrice, orderId);
submitAlarm("网格买入成交", submitAlarm("网格买入成交",
String.format("买入价 %s,数量 %s,止盈目标 %s(间距 %s)", String.format("买入价 %s,数量 %s,挂卖单止盈 %s(间距 %s)",
formatPrice(gridPrice), volume.toPlainString(), formatPrice(gridPrice), volume.toPlainString(),
formatPrice(tpPrice), formatPrice(currentGridGap)), formatPrice(tpPrice), formatPrice(currentGridGap)),
AlarmLevel.INFO); AlarmLevel.INFO);
@ -759,42 +794,43 @@ public class AutoGridStrategy extends BaseStrategy {
submitOrderSync(orderId, clientPositionId, gridPrice, volume, OrderSide.BUY, OrderStatus.FILLED); submitOrderSync(orderId, clientPositionId, gridPrice, volume, OrderSide.BUY, OrderStatus.FILLED);
submitPositionSync(clientPositionId, gridPrice, volume, true); submitPositionSync(clientPositionId, gridPrice, volume, true);
// 成交后再挂下方一格,保证永远只有一笔待成交买单 placeLimitSellOrder(position);
placeNextBuyBelow(gridPrice); placeNextBuyBelow(gridPrice);
} }
}
private void onSellFilled(BigDecimal price, BigDecimal volume, String orderId) { private void onSellFilled(BigDecimal price, BigDecimal volume, String orderId, String clientOid) {
if (orderId != null && !processedSellFillOrderIds.add(orderId)) { if (orderId != null && !processedSellFillOrderIds.add(orderId)) {
return; return;
} }
synchronized (tradeLock) {
GridSellOrder pendingSell = removePendingSell(orderId, clientOid, null);
GridPosition matched = null; GridPosition matched = null;
for (String closingId : closingPositions.keySet()) { if (pendingSell != null) {
for (GridPosition position : openPositions) { for (GridPosition position : openPositions) {
if (position.clientPositionId.equals(closingId)) { if (position.clientPositionId.equals(pendingSell.clientPositionId)) {
matched = position; matched = position;
break; break;
} }
} }
if (matched != null) { }
break; if (matched == null && clientOid != null) {
} BigDecimal entryPrice = parseEntryPriceFromClientOid(clientOid);
} if (entryPrice != null) {
if (matched == null && volume != null) { String posId = "POS_" + entryPrice.toPlainString();
for (GridPosition position : openPositions) { for (GridPosition position : openPositions) {
if (position.quantity.compareTo(volume) == 0) { if (position.clientPositionId.equals(posId)) {
matched = position; matched = position;
break; break;
} }
} }
} }
if (matched == null && !openPositions.isEmpty()) {
matched = openPositions.get(0);
} }
if (matched != null) { if (matched != null) {
openPositions.remove(matched); openPositions.remove(matched);
closingPositions.remove(matched.clientPositionId); pendingSellOrders.remove(matched.clientPositionId);
submitPositionSync(matched.clientPositionId, price, matched.quantity, false); submitPositionSync(matched.clientPositionId, price, matched.quantity, false);
} }
@ -806,9 +842,9 @@ public class AutoGridStrategy extends BaseStrategy {
submitOrderSync(orderId, matched != null ? matched.clientPositionId : "CLOSE", price, volume, submitOrderSync(orderId, matched != null ? matched.clientPositionId : "CLOSE", price, volume,
OrderSide.SELL, OrderStatus.FILLED); OrderSide.SELL, OrderStatus.FILLED);
// 成交后补充下一格买单
replenishBuyGrids(); replenishBuyGrids();
} }
}
private String resolveSymbol(JSONObject payload) { private String resolveSymbol(JSONObject payload) {
String symbol = payload.getString("symbol"); String symbol = payload.getString("symbol");
@ -881,6 +917,68 @@ public class AutoGridStrategy extends BaseStrategy {
return null; return null;
} }
private GridSellOrder removePendingSell(String orderId, String clientOid, BigDecimal price) {
if (orderId != null) {
for (Iterator<Map.Entry<String, GridSellOrder>> it = pendingSellOrders.entrySet().iterator();
it.hasNext(); ) {
Map.Entry<String, GridSellOrder> entry = it.next();
GridSellOrder order = entry.getValue();
if (orderId.equals(order.exchangeOrderId)) {
it.remove();
return order;
}
}
}
if (clientOid != null) {
for (Iterator<Map.Entry<String, GridSellOrder>> it = pendingSellOrders.entrySet().iterator();
it.hasNext(); ) {
Map.Entry<String, GridSellOrder> entry = it.next();
GridSellOrder order = entry.getValue();
if (clientOid.equals(order.clientOrderId)) {
it.remove();
return order;
}
}
}
if (price != null) {
BigDecimal normalized = normalizePrice(price);
for (Iterator<Map.Entry<String, GridSellOrder>> it = pendingSellOrders.entrySet().iterator();
it.hasNext(); ) {
Map.Entry<String, GridSellOrder> entry = it.next();
GridSellOrder order = entry.getValue();
if (normalized.compareTo(order.sellPrice) == 0) {
it.remove();
return order;
}
}
}
return null;
}
/**
* 从 clientOid(SELL_{symbol}_{entryPrice}_{ts})解析入场价。
*/
private BigDecimal parseEntryPriceFromClientOid(String clientOid) {
if (clientOid == null || !clientOid.startsWith("SELL_")) {
return null;
}
String plainSymbol = getPlainSymbol();
String prefix = "SELL_" + plainSymbol + "_";
if (!clientOid.startsWith(prefix)) {
return null;
}
String remainder = clientOid.substring(prefix.length());
int lastUnderscore = remainder.lastIndexOf('_');
if (lastUnderscore <= 0) {
return null;
}
try {
return normalizePrice(new BigDecimal(remainder.substring(0, lastUnderscore)));
} catch (NumberFormatException e) {
return null;
}
}
// ======================== 交易所同步 ======================== // ======================== 交易所同步 ========================
private void syncOpenOrdersFromExchange() { private void syncOpenOrdersFromExchange() {
@ -889,29 +987,63 @@ public class AutoGridStrategy extends BaseStrategy {
if (openOrders == null) { if (openOrders == null) {
return; return;
} }
int buyCount = 0;
int sellCount = 0;
for (int i = 0; i < openOrders.size(); i++) { for (int i = 0; i < openOrders.size(); i++) {
JSONObject order = openOrders.getJSONObject(i); JSONObject order = openOrders.getJSONObject(i);
if (!"buy".equals(order.getString("side"))) { String side = order.getString("side");
continue; String tradeSide = order.getString("tradeSide");
}
if (!"open".equals(order.getString("tradeSide"))) {
continue;
}
BigDecimal price = normalizePrice(order.getBigDecimal("price")); BigDecimal price = normalizePrice(order.getBigDecimal("price"));
String clientOid = order.getString("clientOid"); String clientOid = order.getString("clientOid");
String exchangeOrderId = order.getString("orderId");
BigDecimal quantity = firstPositive(
order.getBigDecimal("size"),
order.getBigDecimal("baseVolume"));
if ("buy".equals(side) && "open".equals(tradeSide)) {
if (clientOid == null) { if (clientOid == null) {
clientOid = "SYNC_" + price.toPlainString(); clientOid = "SYNC_BUY_" + price.toPlainString();
} }
GridBuyOrder gridBuyOrder = new GridBuyOrder(clientOid, price); GridBuyOrder gridBuyOrder = new GridBuyOrder(clientOid, price);
gridBuyOrder.exchangeOrderId = order.getString("orderId"); gridBuyOrder.exchangeOrderId = exchangeOrderId;
pendingBuyOrders.put(priceKey(price), gridBuyOrder); pendingBuyOrders.put(priceKey(price), gridBuyOrder);
buyCount++;
} else if ("sell".equals(side) && "close".equals(tradeSide)) {
if (clientOid == null) {
clientOid = "SYNC_SELL_" + price.toPlainString();
} }
logger.info("同步挂单 {} 笔", pendingBuyOrders.size()); BigDecimal entryPrice = parseEntryPriceFromClientOid(clientOid);
String clientPositionId = entryPrice != null
? "POS_" + entryPrice.toPlainString()
: "SYNC_POS_" + price.toPlainString();
if (quantity == null) {
quantity = baseQuantity;
}
GridSellOrder sellOrder = new GridSellOrder(clientOid, clientPositionId, price, quantity);
sellOrder.exchangeOrderId = exchangeOrderId;
pendingSellOrders.put(clientPositionId, sellOrder);
sellCount++;
if (entryPrice != null && !hasOpenPosition(clientPositionId)) {
openPositions.add(new GridPosition(clientPositionId, entryPrice, quantity, price));
}
}
}
logger.info("同步挂单:买单 {} 笔,卖单 {} 笔", buyCount, sellCount);
} catch (Exception e) { } catch (Exception e) {
logger.warn("同步挂单失败:{}", e.getMessage()); logger.warn("同步挂单失败:{}", e.getMessage());
} }
} }
private boolean hasOpenPosition(String clientPositionId) {
for (GridPosition position : openPositions) {
if (position.clientPositionId.equals(clientPositionId)) {
return true;
}
}
return false;
}
/** /**
* 若交易所上残留多笔买单,只保留最靠近现价(价格最高)的一笔,撤销其余。 * 若交易所上残留多笔买单,只保留最靠近现价(价格最高)的一笔,撤销其余。
*/ */
@ -1039,6 +1171,7 @@ public class AutoGridStrategy extends BaseStrategy {
sb.append("<div>ATR:").append(formatPrice(currentAtr)).append("</div>"); sb.append("<div>ATR:").append(formatPrice(currentAtr)).append("</div>");
sb.append("<div>网格间距:").append(formatPrice(currentGridGap)).append("</div>"); sb.append("<div>网格间距:").append(formatPrice(currentGridGap)).append("</div>");
sb.append("<div>待成交买单:").append(pendingBuyOrders.size()).append(" / 1 笔</div>"); sb.append("<div>待成交买单:").append(pendingBuyOrders.size()).append(" / 1 笔</div>");
sb.append("<div>待成交卖单:").append(pendingSellOrders.size()).append(" 笔</div>");
sb.append("<div>持仓待止盈:").append(openPositions.size()) sb.append("<div>持仓待止盈:").append(openPositions.size())
.append(" / ").append(gridCount).append(" 层</div>"); .append(" / ").append(gridCount).append(" 层</div>");
sb.append("<div>上限暂停:").append(upperBoundPaused ? "是" : "否").append("</div>"); sb.append("<div>上限暂停:").append(upperBoundPaused ? "是" : "否").append("</div>");
@ -1069,6 +1202,17 @@ public class AutoGridStrategy extends BaseStrategy {
rows.add(row); rows.add(row);
} }
List<GridSellOrder> pendingSells = new ArrayList<>(pendingSellOrders.values());
pendingSells.sort(Comparator.comparing(o -> o.sellPrice, Comparator.reverseOrder()));
for (GridSellOrder order : pendingSells) {
JSONObject row = new JSONObject();
row.put("type", "待成交卖单");
row.put("price", formatPrice(order.sellPrice));
row.put("quantity", order.quantity.toPlainString());
row.put("takeProfit", formatPrice(order.sellPrice));
rows.add(row);
}
List<GridPosition> positions = new ArrayList<>(openPositions); List<GridPosition> positions = new ArrayList<>(openPositions);
positions.sort(Comparator.comparing(p -> p.entryPrice, Comparator.reverseOrder())); positions.sort(Comparator.comparing(p -> p.entryPrice, Comparator.reverseOrder()));
for (GridPosition position : positions) { for (GridPosition position : positions) {
@ -1076,7 +1220,7 @@ public class AutoGridStrategy extends BaseStrategy {
row.put("type", "持仓"); row.put("type", "持仓");
row.put("price", formatPrice(position.entryPrice)); row.put("price", formatPrice(position.entryPrice));
row.put("quantity", position.quantity.toPlainString()); row.put("quantity", position.quantity.toPlainString());
row.put("takeProfit", formatPrice(position.takeProfitPrice(currentGridGap))); row.put("takeProfit", formatPrice(position.takeProfitPrice));
rows.add(row); rows.add(row);
} }