test(swap): 修复 GLMS20260701DbDiagnoseTest 编译错误并加 2.3c 模拟 API 流程
L208-209 Convert.ToDecimal 修复 double? decimal 隐式转换错误(CS0266)和非 nullable double ?? 0 错误(CS0019). 新增 2.3b 直接调用 ResolveInterestLegPositions 验证 Clone 行为, 2.3c 模拟 controller 完整流程(ToRemainingClosePercent + GetUnwindInterests) 等价于 HTTP API. 该测试带 [TestCategory(DbDiagnose)] 标记不进 CI, 作为可重复手动诊断工具保留.
This commit is contained in:
@@ -18,7 +18,8 @@ namespace YLErp.Modules.SwapModule
|
||||
[TestClass]
|
||||
public class GLMS20260701DbDiagnoseTest
|
||||
{
|
||||
private const string TradeNumber = "GLMS-20260701-0008";
|
||||
private const string TradeNumber_0008 = "GLMS-20260701-0008";
|
||||
private const string TradeNumber_0013 = "GLMS-20260701-0013";
|
||||
|
||||
#region 1) 录真实数据快照(手动跑)
|
||||
|
||||
@@ -31,8 +32,8 @@ namespace YLErp.Modules.SwapModule
|
||||
try { db = DbContextFactory.GetYLDbContext(); }
|
||||
catch (Exception ex) { Assert.Inconclusive($"无法连接测试库:{ex.Message}"); return; }
|
||||
|
||||
var td = db.trade.FirstOrDefault(t => t.TradeNumber == TradeNumber);
|
||||
Assert.IsNotNull(td, $"测试库无交易 {TradeNumber},请确认环境");
|
||||
var td = db.trade.FirstOrDefault(t => t.TradeNumber == TradeNumber_0008);
|
||||
Assert.IsNotNull(td, $"测试库无交易 {TradeNumber_0008},请确认环境");
|
||||
|
||||
var snapshot = new JObject
|
||||
{
|
||||
@@ -99,13 +100,25 @@ namespace YLErp.Modules.SwapModule
|
||||
[TestMethod]
|
||||
[TestCategory("DbDiagnose")]
|
||||
public void Diagnose_InterestPrincipalFix_Progression_And_UnwindResult()
|
||||
{
|
||||
DiagnoseTrade(TradeNumber_0008);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("DbDiagnose")]
|
||||
public void Diagnose_0013_InterestPrincipalFix_Progression_And_UnwindResult()
|
||||
{
|
||||
DiagnoseTrade(TradeNumber_0013);
|
||||
}
|
||||
|
||||
private void DiagnoseTrade(string tradeNumber)
|
||||
{
|
||||
YLContext db;
|
||||
try { db = DbContextFactory.GetYLDbContext(); }
|
||||
catch (Exception ex) { Assert.Inconclusive($"无法连接测试库:{ex.Message}"); return; }
|
||||
|
||||
var td = db.trade.FirstOrDefault(t => t.TradeNumber == TradeNumber);
|
||||
if (td == null) { Assert.Inconclusive($"测试库无 {TradeNumber}"); return; }
|
||||
var td = db.trade.FirstOrDefault(t => t.TradeNumber == tradeNumber);
|
||||
if (td == null) { Assert.Inconclusive($"测试库无 {tradeNumber}"); return; }
|
||||
|
||||
// 2.1 预付金腿 position.InterestPrincipalFix 当前值(多次平仓后应该已被扣减)
|
||||
var marginPositions = db.swap_position
|
||||
@@ -127,11 +140,11 @@ namespace YLErp.Modules.SwapModule
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine("\n============== 全部 position 全景(对比 IsInitial 原始 vs !IsInitial 剩余) ==============");
|
||||
Console.WriteLine($" {"Id",-8}{"Mode",-6}{"Dir",-6}{"IsInit",-8}{"Fix",-18}{"PosiNotional",-18}{"PosiQty",-12}{"UnderlyingCode",-15}");
|
||||
Console.WriteLine($" {"Id",-8}{"Mode",-6}{"IntDir",-7}{"PosiDir",-8}{"IsInit",-8}{"Fix",-18}{"PosiNotional",-18}{"PosiQty",-12}{"UnderlyingCode",-15}");
|
||||
foreach (var p in allPositions)
|
||||
{
|
||||
var ul = p.UnderlyingCode ?? "";
|
||||
Console.WriteLine($" {p.id,-8}{p.InterestMode,-6}{p.InterestDirection,-6}{p.IsInitial,-8}{p.InterestPrincipalFix,-18}{p.PosiNotionalValue,-18}{p.PosiQuantity,-12}{ul,-15}");
|
||||
Console.WriteLine($" {p.id,-8}{p.InterestMode,-6}{p.InterestDirection,-7}{p.PosiDirection,-8}{p.IsInitial,-8}{p.InterestPrincipalFix,-18}{p.PosiNotionalValue,-18}{p.PosiQuantity,-12}{ul,-15}");
|
||||
}
|
||||
|
||||
// 2.1c 关键诊断:GetUnwindInterests 内部 origPositions vs realPostitions 差异
|
||||
@@ -143,6 +156,14 @@ namespace YLErp.Modules.SwapModule
|
||||
Console.WriteLine($" origPositions(IsInitial=True) 预付金腿 Fix: {string.Join(",", origPositions.Where(x => x.InterestMode == 5 || x.InterestMode == 6).Select(x => x.InterestPrincipalFix))}");
|
||||
Console.WriteLine($" realPostitions(IsInitial=False) 预付金腿 Fix: {string.Join(",", realPostitions.Where(x => x.InterestMode == 5 || x.InterestMode == 6).Select(x => x.InterestPrincipalFix))} ← 应为剩余值");
|
||||
|
||||
// 2.1d 关键诊断:realLeg.PositionId == origPos.id 匹配校验(修复后端 Clone 是否会触发)
|
||||
Console.WriteLine("\n============== realLeg.PositionId ↔ origPos.id 匹配校验(决定 Clone 是否生效)==============");
|
||||
foreach (var origPos in origPositions.Where(p => p.InterestMode == 5 || p.InterestMode == 6))
|
||||
{
|
||||
var realLeg = realPostitions.FirstOrDefault(r => r.PositionId == origPos.id);
|
||||
Console.WriteLine($" origPos.id={origPos.id} Fix={origPos.InterestPrincipalFix} | realLeg found={(realLeg != null)} | realLeg.id={realLeg?.id} realLeg.PositionId={realLeg?.PositionId} realLeg.Fix={realLeg?.InterestPrincipalFix} | 需Clone={(realLeg != null && realLeg.InterestPrincipalFix != origPos.InterestPrincipalFix)}");
|
||||
}
|
||||
|
||||
// 2.2 EOD 持仓 InterestPrincipalFix 逐日序列
|
||||
var eodMarginSeq = db.eod_swap_position
|
||||
.Where(e => e.SwapTradeId == td.id && !e.Invalid
|
||||
@@ -171,12 +192,38 @@ namespace YLErp.Modules.SwapModule
|
||||
Console.WriteLine($" EventDate={f.EventDate:yyyy-MM-dd} PositionId={f.PositionId} InterestPrincipal={f.InterestPrincipal} InterestAmount={f.InterestAmount} Quantity={f.Quantity} TradingAmount={f.TradingAmount}");
|
||||
}
|
||||
|
||||
// 2.3b 直接调 ResolveInterestLegPositions,验证 Clone 是否真的把 Fix 覆盖成 realLeg 值
|
||||
var resolved = SwapDealService.ResolveInterestLegPositions(origPositions, realPostitions);
|
||||
Console.WriteLine("\n============== ResolveInterestLegPositions 直接调用结果 ==============");
|
||||
foreach (var rp in resolved.Where(x => x.InterestMode == 5 || x.InterestMode == 6))
|
||||
{
|
||||
Console.WriteLine($" resolved: id={rp.id} PositionId={rp.PositionId} Mode={rp.InterestMode} Fix={rp.InterestPrincipalFix} (期望=realLeg.Fix)");
|
||||
}
|
||||
|
||||
// 2.3c 模拟前端调用 controller 完整流程:前端传 closePercent=0.7(占期初) + notionalValue/posiNotionalValue
|
||||
// controller 调 ToRemainingClosePercent 转为占剩余,再调 GetUnwindInterests
|
||||
// 等价于 HTTP POST /swaptrade2/GetUnwindInterestList
|
||||
Console.WriteLine("\n============== 模拟 HTTP API 调用(前端 closePercent=0.7 占期初)==============");
|
||||
decimal frontClosePercent = 0.7m;
|
||||
decimal frontNotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0d); // 期初名义本金
|
||||
decimal frontPosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional); // 剩余名义本金
|
||||
Console.WriteLine($" 前端参数: closePercent={frontClosePercent} notionalValue={frontNotionalValue} posiNotionalValue={frontPosiNotionalValue}");
|
||||
decimal convertedClosePercent = SwapDealService.ToRemainingClosePercent(frontClosePercent, frontNotionalValue, frontPosiNotionalValue);
|
||||
Console.WriteLine($" ToRemainingClosePercent 转换后: closePercent={convertedClosePercent}(占剩余)");
|
||||
var svc = new SwapDealService(new OptUserInfo(1, "UnitTest", OptUserFrom.UnitTest));
|
||||
var apiInterests = svc.GetUnwindInterests(DateTime.Today, DateTime.Today, td.id, convertedClosePercent, (int)SwapEventTypeEnum.平仓);
|
||||
Console.WriteLine($" GetUnwindInterests 返回 {apiInterests.Count} 条,预付金腿:");
|
||||
foreach (var ai in apiInterests.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金 || x.InterestMode == (int)InterestModeEnum.追加预付金))
|
||||
{
|
||||
Console.WriteLine($" PositionId={ai.PositionId} Mode={ai.InterestMode} InterestPrincipal={ai.InterestPrincipal} InterestAmount={ai.InterestAmount}");
|
||||
}
|
||||
|
||||
// 2.4 直调后端 GetUnwindInterests(closePercent=1.0) 看"按全部平仓应返"的预付金值
|
||||
try
|
||||
{
|
||||
var user = new OptUserInfo(0, nameof(GLMS20260701DbDiagnoseTest), OptUserFrom.UnitTest);
|
||||
var svc = new SwapDealService(user);
|
||||
var interests = svc.GetUnwindInterests(DateTime.Today, DateTime.Today, td.id, 1.0m, (int)SwapEventTypeEnum.平仓);
|
||||
var svcFull = new SwapDealService(user);
|
||||
var interests = svcFull.GetUnwindInterests(DateTime.Today, DateTime.Today, td.id, 1.0m, (int)SwapEventTypeEnum.平仓);
|
||||
|
||||
Console.WriteLine("============== 后端 GetUnwindInterests(1.0) 实际返回值-预付金腿 ==============");
|
||||
foreach (var it in interests.Where(i => i.InterestMode == 5 || i.InterestMode == 6))
|
||||
|
||||
Reference in New Issue
Block a user