Files
zszq-trs/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs
T
hjhan 4a3fee9292 refactor(swap)+test: 删 GetInterests/CalcSwapInterests 死参数 needPrice/grossPrice;补工厂→接缝映射钉子
死参数收口(另一半):
- SwapDealService.GetInterests 删 needPrice/grossPrice(体内零消费,2026-08 验证);
  InitSwapDealInterest.needPrice 同为死参数一并删
- SwapEodPositionService.CalcSwapInterests 签名+转发同步;两个 EOD 生产调用点
  (SaveAutoEodInterestPosition/SaveEodInterestPositionCopy) 重排实参;
  CalcEodPostCloseSettleInterests/GetIntradayUnwindInterests 委托同步
- 14 个测试文件 ~44 处直调点机械更新(8 处 override 签名 + 36 处调用实参)
- 注意:EOD 编排链(DealInterests→Save*家族)的 grossPrice(期初不含费价)有真实用途,保留未动

新增钉子:CalcEodPostCloseSettleInterests 工厂→接缝参数映射测试——
CalcSwapInterestsCapture 捕获 stub 断言 EodPostCloseSettle 的完整转发契约
(posi=平仓后剩余/closePosi=平掉额/恒1/settment:false/orginPv 等 11 项)。
该段位置转发含三个相邻同型 decimal,编译器不查错位,此测试兜底。

验证:定向 241 测试通过(含 T0/T1 Excel 验证期望值、EntrySemantics 精确值钉子——
任何 decimal 错位即红);全量 903=145失败/746通过/12跳过,与基线逐位一致。
2026-08-14 16:57:56 +08:00

