Merge remote-tracking branch 'dest/glms/feature/1.4.2' into feature/p132_74-risk-engine
This commit is contained in:
@@ -42,6 +42,14 @@ namespace YLErp.Modules.SwapModule
|
||||
/// </summary>
|
||||
private sealed class StubEodPositionService : TestableSwapEodPositionService
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动互换场景可注入固定流水,避免为了验证结算边界而依赖真实计息公式。
|
||||
/// 未赋值时仍走生产使用的真实 GetInterests 计算。
|
||||
/// </summary>
|
||||
public List<swap_flow_event> AutoInterests { get; set; }
|
||||
|
||||
public eod_swap_position LastInterestCalculationEodPosition { get; private set; }
|
||||
|
||||
public StubEodPositionService() : base(nameof(DealInterestsScenarioTest))
|
||||
{
|
||||
}
|
||||
@@ -60,6 +68,12 @@ namespace YLErp.Modules.SwapModule
|
||||
bool add = false, bool settment = true, bool newCalcLast = false,
|
||||
List<swap_flow_event> closeList = null)
|
||||
{
|
||||
LastInterestCalculationEodPosition = eodPositions.SingleOrDefault();
|
||||
if (AutoInterests != null)
|
||||
{
|
||||
return AutoInterests;
|
||||
}
|
||||
|
||||
return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate,
|
||||
eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue,
|
||||
closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice,
|
||||
@@ -75,6 +89,28 @@ namespace YLErp.Modules.SwapModule
|
||||
return PersistedPositions.LastOrDefault();
|
||||
}
|
||||
|
||||
// public 包装:验证自动互换时的“高精度应结 -> 两位实际结算 -> 待实现尾差”链路。
|
||||
public eod_swap_position ExecuteSaveAutoEodInterestPosition(
|
||||
eod_swap_position eodPayPosition, swap_position position, trade td,
|
||||
DateTime valueDate, IntervalModel interval)
|
||||
{
|
||||
SaveAutoEodInterestPosition(eodPayPosition, null, position, td, valueDate, interval,
|
||||
null, DealInterestsScenarioTest.Principal, 0m, 1m, DealInterestsScenarioTest.Principal);
|
||||
return PersistedPositions.LastOrDefault();
|
||||
}
|
||||
|
||||
public eod_swap_position ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
eod_swap_position eodPayPosition, swap_position position, trade td,
|
||||
DateTime valueDate, IntervalModel interval, decimal posiLongNotional,
|
||||
decimal posiShortNotional, List<swap_flow_event> flowEvents,
|
||||
decimal closeNotional, bool autoSwap)
|
||||
{
|
||||
SaveAutoEodWithCloseInterestPosition(eodPayPosition, null, position, td, valueDate, interval,
|
||||
posiLongNotional, posiShortNotional, flowEvents, closeNotional, autoSwap, 1m,
|
||||
DealInterestsScenarioTest.Principal);
|
||||
return PersistedPositions.LastOrDefault();
|
||||
}
|
||||
|
||||
// public 包装:直接调用 protected virtual DealInterests(已改为 virtual,无需反射)
|
||||
public void ExecuteDealInterests(
|
||||
List<swap_position> interestList, List<eod_swap_position> eodPositions,
|
||||
@@ -169,6 +205,27 @@ namespace YLErp.Modules.SwapModule
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建自动互换利息流水。InterestAmount 是高精度应结,生产入口负责将实际结算收敛到两位。
|
||||
/// </summary>
|
||||
private static swap_flow_event CreateAutoSwapFlowEvent(DateTime eventDate, decimal interestAmount)
|
||||
{
|
||||
return new swap_flow_event
|
||||
{
|
||||
id = 2002, SwapTradeId = 1, EventType = (int)SwapFlowEventTypeEnum.自动互换,
|
||||
EventDate = eventDate, UnwindDate = eventDate, PositionId = 1001,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestAmount = interestAmount,
|
||||
TdInterestAmount = interestAmount,
|
||||
InterestClosePnL = interestAmount,
|
||||
InterestRate = FixedRate,
|
||||
InterestMode = (int)InterestModeEnum.标的期初全价,
|
||||
InterestPrincipal = Principal,
|
||||
FloatRate = 0m,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成
|
||||
};
|
||||
}
|
||||
|
||||
private static void AssertDecimal(decimal expected, decimal actual, string message = "")
|
||||
{
|
||||
var tolerance = 1m / (decimal)Math.Pow(10, ConsGlobal.PriceRound - 2);
|
||||
@@ -566,6 +623,151 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
#endregion
|
||||
|
||||
// ================================================================
|
||||
// 场景6:自动互换两位实际结算与到期清零
|
||||
// ================================================================
|
||||
|
||||
#region 场景6:自动互换尾差与最终结算
|
||||
|
||||
/// <summary>
|
||||
/// [DI_AUTO_SETTLEMENT_001] 非最终自动互换:实际结算按两位,尾差继续保留在待实现。
|
||||
/// 0.0082 四舍五入后实际结算 0.01,待实现应为 0.0082 - 0.01 = -0.0018。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_AUTO_SETTLEMENT_001_非最终自动互换保留舍入尾差()
|
||||
{
|
||||
var service = new StubEodPositionService
|
||||
{
|
||||
AutoInterests = new List<swap_flow_event> { CreateAutoSwapFlowEvent(new DateTime(2026, 5, 10), 0.0082m) }
|
||||
};
|
||||
var td = CreateTrade();
|
||||
var position = CreateInterestPosition();
|
||||
var result = service.ExecuteSaveAutoEodInterestPosition(
|
||||
CreatePreEod(new DateTime(2026, 5, 9), 0m), position, td, new DateTime(2026, 5, 10),
|
||||
new IntervalModel { Date = new DateTime(2026, 5, 10), Rate = FixedRate, Settlement = 1 });
|
||||
|
||||
AssertDecimal(0.01m, result.TdCloseInterest, "日终当日已实现必须使用两位实际结算金额");
|
||||
AssertDecimal(-0.0018m, result.InterestIncomeSum, "非最终结算的尾差必须继续留在待实现");
|
||||
AssertDecimal(0.01m, service.AutoInterests.Single().InterestAmount, "自动互换流水金额必须为两位");
|
||||
AssertDecimal(0.01m, service.AutoInterests.Single().InterestClosePnL, "资金汇总使用的流水损益必须为两位");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [DI_AUTO_SETTLEMENT_002] 到期自动互换:仍按两位实际结算,但不存在后续计息时待实现必须清零。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_AUTO_SETTLEMENT_002_到期自动互换清零待实现()
|
||||
{
|
||||
var service = new StubEodPositionService
|
||||
{
|
||||
AutoInterests = new List<swap_flow_event> { CreateAutoSwapFlowEvent(ExerciseDate, 0.0082m) }
|
||||
};
|
||||
var td = CreateTrade();
|
||||
var position = CreateInterestPosition();
|
||||
var result = service.ExecuteSaveAutoEodInterestPosition(
|
||||
CreatePreEod(ExerciseDate.AddDays(-1), 0m), position, td, ExerciseDate,
|
||||
new IntervalModel { Date = ExerciseDate, Rate = FixedRate, Settlement = 1 });
|
||||
|
||||
AssertDecimal(0.01m, result.TdCloseInterest, "到期自动结算仍按金额两位落库");
|
||||
AssertDecimal(0m, result.InterestIncomeSum, "到期最终自动结算后不得遗留待实现尾差");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [DI_AUTO_SETTLEMENT_003] 自动互换后的后续部分平仓必须续接尾差和累计已实现。
|
||||
/// 7/7 自动互换将 0.008191780822 按 0.01 实际结算,留下 -0.001808219178;
|
||||
/// 7/8 平仓一半后,待实现继续参与计算,7/9 全平时才清零。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_AUTO_SETTLEMENT_003_自动互换后部分平仓续接尾差和已实现()
|
||||
{
|
||||
var service = new StubEodPositionService
|
||||
{
|
||||
AutoInterests = new List<swap_flow_event>
|
||||
{
|
||||
CreateAutoSwapFlowEvent(new DateTime(2026, 5, 10), 0.008191780822m)
|
||||
}
|
||||
};
|
||||
var td = CreateTrade();
|
||||
var position = CreateInterestPosition();
|
||||
var autoResult = service.ExecuteSaveAutoEodInterestPosition(
|
||||
CreatePreEod(new DateTime(2026, 5, 9), 0m), position, td, new DateTime(2026, 5, 10),
|
||||
new IntervalModel { Date = new DateTime(2026, 5, 10), Rate = FixedRate, Settlement = 1 });
|
||||
|
||||
var firstCloseDate = new DateTime(2026, 5, 11);
|
||||
var firstCloseFlow = CreateSwapFlowEvent(firstCloseDate, 0.01m);
|
||||
firstCloseFlow.EventType = (int)SwapFlowEventTypeEnum.平仓;
|
||||
firstCloseFlow.InterestPrincipal = 50m;
|
||||
// 模拟 CalcUnwindInterest: 上日尾差 + 本次平仓后的高精度待实现。
|
||||
service.AutoInterests = new List<swap_flow_event>
|
||||
{
|
||||
CreateAutoSwapFlowEvent(firstCloseDate, 0.006383561644m)
|
||||
};
|
||||
service.AutoInterests[0].InterestPrincipal = 50m;
|
||||
var firstCloseResult = service.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
autoResult, position, td, firstCloseDate, null, 50m, 0m,
|
||||
new List<swap_flow_event> { firstCloseFlow }, 50m, false);
|
||||
|
||||
AssertDecimal(-0.001808219178m, service.LastInterestCalculationEodPosition.InterestProfitSum,
|
||||
"部分平仓计算必须带入自动互换遗留的待实现尾差");
|
||||
Assert.AreEqual(position.id, service.LastInterestCalculationEodPosition.PositionId,
|
||||
"部分平仓计息必须按腿标识匹配上一日日终");
|
||||
AssertDecimal(0.006383561644m, firstCloseResult.InterestIncomeSum,
|
||||
"部分平仓后待实现应延续历史尾差");
|
||||
AssertDecimal(0.02m, firstCloseResult.RealizedInterest,
|
||||
"部分平仓后累计已实现应包含此前自动互换和本次平仓");
|
||||
|
||||
var finalCloseDate = firstCloseDate.AddDays(1);
|
||||
var finalCloseFlow = CreateSwapFlowEvent(finalCloseDate, 0.01m);
|
||||
finalCloseFlow.EventType = (int)SwapFlowEventTypeEnum.平仓;
|
||||
finalCloseFlow.InterestPrincipal = 50m;
|
||||
service.AutoInterests = new List<swap_flow_event>
|
||||
{
|
||||
CreateAutoSwapFlowEvent(finalCloseDate, 0.010479452055m)
|
||||
};
|
||||
var finalCloseResult = service.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
firstCloseResult, position, td, finalCloseDate, null, 0m, 0m,
|
||||
new List<swap_flow_event> { finalCloseFlow }, 50m, false);
|
||||
|
||||
AssertDecimal(0m, finalCloseResult.InterestIncomeSum, "全平后待实现应清零");
|
||||
AssertDecimal(0.03m, finalCloseResult.RealizedInterest,
|
||||
"全平后累计已实现应包含自动互换和两次平仓");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [DI_MATURITY_SETTLEMENT_001] 到期日存在手动互换但未带齐待实现时不能清零;
|
||||
/// 当前事件按两位覆盖全部可结金额后,才可视为最终结算并清零。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_MATURITY_SETTLEMENT_001_到期手动互换仅在结清后清零()
|
||||
{
|
||||
var settleDate = ExerciseDate;
|
||||
var td = CreateTrade();
|
||||
var position = CreateInterestPosition();
|
||||
position.InterestPrincipalFix = 0m;
|
||||
|
||||
// 事件未结算此前的 0.0082:到期日也必须保留待实现。
|
||||
var incompleteService = new StubEodPositionService();
|
||||
var incompleteEvent = CreateSwapFlowEvent(settleDate, 0m);
|
||||
incompleteEvent.InterestPrincipal = 0m;
|
||||
incompleteEvent.InterestRate = 0m;
|
||||
var incompleteResult = incompleteService.ExecuteSaveEodInterestPosition(
|
||||
CreatePreEod(settleDate.AddDays(-1), 0.0082m), null, position, td, settleDate,
|
||||
new List<swap_flow_event> { incompleteEvent });
|
||||
AssertDecimal(0.0082m, incompleteResult.InterestIncomeSum, "到期但未结清时不得丢弃历史待实现");
|
||||
|
||||
// 前端按两位提交 0.01,可覆盖 0.0082 的最终金额,允许清零。
|
||||
var finalService = new StubEodPositionService();
|
||||
var finalEvent = CreateSwapFlowEvent(settleDate, 0.01m);
|
||||
finalEvent.InterestPrincipal = 0m;
|
||||
finalEvent.InterestRate = 0m;
|
||||
var finalResult = finalService.ExecuteSaveEodInterestPosition(
|
||||
CreatePreEod(settleDate.AddDays(-1), 0.0082m), null, position, td, settleDate,
|
||||
new List<swap_flow_event> { finalEvent });
|
||||
AssertDecimal(0m, finalResult.InterestIncomeSum, "两位最终结算覆盖待实现后应清零");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,29 @@ namespace YLErp.Modules.SwapModule
|
||||
return (decimal)method.Invoke(null, new object[] { position, unwindData });
|
||||
}
|
||||
|
||||
private static decimal InvokeCalcInitTradingFeePending(swap_position oriPosition, swap_position position, UnwindData unwindData)
|
||||
{
|
||||
var method = typeof(SwapDealService).GetMethod(
|
||||
"CalcInitTradingFeePending",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.IsNotNull(method, "CalcInitTradingFeePending was not found");
|
||||
|
||||
return (decimal)method.Invoke(null, new object[] { oriPosition, position, unwindData });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 百分比模式_按平仓名义本金计算并四舍五入到两位()
|
||||
{
|
||||
var position = new swap_position
|
||||
{
|
||||
PosiFeeType = 0,
|
||||
PosiTradingFeeUnit = 0.1234m
|
||||
PosiTradingFeeUnit = 0.1234m,
|
||||
PosiTradingFeePending = 1234.00m
|
||||
};
|
||||
var unwindData = new UnwindData
|
||||
{
|
||||
NotionalValue = 1_000_000m,
|
||||
CloseNotionalValue = 1_000_000m,
|
||||
CloseQty = 8888m
|
||||
};
|
||||
@@ -42,10 +55,12 @@ namespace YLErp.Modules.SwapModule
|
||||
var position = new swap_position
|
||||
{
|
||||
PosiFeeType = 1,
|
||||
PosiTradingFeeUnit = 1.235m
|
||||
PosiTradingFeeUnit = 1.235m,
|
||||
PosiTradingFeePending = 12.35m
|
||||
};
|
||||
var unwindData = new UnwindData
|
||||
{
|
||||
NotionalQty = 10m,
|
||||
CloseNotionalValue = 1_000_000m,
|
||||
CloseQty = 10m
|
||||
};
|
||||
@@ -61,5 +76,73 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(0m, InvokeCalcInitTradingFee(null, new UnwindData()));
|
||||
Assert.AreEqual(0m, InvokeCalcInitTradingFee(new swap_position(), null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BaseRatePendingFeeUsesTheSameActualCloseAmountAsCloseFee()
|
||||
{
|
||||
var oriPosition = new swap_position
|
||||
{
|
||||
PosiFeeType = 1,
|
||||
PosiTradingFeeUnit = 0.2m
|
||||
};
|
||||
oriPosition.PosiTradingFeePending = 2000m;
|
||||
var position = new swap_position { PosiTradingFeePending = 840m };
|
||||
var unwindData = new UnwindData { NotionalQty = 10000m, CloseQty = 3000m, CloseNotionalValue = 4200m };
|
||||
|
||||
var fee = InvokeCalcInitTradingFeePending(oriPosition, position, unwindData);
|
||||
|
||||
Assert.AreEqual(600m, fee);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BaseRatePendingFeeAllocatesManuallyAdjustedOriginalPendingFee()
|
||||
{
|
||||
var oriPosition = new swap_position
|
||||
{
|
||||
PosiFeeType = 1,
|
||||
PosiTradingFeeUnit = 0.2m,
|
||||
PosiTradingFeePending = 1500m
|
||||
};
|
||||
var unwindData = new UnwindData { NotionalQty = 10000m, CloseQty = 3000m };
|
||||
|
||||
var fee = InvokeCalcInitTradingFeePending(oriPosition, new swap_position(), unwindData);
|
||||
|
||||
Assert.AreEqual(450m, fee);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PartialCloseTradingFeeAndPendingFeeUseTheSameRoundedOriginalFeeAllocation()
|
||||
{
|
||||
var oriPosition = new swap_position
|
||||
{
|
||||
PosiFeeType = 0,
|
||||
PosiTradingFeeUnit = 1.1234m,
|
||||
PosiTradingFeePending = 113.46m
|
||||
};
|
||||
var unwindData = new UnwindData
|
||||
{
|
||||
NotionalValue = 10098m,
|
||||
CloseNotionalValue = 4039.2m,
|
||||
NotionalQty = 10000m,
|
||||
CloseQty = 4000m
|
||||
};
|
||||
|
||||
var tradingFee = InvokeCalcInitTradingFee(oriPosition, unwindData);
|
||||
var pendingFee = InvokeCalcInitTradingFeePending(oriPosition, new swap_position(), unwindData);
|
||||
|
||||
Assert.AreEqual(45.38m, tradingFee);
|
||||
Assert.AreEqual(45.38m, pendingFee);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LegacyPendingFeeKeepsCurrentPositionValueWhenNoBaseRateIsConfigured()
|
||||
{
|
||||
var oriPosition = new swap_position { PosiTradingFeeUnit = 0m };
|
||||
var position = new swap_position { PosiTradingFeePending = 840m };
|
||||
|
||||
var fee = InvokeCalcInitTradingFeePending(oriPosition, position, new UnwindData());
|
||||
|
||||
Assert.AreEqual(840m, fee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,5 +258,36 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(500000.00m, savedData.CloseNotionalValue, "平仓名义本金应按两位小数写入事件");
|
||||
Assert.AreEqual(500000.01, td.StockEqvNotional, 0.000001, "trade 剩余名义本金应在扣减后舍入两位小数");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_010_SwapUnwind_现金与两位利息事件保持一致()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
var service = new TestableSwapDealService(td);
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 10.0049m, closeMethod: (int)CloseMethodEnum.全部平仓,
|
||||
closePercent: 1m, closeQty: 10000m, closeNotionalValue: 1000000m, positionQty: 10000m);
|
||||
var floatEvent = new swap_flow_event
|
||||
{
|
||||
UnderlyingCode = "UT-FLOAT", PositionType = (int)PositionTypeFlag.Long,
|
||||
EventType = (int)SwapEventTypeEnum.平仓, PayDirection = 1, MarkClosePnl = 10m
|
||||
};
|
||||
var interestEvent = new swap_flow_event
|
||||
{
|
||||
PositionType = 0, InterestAmount = 0.0049m, TdInterestAmount = 0.0049m,
|
||||
InterestClosePnL = 0.0049m, InterestFee = 0.0049m
|
||||
};
|
||||
unwindData.FlowEvents.Add(floatEvent);
|
||||
unwindData.FlowEvents.Add(interestEvent);
|
||||
|
||||
service.SwapUnwind(unwindData);
|
||||
|
||||
Assert.AreEqual(0m, interestEvent.InterestAmount);
|
||||
Assert.AreEqual(0m, interestEvent.TdInterestAmount);
|
||||
Assert.AreEqual(0m, interestEvent.InterestClosePnL);
|
||||
Assert.AreEqual(0m, interestEvent.InterestFee);
|
||||
Assert.AreEqual(10m, unwindData.SwapRealizedPnL);
|
||||
Assert.AreEqual(-10d, service.ClientCashCalls[0].amount, 0.001d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
using YLErp.Configuration;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
|
||||
namespace YLErp.UnitTestProject.Modules.TradeModule.DocGenerateModule
|
||||
{
|
||||
[TestClass]
|
||||
public class GuolianContractNoGeneratorTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void IsGuolianSwapTrade_OnlyMatchesGuolianSwap()
|
||||
{
|
||||
var swapTrade = CreateSwapTrade();
|
||||
|
||||
Assert.IsTrue(GuolianContractNoGenerator.IsGuolianSwapTrade(swapTrade, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianContractNoGenerator.IsGuolianSwapTrade(
|
||||
new trade { TradeType = "香草期权" }, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianContractNoGenerator.IsGuolianSwapTrade(swapTrade, CompanyEnum.国泰君安));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGenerateTradeNumberAfterSave_OnlyGeneratesForBlankTradeNumber()
|
||||
{
|
||||
var blankTrade = CreateSwapTrade();
|
||||
var manualTrade = CreateSwapTrade();
|
||||
manualTrade.TradeNumber = "MANUAL-001";
|
||||
|
||||
Assert.IsTrue(GuolianContractNoGenerator.ShouldGenerateTradeNumberAfterSave(
|
||||
blankTrade, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianContractNoGenerator.ShouldGenerateTradeNumberAfterSave(
|
||||
manualTrade, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianContractNoGenerator.ShouldGenerateTradeNumberAfterSave(
|
||||
blankTrade, CompanyEnum.国泰君安));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildTradeNumber_ClientTrade_UsesNumericSequenceAndSanitizesUnderlyingCode()
|
||||
{
|
||||
var trade = CreateSwapTrade();
|
||||
trade.OpponentRole = "甲方";
|
||||
trade.TradeDate = new DateTime(2026, 7, 24);
|
||||
trade.UnderlyingCode = "250210.IB";
|
||||
|
||||
var number = GuolianContractNoGenerator.BuildTradeNumber(trade, "IS", 3);
|
||||
|
||||
Assert.AreEqual("GLMS-IS-20260724-FICC-03-250210IB", number);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(1, "A")]
|
||||
[DataRow(26, "Z")]
|
||||
[DataRow(27, "AA")]
|
||||
public void BuildTradeNumber_NonClientTrade_UsesLetterSequence(int sequenceNo, string sequenceCode)
|
||||
{
|
||||
var trade = CreateSwapTrade();
|
||||
trade.OpponentRole = "乙方";
|
||||
trade.TradeDate = new DateTime(2026, 7, 24);
|
||||
trade.UnderlyingCode = "180205.IB";
|
||||
|
||||
var number = GuolianContractNoGenerator.BuildTradeNumber(trade, "glmscounter", sequenceNo);
|
||||
|
||||
Assert.AreEqual($"GLMS-glmscounter-20260724-FICC-{sequenceCode}-180205IB", number);
|
||||
}
|
||||
|
||||
private static trade CreateSwapTrade()
|
||||
{
|
||||
return new trade { TradeType = "收益互换" };
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
using YLErp.Configuration;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
|
||||
namespace YLErp.UnitTestProject.Modules.TradeModule.DocGenerateModule
|
||||
{
|
||||
[TestClass]
|
||||
public class GuolianSwapConfirmBookScopeTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void IsPreApprovalTrade_AllAllowedStatuses_ReturnsTrue()
|
||||
{
|
||||
var statuses = new[]
|
||||
{
|
||||
ConsTrade.新增待确认,
|
||||
ConsTrade.修改待确认,
|
||||
ConsTrade.审批中
|
||||
};
|
||||
|
||||
foreach (var status in statuses)
|
||||
{
|
||||
var trade = CreateTrade(status);
|
||||
|
||||
Assert.IsTrue(GuolianSwapConfirmBookScope.IsPreApprovalTrade(trade, CompanyEnum.国联), status);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsPreApprovalTrade_NonGuolianSwapOrInvalidTrade_ReturnsFalse()
|
||||
{
|
||||
var confirmedSwap = CreateTrade(ConsTrade.确认成交);
|
||||
var optionTrade = CreateTrade(ConsTrade.审批中, "香草期权");
|
||||
var invalidSwap = CreateTrade(ConsTrade.审批中);
|
||||
invalidSwap.ValidState = ConsGlobal.InValid;
|
||||
|
||||
Assert.IsFalse(GuolianSwapConfirmBookScope.IsPreApprovalTrade(confirmedSwap, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianSwapConfirmBookScope.IsPreApprovalTrade(optionTrade, CompanyEnum.国联));
|
||||
Assert.IsFalse(GuolianSwapConfirmBookScope.IsPreApprovalTrade(CreateTrade(ConsTrade.审批中), CompanyEnum.国泰君安));
|
||||
Assert.IsFalse(GuolianSwapConfirmBookScope.IsPreApprovalTrade(invalidSwap, CompanyEnum.国联));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OtherActiveReferencePredicate_OnlyMatchesOtherActiveTrade()
|
||||
{
|
||||
const string contractCode = "GLMS-TEST-20260728-FICC-04-000001";
|
||||
var predicate = GuolianSwapConfirmBookCleanupService
|
||||
.BuildOtherActiveReferencePredicate(contractCode, 4)
|
||||
.Compile();
|
||||
|
||||
Assert.IsFalse(predicate(CreateRelation(4, contractCode, ContractTypeEnum.Trade, true)));
|
||||
Assert.IsTrue(predicate(CreateRelation(7, contractCode, ContractTypeEnum.Trade, true)));
|
||||
Assert.IsFalse(predicate(CreateRelation(7, contractCode, ContractTypeEnum.Trade, false)));
|
||||
Assert.IsFalse(predicate(CreateRelation(7, "OTHER", ContractTypeEnum.Trade, true)));
|
||||
Assert.IsFalse(predicate(CreateRelation(7, contractCode, ContractTypeEnum.Clearing, true)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TradeConfirmationRelationPredicate_MatchesActiveAndInactiveRelations()
|
||||
{
|
||||
const string contractCode = "GLMS-TEST-20260728-FICC-04-000001";
|
||||
var predicate = GuolianSwapConfirmBookCleanupService
|
||||
.BuildTradeConfirmationRelationPredicate(4)
|
||||
.Compile();
|
||||
|
||||
Assert.IsTrue(predicate(CreateRelation(4, contractCode, ContractTypeEnum.Trade, true)));
|
||||
Assert.IsTrue(predicate(CreateRelation(4, contractCode, ContractTypeEnum.Trade, false)));
|
||||
Assert.IsFalse(predicate(CreateRelation(7, contractCode, ContractTypeEnum.Trade, true)));
|
||||
Assert.IsFalse(predicate(CreateRelation(4, contractCode, ContractTypeEnum.Clearing, true)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ActiveOaResultPredicate_OnlyMatchesRejectedTradeActiveResult()
|
||||
{
|
||||
var predicate = GuolianSwapConfirmBookCleanupService
|
||||
.BuildActiveOaResultPredicate(4)
|
||||
.Compile();
|
||||
|
||||
Assert.IsTrue(predicate(new trade_contract_oa_result { trade_id = 4, is_valid = true }));
|
||||
Assert.IsFalse(predicate(new trade_contract_oa_result { trade_id = 4, is_valid = false }));
|
||||
Assert.IsFalse(predicate(new trade_contract_oa_result { trade_id = 7, is_valid = true }));
|
||||
}
|
||||
|
||||
private static trade CreateTrade(string status, string tradeType = "收益互换")
|
||||
{
|
||||
return new trade
|
||||
{
|
||||
TradeType = tradeType,
|
||||
TradeStatus = status,
|
||||
ValidState = ConsGlobal.Valid
|
||||
};
|
||||
}
|
||||
|
||||
private static trade_contract_r CreateRelation(int tradeId, string contractCode, string type, bool isValid)
|
||||
{
|
||||
return new trade_contract_r
|
||||
{
|
||||
TradeId = tradeId,
|
||||
ContractCode = contractCode,
|
||||
Type = type,
|
||||
IsValid = isValid
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user