From 030e1d50c1f643137220f1ecf1d90ce39174204a Mon Sep 17 00:00:00 2001
From: dd <gitluke@outlook.com>
Date: Sat, 30 May 2026 01:58:54 +0800
Subject: [PATCH] 1

---
 trading-order-huobi/src/main/java/com.yami.trading.huobi/data/job/AbstractGetDataJob.java                           |   46 +
 trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java                   |  162 +++-
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java      |  343 +++++-----
 trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java                              |   54 
 trading-order-admin/src/main/java/com/yami/trading/admin/dto/AdminMarketQuotationsUpdateDto.java                    |   30 
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationService.java          |   24 
 trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrderProfit.java                     |   34 +
 trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java                        |  201 -----
 trading-order-common/src/main/java/com/yami/trading/common/constants/ContractRedisKeys.java                         |   49 
 trading-order-admin/src/main/java/com/yami/trading/admin/task/RealtimeWebsocketServer.java                          |  201 +++---
 trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/AdjustmentValueServiceImpl.java              |   14 
 trading-order-admin/src/main/java/com/yami/trading/admin/controller/data/AdminMarketQuotationsManageController.java |   16 
 trading-order-admin/src/main/java/com/yami/trading/admin/task/contract/ContractApplyOrderHandleJob.java             |   23 
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java                     |  462 ++++++++++++-
 trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java              |    7 
 trading-order-admin/src/main/java/com/yami/trading/admin/facade/MarketQuotationsFacade.java                         |   84 ++
 trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java                       |  138 +--
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java                |   48 +
 18 files changed, 1,175 insertions(+), 761 deletions(-)

diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/data/AdminMarketQuotationsManageController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/data/AdminMarketQuotationsManageController.java
index a0819ed..41f14b1 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/data/AdminMarketQuotationsManageController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/data/AdminMarketQuotationsManageController.java
@@ -30,6 +30,7 @@
 
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.Pattern;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -92,10 +93,17 @@
     @ApiOperation(value = "调整值")
     @PostMapping("adjust.action")
     public Result<String> adjust(@RequestBody AdminMarketQuotationsUpdateDto adminMarketQuotationsUpdateDto) {
-//        if(!itemService.isOpen(adminMarketQuotationsUpdateDto.getSymbol())){
-//            throw new YamiShopBindException("当前休市状态");
-//        }
-        marketQuotationsFacade.adjust(adminMarketQuotationsUpdateDto.getSymbol(), adminMarketQuotationsUpdateDto.getSecond(), adminMarketQuotationsUpdateDto.getValue());
+        List<String> supportCoins = Arrays.asList("btcusdt", "ethusdt", "dotusdt");
+
+        if (supportCoins.contains(adminMarketQuotationsUpdateDto.getSymbol())) {
+            // 包含在列表中
+            return Result.failed("该币种不支持调整!");
+        }
+        marketQuotationsFacade.adjust(
+                adminMarketQuotationsUpdateDto.getSymbol(),
+                adminMarketQuotationsUpdateDto.getSecond(),
+                adminMarketQuotationsUpdateDto.getValue(),
+                adminMarketQuotationsUpdateDto.getType());
         return Result.succeed("操作成功");
     }
 }
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/dto/AdminMarketQuotationsUpdateDto.java b/trading-order-admin/src/main/java/com/yami/trading/admin/dto/AdminMarketQuotationsUpdateDto.java
index df636e6..a4b2754 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/dto/AdminMarketQuotationsUpdateDto.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/dto/AdminMarketQuotationsUpdateDto.java
@@ -1,13 +1,14 @@
 package com.yami.trading.admin.dto;
 
+import com.fasterxml.jackson.annotation.JsonSetter;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
+import lombok.AccessLevel;
 import lombok.Data;
+import lombok.Setter;
 
-import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Pattern;
 import java.math.BigDecimal;
 
 @ApiModel
@@ -16,9 +17,32 @@
     @NotEmpty
     @ApiModelProperty("币对")
     private String symbol;
-    @ApiModelProperty("延迟秒")
+
+    @Setter(AccessLevel.NONE)
+    @ApiModelProperty("延迟秒,0 或空表示立即生效;支持数字或字符串")
     private Double second;
+
+    @JsonSetter("second")
+    public void setSecondFlexible(Object second) {
+        this.second = parseFlexibleDouble(second);
+    }
+
+    private static Double parseFlexibleDouble(Object v) {
+        if (v == null) {
+            return null;
+        }
+        if (v instanceof Number) {
+            return ((Number) v).doubleValue();
+        }
+        String s = String.valueOf(v).trim();
+        if (s.isEmpty()) {
+            return null;
+        }
+        return Double.parseDouble(s);
+    }
     @NotNull(message = "调整值必填")
     @ApiModelProperty("调整值")
     private BigDecimal value;
+    @ApiModelProperty("0=加1个pips 1=减1个pips 2=直接作为调整增量(默认)")
+    private String type = "2";
 }
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/facade/MarketQuotationsFacade.java b/trading-order-admin/src/main/java/com/yami/trading/admin/facade/MarketQuotationsFacade.java
index 8df8766..9860d57 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/facade/MarketQuotationsFacade.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/facade/MarketQuotationsFacade.java
@@ -184,24 +184,88 @@
 
     }
 