520 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json;
using YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 复利 consumedInterest 扣除 - 合成单元测试
/// ============================================================================
/// 验证 c6adb3bb 的修复:复利路径平仓时,扣除历史已通过互换结出的利息。
///
/// 核心场景:
/// 一笔复利交易,N天后做了互换结算(已结N天利息),之后再平仓。
/// 平仓默认值应 = 从头算的全程利息 - 已结利息(consumedInterest)。
/// 如果不扣(bug),平仓默认值 = 全程利息(偏大)。
/// 如果多扣(之前单利的错误),平仓默认值 = 0或负(偏小)。
///
/// 模仿 GetInterestsUnitTest_T0 的 StubSwapDealService 模式。
/// ============================================================================
[TestClass]
public class ConsumedInterestScenarioTest
{
#region 常量
private const decimal Principal = 1000m;
private const decimal FixedRate = 0.0025m; // 加点利率
private const double FloatRate = 0.001; // FR007
private const decimal TotalRate = FixedRate + (decimal)FloatRate; // 综合年化利率
private const int AnnualDays = 365;
private const int ResetPeriod = 3;
private static readonly DateTime StartDate = new(2026, 4, 27);
private static readonly DateTime ExerciseDate = new(2027, 4, 27);
#endregion
#region Stub:内存 SwapDealService + consumedInterest 注入
/// <summary>
/// 继承 SwapDealServiceoverride 两个虚方法:
/// - TryGetFloatRate:返回固定浮动利率(不连库)
/// - GetConsumedInterest:返回注入的历史已结利息(不连库)
/// </summary>
private sealed class StubSwapDealService : SwapDealService
{
private readonly double _floatRate;
private readonly decimal _consumedInterest;
private readonly Func<DateTime, double> _floatRateByDate;
public StubSwapDealService(OptUserInfo optUser, double floatRate, decimal consumedInterest)
: base(optUser)
{
_floatRate = floatRate;
_consumedInterest = consumedInterest;
_floatRateByDate = null;
}
/// <summary>按查询日期返回不同浮动利率(用于复现重置日取价 bug</summary>
public StubSwapDealService(OptUserInfo optUser, Func<DateTime, double> floatRateByDate, decimal consumedInterest = 0m)
: base(optUser)
{
_floatRate = 0;
_consumedInterest = consumedInterest;
_floatRateByDate = floatRateByDate;
}
protected override bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
{
rate = _floatRateByDate != null ? _floatRateByDate(valueDate) : _floatRate;
return true;
}
public override decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate)
{
return _consumedInterest; // 返回注入值
}
}
#endregion
#region 数据构建
private static trade CreateTrade()
{
return new trade
{
id = 1, TradeNumber = "UT-CONSUMED-001", ClientId = 999998,
TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
ExerciseDate = ExerciseDate, TradeStatus = "确认成交", ValidState = "Valid",
StructureType = "单标的", QuoteCurrency = "CNY", SettlementCurrency = "CNY",
trade_extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays,
InterestCalcMode = "10", // 算头不算尾
SettlementRules = 0
})
}
};
}
private static swap_position CreateCompoundPosition()
{
return new swap_position
{
id = 1001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.标的期初全价,
InterestRateDefault = FixedRate, InterestPrincipalFix = Principal,
PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate,
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.复利,
IsAnnualized = true, interest_rest_days = ResetPeriod, interest_rule = 0,
FloatRateUnderlyingCode = "FR007",
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>
{
new IntervalModel { Date = ExerciseDate, Rate = FixedRate, Settlement = 0 }
})
};
}
/// <summary>调用 GetInterests 获取复利利息(统一调用入口,settment:false走盘中平仓路径)</summary>
private static swap_flow_event CalcCompoundUnwind(StubSwapDealService service, DateTime unwindDate, decimal closePercent = 1m)
{
var td = CreateTrade();
var position = CreateCompoundPosition();
var interests = service.GetInterests(td, td.trade_extend, unwindDate, unwindDate,
new List<eod_swap_position>(), new List<swap_position> { position },
Principal, Principal, Principal, Principal, closePercent,
(int)SwapEventTypeEnum.平仓, false, Principal,
add: false, settment: false, newCalcLast: false);
Assert.AreEqual(1, interests.Count);
return interests[0];
}
private static StubSwapDealService CreateService(decimal consumedInterest)
{
return new StubSwapDealService(
new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest),
FloatRate, consumedInterest);
}
private static void AssertDecimal(decimal expected, decimal actual, string message = "")
{
var tolerance = 1m / (decimal)Math.Pow(10, ConsGlobal.PriceRound - 2);
Assert.IsTrue(Math.Abs(expected - actual) <= tolerance,
$"{message} Expected: {expected}, Actual: {actual}, Diff: {expected - actual}");
}
#endregion
// ================================================================
// 场景1:基线——无历史互换(consumedInterest=0),拿到全程复利利息
// ================================================================
/// <summary>
/// [CI_001] 无历史互换结清,复利平仓利息基线
/// ---------------------------------------------------------------
/// consumedInterest=0,平仓利息=从头算的全程复利利息。
/// 此值作为后续场景的参照基线(避免独立复利计算的精度匹配问题)。
/// ---------------------------------------------------------------
/// </summary>
[TestMethod]
public void CI_001_无历史互换平仓利息基线()
{
var unwindDate = StartDate.AddDays(10); // 4/27+10=5/7,算头不算尾约9天
var service = CreateService(consumedInterest: 0m);
var result = CalcCompoundUnwind(service, unwindDate);
Assert.IsTrue(result.InterestAmount > 0, "无互换时复利利息应>0");
Console.WriteLine($"基线(consumedInterest=0): InterestAmount={result.InterestAmount:F6}");
}
// ================================================================
// 场景2consumedInterest>0 → 平仓利息=基线-consumedInterest
// ================================================================
/// <summary>
/// [CI_002] 注入consumedInterest后,平仓利息应=基线-consumedInterest
/// ---------------------------------------------------------------
/// 用相同参数但注入不同的consumedInterest,验证:
/// 利息(有consumed) = 利息(无consumed) - consumedInterest
/// 这是验证cs:793 `interest -= consumedInterest` 的直接方式。
/// ---------------------------------------------------------------
/// </summary>
[TestMethod]
public void CI_002_consumedInterest正确扣除()
{
var unwindDate = StartDate.AddDays(10);
// 基线:consumedInterest=0
var baselineResult = CalcCompoundUnwind(CreateService(0m), unwindDate);
decimal baseline = baselineResult.InterestAmount;
// 注入consumedInterest=基线的50%
decimal consumed = baseline * 0.5m;
var consumedResult = CalcCompoundUnwind(CreateService(consumed), unwindDate);
// 期望 = 基线 - consumed
decimal expected = baseline - consumed;
AssertDecimal(expected, consumedResult.InterestAmount,
$"平仓利息应=基线({baseline:F6})-consumed({consumed:F6})={expected:F6}");
Console.WriteLine($"基线={baseline:F6}, consumed={consumed:F6}");
Console.WriteLine($"平仓利息={consumedResult.InterestAmount:F6}, 期望={expected:F6} ✅");
}
// ================================================================
// 场景3:守恒——consumed + 平仓利息 = 基线
// ================================================================
/// <summary>
/// [CI_003] 守恒:consumedInterest + 平仓利息(扣后) = 基线(无consumed)
/// ---------------------------------------------------------------
/// 注入任意consumedInterest,验证 consumed + 利息 = 基线。
/// 如果扣多了(守恒不成立→合计<基线)或没扣(合计>基线),测试失败。
/// ---------------------------------------------------------------
/// </summary>
[TestMethod]
public void CI_003_守恒consumed加平仓等于基线()
{
var unwindDate = StartDate.AddDays(10);
decimal baseline = CalcCompoundUnwind(CreateService(0m), unwindDate).InterestAmount;
// 注入不同的consumedInterest验证守恒
decimal[] testConsumed = { baseline * 0.3m, baseline * 0.5m, baseline * 0.8m };
foreach (var consumed in testConsumed)
{
var result = CalcCompoundUnwind(CreateService(consumed), unwindDate);
decimal actual = consumed + result.InterestAmount;
AssertDecimal(baseline, actual,
$"守恒: consumed({consumed:F6}) + 利息({result.InterestAmount:F6}) = {actual:F6} 应=基线({baseline:F6})");
Console.WriteLine($"consumed={consumed:F6} + 利息={result.InterestAmount:F6} = {actual:F6} = 基线{baseline:F6} ✅");
}
}
// ================================================================
// 场景4consumedInterest=全部基线 → 平仓利息≈0,不为负
// ================================================================
/// <summary>
/// [CI_004] 全部利息已结清(consumedInterest=基线),再平仓利息应≈0
/// ---------------------------------------------------------------
/// 验证不会扣过头变成负数(之前单利双重扣减的错误)。
/// 复利从头算全程 - 全程consumed = 0,应精确归零或微小正值。
/// ---------------------------------------------------------------
/// </summary>
[TestMethod]
public void CI_004_全部已结再平仓利息不为负()
{
var unwindDate = StartDate.AddDays(10);
decimal baseline = CalcCompoundUnwind(CreateService(0m), unwindDate).InterestAmount;
// consumedInterest=全部基线
var result = CalcCompoundUnwind(CreateService(baseline), unwindDate);
Console.WriteLine($"基线={baseline:F6}, consumed={baseline:F6}, 平仓利息={result.InterestAmount:F6}");
Assert.IsTrue(result.InterestAmount >= -0.01m,
$"全部已结再平仓利息应≈0(实际={result.InterestAmount:F6}),不应为负");
Console.WriteLine($"全部已结平仓≈0{result.InterestAmount:F6})✅");
}
[TestMethod]
public void CI_005_partialClose_scalesConsumedInterest()
{
var unwindDate = StartDate.AddDays(10);
const decimal closePercent = 0.4m;
const decimal consumed = 100m;
var baseline = CalcCompoundUnwind(CreateService(0m), unwindDate, closePercent).InterestAmount;
var result = CalcCompoundUnwind(CreateService(consumed), unwindDate, closePercent).InterestAmount;
AssertDecimal(baseline - consumed * closePercent, result,
$"partial close should deduct consumed interest by closePercent ({closePercent})");
}
// ================================================================
// 场景7:复现"平仓日=重置日 + calcLast=false → 重置日跳过 FR007 取价"
// ================================================================
/// <summary>
/// [CI_007] 平仓日恰好是重置日时,calcLast=false 不应导致该重置日的 FR007 取价被跳过
/// ----------------------------------------------------------------
/// 背景(GLMS-JIATT-20260805 根因)InterestCalcMode='10'(算头不算尾,calcLast=false)
/// CalcDailyCompoundInterest 循环里 `if(!calcLast && accrueDate==endDate) continue` 会跳过平仓日当天。
/// 若平仓日恰好是重置日(i%period==0),这个跳过会让"重置日取新FR007"的代码块永远不执行,
/// 沿用上一个重置周期的旧利率。
///
/// 构造:PosiStartDate=4/27, ResetPeriod=3, InterestCalcMode='10'(calcLast=false)
/// - FR007 按日期分段:5/3之前返回 rateOld=0.0015/3及之后返回 rateNew=0.002
/// - 对照A:平仓日=5/5(非重置日,9? 不: (5/5-4/27)=8, 8%3=2 非重置) → 不该取新值
/// - 对照B:平仓日=5/6(重置日,(5/6-4/27)=9, 9%3=0) → 应取新值 rateNew
///
/// 修复前:5/6 重置日被 calcLast 跳过 → 取到旧 rateOld → 与 5/5 相同
/// 修复后:5/6 重置日正常取价 → 取到 rateNew → 与 5/5 不同
/// ----------------------------------------------------------------
/// </summary>
/// <summary>
/// [CI_007] 平仓日恰好是重置日时,calcLast=false 不应导致该重置日的 FR007 取价被跳过
/// ----------------------------------------------------------------
/// 根因(GLMS-JIATT-20260805)InterestCalcMode='10'(calcLast=false)
/// CalcDailyCompoundInterest 循环 `if(!calcLast && accrueDate==endDate) continue` 跳过平仓日。
/// 若平仓日=重置日,取价代码块被跳过 → flowEvent.FloatRate 停留旧值 → 落库后传染 EOD。
///
/// 构造(避开周末,period=7)
/// PosiStartDate=4/27(周一), period=7, interest_rule=0, InterestCalcMode='10'
/// 重置日:i=0→4/27(周一), i=7→5/4(周一,工作日)
/// 平仓日=5/4(=重置日=endDate)
/// FR007 分界:rateDate>=5/4 返回 rateNew,否则 rateOld
///
/// 修复前:i=7(5/4)被 calcLast 跳过 → FloatRate=rateOld(旧值)
/// 修复后:i=7(5/4)正常取价 → FloatRate=rateNew(新值)
/// ----------------------------------------------------------------
/// </summary>
[TestMethod]
public void CI_007_平仓日等于重置日_calcLast_false_仍应取新FR007()
{
const double rateOld = 0.001;
const double rateNew = 0.002;
// 用 6 月日期避开五一/周末:PosiStartDate=6/1(周一), period=7, 平仓日=6/8(周一,重置日)
DateTime posiStart = new DateTime(2026, 6, 1);
DateTime unwindDate = new DateTime(2026, 6, 8); // (6/8-6/1)=7, 7%7=0 重置日
DateTime newRateFrom = new DateTime(2026, 6, 8); // 6/8(查询日,周一工作日)起为新利率
StubSwapDealService ServiceByDate() => new StubSwapDealService(
new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest),
d => d >= newRateFrom ? rateNew : rateOld);
var td = new trade
{
id = 1, TradeNumber = "UT-CI007", ClientId = 999998,
TradeType = "收益互换", TradeDate = posiStart, StartDate = posiStart,
ExerciseDate = ExerciseDate, TradeStatus = "确认成交", ValidState = "Valid",
StructureType = "单标的", QuoteCurrency = "CNY", SettlementCurrency = "CNY",
trade_extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays, InterestCalcMode = "10", SettlementRules = 0
})
}
};
var position = new swap_position
{
id = 1001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.标的期初全价,
InterestRateDefault = FixedRate, InterestPrincipalFix = Principal,
PosiStartDate = posiStart, PosiMatuirityDate = ExerciseDate,
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.复利,
IsAnnualized = true, interest_rest_days = 7, interest_rule = 0,
FloatRateUnderlyingCode = "FR007",
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>
{
new IntervalModel { Date = ExerciseDate, Rate = FixedRate, Settlement = 0 }
})
};
var interests = ServiceByDate().GetInterests(td, td.trade_extend, unwindDate, unwindDate,
new List<eod_swap_position>(), new List<swap_position> { position },
Principal, Principal, Principal, Principal, 1m,
(int)SwapEventTypeEnum.平仓, false, Principal,
add: false, settment: false, newCalcLast: false);
Assert.AreEqual(1, interests.Count);
var result = interests[0];
Console.WriteLine($"6/8(重置日,周一)平仓:FloatRate={result.FloatRate} Amount={result.InterestAmount:F6}");
Console.WriteLine($" 期望 FloatRate={rateNew}6/8 重置日查询日=6/8工作日,应取新利率)");
// 核心断言:6/8 是重置日,flowEvent.FloatRate 应反映新利率 rateNew
Assert.IsTrue(Math.Abs((result.FloatRate ?? 0) - (decimal)rateNew) < 0.0001m,
$"平仓日=重置日时 FloatRate 应={rateNew}(取到新利率)。" +
$"实际={result.FloatRate},若={rateOld} 说明 calcLast=false 跳过了重置日取价(GLMS-JIATT-20260805 根因)");
var interestBeforeResetDate = Principal * (FixedRate + (decimal)rateOld) * 7m / AnnualDays;
AssertDecimal(Principal + interestBeforeResetDate, result.InterestPrincipal,
"calcLast=false 的重置日仍应将前 7 天复利并入本金");
AssertDecimal(interestBeforeResetDate, result.InterestAmount,
"calcLast=false 不应计入重置日当天利息");
}
[TestMethod]
public void CI_008_ResetDayPartialCloseCarriesRemainingInterestIntoPrincipal()
{
const decimal previousPrincipal = 50061728.39m;
const decimal remainingPrincipal = 30037037.04m;
const decimal previousInterest = 7425.050203320057m;
const decimal fixedRate = 0.001234m;
const double oldFloatRate = 0.0123;
const double newFloatRate = 0.0213;
var startDate = new DateTime(2026, 7, 28);
var resetDate = new DateTime(2026, 8, 4);
var service = new StubSwapDealService(
new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest),
d => d >= resetDate ? newFloatRate : oldFloatRate);
var td = CreateTrade();
td.StartDate = startDate;
td.TradeDate = startDate;
var position = new swap_position
{
id = 1001, SwapTradeId = td.id,
InterestDirection = (int)SwapDirectionEnum.收取,
InterestMode = (int)InterestModeEnum.标的期初全价,
InterestType = (int)InterestTypeEnum.复利,
InterestRateDefault = fixedRate,
PosiStartDate = startDate, PosiMatuirityDate = ExerciseDate,
IsAnnualized = true, interest_rest_days = 7, interest_rule = 0,
FloatRateUnderlyingCode = "FR007",
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>
{
new IntervalModel { Date = ExerciseDate, Rate = fixedRate, Settlement = 0 }
})
};
var preEod = new eod_swap_position
{
id = 1, PositionId = position.id, ValueDate = resetDate.AddDays(-1),
TdInterestPrincipal = previousPrincipal,
InterestIncomeSum = previousInterest,
InterestProfitSum = previousInterest,
FloatRate = (decimal)oldFloatRate
};
var result = service.GetInterests(td, td.trade_extend, resetDate, resetDate,
new List<eod_swap_position> { preEod }, new List<swap_position> { position },
remainingPrincipal, remainingPrincipal, 0m, remainingPrincipal, 1m,
(int)SwapEventTypeEnum.平仓, true, remainingPrincipal,
add: false, settment: false, newCalcLast: false).Single();
var remainingInterest = previousInterest * remainingPrincipal / previousPrincipal;
var expectedPrincipal = remainingPrincipal + remainingInterest;
var expectedDailyInterest = expectedPrincipal * (fixedRate + (decimal)newFloatRate) / AnnualDays;
AssertDecimal(expectedPrincipal, result.InterestPrincipal);
AssertDecimal(expectedDailyInterest,
result.InterestPrincipal * (result.InterestRate + result.FloatRate.Value) / AnnualDays);
}
[TestMethod]
public void CI_009_NonResetUnwindWithCalcLastFalseUsesPreviousEodPendingInterest()
{
const decimal pendingInterest = 10019.043756537721m;
const decimal remainingPrincipal = 30041492.070122881942m;
var startDate = new DateTime(2026, 7, 28);
var unwindDate = new DateTime(2026, 8, 7);
var td = CreateTrade();
td.StartDate = startDate;
td.TradeDate = startDate;
var position = new swap_position
{
id = 1001, SwapTradeId = td.id,
InterestDirection = (int)SwapDirectionEnum.收取,
InterestMode = (int)InterestModeEnum.标的期初全价,
InterestType = (int)InterestTypeEnum.复利,
InterestRateDefault = 0.001234m,
PosiStartDate = startDate, PosiMatuirityDate = ExerciseDate,
IsAnnualized = true, interest_rest_days = 7,
FloatRateUnderlyingCode = "FR007"
};
var preEod = new eod_swap_position
{
id = 1, PositionId = position.id, ValueDate = unwindDate.AddDays(-1),
TdInterestPrincipal = remainingPrincipal,
InterestIncomeSum = pendingInterest,
InterestProfitSum = pendingInterest,
FloatRate = 0.0213m
};
var service = new StubSwapDealService(
new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest), 0.0213, 0m);
var result = service.GetInterests(td, td.trade_extend, unwindDate, unwindDate,
new List<eod_swap_position> { preEod }, new List<swap_position> { position },
remainingPrincipal, remainingPrincipal, 0m, remainingPrincipal, 1m,
(int)SwapEventTypeEnum.平仓, false, remainingPrincipal,
add: false, settment: false, newCalcLast: false).Single();
AssertDecimal(pendingInterest, result.InterestAmount,
"calcLast=false must not accrue unwind-date interest after the previous EOD");
}
[TestMethod]
public void CI_010_EodResetWithoutCloseCarriesFullPendingInterest()
{
const decimal principal = 303139117.80m;
const decimal previousBase = 303230391.742592383565m;
const decimal pendingInterest = 184331.611361300669m;
var startDate = new DateTime(2026, 4, 21);
var resetDate = new DateTime(2026, 4, 28);
var position = new swap_position
{
PosiStartDate = startDate,
InterestType = (int)InterestTypeEnum.复利,
InterestRateDefault = 0.0025m,
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>()),
IsAnnualized = true,
interest_rest_days = 7,
FloatRateUnderlyingCode = "FR007"
};
var preEod = new eod_swap_position
{
id = 1,
ValueDate = resetDate.AddDays(-1),
TdInterestPrincipal = previousBase,
InterestIncomeSum = pendingInterest,
InterestProfitSum = pendingInterest,
FloatRate = 0.013502m
};
var flowEvent = new swap_flow_event { InterestRate = 0.0025m };
var service = new StubSwapDealService(
new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest),
d => 0.0139);
decimal interestAmount = 0m;
decimal tdInterestAmount = 0m;
service.CalcDailyCompoundInterestByEod(preEod, resetDate, startDate, position,
principal, principal, flowEvent, AnnualDays, 0.013502m, 1m,
ref interestAmount, ref tdInterestAmount);
AssertDecimal(principal + pendingInterest, flowEvent.InterestPrincipal,
"无平仓重置日必须完整并入上一期累计待实现利息");
}
}
}