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); + } + } +}