-    public void adjust(String symbol, Double second, BigDecimal value) {
+    /**
+     * 提交调整(与 getValue.action 预计算使用同一套 type 规则)。
+     */
+    public void adjust(String symbol, Double second, BigDecimal value, String type) {
         AdjustmentValueCache.getDelayValue().remove(symbol);
         AdjustmentValueCache.getPreAllocatedAdjustments().remove(symbol);
         AdjustmentValueCache.getCurrentAdjustmentIndex().remove(symbol);
         AdjustmentValueCache.getFrequency().remove(symbol);
 
-        BigDecimal currentValue = this.adjustmentValueService.getCurrentValue(symbol);
-        if (currentValue == null) {
-            Realtime realtime = this.dataService.realtime(symbol).get(0);
-            currentValue = realtime.getClose();
+        String adjustType = StringUtils.isBlank(type) ? "2" : type;
+        BigDecimal effectiveDelta = resolveEffectiveAdjustDelta(symbol, adjustType, value);
+        if (effectiveDelta == null || effectiveDelta.compareTo(BigDecimal.ZERO) == 0) {
+            log.warn("行情调整无效 symbol={} value={} type={}", symbol, value, adjustType);
+            return;
         }
-        String log = MessageFormat.format("ip:" + IPHelper.getIpAddr() + ",管理员调整行情,币种:{0},原值:{1},调整值:{2},调整时间:{3}",
-                symbol, currentValue.toPlainString(), value.toPlainString(), second);
 
-        this.adjustmentValueService.adjust(symbol, value, second);
-        saveLog(log);
-        ThreadUtils.sleep(1000);
+        BigDecimal beforeAdjust = adjustmentValueService.getCurrentValue(symbol);
+        double secondVal = second == null ? 0D : second;
 
+        String logContent = MessageFormat.format(
+                "ip:" + IPHelper.getIpAddr() + ",管理员调整行情,币种:{0},调整前累计:{1},本次增量:{2},type:{3},延迟秒:{4}",
+                symbol,
+                beforeAdjust == null ? "0" : beforeAdjust.toPlainString(),
+                effectiveDelta.toPlainString(),
+                adjustType,
+                secondVal);
+
+        adjustmentValueService.adjust(symbol, effectiveDelta, secondVal);
+
+        // 立即生效时同步内存行情,避免只改缓存累计值但页面仍显示旧价
+        if (secondVal <= 0) {
+            syncRealtimeCacheAfterAdjust(symbol, effectiveDelta);
+        } else {
+            log.info("延迟调整已提交 symbol={} 目标增量={} 时长约{}秒,价格将分步变化(非瞬间到位)",
+                    symbol, effectiveDelta.toPlainString(), secondVal);
+        }
+
+        saveLog(logContent);
+        ThreadUtils.sleep(500);
+    }
+
+    /**
+     * 与 calculateValue 一致:type 0/1 在输入值基础上加减 pips,type 2 直接使用输入值作为本次增量。
+     */
+    private BigDecimal resolveEffectiveAdjustDelta(String symbol, String type, BigDecimal inputValue) {
+        if (inputValue == null) {
+            return null;
+        }
+        Item item = itemService.findBySymbol(symbol);
+        if (item == null) {
+            return inputValue;
+        }
+        double pips = item.getPips() != null ? item.getPips().doubleValue() : 0D;
+        double v = inputValue.doubleValue();
+        if ("0".equalsIgnoreCase(type)) {
+            return BigDecimal.valueOf(Arith.add(v, pips));
+        }
+        if ("1".equalsIgnoreCase(type)) {
+            return BigDecimal.valueOf(Arith.sub(v, pips));
+        }
+        return inputValue;
+    }
+
+    /** 将本次增量反映到 DataCache,与 WebSocket/采集任务展示逻辑一致 */
+    private void syncRealtimeCacheAfterAdjust(String symbol, BigDecimal effectiveDelta) {
+        Realtime realtime = DataCache.getRealtime(symbol);
+        if (realtime == null) {
+            List<Realtime> list = dataService.realtime(symbol);
+            if (CollectionUtil.isEmpty(list)) {
+                return;
+            }
+            realtime = list.get(0);
+        }
+        Integer decimal = itemService.getDecimal(symbol);
+        realtime.setClose(realtime.getClose().add(effectiveDelta).setScale(decimal, RoundingMode.HALF_UP));
+        if (realtime.getAsk() != null) {
+            realtime.setAsk(realtime.getAsk().add(effectiveDelta).setScale(decimal, RoundingMode.HALF_UP));
+        }
+        if (realtime.getBid() != null) {
+            realtime.setBid(realtime.getBid().add(effectiveDelta).setScale(decimal, RoundingMode.HALF_UP));
+        }
+        DataCache.putRealtime(symbol, realtime);
     }
 
     public void saveLog(String content) {
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/task/RealtimeWebsocketServer.java b/trading-order-admin/src/main/java/com/yami/trading/admin/task/RealtimeWebsocketServer.java
index 8a0250d..f7b8e00 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/task/RealtimeWebsocketServer.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/task/RealtimeWebsocketServer.java
@@ -30,32 +30,32 @@
 import java.util.stream.Collectors;
 
 /**
- * 
+ *
  * 深度、近期交易记录 websocket启动服务
  */
 @Component
 public class RealtimeWebsocketServer {
 
-	@Autowired
-	ItemService itemService;
-	@Autowired
-	HobiDataService hobiDataService;
+    @Autowired
+    ItemService itemService;
+    @Autowired
+    HobiDataService hobiDataService;
 
-	public static Map<String, Long> map = new HashMap();
+    public static Map<String, Long> map = new HashMap();
 
-	public void start() {
-		List<Item> itemList = itemService.cacheGetAll().stream().filter(i -> i.getType().equalsIgnoreCase(Item.cryptos)).collect(Collectors.toList());
+    public void start() {
+        List<Item> itemList = itemService.cacheGetAll().stream().filter(i -> i.getType().equalsIgnoreCase(Item.cryptos)).collect(Collectors.toList());
 
-		MarketClient tickerClient = MarketClient.create(new HuobiOptions());
-		for (Item item : itemList) {
-			String symbol = item.getSymbol();
-			SubMarketTickerRequest tickerReq = new SubMarketTickerRequest();
-			tickerReq.setSymbol(symbol);
-			tickerClient.subMarketTicker(tickerReq, (marketTradeEvent) -> {
-				// System.out.println(JSONObject.toJSONString(marketTradeEvent));
-				ticker(marketTradeEvent, symbol);
-			});
-		}
+        MarketClient tickerClient = MarketClient.create(new HuobiOptions());
+        for (Item item : itemList) {
+            String symbol = item.getSymbol();
+            SubMarketTickerRequest tickerReq = new SubMarketTickerRequest();
+            tickerReq.setSymbol(symbol);
+            tickerClient.subMarketTicker(tickerReq, (marketTradeEvent) -> {
+                // System.out.println(JSONObject.toJSONString(marketTradeEvent));
+                ticker(marketTradeEvent, symbol);
+            });
+        }
 
 //		MarketClient marketClient = MarketClient.create(new HuobiOptions());
 //		for (Item item : itemList) {
@@ -79,63 +79,64 @@
 //		    	trade(marketTradeEvent, symbol);
 //		      });
 //		}
-	}
+    }
 
-	private void ticker(MarketTickerEvent event, String symbol) {
-		try {
+    private void ticker(MarketTickerEvent event, String symbol) {
+        try {
 
-			if (map.containsKey(symbol) && map.get(symbol) > event.getTs()) {
-				return;
-			}
-			map.put(symbol, event.getTs() + 500);
-			Realtime realtime = new Realtime();
-			Item item = itemService.findBySymbol(symbol);
-			symbol = item.getSymbol();
-			item = this.itemService.findBySymbol(symbol);
-			//停牌时不更新
-			if (itemService.isSuspended(symbol)) {
-				return;
-			}
-			Double currentValue = AdjustmentValueCache.getCurrentValue().get(symbol).doubleValue();
-			double close = event.getTicker().getClose().doubleValue();
-			double vol = event.getTicker().getVol().doubleValue();
-			double amount = event.getTicker().getAmount().doubleValue();
+            if (map.containsKey(symbol) && map.get(symbol) > event.getTs()) {
+                return;
+            }
+            map.put(symbol, event.getTs() + 500);
+            Realtime realtime = new Realtime();
+            Item item = itemService.findBySymbol(symbol);
+            symbol = item.getSymbol();
+            item = this.itemService.findBySymbol(symbol);
+            //停牌时不更新
+            if (itemService.isSuspended(symbol)) {
+                return;
+            }
+            BigDecimal adjustment = AdjustmentValueCache.getCurrentValue().get(symbol);
+            double currentValue = adjustment != null ? adjustment.doubleValue() : 0D;
+            double close = event.getTicker().getClose().doubleValue();
+            double vol = event.getTicker().getVol().doubleValue();
+            double amount = event.getTicker().getAmount().doubleValue();
 
-			realtime.setSymbol(symbol);
-			realtime.setTs(event.getTs());
-			realtime.setName(item.getName());
-			realtime.setOpen(event.getTicker().getOpen());
-			realtime.setClose(new BigDecimal(close));
-			realtime.setHigh(event.getTicker().getHigh());
-			realtime.setLow(event.getTicker().getLow());
-			realtime.setVolume(new BigDecimal(vol));
-			realtime.setAmount(new BigDecimal(amount));
+            realtime.setSymbol(symbol);
+            realtime.setTs(event.getTs());
+            realtime.setName(item.getName());
+            realtime.setOpen(event.getTicker().getOpen());
+            realtime.setClose(new BigDecimal(close));
+            realtime.setHigh(event.getTicker().getHigh());
+            realtime.setLow(event.getTicker().getLow());
+            realtime.setVolume(new BigDecimal(vol));
+            realtime.setAmount(new BigDecimal(amount));
 
-			if (currentValue != null && currentValue != 0) {
-				realtime.setClose(new BigDecimal(Arith.add(close, currentValue)));
-				//realtime.setVolume(new BigDecimal(Arith.add(vol, Arith.mul(Arith.div(currentValue, close), vol))));
-				//realtime.setAmount(new BigDecimal(Arith.add(amount, Arith.mul(Arith.div(currentValue, close), amount))));
+            if (currentValue != 0) {
+                realtime.setClose(new BigDecimal(Arith.add(close, currentValue)));
+                //realtime.setVolume(new BigDecimal(Arith.add(vol, Arith.mul(Arith.div(currentValue, close), vol))));
+                //realtime.setAmount(new BigDecimal(Arith.add(amount, Arith.mul(Arith.div(currentValue, close), amount))));
 
-				Double high = DataCache.getRealtimeHigh().get(symbol);
-				Double low = DataCache.getRealtimeLow().get(symbol);
+                Double high = DataCache.getRealtimeHigh().get(symbol);
+                Double low = DataCache.getRealtimeLow().get(symbol);
 
-				if (high != null) {
-					realtime.setHigh(BigDecimal.valueOf(high));
-				}
-				if (low != null) {
-					realtime.setLow(BigDecimal.valueOf(low));
-				}
-			}
+                if (high != null) {
+                    realtime.setHigh(BigDecimal.valueOf(high));
+                }
+                if (low != null) {
+                    realtime.setLow(BigDecimal.valueOf(low));
+                }
+            }
 
 
 
-			Realtime current = DataCache.getRealtime(symbol);
-			if (current == null || current.getTs() != event.getTs()) {
-				// 交易量倍数不为空或0时修改倍数
-				if (item.getMultiple().doubleValue() > 0) {
-					realtime.setVolume(realtime.getVolume().multiply(item.getMultiple()));
-					realtime.setAmount(realtime.getAmount().multiply(item.getMultiple()));
-				}
+            Realtime current = DataCache.getRealtime(symbol);
+            if (current == null || current.getTs() != event.getTs()) {
+                // 交易量倍数不为空或0时修改倍数
+                if (item.getMultiple().doubleValue() > 0) {
+                    realtime.setVolume(realtime.getVolume().multiply(item.getMultiple()));
+                    realtime.setAmount(realtime.getAmount().multiply(item.getMultiple()));
+                }
 
 				/*if (high != null && high >= realtime.getClose().doubleValue()) {
 					realtime.setHigh(new BigDecimal(high));
@@ -153,42 +154,42 @@
 				/*if (realtime.getSymbol().equals("galausdt")) {
 					System.out.println("galausdt222" + realtime);
 				}*/
-				DataCache.putRealtime(symbol, realtime);
-				// System.out.println(JSONObject.toJSONString(realtime));
-			}
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-	}
+                DataCache.putRealtime(symbol, realtime);
+                // System.out.println(JSONObject.toJSONString(realtime));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 
-	private void depth(MarketDepthEvent event, String symbol) {
-		Item item = itemService.findBySymbol(symbol);
-		Depth depth = hobiDataService.depthDecorator(event, item);
-		if (depth != null) {
-			DepthTimeObject timeObject = new DepthTimeObject();
-			timeObject.setLastTime(new Date());
-			timeObject.setDepth(depth);
-			DataCache.getDepth().put(item.getSymbol(), timeObject);
-			// System.out.println("深度数据 入缓存" + JSONObject.toJSONString(timeObject));
-		}
-	}
+    private void depth(MarketDepthEvent event, String symbol) {
+        Item item = itemService.findBySymbol(symbol);
+        Depth depth = hobiDataService.depthDecorator(event, item);
+        if (depth != null) {
+            DepthTimeObject timeObject = new DepthTimeObject();
+            timeObject.setLastTime(new Date());
+            timeObject.setDepth(depth);
+            DataCache.getDepth().put(item.getSymbol(), timeObject);
+            // System.out.println("深度数据 入缓存" + JSONObject.toJSONString(timeObject));
+        }
+    }
 
-	private void trade(MarketTradeEvent event, String symbol) {
-		Item item = itemService.findBySymbol(symbol);
-		Trade trade = hobiDataService.tradeDecorator(event, item);
-		if (trade != null) {
-			TradeTimeObject timeObject = new TradeTimeObject();
-			timeObject.setLastTime(new Date());
-			timeObject.put(item.getSymbol(), trade.getData());
-			DataCache.getTrade().put(item.getSymbol(), timeObject);
-		}
-	}
+    private void trade(MarketTradeEvent event, String symbol) {
+        Item item = itemService.findBySymbol(symbol);
+        Trade trade = hobiDataService.tradeDecorator(event, item);
+        if (trade != null) {
+            TradeTimeObject timeObject = new TradeTimeObject();
+            timeObject.setLastTime(new Date());
+            timeObject.put(item.getSymbol(), trade.getData());
+            DataCache.getTrade().put(item.getSymbol(), timeObject);
+        }
+    }
 
-	public void setItemService(ItemService itemService) {
-		this.itemService = itemService;
-	}
+    public void setItemService(ItemService itemService) {
+        this.itemService = itemService;
+    }
 
-	public void setHobiDataService(HobiDataService hobiDataService) {
-		this.hobiDataService = hobiDataService;
-	}
+    public void setHobiDataService(HobiDataService hobiDataService) {
+        this.hobiDataService = hobiDataService;
+    }
 }
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/task/contract/ContractApplyOrderHandleJob.java b/trading-order-admin/src/main/java/com/yami/trading/admin/task/contract/ContractApplyOrderHandleJob.java
index c1e8a5f..ac1bb6c 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/task/contract/ContractApplyOrderHandleJob.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/task/contract/ContractApplyOrderHandleJob.java
@@ -48,6 +48,11 @@
 				List<ContractApplyOrder> list = this.contractApplyOrderService.findSubmitted();
 				for (int i = 0; i < list.size(); i++) {
 					ContractApplyOrder order = list.get(i);
+					ContractApplyOrder latest = this.contractApplyOrderService.findByOrderNo(order.getOrderNo());
+					if (latest == null || !ContractApplyOrder.STATE_SUBMITTED.equals(latest.getState())) {
+						continue;
+					}
+					order = latest;
 					List<Realtime> realtime_list = this.dataService.realtime(order.getSymbol());
 					Realtime realtime = null;
 					if (realtime_list.size() > 0) {
@@ -107,6 +112,10 @@
 	public void handle(ContractApplyOrder applyOrder, Realtime realtime) {
 		boolean lock = false;
 		try {
+			applyOrder = this.contractApplyOrderService.findByOrderNo(applyOrder.getOrderNo());
+			if (applyOrder == null || !ContractApplyOrder.STATE_SUBMITTED.equals(applyOrder.getState())) {
+				return;
+			}
 			if (!ContractLock.add(applyOrder.getOrderNo())) {
 				return;
 			}
@@ -152,6 +161,11 @@
 
 			}
 
+		} catch (YamiShopBindException e) {
+			log.error("委托单处理失败 orderNo={}, msg={}", applyOrder.getOrderNo(), e.getMessage());
+			if (isInsufficientFunds(e)) {
+				this.contractApplyOrderService.saveCancel(applyOrder.getPartyId(), applyOrder.getOrderNo());
+			}
 		} catch (Exception e) {
 			log.error("error:", e);
 		} finally {
@@ -163,6 +177,15 @@
 		}
 
 	}
+
+	private boolean isInsufficientFunds(YamiShopBindException e) {
+		if (e.getMessage() == null) {
+			return false;
+		}
+		String msg = e.getMessage().toLowerCase();
+		return msg.contains("not sufficient funds") || msg.contains("余额不足");
+	}
+
 	public void start(){
 		new Thread(this, "ContractApplyOrderHandleJob").start();
 		if (log.isInfoEnabled())
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java
index d05f720..07e7bf5 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiContractApplyOrderController.java
@@ -28,6 +28,7 @@
 import com.yami.trading.service.contract.ContractApplyOrderService;
 import com.yami.trading.service.contract.ContractLockService;
 import com.yami.trading.service.contract.ContractOrderService;
+import com.yami.trading.service.contract.ContractPositionModeService;
 import com.yami.trading.service.data.DataService;
 import com.yami.trading.service.item.ItemLeverageService;
 import com.yami.trading.service.item.ItemService;
@@ -94,6 +95,9 @@
     private ContractOrderService contractOrderService;
 
     @Autowired
+    private ContractPositionModeService contractPositionModeService;
+
+    @Autowired
     private ContractLockService contractLockService;
     @Autowired
     private SessionTokenService sessionTokenService;
@@ -128,6 +132,7 @@
         data.put("contract_open_limit_min", contractOpenLimitMin);
         BigDecimal contractOpenLimitMax = new BigDecimal(sysparaService.find("contract_open_limit_max").getSvalue());
         data.put("contract_open_limit_max", contractOpenLimitMax);
+        data.put("contract_position_mode", contractPositionModeService.getMode());
 
 
         return Result.succeed(data);
@@ -338,7 +343,7 @@
             order.setPartyId(partyId);
             order.setSymbol(closeAction.getSymbol());
             order.setDirection(closeAction.getDirection());
-            order.setOffset(ContractApplyOrder.OFFSET_OPEN);
+            order.setOffset(ContractApplyOrder.OFFSET_CLOSE);
             order.setVolume(closeAction.getAmount());
             order.setVolumeOpen(closeAction.getAmount());
             order.setPrice(closeAction.getPrice());
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
index 082e20c..61df12d 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/KlineController.java
@@ -123,30 +123,30 @@
             return Result.succeed(this.build(data, line, symbol));
         } catch (Exception e) {
             logger.error("getKline error", e);
-            throw new YamiShopBindException("");
+            throw new YamiShopBindException("Failed to obtain the K-line chart");
         }
     }
 
     private List<Map<String, Object>> build(List<Kline> data, String line, String symbol) {
-        Collections.sort(data);
-        // 相同时间戳保留最新一条(进行中K线会覆盖缓存中的旧数据)
-        Map<Long, Kline> latestByTs = new LinkedHashMap<>();
-        for (Kline kline : data) {
-            latestByTs.put(kline.getTs(), kline);
-        }
-        data = new ArrayList<>(latestByTs.values());
         Collections.sort(data);
         int len = data.size();
         for (int i = 1; i < len; i++) {
             data.get(i).setOpen(data.get(i - 1).getClose());
         }
 
+
+        Set<Long> tsSet = new HashSet<Long>();
         List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
         Item bySymbol = itemService.findBySymbol(symbol);
 
         for (int i = 0; i < data.size(); i++) {
             Kline kline = data.get(i);
             Long ts = kline.getTs();
+            if (tsSet.contains(ts)) {
+                continue;
+            } else {
+                tsSet.add(ts);
+            }
             String fake = bySymbol.getFake();
             //	if("1".equalsIgnoreCase(fake)){
             //		klineService.smoothlyKline(kline, 0.99);
@@ -182,30 +182,28 @@
             map.put("volume", kline.getVolume());
 
             if (i == data.size() - 1) {
+                BigDecimal periodHigh = high;
+                BigDecimal periodLow = low;
+                BigDecimal closePrice = close;
                 Realtime realtime = DataCache.getLatestRealTime(symbol);
-                if (realtime != null && realtime.getClose() != null) {
-                    close = realtime.getClose();
-                    map.put("close", close.setScale(decimal, RoundingMode.HALF_UP));
-                    if (close.compareTo(high) > 0) {
-                        high = close;
-                    }
-                    if (close.compareTo(low) < 0) {
-                        low = close;
-                    }
+                if (realtime != null && realtime.getClose() != null
+                        && realtime.getClose().compareTo(BigDecimal.ZERO) > 0) {
+                    closePrice = realtime.getClose();
+                    map.put("close", closePrice.setScale(decimal, RoundingMode.HALF_UP));
                 }
                 BeforeClose beforeClose = dataDBService.getBeforeClose(kline.getSymbol(), line, ts, realtime);
-                if (beforeClose != null) {
-                    if (beforeClose.getMaxClose() != null
-                            && beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
-                        high = beforeClose.getMaxClose();
-                    }
-                    if (beforeClose.getMinClose() != null
-                            && beforeClose.getMinClose().compareTo(BigDecimal.ZERO) > 0) {
-                        low = beforeClose.getMinClose();
-                    }
+                if (beforeClose != null
+                        && beforeClose.getMaxClose() != null && beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0
+                        && beforeClose.getMinClose() != null && beforeClose.getMinClose().compareTo(BigDecimal.ZERO) > 0) {
+                    periodHigh = periodHigh.max(beforeClose.getMaxClose());
+                    periodLow = periodLow.min(beforeClose.getMinClose());
                 }
-                map.put("high", high.setScale(decimal, RoundingMode.HALF_UP));
-                map.put("low", low.setScale(decimal, RoundingMode.HALF_UP));
+                if (closePrice != null && closePrice.compareTo(BigDecimal.ZERO) > 0) {
+                    periodHigh = periodHigh.max(closePrice);
+                    periodLow = periodLow.min(closePrice);
+                }
+                map.put("high", periodHigh.setScale(decimal, RoundingMode.HALF_UP));
+                map.put("low", periodLow.setScale(decimal, RoundingMode.HALF_UP));
             }
             list.add(map);
         }
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrderProfit.java b/trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrderProfit.java
new file mode 100644
index 0000000..118fca0
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/contract/domain/ContractOrderProfit.java
@@ -0,0 +1,34 @@
+package com.yami.trading.bean.contract.domain;
+
+import com.yami.trading.common.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 永续合约持仓盈亏缓存
+ */
+@Data
+public class ContractOrderProfit extends BaseEntity {
+    /**
+     * 收益
+     */
+    private BigDecimal profit;
+
+    /**
+     * 强平价格
+     */
+    private String forceClosePrice;
+
+    /**
+     * 平仓均价
+     */
+    private BigDecimal closeAvgPrice;
+
+    public BigDecimal getProfit() {
+        if (profit == null) {
+            return BigDecimal.ZERO;
+        }
+        return profit;
+    }
+}
diff --git a/trading-order-common/src/main/java/com/yami/trading/common/constants/ContractRedisKeys.java b/trading-order-common/src/main/java/com/yami/trading/common/constants/ContractRedisKeys.java
index da472f2..823f97e 100644
--- a/trading-order-common/src/main/java/com/yami/trading/common/constants/ContractRedisKeys.java
+++ b/trading-order-common/src/main/java/com/yami/trading/common/constants/ContractRedisKeys.java
@@ -2,29 +2,34 @@
 
 public class ContractRedisKeys {
 
-	/**
-	 * 永续合约,orderNo做key
-	 */
-	public final static String CONTRACT_ORDERNO = "CONTRACT_ORDERNO_";
-	
-	/**
-	 * 永续合约,查询订单map,partyid做key
-	 */
-	public final static String CONTRACT_SUBMITTED_ORDER_PARTY_ID = "CONTRACT_SUBMITTED_ORDER_PARTY_ID_";
+    /**
+     * 永续合约,orderNo做key
+     */
+    public final static String CONTRACT_ORDERNO = "CONTRACT_ORDERNO_";
 
-	/**
-	 * 永续合约,总资产,partyid做key
-	 */
-	public final static String CONTRACT_ASSETS_PARTY_ID = "CONTRACT_ASSETS_PARTY_ID_";
+    /**
+     * 永续利润
+     */
+    public final static String CONTRACT_PROFIT_V1 = "CONTRACT_PROFIT_V1";
 
-	/**
-	 * 永续合约,总保证金,partyid做key
-	 */
-	public final static String CONTRACT_ASSETS_DEPOSIT_PARTY_ID = "CONTRACT_ASSETS_DEPOSIT_PARTY_ID_";
+    /**
+     * 永续合约,查询订单map,partyid做key
+     */
+    public final static String CONTRACT_SUBMITTED_ORDER_PARTY_ID = "CONTRACT_SUBMITTED_ORDER_PARTY_ID_";
 
-	/**
-	 * 永续合约,总未实现盈利,partyid做key
-	 */
-	public final static String CONTRACT_ASSETS_PROFIT_PARTY_ID = "CONTRACT_ASSETS_PROFIT_PARTY_ID_";
-	
+    /**
+     * 永续合约,总资产,partyid做key
+     */
+    public final static String CONTRACT_ASSETS_PARTY_ID = "CONTRACT_ASSETS_PARTY_ID_";
+
+    /**
+     * 永续合约,总保证金,partyid做key
+     */
+    public final static String CONTRACT_ASSETS_DEPOSIT_PARTY_ID = "CONTRACT_ASSETS_DEPOSIT_PARTY_ID_";
+
+    /**
+     * 永续合约,总未实现盈利,partyid做key
+     */
+    public final static String CONTRACT_ASSETS_PROFIT_PARTY_ID = "CONTRACT_ASSETS_PROFIT_PARTY_ID_";
+
 }
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/AdjustmentValueServiceImpl.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/AdjustmentValueServiceImpl.java
index 9f6f207..c02ec1b 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/AdjustmentValueServiceImpl.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/AdjustmentValueServiceImpl.java
@@ -50,15 +50,17 @@
             } else {
                 AdjustmentValueCache.getCurrentValue().put(symbol, currentValue.add(value));
             }
-            // 马上扣除价格,避免因为数据没有拉取导致加不正确
-            realtime.setClose(realtime.getClose().add(value));
             /*
-             * 持久化缓存
+             * 持久化到品种表(重启后 InitHandle 会加载到 AdjustmentValueCache)
              */
             Item item = this.itemService.findBySymbol(symbol);
-            if (item.getAdjustmentValue().compareTo(AdjustmentValueCache.getCurrentValue().get(symbol)) != 0) {
-                item.setAdjustmentValue(AdjustmentValueCache.getCurrentValue().get(symbol));
-                itemService.saveOrUpdate(item);
+            BigDecimal cachedAdjust = AdjustmentValueCache.getCurrentValue().get(symbol);
+            if (item != null && cachedAdjust != null) {
+                BigDecimal itemAdjust = item.getAdjustmentValue();
+                if (itemAdjust == null || itemAdjust.compareTo(cachedAdjust) != 0) {
+                    item.setAdjustmentValue(cachedAdjust);
+                    itemService.saveOrUpdate(item);
+                }
             }
 
         } else {
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java
index bfe29df..9b42c5b 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/DataDBServiceImpl.java
@@ -119,10 +119,10 @@
             LocalDate yesterday = LocalDate.now(ZoneId.of("America/New_York")).minusDays(1);
 
             // 4. 判断是否为昨天
-             boolean isYesterday = tsDate.equals(yesterday);
-             if (isYesterday) {
-                 return realtime;
-             }
+            boolean isYesterday = tsDate.equals(yesterday);
+            if (isYesterday) {
+                return realtime;
+            }
         }
         // 没缓存重新保存
         // 计算当天0点的时间戳(毫秒级)
@@ -146,97 +146,61 @@
         BeforeClose beforeClose = (BeforeClose) redisTemplate.opsForValue().get(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line);
         //超出时间重新计算
         if (beforeClose == null || ts > beforeClose.getTs()) {
-            beforeClose = calcBeforeClose(symbol, line, ts);
-            if (beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
-                redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
-            }
-        }
-
-        if (realtime != null && realtime.getClose() != null) {
-            if (beforeClose.getMaxClose() == null || beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) <= 0) {
-                beforeClose.setMaxClose(realtime.getClose());
-            } else if (realtime.getClose().compareTo(beforeClose.getMaxClose()) > 0) {
-                beforeClose.setMaxClose(realtime.getClose());
-            }
-            if (beforeClose.getMinClose() == null || beforeClose.getMinClose().compareTo(BigDecimal.ZERO) <= 0) {
-                beforeClose.setMinClose(realtime.getClose());
-            } else if (realtime.getClose().compareTo(beforeClose.getMinClose()) < 0) {
-                beforeClose.setMinClose(realtime.getClose());
-            }
+            long currentTimeStamp = System.currentTimeMillis();
+            RequestDataHelper.set("symbol", symbol);
+            QueryWrapper<Realtime> queryWrapper = new QueryWrapper<Realtime>()
+                    .eq("symbol", symbol)
+                    .ge("ts", ts)
+                    .le("ts", currentTimeStamp)
+                    .select("MAX(close) as maxClose", "MIN(close) as minClose");
+            Map<String, Object> resultMap = realtimeService.getMap(queryWrapper);
+            RequestDataHelper.clear();
+            beforeClose = new BeforeClose();
+            BigDecimal maxClose = extractAggregateValue(resultMap, "maxClose");
+            BigDecimal minClose = extractAggregateValue(resultMap, "minClose");
+            mergeRealtimeIntoBeforeClose(beforeClose, maxClose, minClose, realtime);
+            beforeClose.setTs(ts);
+            redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
+        } else if (realtime != null) {
+            mergeRealtimeIntoBeforeClose(beforeClose, beforeClose.getMaxClose(), beforeClose.getMinClose(), realtime);
             redisTemplate.opsForValue().set(RedisKeys.REAL_TIME_BEFORE_CLOSE + symbol + line, beforeClose);
         }
         return beforeClose;
     }
 
-    private BeforeClose calcBeforeClose(String symbol, String line, Long ts) {
-        BeforeClose beforeClose = new BeforeClose();
-        beforeClose.setTs(ts);
-        int interval = sysparaService.find("data_interval").getInteger() / 1000;
-        if (interval <= 0) {
-            interval = 1;
-        }
-        HighLow highLow = null;
-        switch (line) {
-            case Kline.PERIOD_1MIN:
-                highLow = HighLowHandle.get(symbol, 60 / interval, interval);
-                break;
-            case Kline.PERIOD_5MIN:
-                highLow = HighLowHandle.get(symbol, (60 * 5) / interval, interval);
-                break;
-            case Kline.PERIOD_15MIN:
-                highLow = HighLowHandle.get(symbol, (60 * 15) / interval, interval);
-                break;
-            case Kline.PERIOD_30MIN:
-                highLow = HighLowHandle.get(symbol, (60 * 30) / interval, interval);
-                break;
-            case Kline.PERIOD_60MIN:
-                highLow = HighLowHandle.get(symbol, (60 * 60) / interval, interval);
-                break;
-            case Kline.PERIOD_4HOUR:
-                highLow = HighLowHandle.get(symbol, (60 * 60 * 4) / interval, interval);
-                break;
-            case Kline.PERIOD_1DAY:
-                highLow = HighLowHandle.get(symbol, (60 * 60 * 24) / interval, interval);
-                break;
-            case Kline.PERIOD_1WEEK:
-                highLow = HighLowHandle.getByDay(symbol, 7);
-                break;
-            case Kline.PERIOD_1MON:
-                highLow = HighLowHandle.getByDay(symbol, 30);
-                break;
-            default:
-                break;
-        }
-        if (highLow != null && highLow.getHigh() != null) {
-            beforeClose.setMaxClose(highLow.getHigh());
-        }
-        if (highLow != null && highLow.getLow() != null) {
-            beforeClose.setMinClose(highLow.getLow());
-        }
-        if (beforeClose.getMaxClose().compareTo(BigDecimal.ZERO) > 0) {
-            return beforeClose;
-        }
-        long currentTimeStamp = System.currentTimeMillis();
-        RequestDataHelper.set("symbol", symbol);
-        QueryWrapper<Realtime> queryWrapper = new QueryWrapper<Realtime>()
-                .eq("symbol", symbol)
-                .ge("ts", ts)
-                .le("ts", currentTimeStamp)
-                .select("MAX(CAST(close AS DECIMAL(10,4))) as maxClose",
-                        "MIN(CAST(close AS DECIMAL(10,4))) as minClose");
-        Map<String, Object> resultMap = realtimeService.getMap(queryWrapper);
-        RequestDataHelper.clear();
-        if (resultMap != null && !resultMap.isEmpty()) {
-            BigDecimal maxClose = convertToBigDecimal(resultMap.get("maxClose"));
-            BigDecimal minClose = convertToBigDecimal(resultMap.get("minClose"));
-            if (maxClose.compareTo(BigDecimal.ZERO) > 0) {
-                beforeClose.setMaxClose(maxClose);
+    private void mergeRealtimeIntoBeforeClose(BeforeClose beforeClose, BigDecimal maxClose, BigDecimal minClose, Realtime realtime) {
+        BigDecimal max = maxClose;
+        BigDecimal min = minClose;
+        if (realtime != null && realtime.getClose() != null) {
+            BigDecimal latest = realtime.getClose();
+            if (max == null || max.compareTo(BigDecimal.ZERO) <= 0) {
+                max = latest;
+            } else {
+                max = max.max(latest);
             }
-            if (minClose.compareTo(BigDecimal.ZERO) > 0) {
-                beforeClose.setMinClose(minClose);
+            if (min == null || min.compareTo(BigDecimal.ZERO) <= 0) {
+                min = latest;
+            } else {
+                min = min.min(latest);
             }
         }
-        return beforeClose;
+        beforeClose.setMaxClose(max == null ? BigDecimal.ZERO : max);
+        beforeClose.setMinClose(min == null ? BigDecimal.ZERO : min);
+    }
+
+    private BigDecimal extractAggregateValue(Map<String, Object> resultMap, String key) {
+        if (resultMap == null || resultMap.isEmpty()) {
+            return null;
+        }
+        if (resultMap.containsKey(key)) {
+            return convertToBigDecimal(resultMap.get(key));
+        }
+        for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
+            if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) {
+                return convertToBigDecimal(entry.getValue());
+            }
+        }
+        return null;
     }
 
     // 辅助方法:统一转换为BigDecimal,避免类型错误
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java
index 83f5b4b..112215e 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/KlineServiceImpl.java
@@ -29,9 +29,6 @@
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -183,165 +180,6 @@
 
 
     public Kline buildKline(String symbol, String line, String smallLevelLine, int nums) {
-        if (Kline.PERIOD_5DAY.equals(line)) {
-            return buildKlineLegacy(symbol, line, smallLevelLine, nums);
-        }
-        return buildInProgressKline(symbol, line, smallLevelLine);
-    }
-
-    /**
-     * 按当前周期起点拼接进行中的K线(ts 对齐到周期开盘时间,如 5 分钟线的 9:35)
-     */
-    private Kline buildInProgressKline(String symbol, String line, String smallLevelLine) {
-        try {
-            KlineTimeObject timeObject = DataCache.getKline(symbol, line);
-            if (timeObject == null) {
-                return null;
-            }
-            List<Kline> klineList = timeObject.getKline();
-            Item item = itemService.findBySymbol(symbol);
-            Kline latestSameLineKline = null;
-            if (klineList != null && !klineList.isEmpty()) {
-                latestSameLineKline = klineList.get(klineList.size() - 1);
-            } else if (item.getFake().equalsIgnoreCase("0")) {
-                return null;
-            }
-
-            Realtime latestRealtime = DataCache.getLatestRealTime(symbol);
-            if (latestRealtime == null) {
-                latestRealtime = DataCache.getRealtime(symbol);
-            }
-            if (latestRealtime == null || latestRealtime.getClose() == null) {
-                return null;
-            }
-
-            long currentPeriodTs = alignPeriodStartTs(line, latestRealtime.getTs());
-            if (latestSameLineKline != null && latestSameLineKline.getTs() != null
-                    && latestSameLineKline.getTs() > currentPeriodTs) {
-                return null;
-            }
-
-            KlineTimeObject smallObject = DataCache.getKline(symbol, smallLevelLine);
-            List<Kline> periodBars = new ArrayList<>();
-            if (smallObject != null && smallObject.getKline() != null) {
-                periodBars = smallObject.getKline().stream()
-                        .filter(k -> k.getTs() != null && k.getTs() >= currentPeriodTs)
-                        .collect(Collectors.toList());
-            }
-
-            Kline kline = new Kline();
-            kline.setSymbol(symbol);
-            kline.setPeriod(line);
-            kline.setTs(currentPeriodTs);
-
-            if (latestSameLineKline != null && latestSameLineKline.getTs() != null
-                    && latestSameLineKline.getTs() < currentPeriodTs) {
-                kline.setOpen(latestSameLineKline.getClose());
-            } else if (!periodBars.isEmpty() && periodBars.get(0).getOpen() != null) {
-                kline.setOpen(periodBars.get(0).getOpen());
-            } else if (latestRealtime.getOpen() != null) {
-                kline.setOpen(latestRealtime.getOpen());
-            } else {
-                kline.setOpen(latestRealtime.getClose());
-            }
-
-            if (!periodBars.isEmpty()) {
-                Double high = null;
-                Double low = null;
-                for (Kline bar : periodBars) {
-                    if (bar.getHigh() != null) {
-                        if (high == null || high <= bar.getHigh().doubleValue()) {
-                            high = bar.getHigh().doubleValue();
-                        }
-                    }
-                    if (bar.getLow() != null) {
-                        if (low == null || low >= bar.getLow().doubleValue()) {
-                            low = bar.getLow().doubleValue();
-                        }
-                    }
-                }
-                kline.setHigh(high == null ? latestRealtime.getClose() : new BigDecimal(high));
-                kline.setLow(low == null ? latestRealtime.getClose() : new BigDecimal(low));
-                kline.setClose(periodBars.get(periodBars.size() - 1).getClose());
-                kline.setVolume(periodBars.stream()
-                        .map(Kline::getVolume)
-                        .filter(Objects::nonNull)
-                        .reduce(BigDecimal.ZERO, BigDecimal::add));
-                kline.setAmount(periodBars.stream()
-                        .map(Kline::getAmount)
-                        .filter(Objects::nonNull)
-                        .reduce(BigDecimal.ZERO, BigDecimal::add));
-            } else {
-                kline.setHigh(latestRealtime.getClose());
-                kline.setLow(latestRealtime.getClose());
-                kline.setClose(latestRealtime.getClose());
-                kline.setVolume(latestRealtime.getVolume());
-                kline.setAmount(latestRealtime.getAmount());
-            }
-
-            kline.setClose(latestRealtime.getClose());
-            if (latestRealtime.getClose().compareTo(kline.getHigh()) > 0) {
-                kline.setHigh(latestRealtime.getClose());
-            }
-            if (latestRealtime.getClose().compareTo(kline.getLow()) < 0) {
-                kline.setLow(latestRealtime.getClose());
-            }
-
-            repairKline(kline);
-            if (kline.getOpen().compareTo(BigDecimal.ZERO) == 0 || kline.getClose().compareTo(BigDecimal.ZERO) == 0) {
-                return null;
-            }
-            return kline;
-        } catch (Exception e) {
-            logger.error("buildInProgressKline error: {}, {}", symbol, line, e);
-        }
-        return null;
-    }
-
-    /**
-     * 将时间戳对齐到K线周期起点(UTC,与火币 id 字段一致)
-     * 1week:周一 00:00 UTC;1mon:每月 1 日 00:00 UTC
-     */
-    private long alignPeriodStartTs(String line, long tsMillis) {
-        ZonedDateTime zdt = Instant.ofEpochMilli(tsMillis).atZone(ZoneOffset.UTC);
-        switch (line) {
-            case Kline.PERIOD_1MIN:
-                return zdt.withSecond(0).withNano(0).toInstant().toEpochMilli();
-            case Kline.PERIOD_5MIN: {
-                int minute = zdt.getMinute();
-                return zdt.withMinute(minute - minute % 5).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            }
-            case Kline.PERIOD_15MIN: {
-                int minute = zdt.getMinute();
-                return zdt.withMinute(minute - minute % 15).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            }
-            case Kline.PERIOD_30MIN: {
-                int minute = zdt.getMinute();
-                return zdt.withMinute(minute - minute % 30).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            }
-            case Kline.PERIOD_60MIN:
-                return zdt.withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            case Kline.PERIOD_1DAY:
-                return zdt.withHour(0).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            case Kline.PERIOD_1WEEK: {
-                zdt = zdt.withHour(0).withMinute(0).withSecond(0).withNano(0);
-                int dayOfWeek = zdt.getDayOfWeek().getValue();
-                return zdt.minusDays(dayOfWeek - 1L).toInstant().toEpochMilli();
-            }
-            case Kline.PERIOD_1MON:
-                return zdt.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            case Kline.PERIOD_2HOUR:
-                return zdt.withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            case Kline.PERIOD_4HOUR: {
-                int hour = zdt.getHour();
-                return zdt.withHour(hour - hour % 4).withMinute(0).withSecond(0).withNano(0).toInstant().toEpochMilli();
-            }
-            default:
-                return zdt.withSecond(0).withNano(0).toInstant().toEpochMilli();
-        }
-    }
-
-    private Kline buildKlineLegacy(String symbol, String line, String smallLevelLine, int nums) {
         try {
             // 取5分钟K线全部数据集合
             KlineTimeObject timeObject = DataCache.getKline(symbol, line);
@@ -369,31 +207,23 @@
             }
             // 1分钟K线最新的5条数据,上个层级最近的几条数据
             List<Kline> klineOneTop5 = new ArrayList<>(klineOne.subList(klineOne.size() - nums, klineOne.size()));
-            Kline realtimeKline = klineOneTop5.get(nums - 1);
+            Kline realtimeKline = klineOneTop5.get(klineOneTop5.size() - 1);
             if (realtimeKline == null) {
                 return null;
             }
-            if (latestSameLineKline != null && latestSameLineKline.getTs() > realtimeKline.getTs()) {
+            long periodTs = klineOneTop5.get(0).getTs();
+            if (latestSameLineKline != null && latestSameLineKline.getTs() >= periodTs) {
                 return null;
             }
             if (latestSameLineKline != null) {
                 long latestSameLineKlineTs = latestSameLineKline.getTs();
-                if (latestSameLineKlineTs == realtimeKline.getTs()) {
-                    klineOneTop5 = klineOne.stream()
-                            .filter(r -> r.getTs() >= latestSameLineKlineTs)
-                            .collect(Collectors.toList());
-                    if (klineOneTop5.size() > nums) {
-                        klineOneTop5 = new ArrayList<>(klineOneTop5.subList(klineOneTop5.size() - nums, klineOneTop5.size()));
-                    }
-                } else {
-                    klineOneTop5 = klineOneTop5.stream()
-                            .filter(r -> r.getTs() > latestSameLineKlineTs)
-                            .collect(Collectors.toList());
-                }
+                klineOneTop5 = klineOneTop5.stream().filter(r -> r.getTs() > latestSameLineKlineTs).collect(Collectors.toList());
             }
             if (klineOneTop5.isEmpty()) {
                 return null;
             }
+            periodTs = klineOneTop5.get(0).getTs();
+            realtimeKline = klineOneTop5.get(klineOneTop5.size() - 1);
 
             Double high = null;
             Double low = null;
@@ -408,7 +238,7 @@
 
             Kline kline = new Kline();
             kline.setSymbol(symbol);
-            kline.setTs(realtimeKline.getTs());
+            kline.setTs(periodTs);
             if (latestSameLineKline != null) {
                 kline.setOpen(latestSameLineKline.getClose());
             } else {
@@ -848,16 +678,11 @@
                 latestKilne = klineList.get(klineList.size() - 1);
             }
             Realtime realtime = realTimeList.get(realTimeList.size() - 1);
-            long currentMinuteTs = alignPeriodStartTs(Kline.PERIOD_1MIN, realtime.getTs());
-            if (latestKilne != null && latestKilne.getTs() != null && latestKilne.getTs() > currentMinuteTs) {
+            if (latestKilne != null && latestKilne.getTs() >= realtime.getTs()) {
                 return null;
             }
-            realTimeList = realTimeList.stream()
-                    .filter(r -> r.getTs() != null && r.getTs() >= currentMinuteTs)
-                    .collect(Collectors.toList());
-            if (realTimeList.isEmpty()) {
-                realTimeList = Collections.singletonList(realtime);
-            }
+            long lastKlineTs = latestKilne.getTs();
+            realTimeList = realTimeList.stream().filter(r -> r.getTs() > lastKlineTs).collect(Collectors.toList());
             Double high = null;
             Double low = null;
             for (Realtime realTime : realTimeList) {
@@ -872,11 +697,11 @@
             // 保存K线到数据库
             Kline kline = new Kline();
             kline.setSymbol(symbol);
-            kline.setTs(currentMinuteTs);
-            if (latestKilne != null && latestKilne.getTs() != null && latestKilne.getTs() < currentMinuteTs) {
+            kline.setTs(realtime.getTs());
+            if (latestKilne != null) {
                 kline.setOpen(latestKilne.getClose());
             } else {
-                kline.setOpen(realTimeList.get(0).getOpen() != null ? realTimeList.get(0).getOpen() : realtime.getClose());
+                kline.setOpen(realTimeList.get(0).getOpen());
             }
             kline.setHigh(new BigDecimal(high));
             kline.setLow(new BigDecimal(low));
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java
index c97d876..0bca53d 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/internal/RemoteDataServiceImpl.java
@@ -11,6 +11,7 @@
 import com.yami.trading.bean.data.domain.Depth;
 import com.yami.trading.bean.data.domain.Trade;
 import com.yami.trading.bean.data.domain.Trend;
+import com.yami.trading.huobi.hobi.HobiDataService;
 import com.yami.trading.service.data.DataService;
 import com.yami.trading.service.item.ItemService;
 import com.yami.trading.service.syspara.SysparaService;
@@ -22,6 +23,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 
 @Service("remoteDataService")
 public class RemoteDataServiceImpl implements DataService {
@@ -32,6 +34,8 @@
     private SysparaService sysparaService;
     @Autowired
     private KlineService klineService;
+    @Autowired
+    private HobiDataService hobiDataService;
 
     @Override
     public List<Realtime> realtime(String symbols) {
@@ -73,6 +77,10 @@
 
     @Override
     public List<Kline> kline(String symbol, String line) {
+        Item bySymbol = itemService.findBySymbol(symbol);
+        if(Item.cryptos.equals(bySymbol.getType())){
+            return klineCryptos(symbol, line);
+        }
         KlineTimeObject timeObject = DataCache.getKline(symbol, line);
         List<Kline> list = new ArrayList<Kline>();
         if (timeObject != null) {
@@ -81,7 +89,7 @@
         List<Kline> list_clone = new ArrayList<Kline>();
         try {
             for (int i = 0; i < list.size(); i++) {
-                if (list.get(i) == null) {
+                if(list.get(i) == null){
                     continue;
                 }
                 Kline kline = (Kline) list.get(i).clone();
@@ -91,62 +99,116 @@
             e.printStackTrace();
         }
 
+        appendInProgressKline(list_clone, symbol, line);
+        // 按时间升序
+        Collections.sort(list_clone);
+        return list_clone;
+
+    }
+
+    public List<Kline> klineCryptos(String symbol, String line) {
+        KlineTimeObject timeObject = DataCache.getKline(symbol, line);
+        List<Kline> list = new ArrayList<Kline>();
+        if (timeObject != null) {
+            list = timeObject.getKline();
+        }
+        List<Kline> list_clone = new ArrayList<Kline>();
+        try {
+            for (int i = 0; i < list.size(); i++) {
+                Kline kline = (Kline) list.get(i).clone();
+                list_clone.add(kline);
+            }
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+        }
+        // 火币 K 线倒序返回,缓存仅含已收盘 K 线;补拉最新一根未收盘 K 线
+        boolean mergedFromHobi = false;
+        Item item = itemService.findBySymbol(symbol);
+        if (item != null) {
+            List<Kline> latestKlines = hobiDataService.kline(item.getSymbol(), line, 1);
+            if (!latestKlines.isEmpty()) {
+                try {
+                    Kline latest = (Kline) latestKlines.get(0).clone();
+                    latest.setSymbol(symbol);
+                    latest.setPeriod(line);
+                    mergeLatestKline(list_clone, latest);
+                    mergedFromHobi = true;
+                } catch (CloneNotSupportedException e) {
+                    Kline latest = latestKlines.get(0);
+                    latest.setSymbol(symbol);
+                    latest.setPeriod(line);
+                    mergeLatestKline(list_clone, latest);
+                    mergedFromHobi = true;
+                }
+            }
+        }
+        if (!mergedFromHobi) {
+            appendInProgressKline(list_clone, symbol, line);
+        }
+        Collections.sort(list_clone); // 按时间升序
+        return list_clone;
+
+    }
+
+    /**
+     * 合并最新 K 线:同时间戳则更新(未收盘 K 线持续变化),否则追加。
+     */
+    private void mergeLatestKline(List<Kline> list, Kline latest) {
+        if (latest == null || latest.getTs() == null) {
+            return;
+        }
+        if (list.isEmpty()) {
+            list.add(latest);
+            return;
+        }
+        Kline last = list.get(list.size() - 1);
+        if (Objects.equals(last.getTs(), latest.getTs())) {
+            list.set(list.size() - 1, latest);
+        } else if (last.getTs() < latest.getTs()) {
+            list.add(latest);
+        }
+    }
+
+    /**
+     * 基于实时行情聚合当前周期未收盘 K 线(非加密货币等场景兜底)。
+     */
+    private void appendInProgressKline(List<Kline> list, String symbol, String line) {
         Realtime realtime = DataCache.getLatestRealTime(symbol);
         if (realtime == null) {
             realtime = DataCache.getRealtime(symbol);
         }
-        if (realtime != null) {
-            appendOrReplaceKline(list_clone, buildCurrentKline(realtime, line));
-        }
-        Collections.sort(list_clone);
-        return list_clone;
-    }
-
-    private Kline buildCurrentKline(Realtime realtime, String line) {
-        if (KlineConstant.PERIOD_1MIN.equals(line)) {
-            return klineService.bulidKline1Minute(realtime, KlineConstant.PERIOD_1MIN);
-        } else if (KlineConstant.PERIOD_5MIN.equals(line)) {
-            return klineService.bulidKline5Minute(realtime, KlineConstant.PERIOD_5MIN);
-        } else if (KlineConstant.PERIOD_15MIN.equals(line)) {
-            return klineService.bulidKline15Minute(realtime, KlineConstant.PERIOD_15MIN);
-        } else if (KlineConstant.PERIOD_30MIN.equals(line)) {
-            return klineService.bulidKline30Minute(realtime, KlineConstant.PERIOD_30MIN);
-        } else if (KlineConstant.PERIOD_60MIN.equals(line)) {
-            return klineService.bulidKline60Minute(realtime, KlineConstant.PERIOD_60MIN);
-        } else if (KlineConstant.PERIOD_4HOUR.equals(line)) {
-            return klineService.bulidKline4Hour(realtime, KlineConstant.PERIOD_4HOUR);
-        } else if (KlineConstant.PERIOD_1DAY.equals(line)) {
-            return klineService.bulidKline1Day(realtime, KlineConstant.PERIOD_1DAY);
-        } else if (KlineConstant.PERIOD_5DAY.equals(line)) {
-            return klineService.bulidKline5Day(realtime, KlineConstant.PERIOD_5DAY);
-        } else if (KlineConstant.PERIOD_1WEEK.equals(line)) {
-            return klineService.bulidKline1Week(realtime, KlineConstant.PERIOD_1WEEK);
-        } else if (KlineConstant.PERIOD_1MON.equals(line)) {
-            return klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_1MON);
-        } else if (KlineConstant.PERIOD_QUARTER.equals(line)) {
-            return klineService.bulidKlineQuarter(realtime, KlineConstant.PERIOD_QUARTER);
-        } else if (KlineConstant.PERIOD_YEAR.equals(line)) {
-            return klineService.bulidKlineYear(realtime, KlineConstant.PERIOD_YEAR);
-        }
-        return null;
-    }
-
-    public List<Kline> klineCryptos(String symbol, String line) {
-        return kline(symbol, line);
-    }
-
-    private void appendOrReplaceKline(List<Kline> list, Kline kline) {
-        if (kline == null) {
+        if (realtime == null) {
             return;
         }
-        for (int i = list.size() - 1; i >= 0; i--) {
-            Kline existing = list.get(i);
-            if (existing.getTs() != null && existing.getTs().equals(kline.getTs())) {
-                list.set(i, kline);
-                return;
-            }
+        Kline kline = null;
+        if (KlineConstant.PERIOD_1MIN.equals(line)) {
+            kline = klineService.bulidKline1Minute(realtime, KlineConstant.PERIOD_1MIN);
+        } else if (KlineConstant.PERIOD_5MIN.equals(line)) {
+            kline = klineService.bulidKline5Minute(realtime, KlineConstant.PERIOD_5MIN);
+        } else if (KlineConstant.PERIOD_15MIN.equals(line)) {
+            kline = klineService.bulidKline15Minute(realtime, KlineConstant.PERIOD_15MIN);
+        } else if (KlineConstant.PERIOD_30MIN.equals(line)) {
+            kline = klineService.bulidKline30Minute(realtime, KlineConstant.PERIOD_30MIN);
+        } else if (KlineConstant.PERIOD_60MIN.equals(line)) {
+            kline = klineService.bulidKline60Minute(realtime, KlineConstant.PERIOD_60MIN);
+        } else if (KlineConstant.PERIOD_4HOUR.equals(line)) {
+            kline = klineService.bulidKline4Hour(realtime, KlineConstant.PERIOD_4HOUR);
+        } else if (KlineConstant.PERIOD_1DAY.equals(line)) {
+            kline = klineService.bulidKline1Day(realtime, KlineConstant.PERIOD_1DAY);
+        } else if (KlineConstant.PERIOD_5DAY.equals(line)) {
+            kline = klineService.bulidKline5Day(realtime, KlineConstant.PERIOD_5DAY);
+        } else if (KlineConstant.PERIOD_1WEEK.equals(line)) {
+            kline = klineService.bulidKline1Week(realtime, KlineConstant.PERIOD_1WEEK);
+        } else if (KlineConstant.PERIOD_1MON.equals(line)) {
+            kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_1MON);
+        } else if (KlineConstant.PERIOD_QUARTER.equals(line)) {
+            kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_QUARTER);
+        } else if (KlineConstant.PERIOD_YEAR.equals(line)) {
+            kline = klineService.bulidKline1Mon(realtime, KlineConstant.PERIOD_YEAR);
         }
-        list.add(kline);
+        if (kline != null) {
+            mergeLatestKline(list, kline);
+        }
     }
 
     @Override
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/job/AbstractGetDataJob.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/job/AbstractGetDataJob.java
index d10d3e9..b4fdac9 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/job/AbstractGetDataJob.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/data/job/AbstractGetDataJob.java
@@ -94,20 +94,21 @@
                         List<BigDecimal> adjustments = AdjustmentValueCache.getPreAllocatedAdjustments().get(symbol);
                         Integer currentIndex = AdjustmentValueCache.getCurrentAdjustmentIndex().get(symbol);
 
-                        // 首次执行:生成含正负值的调整序列
+                        // 首次执行:生成调整序列(小幅调整用等额分步,确保合计等于目标值)
                         if (adjustments == null || currentIndex == null) {
-                            //分几段执行
-                            int nums = Math.max(10, frequency / 10);
-                            List<BigDecimal> result = RandomNumbersGenerator.generateNumbers(delayValue.getValue(), nums, decimal + 4);
-                            for (int i = 0; i < result.size(); i++) {
-                                if (adjustments == null) {
-                                    adjustments = new ArrayList<>();
+                            if (delayValue.getValue().abs().compareTo(new BigDecimal("2")) <= 0) {
+                                adjustments = buildEqualAdjustments(delayValue.getValue(), frequency, decimal);
+                            } else {
+                                int nums = Math.max(10, frequency / 10);
+                                List<BigDecimal> result = RandomNumbersGenerator.generateNumbers(delayValue.getValue(), nums, decimal + 4);
+                                adjustments = new ArrayList<>();
+                                for (int i = 0; i < result.size(); i++) {
+                                    int count = frequency / nums;
+                                    if (i == result.size() - 1) {
+                                        count += frequency % nums;
+                                    }
+                                    adjustments.addAll(generateRandomAdjustments(result.get(i), count, decimal));
                                 }
-                                int count = frequency / nums;
-                                if (i == result.size() - 1) {
-                                    count += frequency % nums;
-                                }
-                                adjustments.addAll(generateRandomAdjustments(result.get(i), count, decimal));
                             }
                             currentIndex = 0;
                             AdjustmentValueCache.getPreAllocatedAdjustments().put(symbol, adjustments);
@@ -208,6 +209,7 @@
         if (low != null) {
             realtime.setLow(BigDecimal.valueOf(low));
         }
+        DataCache.putRealtime(symbol, realtime);
         this.dataDBService.saveAsyn(realtime);
     }
 
@@ -218,6 +220,26 @@
         AdjustmentValueCache.getFrequency().remove(symbol);
     }
 
+    /**
+     * 等额分步,保证各步相加严格等于 totalValue(适用于管理员小幅调价,如 -0.1)。
+     */
+    private List<BigDecimal> buildEqualAdjustments(BigDecimal totalValue, int count, int decimal) {
+        List<BigDecimal> adjustments = new ArrayList<>(Math.max(1, count));
+        if (count <= 1) {
+            adjustments.add(totalValue.setScale(decimal, RoundingMode.HALF_UP));
+            return adjustments;
+        }
+        BigDecimal per = totalValue.divide(BigDecimal.valueOf(count), decimal + 4, RoundingMode.HALF_UP);
+        BigDecimal sum = BigDecimal.ZERO;
+        for (int i = 0; i < count - 1; i++) {
+            BigDecimal step = per.setScale(decimal, RoundingMode.HALF_UP);
+            adjustments.add(step);
+            sum = sum.add(step);
+        }
+        adjustments.add(totalValue.subtract(sum).setScale(decimal, RoundingMode.HALF_UP));
+        return adjustments;
+    }
+
     private List<BigDecimal> generateRandomAdjustments(BigDecimal totalValue, int count, int decimal) {
         List<BigDecimal> adjustments = new ArrayList<>(count);
         BigDecimal sum = BigDecimal.ZERO; // 整体累积和
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
index 03b1e32..2d12389 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractApplyOrderService.java
@@ -237,10 +237,10 @@
         }
 
         Wallet wallet = this.walletService.findByUserId(order.getPartyId());
-        BigDecimal amountBefore = wallet.getMoney();
-        BigDecimal totalAmountCost = order.getDeposit().add(order.getFee());
+        BigDecimal totalAmountCost = order.getDeposit().add(order.getFee() != null ? order.getFee() : BigDecimal.ZERO);
+        BigDecimal available = getAvailableOpenMargin(order.getPartyId(), null);
 
-        if (amountBefore.compareTo(totalAmountCost) < 0) {
+        if (available.compareTo(totalAmountCost) < 0) {
             throw new YamiShopBindException("not sufficient funds");
         }
 
@@ -255,6 +255,48 @@
             );
         }
         save(order);
+
+        // 市价单同步成交,避免异步撮合失败时长期挂单
+        if (ContractApplyOrder.ORDER_PRICE_TYPE_OPPONENT.equals(order.getOrderPriceType())) {
+            executeOpenImmediately(order);
+        }
+    }
+
+    /**
+     * 市价开仓立即撮合;失败则向上抛出,由事务回滚委托单。
+     */
+    private void executeOpenImmediately(ContractApplyOrder order) {
+        List<Realtime> realtimes = dataService.realtime(order.getSymbol());
+        if (CollectionUtil.isEmpty(realtimes)) {
+            return;
+        }
+        contractOrderService.saveOpen(order, realtimes.get(0));
+    }
+
+    /**
+     * 可用于新开仓的余额(双向持仓下多空各自占用保证金,需扣除未成交市价委托预留额)。
+     *
+     * @param excludeOrderNo 正在成交的委托单号,不计入预留
+     */
+    public BigDecimal getAvailableOpenMargin(String partyId, String excludeOrderNo) {
+        Wallet wallet = walletService.findByUserId(partyId);
+        if (wallet == null || wallet.getMoney() == null) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal available = wallet.getMoney();
+        List<ContractApplyOrder> pendingOpens = findSubmitted(partyId, null, ContractApplyOrder.OFFSET_OPEN, null);
+        for (ContractApplyOrder pending : pendingOpens) {
+            if (excludeOrderNo != null && excludeOrderNo.equals(pending.getOrderNo())) {
+                continue;
+            }
+            if (!ContractApplyOrder.ORDER_PRICE_TYPE_OPPONENT.equals(pending.getOrderPriceType())) {
+                continue;
+            }
+            BigDecimal deposit = pending.getDeposit() != null ? pending.getDeposit() : BigDecimal.ZERO;
+            BigDecimal fee = pending.getFee() != null ? pending.getFee() : BigDecimal.ZERO;
+            available = available.subtract(deposit).subtract(fee);
+        }
+        return available.max(BigDecimal.ZERO);
     }
 
     /**
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationService.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationService.java
index cf8a4af..fb6d63d 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationService.java
@@ -10,13 +10,23 @@
  */
 public interface ContractOrderCalculationService {
 
-	/*
-	 * 订单盈亏计算
-	 */
-	public void saveCalculation(String order_no, List<ContractOrder> partyContractOrders);
-	
-	public void setOrder_close_line(BigDecimal order_close_line);
+    /*
+     * 订单盈亏计算
+     */
+    public void saveCalculation(String order_no, List<ContractOrder> partyContractOrders);
 
-	public void setOrder_close_line_type(int order_close_line_type);
+    public void setOrder_close_line(BigDecimal order_close_line);
+
+    public void setOrder_close_line_type(int order_close_line_type);
+
+    /**
+     * 按与 settle 相同的规则计算强平价格(全仓/逐仓由 order_close_line_type 决定)
+     */
+    String calculateForceClosePriceForOrder(ContractOrder order);
+
+    /**
+     * 按最新行情刷新持仓未实现盈亏(价差盈亏 + 资金费)
+     */
+    void refreshMarkPriceProfit(ContractOrder order);
 
 }
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java
index 2d1416c..f9afbb6 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderCalculationServiceImpl.java
@@ -1,15 +1,12 @@
 package com.yami.trading.service.contract;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.yami.trading.bean.contract.domain.ContractOrder;
+import com.yami.trading.bean.contract.domain.ContractOrderProfit;
 import com.yami.trading.bean.data.domain.Realtime;
 import com.yami.trading.bean.item.domain.Item;
 import com.yami.trading.bean.model.Wallet;
-import com.yami.trading.common.util.ThreadUtils;
-import com.yami.trading.service.StrongLevelCalculationService;
 import com.yami.trading.service.WalletService;
 import com.yami.trading.service.data.DataService;
-import com.yami.trading.service.impl.StrongLevelCalculationServiceImpl;
 import com.yami.trading.service.item.ItemService;
 import com.yami.trading.service.syspara.SysparaService;
 import lombok.extern.slf4j.Slf4j;
@@ -20,8 +17,9 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.util.Date;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 
 @Slf4j
 @Service
@@ -45,9 +43,6 @@
     @Autowired
     private WalletService walletService;
 
-    @Autowired
-    private StrongLevelCalculationService strongLevelCalculationService;
-
     private SysparaService sysparaService;
 
     public void saveCalculation(String order_no, List<ContractOrder> partyContractOrders) {
@@ -67,6 +62,7 @@
             Realtime realtime = list.get(0);
 
             BigDecimal close = realtime.getClose();
+            settle(order, "watch", close, partyContractOrders);
 
             BigDecimal add = order.getTradeAvgPrice().add(order.getPips());
             BigDecimal subtract = order.getTradeAvgPrice().subtract(order.getPips());
@@ -101,101 +97,14 @@
     }
 
     /**
-     * 盈亏计算   收益=(平仓均价-开仓均价)*面值*张数
-     *
-     * USDT保证金合约做多:收益=(平仓均价-开仓均价)*面值*张数
-     * USDT保证金合约做空:收益=(开仓均价-平仓均价)*面值*张数
-     * 以BTC为例,BTC一张合约面值为0.01BTC,在价格19000的时候,开了10张多单。当价格涨到20000的时候,小明的收益=(20000-19000)*0.01*10=100USDT
-     * 币本位合约:
-     * 做多:收益=面值*张数/开仓价-面值*张数/平仓价
-     * 做空:收益=面值*张数/平仓价-面值*张数/开仓价
-     * 以BTC为例,BTC一张合约面值为100美元,小明在价格20000的时候,开了5张空单。当价格下跌到19000的时候,小明的收益=100*5/19000-100*5/20000=0.
+     * 盈亏计算
      *
      * @param profit_loss  profit 盈 loss亏
      * @param currentPrice 当前点位
      */
     public void settle(ContractOrder order, String profit_loss, BigDecimal currentPrice, List<ContractOrder> partyContractOrders) {
 
-        Item item = itemService.findBySymbol(order.getSymbol());
-
-
-        if(null != order.getProfitLossRatio() && order.getProfitLossRatio() > 0){//根据后台设置的盈亏比来
-            order.setProfit(order.getDepositOpen().multiply(new BigDecimal((order.getProfitLossRatio()/100))).setScale(2, RoundingMode.DOWN));
-        }else{
-            /*
-             * 根据偏 差点数和手数算出盈亏金额
-             */
-            /**
-             * 偏差点位
-             */
-            BigDecimal point = currentPrice.subtract(order.getTradeAvgPrice());
-            BigDecimal amount = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);
-            if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
-                order.setProfit(amount);
-            } else{
-                order.setProfit(amount.negate());
-            }
-        }
-
-        double faceValue = 0.01; // 合约面值(固定面值不能调整)
-        double maintenanceMarginRate = 0.004; // 维持保证金率(固定不变)
-
-
-        /**
-         * 全仓收益加入保证金计算
-         */
-        BigDecimal earnings;
-
-        if (order.getLocationType() == 1) {
-            earnings = BigDecimal.ZERO;
-
-            // 统计非当前订单的其他收益
-            /*List<ContractOrder> list = contractOrderService.list(new LambdaQueryWrapper<>(ContractOrder.class)
-                    .eq(ContractOrder::getState, ContractOrder.STATE_SUBMITTED)
-                    .eq(ContractOrder::getPartyId, order.getPartyId())
-                    .ne(ContractOrder::getOrderNo, order.getOrderNo())
-            );
-
-            // 提前计算 currentPrice 与 order.getTradeAvgPrice() 的差值,避免重复计算
-            BigDecimal priceDifference = currentPrice.subtract(order.getTradeAvgPrice());
-
-            // 计算所有订单的收益
-            for (ContractOrder contractOrder : list) {
-                BigDecimal profit = priceDifference
-                        .multiply(new BigDecimal("0.01"))
-                        .multiply(contractOrder.getVolumeOpen())
-                        .setScale(4, RoundingMode.DOWN);
-
-                earnings = earnings.add(profit);  // 累加收益
-            }*/
-
-            // 获取当前账户余额并加到收益中
-            //Map<String, Object> moneyAll = walletService.getMoneyAll(order.getPartyId());
-
-            Wallet wallet = walletService.saveWalletByPartyId(order.getPartyId());
-            earnings = earnings.add(wallet.getMoney());
-            earnings = earnings.add(order.getDepositOpen());
-        } else {
-            // 如果不符合条件,直接使用 order.getDepositOpen() 作为收益
-            earnings = order.getDepositOpen().add(order.getAddDepositOpen());
-        }
-
-        if(ContractOrder.DIRECTION_BUY.equals(order.getDirection())){
-            double forceClosePrice = strongLevelCalculationService.calculateLiquidationPrice(earnings.doubleValue(),
-                    faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue()
-                    , maintenanceMarginRate, item.getUnitFee().doubleValue());
-            order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString());
-        }else{
-            double forceClosePrice = strongLevelCalculationService.calculateEmptyLiquidationPrice(earnings.doubleValue(),
-                    faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue()
-                    , maintenanceMarginRate, item.getUnitFee().doubleValue());
-            order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString());
-        }
-        /**
-         * 多次平仓价格不对,后续修
-         */
-        order.setCloseAvgPrice(currentPrice);
-        this.contractOrderService.updateByIdBuffer(order);
+        applyMarkPriceToOrder(order, currentPrice);
 
         /**
          * 止盈价
@@ -206,7 +115,7 @@
              * 买涨
              */
             if (currentPrice.compareTo(profitStop) >= 0) {
-                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),null);
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), null);
                 return;
             }
         } else if (profitStop != null && profitStop.compareTo(BigDecimal.ZERO) > 0
@@ -215,7 +124,7 @@
              * 买跌
              */
             if (currentPrice.compareTo(profitStop) <= 0) {
-                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),null);
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), null);
                 return;
             }
         }
@@ -230,7 +139,7 @@
              * 买涨
              */
             if (currentPrice.compareTo(loss_stop) <= 0) {
-                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),null);
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), null);
                 return;
 
             }
@@ -240,88 +149,164 @@
              */
 
             if (currentPrice.compareTo(loss_stop) >= 0) {
-                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),null);
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), null);
                 return;
             }
         }
