From 8ac9a8ec1ab3cfcafd916fc15d969caeb1bef3be Mon Sep 17 00:00:00 2001
From: dongzp_book <90fanhua@gmail.com>
Date: Tue, 14 Jul 2026 15:31:12 +0800
Subject: [PATCH] =?UTF-8?q?ScriptUtil=E8=84=9A=E6=9C=AC=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 15 ++-
.../com/sample/trend/StrategyApplication.java | 24 +++--
.../trend/strategy/AutoGridStrategy.java | 97 ++++++++++++++++---
.../trend/strategy/util/ScriptUtil.java | 45 +++++++++
4 files changed, 160 insertions(+), 21 deletions(-)
create mode 100644 src/main/java/com/sample/trend/strategy/util/ScriptUtil.java
diff --git a/pom.xml b/pom.xml
index bec9534..a6ecafd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,13 @@
com.sample
trend-grid-strategy
- 1.0.2-SNAPSHOT
+ 1.0.3-SNAPSHOT
trend-grid-strategy
趋势网格策略
5.0.0-SNAPSHOT
+ 24.1.2
@@ -24,6 +25,18 @@
uuquant-exchange-api
20260619.0130
+
+
+ org.graalvm.polyglot
+ polyglot
+ ${graalvm.version}
+
+
+ org.graalvm.polyglot
+ js
+ ${graalvm.version}
+ pom
+
diff --git a/src/main/java/com/sample/trend/StrategyApplication.java b/src/main/java/com/sample/trend/StrategyApplication.java
index 507f310..ad5ba4d 100644
--- a/src/main/java/com/sample/trend/StrategyApplication.java
+++ b/src/main/java/com/sample/trend/StrategyApplication.java
@@ -2,21 +2,31 @@ package com.sample.trend;
import com.sample.trend.strategy.AutoGridStrategy;
+import com.sample.trend.strategy.util.ScriptUtil;
import vip.uuquant.tradesystem.runner.core.StrategyRunner;
import vip.uuquant.tradesystem.runner.vo.DebugConfig;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
public class StrategyApplication {
public static void main(String[] args) {
// 调试模式
- DebugConfig debugConfig = new DebugConfig();
- debugConfig.setAgentHost("192.168.0.105");
- debugConfig.setRobotId(2074501096701927426L);
- debugConfig.setRobotApiKey("be7520970034b54cb7f2bccab0d546ca");
+// DebugConfig debugConfig = new DebugConfig();
+// debugConfig.setAgentHost("192.168.0.105");
+// debugConfig.setRobotId(2074501096701927426L);
+// debugConfig.setRobotApiKey("be7520970034b54cb7f2bccab0d546ca");
+//
+// StrategyRunner strategyRunner = StrategyRunner.getInstance();
+// strategyRunner.setDebugConfig(debugConfig);
+// strategyRunner.init(AutoGridStrategy.class);
- StrategyRunner strategyRunner = StrategyRunner.getInstance();
- strategyRunner.setDebugConfig(debugConfig);
- strategyRunner.init(AutoGridStrategy.class);
+ Map map = new HashMap<>();
+ map.put("atr", 1);
+ BigDecimal a = ScriptUtil.evalBigDecimal("atr > 1 ? 2 : 5", map);
+ System.out.println(a);
}
diff --git a/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java b/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java
index 8f00151..69e22ba 100644
--- a/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java
+++ b/src/main/java/com/sample/trend/strategy/AutoGridStrategy.java
@@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONObject;
import com.hengyi.xbaseweb.common.result.ApiResult;
import com.sample.trend.config.CommonConstant;
import com.sample.trend.strategy.util.AtrCalculator;
+import com.sample.trend.strategy.util.ScriptUtil;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,6 +53,7 @@ public class AutoGridStrategy extends BaseStrategy {
// 运行参数
private BigDecimal baseQuantity;
private BigDecimal atrMultiplier;
+ private String gridGrpScript;
private String klineInterval;
private int klinePeriod;
private int gridAdjustIntervalMinutes;
@@ -259,12 +261,13 @@ public class AutoGridStrategy extends BaseStrategy {
super.init(context);
logger.info("ATR 动态网格策略初始化");
- tryParams("base_quantity", "atr_multiplier", "kline_interval", "kline_period",
+ tryParams("base_quantity", "atr_multiplier", "grid_grp_script", "kline_interval", "kline_period",
"grid_adjust_interval", "min_gap", "max_gap", "upper_bound", "lower_bound",
"grid_count", "test_mode");
baseQuantity = getRunParams().getBigDecimal("base_quantity");
atrMultiplier = getRunParams().getBigDecimal("atr_multiplier");
+ gridGrpScript = getRunParams().getString("grid_grp_script");
klineInterval = getRunParams().getString("kline_interval");
klinePeriod = getRunParams().getInteger("kline_period");
gridAdjustIntervalMinutes = getRunParams().getInteger("grid_adjust_interval");
@@ -388,7 +391,17 @@ public class AutoGridStrategy extends BaseStrategy {
return;
}
- BigDecimal rawGap = atr.multiply(atrMultiplier);
+ Map scriptVariables = buildGridScriptVariables(atr);
+ BigDecimal rawGap;
+ try {
+ rawGap = ScriptUtil.evalBigDecimal(gridGrpScript, scriptVariables);
+ } catch (Exception scriptException) {
+ logger.warn("网格表达式执行失败,回退默认公式:{}", scriptException.getMessage());
+ rawGap = null;
+ }
+ if (rawGap == null) {
+ rawGap = atr.multiply(atrMultiplier);
+ }
BigDecimal gap = rawGap.max(minGap).min(maxGap).setScale(pricePrecision, RoundingMode.HALF_UP);
BigDecimal oldGap = currentGridGap;
@@ -408,6 +421,26 @@ public class AutoGridStrategy extends BaseStrategy {
}
}
+ private Map buildGridScriptVariables(BigDecimal atr) {
+ Map variables = new java.util.HashMap<>();
+ variables.put("atr", atr == null ? null : atr.doubleValue());
+ variables.put("atrMultiplier", atrMultiplier == null ? null : atrMultiplier.doubleValue());
+ variables.put("baseQuantity", baseQuantity == null ? null : baseQuantity.doubleValue());
+ variables.put("minGap", minGap == null ? null : minGap.doubleValue());
+ variables.put("maxGap", maxGap == null ? null : maxGap.doubleValue());
+ variables.put("upperBound", upperBound == null ? null : upperBound.doubleValue());
+ variables.put("lowerBound", lowerBound == null ? null : lowerBound.doubleValue());
+ variables.put("gridCount", gridCount);
+ variables.put("leverage", leverage);
+ variables.put("pricePrecision", pricePrecision);
+ variables.put("quantityPrecision", quantityPrecision);
+ variables.put("klinePeriod", klinePeriod);
+ variables.put("gridAdjustIntervalMinutes", gridAdjustIntervalMinutes);
+ variables.put("testMode", testMode);
+ variables.put("currentPrice", currentPrice == null ? null : currentPrice.doubleValue());
+ return variables;
+ }
+
private BigDecimal fetchAtr() throws Exception {
JSONObject params = new JSONObject();
params.put("symbol", getPlainSymbol());
@@ -478,10 +511,8 @@ public class AutoGridStrategy extends BaseStrategy {
return;
}
- BigDecimal nextBuyPrice = calcBuyPriceBelowCurrent();
- if (nextBuyPrice != null
- && nextBuyPrice.compareTo(lowerBound) >= 0
- && !hasBuyOrderAt(nextBuyPrice)) {
+ BigDecimal nextBuyPrice = findNextAvailableBuyPrice(calcBuyPriceBelowCurrent());
+ if (nextBuyPrice != null) {
placeLimitBuyOrder(nextBuyPrice);
}
}
@@ -509,10 +540,8 @@ public class AutoGridStrategy extends BaseStrategy {
return;
}
- BigDecimal newPrice = calcBuyPriceBelowCurrent();
- if (newPrice == null
- || newPrice.compareTo(lowerBound) < 0
- || newPrice.compareTo(pending.buyPrice) == 0) {
+ BigDecimal newPrice = findNextAvailableBuyPrice(calcBuyPriceBelowCurrent());
+ if (newPrice == null || newPrice.compareTo(pending.buyPrice) == 0) {
return;
}
@@ -564,8 +593,9 @@ public class AutoGridStrategy extends BaseStrategy {
return;
}
- BigDecimal nextBuyPrice = normalizePrice(filledPrice.subtract(currentGridGap));
- if (nextBuyPrice.compareTo(lowerBound) >= 0 && !hasBuyOrderAt(nextBuyPrice)) {
+ BigDecimal nextBuyPrice = findNextAvailableBuyPrice(
+ normalizePrice(filledPrice.subtract(currentGridGap)));
+ if (nextBuyPrice != null) {
placeLimitBuyOrder(nextBuyPrice);
}
}
@@ -579,7 +609,7 @@ public class AutoGridStrategy extends BaseStrategy {
if (normalizedPrice.compareTo(lowerBound) < 0 || normalizedPrice.compareTo(upperBound) > 0) {
return;
}
- if (hasBuyOrderAt(normalizedPrice)) {
+ if (hasBuyOrderAt(normalizedPrice) || hasOpenPositionAtPrice(normalizedPrice)) {
return;
}
@@ -769,6 +799,17 @@ public class AutoGridStrategy extends BaseStrategy {
synchronized (tradeLock) {
BigDecimal gridPrice = pending.buyPrice;
+ if (hasOpenPositionAtPrice(gridPrice)) {
+ if (orderId != null) {
+ processedBuyFillOrderIds.remove(orderId);
+ }
+ logger.warn("该价位已有持仓,忽略重复买单成交,gridPrice={},orderId={}", gridPrice, orderId);
+ submitAlarm("重复开仓拦截",
+ String.format("价位 %s 已有持仓,忽略 orderId=%s", formatPrice(gridPrice), orderId),
+ AlarmLevel.WARNING);
+ return;
+ }
+
String clientPositionId = "POS_" + gridPrice.toPlainString();
if (currentGridGap == null) {
if (orderId != null) {
@@ -1263,6 +1304,36 @@ public class AutoGridStrategy extends BaseStrategy {
return pendingBuyOrders.containsKey(priceKey(normalizePrice(price)));
}
+ private boolean hasOpenPositionAtPrice(BigDecimal price) {
+ if (price == null) {
+ return false;
+ }
+ String key = priceKey(price);
+ for (GridPosition position : openPositions) {
+ if (priceKey(position.entryPrice).equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 从 startPrice 起向下逐格查找:无持仓且无待成交买单的价位。
+ */
+ private BigDecimal findNextAvailableBuyPrice(BigDecimal startPrice) {
+ if (startPrice == null || currentGridGap == null) {
+ return null;
+ }
+ BigDecimal candidate = normalizePrice(startPrice);
+ while (candidate.compareTo(lowerBound) >= 0) {
+ if (!hasOpenPositionAtPrice(candidate) && !hasBuyOrderAt(candidate)) {
+ return candidate;
+ }
+ candidate = normalizePrice(candidate.subtract(currentGridGap));
+ }
+ return null;
+ }
+
private BigDecimal alignPriceDown(BigDecimal price, BigDecimal gap) {
if (gap == null || gap.compareTo(BigDecimal.ZERO) <= 0) {
return normalizePrice(price);
diff --git a/src/main/java/com/sample/trend/strategy/util/ScriptUtil.java b/src/main/java/com/sample/trend/strategy/util/ScriptUtil.java
new file mode 100644
index 0000000..527a9fa
--- /dev/null
+++ b/src/main/java/com/sample/trend/strategy/util/ScriptUtil.java
@@ -0,0 +1,45 @@
+package com.sample.trend.strategy.util;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.HostAccess;
+import org.graalvm.polyglot.Value;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public final class ScriptUtil {
+
+ private ScriptUtil() {
+ }
+
+ public static BigDecimal evalBigDecimal(String expression, Map variables) {
+ if (expression == null || expression.trim().isEmpty()) {
+ return null;
+ }
+
+ try (Context context = Context.newBuilder("js")
+ .allowHostAccess(HostAccess.NONE)
+ .allowHostClassLookup(className -> false)
+ .build()) {
+ if (variables != null) {
+ for (Map.Entry entry : variables.entrySet()) {
+ context.getBindings("js").putMember(entry.getKey(), entry.getValue());
+ }
+ }
+
+ Value result = context.eval("js", expression);
+ if (result == null || result.isNull()) {
+ return null;
+ }
+ if (result.fitsInLong()) {
+ return BigDecimal.valueOf(result.asLong());
+ }
+ if (result.fitsInDouble()) {
+ return BigDecimal.valueOf(result.asDouble());
+ }
+ return new BigDecimal(result.toString());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("网格脚本执行失败", e);
+ }
+ }
+}