refactor(margin): 保证金计息切换到 CalcMarginInterest + 删 orginPv 外部维度 hack
- GetInterests 保证金分支(5/6)切到 CalcMarginInterest,不再走融资腿通用 CalcEodInterest/CalcUnwindInterest - 删除 InitSwapDealInterest 的保证金 orginPv 维度 hack(保证金已不走该方法,成为死代码) - CalcMarginInterest 修正三处与旧管线的对齐(全量回归发现): · 加 endDate 参数(盘中用 InitInterestDate 的 endDate,否则少算天数) · calcLast 合并 newCalcLast(与 CalcUnwindInterest 一致,算尾) · 盘中保留 accrualBasis 差分(posiPrincipal 对齐状态路径相关,单一本金变量无法覆盖;orginPv 内部按 PreviousBalance 算,消除外部维度 hack) - 更新 Shadow/GoldenReplay 测试调用点(加 endDate) 修正说明:盘中“消除差分”不可行——SPC_006(position 已对齐) 与 PrepaidPrincipalCloseTrace(position 未对齐) 期望相反,差分 accrualBasis 经 orginPv 自适应两种状态。CalcMarginInterest 真实收益收敛为:保证金领域独立 + orginPv 内聚 + 消除浮动/分段/复利死分支,而非消除差分。EOD 路径无差分(用昨日终本金)。 验证:全量 SwapModule 522/522 通过(0 失败);真实库黄金回放 60 条 0 差异。
This commit is contained in:
@@ -125,7 +125,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
swap_flow_event newEvt;
|
||||
try
|
||||
{
|
||||
newEvt = svc.CalcMarginInterest(td, valueDate, posClone, rate,
|
||||
newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, posClone, rate,
|
||||
pos.InterestPrincipalFix, pos.InterestPrincipalFix, 1.0m,
|
||||
annualDays, calcFirst, calcLast, preEod,
|
||||
(int)SwapEventTypeEnum.自动互换, add: false, settment: true, swap: false);
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd);
|
||||
|
||||
// 新方法(独立 preEod,相同初始值)
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
AnnualDays, calcFirst: true, calcLast: true,
|
||||
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: true, swap: false);
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
valueDate, td.StartDate.Value, position, Principal, Principal,
|
||||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd);
|
||||
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
AnnualDays, calcFirst: true, calcLast: true,
|
||||
new eod_swap_position { id = 0 }, 0, add: false, settment: true, swap: false);
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
|
||||
|
||||
// 新方法:notional = posiPrincipal(无差分、无 orginPv)
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
AnnualDays, calcFirst: true, calcLast: false,
|
||||
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false);
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, closePct, orginPv,
|
||||
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
|
||||
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate,
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate,
|
||||
Principal * closePct, Principal, closePct,
|
||||
AnnualDays, calcFirst: true, calcLast: false,
|
||||
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false);
|
||||
@@ -191,7 +191,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
|
||||
var valueDate = StartDate.AddDays(5);
|
||||
|
||||
var svc = new StubSvc();
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
|
||||
AnnualDays, calcFirst: true, calcLast: false,
|
||||
CreatePreEod(StartDate.AddDays(4), 820m), 0, add: false, settment: false, swap: true);
|
||||
|
||||
|
||||
@@ -697,7 +697,13 @@ namespace YLErp.Modules.SwapModule
|
||||
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone);
|
||||
|
||||
// 根据场景计算利息
|
||||
if (settment)
|
||||
if (MarginModes.Contains(position.InterestMode))
|
||||
{
|
||||
// 保证金腿(5/6):专属计息,notional 直接取保证金余额,无融资腿差分公式与 orginPv 维度 hack
|
||||
interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, closePrincipal, posiPrincipal,
|
||||
newClosePercent, annualDays, calcFirst, calcLast||newCalcLast, preEodPosition, eventType, add, settment, swap));
|
||||
}
|
||||
else if (settment)
|
||||
{
|
||||
// 收盘归档场景,使用 CalcEodInterest
|
||||
interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add));
|
||||
@@ -921,17 +927,18 @@ namespace YLErp.Modules.SwapModule
|
||||
///
|
||||
/// 保证金是纯固定利率单利:浮动利率(FR007)/分段利率/复利对其均为死分支(前端无入口、
|
||||
/// 确认书不含、FundingLegRate.Build 对空 FloatRateUnderlyingCode 恒返回 Fixed)。故本方法直接用
|
||||
/// SimpleInterestAccrual 纯函数计息,notional 取保证金余额本身:
|
||||
/// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源)
|
||||
/// 盘中 = 今日本金 posiPrincipal(InterestPrincipalFix)
|
||||
/// 消除融资腿差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 与 orginPv 维度
|
||||
/// hack——对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余。保留累计语义(priorAccrued + 增量),
|
||||
/// 满足下游 SwapEodPositionService 字段契约(InterestAmount=缩放累计、TdInterestAmount=单日参考等)。
|
||||
/// SimpleInterestAccrual 纯函数计息,本金取保证金余额:
|
||||
/// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源,无差分)
|
||||
/// 盘中 = accrualBasis(preEod.TdInterestPrincipal + posiPrincipal - orginPv)
|
||||
/// 盘中保留差分是必要的:posiPrincipal 是否经 ResolveInterestLegPositions 对齐到实时剩余是路径相关的
|
||||
/// (生产对齐 / 诊断测试用原始腿),单一本金变量无法覆盖两种状态,差分经 orginPv 自适应。orginPv 在
|
||||
/// 本方法内部按保证金维度计算(PreviousBalance),消除原 InitSwapDealInterest 的外部维度 hack
|
||||
/// (融资腿 orginPv=浮动端名义本金)。保留累计语义(priorAccrued + 增量),满足下游字段契约。
|
||||
/// </summary>
|
||||
/// <param name="settment">true=收盘归档(EOD),false=盘中平仓/互换。</param>
|
||||
/// <param name="swap">互换事件(仅盘中生效,true 时利息归零,同 InitSwapDealInterest)。</param>
|
||||
public swap_flow_event CalcMarginInterest(
|
||||
trade td, DateTime valueDate, swap_position position, decimal rate,
|
||||
trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate,
|
||||
decimal closePrincipal, decimal posiPrincipal, decimal closePercent,
|
||||
int annualDays, bool calcFirst, bool calcLast,
|
||||
eod_swap_position preEod, int eventType, bool add, bool settment, bool swap)
|
||||
@@ -967,7 +974,7 @@ namespace YLErp.Modules.SwapModule
|
||||
FloatRate = 0m,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成,
|
||||
ClientId = td.ClientId,
|
||||
UnwindDate = valueDate
|
||||
UnwindDate = settment ? valueDate : endDate
|
||||
};
|
||||
|
||||
// 互换事件:利息归零(同 InitSwapDealInterest)
|
||||
@@ -1000,22 +1007,27 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
// 盘中:notional = 今日本金 posiPrincipal(无差分、无 orginPv)
|
||||
// 盘中:accrualBasis 自适应"实时剩余本金"——posiPrincipal 已对齐(ResolveInterestLegPositions)
|
||||
// 时 = posiPrincipal;未对齐的原始腿经 orginPv(=PreviousBalance 昨日终) 修正回昨日终剩余。
|
||||
// 单一本金变量无法覆盖两种 position 状态,故保留差分(与 EOD 直接用 preEod.TdInterestPrincipal 不同)。
|
||||
// orginPv 在此内部按保证金维度计算,消除原 InitSwapDealInterest 的外部维度 hack。
|
||||
var orginPv = MarginCalc.PreviousBalance(preEod, posiPrincipal);
|
||||
var accrualBasis = preEod.TdInterestPrincipal + posiPrincipal - orginPv;
|
||||
var segmentRates = new List<(DateTime, decimal)> { (position.PosiStartDate, rate) };
|
||||
var r = SimpleInterestAccrual.AccruePeriod(
|
||||
priorAccrued: preEod.InterestProfitSum * closePercent,
|
||||
notional: posiPrincipal,
|
||||
notional: accrualBasis,
|
||||
unwindFraction: closePercent,
|
||||
segmentRates: segmentRates,
|
||||
startDate: position.PosiStartDate,
|
||||
endDate: valueDate,
|
||||
endDate: endDate,
|
||||
priorValueDate: preEod.ValueDate,
|
||||
boundary: AccrualBoundary.Of(calcFirst, calcLast),
|
||||
annualDays: annualDays,
|
||||
isAnnualized: position.IsAnnualized);
|
||||
interestAmount = r.Accrued;
|
||||
tdInterestAmount = r.AccruedToday;
|
||||
interest.InterestPrincipal = posiPrincipal * closePercent; // 同 CalcDailySimpleInterest:1304
|
||||
interest.InterestPrincipal = accrualBasis * closePercent; // 同 CalcDailySimpleInterest:1304
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1135,11 +1147,8 @@ namespace YLErp.Modules.SwapModule
|
||||
interest.ClientId = td.ClientId;
|
||||
interest.UnwindDate = endDate;
|
||||
|
||||
// 保证金腿的 orginPv 对齐到保证金本金维度,避免差分公式维度不匹配算出巨负值
|
||||
if (MarginModes.Contains(position.InterestMode))
|
||||
{
|
||||
orginPv = MarginCalc.PreviousBalance(preEodPosition, position.InterestPrincipalFix);
|
||||
}
|
||||
// 保证金腿已走 CalcMarginInterest(不经过本方法),orginPv 维度重映射不再需要;
|
||||
// orginPv 此处仅对融资腿生效(差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv)。
|
||||
|
||||
if (swap)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user