-        /**
-         * 强平计算
-         */
-        //重新计算强平
-        BigDecimal forceClosePrice = new BigDecimal(order.getForceClosePrice());
-        //达到强平价
-        if ((ContractOrder.DIRECTION_BUY.equals(order.getDirection()) && currentPrice.compareTo(new BigDecimal(order.getForceClosePrice())) <= 0)
-                || (ContractOrder.DIRECTION_SELL.equals(order.getDirection()) && currentPrice.compareTo(new BigDecimal(order.getForceClosePrice())) >= 0)) {
-            BigDecimal point = forceClosePrice.subtract(order.getTradeAvgPrice());
-            BigDecimal amount = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);
-            if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
-                order.setProfit(amount);
-            } else{
-                order.setProfit(amount.negate());
-            }
-            //强平利润固定-100%
-            order.setProfit(order.getDepositOpen().add(order.getAddDepositOpen()).negate());
-            //全仓强平利润+账户余额
-            if (order.getLocationType() == 1) {
-                Wallet wallet = this.walletService.findByUserId(order.getPartyId());
-                order.setProfit(order.getProfit().subtract(wallet.getMoney()));
-            }
-        }
+        ContractOrderProfit cp = contractOrderService.getCacheProfit(order.getUuid());
+        BigDecimal profit1 = cp != null ? cp.getProfit() : defaultZero(order.getProfit());
+        order.setForceClosePrice(calculateForceClosePriceForOrder(order));
         this.contractOrderService.updateByIdBuffer(order);
