Administrator
2025-12-09 7e213a5e5a233ba19eaa148cc65c9f3cfa96986e
feat(okx): 优化持仓策略与交易逻辑

- 调整配置文件数据库连接信息
- 更新应用环境激活方式为app模式
- 新增未实现盈亏、已实现盈亏及维持保证金键值定义
- 修改加仓和平仓条件判断逻辑
- 引入收益比例控制平仓操作
- 增强日志记录,包括队列状态和移除价格信息
- 移除旧的盈亏计算方法并重构相关逻辑
- 更新WebSocket客户端中持仓检测字段
- 添加平仓收益比例枚举参数
- 调整网格价格上下限设置
8 files modified
151 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 76 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeEnum.java 4 ●●●● patch | view | raw | blame | history
src/main/resources/application-app.yml 8 ●●●● patch | view | raw | blame | history
src/main/resources/application.yml 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -263,10 +263,10 @@
        } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
            PositionsWs.handleEvent(response, redisUtils);
            String avgPxKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":avgPx";
            String avgPx = (String) redisUtils.get(avgPxKey);
            if (StrUtil.isBlank(avgPx)) {
                log.error("未获取到持仓均价");
            String posKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":pos";
            String pos = (String) redisUtils.get(posKey);
            if (StrUtil.isBlank(pos)) {
                log.error("未获取到持仓数量");
                TradeOrderWs.orderEvent(webSocketClient, redisUtils, OrderParamEnums.INIT.getValue());
                return;
            }
@@ -285,7 +285,7 @@
            }
            String side = caoZuoService.caoZuo();
            if (StrUtil.isNotBlank(avgPx)) {
            if (StrUtil.isNotBlank(pos)) {
                TradeOrderWs.orderEvent(webSocketClient, redisUtils, side);
            }
        } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -44,6 +44,9 @@
        final String positionsMarkPxKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":markPx";
        final String positionsAvgPxKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":avgPx";
        final String positionsOrderPriceKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":orderPrice";
        final String uplKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":upl";
        final String realizedPnlKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":realizedPnl";
        final String imrKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":imr";
        // 获取合约状态
        String state = (String) redisUtils.get(instrumentsKey);
@@ -84,7 +87,7 @@
            // 判断是加仓还是减仓
            if (avgPx.compareTo(markPx) > 0) {
                DescBigDecimal kaiCang = queueKaiCang.peek();
                if (kaiCang != null && kaiCang.getValue().compareTo(markPx) >= 0) {
                if (kaiCang != null && markPx.compareTo(kaiCang.getValue()) <= 0 && avgPx.compareTo(kaiCang.getValue()) >= 0) {
                    log.info("开始加仓...开仓队列价格大于当前价格{}>{}", kaiCang.getValue(), markPx);
                    side = OrderParamEnums.BUY.getValue();
                    redisUtils.set(positionsOrderPriceKey, String.valueOf(kaiCang.getValue()), 0);
@@ -93,10 +96,41 @@
                }
            } else if (avgPx.compareTo(markPx) < 0) {
                AscBigDecimal pingCang = queuePingCang.peek();
                if (pingCang != null && pingCang.getValue().compareTo(markPx) <= 0) {
                if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) {
                    log.info("开始减仓...平仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx);
                    side = OrderParamEnums.SELL.getValue();
                    redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                    //判断当前是否盈利
                    String upl = (String) redisUtils.get(uplKey);
                    String realizedPnl = (String) redisUtils.get(realizedPnlKey);
                    String imr = (String) redisUtils.get(imrKey);
                    if (upl != null && realizedPnl != null && imr != null) {
                        BigDecimal uplValue = new BigDecimal(upl);
                        BigDecimal realizedPnlValue = new BigDecimal(realizedPnl);
                        BigDecimal imrValue = new BigDecimal(imr).multiply(new BigDecimal(OrderParamEnums.PING_CANG_SHOUYI.getValue()));
                        if (realizedPnlValue.compareTo(BigDecimal.ZERO) <= 0) {
                            if (uplValue.compareTo(realizedPnlValue) < 0) {
                                log.info("当前未实现盈亏:{}没有大于已实现收益>{},等待中", uplValue, realizedPnlValue);
                                return OrderParamEnums.HOLDING.getValue();
                            }else if (uplValue.compareTo(realizedPnlValue) > 0 && uplValue.compareTo(imrValue)  >= 0) {
                                log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.SELL.getValue();
                            }else{
                                log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                                return OrderParamEnums.HOLDING.getValue();
                            }
                        }else {
                            if (uplValue.compareTo(imrValue)  >= 0) {
                                log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.SELL.getValue();
                            }else{
                                log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                                return OrderParamEnums.HOLDING.getValue();
                            }
                         }
                    }else {
                        return OrderParamEnums.HOLDING.getValue();
                    }
                } else {
                    log.info("未触发减仓......,等待");
                }
@@ -125,6 +159,7 @@
        if (orderPrice == null) {
            return;
        }
        log.info("需要移除的价格: {}", orderPrice);
        BigDecimal priceDecimal;
        try {
@@ -140,6 +175,8 @@
        } else {
            queueKaiCang.removeIf(item -> item.getValue().equals(priceDecimal));
        }
        // 打印开仓队列
        log.info("开仓队列: {}", queueKaiCang);
        boolean pingCangExists = queuePingCang.stream().anyMatch(item -> item.getValue().equals(priceDecimal));
        if (!pingCangExists) {
@@ -147,35 +184,8 @@
        } else {
            queuePingCang.removeIf(item -> item.getValue().equals(priceDecimal));
        }
    }
    /**
     * 计算盈亏金额。
     *
     * @param faceValue 面值
     * @param position 持仓数量
     * @param contractMultiplier 合约乘数
     * @param markPrice 标记价格
     * @param openPrice 开仓价格
     * @param isLong 是否为多头仓位
     * @param minTickSz 最小变动单位精度
     * @return 盈亏金额,保留指定精度的小数位
     */
    public BigDecimal profit(BigDecimal faceValue, BigDecimal position, BigDecimal contractMultiplier,
                             BigDecimal markPrice, BigDecimal openPrice, boolean isLong, int minTickSz) {
        BigDecimal profit = BigDecimal.ZERO;
        if (isLong) {
            profit = markPrice.subtract(openPrice)
                    .multiply(faceValue)
                    .multiply(contractMultiplier)
                    .multiply(position);
        } else {
            profit = openPrice.subtract(markPrice)
                    .multiply(faceValue)
                    .multiply(contractMultiplier)
                    .multiply(position);
        }
        return profit.setScale(minTickSz, BigDecimal.ROUND_DOWN);
        // 打印平仓队列
        log.info("平仓队列: {}", queuePingCang);
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
@@ -106,8 +106,9 @@
            String mgnRatio = safeGetString(posData, "mgnRatio");
            String markPx = safeGetString(posData, "markPx");
            String bePx = safeGetString(posData, "bePx");
            String realizedPnl = safeGetString(posData, "realizedPnl");
            boolean setResult = saveToRedis(redisUtils, avgPx, pos, upl, imr, mgnRatio, markPx, bePx);
            boolean setResult = saveToRedis(redisUtils, avgPx, pos, upl, imr, mgnRatio, markPx, bePx,realizedPnl);
            if (setResult) {
                calculateAndSaveBuyCount(redisUtils);
@@ -121,14 +122,15 @@
    private static boolean saveToRedis(RedisUtils redisUtils,
                                       String avgPx, String pos, String upl,
                                       String imr, String mgnRatio, String markPx, String bePx) {
                                       String imr, String mgnRatio, String markPx, String bePx, String realizedPnl) {
        return redisUtils.set(REDIS_KEY_PREFIX + ":avgPx", avgPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":pos", pos, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":upl", upl, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":imr", imr, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":mgnRatio", mgnRatio, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":markPx", markPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":bePx", bePx, 0);
                && redisUtils.set(REDIS_KEY_PREFIX + ":bePx", bePx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":realizedPnl", realizedPnl, 0);
    }
    private static void calculateAndSaveBuyCount(RedisUtils redisUtils) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -11,6 +11,8 @@
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import java.math.BigDecimal;
/**
 * @author Administrator
 */
@@ -34,9 +36,14 @@
            side = OrderParamEnums.SELL.getValue();
            buyCnt = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":pos");
        } else {
            String buyCntNormal = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":buyCnt");
            if (StrUtil.isNotBlank(buyCntNormal)) {
                buyCnt = buyCntNormal;
            if (OrderParamEnums.BUY.getValue().equals(side)){
                String buyCntNormal = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":buyCnt");
                if (StrUtil.isNotBlank(buyCntNormal)) {
                    buyCnt = buyCntNormal;
                }
            }else{
                side = OrderParamEnums.SELL.getValue();
                buyCnt = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":pos");
            }
        }
