按最新代码(CalcMarginInterest 已接生产、orginPv hack 已删、MarginAccount 未接线、保证金精度=12)修正: - 删除 orginPv 维度重映射的孤儿 summary(方法已删) - SwapDealService 注释:去掉 orginPv(InitSwapDealInterest)/保证金腿 引用 - SwapInterest/AccrualContext/InterestRate:去掉 Precision=11 是"保证金腿"、"保证金场景"等错误归因(保证金实际跑精度12) - MarginAccount/MarginBalance:标注"尚未接线",生产入口指向 CalcMarginInterest,去掉"余额×利率×天数"过度简化 - 测试注释:去掉"无 orginPv/差分"(盘中保留差分)、"提交2 待切换"(已完成) 仅文档/注释,零代码行为变化。
165 lines
8.9 KiB
C#
165 lines
8.9 KiB
C#
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 = InterestPrincipalFix;closePrincipal = Fix×closePercent(EOD=1);
|
||
/// rate = oldEvt.InterestRate(严格取旧管线算出的 rate,消除 GetFixedRate 差异);
|
||
/// 方向 = FlipDirection(position.InterestDirection)(GetInterests:742 对保证金翻转);
|
||
/// preEod = 该 PositionId 上一日终 eod_swap_position;annualDays/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, 0m, 0m, 1.0m,
|
||
(int)SwapEventTypeEnum.自动互换, tdClose: false, needPrice: false,
|
||
grossPrice: 0m, 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 前必须解决(详见输出)");
|
||
}
|
||
}
|
||
}
|