Merge remote-tracking branch 'origin/glms/feature/1.4.2' into glms/feature/1.4.2
This commit is contained in:
@@ -142,6 +142,88 @@ namespace YLErp.Modules.EodModuleTests
|
||||
Assert.AreEqual(0m, item.InitMarginLoss);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildContract_历史类别为空_仍推送全部TRS合约()
|
||||
{
|
||||
var valueDate = new DateTime(2026, 2, 10);
|
||||
var eodSwap = new eod_swap { ValueDate = valueDate, SwapTradeId = 1645, SwapTradeNo = "ZSZQ-IS-202602090001" };
|
||||
var positions = new List<eod_swap_position>
|
||||
{
|
||||
new() { SwapTradeId = 1645, PositionId = 33306, UnderlyingCode = "220208.IB", PositionType = 1 },
|
||||
new() { SwapTradeId = 1645, PositionId = 33307, InterestMode = (int)InterestModeEnum.标的期初全价, InterestRateDefault = 0.001m, InterestDirection = 1 }
|
||||
};
|
||||
var swapPositions = new Dictionary<long, swap_position>
|
||||
{
|
||||
[33306] = new() { id = 33306, category_tag = null },
|
||||
[33307] = new() { id = 33307, category_tag = null }
|
||||
};
|
||||
|
||||
var item = TrsContractKafkaPushService.BuildContract(
|
||||
eodSwap,
|
||||
new Dictionary<int, trade> { [1645] = new() { id = 1645, UnderlyingCode = "220208.IB" } },
|
||||
positions,
|
||||
swapPositions);
|
||||
|
||||
Assert.AreEqual(0m, item.FixedRate);
|
||||
Assert.AreEqual(1, item.InterestDirection);
|
||||
Assert.AreEqual(1, item.FloatingDirection);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildContract_增强收益腿不参与固定利率取值()
|
||||
{
|
||||
var eodSwap = new eod_swap { ValueDate = new DateTime(2026, 8, 24), SwapTradeId = 7 };
|
||||
var positions = new List<eod_swap_position>
|
||||
{
|
||||
new() { SwapTradeId = 7, PositionId = 101, UnderlyingCode = "600000.SH", PositionType = 2 },
|
||||
new() { SwapTradeId = 7, PositionId = 102, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0123m, InterestDirection = 1 },
|
||||
new() { SwapTradeId = 7, PositionId = 103, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0999m, InterestDirection = 2 }
|
||||
};
|
||||
var swapPositions = new Dictionary<long, swap_position>
|
||||
{
|
||||
[101] = new() { id = 101, category_tag = null },
|
||||
[102] = new() { id = 102, category_tag = "互换利率" },
|
||||
[103] = new() { id = 103, category_tag = "增强收益" }
|
||||
};
|
||||
|
||||
var item = TrsContractKafkaPushService.BuildContract(
|
||||
eodSwap,
|
||||
new Dictionary<int, trade> { [7] = new() { id = 7, UnderlyingCode = "600000.SH" } },
|
||||
positions,
|
||||
swapPositions);
|
||||
|
||||
Assert.AreEqual(0.0123m, item.FixedRate);
|
||||
Assert.AreEqual(1, item.InterestDirection);
|
||||
Assert.AreEqual(2, item.FloatingDirection);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildContract_多条互换利率腿_取第一条()
|
||||
{
|
||||
var eodSwap = new eod_swap { ValueDate = new DateTime(2026, 8, 24), SwapTradeId = 7 };
|
||||
var positions = new List<eod_swap_position>
|
||||
{
|
||||
new() { SwapTradeId = 7, PositionId = 101, UnderlyingCode = "600000.SH", PositionType = 1 },
|
||||
new() { SwapTradeId = 7, PositionId = 102, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0123m, InterestDirection = 1 },
|
||||
new() { SwapTradeId = 7, PositionId = 103, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0456m, InterestDirection = 2 }
|
||||
};
|
||||
var swapPositions = new Dictionary<long, swap_position>
|
||||
{
|
||||
[101] = new() { id = 101, category_tag = null },
|
||||
[102] = new() { id = 102, category_tag = "互换利率" },
|
||||
[103] = new() { id = 103, category_tag = "互换利率" }
|
||||
};
|
||||
|
||||
var item = TrsContractKafkaPushService.BuildContract(
|
||||
eodSwap,
|
||||
new Dictionary<int, trade> { [7] = new() { id = 7, UnderlyingCode = "600000.SH" } },
|
||||
positions,
|
||||
swapPositions);
|
||||
|
||||
Assert.AreEqual(0.0123m, item.FixedRate);
|
||||
Assert.AreEqual(1, item.InterestDirection);
|
||||
}
|
||||
|
||||
private static TestableTrsContractKafkaPushService CreateService(RecordingKafkaProducer producer, DateTime valueDate)
|
||||
{
|
||||
return new TestableTrsContractKafkaPushService(
|
||||
|
||||
@@ -94,6 +94,23 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(20, day3, 1e-6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 授信垫付的初始预付金计入已缴(2026-08-27 修正,交易2538实证):
|
||||
/// 初始预付金走授信腿不产生应付预付金流水(现金净收额=0),已缴初始=授信初始占用净额——
|
||||
/// 目标追加 = 维持 −(现金净收额+初始授信占用净额),否则每个结算日按维持全额重复开追加。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AM_009_授信初始预付金计入已缴_不重复追加()
|
||||
{
|
||||
//交易2538 首日实际数据:维持2,808,000;初始预付金2,000,000全走授信(现金净收额0)
|
||||
var payableNetCash = 0d;
|
||||
var initCreditNet = 2_000_000d;
|
||||
var target = SwapAdditionalMarginCalc.CalcTarget(2_808_000, payableNetCash + initCreditNet);
|
||||
Assert.AreEqual(808_000, target, 1e-6);
|
||||
|
||||
//对照:修正前只扣现金净收额 → 目标2,808,000 全额追加(多收2,000,000 授信占用,即本BUG)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追保回落(维持下降到应付之下)不返还:目标为 0 → 已补足保持,新增 0;
|
||||
/// 超付部分由可用资金公式的负缺口(Σ维持−累计)体现,不产生返还记录。
|
||||
|
||||
@@ -131,6 +131,55 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 2.00));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 层间空隙不落最深层(2026-08-27 修正,交易2538实证):
|
||||
/// 真实模板空头档位 (−∞,0.99]∪(1.00,1.03],(0.99,1.00] 为价格在期初附近小幅波动的"未触发追保"空隙——
|
||||
/// 此前兜底一律按最深层计,ratio≈0.99999 被错误收取最深档(0.04×基数)。
|
||||
/// 修正后:空隙返回 null(追加保证金按0);仅穿出最深层边界(ratio>最深上界)才按最深层计。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SS_008_空头层间空隙_不落最深层_追加为零()
|
||||
{
|
||||
//模板"保证金测试01"实际配置形态:第1层 (−∞,0.99],第2~4层 (1.00,1.03]
|
||||
var tiers = new List<SpanTierConfig>
|
||||
{
|
||||
Tier(null, 0.99, 0.01),
|
||||
Tier(1.00, 1.01, 0.02),
|
||||
Tier(1.01, 1.02, 0.03),
|
||||
Tier(1.02, 1.03, 0.04),
|
||||
};
|
||||
//交易2538 08-24:净价100 → ratio = 100/100.0011 ≈ 0.99999 落 (0.99,1.00] 空隙 → 不追保
|
||||
Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, false, 0.99999));
|
||||
Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, false, 0.995));
|
||||
//空隙边界归第1层
|
||||
Assert.AreSame(tiers[0], SwapSpanMarginCalc.MatchTier(tiers, false, 0.99));
|
||||
//空隙上方正常落档
|
||||
Assert.AreSame(tiers[1], SwapSpanMarginCalc.MatchTier(tiers, false, 1.005));
|
||||
Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, false, 1.015));
|
||||
Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 1.025));
|
||||
//穿出最深层上界 → 按最深层计(原有语义不变)
|
||||
Assert.AreSame(tiers[3], SwapSpanMarginCalc.MatchTier(tiers, false, 1.0301));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多头层间空隙同理:空隙返回 null(追加0),仅跌破最深层下界才按最深层计。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SS_009_多头层间空隙_不落最深层()
|
||||
{
|
||||
var tiers = new List<SpanTierConfig>
|
||||
{
|
||||
Tier(0.98, null, 0.01),
|
||||
Tier(0.96, 0.97, 0.03), //留出 (0.97,0.98) 空隙
|
||||
Tier(0.95, 0.96, 0.04),
|
||||
};
|
||||
Assert.IsNull(SwapSpanMarginCalc.MatchTier(tiers, true, 0.975));
|
||||
Assert.AreSame(tiers[1], SwapSpanMarginCalc.MatchTier(tiers, true, 0.9699));
|
||||
Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, true, 0.955));
|
||||
//穿出最深层下界 → 按最深层计(原有语义不变)
|
||||
Assert.AreSame(tiers[2], SwapSpanMarginCalc.MatchTier(tiers, true, 0.94));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SS_008_无可用层_返回null()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using YLErp.Office.Converters;
|
||||
|
||||
namespace YLErp.PluginTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 国联确认书模板协议段勾选框占位符化后的渲染验证:
|
||||
/// 客户开户维护的主协议类型(client_meta.MainProtocolType,1=NAFMII,其余 SAC)
|
||||
/// 决定协议段 ☑/□ 勾选位置,模板不再依赖 Wingdings 2 硬编码复选框。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class TestGuoLianConfirmTemplate
|
||||
{
|
||||
private static string TemplatePath =>
|
||||
Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\..\\Plugins\\YLErp.Plugins.GuoLian\\App_Docs\\contract_template",
|
||||
"国联民生-收益互换交易确认书-境内模板-【客户看多】-【现券】-清洁版.docx");
|
||||
|
||||
private static string OutputDir => Path.Combine(AppContext.BaseDirectory, "TestOutputs");
|
||||
|
||||
private static JsonVarDic BuildVarDic(bool isNafmii)
|
||||
{
|
||||
var varDic = new JsonVarDic();
|
||||
varDic.SetVar("合同编号", "GL-20260827-001");
|
||||
varDic.SetVar("客户名称", "测试客户");
|
||||
varDic.SetVar("主协议编号", "GL-ZU-2026-001");
|
||||
varDic.SetVar("补充协议编号", "GL-BC-2026-001");
|
||||
|
||||
static string Check(bool on) => on ? "☑" : "□";
|
||||
varDic.SetVar("主协议SAC勾选", Check(!isNafmii));
|
||||
varDic.SetVar("主协议NAFMII勾选", Check(isNafmii));
|
||||
varDic.SetVar("补充协议SAC勾选", Check(!isNafmii));
|
||||
varDic.SetVar("补充协议NAFMII勾选", Check(isNafmii));
|
||||
varDic.SetVar("协会证券业勾选", "□");
|
||||
varDic.SetVar("协会交易商勾选", "□");
|
||||
varDic.SetVar("定义文件商品勾选", "□");
|
||||
varDic.SetVar("定义文件利率勾选", "□");
|
||||
varDic.SetVar("定义文件债券勾选", "□");
|
||||
// 利率类型勾选框(同模板内另一处 ☑/□ 占位符),一并驱动以便预览完整
|
||||
varDic.SetVar("IsFixed", isNafmii ? "□" : "☑");
|
||||
varDic.SetVar("IsFloat", isNafmii ? "☑" : "□");
|
||||
return varDic;
|
||||
}
|
||||
|
||||
private static string RenderAndReadBody(bool isNafmii, string fileName)
|
||||
{
|
||||
Directory.CreateDirectory(OutputDir);
|
||||
var targetPath = Path.Combine(OutputDir, fileName);
|
||||
OfficeFileConverter.ConvertByUsingDocTemplate(TemplatePath, targetPath, BuildVarDic(isNafmii));
|
||||
|
||||
using var zip = ZipFile.OpenRead(targetPath);
|
||||
var entry = zip.GetEntry("word/document.xml");
|
||||
using var stream = entry.Open();
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
var xml = reader.ReadToEnd();
|
||||
|
||||
// 勾选框与协议名称分属不同 run,断言前拼接纯文本
|
||||
var plainText = new StringBuilder();
|
||||
foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(xml, @"<w:t[^>]*>(.*?)</w:t>"))
|
||||
{
|
||||
plainText.Append(m.Groups[1].Value);
|
||||
}
|
||||
return plainText.ToString();
|
||||
}
|
||||
|
||||
[TestMethod("测试-国联确认书-SAC客户协议段勾选")]
|
||||
public void TestSacAgreementChecked()
|
||||
{
|
||||
var body = RenderAndReadBody(isNafmii: false, "guolian_confirm_sac.docx");
|
||||
|
||||
Assert.IsTrue(body.Contains("☑《中国证券期货市场衍生品交易主协议》/"), "SAC 客户主协议应勾选 SAC");
|
||||
Assert.IsTrue(body.Contains("□《中国银行间市场金融衍生产品交易主协议(2009年版)》"), "SAC 客户主协议不应勾选 NAFMII");
|
||||
Assert.IsTrue(body.Contains("☑《中国证券期货市场衍生品交易主协议》补充协议"), "SAC 客户补充协议应勾选 SAC");
|
||||
Assert.IsFalse(body.Contains("{{主协议SAC勾选}}"), "协议段占位符应全部被替换");
|
||||
}
|
||||
|
||||
[TestMethod("测试-国联确认书-NAFMII客户协议段勾选")]
|
||||
public void TestNafmiiAgreementChecked()
|
||||
{
|
||||
var body = RenderAndReadBody(isNafmii: true, "guolian_confirm_nafmii.docx");
|
||||
|
||||
Assert.IsTrue(body.Contains("□《中国证券期货市场衍生品交易主协议》/"), "NAFMII 客户主协议不应勾选 SAC");
|
||||
Assert.IsTrue(body.Contains("☑《中国银行间市场金融衍生产品交易主协议(2009年版)》"), "NAFMII 客户主协议应勾选 NAFMII");
|
||||
Assert.IsTrue(body.Contains("☑《中国银行间市场金融衍生产品交易主协议(2009年版)补充协议》"), "NAFMII 客户补充协议应勾选 NAFMII");
|
||||
Assert.IsFalse(body.Contains("{{主协议NAFMII勾选}}"), "协议段占位符应全部被替换");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user