using Newtonsoft.Json;
using YLErp;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Accrual;
namespace UnitTestProject.Modules.SwapModule.Accrual
{
///
/// 影子测试:CalcDailyCompoundInterest(旧逐日循环)vs CompoundInterestAccrual.AccruePeriod(新分段纯函数)。
///
[TestClass]
public class CompoundPeriodShadowTest
{
private const decimal Notional = 100_000_000m;
private const decimal Spread = 0.0025m;
private const int AnnualDays = 365;
private static readonly DateTime StartDate = new(2026, 4, 21);
private static readonly DateTime EndDate = new(2026, 5, 11); // 21天 = 3×7, 末日是重置日
private static trade CreateTrade()
{
return new trade
{
id = 1, TradeNumber = "UT-COMPOUND-SHADOW", ClientId = 999998,
TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
ExerciseDate = StartDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid",
trade_extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays, InterestCalcMode = "10", SettlementRules = 0
})
}
};
}
private static swap_position CreatePosition()
{
return new swap_position
{
id = 1001, SwapTradeId = 1, PosiDirection = 0,
InterestDirection = (int)SwapDirectionEnum.收取,
InterestMode = (int)InterestModeEnum.标的期初全价,
InterestRateDefault = Spread,
InterestPrincipalFix = Notional,
PosiStartDate = StartDate, PosiMatuirityDate = StartDate.AddYears(1),
IsInitial = true, Invalid = false,
InterestType = (int)InterestTypeEnum.复利,
IsAnnualized = true, interest_rest_days = 7, interest_rule = 0,
FloatRateUnderlyingCode = null,
InterestSwapInterval = "[]"
};
}
private sealed class StubSvc : SwapDealService
{
public StubSvc() : base(new OptUserInfo(0, nameof(CompoundPeriodShadowTest), OptUserFrom.UnitTest)) { }
}
///
/// 固定利率(无FR007)算头不算尾,全平。
/// 重置日 4/28, 5/5, 末日 5/11 恰为重置日(21天=3×7)。
///
[TestMethod]
public void 影子_固定利率_全平_末日重置日_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
AnnualDays, 0m, 1m, true, false,
ref oldI, ref oldTd);
// 新方法:固定利率全段相同,分段点 = PosiStartDate + k×7
// 段: [4/21,4/28), [4/28,5/5), [5/5,5/11] → 注意旧代码 calcLast=false 不算末日
var allInRate = Spread; // 无浮动利率
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: 0m,
realizedInterest: 0m,
unwindFraction: 1m,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
///
/// 部分平仓 30% + consumedInterest 扣除。
///
[TestMethod]
public void 影子_部分平仓30pct_consumedInterest_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
const decimal consumed = 50_000m;
const decimal closePct = 0.3m;
const decimal carry = 0m; // 无历史归档
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional * closePct, flowEvent,
AnnualDays, 0m, closePct, true, false,
ref oldI, ref oldTd, consumedInterest: consumed, resetCarryInterest: carry);
// 新方法
var allInRate = Spread;
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional * closePct,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: carry,
realizedInterest: consumed,
unwindFraction: closePct,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
///
/// 算头算尾(calcMode="11")对比。
///
[TestMethod]
public void 影子_算头算尾_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
AnnualDays, 0m, 1m, true, true,
ref oldI, ref oldTd);
// 新方法
var allInRate = Spread;
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.Both,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: 0m,
realizedInterest: 0m,
unwindFraction: 1m,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
}
}