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;