fix(期权到期): 债券标的到期取价补查中债估值表,并标注债券净/全价映射不一致(Layer2)

- EodPriceQueryService 新增 TryGetSettlementEodPrice:债券走中债估值、期货/股票走原路径
- 单笔 tradeExpireInner + 批量 MultipleTradeExpireConfirm 改用统一方法,修 GLMS-20260715-0002 债券期权到期报'结算价未找到'
- 移除批量路径未初始化的 EodPriceProvider(对债券无效且有误导性的 footgun)
- Layer2:标注 EodPriceProvider.Initialize 与 GetBondPrice 债券 ClosePrice/SettlePrice 净全价定义相反,待统一(不改逻辑)
- 新增白盒单测覆盖债券标的到期取价(3用例 DB驱动,均通过)
This commit is contained in:
hjhan
2026-07-16 14:41:44 +08:00
parent 98122ef509
commit a17cdc2b74
4 changed files with 100 additions and 5 deletions
@@ -0,0 +1,72 @@
namespace YLErp.Modules.DataProviderModule
{
/// <summary>
/// TryGetSettlementEodPrice(债券感知统一取价)的白盒测试。
/// 覆盖期权/交易到期结算场景:债券标的应走中债估值表取到价(修复"结算价未找到"),
/// 非债券标的行为应与原 TryGetEodPrice 完全一致(不影响期货/股票)。
/// 注:DB 驱动,需连测试库;无数据时 Assert.Inconclusive 跳过。
/// </summary>
[TestClass]
public class EodPriceQueryServiceSettlementTest : YLUnitTestBase
{
[TestMethod]
public void BondUnderlying_RoutesToChinaBondValuation()
{
using var db = DbContextFactory.GetYLDbContext();
var bond = (from b in db.china_bond_valuation
join u in db.underlying_manager on b.bond_id equals u.UnderlyingCode
where b.dirty_price_close > 0
orderby b.valuation_date descending
select new { b.bond_id, vd = b.valuation_date }).FirstOrDefault();
if (bond == null) Assert.Inconclusive("测试库无债券估值数据,跳过");
var ok = EodPriceQueryService.TryGetSettlementEodPrice(bond.vd, bond.bond_id, out var ep);
Assert.IsTrue(ok, "债券标的应走中债估值表取到价(修复点)");
Assert.IsNotNull(ep);
// 债券 ClosePrice=全价(dirty_price_close),应与 GetBondPrice().ClosePrice 一致
var bondPrice = EodPriceQueryService.GetBondPrice(bond.vd, bond.bond_id);
Assert.IsNotNull(bondPrice);
Assert.AreEqual(bondPrice.ClosePrice, ep.ClosePrice, 1e-6);
}
[TestMethod]
public void NonBondUnderlying_RoutesToStockOrFuturePath()
{
using var db = DbContextFactory.GetYLDbContext();
var stock = (from s in db.eod_stock_price
join u in db.underlying_manager on s.UnderlyingCode equals u.UnderlyingCode
where s.ClosePrice > 0 && u.UnderlyingInstrumentType == "Stock"
select new { s.UnderlyingCode, s.ValueDate }).FirstOrDefault();
if (stock == null) Assert.Inconclusive("测试库无(股票类型)价格数据,跳过");
var ok = EodPriceQueryService.TryGetSettlementEodPrice(stock.ValueDate, stock.UnderlyingCode, out var ep);
var okOld = EodPriceQueryService.TryGetEodPrice(stock.ValueDate, stock.UnderlyingCode, out var epOld);
Assert.AreEqual(okOld, ok, "非债券标的行为应与原 TryGetEodPrice 一致");
if (ok)
{
Assert.IsNotNull(ep);
Assert.AreEqual(epOld.ClosePrice, ep.ClosePrice, 1e-6, "非债券标的取到的收盘价应与原路径相同");
}
}
[TestMethod]
public void BondOptionExpiry_Regression_OldPathFailsNewPathSucceeds()
{
using var db = DbContextFactory.GetYLDbContext();
var bond = (from b in db.china_bond_valuation
join u in db.underlying_manager on b.bond_id equals u.UnderlyingCode
where b.dirty_price_close > 0
orderby b.valuation_date descending
select new { b.bond_id, vd = b.valuation_date }).FirstOrDefault();
if (bond == null) Assert.Inconclusive("测试库无债券估值数据,跳过");
// 旧路径:TryGetEodPrice 只 join 期货/股票两表,债券取不到价
var oldOk = EodPriceQueryService.TryGetEodPrice(bond.vd, bond.bond_id, out _);
// 新路径:债券感知统一取价,应能取到
var newOk = EodPriceQueryService.TryGetSettlementEodPrice(bond.vd, bond.bond_id, out var ep);
Assert.IsFalse(oldOk, "回归基线:旧路径对债券标的应取不到价(这正是期权到期报'结算价未找到'的根因)");
Assert.IsTrue(newOk && ep != null && ep.ClosePrice > 0,
"修复验证:统一取价应能为债券标的取到结算价,期权到期不再报'结算价未找到'");
}
}
}