@@ -80,6 +87,34 @@
        }
    }
    /**
     * 计算盈亏金额。
     *
     * @param faceValue 面值
     * @param position 持仓数量
     * @param markPrice 标记价格
     * @param openPrice 开仓价格
     * @param isLong 是否为多头仓位
     * @param minTickSz 最小变动单位精度
     * @return 盈亏金额,保留指定精度的小数位
     */
    public BigDecimal profit(BigDecimal faceValue, BigDecimal position,
                             BigDecimal markPrice, BigDecimal openPrice, boolean isLong, int minTickSz) {
        BigDecimal profit = BigDecimal.ZERO;
        if (isLong) {
            profit = markPrice.subtract(openPrice)
                    .multiply(faceValue)
                    .multiply(position);
        } else {
            profit = openPrice.subtract(markPrice)
                    .multiply(faceValue)
                    .multiply(position);
        }
        return profit.setScale(minTickSz, BigDecimal.ROUND_DOWN);
    }
    /**
     * 统一封装 Redis Key 构建逻辑
     *
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java
@@ -29,6 +29,8 @@
    LEVERAGE("杠杆倍数", "100"),
    PING_CANG_SHOUYI("平仓收益比例", "0.2"),
    EVERY_TIME_USDT("总下单次数", "20"),
    //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金
    TOTAL_ORDER_USDT("下单的总金额比例", "0.5"),
src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeEnum.java
@@ -9,8 +9,8 @@
public enum WangGeEnum {
    XIAOSHU_WEISHU("网格价格小数位数", "2"),
    JIAGE_SHANGXIAN("网格上限", "94000"),
    JIAGE_XIAXIAN("网格下限", "90000"),
    JIAGE_SHANGXIAN("网格上限", "93000"),
    JIAGE_XIAXIAN("网格下限", "89000"),
    JIAN_JU("网格间距", "100")
    ;
src/main/resources/application-app.yml
@@ -5,11 +5,11 @@
spring:
  profiles:
    active: prd
    active: app
  datasource:
    url: jdbc:mysql://47.76.217.51:3306/db_base?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8
    username: root
    password: =[;.-pl,1234!@#$!QAZ
    url: jdbc:mysql://47.76.217.51:3306/db_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&allowMultiQueries=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
    username: db_base
    password: P@ssw0rd!123
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
src/main/resources/application.yml
@@ -7,7 +7,7 @@
  OKEX:
    baseurl: https://www.okex.com
  profiles:
    active: test
    active: app
  datasource:
    url: jdbc:mysql://120.27.238.55:3406/db_base?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8
    username: ct_test