diff --git a/YLErpDAL/Modules/SwapModule/ReturnLegs/InterestIncomeCalc.cs b/YLErpDAL/Modules/SwapModule/ReturnLegs/InterestIncomeCalc.cs
new file mode 100644
index 00000000..8f1b2aa4
--- /dev/null
+++ b/YLErpDAL/Modules/SwapModule/ReturnLegs/InterestIncomeCalc.cs
@@ -0,0 +1,21 @@
+namespace YLErp.Modules.SwapModule.ReturnLegs;
+
+///
+/// EOD 利息腿展示值计算——轻量单日公式(非 SwapDealService 的分段计息引擎)。
+///
+public static class InterestIncomeCalc
+{
+ /// 日应计利息 = 本金 × (固定利率 + 浮动利差) ÷ 年化天数(若年化)。
+ /// 原 3 处内联 `principal*(rate+float); if(annualized) /=annualDays` 收口到此。
+ public static decimal DailyAccrual(decimal principal, decimal rate, decimal floatRate, bool isAnnualized, int annualDays)
+ {
+ var amount = principal * (rate + floatRate);
+ return isAnnualized ? amount / annualDays : amount;
+ }
+
+ /// 已实现利息滚存。(prev + today×ratio, prevFee + todayFee)。
+ /// 原 4 处内联 2 行赋值收口到此。
+ public static (decimal Interest, decimal Fee) RollRealized(
+ decimal prevInterest, decimal prevFee, decimal closeInterest, decimal closeInterestFee, int ratio)
+ => (prevInterest + closeInterest * ratio, prevFee + closeInterestFee);
+}
diff --git a/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs b/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs
index 4bbb5620..7fab2a8f 100644
--- a/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs
+++ b/YLErpDAL/Modules/SwapModule/ReturnLegs/MtmCalc.cs
@@ -19,4 +19,8 @@ public static class MtmCalc
/// 收取=1, 支付=-1。
public static decimal UnrealizedPnl(decimal price, decimal costGrossPrice, decimal qty, decimal contractSize, int shortRatio, int ratio)
=> (price - costGrossPrice) * qty * contractSize * shortRatio * ratio;
+
+ /// 浮动端总未实现盈亏 = 盯市盈亏 + 分红 + 待结费用。原 4 处内联收口到此。
+ public static decimal ReturnLegProfitSum(decimal mtmPnl, decimal dividendSum, decimal feePending)
+ => mtmPnl + dividendSum + feePending;
}
diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index 7fa54924..d4b14f00 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -1081,11 +1081,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.TdCloseInterest = flowEvents.Sum(x => x.InterestAmount);
newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
//持仓内容-利息腿-损益统计(本方视角)
- var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
- if (position.IsAnnualized)
- {
- intersetAcmount /= tradeExtend.AnnualDays;
- }
+ var intersetAcmount = InterestIncomeCalc.DailyAccrual(
+ newEodPayPosition.TdInterestPrincipal, newEodPayPosition.TdInterestRate, newEodPayPosition.FloatRate,
+ newEodPayPosition.IsAnnualized, tradeExtend.AnnualDays);
newEodPayPosition.TdInterestIncome = intersetAcmount;// 要算一下当天产生的利息
var interestIncomeBeforeSettlement = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome;
var interestFeeBeforeSettlement = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee;
@@ -1111,8 +1109,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
//累计已实现
- newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
- newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
+ var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
+ newEodPayPosition.RealizedInterest = rolled.Interest;
+ newEodPayPosition.RealizedInterestFee = rolled.Fee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
DirectionRatio.RateType(position.InterestDirection));
@@ -1250,8 +1249,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
//累计已实现
- newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
- newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
+ var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
+ newEodPayPosition.RealizedInterest = rolled.Interest;
+ newEodPayPosition.RealizedInterestFee = rolled.Fee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
DirectionRatio.RateType(position.InterestDirection));
@@ -1408,11 +1408,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.TdCloseInterest = manualSettledInterestAmount + autoSettledInterestAmount;
// intersetAcmount 是收盘后本金的一天应计展示值。算尾部分平仓时,下面的复利分支会改用
// 平仓前全额本金重算当天新增,但跨日携带的 TdInterestPrincipal 仍只能是剩余本金。
- var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
- if (position.IsAnnualized)
- {
- intersetAcmount /= tradeExtend.AnnualDays;
- }
+ var intersetAcmount = InterestIncomeCalc.DailyAccrual(
+ newEodPayPosition.TdInterestPrincipal, newEodPayPosition.TdInterestRate, newEodPayPosition.FloatRate,
+ newEodPayPosition.IsAnnualized, tradeExtend.AnnualDays);
newEodPayPosition.TdInterestIncome = autoSwap
? intersetAcmount
: !hasPreviousEod
@@ -1462,12 +1460,9 @@ namespace YLErp.Modules.SwapModule
var accrualPrincipal = calcLast
? fullPrincipal
: newEodPayPosition.TdInterestPrincipal;
- newEodPayPosition.TdInterestIncome = accrualPrincipal
- * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
- if (position.IsAnnualized)
- {
- newEodPayPosition.TdInterestIncome /= tradeExtend.AnnualDays;
- }
+ newEodPayPosition.TdInterestIncome = InterestIncomeCalc.DailyAccrual(
+ accrualPrincipal, newEodPayPosition.TdInterestRate, newEodPayPosition.FloatRate,
+ newEodPayPosition.IsAnnualized, tradeExtend.AnnualDays);
}
if (!autoSwap
&& closePercent > 0m && closePercent < 1m
@@ -1513,8 +1508,9 @@ namespace YLErp.Modules.SwapModule
//累计已实现
// RealizedInterest 只增不回滚:上日累计已实现 + 当日结息按方向后的金额。
// 收取腿的 -37119.14 会把累计已实现更新为 -37119.14;后续普通 EOD 保持该值。
- newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
- newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
+ var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
+ newEodPayPosition.RealizedInterest = rolled.Interest;
+ newEodPayPosition.RealizedInterestFee = rolled.Fee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
DirectionRatio.RateType(position.InterestDirection));
@@ -1631,8 +1627,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
//累计已实现
- newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
- newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
+ var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
+ newEodPayPosition.RealizedInterest = rolled.Interest;
+ newEodPayPosition.RealizedInterestFee = rolled.Fee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
DirectionRatio.RateType(eodPayPosition.InterestDirection));
@@ -1724,7 +1721,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.TdPosiDividend = Math.Round(dividendIn * ratio, 2);
newEodPayPosition.PosiMtmPnL = MtmCalc.UnrealizedPnl(newEodPayPosition.UnderlyingPrice, newEodPayPosition.PosiGrossPrice, newEodPayPosition.PosiQuantity, newEodPayPosition.ContractSize, shortRatio, (int)ratio);
newEodPayPosition.PosiDividendSum = Math.Round(newEodPayPosition.TdPosiDividend - newEodPayPosition.TdCloseDividend, 2);
- newEodPayPosition.PosiProfitSum = newEodPayPosition.PosiMtmPnL + newEodPayPosition.PosiDividendSum + newEodPayPosition.PosiFeePending;
+ newEodPayPosition.PosiProfitSum = MtmCalc.ReturnLegProfitSum(newEodPayPosition.PosiMtmPnL, newEodPayPosition.PosiDividendSum, newEodPayPosition.PosiFeePending);
//持仓价值
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum);
@@ -1797,7 +1794,7 @@ namespace YLErp.Modules.SwapModule
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;
+ curretEod.PosiProfitSum = MtmCalc.ReturnLegProfitSum(curretEod.PosiMtmPnL, curretEod.PosiDividendSum, curretEod.PosiFeePending);
curretEod.TdCloseFee = 0;
curretEod.TdCloseQty = 0;
curretEod.TdCloseMtmPnl = 0;
@@ -1910,7 +1907,7 @@ namespace YLErp.Modules.SwapModule
SetFloatingRealizedPnl(curretEod);
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
- curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending;
+ curretEod.PosiProfitSum = MtmCalc.ReturnLegProfitSum(curretEod.PosiMtmPnL, curretEod.PosiDividendSum, curretEod.PosiFeePending);
if (curretEod.PosiStatus == 1)
{
curretEod.PosiNotionalValue = 0;
@@ -2083,7 +2080,7 @@ namespace YLErp.Modules.SwapModule
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.PosiProfitSum = MtmCalc.ReturnLegProfitSum(curretEod.PosiMtmPnL, curretEod.PosiDividendSum, curretEod.PosiFeePending);
curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl;
curretEod.RealizedDividend = curretEod.TdCloseDividend;
curretEod.RealizedFee = curretEod.TdCloseFee;