diff --git a/YLErpDAL/Modules/SwapModule/ClosePercentMath.cs b/YLErpDAL/Modules/SwapModule/ClosePercentMath.cs
new file mode 100644
index 00000000..9269a449
--- /dev/null
+++ b/YLErpDAL/Modules/SwapModule/ClosePercentMath.cs
@@ -0,0 +1,65 @@
+using YLErp.DBModels;
+
+namespace YLErp.Modules.SwapModule;
+
+///
+/// 平仓比例(ClosePercent) 数学——占期初(A) / 占剩余(B) 两种口径的转换。
+/// 从 SwapDealService 提取为共享模块,两个 service 均可引用。
+///
+public static class ClosePercentMath
+{
+ ///
+ /// 取上一日终的浮动端名义本金(orginPv 的来源)。
+ /// 优先取浮动腿 PosiNotionalValue 之和,取不到用 eod_swap 多空绝对值之和,都没有用 currentNotional 兜底。
+ ///
+ public static decimal ResolveUnwindPreviousNotional(
+ eod_swap lastEod,
+ IEnumerable lastEodPositions,
+ decimal currentNotional)
+ {
+ var floatingPositions = lastEodPositions?.Where(x => x.PosiDirection > 0).ToList();
+ decimal previousNotional;
+ if (floatingPositions?.Count > 0)
+ {
+ previousNotional = floatingPositions.Sum(x => x.PosiNotionalValue);
+ }
+ else
+ {
+ previousNotional = lastEod == null
+ ? currentNotional
+ : Math.Abs(lastEod.NotionalValueLong) + Math.Abs(lastEod.NotionalValueShort);
+ }
+
+ return previousNotional == 0m && currentNotional != 0m
+ ? currentNotional
+ : previousNotional;
+ }
+
+ ///
+ /// A(占期初) → B(占剩余),用于把前端传入的占期初比例换算成后端计算用的占剩余比例。
+ ///
+ public static decimal ToRemainingClosePercent(decimal originalClosePercent, decimal notionalValue, decimal posiNotionalValue)
+ {
+ if (posiNotionalValue <= 0) return originalClosePercent;
+ var remaining = originalClosePercent * notionalValue / posiNotionalValue;
+ return remaining > 1 ? 1 : remaining;
+ }
+
+ ///
+ /// B(占剩余) → A(占期初),用于落库 / 事件列表展示还原。见 ToRemainingClosePercent。
+ ///
+ public static decimal ToOriginalClosePercent(decimal remainingClosePercent, decimal notionalValue, decimal posiNotionalValue)
+ {
+ if (notionalValue <= 0) return remainingClosePercent;
+ return remainingClosePercent * posiNotionalValue / notionalValue;
+ }
+
+ ///
+ /// 计算 InitUnwind 默认占期初(A)平仓比例 = PosiNotionalValue / NotionalValue。
+ /// 未平仓时 =1(平100%);部分平仓后自动变为剩余比例。
+ ///
+ public static decimal CalcDefaultInitClosePercent(decimal notionalValue, decimal posiNotionalValue)
+ {
+ return notionalValue > 0 ? posiNotionalValue / notionalValue : 1;
+ }
+}
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index eab142cc..8a3f0796 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -625,24 +625,7 @@ namespace YLErp.Modules.SwapModule
eod_swap lastEod,
IEnumerable lastEodPositions,
decimal currentNotional)
- {
- var floatingPositions = lastEodPositions?.Where(x => x.PosiDirection > 0).ToList();
- decimal previousNotional;
- if (floatingPositions?.Count > 0)
- {
- previousNotional = floatingPositions.Sum(x => x.PosiNotionalValue);
- }
- else
- {
- previousNotional = lastEod == null
- ? currentNotional
- : Math.Abs(lastEod.NotionalValueLong) + Math.Abs(lastEod.NotionalValueShort);
- }
-
- return previousNotional == 0m && currentNotional != 0m
- ? currentNotional
- : previousNotional;
- }
+ => ClosePercentMath.ResolveUnwindPreviousNotional(lastEod, lastEodPositions, currentNotional);
///
/// 获取利息腿"已通过历史互换结出的累计利息"(用于复利重算时扣除,类比分红的 CalcConsumedDividend)。
@@ -822,32 +805,19 @@ namespace YLErp.Modules.SwapModule
/// 分母为 0(无持仓等异常场景)时原样返回,避免除零。
///
public static decimal ToRemainingClosePercent(decimal originalClosePercent, decimal notionalValue, decimal posiNotionalValue)
- {
- if (posiNotionalValue <= 0) return originalClosePercent;
- var remaining = originalClosePercent * notionalValue / posiNotionalValue;
- return remaining > 1 ? 1 : remaining;
- }
+ => ClosePercentMath.ToRemainingClosePercent(originalClosePercent, notionalValue, posiNotionalValue);
///
/// B(占剩余) → A(占期初),用于落库 / 事件列表展示还原。见 ToRemainingClosePercent。
///
public static decimal ToOriginalClosePercent(decimal remainingClosePercent, decimal notionalValue, decimal posiNotionalValue)
- {
- if (notionalValue <= 0) return remainingClosePercent;
- return remainingClosePercent * posiNotionalValue / notionalValue;
- }
+ => ClosePercentMath.ToOriginalClosePercent(remainingClosePercent, notionalValue, posiNotionalValue);
///
- /// 计算 InitUnwind 默认占期初(A)平仓比例 = "平掉剩余全部持仓"对应的占期初比例。
- /// 即:ClosePercent(A) = PosiNotionalValue / NotionalValue。
- /// 未平仓时 PosiNotionalValue==NotionalValue → 1(平100%);
- /// 部分平仓后自动变为剩余比例(如已平 30% 则默认 0.7)。
- /// 与互换/提前终止 InitIncome 保持一致。抽出为纯函数以支持无库单测。
+ /// 计算 InitUnwind 默认占期初(A)平仓比例 = PosiNotionalValue / NotionalValue。
///
public static decimal CalcDefaultInitClosePercent(decimal notionalValue, decimal posiNotionalValue)
- {
- return notionalValue > 0 ? posiNotionalValue / notionalValue : 1;
- }
+ => ClosePercentMath.CalcDefaultInitClosePercent(notionalValue, posiNotionalValue);
///
/// 读取"上一收盘日"浮动腿的待实现分红(eod_swap_position.PosiDividendSum),