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;
namespace UnitTestProject.Modules.SwapModule.Margin
{
///
/// 影子对账:保证金腿方法 CalcMarginInterest(EOD 用昨日终本金、盘中用 accrualBasis 差分)vs
/// 旧通用管线 CalcDailySimpleInterestByEod/CalcDailySimpleInterest。
///
/// 保证金是纯固定利率单利(FloatRateUnderlyingCode 恒空、InterestType 恒单利、SwapIntervalList 单段)。
/// 本测试在生产切到 CalcMarginInterest 后作为回归守护,确认其 InterestAmount/TdInterestAmount
/// 与旧纯函数(SimpleInterestAccrual)数值一致。覆盖 EOD 续接/首日、盘中全平/部分平仓/互换。
///
[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 })
}
};
/// 保证金腿(初始预付金 mode 5):固定利率、单利、年化、无浮动标的。
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 = "[]"
};
/// 构造昨日终 eod_swap_position(已含累计利息 InterestProfitSum 与昨日终本金)。
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 路径 ────────────────────────────
/// EOD 续接单日:有历史归档,notional=昨日终本金。
[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, interestWindowEmpty: 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 一致");
}
/// EOD 首日(preEod.id==0):首日初始化 notional=posiPrincipal。
[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, interestWindowEmpty: false);
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
}
// ──────────────────────────── 盘中路径 ────────────────────────────
/// 盘中全平(closePercent=1):新方法 notional=posiPrincipal,旧方法差分 accrualBasis 恒=posiPrincipal。
[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, interestWindowEmpty: 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 一致");
}
/// 盘中部分平仓(closePercent=0.5):缩放累计,新旧线性等价。
[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, interestWindowEmpty: 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 一致");
}
/// 互换事件(swap=true,盘中):利息应归零。
[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, interestWindowEmpty: true);
Assert.AreEqual(0m, newEvt.InterestAmount, "互换利息归零");
Assert.AreEqual(0m, newEvt.TdInterestAmount, "互换 TdInterestAmount 归零");
Assert.AreEqual(0m, newEvt.InterestClosePnL, "互换 InterestClosePnL 归零");
}
}
}