diff --git a/UnitTestProject/Modules/SwapModule/SwapAdditionalMarginCalcTest.cs b/UnitTestProject/Modules/SwapModule/SwapAdditionalMarginCalcTest.cs
index f1d27fa9..7069ed32 100644
--- a/UnitTestProject/Modules/SwapModule/SwapAdditionalMarginCalcTest.cs
+++ b/UnitTestProject/Modules/SwapModule/SwapAdditionalMarginCalcTest.cs
@@ -94,6 +94,23 @@ namespace YLErp.Modules.SwapModule
Assert.AreEqual(20, day3, 1e-6);
}
+ ///
+ /// 授信垫付的初始预付金计入已缴(2026-08-27 修正,交易2538实证):
+ /// 初始预付金走授信腿不产生应付预付金流水(现金净收额=0),已缴初始=授信初始占用净额——
+ /// 目标追加 = 维持 −(现金净收额+初始授信占用净额),否则每个结算日按维持全额重复开追加。
+ ///
+ [TestMethod]
+ public void AM_009_授信初始预付金计入已缴_不重复追加()
+ {
+ //交易2538 首日实际数据:维持2,808,000;初始预付金2,000,000全走授信(现金净收额0)
+ var payableNetCash = 0d;
+ var initCreditNet = 2_000_000d;
+ var target = SwapAdditionalMarginCalc.CalcTarget(2_808_000, payableNetCash + initCreditNet);
+ Assert.AreEqual(808_000, target, 1e-6);
+
+ //对照:修正前只扣现金净收额 → 目标2,808,000 全额追加(多收2,000,000 授信占用,即本BUG)
+ }
+
///
/// 追保回落(维持下降到应付之下)不返还:目标为 0 → 已补足保持,新增 0;
/// 超付部分由可用资金公式的负缺口(Σ维持−累计)体现,不产生返还记录。
diff --git a/UnitTestProject/Modules/SwapModule/SwapSpanMarginCalcTest.cs b/UnitTestProject/Modules/SwapModule/SwapSpanMarginCalcTest.cs
index 9407a1c6..1c092896 100644
--- a/UnitTestProject/Modules/SwapModule/SwapSpanMarginCalcTest.cs
+++ b/UnitTestProject/Modules/SwapModule/SwapSpanMarginCalcTest.cs
@@ -131,6 +131,55 @@ namespace YLErp.Modules.SwapModule
Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 2.00));
}
+ ///
+ /// 层间空隙不落最深层(2026-08-27 修正,交易2538实证):
+ /// 真实模板空头档位 (−∞,0.99]∪(1.00,1.03],(0.99,1.00] 为价格在期初附近小幅波动的"未触发追保"空隙——
+ /// 此前兜底一律按最深层计,ratio≈0.99999 被错误收取最深档(0.04×基数)。
+ /// 修正后:空隙返回 null(追加保证金按0);仅穿出最深层边界(ratio>最深上界)才按最深层计。
+ ///
+ [TestMethod]
+ public void SS_008_空头层间空隙_不落最深层_追加为零()
+ {
+ //模板"保证金测试01"实际配置形态:第1层 (−∞,0.99],第2~4层 (1.00,1.03]
+ var tiers = new List
+ {
+ Tier(null, 0.99, 0.01),
+ Tier(1.00, 1.01, 0.02),
+ Tier(1.01, 1.02, 0.03),
+ Tier(1.02, 1.03, 0.04),
+ };
+ //交易2538 08-24:净价100 → ratio = 100/100.0011 ≈ 0.99999 落 (0.99,1.00] 空隙 → 不追保
+ Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, false, 0.99999));
+ Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, false, 0.995));
+ //空隙边界归第1层
+ Assert.AreSame(tiers[0], SwapSpanMarginCalc.MatchTier(tiers, false, 0.99));
+ //空隙上方正常落档
+ Assert.AreSame(tiers[1], SwapSpanMarginCalc.MatchTier(tiers, false, 1.005));
+ Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, false, 1.015));
+ Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 1.025));
+ //穿出最深层上界 → 按最深层计(原有语义不变)
+ Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 1.0301));
+ }
+
+ ///
+ /// 多头层间空隙同理:空隙返回 null(追加0),仅跌破最深层下界才按最深层计。
+ ///
+ [TestMethod]
+ public void SS_009_多头层间空隙_不落最深层()
+ {
+ var tiers = new List
+ {
+ Tier(0.98, null, 0.01),
+ Tier(0.96, 0.97, 0.03), //留出 (0.97,0.98) 空隙
+ Tier(0.95, 0.96, 0.04),
+ };
+ Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, true, 0.975));
+ Assert.AreSame(tiers[1], SwapSpanMarginCalc.MatchTier(tiers, true, 0.9699));
+ Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, true, 0.955));
+ //穿出最深层下界 → 按最深层计(原有语义不变)
+ Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, true, 0.94));
+ }
+
[TestMethod]
public void SS_008_无可用层_返回null()
{
diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs
index 08e26da3..f6540009 100644
--- a/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs
+++ b/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs
@@ -8,7 +8,9 @@ namespace YLErp.Modules.SwapModule.Margin
{
///
/// R3 阶段四 §4.1:合约维度(MarginWatchRule==0)规则15 交易日终结算产生"追加保证金"资金记录。
- /// 交易维度追加保证金 = 维持保证金(阶段三引擎 trade_span 产出)− 累计保证金(应付预付金+追加保证金 流水净额 + 追加授信占用);
+ /// 交易维度追加保证金 = 维持保证金(阶段三引擎 trade_span 产出)− 已缴保证金净额
+ /// (应付预付金现金净收额 + 初始授信占用净额 + 追加保证金现金累计 + 追加授信占用累计——
+ /// 2026-08-27 修正:授信垫付的初始预付金不产生应付预付金流水,此前未计入已缴导致每个结算日按维持全额重复开追加);
/// 现金部分为逐结算日增量记录(BUG-03 修正:每结算日一条、Money=−increment,键 TradeId+Action+Deal+HappenDate 幂等),
/// 需求上升只增不减;授信优先(阶段二规则):授信部分只写授信出入表(remark 前缀=追加保证金,position_id 空、冗余 trade_id)。
/// 由 EOD 在客户资金计算之前调用:当日新记录计入当日出入金窗口并翻"已结算",重跑时 目标/已补足 不变 → 新增为 0 不重复写。
@@ -107,6 +109,16 @@ namespace YLErp.Modules.SwapModule.Margin
.Select(g => new { TradeId = g.Key ?? 0, Funded = g.Sum(x => x.amount) })
.ToDictionary(x => x.TradeId, x => x.Funded);
+ //初始预付金的授信占用净额(非"追加保证金"前缀:簿记初始占用 + 平仓释放取负,Σ(amount) 即净已缴):
+ //授信腿不产生应付预付金流水,目标追加里只扣现金净收额会把授信垫付的初始预付金漏掉——
+ //每个结算日都按维持保证金全额重复开追加(BUG:多收授信占用/现金,2026-08-27 交易2538实证:初始授信200万未扣、首日全额追加280.8万)
+ var initCreditByTrade = DbContext.client_credit_inout.AsNoTracking()
+ .Where(x => x.trade_id != null && tradeIds.Contains(x.trade_id ?? 0)
+ && (x.remark == null || !x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark)))
+ .GroupBy(x => x.trade_id)
+ .Select(g => new { TradeId = g.Key ?? 0, Funded = g.Sum(x => x.amount) })
+ .ToDictionary(x => x.TradeId, x => x.Funded);
+
var fundTagService = new SwapFundTagService(this);
var cashService = new ClientCashInCashOutService(this);
var creditService = new ClientCreditInoutService(this);
@@ -121,8 +133,11 @@ namespace YLErp.Modules.SwapModule.Margin
{
continue;
}
- var target = SwapAdditionalMarginCalc.CalcTarget(maintenance,
- payableNetByTrade.TryGetValue(td.id, out var payableNet) ? payableNet : 0);
+ //目标追加 = 维持保证金 − 已缴初始保证金净额(现金应付预付金净收额 + 授信初始占用净额,
+ //授信垫付与现金垫付同等对待,杜绝授信初始预付金被重复追加)
+ var payableNet = (payableNetByTrade.TryGetValue(td.id, out var payable) ? payable : 0)
+ + (initCreditByTrade.TryGetValue(td.id, out var initCredit) ? initCredit : 0);
+ var target = SwapAdditionalMarginCalc.CalcTarget(maintenance, payableNet);
if (target <= 0)
{
continue;
diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanMarginCalc.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanMarginCalc.cs
index 93c2155c..7ae27f18 100644
--- a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanMarginCalc.cs
+++ b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanMarginCalc.cs
@@ -72,11 +72,27 @@ namespace YLErp.Modules.SwapModule.Margin
return tier;
}
}
- //未落任何层:价格已穿出最深一层边界(低于多头最深层下界/高于空头最深层上界),按最深层计;
- //最深层按边界值取(多头=最小下界、空头=最大上界),不依赖配置数组顺序(BUG-25 引擎侧防御)
- return isCustomerLong
+ //未落任何层分两种情形:
+ //① 价格穿出最深一层边界(多头低于最深层下界/空头高于最深层上界)→ 按最深层计(追保金额不再上升);
+ // 最深层按边界值取(多头=最小下界、空头=最大上界),不依赖配置数组顺序(BUG-25 引擎侧防御)。
+ //② 层间空隙(如空头 (0.99,1.00]——价格在期初附近小幅波动、未触发追保的区间)→ 返回 null,追加保证金按 0。
+ // 此前兜底不分情形一律按最深层计,空隙价格被错误收取最深档追保
+ // (2026-08-27 交易2538实证:08-24净价100→ratio 0.99999 落空头(0.99,1.00]空档,被按0.04最深档收80.8万)。
+ var deepest = isCustomerLong
? valid.OrderBy(t => t.Lower ?? double.MinValue).First()
: valid.OrderByDescending(t => t.Upper ?? double.MaxValue).First();
+ if (isCustomerLong)
+ {
+ if (priceRatio < (deepest.Lower ?? double.MinValue))
+ {
+ return deepest;
+ }
+ }
+ else if (priceRatio > (deepest.Upper ?? double.MaxValue))
+ {
+ return deepest;
+ }
+ return null;
}
///