-        //判断是全仓还是逐仓
-        if (order.getLocationType() == 1) {
-//            /**
-//             * 收益
-//             */
-//            BigDecimal profit = BigDecimal.ZERO;
-//
-//            List<ContractOrder> list = partyContractOrders;
-//            for (int i = 0; i < list.size(); i++) {
-//                ContractOrder close_line = list.get(i);
-//                profit = profit.add(close_line.getProfit()).add(close_line.getDeposit());
-//            }
-
-
-//            Wallet wallet = this.walletService.findByUserId(order.getPartyId().toString());
-            //this.contractOrderService.updateByIdBuffer(order);
-
-//            if (profit.add(wallet.getMoney()).compareTo(BigDecimal.ZERO) <= 0) {
-            //判断买涨还是买跌"buy":买(多) "sell":卖(空)
-            if(order.getDirection().equals("buy")){
-                if (currentPrice.compareTo(new BigDecimal(order.getForceClosePrice())) <= 0) {//达到强平价
-                    /**
-                     * 触发全仓强平
-                     */
-                    log.info("------------------currentPrice-------------:"+currentPrice);
-                    log.info("------------------order.getForceClosePrice()-------------"+order.getForceClosePrice());
-                    log.info("------------------开多强平-------------");
-                    this.contractOrderService.allClose(order.getPartyId());
+        if (order_close_line_type == 1) {
+            Wallet wallet = this.walletService.findByUserId(order.getPartyId().toString());
+            List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
+            BigDecimal totalEquity = defaultZero(wallet.getMoney());
+            for (ContractOrder contractOrder : list) {
+                if (ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())) {
+                    contractOrderService.wrapProfit(contractOrder);
                 }
-            }else{
-                if (currentPrice.compareTo(new BigDecimal(order.getForceClosePrice()))>= 0) {//达到强平价
-                    /**
-                     * 触发全仓强平
-                     */
-                    log.info("------------------currentPrice-------------:"+currentPrice);
-                    log.info("------------------order.getForceClosePrice()-------------"+order.getForceClosePrice());
-                    log.info("------------------开空强平-------------");
-                    this.contractOrderService.allClose(order.getPartyId());
+                totalEquity = totalEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
+            }
 
-                }
+            if (totalEquity.compareTo(BigDecimal.ZERO) <= 0) {
+                /**
+                 * 触发全仓强平
+                 */
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), "强平");
+
             }
         } else {
-            if(order.getDirection().equals("buy")){
-                if (currentPrice.compareTo(new BigDecimal(order.getForceClosePrice())) <= 0) {//达到强平价
-                    this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),"强平");
-                }
-            }else{
-                if (currentPrice.compareTo(new BigDecimal(order.getForceClosePrice())) >= 0) {//达到强平价
-                    this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(),"强平");
-                }
+            if (profit1.compareTo(BigDecimal.ZERO) >= 0 || profit1.abs().compareTo(new BigDecimal("0.000001")) < 0) {
+                return;
             }
