| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
| 2026-01-06 | Administrator | ![]() |
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -10,6 +10,7 @@ import com.xcong.excoin.modules.blackchain.service.DateUtil; import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService; import com.xcong.excoin.modules.okxNewPrice.indicator.TradingStrategy; import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdEmaStrategy; import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdMaStrategy; import com.xcong.excoin.modules.okxNewPrice.okxWs.*; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums; @@ -21,6 +22,7 @@ import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeInfoEnum; import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeLoginService; import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig; import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild; import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild; import com.xcong.excoin.utils.RedisUtils; import lombok.extern.slf4j.Slf4j; @@ -343,10 +345,9 @@ */ String confirm = data.getString(8); if ("1".equals(confirm)){ log.info("{}开仓{}:{}",time,closePx,instId); //调用策略 // 创建策略实例 MacdMaStrategy strategy = new MacdMaStrategy(); MacdEmaStrategy strategy = new MacdEmaStrategy(); // 生成200个1m价格数据点 List<Kline> kline1MinuteData = getKlineDataByInstIdAndBar(instId, "1m"); @@ -354,14 +355,8 @@ .map(Kline::getC) .collect(Collectors.toList()); // 生成200个1D价格数据点 List<Kline> kline1DayData = getKlineDataByInstIdAndBar(instId, "1D"); List<BigDecimal> historicalPrices1D = kline1DayData.stream() .map(Kline::getC) .collect(Collectors.toList()); log.info("1D:{}", JSONUtil.parse( historicalPrices1D)); // 使用策略分析最新价格数据 MacdMaStrategy.TradingOrder tradingOrderOpenOpen = strategy.generateTradingOrder(historicalPrices1M,historicalPrices1D,MacdMaStrategy.OperationType.open.name()); MacdEmaStrategy.TradingOrder tradingOrderOpenOpen = strategy.generateTradingOrder(historicalPrices1M, MacdMaStrategy.OperationType.open.name()); if (tradingOrderOpenOpen == null){ return; } @@ -376,80 +371,8 @@ String accountName = client.getAccountName(); if (accountName != null) { if (ObjectUtil.isNotEmpty(tradingOrderOpenOpen)){ // 根据信号执行交易操作 TradeRequestParam tradeRequestParam = new TradeRequestParam(); String posSide = tradingOrderOpenOpen.getPosSide(); tradeRequestParam.setPosSide(posSide); String currentPrice = String.valueOf(closePx); tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide); String side = tradingOrderOpenOpen.getSide(); tradeRequestParam.setSide(side); String clOrdId = WsParamBuild.getOrderNum(side); tradeRequestParam.setClOrdId(clOrdId); String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); tradeRequestParam.setSz(sz); TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam); } } } }else{ log.info("{}平仓{}:{}",time,closePx,instId); //调用策略 // 创建策略实例 MacdMaStrategy strategy = new MacdMaStrategy(); // 生成200个1m价格数据点 List<Kline> kline1MinuteData = getKlineDataByInstIdAndBar(instId, "1m"); List<BigDecimal> historicalPrices1M = kline1MinuteData.stream() .map(Kline::getC) .collect(Collectors.toList()); // 生成200个1D价格数据点 List<Kline> kline1DayData = getKlineDataByInstIdAndBar(instId, "1D"); List<BigDecimal> historicalPrices1D = kline1DayData.stream() .map(Kline::getC) .collect(Collectors.toList()); // 使用策略分析最新价格数据 MacdMaStrategy.TradingOrder tradingOrderOpenClose = strategy.generateTradingOrder(historicalPrices1M,historicalPrices1D,MacdMaStrategy.OperationType.close.name()); if (tradingOrderOpenClose == null){ return; } Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients(); //如果为空,则直接返回 if (allClients.isEmpty()) { return; } // 获取所有OkxQuantWebSocketClient实例 for (OkxQuantWebSocketClient client : clientManager.getAllClients()) { String accountName = client.getAccountName(); if (accountName != null) { if (ObjectUtil.isNotEmpty(tradingOrderOpenClose)){ // 根据信号执行交易操作 TradeRequestParam tradeRequestParam = new TradeRequestParam(); tradeRequestParam.setAccountName(accountName); tradeRequestParam.setMarkPx(String.valueOf(closePx)); tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode()); tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode()); tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode()); String posSide = tradingOrderOpenClose.getPosSide(); tradeRequestParam.setPosSide(posSide); String side = tradingOrderOpenClose.getSide(); tradeRequestParam.setSide(side); String clOrdId = WsParamBuild.getOrderNum(side); tradeRequestParam.setClOrdId(clOrdId); String positionAccountName = PositionsWs.initAccountName(accountName, posSide); BigDecimal pos = PositionsWs.getAccountMap(positionAccountName).get("pos"); tradeRequestParam.setSz(String.valueOf(pos)); TradeOrderWs.orderZhiYingZhiSunEventNoState(client.getWebSocketClient(), tradeRequestParam); log.info("{}开仓{}:{}",instId,tradingOrderOpenOpen.getPosSide(),tradingOrderOpenOpen.getSide()); doOpen(client.getWebSocketClient(),accountName, tradingOrderOpenOpen, closePx); } } } @@ -460,6 +383,26 @@ } } private void doOpen(WebSocketClient webSocketClient, String accountName, MacdEmaStrategy.TradingOrder tradingOrderOpenOpen, BigDecimal closePx) { // 根据信号执行交易操作 TradeRequestParam tradeRequestParam = new TradeRequestParam(); String posSide = tradingOrderOpenOpen.getPosSide(); tradeRequestParam.setPosSide(posSide); String currentPrice = String.valueOf(closePx); tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide); String side = tradingOrderOpenOpen.getSide(); tradeRequestParam.setSide(side); String clOrdId = WsParamBuild.getOrderNum(side); tradeRequestParam.setClOrdId(clOrdId); String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); tradeRequestParam.setSz(sz); TradeOrderWs.orderEvent(webSocketClient, tradeRequestParam); } private List<Kline> getKlineDataByInstIdAndBar(String instId, String bar) { List<Kline> klineList = new ArrayList<>(); try { src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -18,6 +18,7 @@ import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -383,9 +384,9 @@ // 注意:当前实现中,OrderInfoWs等类使用静态Map存储数据 // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联 if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) { OrderInfoWs.handleEvent(response, redisUtils, account.name()); // TradeRequestParam tradeRequestParam = OrderInfoWs.handleEvent(response, redisUtils, account.name()); // TradeOrderWs.orderZhiYingZhiSunEventNoState(webSocketClient, tradeRequestParam); // OrderInfoWs.handleEvent(response, redisUtils, account.name()); List<TradeRequestParam> tradeRequestParams = OrderInfoWs.handleEvent(response, redisUtils, account.name()); TradeOrderWs.orderZhiYingZhiSunEventNoState(webSocketClient, tradeRequestParams); }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) { AccountWs.handleEvent(response, account.name()); } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) { src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdEmaStrategy.java
New file @@ -0,0 +1,463 @@ /** * MACD和MA组合交易策略实现类 * 基于多时间粒度K线数据生成交易信号并确定持仓方向 * * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素, * 形成了一套完整的开仓、平仓和持仓管理机制。 * 支持1分钟(K线)和1分钟(K线)级别的数据输入,数据顺序要求从新到旧排列。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * MACD和MA组合交易策略实现 * <p> * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤, * 为多时间粒度K线级别交易提供综合决策支持。 * <p> * 数据输入要求: * - historicalPrices1M:1分钟K线收盘价列表,顺序从新到旧 * - historicalPrices1D:日线K线收盘价列表,顺序从新到旧 */ @Slf4j public class MacdEmaStrategy { /** 操作类型枚举 */ public enum OperationType { /** 开仓 */ open, /** 平仓 */ close } /** 持仓状态枚举 */ public enum PositionType { /** 多头开仓 */ LONG_BUY, /** 多头平仓 */ LONG_SELL, /** 空头开仓 */ SHORT_SELL, /** 空头平仓 */ SHORT_BUY, /** 空仓 */ NONE } /** 交易指令类,封装side和posSide的组合 */ public static class TradingOrder { private String side; // buy或sell private String posSide; // long或short public TradingOrder(String side, String posSide) { this.side = side; this.posSide = posSide; } public String getSide() { return side; } public String getPosSide() { return posSide; } @Override public String toString() { return String.format("TradingOrder{side='%s', posSide='%s'}", side, posSide); } } // 策略参数 private int shortPeriod; // 短期EMA周期 private int longPeriod; // 长期EMA周期 private int signalPeriod; // MACD信号线周期 private int volatilityPeriod; // 波动率计算周期 private int trendPeriod = 200; // 趋势过滤EMA周期(200日) private BigDecimal stopLossRatio; // 止损比例 private BigDecimal takeProfitRatio; // 止盈比例 /** * 默认构造函数,使用标准MACD参数 * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20 * 止损比例=1%, 止盈比例=2% */ public MacdEmaStrategy() { this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02")); } /** * 自定义参数构造函数,使用默认趋势周期200 * * @param shortPeriod 短期EMA周期 * @param longPeriod 长期EMA周期 * @param signalPeriod MACD信号线周期 * @param volatilityPeriod 波动率计算周期 * @param stopLossRatio 止损比例 * @param takeProfitRatio 止盈比例 */ public MacdEmaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, BigDecimal stopLossRatio, BigDecimal takeProfitRatio) { this(shortPeriod, longPeriod, signalPeriod, volatilityPeriod, 200, stopLossRatio, takeProfitRatio); } /** * 自定义参数构造函数 * * @param shortPeriod 短期EMA周期 * @param longPeriod 长期EMA周期 * @param signalPeriod MACD信号线周期 * @param volatilityPeriod 波动率计算周期 * @param trendPeriod 趋势过滤EMA周期(200日) * @param stopLossRatio 止损比例 * @param takeProfitRatio 止盈比例 */ public MacdEmaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, int trendPeriod, BigDecimal stopLossRatio, BigDecimal takeProfitRatio) { this.shortPeriod = shortPeriod; this.longPeriod = longPeriod; this.signalPeriod = signalPeriod; this.volatilityPeriod = volatilityPeriod; this.trendPeriod = trendPeriod; this.stopLossRatio = stopLossRatio; this.takeProfitRatio = takeProfitRatio; } // 主流程方法 /** * 分析历史价格数据并生成交易指令 * * @param historicalPrices 历史价格序列(1分钟K线收盘价),顺序从新到旧 * @param operation 操作类型(open/close) * @return 交易指令(包含side和posSide),如果没有交易信号则返回null */ public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices, String operation) { PositionType signal = null; if (OperationType.open.name().equals(operation)) { signal = analyzeOpen(historicalPrices); } else if (OperationType.close.name().equals(operation)) { signal = analyzeClose(historicalPrices); } // 根据信号生成交易指令 return convertSignalToTradingOrder(signal); } /** * 分析最新价格数据并生成开仓信号 * * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 生成的交易信号(LONG_BUY、SHORT_SELL或NONE) */ public PositionType analyzeOpen(List<BigDecimal> closePrices) { // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA) if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) { return PositionType.NONE; // 数据不足,无法生成信号 } // 计算MACD指标 MACDResult macdResult = MACDCalculator.calculateMACD( closePrices, shortPeriod, longPeriod, signalPeriod); log.info("MACD计算结果:{}", macdResult.getMacdData().get(0)); // 多头开仓条件检查 if (isLongEntryCondition(macdResult, closePrices)) { log.info("多头开仓信号,价格:{}", closePrices.get(0)); return PositionType.LONG_BUY; } // 空头开仓条件检查 if (isShortEntryCondition(macdResult, closePrices)) { log.info("空头开仓信号,价格:{}", closePrices.get(0)); return PositionType.SHORT_SELL; } // 无信号 return PositionType.NONE; } /** * 分析最新价格数据并生成平仓信号 * * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE) */ public PositionType analyzeClose(List<BigDecimal> closePrices) { // 数据检查:确保有足够的数据点进行计算 if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) { return PositionType.NONE; // 数据不足,无法生成信号 } // 计算MACD指标 MACDResult macdResult = MACDCalculator.calculateMACD( closePrices, shortPeriod, longPeriod, signalPeriod); // 最新收盘价 BigDecimal latestPrice = closePrices.get(0); if (isLongExitCondition(macdResult, latestPrice)) { log.info("多头平仓信号,价格:{}", latestPrice); return PositionType.LONG_SELL; } if (isShortExitCondition(macdResult, latestPrice)) { log.info("空头平仓信号,价格:{}", latestPrice); return PositionType.SHORT_BUY; } // 无信号 return PositionType.NONE; } // 信号转换方法 /** * 将持仓信号转换为交易指令 * * @param signal 持仓信号 * @return 交易指令,无信号则返回null */ private TradingOrder convertSignalToTradingOrder(PositionType signal) { if (signal == null) { return null; } switch (signal) { case LONG_BUY: // 开多:买入开多(side 填写 buy; posSide 填写 long ) return new TradingOrder("buy", "long"); case LONG_SELL: // 平多:卖出平多(side 填写 sell; posSide 填写 long ) return new TradingOrder("sell", "long"); case SHORT_SELL: // 开空:卖出开空(side 填写 sell; posSide 填写 short ) return new TradingOrder("sell", "short"); case SHORT_BUY: // 平空:买入平空(side 填写 buy; posSide 填写 short ) return new TradingOrder("buy", "short"); default: // 无信号 return null; } } // 开仓条件检查方法 /** * 多头开仓条件检查 * * @param macdResult MACD计算结果 * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 是否满足多头开仓条件 */ private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) { // 1. 计算200日EMA(趋势过滤) // 复制并反转日线数据,确保从旧到新计算EMA List<BigDecimal> reversed1DPrices = new ArrayList<>(closePrices); Collections.reverse(reversed1DPrices); List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true); BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1); BigDecimal latestPrice = closePrices.get(0); // 2. 价格必须位于200日EMA上方(多头趋势确认) boolean isAboveTrend = latestPrice.compareTo(latestTrendEma) > 0; // 3. MACD金叉检查 boolean isGoldenCross = isGoldenCross(macdResult); // 4. MACD柱状线由负转正(动量转变) boolean isMacdHistTurningPositive = isMacdHistTurningPositive(macdResult); // 5. 底背离检查(增强多头信号可靠性) boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult); log.info("多头信号检查, 200日EMA价格{}位于上方: {}, 金叉: {}, MACD柱状线由负转正: {}, 底背离: {}", latestTrendEma,isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence); // 多头开仓条件:柱状线转强 + 金叉 + (趋势向上或底背离) return isMacdHistTurningPositive && isGoldenCross && ( isAboveTrend|| isBottomDivergence); } /** * 空头开仓条件检查 * * @param macdResult MACD计算结果 * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 是否满足空头开仓条件 */ private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) { // 1. 计算200日EMA(趋势过滤) // 复制并反转日线数据,确保从旧到新计算EMA List<BigDecimal> reversed1DPrices = new ArrayList<>(closePrices); Collections.reverse(reversed1DPrices); List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true); BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1); BigDecimal latestPrice = closePrices.get(0); // 2. 价格必须位于200日EMA下方(空头趋势确认) boolean isBelowTrend = latestPrice.compareTo(latestTrendEma) < 0; // 3. MACD死叉检查 boolean isDeathCross = isDeathCross(macdResult); // 4. MACD柱状线由正转负(动量转变) boolean isMacdHistTurningNegative = isMacdHistTurningNegative(macdResult); // 5. 顶背离检查(增强空头信号可靠性) boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult); log.info("空头信号检查, 200日EMA价格{}位于下方: {}, 死叉: {}, MACD柱状线由正转负: {}, 顶背离: {}", latestTrendEma,isBelowTrend, isDeathCross, isMacdHistTurningNegative, isTopDivergence); // 空头开仓条件:柱状线转弱 + 死叉 + (趋势向下或顶背离) return isMacdHistTurningNegative && isDeathCross && ( isBelowTrend || isTopDivergence); } // 平仓条件检查方法 /** * 多头平仓条件检查 * * @param macdResult MACD计算结果 * @param currentPrice 当前价格 * @return 是否满足多头平仓条件 */ private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) { // 多头平仓条件:MACD柱状线动量减弱(由正转弱) List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() >= 2) { PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由正转弱:前一根为正,当前绝对值减小 boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 && latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0; return momentumWeakening; } return false; } /** * 空头平仓条件检查 * * @param macdResult MACD计算结果 * @param currentPrice 当前价格 * @return 是否满足空头平仓条件 */ private boolean isShortExitCondition(MACDResult macdResult, BigDecimal currentPrice) { // 空头平仓条件:MACD柱状线动量减弱(由负转弱) List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() >= 2) { PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由负转弱:前一根为负,当前绝对值减小 boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 && latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0; return momentumWeakening; } return false; } // MACD信号辅助方法 /** * 简单金叉判断 * <p> * 条件:DIF线从下往上穿过DEA线 * * @param macdResult MACD计算结果 * @return 是否形成金叉 */ private boolean isGoldenCross(MACDResult macdResult) { List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() < 2) { return false; } PriceData latest = macdData.get(0); PriceData previous = macdData.get(1); // 金叉判断:DIF从下往上穿过DEA return previous.getDif().compareTo(previous.getDea()) < 0 && latest.getDif().compareTo(latest.getDea()) > 0; } /** * 简单死叉判断 * <p> * 条件:DIF线从上往下穿过DEA线 * * @param macdResult MACD计算结果 * @return 是否形成死叉 */ private boolean isDeathCross(MACDResult macdResult) { List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() < 2) { return false; } PriceData latest = macdData.get(0); PriceData previous = macdData.get(1); // 死叉判断:DIF从上往下穿过DEA return previous.getDif().compareTo(previous.getDea()) > 0 && latest.getDif().compareTo(latest.getDea()) < 0; } /** * MACD柱状线由负转正判断 * <p> * 条件:前一根柱状线为负,当前柱状线为正 * * @param macdResult MACD计算结果 * @return 是否由负转正 */ private boolean isMacdHistTurningPositive(MACDResult macdResult) { List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() < 2) { return false; } PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由负转正:前一根为负,当前为正 return previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 && latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0; } /** * MACD柱状线由正转负判断 * <p> * 条件:前一根柱状线为正,当前柱状线为负 * * @param macdResult MACD计算结果 * @return 是否由正转负 */ private boolean isMacdHistTurningNegative(MACDResult macdResult) { List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() < 2) { return false; } PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由正转负:前一根为正,当前为负 return previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 && latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0; } } src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
@@ -16,6 +16,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -69,7 +71,7 @@ private static final String STATE_KEY = "state"; private static final String FILLFEE_KEY = "fillFee"; private static final String POSSIDE_KEY = "posSide"; public static TradeRequestParam handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) { public static List<TradeRequestParam> handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) { log.info("开始执行OrderInfoWs......"); try { @@ -116,6 +118,7 @@ log.info("{}: 订单详情已完成: {}, 自定义编号: {}", accountName, CoinEnums.HE_YUE.getCode(), clOrdId); ArrayList<TradeRequestParam> tradeRequestParams = new ArrayList<>(); TradeRequestParam tradeRequestParam = new TradeRequestParam(); tradeRequestParam.setAccountName(accountName); BigDecimal zhiYingPx = getZhiYingPx( @@ -137,7 +140,31 @@ tradeRequestParam.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode()); tradeRequestParam.setClOrdId(WsParamBuild.getOrderNum(tradeRequestParam.getSide())); tradeRequestParam.setSz(accFillSz); return tradeRequestParam; tradeRequestParams.add(tradeRequestParam); TradeRequestParam tradeRequestParamZhiSun = new TradeRequestParam(); tradeRequestParamZhiSun.setAccountName(accountName); BigDecimal zhiSunPx = getZhiSunPx( accountName, posSide, fillFee, WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CTVAL.name())), WsMapBuild.parseBigDecimalSafe(accFillSz), WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CONTRACTMULTIPLIER.name())), WsMapBuild.parseBigDecimalSafe(avgPx), WsMapBuild.parseBigDecimalSafe(CoinEnums.LEVERAGE.getCode()) ); tradeRequestParamZhiSun.setMarkPx(String.valueOf(zhiSunPx)); tradeRequestParamZhiSun.setInstId(CoinEnums.HE_YUE.getCode()); tradeRequestParamZhiSun.setTdMode(CoinEnums.CROSS.getCode()); tradeRequestParamZhiSun.setPosSide(posSide); tradeRequestParamZhiSun.setOrdType(CoinEnums.ORDTYPE_LIMIT.getCode()); tradeRequestParamZhiSun.setTradeType(OrderParamEnums.TRADE_YES.getValue()); tradeRequestParamZhiSun.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode()); tradeRequestParamZhiSun.setClOrdId(WsParamBuild.getOrderNum(tradeRequestParamZhiSun.getSide())); tradeRequestParamZhiSun.setSz(accFillSz); tradeRequestParams.add(tradeRequestParamZhiSun); return tradeRequestParams; } return null; @@ -164,6 +191,23 @@ /** * 计算预期收益 */ public static BigDecimal getZhiSunPx( String accountName, String posSide, String fillFee, BigDecimal coinValue, BigDecimal coinNum, BigDecimal contractMultiplier, BigDecimal avgPx, BigDecimal leverage ) { BigDecimal initMargin = getInitMargin(coinValue, coinNum, contractMultiplier, avgPx, leverage); String pingCangImr = StrUtil.isEmpty(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name())) ? "0.2" : InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name()); BigDecimal expectProfit = (initMargin) .multiply(new BigDecimal(pingCangImr)) .add(new BigDecimal(fillFee).abs()) .multiply(new BigDecimal("-1")) .setScale(4, RoundingMode.DOWN); log.info("{}: 订单详情-预期收益: {}", accountName, expectProfit); return getMarkPrice(expectProfit,posSide, coinValue, coinNum, contractMultiplier, avgPx, leverage); } /** * 计算预期收益 */ public static BigDecimal getZhiYingPx( String accountName, String posSide, String fillFee, BigDecimal coinValue, BigDecimal coinNum, BigDecimal contractMultiplier, BigDecimal avgPx, BigDecimal leverage src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -13,6 +13,7 @@ import org.java_websocket.client.WebSocketClient; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -33,6 +34,7 @@ } public static final String ORDERWS_CHANNEL = "order"; public static final String BATCH_ORDERSWS_CHANNEL = "batch-orders"; public static void orderEvent(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) { @@ -136,64 +138,65 @@ } } public static void orderZhiYingZhiSunEventNoState(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) { public static void orderZhiYingZhiSunEventNoState(WebSocketClient webSocketClient, List<TradeRequestParam> tradeRequestParams) { log.info("开始执行TradeOrderWs......"); if (tradeRequestParam == null){ if (tradeRequestParams == null){ log.warn("下单{}参数缺失,取消发送",tradeRequestParam); return; } String accountName = tradeRequestParam.getAccountName(); String markPx = tradeRequestParam.getMarkPx(); String instId = tradeRequestParam.getInstId(); String tdMode = tradeRequestParam.getTdMode(); String posSide = tradeRequestParam.getPosSide(); String ordType = tradeRequestParam.getOrdType(); String tradeType = tradeRequestParam.getTradeType(); String clOrdId = tradeRequestParam.getClOrdId(); String side = tradeRequestParam.getSide(); String sz = tradeRequestParam.getSz(); /** * 校验必要参数 * 验证下单参数是否存在空值 */ if ( StrUtil.isBlank(accountName) || StrUtil.isBlank(instId) || StrUtil.isBlank(tdMode) || StrUtil.isBlank(posSide) || StrUtil.isBlank(ordType) || StrUtil.isBlank(clOrdId) || StrUtil.isBlank(side) || StrUtil.isBlank(sz) || StrUtil.isBlank(markPx) ){ log.warn("下单参数缺失,取消发送"); return; } log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},", accountName,ordType, markPx, instId, posSide,side, sz, tradeType, clOrdId); //验证是否允许下单 if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) { log.warn("账户{}不允许下单,取消发送", accountName); log.warn("下单{}参数缺失,取消发送",tradeRequestParams); return; } /** * 检验账户和仓位是否准备就绪 * 开多:买入开多(side 填写 buy; posSide 填写 long ) * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪 * 平多:卖出平多(side 填写 sell;posSide 填写 long ) * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪 */ JSONArray argsArray = new JSONArray(); for (TradeRequestParam tradeRequestParam : tradeRequestParams){ String accountName = tradeRequestParam.getAccountName(); String markPx = tradeRequestParam.getMarkPx(); String instId = tradeRequestParam.getInstId(); String tdMode = tradeRequestParam.getTdMode(); String posSide = tradeRequestParam.getPosSide(); String ordType = tradeRequestParam.getOrdType(); try { JSONArray argsArray = new JSONArray(); String tradeType = tradeRequestParam.getTradeType(); String clOrdId = tradeRequestParam.getClOrdId(); String side = tradeRequestParam.getSide(); String sz = tradeRequestParam.getSz(); /** * 校验必要参数 * 验证下单参数是否存在空值 */ if ( StrUtil.isBlank(accountName) || StrUtil.isBlank(instId) || StrUtil.isBlank(tdMode) || StrUtil.isBlank(posSide) || StrUtil.isBlank(ordType) || StrUtil.isBlank(clOrdId) || StrUtil.isBlank(side) || StrUtil.isBlank(sz) || StrUtil.isBlank(markPx) ){ log.warn("下单参数缺失,取消发送"); continue; } log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},", accountName,ordType, markPx, instId, posSide,side, sz, tradeType, clOrdId); //验证是否允许下单 if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) { log.warn("账户{}不允许下单,取消发送", accountName); continue; } /** * 检验账户和仓位是否准备就绪 * 开多:买入开多(side 填写 buy; posSide 填写 long ) * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪 * 平多:卖出平多(side 填写 sell;posSide 填写 long ) * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪 */ JSONObject args = new JSONObject(); args.put("instId", instId); args.put("tdMode", tdMode); @@ -205,15 +208,12 @@ args.put("sz", sz); args.put("px", markPx); argsArray.add(args); String connId = WsParamBuild.getOrderNum(ORDERWS_CHANNEL); JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, ORDERWS_CHANNEL, argsArray); webSocketClient.send(jsonObject.toJSONString()); log.info("发送下单频道:{},数量:{}", side, sz); } catch (Exception e) { log.error("下单构建失败", e); } String connId = WsParamBuild.getOrderNum(null); JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, BATCH_ORDERSWS_CHANNEL, argsArray); webSocketClient.send(jsonObject.toJSONString()); log.info("发送止盈止损批量下单频道:{}",argsArray); } src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
@@ -43,7 +43,7 @@ READY_STATE_YES("准备就绪ready_state", "1"), READY_STATE_NO("未准备就绪ready_state", "0"), PING_CANG_SHOUYI("平仓收益比例", "0.5"), PING_CANG_SHOUYI("平仓收益比例", "1"), //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金 TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.06"), TOTAL_ORDER_USDT("总保证金totalOrderUsdt","0"), src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java
@@ -13,14 +13,14 @@ * 模拟盘账户信息 * 存储了模拟盘交易所需的API密钥、秘钥和通过码 */ OKX_UAT("f512673b-2685-4fcb-9bb1-2ae8db745d62", "B0C1CC8F39625B41140D93DC25039E33", "Aa12345678@", true); // OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", // "AA06C5ED1D7C7F5AFE6484052E231C55", // OKX_UAT("f512673b-2685-4fcb-9bb1-2ae8db745d62", // "B0C1CC8F39625B41140D93DC25039E33", // "Aa12345678@", // false); // true); OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", "AA06C5ED1D7C7F5AFE6484052E231C55", "Aa12345678@", false); // /** // * 模拟盘账户信息