Merge remote-tracking branch 'origin/glms/feature/1.4.2' into glms/feature/1.4.2
This commit is contained in:
@@ -74,11 +74,6 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
var underlying = Context.GetTradeUnderlying(flowEventGroup.UnderlyingCode);
|
||||
var closeNotionalValue = unwindData?.CloseNotionalValue
|
||||
?? flowEventGroup.Quantity * flowEventGroup.ContractSize * posi.PosiGrossPrice;
|
||||
var settlementDate = flowEventGroup.UnwindDate
|
||||
?? throw new ServiceException($"平仓事件{flowEventGroup.id}缺少结算日");
|
||||
var currentDayFloatingDividend = Context.GetEodPositions(tradeId, settlementDate)
|
||||
.FirstOrDefault(x => x.PositionId == flowEventGroup.PositionId)
|
||||
?.TdPosiDividend ?? 0m;
|
||||
|
||||
// 行构造器统一处理客户视角、结算公式、品种差异和模板展示精度。
|
||||
var row = SwapSettlementBillRowBuilder.Build(new SwapSettlementBillRowInput
|
||||
@@ -91,7 +86,6 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
Positions = positions,
|
||||
UnderlyingInstrumentType = underlying?.UnderlyingInstrumentType,
|
||||
CloseNotionalValue = closeNotionalValue,
|
||||
CurrentDayFloatingDividend = currentDayFloatingDividend,
|
||||
// 与提前终止详情页保持同一来源:读取 swap_flow_event 中的平仓浮动腿记录。
|
||||
ExitYtm = flowEventGroup.ExitYtm,
|
||||
IncludePeriodPaymentInNetting = (tradeExtend?.ExtendObj?.DividendPayDate ?? 1) == 0
|
||||
|
||||
@@ -3,9 +3,12 @@ using YLErp.Modules.SwapModule.Margin;
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// R2 阶段三 §3.2 可用资金公式测试(需求拆分 R2 口径,实时/EOD/报告三处共用 SwapSpanBalanceCalc):
|
||||
/// 客户维度 = Max(现金结存 + 授信 − 已使用授信 + 初始保证金 − 维持保证金, 0);
|
||||
/// 合约维度 = Max(现金结存 + 授信 − 已使用授信 − 交易维度追加保证金合计, 0)。
|
||||
/// R2 阶段三 §3.2 可用资金/追保/可取资金公式测试(定稿 2026-08-28 口径,实时/EOD/报告三处共用 SwapSpanBalanceCalc):
|
||||
/// 客户维度可用 = Max(现金结存 + 授信 − 已使用授信 + 初始保证金 − 维持保证金, 0);
|
||||
/// 合约维度可用 = Max(现金结存 + 授信 − 已使用授信, 0)(定稿删除"−交易维度追加保证金",避免与现金结存/已使用授信双重扣减);
|
||||
/// 追保金额两维度均 Max(...,0) 截断、恒 ≥ 0(只追不退);合约维度(2026-08-28 调整)= 当日追加保证金现金部分合计
|
||||
/// (不与现金结存轧差、闲置现金不冲抵、授信不追),客户维度 = Max((维持−初始) − (现金+授信−已使用), 0);
|
||||
/// 可取资金只算现金部分:合约维度 = Max(现金结存 + min(持仓盈亏,0), 0),客户维度 = Max(现金结存 + 初始 − 维持 + min(持仓盈亏,0), 0)。
|
||||
/// 授信额度为 credit.Credit 合计(保存时已折算比例),现金结存=期末结存(阶段二起授信不进资金)。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
@@ -35,40 +38,27 @@ namespace YLErp.Modules.SwapModule
|
||||
cashBalance: 20, totalCredit: 100, usedCredit: 20, initialMargin: 100, maintenanceMargin: 130), 1e-6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合约维度可用资金(定稿 2026-08-28):Max(现金结存 + 授信 − 已使用授信, 0),不再减交易维度追加保证金。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_004_合约维度_按交易追加合计扣减()
|
||||
public void SB_004_合约维度_可用资金()
|
||||
{
|
||||
//现金20 + 授信100 − 已使用20 − 追加合计30 = 70
|
||||
Assert.AreEqual(70, SwapSpanBalanceCalc.CalcContractDimensionAvailable(
|
||||
cashBalance: 20, totalCredit: 100, usedCredit: 20, tradeAdditionalMarginSum: 30), 1e-6);
|
||||
//现金20 + 授信100 − 已使用20 = 100
|
||||
Assert.AreEqual(100, SwapSpanBalanceCalc.CalcContractDimensionAvailable(
|
||||
cashBalance: 20, totalCredit: 100, usedCredit: 20), 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_005_合约维度_扣尽截断为0()
|
||||
{
|
||||
//现金20 + 授信0 − 已使用30 = −10 → 0
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionAvailable(
|
||||
cashBalance: 20, totalCredit: 0, usedCredit: 0, tradeAdditionalMarginSum: 30), 1e-6);
|
||||
cashBalance: 20, totalCredit: 0, usedCredit: 30), 1e-6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阶段三口径一致性:无追加保证金流水时(阶段四前),两维度公式数值一致——
|
||||
/// 交易追加合计 = Σ(维持−累计) = 维持 − 初始,与客户维度的 (初始−维持) 项互相抵消。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_006_阶段三两维度数值一致()
|
||||
{
|
||||
var cash = 20d;
|
||||
var credit = 100d;
|
||||
var used = 20d;
|
||||
var initial = 100d;
|
||||
var maintenance = 130d;
|
||||
var clientDimension = SwapSpanBalanceCalc.CalcClientDimensionAvailable(cash, credit, used, initial, maintenance);
|
||||
var contractDimension = SwapSpanBalanceCalc.CalcContractDimensionAvailable(cash, credit, used, maintenance - initial);
|
||||
Assert.AreEqual(clientDimension, contractDimension, 1e-9);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阶段四 §4.2 客户维度双向追保:正数=需追保,不以 0 截断。
|
||||
/// 客户维度追保金额(定稿 2026-08-28:Max(...,0) 截断,恒 ≥ 0,只追不退)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_010_客户维度追保金额_正数需追保()
|
||||
@@ -79,15 +69,15 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_011_客户维度追保金额_负数可返还()
|
||||
public void SB_011_客户维度追保金额_盈余截断为0()
|
||||
{
|
||||
//差额 (维持130−初始100)=30,资金 20+100−20=100 → 追保 = 30 − 100 = −70(可返还,不截断为 0)
|
||||
Assert.AreEqual(-70, SwapSpanBalanceCalc.CalcClientDimensionCallMargin(
|
||||
//差额 (维持130−初始100)=30,资金 20+100−20=100 → 30 − 100 = −70 → 截断为 0(资金富余不展示负数)
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcClientDimensionCallMargin(
|
||||
cashBalance: 20, totalCredit: 100, usedCredit: 20, initialMargin: 100, maintenanceMargin: 130), 1e-6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户维度追保金额与可用资金公式互为反向(去 Max 截断):追保 = −(未截断可用资金)。
|
||||
/// 客户维度追保金额与可用资金公式互为反向(各加 Max 截断):追保 = Max(−(未截断可用资金), 0)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_012_客户维度追保与可用资金反向一致()
|
||||
@@ -100,7 +90,7 @@ namespace YLErp.Modules.SwapModule
|
||||
var available = SwapSpanBalanceCalc.CalcClientDimensionAvailable(cash, credit, used, initial, maintenance);
|
||||
var callMargin = SwapSpanBalanceCalc.CalcClientDimensionCallMargin(cash, credit, used, initial, maintenance);
|
||||
var availableUnfloored = cash + credit - used + initial - maintenance;
|
||||
Assert.AreEqual(-availableUnfloored, callMargin, 1e-9);
|
||||
Assert.AreEqual(Math.Max(-availableUnfloored, 0), callMargin, 1e-9);
|
||||
//可用资金被 0 截断时追保为正(需追保),两者不矛盾
|
||||
if (availableUnfloored < 0)
|
||||
{
|
||||
@@ -110,22 +100,60 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合约维度追保金额(Max((现金+授信−已使用)×−1, 0)):账户透支为正=应补足,盈余截断为 0。
|
||||
/// 合约维度追保金额(2026-08-28 新口径):当日产生的追加保证金现金部分合计,不与现金结存轧差、
|
||||
/// 闲置现金不冲抵、授信部分不追;Max(...,0) 兜底截断(正常无负记录,追保回落不返还)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_013_合约维度追保金额_透支为正()
|
||||
public void SB_013_合约维度追保金额_当日现金追加全额()
|
||||
{
|
||||
//现金−80 + 授信100 − 已使用30 = −10 → 追保 = 10(应补足)
|
||||
Assert.AreEqual(10, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(
|
||||
cashBalance: -80, totalCredit: 100, usedCredit: 30), 1e-6);
|
||||
//场景:入金60万、现金初保50万(闲置10万),当日需追加40万(现金)——
|
||||
//旧口径缺口法追 30万(闲置现金被冲抵);新口径追当日现金追加全额 40万
|
||||
Assert.AreEqual(400000, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(400000), 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_014_合约维度追保金额_盈余截断为0()
|
||||
public void SB_014_合约维度追保金额_无当日追加为0()
|
||||
{
|
||||
//现金50 + 授信100 − 已使用20 = 130 → 盈余,Max(...,0) 截断 → 追保 = 0
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(
|
||||
cashBalance: 50, totalCredit: 100, usedCredit: 20), 1e-6);
|
||||
//当日追加全走授信(无现金流水)/当日无追保 → 0;负值兜底截断为 0
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(0), 1e-6);
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(-100), 1e-6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可取资金(定稿 2026-08-28):只算现金部分(授信不可取现);min(持仓盈亏,0) 只扣浮亏、浮盈不放行。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SB_015_客户维度可取资金_浮亏扣减()
|
||||
{
|
||||
//现金200 + 初始100 − 维持150 + min(−30,0) = 120
|
||||
Assert.AreEqual(120, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund(
|
||||
cashBalance: 200, initialMargin: 100, maintenanceMargin: 150, positionPnl: -30), 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_016_客户维度可取资金_浮盈不放行()
|
||||
{
|
||||
//现金200 + 初始100 − 维持150 + min(50,0)=0 → 150
|
||||
Assert.AreEqual(150, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund(
|
||||
cashBalance: 200, initialMargin: 100, maintenanceMargin: 150, positionPnl: 50), 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_017_客户维度可取资金_截断为0()
|
||||
{
|
||||
//现金20 + 初始100 − 维持150 + min(−40,0) = −70 → 0
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcClientDimensionDesirableFund(
|
||||
cashBalance: 20, initialMargin: 100, maintenanceMargin: 150, positionPnl: -40), 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SB_018_合约维度可取资金()
|
||||
{
|
||||
//Max(100 + min(−30,0), 0) = 70;浮亏超过现金时截断为 0
|
||||
Assert.AreEqual(70, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 100, positionPnl: -30), 1e-6);
|
||||
Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 20, positionPnl: -50), 1e-6);
|
||||
//浮盈不放行:Max(100 + min(80,0), 0) = 100
|
||||
Assert.AreEqual(100, SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(cashBalance: 100, positionPnl: 80), 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Dapper;
|
||||
using YLErp.Modules.UnderlyingModule;
|
||||
|
||||
namespace YLErp.UnitTestProject.Modules.UnderlyingModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 基金管理人取数(bigdata 数据源)连通性与库名插值验证(连 96 测试库):
|
||||
/// - DbSchema.Of 从连接串解析物理库名(glms_bigdata),SQL 不再硬编码库名、跨环境库名不同也能命中;
|
||||
/// - Lookup 全链路(连接 96 → 插值 SQL 执行 → 结果归并)不返回 Unavailable 即为连通且 SQL 有效。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class FundManagerLookupServiceTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DbSchema_从连接串解析物理库名()
|
||||
{
|
||||
Assert.AreEqual("`glms_bigdata`", DbSchema.Of("bigdata"));
|
||||
Assert.AreEqual("`glms_yltrs_ylcms`", DbSchema.Of("ylcms"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Lookup_连接96大数据库_非降级()
|
||||
{
|
||||
var service = new FundManagerLookupService();
|
||||
//96 库现有测试数据 161210/630006/511160(SECUCODE 无后缀,与去后缀匹配逻辑一致),用库存代码验证全链路命中
|
||||
foreach (var code in new[] { "511160.SH", "161210.SZ", "630006.SH" })
|
||||
{
|
||||
var result = service.Lookup(code);
|
||||
Console.WriteLine($"code={code} → Status={result.Status}, InvestAdvisorName={result.InvestAdvisorName}");
|
||||
Assert.AreNotEqual(FundManagerLookupStatus.Unavailable, result.Status,
|
||||
$"code={code} 返回 Unavailable:96 bigdata 库不可达或插值 SQL 执行失败");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Diag_查看库存secucode样例与行数()
|
||||
{
|
||||
using var connection = new MySqlConnector.MySqlConnection(AppManager.GetConnectionString("bigdata"));
|
||||
var count = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_fundarchives");
|
||||
var samples = connection.Query<string>($"SELECT SECUCODE FROM {DbSchema.Of("bigdata")}.mf_fundarchives LIMIT 8");
|
||||
Console.WriteLine($"mf_fundarchives 行数={count}, SECUCODE样例=[{string.Join(",", samples)}]");
|
||||
var advCount = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_investadvisoroutline");
|
||||
Console.WriteLine($"mf_investadvisoroutline 行数={advCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
},
|
||||
"LibreOffice": {
|
||||
"ExePath": "",
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace YLErp
|
||||
"yladmin" => _configuration.GetConnectionString("yladmin"),
|
||||
"ylclient" => _configuration.GetConnectionString("ylclient"),
|
||||
"bondoms" => _configuration.GetConnectionString("bondoms"),
|
||||
"glms_bigdata" => _configuration.GetConnectionString("glms_bigdata"),
|
||||
"bigdata" => _configuration.GetConnectionString("bigdata"),
|
||||
"apex_oracle"=> _configuration.GetConnectionString("apex_oracle"),
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace YLErp.BLL.EodSettlement
|
||||
/// </summary>
|
||||
public class ClientBalanceUtility
|
||||
{
|
||||
private static readonly IYcLogger logger = LogFactory.GetLogger("客户资金结算");
|
||||
|
||||
/// <summary>
|
||||
/// 获取startDate至endDate 所有客户结算信息统计
|
||||
/// </summary>
|
||||
@@ -303,6 +305,8 @@ namespace YLErp.BLL.EodSettlement
|
||||
var usedCreditDic = ClientCreditInoutService.GetUsedCreditByClients(clientIdS, db);
|
||||
var swapInitMarginDic = SwapSpanBalanceQueryService.GetSwapInitMarginByClients(clientIdS, lastDate, db);
|
||||
var swapAdditionalDic = SwapSpanBalanceQueryService.GetTradeAdditionalMarginByClients(clientIdS, lastDate, db);
|
||||
//合约维度追保金额新口径(2026-08-28):当日追加保证金现金部分合计
|
||||
var todayCashAddDic = SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients(clientIdS, lastDate, db);
|
||||
//原始授信额度(展示用):与 EOD 写入 clientbalancedaily.Credit 同批过滤条件(EodClientBalanceCalc :68),
|
||||
//取 Σ(OriginalCredit ?? Credit) 不经比例折算;TotalCredit 仍为折算后值供公式使用
|
||||
var originalCreditDic = db.credit.AsNoTracking()
|
||||
@@ -387,8 +391,9 @@ namespace YLErp.BLL.EodSettlement
|
||||
// 未配置(MarginWatchRule=NULL,存量客户)维持旧口径:期末结存-追保账户余额
|
||||
if (client?.MarginWatchRule == 0)
|
||||
{
|
||||
//定稿 2026-08-28:Max(现金结存 + 授信额度 − 已使用授信, 0),不再减交易维度追加保证金(避免双重扣减)
|
||||
balance.AvailableAmount = SwapSpanBalanceCalc.CalcContractDimensionAvailable(
|
||||
balance.AmountFund, balance.TotalCredit, balance.UsedCredit, balance.SwapAdditionalMarginTotal);
|
||||
balance.AmountFund, balance.TotalCredit, balance.UsedCredit);
|
||||
}
|
||||
else if (client?.MarginWatchRule == 1)
|
||||
{
|
||||
@@ -401,9 +406,9 @@ namespace YLErp.BLL.EodSettlement
|
||||
{
|
||||
balance.AvailableAmount = balance.MarginBalance - (balance.VmInFundSum - balance.VmOutFundSum);
|
||||
}
|
||||
// 是否追保/追保金额(阶段四 §4.2 按维度分流,允许负值=双向,不以 0 截断):
|
||||
// 客户维度(==1)= (维持−初始) − (现金+授信−已使用),负=可返还;
|
||||
// 合约维度(==0)= Max(−(现金+授信−已使用), 0)(需求原文公式,盈余截断为 0);
|
||||
// 是否追保/追保金额(按维度分流,定稿 2026-08-28 起均 Max(...,0) 截断、恒 ≥ 0,只追不退):
|
||||
// 客户维度(==1)= Max((维持−初始) − (现金+授信−已使用), 0);
|
||||
// 合约维度(==0)= 当日追加保证金现金部分合计(2026-08-28 新口径:不与现金结存轧差、闲置现金不冲抵、授信部分不追);
|
||||
// 未配置(NULL 存量)维持旧口径:盯市低于维持时 = 初始保证金金额−盯市金额,否则 0
|
||||
if (client?.MarginWatchRule == 1)
|
||||
{
|
||||
@@ -414,7 +419,7 @@ namespace YLErp.BLL.EodSettlement
|
||||
else if (client?.MarginWatchRule == 0)
|
||||
{
|
||||
balance.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcContractDimensionCallMargin(
|
||||
balance.AmountFund, balance.TotalCredit, balance.UsedCredit);
|
||||
todayCashAddDic.TryGetValue(data.ClientId, out var todayCashAdd) ? todayCashAdd : 0);
|
||||
balance.NeedAddMargin = balance.MarginByPayableMarginTotal > 0;
|
||||
}
|
||||
else
|
||||
@@ -422,8 +427,25 @@ namespace YLErp.BLL.EodSettlement
|
||||
balance.NeedAddMargin = balance.SwapMarketAmount < balance.MaintenanceMargin;
|
||||
balance.MarginByPayableMarginTotal = balance.NeedAddMargin ? (balance.MySideMargin - balance.SwapMarketAmount) : 0;
|
||||
}
|
||||
// 可取资金=Math.Max(期末结存-min(持仓盈亏,0)-初始保证金,0)
|
||||
// 可取资金(定稿 2026-08-28 分维度口径,只算现金部分——授信不可取现;min(持仓盈亏,0) 只扣浮亏、浮盈不放行):
|
||||
// 合约维度(==0)= Max(现金结存 + min(持仓盈亏,0), 0)(追保已落账进现金结存,无需再叠加保证金约束);
|
||||
// 客户维度(==1)= Max(现金结存 + 初始保证金 − 维持保证金 + min(持仓盈亏,0), 0)(追保不产现金流,显式叠加保证金约束);
|
||||
// 未配置(NULL 存量)维持旧口径
|
||||
if (client?.MarginWatchRule == 0)
|
||||
{
|
||||
balance.DesirableFund = SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(
|
||||
balance.AmountFund, balance.RoundedPositionPnl);
|
||||
}
|
||||
else if (client?.MarginWatchRule == 1)
|
||||
{
|
||||
balance.DesirableFund = SwapSpanBalanceCalc.CalcClientDimensionDesirableFund(
|
||||
balance.AmountFund, balance.SwapInitMargin, -balance.MySideMargin, balance.RoundedPositionPnl);
|
||||
}
|
||||
else
|
||||
{
|
||||
balance.DesirableFund = Math.Max(balance.MarginBalance + Math.Min(balance.RoundedPositionPnl, 0), 0);
|
||||
}
|
||||
logger.Info($"客户资金结算:客户{client?.id}({client?.Name}){balance.ValueDate:yyyy-MM-dd} MarginWatchRule={client?.MarginWatchRule?.ToString() ?? "NULL"} 可用资金={balance.AvailableAmount:0.00} 追保金额={balance.MarginByPayableMarginTotal:0.00} 是否追保={balance.NeedAddMargin} 可取资金={balance.DesirableFund:0.00}(期末结存{balance.AmountFund:0.00} 授信{balance.TotalCredit:0.00} 已用授信{balance.UsedCredit:0.00} 互换初始{balance.SwapInitMargin:0.00} 维持{-balance.MySideMargin:0.00} 追加合计{balance.SwapAdditionalMarginTotal:0.00})");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace YLErp.BLL.Eod
|
||||
/// </summary>
|
||||
public class RealTimeClientBanlanceService : YLBaseService
|
||||
{
|
||||
private static readonly IYcLogger logger = LogFactory.GetLogger("实时客户资金");
|
||||
|
||||
readonly valuedate _systemDate; //系统参数
|
||||
readonly DateTime _valueDate; //系统交易日
|
||||
readonly Dictionary<int, ClientBalanceEx> _clientBalanceDic;
|
||||
@@ -241,6 +243,8 @@ namespace YLErp.BLL.Eod
|
||||
var usedCreditDic = Modules.SwapModule.ClientCreditInoutService.GetUsedCreditByClients(clientIdList, DbContext);
|
||||
var swapInitMarginDic = SwapSpanBalanceQueryService.GetSwapInitMarginByClients(clientIdList, calcDate.Value, DbContext);
|
||||
var swapAdditionalDic = SwapSpanBalanceQueryService.GetTradeAdditionalMarginByClients(clientIdList, calcDate.Value, DbContext);
|
||||
//合约维度追保金额新口径(2026-08-28):当日追加保证金现金部分合计(日间 EOD 未跑时无当日记录 → 0,与通知书口径一致)
|
||||
var todayCashAddDic = SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients(clientIdList, calcDate.Value, DbContext);
|
||||
|
||||
foreach (var item in _clientBalanceDic.Values)
|
||||
{
|
||||
@@ -286,8 +290,9 @@ namespace YLErp.BLL.Eod
|
||||
var ruleClient = DataCacheProvider.GetClientDataSource().GetData(clientId);
|
||||
if (ruleClient?.MarginWatchRule == 0)
|
||||
{
|
||||
//定稿 2026-08-28:Max(现金结存 + 授信额度 − 已使用授信, 0),不再减交易维度追加保证金(避免双重扣减)
|
||||
item.AvailableAmount = SwapSpanBalanceCalc.CalcContractDimensionAvailable(
|
||||
item.AmountFund, item.TotalCredit, item.UsedCredit, item.SwapAdditionalMarginTotal);
|
||||
item.AmountFund, item.TotalCredit, item.UsedCredit);
|
||||
}
|
||||
else if (ruleClient?.MarginWatchRule == 1)
|
||||
{
|
||||
@@ -300,9 +305,10 @@ namespace YLErp.BLL.Eod
|
||||
{
|
||||
item.AvailableAmount = item.MarginBalance - item.FrozenMarginMoney;
|
||||
}
|
||||
// 是否追保/追保金额(按维度分流,与 ClientBalanceUtility 报告口径一致):
|
||||
// 客户维度(==1)双向追保 = (维持−初始) − (现金+授信−已使用),允许负值(负=可返还);
|
||||
// 合约维度(==0)= Max(−(现金+授信−已使用), 0),盈余截断为 0;NULL 存量维持旧口径
|
||||
// 是否追保/追保金额(按维度分流,与 ClientBalanceUtility 报告口径一致,定稿 2026-08-28 起均 Max(...,0) 截断):
|
||||
// 客户维度(==1)= Max((维持−初始) − (现金+授信−已使用), 0);
|
||||
// 合约维度(==0)= 当日追加保证金现金部分合计(2026-08-28 新口径:不与现金结存轧差、闲置现金不冲抵、授信部分不追);
|
||||
// NULL 存量维持旧口径
|
||||
if (ruleClient?.MarginWatchRule == 1)
|
||||
{
|
||||
item.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcClientDimensionCallMargin(
|
||||
@@ -312,7 +318,7 @@ namespace YLErp.BLL.Eod
|
||||
else if (ruleClient?.MarginWatchRule == 0)
|
||||
{
|
||||
item.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcContractDimensionCallMargin(
|
||||
item.AmountFund, item.TotalCredit, item.UsedCredit);
|
||||
todayCashAddDic.TryGetValue(clientId, out var todayCashAdd) ? todayCashAdd : 0);
|
||||
item.NeedAddMargin = item.MarginByPayableMarginTotal > 0;
|
||||
}
|
||||
else
|
||||
@@ -322,8 +328,25 @@ namespace YLErp.BLL.Eod
|
||||
// 追保金额=初始保证金金额-盯市金额
|
||||
item.MarginByPayableMarginTotal = item.NeedAddMargin ? (item.MySideMargin - item.SwapMarketAmount) : 0;
|
||||
}
|
||||
// 可取资金=max(期末结存+min(持仓盈亏,0)-初始保证金,0)
|
||||
item.DesirableFund =Math.Max( item.MarginBalance - item.FrozenMarginMoney + Math.Min(item.RoundedPositionPnl, 0),0);
|
||||
// 可取资金(定稿 2026-08-28 分维度口径,只算现金部分;min(持仓盈亏,0) 只扣浮亏、浮盈不放行):
|
||||
// 合约维度(==0)= Max(现金结存 + min(持仓盈亏,0), 0);
|
||||
// 客户维度(==1)= Max(现金结存 + 初始保证金 − 维持保证金 + min(持仓盈亏,0), 0);
|
||||
// NULL 存量维持旧口径(含冻结保证金扣减)
|
||||
if (ruleClient?.MarginWatchRule == 0)
|
||||
{
|
||||
item.DesirableFund = SwapSpanBalanceCalc.CalcContractDimensionDesirableFund(
|
||||
item.AmountFund, item.RoundedPositionPnl);
|
||||
}
|
||||
else if (ruleClient?.MarginWatchRule == 1)
|
||||
{
|
||||
item.DesirableFund = SwapSpanBalanceCalc.CalcClientDimensionDesirableFund(
|
||||
item.AmountFund, item.SwapInitMargin, -item.MySideMargin, item.RoundedPositionPnl);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.DesirableFund = Math.Max(item.MarginBalance - item.FrozenMarginMoney + Math.Min(item.RoundedPositionPnl, 0), 0);
|
||||
}
|
||||
logger.Info($"实时客户资金:客户{clientId} MarginWatchRule={ruleClient?.MarginWatchRule?.ToString() ?? "NULL"} 可用资金={item.AvailableAmount:0.00} 追保金额={item.MarginByPayableMarginTotal:0.00} 是否追保={item.NeedAddMargin} 可取资金={item.DesirableFund:0.00}(期末结存{item.AmountFund:0.00} 授信{item.TotalCredit:0.00} 已用授信{item.UsedCredit:0.00} 互换初始{item.SwapInitMargin:0.00} 维持{-item.MySideMargin:0.00} 追加合计{item.SwapAdditionalMarginTotal:0.00})");
|
||||
}
|
||||
|
||||
return _clientBalanceDic.Values;
|
||||
|
||||
@@ -2328,6 +2328,31 @@ namespace YLErp.BLL.Eod
|
||||
var clientBalance = clientBalances[0];
|
||||
using (var db = new YLContext())
|
||||
{
|
||||
//修改/复核已簿记交易(trade.id!=0)按增量口径校验:本笔自身已簿记的成交金额(期权费记录)
|
||||
//与初始预付金流水已计入现金结存、初始授信占用已计入已使用授信,比较前先剔除——
|
||||
//否则改备注等无关字段保存会被存量预付金二次拦截(例:入金60万、初始预付金已付50万、余额10万)。
|
||||
//只剔除确认时会重写的记录;追保/票息/平仓等生命周期记录确认后仍保留,不剔除。
|
||||
var ownCashBooked = 0d;
|
||||
var ownCreditBooked = 0d;
|
||||
if (trade.id != 0)
|
||||
{
|
||||
ownCashBooked = db.ClientCashInCashOut
|
||||
.Where(x => x.TradeId == trade.id && x.ValidState != "InValid")
|
||||
.ToList()
|
||||
.Where(x => (x.State == ClientCashInCashOut.已结算 || x.State == ClientCashInCashOut.已确认)
|
||||
&& (x.Action == ClientCashInCashOut.系统操作_期权费 || x.Action == ClientCashInCashOut.系统操作_应付预付金))
|
||||
.Sum(x => x.Money ?? 0);
|
||||
ownCreditBooked = db.client_credit_inout
|
||||
.Where(x => x.trade_id == trade.id)
|
||||
.ToList()
|
||||
.Where(x => !Modules.SwapModule.ClientCreditInoutService.IsAdditionalMarginRecord(x))
|
||||
.Sum(x => x.amount);
|
||||
clientBalance.AmountFund -= ownCashBooked;
|
||||
clientBalance.AvailableAmount -= ownCashBooked;
|
||||
}
|
||||
var ownBookedNote = trade.id != 0
|
||||
? $"(已剔除本笔已簿记资金{ownCashBooked:#,##0.000}与初始授信占用{ownCreditBooked:#,##0.000})"
|
||||
: "";
|
||||
var tradePrice = trade.TradePrice * (-TradeCalcHelper.GetSign(trade.BuySell));
|
||||
var AvailableAmount = clientBalance.AvailablePremium();
|
||||
|
||||
@@ -2381,14 +2406,15 @@ namespace YLErp.BLL.Eod
|
||||
}
|
||||
}
|
||||
var usedCredit = Modules.SwapModule.ClientCreditInoutService.GetUsedCredit(clientId, db);
|
||||
var creditCap = Math.Max(clientBalance.TotalCredit - usedCredit, 0);
|
||||
//授信上限剔除本笔自身初始占用(ownCreditBooked,增量口径,见上方净扣说明)
|
||||
var creditCap = Math.Max(clientBalance.TotalCredit - (usedCredit - ownCreditBooked), 0);
|
||||
var creditCovered = Math.Min(creditPayable, creditCap);
|
||||
//授信覆盖不足的回落现金部分 + 走现金部分 + 成交金额,合计必须 ≤ 现金结存
|
||||
var cashNeed = tradePrice + cashPayable + (creditPayable - creditCovered);
|
||||
if (cashNeed > clientBalance.AmountFund)
|
||||
{
|
||||
var totalPayable = tradePrice + cashPayable + creditPayable;
|
||||
errorMsg = $"当前交易应付总额:{totalPayable:#,##0.000}(走现金:{cashPayable + tradePrice:#,##0.000},选授信:{creditPayable:#,##0.000})。当前现金结存:{clientBalance.AmountFund:F3}(走现金部分只认现金结存),授信额度:{clientBalance.TotalCredit:F3},已使用授信:{usedCredit:F3},剩余授信:{creditCap:F3}(授信仅覆盖选授信部分,不足回落现金)。现金不足以覆盖应付的现金部分。";
|
||||
errorMsg = $"当前交易应付总额:{totalPayable:#,##0.000}(走现金:{cashPayable + tradePrice:#,##0.000},选授信:{creditPayable:#,##0.000})。当前现金结存:{clientBalance.AmountFund:F3}(走现金部分只认现金结存),授信额度:{clientBalance.TotalCredit:F3},已使用授信:{usedCredit - ownCreditBooked:F3},剩余授信:{creditCap:F3}(授信仅覆盖选授信部分,不足回落现金)。现金不足以覆盖应付的现金部分。{ownBookedNote}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,7 +531,12 @@ namespace YLErp.BLL.MarginCalculation
|
||||
}
|
||||
}
|
||||
|
||||
return SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice);
|
||||
var margin = SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice);
|
||||
if (margin.HasValue)
|
||||
{
|
||||
logger.Info($"规则15新引擎:交易{trade.id} 标的{trade.UnderlyingCode} 收盘价={closePrice:0.####}(试算初始={isInitialCalc})→ 维持保证金={margin.Value:0.00}");
|
||||
}
|
||||
return margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using MySqlConnector;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace YLErp.BLL
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨库 SQL 的物理库名解析器:代码里只写逻辑连接名(appsettings ConnectionStrings 的 key,
|
||||
/// 与 Java @Mapper 指定数据源名同语义),物理库名由各环境连接串的 database= 决定——
|
||||
/// 同一逻辑库在不同环境库名不同(如 bigdata 在测试环境为 glms_bigdata),SQL 中硬编码库名会跨环境失败。
|
||||
/// 用法:{@DbSchema.Of("bigdata")}.mf_fundarchives(返回带反引号的库名,可直接内插)。
|
||||
/// 仅标识自家的 appsettings 连接串,值不来自用户输入;仍做 \w+ 白名单校验防御配置笔误。
|
||||
/// </summary>
|
||||
public static class DbSchema
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, string> Cache = new();
|
||||
private static readonly Regex SafeIdentifier = new(@"^\w+$", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// 取逻辑连接名对应的物理库名(形如 `glms_bigdata`,含反引号)。配置缺失或库名非法立即抛错——
|
||||
/// 跨库 SQL 拼错库名在运行期才暴露更难排查,配置错误应尽早失败。
|
||||
/// </summary>
|
||||
public static string Of(string connectionKey)
|
||||
{
|
||||
return Cache.GetOrAdd(connectionKey, key =>
|
||||
{
|
||||
var connectionString = AppManager.GetConnectionString(key);
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException($"跨库SQL依赖的连接串未配置:{key}");
|
||||
}
|
||||
var database = new MySqlConnectionStringBuilder(connectionString).Database;
|
||||
if (string.IsNullOrWhiteSpace(database) || !SafeIdentifier.IsMatch(database))
|
||||
{
|
||||
throw new InvalidOperationException($"连接串 {key} 缺少 database 或库名非法:{database}");
|
||||
}
|
||||
return "`" + database + "`";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,12 +110,14 @@ namespace YLErp.Modules.MarginModule
|
||||
//2.无预付金规则:率直接为 0
|
||||
if (template.RuleType == (int)MarginRuleTypeEnum.无预付金)
|
||||
{
|
||||
logger.Info($"预付金模板取数:模板{template.id}(规则=无预付金)标的{underlyingCode},x=y=0");
|
||||
return new MarginRateResult { Template = template, InitRate = 0m, MaintainRate = 0m };
|
||||
}
|
||||
|
||||
if (template.RuleType != (int)MarginRuleTypeEnum.区间追保结构)
|
||||
{
|
||||
//其他规则不在本帮助类支持范围,显式返回 null
|
||||
logger.Info($"【警告】预付金模板取数:模板{template.id} 规则{template.RuleType}不在取数支持范围(仅 无预付金/区间追保结构),返回null由调用方兜底");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -124,6 +126,7 @@ namespace YLErp.Modules.MarginModule
|
||||
.Where(x => x.MarginTemplateId == template.id && x.ValueDate <= valueDate);
|
||||
if (!detailQuery.Any())
|
||||
{
|
||||
logger.Info($"【警告】预付金模板取数:模板{template.id} 无生效明细行(ValueDate≤{valueDate:yyyy-MM-dd}),返回null由调用方兜底");
|
||||
return null;
|
||||
}
|
||||
var latestValueDate = detailQuery.Max(x => x.ValueDate);
|
||||
@@ -158,6 +161,7 @@ namespace YLErp.Modules.MarginModule
|
||||
}
|
||||
if (!matched.Any())
|
||||
{
|
||||
logger.Info($"【警告】预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})无匹配明细行,返回null由调用方兜底");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -170,12 +174,15 @@ namespace YLErp.Modules.MarginModule
|
||||
}
|
||||
|
||||
var detail = matched.First();
|
||||
var initRate = ToDecimalRate(detail.MarginRatio1);
|
||||
var maintainRate = ToDecimalRate(detail.MarginRatio2);
|
||||
logger.Info($"预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})命中明细行{detail.id}:初始x={(initRate?.ToString("0.####") ?? "未配")},维持y={(maintainRate?.ToString("0.####") ?? "未配")}");
|
||||
return new MarginRateResult
|
||||
{
|
||||
Template = template,
|
||||
Detail = detail,
|
||||
InitRate = ToDecimalRate(detail.MarginRatio1),
|
||||
MaintainRate = ToDecimalRate(detail.MarginRatio2)
|
||||
InitRate = initRate,
|
||||
MaintainRate = maintainRate
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,6 +233,7 @@ namespace YLErp.Modules.MarginModule
|
||||
var bound = db.margin_template_v2.AsNoTracking().FirstOrDefault(x => x.id == bindingTemplateId.Value && x.IsValid);
|
||||
if (bound != null && bound.IsApplicableToBook(tradeAssetId))
|
||||
{
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=交易绑定 → 模板{bound.id}");
|
||||
return bound;
|
||||
}
|
||||
if (bound != null)
|
||||
@@ -264,17 +272,23 @@ namespace YLErp.Modules.MarginModule
|
||||
var ret = clientTemplate.FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId));
|
||||
if (ret != null)
|
||||
{
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{ret.id}");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
//3.全局默认
|
||||
return db.margin_template_v2.AsNoTracking()
|
||||
var globalDefault = db.margin_template_v2.AsNoTracking()
|
||||
.Where(x => x.IsDefault && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换") && x.ValueDate <= valueDate)
|
||||
.OrderByDescending(x => x.ValueDate)
|
||||
.ThenByDescending(x => x.id)
|
||||
.ToList()
|
||||
.FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId));
|
||||
if (globalDefault != null)
|
||||
{
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=全局默认 → 模板{globalDefault.id}");
|
||||
}
|
||||
return globalDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -373,6 +387,7 @@ namespace YLErp.Modules.MarginModule
|
||||
if (boundTemplates.TryGetValue(templateId, out var bound) && bound.IsApplicableToBook(trade.AssetId))
|
||||
{
|
||||
result[trade.id] = bound;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=交易绑定 → 模板{bound.id}");
|
||||
}
|
||||
else if (boundTemplates.ContainsKey(templateId))
|
||||
{
|
||||
@@ -391,12 +406,18 @@ namespace YLErp.Modules.MarginModule
|
||||
if (clientTemplate != null)
|
||||
{
|
||||
result[trade.id] = clientTemplate.Template;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{clientTemplate.Template.id}");
|
||||
continue;
|
||||
}
|
||||
var globalDefault = globalDefaults.FirstOrDefault(x => x.IsApplicableToBook(trade.AssetId));
|
||||
if (globalDefault != null)
|
||||
{
|
||||
result[trade.id] = globalDefault;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=全局默认 → 模板{globalDefault.id}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"【警告】预付金模板取数:交易{trade.id}(客户{trade.ClientId})三层级(交易绑定/客户默认/全局默认)均未命中有效模板");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using YLErp.DBModels;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.MarginModule;
|
||||
using YLErp.Modules.TradeModule;
|
||||
using YLErp.Helpers;
|
||||
|
||||
namespace YLErp.Modules.SwapModule.Margin
|
||||
{
|
||||
@@ -24,6 +25,8 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
/// </summary>
|
||||
public class SwapAdditionalMarginService : YLBaseService
|
||||
{
|
||||
private static readonly IYcLogger logger = LogFactory.GetLogger("EOD追保");
|
||||
|
||||
/// <summary>
|
||||
/// EOD 追保腿打标(OptName):与手工追加预付金腿(OptName=操作员实名)区分,
|
||||
/// 幂等清理、RemoveByTrade 保护与时间轴回退清理均以此识别。拆单现金腿落库时被打服务身份,簿记后回打本标识。
|
||||
@@ -79,8 +82,23 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
|
||||
/// <summary>
|
||||
/// 结算日逐客户逐交易产生追加保证金(clientFilter 为部分结算的客户过滤,与 EOD 请求一致)。
|
||||
/// 顶层兜异常日志(Error 级)后原样抛出,避免异常栈被 EOD 框架层吞掉无从定位。
|
||||
/// </summary>
|
||||
public void SettleAdditionalMargin(DateTime settleDate, List<int> clientFilter = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
SettleAdditionalMarginCore(settleDate, clientFilter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var filterDesc = clientFilter != null && clientFilter.Any() ? string.Join(",", clientFilter) : "全部";
|
||||
logger.Error($"EOD追保:结算日{settleDate:yyyy-MM-dd}(客户范围:{filterDesc})追保腿生成异常", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SettleAdditionalMarginCore(DateTime settleDate, List<int> clientFilter)
|
||||
{
|
||||
//合约维度盯市客户
|
||||
var watchClientIds = DbContextFactory.GetClientDbContext(OptUser).client.AsNoTracking()
|
||||
@@ -93,8 +111,10 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
}
|
||||
if (watchClientIds.Count == 0)
|
||||
{
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 无合约维度盯市(MarginWatchRule=0)客户,结束");
|
||||
return;
|
||||
}
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 合约维度盯市客户{watchClientIds.Count}个({string.Join(",", watchClientIds)})");
|
||||
|
||||
//存续中的互换交易(状态口径与 eodSwapQuery 一致,含当日已了结)
|
||||
var tradeStatuses = ConsTrade.TradeStatusAfterConfirmed;
|
||||
@@ -107,17 +127,39 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
.ToList();
|
||||
if (trades.Count == 0)
|
||||
{
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 盯市客户名下无存续收益互换交易,结束");
|
||||
return;
|
||||
}
|
||||
|
||||
//开始日门槛:未到交易开始日(未起息)的不参与追保——维持保证金引擎从成交日就产出 trade_span,
|
||||
//初始预付金流水却到开始日才入账,不过滤会在 成交日~开始日 之间把未到期的初始预付金
|
||||
//误判成缺口、按维持全额追加(2026-08-28 交易2571实证:07-31成交/08-03起息,重刷07-31误追500000)
|
||||
var notStartedTrades = trades.Where(t => (t.StartDate ?? t.TradeDate) > settleDate).ToList();
|
||||
if (notStartedTrades.Count > 0)
|
||||
{
|
||||
logger.Info($"EOD追保:{notStartedTrades.Count}笔交易未到开始日(id=[{string.Join(",", notStartedTrades.Select(t => t.id))}]),不参与追保结算");
|
||||
trades = trades.Except(notStartedTrades).ToList();
|
||||
if (trades.Count == 0)
|
||||
{
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 交易均未到开始日,结束");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//规则15(区间追保结构)交易:R1 三层级解析(BUG-02 修正,与引擎/确认书同口径)——
|
||||
//交易绑定→客户默认→全局默认 找到即停,只配客户/全局默认模板的交易同样纳入追保结算
|
||||
var candidateCount = trades.Count;
|
||||
var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(trades, settleDate, DbContext);
|
||||
trades = trades.Where(t => templatesByTrade.TryGetValue(t.id, out var tpl)
|
||||
&& tpl.RuleType == (int)MarginRuleTypeEnum.区间追保结构).ToList();
|
||||
if (candidateCount > trades.Count)
|
||||
{
|
||||
logger.Info($"EOD追保:{candidateCount - trades.Count}笔交易模板非区间追保结构(或三级未命中),不参与追保结算");
|
||||
}
|
||||
var tradeIds = trades.Select(t => t.id).ToList();
|
||||
if (tradeIds.Count == 0)
|
||||
{
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 规则15交易为0,结束");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,21 +217,28 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
var eodPositionService = new SwapEodPositionService(this);
|
||||
var flowEventService = new SwapFlowEventService(this);
|
||||
|
||||
var settledTrades = 0;
|
||||
var settledLegs = 0;
|
||||
var totalIncrement = 0d;
|
||||
|
||||
foreach (var clientGroup in trades.GroupBy(t => t.ClientId).OrderBy(g => g.Key))
|
||||
{
|
||||
foreach (var td in clientGroup.OrderBy(t => t.id))
|
||||
{
|
||||
if (!maintenanceByTrade.TryGetValue(td.id, out var maintenance) || maintenance <= 0)
|
||||
{
|
||||
logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——当日无trade_span维持保证金或维持≤0(maintenance={maintenance:0.00})");
|
||||
continue;
|
||||
}
|
||||
//目标追加 = 维持保证金 − 已缴初始保证金净额(现金应付预付金净收额 + 授信初始占用净额,
|
||||
//授信垫付与现金垫付同等对待,杜绝授信初始预付金被重复追加)
|
||||
var payableNet = (payableNetByTrade.TryGetValue(td.id, out var payable) ? payable : 0)
|
||||
+ (initCreditByTrade.TryGetValue(td.id, out var initCredit) ? initCredit : 0);
|
||||
var payable = payableNetByTrade.TryGetValue(td.id, out var p) ? p : 0;
|
||||
var initCredit = initCreditByTrade.TryGetValue(td.id, out var ic) ? ic : 0;
|
||||
var payableNet = payable + initCredit;
|
||||
var target = SwapAdditionalMarginCalc.CalcTarget(maintenance, payableNet);
|
||||
if (target <= 0)
|
||||
{
|
||||
logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——目标追加≤0:维持={maintenance:0.00},已缴={payableNet:0.00}(应付净额{payable:0.00}+初始授信占用{initCredit:0.00})已覆盖");
|
||||
continue;
|
||||
}
|
||||
var fundedCash = addRecordByTrade.TryGetValue(td.id, out var cash) ? cash : 0;
|
||||
@@ -198,8 +247,10 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
if (increment <= 0)
|
||||
{
|
||||
//已补足;追保回落(目标下降)不返还——负缺口在可用资金公式(Σ维持−累计)体现
|
||||
logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——增量≤0:目标={target:0.00},已补足(现金{fundedCash:0.00}+授信{fundedCredit:0.00}),追保回落不返还");
|
||||
continue;
|
||||
}
|
||||
logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})需追加:维持={maintenance:0.00},已缴={payableNet:0.00}(应付净额{payable:0.00}+初始授信占用{initCredit:0.00}),目标={target:0.00},已补足(现金{fundedCash:0.00}+授信{fundedCredit:0.00}),本次增量={increment:0.00}");
|
||||
|
||||
//幂等清理:先删本结算日起 EOD 旧追保腿及其簿记(腿/流水/占用/快照同生共死),再按最新增量重建;
|
||||
//手工追加预付金腿(OptName≠EOD追保)不受影响
|
||||
@@ -228,6 +279,10 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
.ToList();
|
||||
newLegs.ForEach(x => x.OptName = EodOptName);
|
||||
DbContext.SaveChanges();
|
||||
logger.Info($"EOD追保:交易{td.id} 追保腿落库完成——新腿{newLegs.Count}条(id=[{string.Join(",", newLegs.Select(x => x.id))}],含拆单现金腿)");
|
||||
settledTrades++;
|
||||
settledLegs += newLegs.Count;
|
||||
totalIncrement += increment;
|
||||
|
||||
//实时持仓克隆 + 开仓事件(参照 TradeConfirmService 簿记后动作,但只针对本次新腿——
|
||||
//整交易 InitialPosition 会把浮动腿实时持仓重置回开仓态、AddPositionEvent 会为全部腿重复建开仓事件,EOD 场景不可用);
|
||||
@@ -252,6 +307,8 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 完成——规则15交易{tradeIds.Count}笔,{settledTrades}笔产生追保(腿{settledLegs}条,合计增量{totalIncrement:0.00})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -289,6 +346,7 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
var oldEodRows = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id
|
||||
&& x.ValueDate >= settleDate && oldInitialIds.Contains(x.PositionId))
|
||||
.ToList();
|
||||
logger.Info($"EOD追保:交易{td.id} 幂等清理(重跑)——删EOD追保腿{oldLegs.Count}条(id=[{string.Join(",", oldLegs.Select(x => x.id))}])及其簿记:现金流水{oldCashRecords.Count}条、授信占用{oldCreditRecords.Count}条、开仓事件{oldEvents.Count}条、eod快照{oldEodRows.Count}行");
|
||||
DbContext.eod_swap_position.RemoveRange(oldEodRows);
|
||||
DbContext.swap_position.RemoveRange(oldLegs);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
/// 授信额度取 credit.Credit 合计(阶段一 §1.1 已在保存时折算 原始授信值×最大授信可用比例,消费方不再乘比例);
|
||||
/// 现金结存 = 期末结存 AmountFund(阶段二起授信不进资金,流水天然不含授信部分,无需排除);
|
||||
/// 已使用授信 = 授信出入表 Σ(amount);
|
||||
/// 初始保证金(净收取为正)= 应付预付金流水收付净额(平仓返还自动冲减);
|
||||
/// 初始保证金(净收取为正)= 初始预付金的和 = 应付预付金流水收付净额 + 初始预付金授信占用净额(2026-08-28 业务裁定:与页面"初始保证金金额"字段同口径);
|
||||
/// 维持保证金(净收取为正)= −MySideMargin(client_span 维持保证金写入 trade_span 后经 CalcClientMargin 反号聚合)。
|
||||
/// 公式整体待业务校验(EQD-6948),参数化集中在此便于校验后调整。
|
||||
/// </summary>
|
||||
@@ -24,34 +24,61 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
|
||||
/// <summary>
|
||||
/// 可用资金(合约维度,MarginWatchRule=0):
|
||||
/// Max(现金结存 + 授信额度 − 已使用授信 − 交易维度追加保证金合计, 0);
|
||||
/// 交易维度追加保证金合计 = Σ(维持保证金 − 累计保证金)(阶段三:累计=应付预付金净额;阶段四含追加保证金流水)。
|
||||
/// Max(现金结存 + 授信额度 − 已使用授信, 0)(定稿 2026-08-28:删除"−交易维度追加保证金"——
|
||||
/// 每日追保落账后现金部分已进现金结存、授信部分已进已使用授信,再减属双重扣减)。
|
||||
/// </summary>
|
||||
public static double CalcContractDimensionAvailable(double cashBalance, double totalCredit, double usedCredit,
|
||||
double tradeAdditionalMarginSum)
|
||||
public static double CalcContractDimensionAvailable(double cashBalance, double totalCredit, double usedCredit)
|
||||
{
|
||||
return Math.Max(cashBalance + totalCredit - usedCredit - tradeAdditionalMarginSum, 0);
|
||||
return Math.Max(cashBalance + totalCredit - usedCredit, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追保金额(客户维度,MarginWatchRule=1,阶段四 §4.2 双向——不以 0 截断):
|
||||
/// (维持保证金 − 初始保证金) − (现金结存 + 授信额度 − 已使用授信),正=需追保、负=可返还。
|
||||
/// 即客户维度可用资金公式的反向值(去 Max 截断)——与需求原文"现金结存+授信额度−(维持−初始)"数值互为相反数,
|
||||
/// 追保金额(客户维度,MarginWatchRule=1,定稿 2026-08-28:Max(...,0) 截断,恒 ≥ 0,只追不退):
|
||||
/// Max((维持保证金 − 初始保证金) − (现金结存 + 授信额度 − 已使用授信), 0),正=需追保;
|
||||
/// 资金富余时为 0(富余状态由可用资金/可取资金为正表达)。
|
||||
/// 即客户维度可用资金公式反向值的 Max 截断——与需求原文"现金结存+授信额度−(维持−初始)"数值互为相反数,
|
||||
/// 此处按 MarginByPayableMarginTotal 字段既有口径(正数=应追加,估值报告"应追加预付金X元")定向。
|
||||
/// </summary>
|
||||
public static double CalcClientDimensionCallMargin(double cashBalance, double totalCredit, double usedCredit,
|
||||
double initialMargin, double maintenanceMargin)
|
||||
{
|
||||
return Math.Round(maintenanceMargin - initialMargin - (cashBalance + totalCredit - usedCredit), 2, MidpointRounding.AwayFromZero);
|
||||
return Math.Max(Math.Round(maintenanceMargin - initialMargin - (cashBalance + totalCredit - usedCredit), 2, MidpointRounding.AwayFromZero), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追保金额(合约维度,MarginWatchRule=0):Max(−(现金结存 + 授信额度 − 已使用授信), 0)。
|
||||
/// 即需求原文公式 Max((现金结存+授信额度−已使用授信)×−1, 0):账户透支(现金+授信不足)为正=应补足,盈余截断为 0(不展示负数)。
|
||||
/// 追保金额(合约维度,MarginWatchRule=0,2026-08-28 新口径):
|
||||
/// = 当日产生的追加保证金资金记录中"资金来源=现金"的金额合计(入参即该值,见
|
||||
/// SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients),Max(...,0) 兜底截断。
|
||||
/// 语义:当日该笔合约产生的现金追保额(不与现金结存轧差、闲置现金不冲抵;授信部分不追);
|
||||
/// 恒 ≥ 0(只追不退,退还走平仓/出金)。通知书按当日现金追保全额展示,
|
||||
/// 累计未补足欠款体现在账户余额(现金结存)中,不并入本字段。
|
||||
/// </summary>
|
||||
public static double CalcContractDimensionCallMargin(double cashBalance, double totalCredit, double usedCredit)
|
||||
public static double CalcContractDimensionCallMargin(double todayCashAdditionalMargin)
|
||||
{
|
||||
return Math.Max(Math.Round(-(cashBalance + totalCredit - usedCredit), 2, MidpointRounding.AwayFromZero), 0);
|
||||
return Math.Max(Math.Round(todayCashAdditionalMargin, 2, MidpointRounding.AwayFromZero), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可取资金(客户维度,MarginWatchRule=1,定稿 2026-08-28 新增):
|
||||
/// Max(现金结存 + 初始保证金 − 维持保证金 + min(持仓盈亏, 0), 0)。
|
||||
/// 可用资金含授信但授信不可取现,可取资金只算现金部分;min(持仓盈亏,0) 只扣未实现亏损、浮盈不放行;
|
||||
/// 客户维度追保不产生现金流,需显式叠加保证金约束(+初始−维持)。
|
||||
/// initialMargin/maintenanceMargin 均为净收取为正。
|
||||
/// </summary>
|
||||
public static double CalcClientDimensionDesirableFund(double cashBalance, double initialMargin,
|
||||
double maintenanceMargin, double positionPnl)
|
||||
{
|
||||
return Math.Max(cashBalance + initialMargin - maintenanceMargin + Math.Min(positionPnl, 0), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可取资金(合约维度,MarginWatchRule=0,定稿 2026-08-28 新增):
|
||||
/// Max(现金结存 + min(持仓盈亏, 0), 0)。
|
||||
/// 合约维度追保已通过资金记录反映在现金结存中,无需再叠加保证金约束。
|
||||
/// </summary>
|
||||
public static double CalcContractDimensionDesirableFund(double cashBalance, double positionPnl)
|
||||
{
|
||||
return Math.Max(cashBalance + Math.Min(positionPnl, 0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Helpers;
|
||||
using YLErp.Modules.MarginModule;
|
||||
|
||||
namespace YLErp.Modules.SwapModule.Margin
|
||||
@@ -18,6 +19,8 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
/// </summary>
|
||||
public static class SwapSpanBalanceQueryService
|
||||
{
|
||||
private static readonly IYcLogger logger = LogFactory.GetLogger("预付金缺口查询");
|
||||
|
||||
/// <summary>
|
||||
/// 客户维度输入:互换初始保证金(净收取为正,按客户汇总)。
|
||||
/// 初始保证金 = 应付预付金流水收付净额 + 初始预付金授信占用净额(非"追加保证金"前缀、关联交易)——
|
||||
@@ -95,6 +98,11 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
.Select(t => t.id)
|
||||
.ToHashSet();
|
||||
maintenance = maintenance.Where(x => rule15TradeIds.Contains(x.TradeId)).ToList();
|
||||
var droppedCount = spanTradeIds.Count - rule15TradeIds.Count;
|
||||
if (droppedCount > 0)
|
||||
{
|
||||
logger.Info($"预付金缺口查询:{valueDate:yyyy-MM-dd} {droppedCount}笔有span交易模板非规则15(或三级未命中),不计入交易维度追加合计");
|
||||
}
|
||||
if (maintenance.Count == 0)
|
||||
{
|
||||
return result;
|
||||
@@ -142,7 +150,38 @@ namespace YLErp.Modules.SwapModule.Margin
|
||||
result[group.Key] = total;
|
||||
}
|
||||
|
||||
logger.Info($"预付金缺口查询:{valueDate:yyyy-MM-dd} 规则15交易{rule15TradeIds.Count}笔,客户{result.Count}个,交易维度追加合计:{string.Join(";", result.Select(kv => $"客户{kv.Key}={kv.Value:0.00}"))}");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合约维度追保金额输入(2026-08-28 新口径):当日产生的追加保证金资金记录中"资金来源=现金"的金额合计(按客户)。
|
||||
/// 仅统计 Action=系统操作_追加保证金 且 HappenDate=当日 的现金流水(−Σ(Money),收取为正)——
|
||||
/// 授信部分只写 client_credit_inout 不产生资金记录(授信不追、现金才追);
|
||||
/// 腿化改造后现金流水逐日增量(每日新腿一条、HappenDate=结算日,同日重跑先清后建,幂等);
|
||||
/// 该 Action 仅 EOD 追保会写(手工链路写应付预付金,语义天然隔离);追保回落不返还(无负记录),结果天然 ≥ 0。
|
||||
/// 注意:EOD 未跑(日间实时)时当日无记录,结果为 0——与通知书口径一致。
|
||||
/// </summary>
|
||||
public static Dictionary<int, double> GetTodayCashAdditionalMarginByClients(List<int> clientIds, DateTime valueDate, YLContext db)
|
||||
{
|
||||
if (clientIds == null || clientIds.Count == 0)
|
||||
{
|
||||
return new Dictionary<int, double>();
|
||||
}
|
||||
|
||||
var dayStart = valueDate.Date;
|
||||
var dayEnd = dayStart.AddDays(1);
|
||||
return db.ClientCashInCashOut.AsNoTracking()
|
||||
.Where(x => clientIds.Contains(x.ClientId ?? 0)
|
||||
&& x.Action == ClientCashInCashOut.系统操作_追加保证金
|
||||
&& x.HappenDate >= dayStart && x.HappenDate < dayEnd
|
||||
&& x.ValidState != ConsGlobal.InValid
|
||||
&& (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算)
|
||||
&& x.Money != null)
|
||||
.GroupBy(x => x.ClientId ?? 0)
|
||||
.Select(g => new { ClientId = g.Key, Sum = -g.Sum(x => x.Money ?? 0d) })
|
||||
.ToDictionary(x => x.ClientId, x => x.Sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.SwapModule.Margin;
|
||||
using YLErp.Modules.TradeModule;
|
||||
using YLErp.Helpers;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
@@ -15,6 +16,8 @@ namespace YLErp.Modules.SwapModule
|
||||
/// </summary>
|
||||
public class SwapFundTagService : YLBaseService
|
||||
{
|
||||
private static readonly IYcLogger logger = LogFactory.GetLogger("预付金簿记");
|
||||
|
||||
public SwapFundTagService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
@@ -167,6 +170,7 @@ namespace YLErp.Modules.SwapModule
|
||||
.ThenBy(x => x.Leg.id)
|
||||
.ToList();
|
||||
var plans = FundTagCalc.AllocateByLegPreference(allocateLegs, (decimal)creditAvailable, ignoreMoneyCheck);
|
||||
logger.Info($"预付金簿记:交易{td.id}(客户{td.ClientId})簿记日{valueDate:yyyy-MM-dd}(Action={cashAction})可用授信={creditAvailable:0.00},{marginLegs.Count}条预付金腿分配:{(plans.Any() ? string.Join(";", plans.Select(p => $"腿{p.Leg.id}应付{p.Amount:0.00}→授信{p.CreditAmount:0.00}/现金{p.CashAmount:0.00}{(p.NeedSplit ? "(拆单)" : "")}")) : "无正应付腿,全部直通现金标签")}");
|
||||
|
||||
//先落库拆分的新现金腿(需要 id 才能绑定现金流水)
|
||||
foreach (var plan in plans.Where(p => p.NeedSplit))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Helpers;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.SwapModule;
|
||||
using YLErp.Modules.SwapModule.ReturnLegs;
|
||||
@@ -9,7 +10,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
|
||||
/// <summary>
|
||||
/// 构造普通收益互换结算单单行数据所需的业务输入。
|
||||
/// 数据来自平仓事件、关联交易及结算日日终持仓。
|
||||
/// 数据来自平仓事件及其关联交易。
|
||||
/// </summary>
|
||||
public sealed class SwapSettlementBillRowInput
|
||||
{
|
||||
@@ -40,9 +41,6 @@ public sealed class SwapSettlementBillRowInput
|
||||
/// <summary>平仓事件浮动腿记录的期末结算收益率(展示态数值)。</summary>
|
||||
public decimal? ExitYtm { get; set; }
|
||||
|
||||
/// <summary>结算日日终持仓中的当日浮动端分红,保留原始收付方向。</summary>
|
||||
public decimal CurrentDayFloatingDividend { get; set; }
|
||||
|
||||
/// <summary>期间付息或分红是否计入本次净额结算。</summary>
|
||||
public bool IncludePeriodPaymentInNetting { get; set; }
|
||||
}
|
||||
@@ -89,6 +87,7 @@ public static class SwapSettlementBillRowBuilder
|
||||
var floatingAmount = -input.CloseFlow.MarkClosePnl;
|
||||
var fee = -(input.CloseFlow.TradingFee + input.CloseFlow.TradingFeePending);
|
||||
var marginInterest = -marginEvents.Sum(x => x.InterestClosePnL);
|
||||
var periodAmount = -input.CloseFlow.DividendPending;
|
||||
var initialMargin = SumMargin(effectiveMargins, InterestModeEnum.初始预付金);
|
||||
|
||||
var additionalMarginPositions = positions
|
||||
@@ -106,6 +105,16 @@ public static class SwapSettlementBillRowBuilder
|
||||
|
||||
|
||||
var isCashBond = ConsGlobal.InstrumentType.IsBond(input.UnderlyingInstrumentType);
|
||||
var isEtf = ConsGlobal.InstrumentType.Fund.Equals(
|
||||
input.UnderlyingInstrumentType,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
var initialPrice = settlementPosition?.PosiGrossPrice ?? 0m;
|
||||
var closePrice = input.CloseFlow.TradingAmountAvg;
|
||||
if (isCashBond)
|
||||
{
|
||||
initialPrice = BondPriceConverter.ToDisplay(initialPrice);
|
||||
closePrice = BondPriceConverter.ToDisplay(closePrice);
|
||||
}
|
||||
|
||||
|
||||
return new ExcelReportModel
|
||||
@@ -121,16 +130,16 @@ public static class SwapSettlementBillRowBuilder
|
||||
PosiNotionalValue = input.CloseNotionalValue.ToString("0.00"),
|
||||
Quantity = input.CloseFlow.Quantity.ToString("0.00"),
|
||||
DividendIn = isCashBond
|
||||
? input.CurrentDayFloatingDividend.ToString("0.00")
|
||||
? periodAmount.ToString("0.00")
|
||||
: string.Empty,
|
||||
PeriodDividend = isCashBond
|
||||
? string.Empty
|
||||
: input.CurrentDayFloatingDividend.ToString("0.00"),
|
||||
PosiNetPrice = ((settlementPosition?.PosiGrossPrice ?? 0m) * 100m).ToString("0.00000000"),
|
||||
PeriodDividend = isEtf
|
||||
? periodAmount.ToString("0.00")
|
||||
: string.Empty,
|
||||
PosiNetPrice = initialPrice.ToString("0.00000000"),
|
||||
InitYtm = isCashBond && input.Trade.InitYtm.HasValue
|
||||
? input.Trade.InitYtm.Value.ToString("0.####%")
|
||||
: string.Empty,
|
||||
ClosePrice = (input.CloseFlow.TradingAmountAvg * 100m).ToString("0.00000000"),
|
||||
ClosePrice = closePrice.ToString("0.00000000"),
|
||||
ExitYtm = input.ExitYtm.HasValue
|
||||
? input.ExitYtm.Value.ToString("0.0000")
|
||||
: string.Empty,
|
||||
|
||||
@@ -334,7 +334,9 @@ namespace YLErp.BLL
|
||||
.FirstOrDefault();
|
||||
if (oa != null)
|
||||
{
|
||||
tradeLinq.OaRemark = string.IsNullOrWhiteSpace(oa.oa_msg)
|
||||
tradeLinq.OaRemark = oa.status == "提交失败"
|
||||
? oa.status
|
||||
: string.IsNullOrWhiteSpace(oa.oa_msg)
|
||||
? oa.status
|
||||
: oa.status + ":" + oa.oa_msg;
|
||||
}
|
||||
|
||||
@@ -30,16 +30,21 @@ namespace YLErp.Modules.UnderlyingModule
|
||||
public string InvestAdvisorName { get; set; }
|
||||
}
|
||||
|
||||
private const string LookupSql = @"
|
||||
//物理库名由连接串 database= 决定(DbSchema 解析),各环境库名不同(如测试环境 glms_bigdata),
|
||||
//SQL 不硬编码库名;连接开在 bigdata 数据源上,将来跨库 join ERP 主库时用 DbSchema.Of("ylcms") 同法插值
|
||||
private string BuildLookupSql()
|
||||
{
|
||||
return $@"
|
||||
SELECT
|
||||
ia.investadvisorcode AS InvestAdvisorCode,
|
||||
ia.investadvisorname AS InvestAdvisorName
|
||||
FROM glms_bigdata.mf_fundarchives AS fa
|
||||
INNER JOIN glms_bigdata.mf_investadvisoroutline AS ia
|
||||
FROM {DbSchema.Of("bigdata")}.mf_fundarchives AS fa
|
||||
INNER JOIN {DbSchema.Of("bigdata")}.mf_investadvisoroutline AS ia
|
||||
ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||
WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
CONVERT(TRIM(SUBSTRING_INDEX(@UnderlyingCode, '.', 1)) USING utf8mb4) COLLATE utf8mb4_unicode_ci";
|
||||
}
|
||||
|
||||
public FundManagerLookupResult Lookup(string underlyingCode)
|
||||
{
|
||||
@@ -49,7 +54,7 @@ WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
return new FundManagerLookupResult { Status = FundManagerLookupStatus.NotFound };
|
||||
}
|
||||
|
||||
var connectionString = AppManager.GetConnectionString("glms_bigdata");
|
||||
var connectionString = AppManager.GetConnectionString("bigdata");
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
return new FundManagerLookupResult { Status = FundManagerLookupStatus.Unavailable };
|
||||
@@ -58,7 +63,7 @@ WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
try
|
||||
{
|
||||
using var connection = new MySqlConnection(connectionString);
|
||||
var matches = connection.Query<FundManagerRow>(LookupSql, new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
||||
var matches = connection.Query<FundManagerRow>(BuildLookupSql(), new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
||||
.Where(row => !string.IsNullOrWhiteSpace(row.InvestAdvisorName))
|
||||
.GroupBy(row => (row.InvestAdvisorCode ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => group.Select(row => row.InvestAdvisorName.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray())
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace YLErp.Web.Controllers
|
||||
else
|
||||
{
|
||||
var marginTemplate = yldb.margin_template_v2.Find(template.MarginTemplateId);
|
||||
//绑定模板不在上方下拉筛选范围内(如已失效)时,Find 得到的是未挂 Details 的新实例,右栏只读回显会空白
|
||||
if (marginTemplate.Details.Count == 0)
|
||||
{
|
||||
marginTemplate.Details = yldb.margin_template_detail.Where(y => y.MarginTemplateId == marginTemplate.id).ToList();
|
||||
}
|
||||
ViewBag.MarginTemplate = marginTemplate;
|
||||
}
|
||||
return View(template);
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
@if (pageObj.isAdd)
|
||||
{
|
||||
@*新增:客户多选(类似适用簿记账户),保存时按选中客户逐条调用原有单条保存接口*@
|
||||
<select id="ClientId" class="chosen-select" multiple data-placeholder="请选择客户" style="width:260px;" v-model="selectedClientIds">
|
||||
<select id="ClientId" class="chosen-select" multiple data-placeholder="请选择客户" style="width:260px;">
|
||||
<option v-for="item in clientNames" v-bind:value="item.Value">{{item.Text}}</option>
|
||||
</select>
|
||||
}
|
||||
@@ -104,8 +104,69 @@
|
||||
</div>
|
||||
|
||||
<div class="col form-layout" style="height: 520px; overflow-y:auto;">
|
||||
@*区间追保结构(规则15)只读回显:参数组区块与 margin_template_v2Edit 同款样式(本页为绑定信息页,参数本身在预付金模板V2维护,故全部只读)*@
|
||||
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
|
||||
<div v-for="blk in ruleRangeBlocks" v-bind:key="'spanblk'+blk.key" class="border spanBlk" style="padding:6px 10px; margin-top:10px;">
|
||||
<p style="margin:4px 0; font-weight:bold;">
|
||||
参数组{{blk.no}}:<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
|
||||
<span v-if="blk.etfKind"> ETF 子类:{{blk.etfKind}}</span>
|
||||
<span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、>30y,每个档位独立设置)</span>
|
||||
</p>
|
||||
<div v-for="sec in blk.sections" v-bind:key="sec.key">
|
||||
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
|
||||
<template v-if="sec.detail && sec.detail.SpanConfig">
|
||||
<p style="margin:4px 0;">
|
||||
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent" disabled></vue-number-input>
|
||||
平仓线:<vue-number-input v-model="sec.detail.SpanConfig.CloseLine" v-bind:format="inputFormatPercent" disabled></vue-number-input>
|
||||
</p>
|
||||
<p style="margin:4px 0;">多头方向(客户看多)</p>
|
||||
<table class="table table-bordered" style="margin-bottom:6px;">
|
||||
<thead>
|
||||
<tr><th>追保价格区间</th><th>追保金额</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(tier, ti) in sec.detail.SpanConfig.LongSpans" v-bind:key="'long'+ti">
|
||||
<td>
|
||||
<template v-if="ti === 0">
|
||||
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}}
|
||||
</template>
|
||||
<template v-else>
|
||||
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}} < <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}}
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).amountBase}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin:4px 0;">空头方向(客户看空)</p>
|
||||
<table class="table table-bordered" style="margin-bottom:0;">
|
||||
<thead>
|
||||
<tr><th>追保价格区间</th><th>追保金额</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(tier, ti) in sec.detail.SpanConfig.ShortSpans" v-bind:key="'short'+ti">
|
||||
<td>
|
||||
<template v-if="ti === 0">
|
||||
{{spanText(sec.detail).priceCurShort1}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}}
|
||||
</template>
|
||||
<template v-else>
|
||||
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}} < {{spanText(sec.detail).priceCurShort}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).priceInit}}
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent" disabled></vue-number-input> × {{spanText(sec.detail).amountBase}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-for="(detail,index) in marginTemplate.Details">
|
||||
<div class="border detail">
|
||||
<div class="border detail" v-show="marginTemplate.RuleType != @((int)MarginRuleTypeEnum.区间追保结构)">
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按固定利率) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using YLErp.Enums;
|
||||
@{
|
||||
ViewBag.Title = "预付金模板V2";
|
||||
ViewBag.Title = "预付金模板";
|
||||
ViewBag.Menu = "数据管理-预付金模板V2";
|
||||
Layout = "~/Views/Shared/_MainLayout.cshtml";
|
||||
}
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
function setClientMarginTemplate(id) {
|
||||
var editurl = "/client_margin_template/client_margin_templateEdit/?id=" + id;
|
||||
main.infopage("设置客户预付金", editurl, { area: ['1480px', '800px'] });
|
||||
main.infopage(id ? "编辑客户预付金绑定" : "新增客户预付金绑定", editurl, { area: ['1480px', '800px'] });
|
||||
}
|
||||
|
||||
function deleteClientMarginTemplate(id) {
|
||||
|
||||
@@ -60,6 +60,14 @@
|
||||
changeFormCredit(Number(moneytemp).toFixed(2) + "");
|
||||
}
|
||||
|
||||
//授信可用比例默认填1;授信额度置灰,按 原始授信值×最大授信可用比例 联动计算
|
||||
if (!$("#MaxCreditUseRatio").val()) {
|
||||
$("#MaxCreditUseRatio").val("1");
|
||||
}
|
||||
$("#OriginalCredit").on("input change", calcCreditByRatio);
|
||||
$("#MaxCreditUseRatio").on("input change", calcCreditByRatio);
|
||||
calcCreditByRatio();
|
||||
|
||||
//$('#Credit').keyup(function () {
|
||||
// changePrice();
|
||||
//});
|
||||
@@ -134,6 +142,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
//授信额度 = 原始授信值 × 最大授信可用比例(空白按1);原始授信值为空时不折算,保留原授信额度
|
||||
function calcCreditByRatio() {
|
||||
var original = $("#OriginalCredit").val();
|
||||
if (original == null || original == "") {
|
||||
return;
|
||||
}
|
||||
var ratio = $("#MaxCreditUseRatio").val();
|
||||
var money = (Number(original.replace(/,/g, "")) * (ratio == "" ? 1 : Number(ratio))).toFixed(2);
|
||||
$("#Credit").val(money);
|
||||
$("#CreditVal").val(money);
|
||||
changeFormCredit(money);
|
||||
}
|
||||
|
||||
function checkSubmitData() {
|
||||
var pass = $('#creditEditForm').valid();
|
||||
if (pass) {
|
||||
@@ -416,11 +437,11 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@Html.MyDecimalFor(model => model.Credit, new { onfocus = "moneyOnFocus()", onblur = "this.value=cc(this.value);" }, !pageObj.UseClientStockEqvNotional)
|
||||
@Html.MyDecimalFor(model => model.Credit, new { @readonly = "readonly", style = "background-color:#eee;" }, !pageObj.UseClientStockEqvNotional)
|
||||
<input type="hidden" value="@Model.Credit" name="CreditVal" id="CreditVal" />
|
||||
@Html.MyDecimalFor(model => model.OriginalCredit, new { onfocus = "moneyOnFocusOriginalCredit()", onblur = "this.value=cc(this.value);" }, required: false)
|
||||
@Html.MyDecimalFor(model => model.MaxCreditUseRatio, required: false)
|
||||
<span style="color:red">注:填写原始授信值后,授信额度将按"原始授信值×最大授信可用比例(空白按1)"自动计算.</span>
|
||||
<span style="color:red">注:授信额度按"原始授信值×最大授信可用比例(默认1)"自动计算,无需填写;原始授信值为空时不折算,保留原授信额度.</span>
|
||||
@if (PS.Config.Company == CompanyEnum.中金)
|
||||
{
|
||||
@Html.MyDecimalFor(model => model.PFECredit, new { onkeyup = "changePFECredit()", onfocus = "pfeCreditOnFocus()", onblur = "this.value=cc(this.value);" }, true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@{
|
||||
ViewBag.Title = "预付金模板V2";
|
||||
ViewBag.Title = "预付金模板";
|
||||
ViewBag.Menu = "数据管理-预付金模板V2";
|
||||
Layout = "~/Views/Shared/_MainLayout.cshtml";
|
||||
}
|
||||
@@ -119,13 +119,13 @@
|
||||
if (id) {
|
||||
editurl = "/margin_template_v2/margin_template_v2ClientEdit/?enid=" + id;
|
||||
}
|
||||
main.infopage("编辑默认预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
main.infopage(id ? "编辑客户预付金模板" : "新增客户预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
}
|
||||
|
||||
function startCopymargin_template_v2(id) {
|
||||
var editurl = "/margin_template_v2/margin_template_v2ClientCopy/?enid=" + id;
|
||||
|
||||
main.infopage("复制默认预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
main.infopage("复制客户预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
}
|
||||
|
||||
function deleteEditmargin_template_v2(id) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@{
|
||||
ViewBag.Title = "预付金模板V2";
|
||||
ViewBag.Title = "预付金模板";
|
||||
ViewBag.Menu = "数据管理-预付金模板V2";
|
||||
Layout = "~/Views/Shared/_MainLayout.cshtml";
|
||||
}
|
||||
@@ -127,12 +127,12 @@
|
||||
if (id) {
|
||||
editurl = "/margin_template_v2/margin_template_v2DefaultEdit/?enid=" + id;
|
||||
}
|
||||
main.infopage("编辑默认预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
main.infopage(id ? "编辑全局预付金模板" : "新增全局预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
}
|
||||
|
||||
function startCopymargin_template_v2(id) {
|
||||
editurl = "/margin_template_v2/margin_template_v2DefaultCopy/?enid=" + id;
|
||||
main.infopage("复制默认预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
main.infopage("复制全局预付金模板", editurl, { area: ['1480px', '800px'] });
|
||||
}
|
||||
|
||||
function deleteEditmargin_template_v2(id) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using YLErp.Enums;
|
||||
@{
|
||||
ViewBag.Title = "预付金模板V2";
|
||||
ViewBag.Title = "预付金模板";
|
||||
ViewBag.Menu = "数据管理-预付金模板V2";
|
||||
Layout = "~/Views/Shared/_MainLayout.cshtml";
|
||||
}
|
||||
@@ -134,13 +134,13 @@
|
||||
if (id) {
|
||||
editurl = "/margin_template_v2/margin_template_v2Edit/?enid=" + id;
|
||||
}
|
||||
main.infopage("编辑预付金模板", editurl, { area: ['1180px', '92%'] });
|
||||
main.infopage(id ? "编辑自定义预付金模板" : "新增自定义预付金模板", editurl, { area: ['1180px', '92%'] });
|
||||
}
|
||||
|
||||
function startCopymargin_template_v2(id) {
|
||||
editurl = "/margin_template_v2/margin_template_v2Copy/?enid=" + id;
|
||||
|
||||
main.infopage("复制预付金模板", editurl, { area: ['1180px', '92%'] });
|
||||
main.infopage("复制自定义预付金模板", editurl, { area: ['1180px', '92%'] });
|
||||
}
|
||||
|
||||
function startDeletemargin_template_v2(id) {
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
},
|
||||
"AppSettings": {
|
||||
"VirtualPathRoot": "",
|
||||
|
||||
@@ -3,6 +3,21 @@ const inputFormatPercent = Object.freeze({ precision: 2, append: '%' });
|
||||
const inputFormatPercentNegative = Object.freeze({ precision: 2, append: '%', negative: true });
|
||||
const inputFormatNegative = Object.freeze({ precision: 2, negative: true });
|
||||
|
||||
//区间追保结构(RuleType=15)只读回显用常量,与 marginTemplateV2Edit.js 保持一致
|
||||
const RangeMarginRuleType = 15;
|
||||
//允许配期限档(SpanConfig.BondTerm)的资产类型掩码,本期仅利率债
|
||||
const TermTierEnabledUnderlyingMask = 16;
|
||||
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案])
|
||||
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
|
||||
//标的资产类型标志位 -> 名称(与 marginTemplateV2Edit 的录入区块标题一致)
|
||||
const UnderlyingTypeNames = [
|
||||
[16, "利率债"], [32, "信用债"], [64, "其它债券"], [128, "股票"], [256, "股指"], [512, "股指期货"],
|
||||
[1024, "商品期货"], [2048, "商品现货"], [4096, "新三板挂牌股票"], [8192, "香港股票"], [16384, "香港股指"],
|
||||
[32768, "基金及基金专户"], [65536, "黄金期货"], [131072, "国债期货"], [262144, "其他期货"], [524288, "黄金现货"],
|
||||
[1048576, "其他现货"], [2097152, "境外期货"], [4194304, "境外现货"], [8388608, "境外股票"], [16777216, "境外股指"],
|
||||
[33554432, "汇率"], [67108864, "Shibor"], [134217728, "银行间回购定盘"], [268435456, "利率收益率"], [536870912, "债券指数"]
|
||||
];
|
||||
|
||||
const vue = new Vue({
|
||||
el: '#marginTemplateV2Form',
|
||||
data: {
|
||||
@@ -10,14 +25,92 @@ const vue = new Vue({
|
||||
marginTemplates: page.marginTemplates,
|
||||
marginTemplate: page.marginTemplate,
|
||||
clientNames: [],
|
||||
//新增模式的客户多选(编辑模式仍为单条绑定,走 clientMarginTemplate.ClientId)
|
||||
selectedClientIds: [],
|
||||
isAdd: page.isAdd
|
||||
},
|
||||
created: function () {
|
||||
this.clientMarginTemplate.ValueDate = new moment(this.clientMarginTemplate.ValueDate).format("YYYY-MM-DD");
|
||||
},
|
||||
computed: {
|
||||
//区间追保结构只读参数组:连续利率债行归一个区块、按固定4档对位(缺档不补造、同档重复/异常档独立成段保留展示);
|
||||
//其余行(含ETF子类行)每行独立成块——分组口径与 marginTemplateV2Edit.rebuildSpanBlocks 一致,但只回显不新增行
|
||||
ruleRangeBlocks() {
|
||||
if (this.marginTemplate.RuleType != RangeMarginRuleType) return [];
|
||||
var details = this.marginTemplate.Details || [];
|
||||
var that = this;
|
||||
var blocks = [];
|
||||
var i = 0;
|
||||
while (i < details.length) {
|
||||
if ((details[i].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) {
|
||||
var j = i;
|
||||
while (j < details.length && (details[j].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) j++;
|
||||
var byTerm = {};
|
||||
var extras = [];
|
||||
for (var k = i; k < j; k++) {
|
||||
var bt = (details[k].SpanConfig && details[k].SpanConfig.BondTerm) || '';
|
||||
var canonical = SpanBondTerms.some(function (t) { return t[0] === bt; });
|
||||
if (canonical && !byTerm[bt]) byTerm[bt] = details[k]; else extras.push(details[k]);
|
||||
}
|
||||
var sections = SpanBondTerms.map(function (t) {
|
||||
return { key: 't' + t[0], termLabel: t[1], detail: byTerm[t[0]] || null };
|
||||
});
|
||||
extras.forEach(function (r, xi) {
|
||||
sections.push({ key: 'x' + xi, termLabel: that.bondTermLabel(r), detail: r });
|
||||
});
|
||||
blocks.push({ key: 'i' + i, no: blocks.length + 1, ut: TermTierEnabledUnderlyingMask, isTBond: true, etfKind: '', sections: sections });
|
||||
i = j;
|
||||
} else {
|
||||
var d = details[i];
|
||||
blocks.push({
|
||||
key: 'i' + i, no: blocks.length + 1, ut: d.UnderlyingType || 0, isTBond: false,
|
||||
etfKind: (d.SpanConfig && d.SpanConfig.EtfKind) || '',
|
||||
sections: [{ key: 'single', termLabel: '', detail: d }]
|
||||
});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//资产类型标志位掩码 -> 名称(0=全部),与 marginTemplateV2Edit.underlyingTypeNames 一致
|
||||
underlyingTypeNames(mask) {
|
||||
mask = parseInt(mask) || 0;
|
||||
if (mask === 0) return "全部";
|
||||
var names = [];
|
||||
for (var i = 0; i < UnderlyingTypeNames.length; i++) {
|
||||
if ((mask & UnderlyingTypeNames[i][0]) > 0) names.push(UnderlyingTypeNames[i][1]);
|
||||
}
|
||||
return names.length > 0 ? names.join("、") : "全部";
|
||||
},
|
||||
//期限档 BondTerm -> 展示文案(空=全部)
|
||||
bondTermLabel(detail) {
|
||||
var v = (detail.SpanConfig && detail.SpanConfig.BondTerm) || '';
|
||||
var map = { '': '全部', '<5y': '≤5y', '5y-10y': '(5y-10y]', '10y-30y': '(10y-30y]', '>30y': '>30y' };
|
||||
return map[v] !== undefined ? map[v] : v;
|
||||
},
|
||||
//区间计价口径文案:按 detail 资产类型切换,与取数侧对齐(债券类=中债估值净价/期初全价×券面总额,其余=收盘价口径),
|
||||
//直接沿用 marginTemplateV2Edit.spanText 的判定
|
||||
spanText(detail) {
|
||||
var ut = (detail && detail.UnderlyingType) || 0;
|
||||
var bondMask = 16 | 32 | 64;
|
||||
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
|
||||
if (!isBond) {
|
||||
return {
|
||||
priceInit: '参考标的期初净价',
|
||||
priceCur: '参考标的当前收盘价',
|
||||
priceCurShort1: '参考标的当前收盘价',
|
||||
priceCurShort: '参考标的当前收盘价',
|
||||
amountBase: '参考标的期初价格 × 参考标的名义份额'
|
||||
};
|
||||
}
|
||||
return {
|
||||
priceInit: '期初净价',
|
||||
priceCur: '当前净价',
|
||||
priceCurShort1: '当前净价',
|
||||
priceCurShort: '当前净价',
|
||||
amountBase: '期初全价 × 券面总额'
|
||||
};
|
||||
},
|
||||
changeMarginTemplate() {
|
||||
this.marginTemplates.forEach(x => {
|
||||
if (this.clientMarginTemplate.MarginTemplateId === x.id) {
|
||||
@@ -32,7 +125,12 @@ const vue = new Vue({
|
||||
//当前内容上供修正重试(已成功的客户重试会因同天同结构互斥校验失败,不会重复入库)
|
||||
saveMarginTemplateV2() {
|
||||
var thisObj = this;
|
||||
var ids = this.isAdd ? (this.selectedClientIds || []) : [this.clientMarginTemplate.ClientId];
|
||||
//取值直接读控件(与兄弟页面读 chosen 的 $("#tradeTypes").val() 一致,不依赖 v-model 同步):
|
||||
//新增=多选控件 val()(数组);编辑=表单对象上的原绑定客户
|
||||
var chosenVal = this.isAdd ? $("#ClientId").val() : this.clientMarginTemplate.ClientId;
|
||||
var ids = this.isAdd
|
||||
? (Array.isArray(chosenVal) ? chosenVal : (chosenVal ? [chosenVal] : []))
|
||||
: [chosenVal];
|
||||
if (!ids.length) {
|
||||
main.message("请至少选择一个客户");
|
||||
return;
|
||||
|
||||
@@ -1968,6 +1968,8 @@ function __init(vue) {
|
||||
let clientId = page.Trade.ClientId;
|
||||
let client = clientId ? consClients.find(x => x.id === clientId) : null;
|
||||
!client && page.isAdd && (client = consClients[0]);
|
||||
//setData 只写控件显示值、不触发 onSelect 回写 v-model——新增时默认客户须显式回写,否则 trade.ClientId 为空、保存报"请设置交易对手方"
|
||||
client && page.isAdd && vue.changeClient(client);
|
||||
autoClient.setData(client);
|
||||
_getMainProtocolCode(client);
|
||||
}
|
||||
|
||||
@@ -1185,6 +1185,8 @@ function __init(vue) {
|
||||
let clientId = page.Trade.ClientId;
|
||||
let client = clientId ? consClients.find(x => x.id === clientId) : null;
|
||||
!client && page.IsNew && (client = consClients[0]);
|
||||
//setData 只写控件显示值、不触发 onSelect 回写 v-model——新增时默认客户须显式回写,否则 trade.ClientId 为空、保存报"请设置交易对手方"
|
||||
client && page.IsNew && (vue.trade.ClientId = client.id);
|
||||
autoClient.setData(client);
|
||||
_getMainProtocolCode(client);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user