- 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 差异。
204 lines
11 KiB
C#
204 lines
11 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||
using Newtonsoft.Json;
|
||
using YLErp;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.Modules.SwapModule;
|
||
using YLErp.Modules.SwapModule.Accrual;
|
||
using YLErp.Modules.SwapModule.Margin;
|
||
using YLErp.Derivatives.Interest;
|
||
|
||
namespace UnitTestProject.Modules.SwapModule.Margin
|
||
{
|
||
/// <summary>
|
||
/// 影子对账:保证金腿新方法 CalcMarginInterest(无 orginPv/差分)vs
|
||
/// 旧通用管线 CalcDailySimpleInterestByEod/CalcDailySimpleInterest(带差分 + orginPv hack)。
|
||
///
|
||
/// 保证金是纯固定利率单利(FloatRateUnderlyingCode 恒空、InterestType 恒单利、SwapIntervalList 单段),
|
||
/// 旧管线差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 对保证金恒等于 posiPrincipal
|
||
/// (因 orginPv 经 PreviousBalance 对齐到昨日终保证金余额),故新方法直接用 posiPrincipal/昨日终本金作
|
||
/// notional 应与旧管线严格数值一致。本测试即在多种场景下证明这一等价,为提交2 切换生产路径提供安全网。
|
||
/// </summary>
|
||
[TestClass]
|
||
public class MarginInterestShadowTest
|
||
{
|
||
private const decimal Principal = 2_000_000m; // 保证金本金(InterestPrincipalFix)
|
||
private const decimal Rate = 0.03m; // 3% 年化固定利率
|
||
private const int AnnualDays = 365;
|
||
private static readonly DateTime StartDate = new(2026, 7, 1);
|
||
private static readonly DateTime ExerciseDate = new(2027, 6, 30);
|
||
|
||
private sealed class StubSvc : SwapDealService
|
||
{
|
||
public StubSvc() : base(new OptUserInfo(0, nameof(MarginInterestShadowTest), OptUserFrom.UnitTest)) { }
|
||
}
|
||
|
||
private static trade CreateTrade() => new trade
|
||
{
|
||
id = 1, TradeNumber = "UT-MARGIN-SHADOW", ClientId = 999998,
|
||
TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
|
||
ExerciseDate = ExerciseDate, TradeStatus = "确认成交", ValidState = "Valid",
|
||
trade_extend = new trade_extend
|
||
{
|
||
TradeId = 1,
|
||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||
{ AnnualDays = AnnualDays, InterestCalcMode = "10", SettlementRules = 0 })
|
||
}
|
||
};
|
||
|
||
/// <summary>保证金腿(初始预付金 mode 5):固定利率、单利、年化、无浮动标的。</summary>
|
||
private static swap_position CreateMarginPosition() => new swap_position
|
||
{
|
||
id = 2001, SwapTradeId = 1, PosiDirection = 0,
|
||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||
InterestMode = (int)InterestModeEnum.初始预付金,
|
||
InterestRateDefault = Rate, InterestPrincipalFix = Principal,
|
||
PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate,
|
||
IsInitial = true, Invalid = false,
|
||
InterestType = (int)InterestTypeEnum.单利,
|
||
IsAnnualized = true, interest_rest_days = 1, interest_rule = 0,
|
||
FloatRateUnderlyingCode = null, InterestSwapInterval = "[]"
|
||
};
|
||
|
||
/// <summary>构造昨日终 eod_swap_position(已含累计利息 InterestProfitSum 与昨日终本金)。</summary>
|
||
private static eod_swap_position CreatePreEod(DateTime valueDate, decimal profitSum) => new eod_swap_position
|
||
{
|
||
id = 1, SwapTradeId = 1, PositionId = 2001,
|
||
ValueDate = valueDate,
|
||
TdInterestPrincipal = Principal, InterestPrincipalFix = Principal,
|
||
InterestProfitSum = profitSum, PosiNotionalValue = Principal, FloatRate = 0m
|
||
};
|
||
|
||
// ──────────────────────────── EOD 路径 ────────────────────────────
|
||
|
||
/// <summary>EOD 续接单日:有历史归档,notional=昨日终本金。</summary>
|
||
[TestMethod]
|
||
public void 影子_EOD续接单日_新旧一致()
|
||
{
|
||
var td = CreateTrade();
|
||
var position = CreateMarginPosition();
|
||
var valueDate = StartDate.AddDays(5);
|
||
const decimal profitSum = 820m;
|
||
|
||
// 旧方法
|
||
decimal oldI = 0, oldTd = 0;
|
||
var svc = new StubSvc();
|
||
svc.CalcDailySimpleInterestByEod(CreatePreEod(StartDate.AddDays(4), profitSum),
|
||
valueDate, td.StartDate.Value, position, Principal, Principal,
|
||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd);
|
||
|
||
// 新方法(独立 preEod,相同初始值)
|
||
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);
|
||
|
||
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
|
||
Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount} ClosePnL={newEvt.InterestClosePnL}");
|
||
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
|
||
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
|
||
}
|
||
|
||
/// <summary>EOD 首日(preEod.id==0):首日初始化 notional=posiPrincipal。</summary>
|
||
[TestMethod]
|
||
public void 影子_EOD首日_新旧一致()
|
||
{
|
||
var td = CreateTrade();
|
||
var position = CreateMarginPosition();
|
||
var valueDate = StartDate;
|
||
|
||
decimal oldI = 0, oldTd = 0;
|
||
var svc = new StubSvc();
|
||
svc.CalcDailySimpleInterestByEod(new eod_swap_position { id = 0 },
|
||
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, valueDate, position, Rate, Principal, Principal, 1m,
|
||
AnnualDays, calcFirst: true, calcLast: true,
|
||
new eod_swap_position { id = 0 }, 0, add: false, settment: true, swap: false);
|
||
|
||
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
|
||
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
|
||
}
|
||
|
||
// ──────────────────────────── 盘中路径 ────────────────────────────
|
||
|
||
/// <summary>盘中全平(closePercent=1):新方法 notional=posiPrincipal,旧方法差分 accrualBasis 恒=posiPrincipal。</summary>
|
||
[TestMethod]
|
||
public void 影子_盘中全平_新旧一致()
|
||
{
|
||
var td = CreateTrade();
|
||
var position = CreateMarginPosition();
|
||
var valueDate = StartDate.AddDays(5);
|
||
const decimal profitSum = 820m;
|
||
|
||
// 旧方法:orginPv 经 PreviousBalance 对齐到昨日终保证金余额 → accrualBasis 恒= Principal
|
||
decimal oldI = 0, oldTd = 0;
|
||
var svc = new StubSvc();
|
||
var preEodOld = CreatePreEod(StartDate.AddDays(4), profitSum);
|
||
decimal orginPv = MarginCalc.PreviousBalance(preEodOld, Principal);
|
||
svc.CalcDailySimpleInterest(preEodOld, valueDate, position, Principal,
|
||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, orginPv,
|
||
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
|
||
|
||
// 新方法:notional = posiPrincipal(无差分、无 orginPv)
|
||
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);
|
||
|
||
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
|
||
Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}");
|
||
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
|
||
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
|
||
}
|
||
|
||
/// <summary>盘中部分平仓(closePercent=0.5):缩放累计,新旧线性等价。</summary>
|
||
[TestMethod]
|
||
public void 影子_盘中部分平仓_新旧一致()
|
||
{
|
||
var td = CreateTrade();
|
||
var position = CreateMarginPosition();
|
||
var valueDate = StartDate.AddDays(5);
|
||
const decimal profitSum = 820m;
|
||
const decimal closePct = 0.5m;
|
||
|
||
decimal oldI = 0, oldTd = 0;
|
||
var svc = new StubSvc();
|
||
var preEodOld = CreatePreEod(StartDate.AddDays(4), profitSum);
|
||
decimal orginPv = MarginCalc.PreviousBalance(preEodOld, Principal);
|
||
svc.CalcDailySimpleInterest(preEodOld, valueDate, position, Principal,
|
||
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, closePct, orginPv,
|
||
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
|
||
|
||
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);
|
||
|
||
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
|
||
Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}");
|
||
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
|
||
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
|
||
}
|
||
|
||
/// <summary>互换事件(swap=true,盘中):利息应归零。</summary>
|
||
[TestMethod]
|
||
public void 影子_盘中互换_利息归零()
|
||
{
|
||
var td = CreateTrade();
|
||
var position = CreateMarginPosition();
|
||
var valueDate = StartDate.AddDays(5);
|
||
|
||
var svc = new StubSvc();
|
||
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);
|
||
|
||
Assert.AreEqual(0m, newEvt.InterestAmount, "互换利息归零");
|
||
Assert.AreEqual(0m, newEvt.TdInterestAmount, "互换 TdInterestAmount 归零");
|
||
Assert.AreEqual(0m, newEvt.InterestClosePnL, "互换 InterestClosePnL 归零");
|
||
}
|
||
}
|
||
}
|