| | |
| | | WAITING_KLINE, OPENING, ACTIVE, REOPENING_LONG, REOPENING_SHORT, STOPPED |
| | | } |
| | | |
| | | private static final String AUTO_SIZE_LONG = "close_long"; |
| | | private static final String AUTO_SIZE_SHORT = "close_short"; |
| | | private static final String ORDER_TYPE_CLOSE_LONG = "close-long-position"; |
| | | private static final String ORDER_TYPE_CLOSE_SHORT = "close-short-position"; |
| | | |
| | | private final GateConfig config; |
| | | private final GateTradeExecutor executor; |
| | | private final FuturesApi futuresApi; |
| | |
| | | private volatile BigDecimal cumulativePnl = BigDecimal.ZERO; |
| | | private Long userId; |
| | | |
| | | /** 多头补仓连续失败次数 */ |
| | | private int longReopenFails = 0; |
| | | /** 空头补仓连续失败次数 */ |
| | | private int shortReopenFails = 0; |
| | | |
| | | public GateGridTradeService(GateConfig config) { |
| | |
| | | |
| | | FuturesOrder closeOrder = new FuturesOrder(); |
| | | closeOrder.setContract(config.getContract()); |
| | | closeOrder.setSize(closeSize); |
| | | closeOrder.setPrice("0"); |
| | | closeOrder.setTif(FuturesOrder.TifEnum.IOC); |
| | | closeOrder.setReduceOnly(true); |
| | | if (mode != null && mode.getValue() != null && mode.getValue().contains("dual")) { |
| | | closeOrder.setSize("0"); |
| | | closeOrder.setClose(false); |
| | | closeOrder.setAutoSize(isLong ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT); |
| | | } else { |
| | | closeOrder.setSize(closeSize); |
| | | } |
| | | closeOrder.setText("t-grid-init-close"); |
| | | futuresApi.createFuturesOrder(SETTLE, closeOrder, null); |
| | |
| | | longActive = true; |
| | | } |
| | | executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | "close-long-position", "close_long"); |
| | | }); |
| | | ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG); |
| | | }, null); |
| | | executor.openShort(negate(config.getQuantity()), () -> { |
| | | synchronized (this) { |
| | | shortEntryPrice = lastKlinePrice; |
| | | shortActive = true; |
| | | } |
| | | executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | "close-short-position", "close_short"); |
| | | ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT); |
| | | if (longActive && shortActive && state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | log.info("[Gate] 已激活, 多头入场:{}, 空头入场:{}, 多头止盈:{}, 空头止盈:{}", |
| | | longEntryPrice, shortEntryPrice, longTpPrice(), shortTpPrice()); |
| | | } |
| | | }); |
| | | }, null); |
| | | } |
| | | |
| | | /** |
| | | * 仓位推送回调。检测 size=0 触发补仓。 |
| | | */ |
| | | public void onPositionUpdate(String contract, String mode, BigDecimal size, BigDecimal entryPrice) { |
| | | public void onPositionUpdate(String contract, Position.ModeEnum mode, BigDecimal size, |
| | | BigDecimal entryPrice) { |
| | | if (state == StrategyState.STOPPED || state == StrategyState.WAITING_KLINE) { |
| | | return; |
| | | } |
| | | |
| | | boolean hasPosition = size.abs().compareTo(BigDecimal.ZERO) > 0; |
| | | |
| | | if ("dual_long".equals(mode)) { |
| | | if (Position.ModeEnum.DUAL_LONG == mode) { |
| | | if (longActive && !hasPosition) { |
| | | log.info("[Gate] 多头已平仓"); |
| | | longActive = false; |
| | | tryReopenLong(0); |
| | | tryReopenLong(); |
| | | } else if (hasPosition) { |
| | | longActive = true; |
| | | longEntryPrice = entryPrice; |
| | | } |
| | | } else if ("dual_short".equals(mode)) { |
| | | } else if (Position.ModeEnum.DUAL_SHORT == mode) { |
| | | if (shortActive && !hasPosition) { |
| | | log.info("[Gate] 空头已平仓"); |
| | | shortActive = false; |
| | | tryReopenShort(0); |
| | | tryReopenShort(); |
| | | } else if (hasPosition) { |
| | | shortActive = true; |
| | | shortEntryPrice = entryPrice; |
| | |
| | | } |
| | | } |
| | | |
| | | // ---- 补仓(含重试) ---- |
| | | // ---- 补仓(含失败重试) ---- |
| | | |
| | | private void tryReopenLong(int retry) { |
| | | private void tryReopenLong() { |
| | | if (state == StrategyState.STOPPED) { |
| | | return; |
| | | } |
| | | if (longActive) { |
| | | return; |
| | | } |
| | | |
| | | longReopenFails++; |
| | | if (longReopenFails > config.getReopenMaxRetries()) { |
| | | log.warn("[Gate] 多头补仓连续失败{}次,停止策略", longReopenFails); |
| | | state = StrategyState.STOPPED; |
| | | return; |
| | | } |
| | | |
| | |
| | | longActive = true; |
| | | } |
| | | executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | "close-long-position", "close_long"); |
| | | ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG); |
| | | longReopenFails = 0; |
| | | if (state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | } |
| | | log.info("[Gate] 多头已补开, 价格:{}", longEntryPrice); |
| | | }); |
| | | }, this::tryReopenLong); |
| | | } |
| | | |
| | | private void tryReopenShort(int retry) { |
| | | private void tryReopenShort() { |
| | | if (state == StrategyState.STOPPED) { |
| | | return; |
| | | } |
| | | if (shortActive) { |
| | | return; |
| | | } |
| | | |
| | | shortReopenFails++; |
| | | if (shortReopenFails > config.getReopenMaxRetries()) { |
| | | log.warn("[Gate] 空头补仓连续失败{}次,停止策略", shortReopenFails); |
| | | state = StrategyState.STOPPED; |
| | | return; |
| | | } |
| | | |
| | |
| | | shortActive = true; |
| | | } |
| | | executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | "close-short-position", "close_short"); |
| | | ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT); |
| | | shortReopenFails = 0; |
| | | if (state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | } |
| | | log.info("[Gate] 空头已补开, 价格:{}", shortEntryPrice); |
| | | }); |
| | | }, this::tryReopenShort); |
| | | } |
| | | |
| | | // ---- 止盈价格计算 ---- |