fix(swap): 多次部分平仓预付金返还默认显示初始值(GLMS-20260701-0008)
根因: GetUnwindInterests 利息腿迭代源取 origPositions(IsInitial=1), 其预付金腿 InterestPrincipalFix 恒为初始值(99000); 而"当前剩余本金"(73260) 存于实时腿 realPositions(IsInitial=0)。CalcNotionalByMode 与 InitSwapDealInterest 均读 position.InterestPrincipalFix, 导致多次部分平仓后平仓页默认按初始本金计算 返还。首次平仓 orig==real 掩盖了 bug, 故此前"只修好一次部分平仓"。 修复: 抽纯函数 ResolveInterestLegPositions(orig, real)——迭代源仍用 origPositions (保留 orig.id -> eod_swap_position.PositionId 的日终匹配, 全库 eod 均按 orig.id 归档, 换 realPositions 会破坏 preEod 匹配导致利息重算错误), 仅对预付金腿(初始5/追加6) Clone 覆盖 InterestPrincipalFix 为实时腿剩余本金; real.PositionId == orig.id 精确 1:1 关联; orig==real 时返回原始腿本身不克隆。 测试: 新增 SwapUnwindPrepayOrigVsRealBugTdd(6 用例)直接单测该纯函数, 覆盖既有 19 个用例(均绕过 orig/real 选择)的盲区。dotnet test --filter SwapUnwindPrepay 25 passed(19+6) 失败 0。
This commit is contained in:
@@ -500,7 +500,10 @@ namespace YLErp.Modules.SwapModule
|
||||
var allpositions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid).ToList();
|
||||
var origPositions = allpositions.Where(x => x.IsInitial).ToList();
|
||||
var realPostitions = allpositions.Where(x => !x.IsInitial).ToList();
|
||||
var positions = origPositions.Where(x => x.PosiDirection == 0).ToList();
|
||||
// 根因修复(多次部分平仓预付金返还错误):见 ResolveInterestLegPositions 注释。
|
||||
// 迭代源仍用 origPositions(保留 orig.id → eod_swap_position.PositionId 的日终匹配),
|
||||
// 仅对预付金腿以实时腿的剩余本金克隆覆盖,故此处不改任何日终匹配行为。
|
||||
var positions = ResolveInterestLegPositions(origPositions, realPostitions);
|
||||
var fpositions = origPositions.Where(x => x.PosiDirection > 0).ToList();
|
||||
var longPositions = fpositions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).ToList();
|
||||
var shortPositions = fpositions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).ToList();
|
||||
@@ -521,6 +524,40 @@ namespace YLErp.Modules.SwapModule
|
||||
return interests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析利息腿(PosiDirection==0)持仓,供 GetUnwindInterests 使用。抽为纯函数以便无库单测。
|
||||
/// <para>根因(多次部分平仓预付金返还错误):预付金腿(初始/追加)的"当前剩余本金"存于实时持仓
|
||||
/// realPositions.InterestPrincipalFix,每次平仓由 UpdateInitalPosition 递减;而原始腿
|
||||
/// origPositions(IsInitial=1)的 InterestPrincipalFix 恒为初始值。GetInterests 算
|
||||
/// closePrincipal = Fix × closePercent 与预付金计息基数 orginPv(InitSwapDealInterest) 时都读
|
||||
/// position.InterestPrincipalFix,若沿用原始腿,会在多次部分平仓后仍返还/计算初始本金(如始终 99000)。</para>
|
||||
/// <para>修复:迭代源仍用 origPositions(保留 orig.id → eod_swap_position.PositionId 的日终匹配,
|
||||
/// 全库实测 eod 均按 orig.id 归档;若换 realPositions 会破坏 preEod 匹配导致利息重算错误),仅对预付金腿
|
||||
/// Clone 覆盖其本金值为实时腿的剩余本金。real 与 orig 通过 real.PositionId == orig.id 精确 1:1 关联。
|
||||
/// 首次平仓时 orig==real 行为不变;仅在发生过部分平仓后 real≠orig 时用实时腿本金纠正。</para>
|
||||
/// </summary>
|
||||
/// <param name="origPositions">原始腿(IsInitial=1)全集</param>
|
||||
/// <param name="realPositions">实时腿(IsInitial=0)全集,其 PositionId 指向对应 orig 的 id</param>
|
||||
/// <returns>利息腿(PosiDirection==0)列表:预付金腿本金已对齐实时剩余本金,其余保持原始腿</returns>
|
||||
public static List<swap_position> ResolveInterestLegPositions(List<swap_position> origPositions, List<swap_position> realPositions)
|
||||
{
|
||||
realPositions ??= new List<swap_position>();
|
||||
return origPositions.Where(x => x.PosiDirection == 0).Select(p =>
|
||||
{
|
||||
if (p.InterestMode == (int)InterestModeEnum.初始预付金 || p.InterestMode == (int)InterestModeEnum.追加预付金)
|
||||
{
|
||||
var realLeg = realPositions.FirstOrDefault(r => r.PositionId == p.id);
|
||||
if (realLeg != null && realLeg.InterestPrincipalFix != p.InterestPrincipalFix)
|
||||
{
|
||||
var clone = p.Clone();
|
||||
clone.InterestPrincipalFix = realLeg.InterestPrincipalFix;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取利息腿"已通过历史互换结出的累计利息"(用于复利重算时扣除,类比分红的 CalcConsumedDividend)。
|
||||
/// 数据源为事件级 swap_flow_event.InterestAmount(互换/自动互换 完成态事件,互换当时即落库,不依赖日终归档)。
|
||||
|
||||
Reference in New Issue
Block a user