Administrator
5 days ago 23ece6103fd890655f0eef79331d3d73921611a2
feat(trade): 优化交易系统止损逻辑和订单处理机制

- 实现双向持仓亏损比较,优先止损亏损较大的方向
- 修改OrderInfoWs和PositionsWs的数据处理方式,统一订单参数传递格式
- 增加网格价格止损点位的自动计算和订单生成
- 简化PositionsWs持仓处理逻辑,移除重复的止损参数构建
- 优化TradeOrderWs限价单处理,支持批量订单参数处理
- 修复多账号数据冲突问题,增强交易系统的稳定性
5 files modified
171 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 87 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -395,15 +395,14 @@
        // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联
        if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
            TradeRequestParam tradeRequestParam = OrderInfoWs.handleEvent(response, redisUtils, account.name());
            TradeOrderWs.orderZhiYingEvent(webSocketClient, tradeRequestParam);
            List<TradeRequestParam> tradeRequestParams = OrderInfoWs.handleEvent(response, redisUtils, account.name());
            TradeOrderWs.orderZhiYingEvent(webSocketClient, tradeRequestParams);
        }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
            AccountWs.handleEvent(response, account.name());
        } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
            List<TradeRequestParam> tradeRequestParams = PositionsWs.handleEvent(response, account.name());
            TradeOrderWs.orderZhiSunEvent(webSocketClient, tradeRequestParams);
            PositionsWs.handleEvent(response, account.name());
        } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) {
            BalanceAndPositionWs.handleEvent(response);
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -81,6 +81,17 @@
            // 账户预期亏损金额比这个还小时,立即止损
            if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
                log.warn("账户冷静止损......");
                //目前止损掉损失较大的一个方向
                String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
                BigDecimal upl = PositionsWs.getAccountMap(positionAccountName).get("upl");
                String posSideOther = CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.POSSIDE_SHORT.getCode() : CoinEnums.POSSIDE_LONG.getCode();
                String positionAccountOther = PositionsWs.initAccountName(accountName, posSideOther);
                BigDecimal uplOther = PositionsWs.getAccountMap(positionAccountOther).get("upl");
                if (upl.compareTo(uplOther) > 0){
                    log.warn("{}的亏损{},{}的亏损{},止损{}......",posSide,upl,posSideOther,uplOther,uplOther);
                    posSide = posSideOther;
                }
                WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(),  OrderParamEnums.OUT_YES.getValue());
                tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
                return caoZuoZhiSunEvent(accountName, markPx, posSide);
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 {
@@ -140,7 +142,7 @@
                    log.info("{}: 订单详情已完成: {}, 自定义编号: {}", accountName, CoinEnums.HE_YUE.getCode(), clOrdId);
                    List<TradeRequestParam> tradeRequestParamList = new ArrayList<>();
                    TradeRequestParam tradeRequestParam = new TradeRequestParam();
                    tradeRequestParam.setAccountName(accountName);
@@ -176,7 +178,25 @@
                    tradeRequestParam.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode());
                    tradeRequestParam.setClOrdId(WsParamBuild.getOrderNum(side));
                    tradeRequestParam.setSz(accFillSz);
                    return tradeRequestParam;
                    tradeRequestParamList.add(tradeRequestParam);
                    // 1. 判断当前价格属于哪个网格去限价止损
                    WangGeListEnum gridByPriceNew = WangGeListEnum.getGridByPrice(new BigDecimal(avgPx));
                    if (gridByPriceNew != null) {
                        TradeRequestParam tradeRequestParamZhiSun = new TradeRequestParam();
                        tradeRequestParamZhiSun.setAccountName(accountName);
                        tradeRequestParamZhiSun.setMarkPx(String.valueOf(gridByPriceNew.getZhi_sun_dian()));
                        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(side));
                        tradeRequestParamZhiSun.setSz(accFillSz);
                        tradeRequestParamList.add(tradeRequestParamZhiSun);
                    }
                    return tradeRequestParamList;
                }
                return null;
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
@@ -65,7 +65,7 @@
        initParam(arg, accountName,CoinEnums.POSSIDE_SHORT.getCode());
    }
    public static List<TradeRequestParam> handleEvent(JSONObject response, String accountName) {
    public static void handleEvent(JSONObject response, String accountName) {
        log.info("开始执行PositionsWs......");
@@ -73,10 +73,8 @@
            JSONArray dataArray = response.getJSONArray("data");
            if (dataArray == null || dataArray.isEmpty()) {
                log.info("账户持仓频道数据为空,等待更新");
                return null;
                return;
            }
            List<TradeRequestParam> tradeRequestParamList = new ArrayList<>();
            for (int i = 0; i < dataArray.size(); i++) {
                JSONObject posData = dataArray.getJSONObject(i);
@@ -118,45 +116,11 @@
                    );
                    //先更新缓存
                    Map<String, BigDecimal> stringBigDecimalMap = initParam(posData, accountName, posSide);
                    //构建止损参数
                    if (stringBigDecimalMap.get("pos").compareTo(BigDecimal.ZERO) > 0){
                        TradeRequestParam tradeRequestParam = new TradeRequestParam();
                        // 1. 判断当前价格属于哪个网格
                        WangGeListEnum gridByPriceNew = WangGeListEnum.getGridByPrice(new BigDecimal(avgPx));
                        if (gridByPriceNew != null) {
                            String zhiSunDian = gridByPriceNew.getZhi_sun_dian();
                            String fangXiang = gridByPriceNew.getFang_xiang();
                            BigDecimal fangXiangNow = stringBigDecimalMap.get("posSide");
                            if (fangXiangNow.equals(fangXiang)){
                                tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_LIMIT.getCode());
                                tradeRequestParam.setMarkPx(String.valueOf(zhiSunDian));
                            }else{
                                tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
                                tradeRequestParam.setMarkPx(String.valueOf(markPx));
                            }
                        }else{
                            tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
                            tradeRequestParam.setMarkPx(String.valueOf(markPx));
                        }
                        tradeRequestParam.setAccountName(accountName);
                        tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
                        tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
                        tradeRequestParam.setPosSide(posSide);
                        tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
                        tradeRequestParam.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode());
                        tradeRequestParam.setClOrdId(WsParamBuild.getOrderNum(tradeRequestParam.getSide()));
                        tradeRequestParam.setSz(String.valueOf(stringBigDecimalMap.get("pos")));
                        tradeRequestParamList.add(tradeRequestParam);
                    }
                }
            }
            return tradeRequestParamList;
        } catch (Exception e) {
            log.error("处理持仓频道推送数据失败", e);
        }
        return null;
    }
    private static Map<String, BigDecimal> initParam(JSONObject posData, String accountName,String posSide) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -130,15 +130,15 @@
        }
    }
    public static void orderZhiYingEvent(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) {
    public static void orderZhiYingEvent(WebSocketClient webSocketClient, List<TradeRequestParam> tradeRequestParams) {
        log.info("开始执行TradeOrderWs......");
        if (tradeRequestParam == null){
            log.warn("下单{}参数缺失,取消发送",tradeRequestParam);
        log.info("开始执行限价{}......",JSONUtil.parse(tradeRequestParams));
        if (tradeRequestParams == null){
            log.warn("限价下单参数缺失,取消发送");
            return;
        }
        for (TradeRequestParam tradeRequestParam : tradeRequestParams){
        String accountName = tradeRequestParam.getAccountName();
        String markPx = tradeRequestParam.getMarkPx();
        String instId = tradeRequestParam.getInstId();
@@ -209,84 +209,7 @@
            log.error("下单构建失败", e);
        }
    }
    public static void orderZhiSunEvent(WebSocketClient webSocketClient, List<TradeRequestParam> tradeRequestParams) {
        log.info("开始止损......");
        if (CollUtil.isEmpty(tradeRequestParams)){
            log.error("止损下单{}参数缺失,取消发送", JSONUtil.parse(tradeRequestParams));
            return;
        }
        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();
            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.error("止损下单参数缺失,取消发送");
                return;
            }
            log.info("止损账户:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
                    accountName, markPx, instId, posSide,side,  sz, tradeType, clOrdId);
            //验证是否允许下单
            if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
                log.error("止损账户{}不允许下单,取消发送", accountName);
                return;
            }
            /**
             * 检验账户和仓位是否准备就绪
             * 开多:买入开多(side 填写 buy; posSide 填写 long )
             * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪
             * 平多:卖出平多(side 填写 sell;posSide 填写 long )
             * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪
             */
            try {
                JSONArray argsArray = new JSONArray();
                JSONObject args = new JSONObject();
                args.put("instId", instId);
                args.put("tdMode", tdMode);
                args.put("clOrdId", clOrdId);
                args.put("side", side);
                args.put("posSide", posSide);
                args.put("ordType", ordType);
                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("止损已发送......");
            } catch (Exception e) {
                log.error("下单构建失败", e);
            }
        }
    }
    /**