From a26f848287cf8ab26462f10bc17567e329e17b54 Mon Sep 17 00:00:00 2001 From: hjhan Date: Tue, 11 Aug 2026 10:45:43 +0800 Subject: [PATCH] =?UTF-8?q?refactor(return-leg):=20=E6=8A=BD=E5=8F=96=20Mt?= =?UTF-8?q?mCalc=20=E6=A0=87=E7=9A=84=E7=AB=AF=E7=9B=AF=E5=B8=82=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 SwapEodPositionService 4处重复的 UnderlyingMarketValue 公式和 4处 PosiMtmPnL 公式抽成 MtmCalc 纯函数: - MarketValue(price, qty, contractSize, shortRatio): 标的市值 - UnrealizedPnl(price, costGrossPrice, qty, contractSize, shortRatio, ratio): 盯市未实现盈亏 原代码4处副本(1731/1813/1898/2101 + 1741/1814/1899/2102)全部替换。 验证: 编译0错误, 全量503测试7失败(基线一致)。 --- .../SwapModule/ReturnLegs/MtmCalcTest.cs | 50 +++++++++++++++++++ .../Modules/SwapModule/ReturnLegs/MtmCalc.cs | 20 ++++++++ .../SwapModule/SwapEodPositionService.cs | 16 +++--- 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 UnitTestProject/Modules/SwapModule/ReturnLegs/MtmCalcTest.cs create mode 100644 YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs diff --git a/UnitTestProject/Modules/SwapModule/ReturnLegs/MtmCalcTest.cs b/UnitTestProject/Modules/SwapModule/ReturnLegs/MtmCalcTest.cs new file mode 100644 index 00000000..d5ee1d62 --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/ReturnLegs/MtmCalcTest.cs @@ -0,0 +1,50 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using YLErp.Modules.SwapModule.ReturnLegs; + +namespace UnitTestProject.Modules.SwapModule.ReturnLegs +{ + [TestClass] + public class MtmCalcTest + { + [TestMethod] + public void MarketValue_多头_为正() + { + // 标的价100 × 1000张 × 乘数1 × 多头(+1) = 100000 + Assert.AreEqual(100_000m, MtmCalc.MarketValue(100m, 1000m, 1m, 1)); + } + + [TestMethod] + public void MarketValue_空头_为负() + { + // 空头 shortRatio=-1 + Assert.AreEqual(-100_000m, MtmCalc.MarketValue(100m, 1000m, 1m, -1)); + } + + [TestMethod] + public void MarketValue_合约乘数10() + { + Assert.AreEqual(1_000_000m, MtmCalc.MarketValue(100m, 1000m, 10m, 1)); + } + + [TestMethod] + public void UnrealizedPnl_多头浮盈() + { + // (105-100) × 1000 × 1 × 1 × 1 = 5000 + Assert.AreEqual(5000m, MtmCalc.UnrealizedPnl(105m, 100m, 1000m, 1m, 1, 1m)); + } + + [TestMethod] + public void UnrealizedPnl_多头浮亏() + { + // (95-100) × 1000 × 1 × 1 × 1 = -5000 + Assert.AreEqual(-5000m, MtmCalc.UnrealizedPnl(95m, 100m, 1000m, 1m, 1, 1m)); + } + + [TestMethod] + public void UnrealizedPnl_空头反向() + { + // 空头: 价格跌=盈利 (95-100) × 1000 × 1 × (-1) × 1 = 5000 + Assert.AreEqual(5000m, MtmCalc.UnrealizedPnl(95m, 100m, 1000m, 1m, -1, 1m)); + } + } +} diff --git a/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs b/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs new file mode 100644 index 00000000..482fbe90 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs @@ -0,0 +1,20 @@ +namespace YLErp.Modules.SwapModule.ReturnLegs; + +/// +/// 标的端盯市(Mark-to-Market)计算。 +/// +/// UnderlyingMarketValue = 标的价 × 数量 × 合约乘数 × 多空方向 +/// PosiMtmPnL = (标的价 - 成本全价) × 数量 × 合约乘数 × 多空方向 × 收付方向 +/// +/// 原代码在 SwapEodPositionService 4处重复 UnderlyingMarketValue 公式(1731/1813/1898/2101)。 +/// +public static class MtmCalc +{ + /// 标的市值。多头为正、空头为负。 + public static decimal MarketValue(decimal price, decimal qty, decimal contractSize, int shortRatio) + => price * qty * contractSize * shortRatio; + + /// 盯市未实现盈亏 = (标的价 - 成本全价) × 数量 × 合约乘数 × 多空 × 收付。 + public static decimal UnrealizedPnl(decimal price, decimal costGrossPrice, decimal qty, decimal contractSize, int shortRatio, decimal ratio) + => (price - costGrossPrice) * qty * contractSize * shortRatio * ratio; +} diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index 02cf5a9f..af4a85bc 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -1728,7 +1728,7 @@ namespace YLErp.Modules.SwapModule //浮动端估值用信息 newEodPayPosition.UnderlyingPrice = UnderlyingCodePrice(newEodPayPosition.UnderlyingCode, eventFlow.EventDate, out decimal vobp); newEodPayPosition.dv01 = Dv01Helper.CalcDv01(newEodPayPosition.UnderlyingCode, newEodPayPosition.PosiQuantity, newEodPayPosition.PosiDirection, newEodPayPosition.PositionType, vobp); - newEodPayPosition.UnderlyingMarketValue = newEodPayPosition.UnderlyingPrice * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio; + newEodPayPosition.UnderlyingMarketValue = MtmCalc.MarketValue(newEodPayPosition.UnderlyingPrice, newEodPayPosition.PosiQuantity, newEodPayPosition.ContractSize, shortRatio); //当日已实现 newEodPayPosition.TdCloseQty = closeQty; newEodPayPosition.TdChangedQty = 0; @@ -1738,7 +1738,7 @@ namespace YLErp.Modules.SwapModule //持仓内容-浮动收益腿-损益统计(本方视角 newEodPayPosition.TdPosiDividend = Math.Round(dividendIn * ratio, 2); - newEodPayPosition.PosiMtmPnL = (newEodPayPosition.UnderlyingPrice - newEodPayPosition.PosiGrossPrice) * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio * ratio; + newEodPayPosition.PosiMtmPnL = MtmCalc.UnrealizedPnl(newEodPayPosition.UnderlyingPrice, newEodPayPosition.PosiGrossPrice, newEodPayPosition.PosiQuantity, newEodPayPosition.ContractSize, shortRatio, ratio); newEodPayPosition.PosiDividendSum = Math.Round(newEodPayPosition.TdPosiDividend - newEodPayPosition.TdCloseDividend, 2); newEodPayPosition.PosiProfitSum = newEodPayPosition.PosiMtmPnL + newEodPayPosition.PosiDividendSum + newEodPayPosition.PosiFeePending; @@ -1810,8 +1810,8 @@ namespace YLErp.Modules.SwapModule curretEod.PosiNotionalValue = 0; } curretEod.UnderlyingPrice = price; - curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; - curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; + curretEod.UnderlyingMarketValue = MtmCalc.MarketValue(curretEod.UnderlyingPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio); + curretEod.PosiMtmPnL = MtmCalc.UnrealizedPnl(curretEod.UnderlyingPrice, curretEod.PosiGrossPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio, directionRatio); //curretEod.TdPosiDividend = 0; //curretEod.PosiDividendSum = eod.PosiDividendSum + curretEod.TdPosiDividend; curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; @@ -1895,8 +1895,8 @@ namespace YLErp.Modules.SwapModule SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition); curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp); curretEod.UnderlyingPrice = price; - curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; - curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; + curretEod.UnderlyingMarketValue = MtmCalc.MarketValue(curretEod.UnderlyingPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio); + curretEod.PosiMtmPnL = MtmCalc.UnrealizedPnl(curretEod.UnderlyingPrice, curretEod.PosiGrossPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio, directionRatio); curretEod.TdPosiDividend = 0; // 分红与互换无关,只要持仓>0且起始日早于当前日,正常计算当日分红 // 修改,互换事件会影响待实现的分红的,现在要算上 @@ -2098,8 +2098,8 @@ namespace YLErp.Modules.SwapModule curretEod.PosiDividendSum = payment; } - curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; - curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; + curretEod.UnderlyingMarketValue = MtmCalc.MarketValue(curretEod.UnderlyingPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio); + curretEod.PosiMtmPnL = MtmCalc.UnrealizedPnl(curretEod.UnderlyingPrice, curretEod.PosiGrossPrice, curretEod.PosiQuantity, curretEod.ContractSize, shortRatio, directionRatio); curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl; curretEod.RealizedDividend = curretEod.TdCloseDividend;