ScriptUtil脚本实现

This commit is contained in:
tony 2026-07-14 15:31:12 +08:00
parent 3bac0e4aee
commit 8ac9a8ec1a
4 changed files with 160 additions and 21 deletions

15
pom.xml
View File

@ -5,12 +5,13 @@
<groupId>com.sample</groupId>
<artifactId>trend-grid-strategy</artifactId>
<version>1.0.2-SNAPSHOT</version>
<version>1.0.3-SNAPSHOT</version>
<name>trend-grid-strategy</name>
<description>趋势网格策略</description>
<properties>
<trade.version>5.0.0-SNAPSHOT</trade.version>
<graalvm.version>24.1.2</graalvm.version>
</properties>
<dependencies>
<dependency>
@ -24,6 +25,18 @@
<artifactId>uuquant-exchange-api</artifactId>
<version>20260619.0130</version>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>polyglot</artifactId>
<version>${graalvm.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>${graalvm.version}</version>
<type>pom</type>
</dependency>
</dependencies>
<build>

View File

@ -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<String, Object> map = new HashMap<>();
map.put("atr", 1);
BigDecimal a = ScriptUtil.evalBigDecimal("atr > 1 ? 2 : 5", map);
System.out.println(a);
}

View File

@ -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<String, Object> 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<String, Object> buildGridScriptVariables(BigDecimal atr) {
Map<String, Object> 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);

View File

@ -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<String, Object> 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<String, Object> 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);
}
}
}