using YLErp.Modules.SwapModule.Margin; namespace YLErp.Modules.SwapModule { /// /// R2 阶段三 §3.2 可用资金/追保/可取资金公式测试(定稿 2026-08-28 口径,实时/EOD/报告三处共用 SwapSpanBalanceCalc): /// 客户维度可用 = Max(现金结存 + 授信 − 已使用授信 + 初始保证金 − 维持保证金, 0); /// 合约维度可用 = Max(现金结存 + 授信 − 已使用授信, 0)(定稿删除"−交易维度追加保证金",避免与现金结存/已使用授信双重扣减); /// 追保金额两维度均 Max(...,0) 截断、恒 ≥ 0(只追不退);合约维度(2026-08-28 调整)= 当日追加保证金现金部分合计 /// (不与现金结存轧差、闲置现金不冲抵、授信不追),客户维度 = Max((维持−初始) − (现金+授信−已使用), 0); /// 可取资金只算现金部分:合约维度 = Max(现金结存 + min(持仓盈亏,0), 0),客户维度 = Max(现金结存 + 初始 − 维持 + min(持仓盈亏,0), 0)。 /// 授信额度为 credit.Credit 合计(保存时已折算比例),现金结存=期末结存(阶段二起授信不进资金)。 /// [TestClass] public class SwapSpanBalanceCalcTest { [TestMethod] public void SB_001_客户维度_现金充足() { //现金200 + 授信100 − 已使用20 + 初始100 − 维持150 = 230 Assert.AreEqual(230, SwapSpanBalanceCalc.CalcClientDimensionAvailable( cashBalance: 200, totalCredit: 100, usedCredit: 20, initialMargin: 100, maintenanceMargin: 150), 1e-6); } [TestMethod] public void SB_002_客户维度_现金不足_截断为0() { //现金20 + 0 − 0 + 100 − 130 = −10 → 0(追加10未补足时无可用资金) Assert.AreEqual(0, SwapSpanBalanceCalc.CalcClientDimensionAvailable( cashBalance: 20, totalCredit: 0, usedCredit: 0, initialMargin: 100, maintenanceMargin: 130), 1e-6); } [TestMethod] public void SB_003_客户维度_授信可用额参与覆盖() { //现金20 + 授信100 − 已使用20 + 100 − 130 = 70 Assert.AreEqual(70, SwapSpanBalanceCalc.CalcClientDimensionAvailable( cashBalance: 20, totalCredit: 100, usedCredit: 20, initialMargin: 100, maintenanceMargin: 130), 1e-6); } /// /// 合约维度可用资金(定稿 2026-08-28):Max(现金结存 + 授信 − 已使用授信, 0),不再减交易维度追加保证金。 /// [TestMethod] public void SB_004_合约维度_可用资金() { //现金20 + 授信100 − 已使用20 = 100 Assert.AreEqual(100, SwapSpanBalanceCalc.CalcContractDimensionAvailable( cashBalance: 20, totalCredit: 100, usedCredit: 20), 1e-6); } [TestMethod] public void SB_005_合约维度_扣尽截断为0() { //现金20 + 授信0 − 已使用30 = −10 → 0 Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionAvailable( cashBalance: 20, totalCredit: 0, usedCredit: 30), 1e-6); } /// /// 客户维度追保金额(定稿 2026-08-28:Max(...,0) 截断,恒 ≥ 0,只追不退)。 /// [TestMethod] public void SB_010_客户维度追保金额_正数需追保() { //追保差额 (维持150 − 初始100)=50;资金 现金5+授信0−已使用0=5 → 追保 = 50 − 5 = 45 Assert.AreEqual(45, SwapSpanBalanceCalc.CalcClientDimensionCallMargin( cashBalance: 5, totalCredit: 0, usedCredit: 0, initialMargin: 100, maintenanceMargin: 150), 1e-6); } [TestMethod] public void SB_011_客户维度追保金额_盈余截断为0() { //差额 (维持130−初始100)=30,资金 20+100−20=100 → 30 − 100 = −70 → 截断为 0(资金富余不展示负数) Assert.AreEqual(0, SwapSpanBalanceCalc.CalcClientDimensionCallMargin( cashBalance: 20, totalCredit: 100, usedCredit: 20, initialMargin: 100, maintenanceMargin: 130), 1e-6); } /// /// 客户维度追保金额与可用资金公式互为反向(各加 Max 截断):追保 = Max(−(未截断可用资金), 0)。 /// [TestMethod] public void SB_012_客户维度追保与可用资金反向一致() { var cash = 20d; var credit = 100d; var used = 20d; var initial = 100d; var maintenance = 130d; var available = SwapSpanBalanceCalc.CalcClientDimensionAvailable(cash, credit, used, initial, maintenance); var callMargin = SwapSpanBalanceCalc.CalcClientDimensionCallMargin(cash, credit, used, initial, maintenance); var availableUnfloored = cash + credit - used + initial - maintenance; Assert.AreEqual(Math.Max(-availableUnfloored, 0), callMargin, 1e-9); //可用资金被 0 截断时追保为正(需追保),两者不矛盾 if (availableUnfloored < 0) { Assert.AreEqual(0, available, 1e-9); Assert.IsTrue(callMargin > 0); } } /// /// 合约维度追保金额(2026-08-28 新口径):当日产生的追加保证金现金部分合计,不与现金结存轧差、 /// 闲置现金不冲抵、授信部分不追;Max(...,0) 兜底截断(正常无负记录,追保回落不返还)。 /// [TestMethod] public void SB_013_合约维度追保金额_当日现金追加全额() { //场景:入金60万、现金初保50万(闲置10万),当日需追加40万(现金)—— //旧口径缺口法追 30万(闲置现金被冲抵);新口径追当日现金追加全额 40万 Assert.AreEqual(400000, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(400000), 1e-6); } [TestMethod] public void SB_014_合约维度追保金额_无当日追加为0() { //当日追加全走授信(无现金流水)/当日无追保 → 0;负值兜底截断为 0 Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(0), 1e-6); Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(-100), 1e-6); } /// /// 可取资金(定稿 2026-08-28):只算现金部分(授信不可取现);min(持仓盈亏,0) 只扣浮亏、浮盈不放行。 /// [TestMethod] public void SB_015_客户维度可取资金_浮亏扣减() { //现金200 + 初始100 − 维持150 + min(−30,0) = 120 Assert.AreEqual(120, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund( cashBalance: 200, initialMargin: 100, maintenanceMargin: 150, positionPnl: -30), 1e-6); } [TestMethod] public void SB_016_客户维度可取资金_浮盈不放行() { //现金200 + 初始100 − 维持150 + min(50,0)=0 → 150 Assert.AreEqual(150, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund( cashBalance: 200, initialMargin: 100, maintenanceMargin: 150, positionPnl: 50), 1e-6); } [TestMethod] public void SB_017_客户维度可取资金_截断为0() { //现金20 + 初始100 − 维持150 + min(−40,0) = −70 → 0 Assert.AreEqual(0, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund( cashBalance: 20, initialMargin: 100, maintenanceMargin: 150, positionPnl: -40), 1e-6); } [TestMethod] public void SB_018_合约维度可取资金() { //Max(100 + min(−30,0), 0) = 70;浮亏超过现金时截断为 0 Assert.AreEqual(70, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 100, positionPnl: -30), 1e-6); Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 20, positionPnl: -50), 1e-6); //浮盈不放行:Max(100 + min(80,0), 0) = 100 Assert.AreEqual(100, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 100, positionPnl: 80), 1e-6); } } }