+            BigDecimal divide = order.getDeposit().divide(profit1.abs(), 10, RoundingMode.HALF_UP);
+            if (divide.compareTo(order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP)) <= 0) {
+                /**
+                 * 低于系统默认平仓线,进行强平
+                 */
+                this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo(), "强平");
+            }
+        }
+    }
 
+    @Override
+    public String calculateForceClosePriceForOrder(ContractOrder order) {
+        if (order == null || defaultZero(order.getVolume()).compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO.toPlainString();
+        }
+        BigDecimal forceClose;
+        if (order_close_line_type == 1) {
+            Wallet wallet = this.walletService.findByUserId(order.getPartyId().toString());
+            if (wallet == null) {
+                forceClose = calculateType2ForceClosePrice(order);
+            } else {
+                forceClose = calculateType1ForceClosePrice(order, wallet);
+            }
+        } else {
+            forceClose = calculateType2ForceClosePrice(order);
+        }
+        if (forceClose.compareTo(BigDecimal.ZERO) < 0) {
+            forceClose = BigDecimal.ZERO;
+        }
+        Integer decimal = itemService.getDecimal(order.getSymbol());
+        return forceClose.setScale(decimal, RoundingMode.HALF_UP).toPlainString();
+    }
+
+    private BigDecimal defaultZero(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
+    }
+
+    /**
+     * 合约张数对应标的数量系数(每张面值,与 countSheets 一致,默认 0.01)
+     */
+    private BigDecimal getContractFaceValue(ContractOrder order) {
+        Item item = itemService.findBySymbol(order.getSymbol());
+        if (item != null && item.getFaceValue() > 0) {
+            return BigDecimal.valueOf(item.getFaceValue());
+        }
+        return new BigDecimal("0.01");
+    }
+
+    /**
+     * 每张张数对应的价格敏感度:盈亏/强平 = 价差 × volume × faceValue
+     */
+    private BigDecimal getVolumePriceFactor(ContractOrder order) {
+        return defaultZero(order.getVolume()).multiply(getContractFaceValue(order));
+    }
+
+    private BigDecimal calculateType1ForceClosePrice(ContractOrder order, Wallet wallet) {
+        BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
+        BigDecimal volumeFactor = getVolumePriceFactor(order);
+        if (volumeFactor.compareTo(BigDecimal.ZERO) <= 0 || tradeAvgPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
         }
 
+        List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
+        BigDecimal otherEquity = BigDecimal.ZERO;
+        for (ContractOrder contractOrder : list) {
+            if (contractOrder == null) {
+                continue;
+            }
+            if (ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())) {
+                contractOrderService.wrapProfit(contractOrder);
+            }
+            if (Objects.equals(order.getUuid(), contractOrder.getUuid())) {
+                continue;
+            }
+            otherEquity = otherEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
+        }
+
+        BigDecimal baseEquity = defaultZero(wallet != null ? wallet.getMoney() : null)
+                .add(otherEquity)
+                .add(defaultZero(order.getDeposit()));
+        BigDecimal priceOffset = baseEquity.divide(volumeFactor, 10, RoundingMode.HALF_UP);
+        if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
+            return tradeAvgPrice.subtract(priceOffset);
+        }
+        return tradeAvgPrice.add(priceOffset);
+    }
+
+    private BigDecimal calculateType2ForceClosePrice(ContractOrder order) {
+        BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
+        BigDecimal volumeFactor = getVolumePriceFactor(order);
+        if (volumeFactor.compareTo(BigDecimal.ZERO) <= 0 || tradeAvgPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal thresholdRatio = order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP);
+        if (thresholdRatio.compareTo(BigDecimal.ZERO) <= 0) {
+            return tradeAvgPrice;
+        }
+        BigDecimal availableDeposit = defaultZero(order.getDeposit());
+        if (availableDeposit.compareTo(BigDecimal.ZERO) <= 0) {
+            return tradeAvgPrice;
+        }
+        BigDecimal requiredLoss = availableDeposit.divide(thresholdRatio, 10, RoundingMode.HALF_UP);
+        BigDecimal priceOffset = requiredLoss.divide(volumeFactor, 10, RoundingMode.HALF_UP);
+        if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
+            return tradeAvgPrice.subtract(priceOffset);
+        }
+        return tradeAvgPrice.add(priceOffset);
+    }
+
+    private void applyMarkPriceToOrder(ContractOrder order, BigDecimal currentPrice) {
+        BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
+        if (tradeAvgPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        BigDecimal volumeFactor = getVolumePriceFactor(order);
+        if (volumeFactor.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        // 与平仓结算一致:盈亏 = (现价 - 开仓价) × 张数 × 面值
+        BigDecimal point = currentPrice.subtract(tradeAvgPrice);
+        BigDecimal priceProfit = point.multiply(volumeFactor).setScale(6, RoundingMode.DOWN);
+        if (!ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
+            priceProfit = priceProfit.negate();
+        }
+        BigDecimal fundingPnl = contractOrderService.calculateAccruedFundingPnl(order, currentPrice, new Date());
+        order.setProfit(priceProfit.add(fundingPnl));
+        order.setCloseAvgPrice(currentPrice);
+        this.contractOrderService.updateByIdBuffer(order);
     }
 
     public void setDataService(DataService dataService) {
@@ -348,10 +333,18 @@
         this.order_close_line_type = order_close_line_type;
     }
 
-    public static void main(String[] args) {
-        StrongLevelCalculationService strongLevelCalculationService = new StrongLevelCalculationServiceImpl();
-        double forceClosePrice = strongLevelCalculationService.calculateLiquidationPrice(100,
-                0.01, 67704.80, 1.477
-                , 0.004, 0.0005);
+    @Override
+    public void refreshMarkPriceProfit(ContractOrder order) {
+        if (order == null || !ContractOrder.STATE_SUBMITTED.equals(order.getState())) {
+            return;
+        }
+        if (defaultZero(order.getVolume()).compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        List<Realtime> list = this.dataService.realtime(order.getSymbol());
+        if (list.isEmpty()) {
+            return;
+        }
+        applyMarkPriceToOrder(order, list.get(0).getClose());
     }
 }
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
index 49e520d..8a7a4ed 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
@@ -1,5 +1,6 @@
 package com.yami.trading.service.contract;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.date.DateUtil;
@@ -9,6 +10,8 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.yami.trading.bean.contract.domain.ContractApplyOrder;
+import com.yami.trading.bean.contract.domain.ContractOrderProfit;
+import com.yami.trading.bean.syspara.domain.Syspara;
 import com.yami.trading.bean.contract.dto.ContractApplyOrderDTO;
 import com.yami.trading.bean.contract.dto.ContractOrderDTO;
 import com.yami.trading.bean.contract.query.ContractApplyOrderQuery;
@@ -32,7 +35,9 @@
 import com.yami.trading.service.user.UserService;
 import com.yami.trading.service.WalletService;
 import com.yami.trading.service.item.ItemService;
+import com.yami.trading.service.syspara.SysparaService;
 import com.yami.trading.util.ConverterUtil;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.jetbrains.annotations.NotNull;
@@ -52,6 +57,10 @@
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.stream.Collectors;
@@ -62,9 +71,11 @@
  * @author lucas
  * @version 2023-03-29
  */
+@Slf4j
 @Service
 @Transactional
 public class ContractOrderService extends ServiceImpl<ContractOrderMapper, ContractOrder> {
+    private static final int FUNDING_SETTLEMENT_INTERVAL_MINUTES = 240;
     private final ConcurrentMap<String, ContractOrder> map = new ConcurrentHashMap<>();
     private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 
@@ -100,10 +111,20 @@
     private ContractApplyOrderService contractApplyOrderService;
 
     @Autowired
+    @Lazy
+    private ContractOrderCalculationService contractOrderCalculationService;
+
+    @Autowired
     private StrongLevelCalculationService strongLevelCalculationService;
 
     @Autowired
     MoneyLogService moneyLogService;
+
+    @Autowired
+    private SysparaService sysparaService;
+
+    @Autowired
+    private ContractPositionModeService contractPositionModeService;
 
     public IPage<ContractOrderDTO> listRecord(Page page, ContractOrderQuery query) {
         return baseMapper.listRecord(page, query);
@@ -214,7 +235,7 @@
     }
 
     public ContractOrder findByOrderNoRedis(String orderNo) {
-       return  RedisUtil.get(ContractRedisKeys.CONTRACT_ORDERNO + orderNo);
+        return  RedisUtil.get(ContractRedisKeys.CONTRACT_ORDERNO + orderNo);
 
     }
     public ContractOrder findByOrderNo(String orderNo) {
@@ -272,6 +293,8 @@
                  */
                 return null;
             }
+
+            contractOrderCalculationService.refreshMarkPriceProfit(order);
 
             /**
              * 收益
@@ -425,7 +448,145 @@
      */
     @Transactional(propagation = Propagation.NOT_SUPPORTED)
     public void updateByIdBuffer(ContractOrder entity) {
+        updateProfit(entity);
         map.put(entity.getUuid(), entity);
+    }
+
+    public void wrapProfit(ContractOrder contractOrder) {
+        if (ContractOrder.STATE_SUBMITTED.equalsIgnoreCase(contractOrder.getState())) {
+            ContractOrderProfit cacheProfit = getCacheProfit(contractOrder.getUuid());
+            if (cacheProfit != null) {
+                contractOrder.setProfit(cacheProfit.getProfit());
+                contractOrder.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
+                contractOrder.setForceClosePrice(cacheProfit.getForceClosePrice());
+            } else {
+                contractOrder.setProfit(BigDecimal.ZERO);
+            }
+        }
+    }
+
+    public ContractOrderProfit getCacheProfit(String uuid) {
+        return RedisUtil.get(ContractRedisKeys.CONTRACT_PROFIT_V1 + uuid);
+    }
+
+    public void updateProfit(ContractOrder order) {
+        if (ContractOrder.STATE_SUBMITTED.equals(order.getState())) {
+            RedisUtil.set(ContractRedisKeys.CONTRACT_PROFIT_V1 + order.getUuid(), BeanUtil.copyProperties(order, ContractOrderProfit.class));
+            Map<String, ContractOrder> submittedMap =
+                    RedisUtil.get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId());
+            if (null == submittedMap) {
+                submittedMap = new ConcurrentHashMap<>();
+            }
+
+            ContractOrder orderOld = submittedMap.get(order.getOrderNo());
+            if (orderOld == null) {
+                return;
+            }
+            orderOld.setCloseAvgPrice(order.getCloseAvgPrice());
+            orderOld.setProfit(order.getProfit());
+            orderOld.setForceClosePrice(order.getForceClosePrice());
+            submittedMap.put(order.getOrderNo(), orderOld);
+
+            RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), submittedMap);
+
+            Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order);
+            Map<String, BigDecimal> contractAssetsOrderOld = this.walletService.getMoneyContractByOrder(orderOld);
+
+            BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId().toString());
+            if (contractAssets == null) {
+                contractAssets = BigDecimal.ZERO;
+            }
+            BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId().toString());
+            if (contractAssetsDeposit == null) {
+                contractAssetsDeposit = BigDecimal.ZERO;
+            }
+            BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString());
+            if (contractAssetsProfit == null) {
+                contractAssetsProfit = BigDecimal.ZERO;
+            }
+
+            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId().toString(),
+                    contractAssets.add(contractAssetsOrder.get("money_contract")).subtract(contractAssetsOrderOld.get("money_contract")));
+            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId().toString(),
+                    contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")).subtract(contractAssetsOrderOld.get("money_contract_deposit")));
+            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString(),
+                    contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")).subtract(contractAssetsOrderOld.get("money_contract_profit")));
+        }
+    }
+
+    /**
+     * 累计资金费带来的盈亏(正数表示用户获利):多仓为 -合约价值×费率×次数,空仓为 +合约价值×费率×次数。
+     */
+    public BigDecimal calculateAccruedFundingPnl(ContractOrder order, BigDecimal markPrice, Date asOf) {
+        if (order == null || markPrice == null || asOf == null || order.getCreateTime() == null) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal vol = defaultZero(order.getVolume());
+        if (vol.compareTo(BigDecimal.ZERO) <= 0 || markPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        long periods = countFundingSettlementPoints(
+                order.getCreateTime().toInstant(),
+                asOf.toInstant(),
+                ZoneId.systemDefault(),
+                FUNDING_SETTLEMENT_INTERVAL_MINUTES);
+        if (periods <= 0) {
+            return BigDecimal.ZERO;
+        }
+        Item item = itemService.findBySymbol(order.getSymbol());
+        BigDecimal faceValue = item != null && item.getFaceValue() > 0
+                ? BigDecimal.valueOf(item.getFaceValue()) : new BigDecimal("0.01");
+        BigDecimal notional = markPrice.multiply(vol).multiply(faceValue);
+        BigDecimal rate = getContractFundingRate();
+        BigDecimal signed = ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())
+                ? rate.negate() : rate;
+        return notional.multiply(signed).multiply(BigDecimal.valueOf(periods)).setScale(8, RoundingMode.HALF_UP);
+    }
+
+    public BigDecimal getContractFundingRate() {
+        try {
+            Syspara p = sysparaService.find("funding_fee");
+            if (p == null || StringUtils.isEmptyString(p.getSvalue())) {
+                return BigDecimal.ZERO;
+            }
+            return new BigDecimal(p.getSvalue().trim());
+        } catch (Exception e) {
+            log.warn("parse funding_fee syspara failed", e);
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private static ZonedDateTime nextFundingSettlementStrictlyAfter(ZonedDateTime t, int intervalMinutes) {
+        int step = intervalMinutes <= 0 ? 240 : intervalMinutes;
+        ZonedDateTime tTrunc = t.withSecond(0).withNano(0);
+        ZonedDateTime dayStart = tTrunc.toLocalDate().atStartOfDay(tTrunc.getZone());
+        long minutesFromDayStart = ChronoUnit.MINUTES.between(dayStart, tTrunc);
+        long slotIndex = minutesFromDayStart / step;
+        ZonedDateTime slotStart = dayStart.plusMinutes(slotIndex * step);
+        ZonedDateTime next = slotStart;
+        while (!next.isAfter(t)) {
+            next = next.plusMinutes(step);
+        }
+        return next;
+    }
+
+    private static long countFundingSettlementPoints(Instant open, Instant end, ZoneId zone, int intervalMinutes) {
+        if (open == null || end == null || !end.isAfter(open)) {
+            return 0;
+        }
+        int step = intervalMinutes <= 0 ? 240 : intervalMinutes;
+        ZonedDateTime zEnd = end.atZone(zone);
+        ZonedDateTime cursor = nextFundingSettlementStrictlyAfter(open.atZone(zone), step);
+        long cnt = 0;
+        while (!cursor.isAfter(zEnd)) {
+            cnt++;
+            cursor = cursor.plusMinutes(step);
+        }
+        return cnt;
+    }
+
+    private BigDecimal defaultZero(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
     }
 
     public void update(ContractOrder order) {
@@ -515,49 +676,58 @@
 
 
     /**
+     * 价差盈亏(与持仓定时计算一致):(平仓价 - 开仓价) × 平仓张数 × 面值,空仓取反
+     */
+    private BigDecimal calculatePricePnl(ContractOrder order, BigDecimal closePrice, BigDecimal sheets) {
+        BigDecimal tradeAvgPrice = order.getTradeAvgPrice() == null ? BigDecimal.ZERO : order.getTradeAvgPrice();
+        if (tradeAvgPrice.compareTo(BigDecimal.ZERO) <= 0 || sheets == null || sheets.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        Item item = itemService.findBySymbol(order.getSymbol());
+        BigDecimal faceValue = item != null && item.getFaceValue() > 0
+                ? BigDecimal.valueOf(item.getFaceValue()) : new BigDecimal("0.01");
+        BigDecimal point = closePrice.subtract(tradeAvgPrice);
+        BigDecimal pnl = point.multiply(faceValue).multiply(sheets).setScale(4, RoundingMode.DOWN);
+        if (!ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
+            pnl = pnl.negate();
+        }
+        return pnl;
+    }
+
+    /**
      * 收益结算,平仓时计算
      *
-     * @param closevolume 平仓的张数
+     * @param volume 平仓的张数
      */
     public BigDecimal settle(ContractOrder order, BigDecimal volume) {
-        /**
-         * 偏差点位
-         */
-        List<Realtime> list = this.dataService.realtime(order.getSymbol());
-        if (list.size() == 0) {
-            order.getProfit();
+        ContractOrderProfit cacheProfit = getCacheProfit(order.getUuid());
+        BigDecimal currentVolume = order.getVolume() == null ? BigDecimal.ZERO : order.getVolume();
+        if (currentVolume.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
         }
-        Realtime realtime = list.get(0);
-        BigDecimal close = realtime.getClose();
-        BigDecimal point = close.subtract(order.getTradeAvgPrice());
-        BigDecimal profit = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);;
-        if (order.getForceClosePrice() != null && !order.getForceClosePrice().isEmpty()) {  //达到强平价
-            if ((ContractOrder.DIRECTION_BUY.equals(order.getDirection()) && close.compareTo(new BigDecimal(order.getForceClosePrice())) <= 0)
-                || (ContractOrder.DIRECTION_SELL.equals(order.getDirection()) && close.compareTo(new BigDecimal(order.getForceClosePrice())) >= 0)) {
-                point = new BigDecimal(order.getForceClosePrice()).subtract(order.getTradeAvgPrice());
-                profit = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);;
-                System.out.println("------------------point-------------:"+point);
-                System.out.println("------------------profit------------:"+profit);
-                profit = order.getDepositOpen().add(order.getAddDepositOpen()).negate();
-                System.out.println("------------------扣除保证金------------:"+profit);
-                if (order.getLocationType() == 1) {
-                    Wallet wallet = this.walletService.findByUserId(order.getPartyId());
-                    System.out.println("------------------扣除余额------------:"+wallet.getMoney());
-                    profit = profit.subtract(wallet.getMoney());
-                }
+
+        BigDecimal closeRatio = volume.divide(currentVolume, 10, RoundingMode.HALF_UP);
+        BigDecimal originProfit = BigDecimal.ZERO;
+        if (cacheProfit != null) {
+            originProfit = cacheProfit.getProfit();
+            if (cacheProfit.getCloseAvgPrice() != null) {
+                order.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
+            }
+        } else {
+            List<Realtime> list = this.dataService.realtime(order.getSymbol());
+            if (!list.isEmpty()) {
+                originProfit = calculatePricePnl(order, list.get(0).getClose(), currentVolume);
             }
         }
 
-        if(null != order.getProfitLossRatio() && order.getProfitLossRatio() > 0){
-            profit = order.getDepositOpen().multiply(new BigDecimal((order.getProfitLossRatio()/100))).setScale(2, RoundingMode.DOWN);
+        BigDecimal profit = originProfit.multiply(closeRatio).setScale(4, RoundingMode.DOWN);
+
+        if (null != order.getProfitLossRatio() && order.getProfitLossRatio() > 0) {
+            profit = order.getDepositOpen().multiply(new BigDecimal((order.getProfitLossRatio() / 100))).setScale(2, RoundingMode.DOWN);
         }
-        if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
-            order.setProfit(profit);
-        } else{
-            order.setProfit(profit.negate());
-        }
+
+        order.setProfit(profit);
         BigDecimal rentalProfit = order.getDepositOpen().add(order.getProfit()).add(order.getAddDepositOpen());
-        System.out.println("------------------rentalProfit------------:"+rentalProfit);
         BigDecimal rate = volume.divide(order.getVolumeOpen(), 2, RoundingMode.HALF_UP);
         order.setAmountClose(order.getAmountClose().add(profit));
         order.setVolume(order.getVolume().subtract(volume));
@@ -566,12 +736,192 @@
             order.setState(ContractOrder.STATE_CREATED);
             order.setCloseTime(DateUtil.currentSeconds());
             order.setCloseTimeTs(DateUtil.currentSeconds());
-
         }
         return rentalProfit;
     }
+
+    /**
+     * 单向持仓:反向开仓前先按张数平掉对向持仓,释放保证金后再开剩余仓位。
+     *
+     * @return 对冲后仍需新开仓的张数
+     */
+    private BigDecimal netOppositePositions(ContractApplyOrder applyOrder, Realtime realtime, BigDecimal openVolume) {
+        if (openVolume == null || openVolume.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        String oppositeDirection = ContractOrder.DIRECTION_BUY.equals(applyOrder.getDirection())
+                ? ContractOrder.DIRECTION_SELL
+                : ContractOrder.DIRECTION_BUY;
+        List<ContractOrder> oppositeList = findSubmitted(
+                applyOrder.getPartyId(), applyOrder.getSymbol(), oppositeDirection);
+        if (CollectionUtil.isEmpty(oppositeList)) {
+            return openVolume;
+        }
+
+        BigDecimal remainingOpenVolume = openVolume;
+        for (ContractOrder opposite : oppositeList) {
+            if (remainingOpenVolume.compareTo(BigDecimal.ZERO) <= 0) {
+                break;
+            }
+            if (opposite.getVolume() == null || opposite.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            if (!ContractLock.add(opposite.getOrderNo())) {
+                continue;
+            }
+            try {
+                BigDecimal closeVolume = remainingOpenVolume.min(opposite.getVolume());
+                contractOrderCalculationService.refreshMarkPriceProfit(opposite);
+                BigDecimal profit = settle(opposite, closeVolume);
+                update(opposite);
+
+                Wallet wallet = walletService.findByUserId(applyOrder.getPartyId());
+                if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
+                    profit = wallet.getMoney().negate();
+                }
+                walletService.updateMoney(opposite.getSymbol(), applyOrder.getPartyId(), profit, BigDecimal.ZERO,
+                        Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT,
+                        Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE,
+                        "反手对冲平仓,平仓合约数[" + closeVolume + "],订单号[" + opposite.getOrderNo() + "]");
+
+                if (ContractOrder.STATE_CREATED.equals(opposite.getState())) {
+                    User party = userService.getById(opposite.getPartyId());
+                    if (party != null) {
+                        if (party.getWithdrawLimitNowAmount() == null) {
+                            party.setWithdrawLimitNowAmount(BigDecimal.ZERO);
+                        }
+                        party.setWithdrawLimitNowAmount(party.getWithdrawLimitNowAmount().add(opposite.getDepositOpen()));
+                        userService.updateById(party);
+                    }
+                    userDataService.saveClose(opposite);
+                    User partyUser = userService.getById(opposite.getPartyId());
+                    if (partyUser != null && Constants.SECURITY_ROLE_MEMBER.equals(partyUser.getRoleName())) {
+                        tipService.deleteTip(opposite.getUuid().toString());
+                    }
+                }
+                remainingOpenVolume = remainingOpenVolume.subtract(closeVolume);
+            } finally {
+                ContractLock.remove(opposite.getOrderNo());
+            }
+        }
+        return remainingOpenVolume;
+    }
+
+    /**
+     * 反向开仓完全被对向持仓对冲,无需再开新仓。
+     */
+    private void completeNettedApplyOrder(ContractApplyOrder applyOrder,
+                                          BigDecimal originalDeposit, BigDecimal originalFee) {
+        applyOrder.setVolume(BigDecimal.ZERO);
+        applyOrder.setState(ContractApplyOrder.STATE_CREATED);
+        contractApplyOrderService.updateById(applyOrder);
+        if (ContractApplyOrder.ORDER_PRICE_TYPE_LIMIT.equals(applyOrder.getOrderPriceType())) {
+            BigDecimal refund = defaultZero(originalDeposit).add(defaultZero(originalFee));
+            if (refund.compareTo(BigDecimal.ZERO) > 0) {
+                walletService.updateMoney(applyOrder.getSymbol(), applyOrder.getPartyId(), refund, BigDecimal.ZERO,
+                        Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT,
+                        Constants.MONEYLOG_CONTENT_CONTRACT_CONCEL,
+                        "反手完全对冲,退回保证金,订单号[" + applyOrder.getOrderNo() + "]");
+            }
+        }
+    }
+
+    /**
+     * 部分对冲后,按剩余比例调整委托单保证金/手续费;限价单退回多扣部分。
+     */
+    private void adjustApplyOrderAfterPartialNet(ContractApplyOrder applyOrder,
+                                                 BigDecimal originalVolume,
+                                                 BigDecimal remainingVolume,
+                                                 BigDecimal originalMoney,
+                                                 BigDecimal originalDeposit,
+                                                 BigDecimal originalFee) {
+        BigDecimal ratio = remainingVolume.divide(originalVolume, 10, RoundingMode.HALF_UP);
+        BigDecimal remainingMoney = originalMoney.multiply(ratio).setScale(4, RoundingMode.DOWN);
+        BigDecimal remainingDeposit = defaultZero(originalDeposit).multiply(ratio).setScale(4, RoundingMode.DOWN);
+        BigDecimal remainingFee = defaultZero(originalFee).multiply(ratio).setScale(4, RoundingMode.DOWN);
+
+        if (ContractApplyOrder.ORDER_PRICE_TYPE_LIMIT.equals(applyOrder.getOrderPriceType())) {
+            BigDecimal refund = defaultZero(originalDeposit).subtract(remainingDeposit)
+                    .add(defaultZero(originalFee).subtract(remainingFee));
+            if (refund.compareTo(BigDecimal.ZERO) > 0) {
+                walletService.updateMoney(applyOrder.getSymbol(), applyOrder.getPartyId(), refund, BigDecimal.ZERO,
+                        Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT,
+                        Constants.MONEYLOG_CONTENT_CONTRACT_CONCEL,
+                        "反手部分对冲,退回多余保证金,订单号[" + applyOrder.getOrderNo() + "]");
+            }
+        }
+        applyOrder.setVolume(remainingVolume);
+        applyOrder.setVolumeOpen(remainingVolume);
+        applyOrder.setMoney(remainingMoney);
+        applyOrder.setDeposit(remainingDeposit);
+        applyOrder.setFee(remainingFee);
+    }
+
+    private void applyLimitOpenToOrder(ContractOrder order, ContractApplyOrder applyOrder) {
+        order.setUnitAmount(applyOrder.getUnitAmount());
+        order.setDepositOpen(applyOrder.getDeposit());
+        order.setDeposit(applyOrder.getDeposit());
+        order.setFee(defaultZero(applyOrder.getFee()));
+        order.setVolume(applyOrder.getVolume());
+        order.setVolumeOpen(applyOrder.getVolumeOpen());
+        order.setTradeAvgPrice(applyOrder.getPrice());
+    }
+
+    private void mergeLimitOpenToPosition(ContractOrder position, ContractApplyOrder applyOrder, Item item) {
+        position.setDepositOpen(position.getDepositOpen().add(applyOrder.getDeposit()));
+        position.setDeposit(position.getDeposit().add(applyOrder.getDeposit()));
+        position.setFee(defaultZero(position.getFee()).add(defaultZero(applyOrder.getFee())));
+        position.setLeverRate(applyOrder.getLeverRate());
+        double number = strongLevelCalculationService.countSheets(
+                position.getDepositOpen().doubleValue(),
+                applyOrder.getLeverRate().intValue(),
+                0.01,
+                applyOrder.getPrice().doubleValue());
+        position.setVolumeOpen(new BigDecimal(number));
+        position.setVolume(new BigDecimal(number));
+        BigDecimal price = applyOrder.getPrice().multiply(BigDecimal.valueOf(item.getFaceValue()));
+        position.setUnitAmount(price.setScale(4, RoundingMode.DOWN));
+        BigDecimal avgPrice = position.getTradeAvgPrice().add(applyOrder.getPrice())
+                .divide(new BigDecimal(2), 4, RoundingMode.DOWN);
+        position.setTradeAvgPrice(avgPrice);
+    }
+
+    private void ensureOpponentOpenMargin(ContractApplyOrder applyOrder) {
+        BigDecimal cost = defaultZero(applyOrder.getDeposit()).add(defaultZero(applyOrder.getFee()));
+        BigDecimal available = contractApplyOrderService.getAvailableOpenMargin(
+                applyOrder.getPartyId(), applyOrder.getOrderNo());
+        if (available.compareTo(cost) < 0) {
+            throw new YamiShopBindException("not sufficient funds");
+        }
+    }
+
     public void saveOpen(ContractApplyOrder applyOrder, Realtime realtime) {
         Item item = this.itemService.findBySymbol(applyOrder.getSymbol());
+        if (item == null) {
+            throw new YamiShopBindException("Parameter Error3");
+        }
+
+        // 单向持仓:反向开仓前先平掉对向仓位;双向持仓(默认)允许同币种多空并存
+        if (contractPositionModeService.isOneWayMode()) {
+            BigDecimal originalVolume = applyOrder.getVolume();
+            BigDecimal originalMoney = applyOrder.getMoney();
+            BigDecimal originalDeposit = applyOrder.getDeposit();
+            BigDecimal originalFee = applyOrder.getFee();
+
+            BigDecimal remainingVolume = netOppositePositions(applyOrder, realtime, originalVolume);
+            if (remainingVolume.compareTo(BigDecimal.ZERO) <= 0) {
+                completeNettedApplyOrder(applyOrder, originalDeposit, originalFee);
+                return;
+            }
+            if (remainingVolume.compareTo(originalVolume) < 0) {
+                adjustApplyOrderAfterPartialNet(applyOrder, originalVolume, remainingVolume,
+                        originalMoney, originalDeposit, originalFee);
+            }
+        }
+
+        if (ContractApplyOrder.ORDER_PRICE_TYPE_OPPONENT.equals(applyOrder.getOrderPriceType())) {
+            ensureOpponentOpenMargin(applyOrder);
+        }
 
         ContractOrder f = getOne(new LambdaQueryWrapper<>(ContractOrder.class)
                 .eq(ContractOrder::getPartyId, applyOrder.getPartyId())
@@ -600,7 +950,7 @@
 
                     fee = applyOrder.getDeposit().multiply(f.getLeverRate()).multiply(item.getUnitFee());
                     fee = fee.setScale(4, RoundingMode.DOWN);  // 保留两位小数
-                    f.setFee(f.getFee().add(fee));
+                    f.setFee(defaultZero(f.getFee()).add(fee));
                 }
                 double number = strongLevelCalculationService.countSheets(f.getDepositOpen().doubleValue(), applyOrder.getLeverRate().intValue(), 0.01, realtime.getClose().doubleValue());
                 f.setVolumeOpen(new BigDecimal(number));
@@ -613,9 +963,11 @@
                 walletService.updateMoney(f.getSymbol(), f.getPartyId(), BigDecimal.ZERO.subtract(fee), BigDecimal.ZERO
                         , Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_FEE, "委托单,订单号[" + f.getOrderNo() + "]"
                 );
+            } else {
+                mergeLimitOpenToPosition(f, applyOrder, item);
             }
             //计算强平价格
-            getStrongPrice(f,item);
+            getStrongPrice(f);
             update(f);
             refreshOrder(applyOrder, f);
         }else{
@@ -634,9 +986,10 @@
                     fee = fee.setScale(4, RoundingMode.DOWN);  // 保留两位小数
                     order.setFee(fee);
                 }
-                double number = strongLevelCalculationService.countSheets(order.getDepositOpen().doubleValue(), order.getLeverRate().intValue(), 0.01, applyOrder.getPrice().doubleValue());
+                double number = strongLevelCalculationService.countSheets(order.getDepositOpen().doubleValue(), order.getLeverRate().intValue(), 0.01, realtime.getClose().doubleValue());
                 order.setVolume(new BigDecimal(number));
                 order.setVolumeOpen(new BigDecimal(number));
+                order.setTradeAvgPrice(realtime.getClose());
 
                 walletService.updateMoney(order.getSymbol(), order.getPartyId(), BigDecimal.ZERO.subtract(order.getDeposit()), BigDecimal.ZERO
                         , Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_OPEN, "委托单,订单号[" + order.getOrderNo() + "]"
@@ -644,10 +997,12 @@
                 walletService.updateMoney(order.getSymbol(), order.getPartyId(), BigDecimal.ZERO.subtract(order.getFee()), BigDecimal.ZERO
                         , Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_FEE, "委托单,订单号[" + order.getOrderNo() + "]"
                 );
+            } else {
+                applyLimitOpenToOrder(order, applyOrder);
             }
-            //计算强平价格
-            getStrongPrice(order,item);
             save(order);
+            getStrongPrice(order);
+            update(order);
             refreshOrder(applyOrder, order);
         }
     }
@@ -719,31 +1074,8 @@
         }
     }
 
