Files
zszq-trs/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs
T

116 lines
6.4 KiB
C#

using YLErp.BLL;
using YLErp.DBModels;
using YLErp.Enums;
using YLErp.Modules.MarginModule;
namespace YLErp.Modules.SwapModule.Margin
{
/// <summary>
/// R2 阶段三 §3.2 估值报告/可用资金查询输入(静态查询,供 ClientBalanceUtility 与 RealTimeClientBanlanceService 共用,保证三处口径一致)。
/// 口径:
/// - 互换初始保证金(净收取为正)= 客户 应付预付金 流水收付净额取反(客户应付入金记负、平仓返还为正,取负号后净收取为正);
/// - 交易维度追加保证金(合约维度)= Σ(维持保证金 − 累计保证金):
/// 维持保证金取当日 trade_span.Spv(区间追保结构引擎产出,我方净收取为正);
/// 累计保证金 = 该交易 应付预付金+追加保证金 流水收付净额 + 追加保证金授信占用净额(§0 口径,阶段四 §4.1 起);
/// 仅统计规则15(区间追保结构,R1 三层级解析,与引擎同口径)且有当日 trade_span 的交易。
/// </summary>
public static class SwapSpanBalanceQueryService
{
/// <summary>
/// 客户维度输入:互换初始保证金(净收取为正,按客户汇总)。
/// </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();
return flows.GroupBy(x => x.ClientId)
.ToDictionary(g => g.Key, g => -g.Sum(x => x.Money));
}
/// <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();
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);
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));
result[group.Key] = total;
}
return result;
}
}
}