diff --git a/src/main/java/com/sample/trend/config/CommonConstant.java b/src/main/java/com/sample/trend/config/CommonConstant.java index a54adfc..2447d09 100644 --- a/src/main/java/com/sample/trend/config/CommonConstant.java +++ b/src/main/java/com/sample/trend/config/CommonConstant.java @@ -4,12 +4,18 @@ import vip.uuquant.exhangeapi.core.ExchangeFactory; import vip.uuquant.exhangeapi.core.HttpClient; import vip.uuquant.exhangeapi.core.WebSocketManager; +import java.net.InetSocketAddress; +import java.net.Proxy; + /** * 交易所客户端全局单例 */ public class CommonConstant { - public static final HttpClient httpClient = HttpClient.builder().build(); + /** 全局 HTTP 代理 */ + public static final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7897)); + + public static final HttpClient httpClient = HttpClient.builder().proxy(proxy).build(); public static final WebSocketManager webSocketManager = new WebSocketManager(httpClient); diff --git a/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java b/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java index bf7cb1f..f433e84 100644 --- a/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java +++ b/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java @@ -32,6 +32,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * 基于 ATR 的动态网格策略(只做多)。 *
* 网格间距 = atr_multiplier × ATR,并限制在 [min_gap, max_gap] 区间。
+ * 下方买单始终最多挂 1 笔:成交后再挂下一格,不会批量铺满网格。
* 采用限价挂单买入,市价止盈卖出。
* 网格间距变化时不撤销已有挂单,已成交仓位按最新间距计算止盈价。
*/
@@ -54,7 +55,7 @@ public class AutoGridStrategy extends BaseStrategy {
private boolean testMode;
private int leverage;
/**
- * 最大待成交买单数量
+ * 最大持仓层数;达到后不再新建买单。待成交买单始终最多 1 笔。
*/
private int gridCount;
@@ -246,6 +247,7 @@ public class AutoGridStrategy extends BaseStrategy {
refreshGridGap(true);
syncOpenOrdersFromExchange();
+ pruneExcessPendingBuys();
placeInitialBuyGrids();
createTask(this::syncAccount, 5, 600);
@@ -299,7 +301,7 @@ public class AutoGridStrategy extends BaseStrategy {
bitGetClient.changeTestMode(testMode);
AccountVO account = getAccount();
bitGetClient.init(account.getApiKey(), account.getSecretKey(), account.getPassphrase(),
- null, CommonConstant.webSocketManager);
+ CommonConstant.httpClient, CommonConstant.webSocketManager);
// try {
// bitGetClient.setLeverage(getSymbol(), leverage);
// } catch (Exception e) {
@@ -357,19 +359,14 @@ public class AutoGridStrategy extends BaseStrategy {
private BigDecimal fetchAtr() throws Exception {
JSONObject params = new JSONObject();
params.put("symbol", getPlainSymbol());
- params.put("granularity", normalizeKlineInterval(klineInterval));
+ params.put("granularity", klineInterval);
params.put("limit", String.valueOf(klinePeriod + 2));
JSONArray klines = bitGetClient.getKLines(params);
if (klines == null || klines.isEmpty()) {
return null;
}
- // BitGet 返回倒序,转为升序
- JSONArray sorted = new JSONArray();
- for (int i = klines.size() - 1; i >= 0; i--) {
- sorted.add(klines.getJSONArray(i));
- }
- return AtrCalculator.calculate(sorted, klinePeriod);
+ return AtrCalculator.calculate(klines, klinePeriod);
}
private String normalizeKlineInterval(String interval) {
@@ -418,7 +415,7 @@ public class AutoGridStrategy extends BaseStrategy {
// ======================== 网格挂单 ========================
/**
- * 初始化仅挂下方一格买单,与运行中补单逻辑一致,避免网格间距调整后批量挂单失配。
+ * 初始化仅挂下方一格买单,与运行中补单逻辑一致。
*/
private void placeInitialBuyGrids() {
if (currentPrice == null || currentGridGap == null) {
@@ -427,25 +424,22 @@ public class AutoGridStrategy extends BaseStrategy {
replenishBuyGrids();
}
+ /**
+ * 无待成交买单时,在现价下方挂一格;已有挂单则不补,保证始终只有 1 笔买单。
+ */
private void replenishBuyGrids() {
- if (!canPlaceBuyOrders() || currentGridGap == null) {
+ if (!canPlaceBuyOrders() || currentGridGap == null || currentPrice == null) {
return;
}
synchronized (tradeLock) {
- if (pendingBuyOrders.size() >= gridCount) {
+ if (!pendingBuyOrders.isEmpty() || openPositions.size() >= gridCount) {
return;
}
- BigDecimal lowestPending = findLowestPendingBuyPrice();
- BigDecimal nextBuyPrice;
-
- if (lowestPending != null) {
- nextBuyPrice = lowestPending.subtract(currentGridGap);
- } else if (currentPrice != null) {
- nextBuyPrice = alignPriceDown(currentPrice, currentGridGap);
- } else {
- return;
+ BigDecimal nextBuyPrice = alignPriceDown(currentPrice, currentGridGap);
+ if (nextBuyPrice.compareTo(currentPrice) >= 0) {
+ nextBuyPrice = nextBuyPrice.subtract(currentGridGap);
}
if (nextBuyPrice.compareTo(lowerBound) >= 0 && !hasBuyOrderAt(nextBuyPrice)) {
@@ -454,8 +448,28 @@ public class AutoGridStrategy extends BaseStrategy {
}
}
+ /**
+ * 买单成交后,挂「成交价再下一格」的限价买单。
+ */
+ private void placeNextBuyBelow(BigDecimal filledPrice) {
+ if (!canPlaceBuyOrders() || currentGridGap == null || filledPrice == null) {
+ return;
+ }
+
+ synchronized (tradeLock) {
+ if (!pendingBuyOrders.isEmpty() || openPositions.size() >= gridCount) {
+ return;
+ }
+
+ BigDecimal nextBuyPrice = normalizePrice(filledPrice.subtract(currentGridGap));
+ if (nextBuyPrice.compareTo(lowerBound) >= 0 && !hasBuyOrderAt(nextBuyPrice)) {
+ placeLimitBuyOrder(nextBuyPrice);
+ }
+ }
+ }
+
private void placeLimitBuyOrder(BigDecimal price) {
- if (pendingBuyOrders.size() >= gridCount) {
+ if (!pendingBuyOrders.isEmpty() || openPositions.size() >= gridCount) {
return;
}
BigDecimal normalizedPrice = normalizePrice(price);
@@ -613,6 +627,9 @@ public class AutoGridStrategy extends BaseStrategy {
submitOrderSync(orderId, clientPositionId, normalizedPrice, volume, OrderSide.BUY, OrderStatus.FILLED);
submitPositionSync(clientPositionId, normalizedPrice, volume, true);
+
+ // 成交后再挂下方一格,保证永远只有一笔待成交买单
+ placeNextBuyBelow(normalizedPrice);
}
private void onSellFilled(BigDecimal price, BigDecimal volume, String orderId) {
@@ -688,6 +705,48 @@ public class AutoGridStrategy extends BaseStrategy {
}
}
+ /**
+ * 若交易所上残留多笔买单,只保留最靠近现价(价格最高)的一笔,撤销其余。
+ */
+ private void pruneExcessPendingBuys() {
+ if (pendingBuyOrders.size() <= 1) {
+ return;
+ }
+
+ synchronized (tradeLock) {
+ GridBuyOrder keep = pendingBuyOrders.values().stream()
+ .max(Comparator.comparing(o -> o.buyPrice))
+ .orElse(null);
+ if (keep == null) {
+ return;
+ }
+
+ List