Files
zszq-trs/UnitTestProject/Modules/CalcModules/MarginTemplateV2RateHelperFallbackTest.cs

242 lines
13 KiB
C#

using YLErp.BLL;
using YLErp.DBModels;
using YLErp.Enums;
using YLErp.Modules.MarginModule;
namespace YLErp.Modules.CalcModules
{
/// <summary>
/// R1 模板三层级找到即停回退集成测试(连 dev 库,MarginTemplateV2RateHelper.ResolveTieredTemplate/GetTradeMarginRate):
/// 交易绑定(自定义)→ 客户默认(client_margin_template 按客户)→ 全局默认(IsDefault&&!IsForClient)。
/// 测试数据全部带 "ZZZ-R1回退测试-" 名称前缀,TestInitialize/TestCleanup 双向清理,不触碰真实交易。
/// </summary>
[TestClass]
public class MarginTemplateV2RateHelperFallbackTest
{
private const string Marker = "ZZZ-R1回退测试-";
private const int SentinelTradeId = 1900000001;
private const int SentinelTradeId2 = 1900000002;
private DateTime EffectiveDate = new DateTime(2000, 1, 1);
private YLContext db;
private int clientId;
[TestInitialize]
public void Init()
{
db = new YLContext();
Cleanup();
//取一个真实客户做客户级绑定(只写 client_margin_template,不动客户数据)
using (var clientDb = DbContextFactory.GetClientDbContext(OptUserInfo.SystemUser))
{
clientId = clientDb.client.Where(c => c.id > 0).OrderBy(c => c.id).Select(c => c.id).First();
}
}
[TestCleanup]
public void CleanupFixture()
{
Cleanup();
db.Dispose();
}
private void Cleanup()
{
var templateIds = db.margin_template_v2.Where(x => x.Name.StartsWith(Marker)).Select(x => x.id).ToList();
if (templateIds.Count > 0)
{
db.margin_template_detail.RemoveRange(db.margin_template_detail.Where(x => templateIds.Contains(x.MarginTemplateId)));
db.trade_margin_template.RemoveRange(db.trade_margin_template.Where(x => templateIds.Contains(x.MarginTemplateId)));
db.client_margin_template.RemoveRange(db.client_margin_template.Where(x => templateIds.Contains(x.MarginTemplateId)));
db.margin_template_v2.RemoveRange(db.margin_template_v2.Where(x => templateIds.Contains(x.id)));
db.SaveChanges();
}
db.trade_margin_template.RemoveRange(db.trade_margin_template.Where(x => x.TradeId == SentinelTradeId || x.TradeId == SentinelTradeId2));
db.SaveChanges();
}
private margin_template_v2 AddTemplate(string name, bool isDefault, bool isForClient, string tradeTypes = "收益互换", bool isValid = true, int ruleType = (int)MarginRuleTypeEnum.区间追保结构, string bookIds = null, DateTime? valueDate = null)
{
var t = new margin_template_v2
{
Name = name,
IsDefault = isDefault,
IsForClient = isForClient,
IsValid = isValid,
TradeTypes = tradeTypes,
RuleType = ruleType,
ValueDate = valueDate ?? EffectiveDate,
BookIds = bookIds
};
db.margin_template_v2.Add(t);
db.SaveChanges();
return t;
}
[TestMethod]
public void TF_001_交易绑定优先_压过客户与全局()
{
var custom = AddTemplate(Marker + "自定义", isDefault: false, isForClient: false);
var clientTpl = AddTemplate(Marker + "客户", isDefault: true, isForClient: true);
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
db.trade_margin_template.Add(new trade_margin_template { TradeId = SentinelTradeId, MarginTemplateId = custom.id, ValueDate = EffectiveDate, IsLatest = true });
db.client_margin_template.Add(new client_margin_template { ClientId = clientId, MarginTemplateId = clientTpl.id, ValueDate = EffectiveDate, ClientLevel = "" });
db.SaveChanges();
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(SentinelTradeId, clientId, DateTime.Today, db);
Assert.IsNotNull(resolved);
Assert.AreEqual(custom.id, resolved.id, "交易绑定(自定义)应找到即停,压过客户与全局默认");
}
[TestMethod]
public void TF_002_无交易绑定_落到客户默认()
{
var clientTpl = AddTemplate(Marker + "客户", isDefault: true, isForClient: true);
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
db.client_margin_template.Add(new client_margin_template { ClientId = clientId, MarginTemplateId = clientTpl.id, ValueDate = EffectiveDate, ClientLevel = "" });
db.SaveChanges();
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId: null, clientId, DateTime.Today, db);
Assert.IsNotNull(resolved);
Assert.AreEqual(clientTpl.id, resolved.id, "无交易绑定时应命中客户默认,压过全局默认");
}
[TestMethod]
public void TF_003_无交易无客户绑定_落到全局默认()
{
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
AddTemplate(Marker + "客户", isDefault: true, isForClient: true); //未绑定不应被取到
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId: null, clientId, DateTime.Today, db);
AssertGlobal(resolved, globalTpl);
}
[TestMethod]
public void TF_004_客户绑定指向非互换模板_跳过落全局()
{
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
var optionTpl = AddTemplate(Marker + "期权客户模板", isDefault: true, isForClient: true, tradeTypes: "香草期权");
db.client_margin_template.Add(new client_margin_template { ClientId = clientId, MarginTemplateId = optionTpl.id, ValueDate = EffectiveDate, ClientLevel = "" });
db.SaveChanges();
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId: null, clientId, DateTime.Today, db);
AssertGlobal(resolved, globalTpl);
}
/// <summary>
/// 同日并存"互换绑定+期权绑定"(页面互斥只挡适用结构重叠,此组合允许保存):
/// 应命中互换绑定,不因先取到期权绑定被过滤而误穿透到全局。
/// </summary>
[TestMethod]
public void TF_007_同日并存互换与期权绑定_命中间换绑定()
{
var swapTpl = AddTemplate(Marker + "客户", isDefault: true, isForClient: true);
var optionTpl = AddTemplate(Marker + "期权客户模板", isDefault: true, isForClient: true, tradeTypes: "香草期权");
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
db.client_margin_template.Add(new client_margin_template { ClientId = clientId, MarginTemplateId = swapTpl.id, ValueDate = EffectiveDate, ClientLevel = "" });
db.client_margin_template.Add(new client_margin_template { ClientId = clientId, MarginTemplateId = optionTpl.id, ValueDate = EffectiveDate, ClientLevel = "" });
db.SaveChanges();
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId: null, clientId, DateTime.Today, db);
Assert.IsNotNull(resolved);
Assert.AreEqual(swapTpl.id, resolved.id, "同日期权+互换绑定并存时应命中互换绑定(过滤后再取最新),而非穿透全局");
}
[TestMethod]
public void TF_005_绑定模板已失效_找到即停返回null()
{
var invalidTpl = AddTemplate(Marker + "已失效", isDefault: false, isForClient: false, isValid: false);
var globalTpl = AddTemplate(Marker + "全局", isDefault: true, isForClient: false);
db.trade_margin_template.Add(new trade_margin_template { TradeId = SentinelTradeId2, MarginTemplateId = invalidTpl.id, ValueDate = EffectiveDate, IsLatest = true });
db.SaveChanges();
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(SentinelTradeId2, clientId, DateTime.Today, db);
Assert.IsNull(resolved, "交易绑定指向已失效模板时应找到即停(不向下回退到全局默认)");
}
/// <summary>
/// 端到端:交易绑定(区间追保结构 + 明细 x/y)经 GetTradeMarginRate 完整取到率——
/// 兼容回归交易级取数路径在新回退结构下行为不变。
/// </summary>
[TestMethod]
public void TF_006_交易绑定端到端取率()
{
var custom = AddTemplate(Marker + "自定义", isDefault: false, isForClient: false);
db.margin_template_detail.Add(new margin_template_detail
{
MarginTemplateId = custom.id,
ValueDate = EffectiveDate,
UnderlyingType = UnderlyingTypeEnum.None,
MarginRatio1 = 0.05,
MarginRatio2 = 0.03
});
db.trade_margin_template.Add(new trade_margin_template { TradeId = SentinelTradeId, MarginTemplateId = custom.id, ValueDate = EffectiveDate, IsLatest = true });
db.SaveChanges();
var rate = MarginTemplateV2RateHelper.GetTradeMarginRate(SentinelTradeId, "240004.IB", "TBonds", DateTime.Today, db);
Assert.IsNotNull(rate);
Assert.AreEqual(custom.id, rate.Template.id);
Assert.AreEqual(0.05m, rate.InitRate.Value);
Assert.AreEqual(0.03m, rate.MaintainRate.Value);
}
/// <summary>
/// 双全局默认并存(2026-08-28 裁定回归,复刻 dev 274/275 结构):
/// 同生效日下通配模板(无簿记限制)id 更新排前,限定簿记且匹配的模板排后——
/// 原口径"先按生效日/id 取最新再验簿记"使通配恒胜、限定簿记模板永不生效;
/// 新口径簿记条件满足者优先。需一笔隔离的真实交易(有簿记账户、无交易绑定,客户无客户级绑定)。
/// </summary>
[TestMethod]
public void TF_009_双全局默认_限定簿记匹配优先于通配()
{
var boundTradeIds = db.trade_margin_template.AsNoTracking().Select(x => x.TradeId).ToList();
var boundClientIds = db.client_margin_template.AsNoTracking()
.Where(x => x.ClientId > 0).Select(x => x.ClientId).Distinct().ToList();
var levelBoundNames = db.client_margin_template.AsNoTracking()
.Where(x => x.ClientId == 0 && x.ClientLevel != null && x.ClientLevel != "")
.Select(x => x.ClientLevel).Distinct().ToList();
int tradeId, assetId, tradeClientId;
using (var clientDb = DbContextFactory.GetClientDbContext(OptUserInfo.SystemUser))
{
var excludedLevelIds = clientDb.clientlevel.AsNoTracking()
.Where(l => levelBoundNames.Contains(l.LevelName)).Select(l => l.id).ToList();
var excludedClientIds = clientDb.client.AsNoTracking()
.Where(c => boundClientIds.Contains(c.id) || (c.LevelId != null && excludedLevelIds.Contains(c.LevelId ?? 0)))
.Select(c => c.id).ToList();
var picked = db.trade.AsNoTracking()
.Where(t => t.AssetId > 0 && t.ValidState != "InValid"
&& !boundTradeIds.Contains(t.id) && !excludedClientIds.Contains(t.ClientId))
.OrderByDescending(t => t.id)
.Select(t => new { t.id, t.AssetId, t.ClientId }).FirstOrDefault();
if (picked == null)
{
Assert.Inconclusive("dev 库无可用的隔离测试交易(有簿记账户且交易/客户均无绑定)");
return;
}
tradeId = picked.id;
assetId = picked.AssetId;
tradeClientId = picked.ClientId;
}
//同生效日(今日,晚于 dev 既有默认模板)双全局默认:通配后建(id 更大排前)
var generic = AddTemplate(Marker + "全局通配", isDefault: true, isForClient: false, valueDate: DateTime.Today);
var bookScoped = AddTemplate(Marker + "全局限定簿记", isDefault: true, isForClient: false, bookIds: assetId.ToString(), valueDate: DateTime.Today);
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId, tradeClientId, DateTime.Today, db);
Assert.IsNotNull(resolved);
Assert.IsTrue(resolved.Name.StartsWith(Marker), $"应命中本用例创建的标记模板,实际命中模板{resolved.id}(dev 出现同日/更晚生效默认模板会干扰,请重跑)");
Assert.AreEqual(bookScoped.id, resolved.id, "限定簿记且匹配的全局默认应优先于同生效日的通配全局默认");
}
private void AssertGlobal(margin_template_v2 resolved, margin_template_v2 expected)
{
Assert.IsNotNull(resolved);
if (resolved.Name.StartsWith(Marker))
{
Assert.AreEqual(expected.id, resolved.id, "应命中本用例创建的全局默认模板");
}
//dev 库存在其他真实全局默认模板时,按 ValueDate 最新者胜出,不做更严格断言
}
}
}