-    private void getStrongPrice(ContractOrder order, Item item) {
-        BigDecimal earnings = BigDecimal.ZERO;
-        if(order.getLocationType() == 1){//全仓
-            // 获取当前账户余额并加到收益中
-            //Map<String, Object> moneyAll = walletService.getMoneyAll(order.getPartyId());
-            //earnings = order.getDepositOpen().add(new BigDecimal(moneyAll.get("money_wallet").toString()));
-            Wallet wallet = walletService.saveWalletByPartyId(order.getPartyId());
-            earnings = order.getDepositOpen().add(earnings.add(wallet.getMoney()));
-        }else{
-            earnings = order.getDepositOpen().add(order.getAddDepositOpen());
-        }
-        double faceValue = 0.01; // 合约面值(固定面值不能调整)
-        double maintenanceMarginRate = 0.004; // 维持保证金率(固定不变)
-        //"buy":买(多) "sell":卖(空)
-        if(order.getDirection().equals("buy")){
-            double forceClosePrice = strongLevelCalculationService.calculateLiquidationPrice(earnings.doubleValue(),
-                    faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue()
-                    , maintenanceMarginRate, item.getUnitFee().doubleValue());
-            order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString());
-        }else{
-            double forceClosePrice = strongLevelCalculationService.calculateEmptyLiquidationPrice(earnings.doubleValue(),
-                    faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue()
-                    , maintenanceMarginRate, item.getUnitFee().doubleValue());
-            order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString());
-        }
+    private void getStrongPrice(ContractOrder order) {
+        order.setForceClosePrice(contractOrderCalculationService.calculateForceClosePriceForOrder(order));
     }
 
     public ContractApplyOrder saveClose(ContractApplyOrder applyOrder, Realtime realtime, String order_no) {

--
Gitblit v1.9.3