refactor: 提取融资腿计息领域模型 (AccrualPolicy/FundingLegRate/AccrualState/FundingLegAccrual) - 将 CalcDailySimpleInterestByEod 纯数学下沉至 FundingLegAccrual.AccrueSimpleEod, 消除 double 往返转换, 引入值对象收敛散落参数

This commit is contained in:
hjhan
2026-08-11 22:37:19 +08:00
parent 246392b665
commit 0c447ff5ff
6 changed files with 403 additions and 32 deletions
+38 -32
View File
@@ -7,6 +7,7 @@ using YLErp.Derivatives.Interest;
using YLErp.Helpers;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.EodModule;
using YLErp.Modules.SwapModule.Accrual;
using YLErp.Modules.SwapModule.FundingLegs;
using YLErp.Modules.SwapModule.Margin;
using YLErp.Modules.SwapModule.ReturnLegs;
@@ -1552,49 +1553,54 @@ namespace YLErp.Modules.SwapModule
/// </summary>
public void CalcDailySimpleInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
int interestPeriod = position.interest_rest_days ?? 1;
double floatRate = Convert.ToDouble(floateRate);
var calcDays = (endDate - tradeDate).Days;
// 修复:首次操作时(preEodPosition.id == 0),TdInterestPrincipal 需要正确初始化
// 首次操作(preEod.id == 0):计息基数按存量本金初始化——保留旧行为(含对 preEod 的就地修正)。
if (preEodPosition.id == 0)
{
preEodPosition.TdInterestPrincipal = posiPrincipal;
}
// 检查是否到达重置周期
if (calcDays % interestPeriod == 0)
// 取率:重置日按 interest_rule 重新定盘浮动利率(GLMS-JIATT-20260805 根因——
// 重置日=平仓日必须用新利率,否则沿用旧周期利率并污染后续 EOD)。
decimal effectiveFloat = floateRate;
int interestPeriod = position.interest_rest_days ?? 1;
if ((endDate - tradeDate).Days % interestPeriod == 0
&& !string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
var fixingDate = IndexFixerBase.GetFixingDate(endDate, position.interest_rule);
if (IndexFixer.TryGetFixing(fixingDate, position.FloatRateUnderlyingCode, out decimal fixing))
{
var fixingDate = IndexFixerBase.GetFixingDate(endDate, position.interest_rule);
if (IndexFixer.TryGetFixing(fixingDate, position.FloatRateUnderlyingCode, out decimal fixing))
{
if (fixing != 0m) floatRate = Convert.ToDouble(fixing);
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fixingDate:yyyy年MM月dd日}的价格");
}
if (fixing != 0m) effectiveFloat = fixing;
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fixingDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var baseTdInterestPrincipal = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
var baseInterestPrincipal = baseTdInterestPrincipal * closePercent;
flowEvent.FloatRate = effectiveFloat;
// 修复:正确计算本次利息(基于实际持仓本金)
decimal interest = baseInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
decimal tdinterest = baseTdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest /= annualDays;
tdinterest /= annualDays;
}
InterestAmount = Math.Round(interestProfitSum + interest, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
// 纯数学下沉至 FundingLegAccrualDDD 命名 + 末位生产精度 12 舍入),行为与上版逐字对齐。
// 利率构成按腿型封装:固定腿 → FixedRate;浮动腿 → Spread + IndexFixing(沿用旧实现 InterestRate+浮动利率 的口径)。
var isFixedLeg = string.IsNullOrEmpty(position.FloatRateUnderlyingCode);
var legRate = isFixedLeg
? new FundingLegRate(fixedRate: flowEvent.InterestRate)
: new FundingLegRate(spread: flowEvent.InterestRate, indexFixing: effectiveFloat);
var accrualPolicy = new AccrualPolicy(
convention: AccrualBoundary.Both,
isCompound: false,
resetPeriodDays: position.interest_rest_days ?? 1,
annualDays: annualDays,
isAnnualized: position.IsAnnualized);
var result = FundingLegAccrual.AccrueSimpleEod(
priorUnrealized: preEodPosition.InterestProfitSum,
priorAccrualPrincipal: preEodPosition.TdInterestPrincipal,
positionPrincipal: posiPrincipal,
closeRatio: closePercent,
originalPv: orginPv,
rate: legRate,
policy: accrualPolicy);
InterestAmount = result.Accrued;
TdInterestAmount = result.AccruedToday;
}
/// <summary>