Files
zszq-trs/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs
T
hjhan fee2203d6e test(swap): DealFloatPositions浮动腿归档合成测试(testable迁移)
新增4个浮动腿归档场景测试:
- DF_001: 空持仓返回空列表
- DF_002: 首日无eod走SaveCurrentEodInitalPosi分支
- DF_003: SaveCurrentEodInitalPosi纯计算字段验证(数量/均价/类型)
- DF_004: 有eod无平仓走CopyEodPosition分支(分支选择验证)

DealFloatPositions和子方法无直接DB调用(数据从参数传入),
但CopyEodPosition/UpdateEodPosition依赖外部数据源
(DataCacheProvider/UnderlyingCodePrice/BondPaymentService),
值验证需后续加接缝。当前覆盖分支选择+首日初始化值验证。

验证: 104+4=108全通过。
2026-07-02 08:49:50 +08:00

226 lines
11 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 YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// DealFloatPositions 浮动腿收盘归档 - 合成单元测试
/// ============================================================================
/// DealFloatPositions 处理浮动腿(标的持仓)的日终归档,三个分支:
/// ① 无前日eod → SaveCurrentEodInitalPosi(首日初始化,纯计算)
/// ② 有eod无平仓 → CopyEodPosition(复制+更新价格,依赖外部数据源)
/// ③ 有eod有平仓 → UpdateEodPosition(更新持仓,依赖外部数据源)
///
/// 当前可测范围:
/// - 分支选择逻辑(DealFloatPositions 调度层,纯内存)
/// - SaveCurrentEodInitalPosi(首日初始化,纯计算,无外部依赖)
/// CopyEodPosition/UpdateEodPosition 需额外接缝(UnderlyingCodePrice等),留后续。
/// ============================================================================
[TestClass]
public class DealFloatPositionsScenarioTest
{
private const int SwapTradeId = 200;
private static readonly DateTime TradeDate = new(2026, 4, 28);
private static readonly DateTime PreSettleDate = new(2026, 4, 27);
#region Stub
private sealed class StubEodService : SwapEodPositionService
{
public StubEodService() : base(new OptUserInfo(0, nameof(DealFloatPositionsScenarioTest), OptUserFrom.UnitTest))
{
}
// DealFloatPositions 和子方法都是 protected,通过 public 包装暴露
public List<eod_swap_position> ExecuteDealFloatPositions(
List<swap_position> posiList, List<swap_position> realPosiList,
List<eod_swap_position> eodPositions, List<eod_swap_position> todyEodPositions,
DateTime settleDate, trade td, DateTime preSettleDate, List<swap_flow_event> flowEvents)
{
return DealFloatPositions(posiList, realPosiList, eodPositions, todyEodPositions,
settleDate, td, preSettleDate, flowEvents);
}
public eod_swap_position ExecuteSaveCurrentEodInitalPosi(
swap_position position, trade td, DateTime settleDate, DateTime preSettleDate,
List<swap_flow_event> unwindEvents)
{
return SaveCurrentEodInitalPosi(position, td, settleDate, preSettleDate, unwindEvents);
}
}
#endregion
#region 数据构建
private static trade CreateTrade()
{
return new trade
{
id = SwapTradeId, TradeNumber = "UT-FLOAT-001", ClientId = 999998,
TradeType = "收益互换", TradeDate = PreSettleDate, StartDate = PreSettleDate,
ExerciseDate = new DateTime(2027, 4, 27), TradeStatus = "确认成交",
ValidState = "Valid", StructureType = "单标的",
QuoteCurrency = "CNY", SettlementCurrency = "CNY",
OriginalStockEqvNotional = 10000
};
}
private static swap_position CreateFloatPosition(int id = 3001, decimal qty = 10000m)
{
return new swap_position
{
id = id, SwapTradeId = SwapTradeId,
PosiDirection = 2, PositionType = (int)PositionTypeFlag.Long,
UnderlyingCode = "210210.IB", ContractSize = 1m,
PosiQuantity = qty, PosiNotionalValue = qty,
PosiNetPrice = 1.005m, PosiGrossPrice = 1.002m,
PosiNetFeePrice = 1.004m, PosiNetNoFeePrice = 1.001m,
IsInitial = true, Invalid = false,
PosiTradingFee = 0, PosiTradingFeePending = 0
};
}
private static swap_flow_event CreateCloseEvent(int positionId, decimal qty, decimal markClosePnl = 100m)
{
return new swap_flow_event
{
SwapTradeId = SwapTradeId, EventType = (int)SwapFlowEventTypeEnum.平仓,
PositionId = positionId, Quantity = qty,
MarkClosePnl = markClosePnl, DividendIn = 0, CloseFee = 5m,
TradingFeePending = 0, TradingAmount = qty * 1.002m,
UnwindDate = TradeDate, EventDate = TradeDate, PayDate = TradeDate,
DataState = (int)SwapFlowDateStateEnum.完成
};
}
#endregion
// ================================================================
// 场景1:空持仓列表 → 返回空列表
// ================================================================
[TestMethod]
public void DF_001_空持仓返回空列表()
{
var service = new StubEodService();
var result = service.ExecuteDealFloatPositions(
new List<swap_position>(), new List<swap_position>(),
new List<eod_swap_position>(), new List<eod_swap_position>(),
TradeDate, CreateTrade(), PreSettleDate, new List<swap_flow_event>());
Assert.AreEqual(0, result.Count, "空持仓应返回空列表");
}
// ================================================================
// 场景2:首日无前日eod → 走 SaveCurrentEodInitalPosi 分支
// ================================================================
/// <summary>
/// 无前日eodeodPositions 不含该持仓),应走 SaveCurrentEodInitalPosi。
/// SaveCurrentEodInitalPosi 是纯计算,验证基本字段正确。
/// </summary>
[TestMethod]
public void DF_002_首日无eod走初始化分支()
{
var service = new StubEodService();
var td = CreateTrade();
var position = CreateFloatPosition();
var result = service.ExecuteDealFloatPositions(
new List<swap_position> { position },
new List<swap_position> { position },
new List<eod_swap_position>(), // 无前日eod
new List<eod_swap_position>(), // 无当日eod
TradeDate, td, PreSettleDate,
new List<swap_flow_event>()); // 无平仓事件
Assert.AreEqual(1, result.Count, "应生成1条浮动腿eod");
var eod = result[0];
Assert.AreEqual(position.id, eod.PositionId, "PositionId应匹配");
Assert.AreEqual(SwapTradeId, eod.SwapTradeId);
Assert.AreEqual(TradeDate, eod.ValueDate);
Console.WriteLine($"首日初始化: PosiQuantity={eod.PosiQuantity}, PosiNetPrice={eod.PosiNetPrice}");
}
// ================================================================
// 场景3SaveCurrentEodInitalPosi 直接验证(纯计算方法)
// ================================================================
/// <summary>
/// 直接测 SaveCurrentEodInitalPosi,验证它正确初始化 eod 的关键字段。
/// 这个方法无外部依赖(纯计算),可以精确验证值。
/// </summary>
[TestMethod]
public void DF_003_首日初始化字段正确()
{
var service = new StubEodService();
var td = CreateTrade();
var position = CreateFloatPosition(qty: 10000m);
var eod = service.ExecuteSaveCurrentEodInitalPosi(
position, td, TradeDate, PreSettleDate, new List<swap_flow_event>());
// 验证关键字段
Assert.AreEqual(10000m, eod.PosiQuantity, "持仓数量应=初始数量");
Assert.AreEqual(1.005m, eod.PosiNetPrice, "含费均价应=持仓均价");
Assert.AreEqual(1.002m, eod.PosiGrossPrice, "不含费均价");
Assert.AreEqual((int)PositionTypeFlag.Long, eod.PositionType, "持仓类型");
Assert.AreEqual(SwapTradeId, eod.SwapTradeId, "交易ID");
Assert.AreEqual(td.ClientId, eod.ClientId, "客户ID");
Assert.AreEqual(0, eod.TdCloseQty, "首日无平仓数量");
Assert.AreEqual(0, eod.TdCloseMtmPnl, "首日无平仓盈亏");
Console.WriteLine($"首日初始化 eod: Qty={eod.PosiQuantity}, NetPrice={eod.PosiNetPrice}, GrossPrice={eod.PosiGrossPrice} ✅");
}
// ================================================================
// 场景4:有前日eod无平仓 → 走 CopyEodPosition 分支
// ================================================================
/// <summary>
/// 有前日eod但无平仓事件,应走 CopyEodPosition 分支。
/// CopyEodPosition 依赖外部数据源(DataCacheProvider/UnderlyingCodePrice),
/// 测试验证分支选择正确(不验证值),且不抛异常。
/// </summary>
[TestMethod]
public void DF_004_有eod无平仓走Copy分支()
{
var service = new StubEodService();
var td = CreateTrade();
var position = CreateFloatPosition();
var preEod = new eod_swap_position
{
id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id,
ValueDate = PreSettleDate, PosiQuantity = 10000m,
PosiNetPrice = 1.005m, PosiGrossPrice = 1.002m,
UnderlyingCode = "210210.IB", ContractSize = 1m,
PositionType = (int)PositionTypeFlag.Long, PosiDirection = 2
};
// CopyEodPosition 内部调 DataCacheProvider/UnderlyingCodePrice
// 这些连缓存可能返回null → 方法 cs:1492 if(um==null) return curretEod
// 所以即使缓存没数据,也不会抛异常,只是字段不更新
try
{
var result = service.ExecuteDealFloatPositions(
new List<swap_position> { position },
new List<swap_position> { position },
new List<eod_swap_position> { preEod },
new List<eod_swap_position>(),
TradeDate, td, PreSettleDate,
new List<swap_flow_event>()); // 无平仓
Assert.AreEqual(1, result.Count, "应生成1条eod");
// um==null时 CopyEodPosition 直接返回 clone,字段不变
Assert.AreEqual(10000m, result[0].PosiQuantity, "无缓存时数量应=前日值");
Console.WriteLine($"Copy分支(无缓存): PosiQuantity={result[0].PosiQuantity}(保持前日值)");
}
catch (Exception ex)
{
Assert.Inconclusive($"CopyEodPosition 依赖外部数据源,需额外接缝。异常: {ex.Message}");
}
}
}
}