Files
zszq-trs/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs
T
hjhan ef37c8e71d refactor(swap): 合并多空名义本金参数为单一 posiTotalNotional 并加固测试
- DealInterests 的 posiLongNotionalValue + posiShortNotionalValue 合并为 posiTotalNotional(调用点以 posiLongNotional+posiShortNotional 求和传入),净减一个参数

- SwapDealService / SwapEodPositionService / InterestCalcRequest 同步收敛多空死管道参数

- 19 个测试调用点适配新签名

- SwapEodPositionServiceIntegrationTest 参数计数断言由裸数字改为参数名集合断言(CollectionAssert.AreEquivalent,对增删/重排/改名敏感)
2026-08-14 17:30:46 +08:00

165 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YLErp;
using YLErp.DBModels;
using YLErp.DBModels.Enums;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Margin;
namespace UnitTestProject.Modules.SwapModule.Margin
{
/// <summary>
/// 黄金回放验证(连真实测试库 192.168.2.96):对真实保证金交易逐日 EOD 比对
/// GetInterests(settment=true,保证金分支现走 CalcMarginInterest) vs 直接调 CalcMarginInterest
/// 验证 GetInterests→CalcMarginInterest 接线的参数对齐(rate/posiPrincipal/preEod 等)正确。
///
/// 数据来自 96 库的真实保证金交易,覆盖追加预付金多行、多次部分平仓(InterestPrincipalFix 下台阶)、
/// 跨 EOD 续接等单元测试够不到的边界。作为保证金计息迁移后的真实库回归守护。
/// </summary>
[TestClass]
public class MarginInterestGoldenReplayTest
{
// 96 库里已确认含真实保证金腿的交易
private static readonly string[] TradeNumbers =
{
"GLMS-20260701-0008",
"GLMS-20260701-0013",
"GLMS-20260701-0006",
};
private sealed class StubSvc : SwapDealService
{
public StubSvc() : base(new OptUserInfo(0, nameof(MarginInterestGoldenReplayTest), OptUserFrom.UnitTest)) { }
}
/// <summary>
/// 逐交易、逐 EOD 日,比对保证金腿新旧计息 InterestAmount/TdInterestAmount。
/// 入参对齐口径(与 GetInterests 内部一致):
/// posiPrincipal = InterestPrincipalFixclosePrincipal = Fix×closePercent(EOD=1)
/// rate = oldEvt.InterestRate(严格取旧管线算出的 rate,消除 GetFixedRate 差异);
/// 方向 = FlipDirection(position.InterestDirection)GetInterests:742 对保证金翻转);
/// preEod = 该 PositionId 上一日终 eod_swap_positionannualDays/calcFirst/calcLast 来自 trade_extend。
/// </summary>
[TestMethod]
[TestCategory("DbDiagnose")]
public void 保证金腿_真实库_EOD逐日新旧比对()
{
YLContext db;
try { db = DbContextFactory.GetYLDbContext(); }
catch (Exception ex) { Assert.Inconclusive($"无法连接测试库(192.168.2.96){ex.Message}"); return; }
var svc = new StubSvc();
int totalCompared = 0, mismatches = 0, skipped = 0;
var diffLog = new StringBuilder();
foreach (var tradeNumber in TradeNumbers)
{
var td = db.trade.FirstOrDefault(t => t.TradeNumber == tradeNumber);
if (td == null) { Console.WriteLine($"跳过:库无 {tradeNumber}"); skipped++; continue; }
var extend = db.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
int annualDays = extend?.ExtendObj.AnnualDays ?? 365;
bool calcFirst = extend?.ExtendObj.InterestCalcMode?.StartsWith("1") ?? true;
bool calcLast = extend?.ExtendObj.InterestCalcMode?.EndsWith("1") ?? true;
var marginPositions = db.swap_position
.Where(p => p.SwapTradeId == td.id && !p.Invalid
&& (p.InterestMode == (int)InterestModeEnum.初始预付金
|| p.InterestMode == (int)InterestModeEnum.追加预付金))
.ToList();
if (marginPositions.Count == 0) { Console.WriteLine($"跳过:{tradeNumber} 无保证金腿"); skipped++; continue; }
// 该交易保证金腿的 EOD 日期序列
var eodDates = db.eod_swap_position
.Where(e => e.SwapTradeId == td.id && (e.InterestMode == 5 || e.InterestMode == 6))
.Select(e => e.ValueDate).Distinct().OrderBy(d => d).ToList();
Console.WriteLine($"===== {tradeNumber} (id={td.id}){marginPositions.Count} 条保证金腿,{eodDates.Count} 个 EOD 日 =====");
foreach (var valueDate in eodDates)
{
// 上一日终 preEod(取 eod_swap 最近 < valueDate 的日期)
var preDate = db.eod_swap
.Where(e => e.SwapTradeId == td.id && e.ValueDate < valueDate)
.OrderByDescending(e => e.ValueDate)
.Select(e => (DateTime?)e.ValueDate).FirstOrDefault();
var preEods = preDate == null
? new List<eod_swap_position>()
: db.eod_swap_position
.Where(e => e.SwapTradeId == td.id && e.ValueDate == preDate.Value
&& (e.InterestMode == 5 || e.InterestMode == 6))
.ToList();
// 旧管线:GetInterests(settment=true)。保证金分支不用 posiNotionalValue/closePosiNotionalValue/grossPrice/orginPv,传 0。
List<swap_flow_event> oldList;
try
{
oldList = svc.GetInterests(td, extend, valueDate, valueDate,
preEods, marginPositions,
0m, 0m, 1.0m,
(int)SwapEventTypeEnum.自动互换, tdClose: false,
orginPv: 0m,
add: false, settment: true, newCalcLast: false, closeList: null);
}
catch (Exception ex)
{
Console.WriteLine($" {tradeNumber} @ {valueDate:yyyy-MM-dd} 旧管线异常:{ex.GetType().Name} {ex.Message}");
continue;
}
// 新方法:逐保证金腿
foreach (var pos in marginPositions)
{
var oldEvt = oldList.FirstOrDefault(i => i.PositionId == pos.id);
if (oldEvt == null) continue;
var preEod = preEods.FirstOrDefault(e => e.PositionId == pos.id) ?? new eod_swap_position { id = 0 };
var posClone = pos.Clone();
posClone.InterestDirection = MarginCalc.FlipDirection(pos.InterestDirection);
decimal rate = oldEvt.InterestRate; // 严格对齐旧管线 rate(含 GetFixedRate + Round(12)
swap_flow_event newEvt;
try
{
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);
}
catch (Exception ex)
{
mismatches++;
diffLog.AppendLine($"✗ {tradeNumber} PosId={pos.id} @ {valueDate:yyyy-MM-dd} 新方法异常:{ex.GetType().Name} {ex.Message}");
continue;
}
totalCompared++;
decimal diffI = Math.Abs(newEvt.InterestAmount - oldEvt.InterestAmount);
decimal diffTd = Math.Abs(newEvt.TdInterestAmount - oldEvt.TdInterestAmount);
const decimal tol = 0.000001m;
if (diffI > tol || diffTd > tol)
{
mismatches++;
diffLog.AppendLine($"✗ {tradeNumber} PosId={pos.id} Mode={pos.InterestMode} @ {valueDate:yyyy-MM-dd}: " +
$"旧 I={oldEvt.InterestAmount} Td={oldEvt.TdInterestAmount} | " +
$"新 I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount} | " +
$"diffI={diffI} diffTd={diffTd} | " +
$"preEod.id={preEod.id} TdIntPrin={preEod.TdInterestPrincipal} ProfitSum={preEod.InterestProfitSum} | " +
$"Fix={pos.InterestPrincipalFix} rate={rate} IntType={pos.InterestType} IsAnnualized={pos.IsAnnualized}");
}
}
}
}
Console.WriteLine($"\n===== 比对汇总:共 {totalCompared} 条,不一致 {mismatches} 条,跳过 {skipped} 个交易 =====");
if (diffLog.Length > 0) Console.WriteLine(diffLog.ToString());
Assert.IsTrue(mismatches == 0,
$"保证金新旧管线 EOD 真实库比对有 {mismatches}/{totalCompared} 条不一致——提交2 前必须解决(详见输出)");
}
}
}