- EOD追保生成主流程:入口汇总/六处跳过原因/决策中间量(维持·已缴·目标·已补足·增量)/建腿落库/幂等清理/收尾汇总,顶层异常Error留栈 - 预付金模板取数:三级层级命中来源(交易绑定/客户默认/全局默认)+明细行命中(品种·期限档·x/y),null返回各留一句 - 规则15引擎:成功路径补收盘价→维持保证金,与既有无价格告警闭环 - EOD/实时客户资金:每客户一行维度分流结果(MarginWatchRule·可用资金·追保金额·可取资金及全部输入) - 预付金缺口查询:非规则15剔除数与按客户追加合计;预付金簿记:授信/现金拆分结果
159 lines
9.6 KiB
C#
159 lines
9.6 KiB
C#
using YLErp.BLL;
|
||
using YLErp.DBModels;
|
||
using YLErp.Enums;
|
||
using YLErp.Helpers;
|
||
using YLErp.Modules.MarginModule;
|
||
|
||
namespace YLErp.Modules.SwapModule.Margin
|
||
{
|
||
/// <summary>
|
||
/// R2 阶段三 §3.2 估值报告/可用资金查询输入(静态查询,供 ClientBalanceUtility 与 RealTimeClientBanlanceService 共用,保证三处口径一致)。
|
||
/// 口径:
|
||
/// - 互换初始保证金(净收取为正)= 客户 应付预付金 流水收付净额取反 + 初始预付金授信占用净额
|
||
/// (授信垫付的初始预付金无资金流水,2026-08-27 补入口径,否则授信垫付客户初始保证金展示为 0);
|
||
/// - 交易维度追加保证金(合约维度)= Σ(维持保证金 − 累计保证金):
|
||
/// 维持保证金取当日 trade_span.Spv(区间追保结构引擎产出,我方净收取为正);
|
||
/// 累计保证金 = 该交易 应付预付金+追加保证金 流水收付净额 + 追加保证金授信占用净额(§0 口径,阶段四 §4.1 起)
|
||
/// + 初始预付金授信占用净额(非"追加保证金"前缀,授信垫付的初始预付金无现金流水,2026-08-27 与 EOD 追保侧同步修正);
|
||
/// 仅统计规则15(区间追保结构,R1 三层级解析,与引擎同口径)且有当日 trade_span 的交易。
|
||
/// </summary>
|
||
public static class SwapSpanBalanceQueryService
|
||
{
|
||
private static readonly IYcLogger logger = LogFactory.GetLogger("预付金缺口查询");
|
||
|
||
/// <summary>
|
||
/// 客户维度输入:互换初始保证金(净收取为正,按客户汇总)。
|
||
/// 初始保证金 = 应付预付金流水收付净额 + 初始预付金授信占用净额(非"追加保证金"前缀、关联交易)——
|
||
/// 授信垫付的初始预付金不产生资金流水,只算流水会把授信垫付部分漏掉(展示为 0)。
|
||
/// </summary>
|
||
public static Dictionary<int, double> GetSwapInitMarginByClients(List<int> clientIds, DateTime valueDate, YLContext db)
|
||
{
|
||
if (clientIds == null || clientIds.Count == 0)
|
||
{
|
||
return new Dictionary<int, double>();
|
||
}
|
||
|
||
var flows = db.ClientCashInCashOut.AsNoTracking()
|
||
.Where(x => clientIds.Contains(x.ClientId ?? 0)
|
||
&& x.Action == ClientCashInCashOut.系统操作_应付预付金
|
||
&& x.HappenDate <= valueDate
|
||
&& x.ValidState != ConsGlobal.InValid
|
||
&& (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算)
|
||
&& x.Money != null)
|
||
.Select(x => new { ClientId = x.ClientId ?? 0, Money = x.Money ?? 0d })
|
||
.ToList();
|
||
|
||
var result = flows.GroupBy(x => x.ClientId)
|
||
.ToDictionary(g => g.Key, g => -g.Sum(x => x.Money));
|
||
|
||
//初始预付金的授信占用净额(占用记正/释放记负,Σ(amount) 即净已缴;trade_id != null 排除人工调整类记录,
|
||
//"追加保证金"前缀为 EOD 追保占用,不计入初始保证金)
|
||
var initCredit = db.client_credit_inout.AsNoTracking()
|
||
.Where(x => clientIds.Contains(x.client_id)
|
||
&& x.trade_id != null
|
||
&& x.happen_date <= valueDate
|
||
&& (x.remark == null || !x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark)))
|
||
.GroupBy(x => x.client_id)
|
||
.Select(g => new { ClientId = g.Key, Sum = g.Sum(x => x.amount) })
|
||
.ToList();
|
||
|
||
foreach (var item in initCredit)
|
||
{
|
||
result[item.ClientId] = (result.TryGetValue(item.ClientId, out var cash) ? cash : 0d) + item.Sum;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 合约维度输入:交易维度追加保证金合计 = Σ(维持保证金 − 累计保证金),按客户汇总。
|
||
/// </summary>
|
||
public static Dictionary<int, double> GetTradeAdditionalMarginByClients(List<int> clientIds, DateTime valueDate, YLContext db)
|
||
{
|
||
var result = new Dictionary<int, double>();
|
||
if (clientIds == null || clientIds.Count == 0)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
//当日维持保证金(引擎产出:我方净收取为正)——先取当日有 span 的客户交易,再按规则15过滤
|
||
var maintenance = db.trade_span.AsNoTracking()
|
||
.Where(x => x.ValueDate == valueDate && x.Spv != null
|
||
&& x.ClientId != null && clientIds.Contains(x.ClientId.Value))
|
||
.Select(x => new { x.TradeId, x.ClientId, Spv = x.Spv ?? 0d })
|
||
.ToList();
|
||
if (maintenance.Count == 0)
|
||
{
|
||
return result;
|
||
}
|
||
var spanTradeIds = maintenance.Select(x => x.TradeId).Distinct().ToList();
|
||
|
||
//规则15(区间追保结构)交易:R1 三层级解析(BUG-02 修正,与引擎/结算判定同口径)——
|
||
//交易绑定→客户默认→全局默认 找到即停;无预付金等其他规则产出/留存的 span 行不计入追保缺口
|
||
var spanTrades = db.trade.AsNoTracking().Where(t => spanTradeIds.Contains(t.id)).ToList();
|
||
var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(spanTrades, valueDate, db);
|
||
var rule15TradeIds = spanTrades
|
||
.Where(t => templatesByTrade.TryGetValue(t.id, out var tpl)
|
||
&& tpl.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
||
.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;
|
||
}
|
||
|
||
//累计保证金:该交易 应付预付金+追加保证金 流水收付净额取反(收取为正);
|
||
//追加保证金的授信占用部分不产生资金流水(阶段二口径),阶段四 §4.1 起一并计入——
|
||
//否则结算后缺口残留(=授信部分),与可用资金公式里的 −已使用授信 形成双扣
|
||
var marginActions = new List<string> { ClientCashInCashOut.系统操作_应付预付金, ClientCashInCashOut.系统操作_追加保证金 };
|
||
var accumulated = db.ClientCashInCashOut.AsNoTracking()
|
||
.Where(x => x.TradeId != null && spanTradeIds.Contains(x.TradeId ?? 0)
|
||
&& marginActions.Contains(x.Action)
|
||
&& x.HappenDate <= valueDate
|
||
&& x.ValidState != ConsGlobal.InValid
|
||
&& (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算)
|
||
&& x.Money != null)
|
||
.GroupBy(x => x.TradeId)
|
||
.Select(g => new { TradeId = g.Key ?? 0, Sum = -g.Sum(x => x.Money ?? 0d) })
|
||
.ToDictionary(x => x.TradeId, x => x.Sum);
|
||
|
||
//追加保证金的授信占用净额(amount 占用记正 → 直接求和(BUG-01 修正口径);remark 前缀标识)
|
||
var addCreditOccupied = db.client_credit_inout.AsNoTracking()
|
||
.Where(x => x.trade_id != null && spanTradeIds.Contains(x.trade_id ?? 0)
|
||
&& x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark))
|
||
.GroupBy(x => x.trade_id)
|
||
.Select(g => new { TradeId = g.Key ?? 0, Sum = g.Sum(x => x.amount) })
|
||
.ToDictionary(x => x.TradeId, x => x.Sum);
|
||
|
||
//初始预付金的授信占用净额(非"追加保证金"前缀:簿记初始占用 + 平仓释放取负,Σ(amount) 即净已缴)——
|
||
//授信垫付的初始预付金不产生应付预付金流水,不计入会把授信初始占用当作未缴缺口多扣可用资金
|
||
//(与 SwapAdditionalMarginService EOD 追保侧同笔修正,2026-08-27 交易2538实证:初始授信200万未扣)
|
||
var initCreditOccupied = db.client_credit_inout.AsNoTracking()
|
||
.Where(x => x.trade_id != null && spanTradeIds.Contains(x.trade_id ?? 0)
|
||
&& (x.remark == null || !x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark)))
|
||
.GroupBy(x => x.trade_id)
|
||
.Select(g => new { TradeId = g.Key ?? 0, Sum = g.Sum(x => x.amount) })
|
||
.ToDictionary(x => x.TradeId, x => x.Sum);
|
||
|
||
foreach (var group in maintenance.GroupBy(x => x.ClientId ?? 0))
|
||
{
|
||
var total = group.Sum(x => x.Spv
|
||
- (accumulated.TryGetValue(x.TradeId, out var acc) ? acc : 0d)
|
||
- (addCreditOccupied.TryGetValue(x.TradeId, out var occupied) ? occupied : 0d)
|
||
- (initCreditOccupied.TryGetValue(x.TradeId, out var initOccupied) ? initOccupied : 0d));
|
||
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;
|
||
}
|
||
}
|
||
}
|