164 lines
9.6 KiB
C#
164 lines
9.6 KiB
C#
using YLErp.BLL;
|
||
using YLErp.DBModels;
|
||
using YLErp.Enums;
|
||
using YLErp.Modules.MarginModule;
|
||
using YLErp.Modules.TradeModule;
|
||
|
||
namespace YLErp.Modules.SwapModule.Margin
|
||
{
|
||
/// <summary>
|
||
/// R3 阶段四 §4.1:合约维度(MarginWatchRule==0)规则15 交易日终结算产生"追加保证金"资金记录。
|
||
/// 交易维度追加保证金 = 维持保证金(阶段三引擎 trade_span 产出)− 累计保证金(应付预付金+追加保证金 流水净额 + 追加授信占用);
|
||
/// 现金部分为逐结算日增量记录(BUG-03 修正:每结算日一条、Money=−increment,键 TradeId+Action+Deal+HappenDate 幂等),
|
||
/// 需求上升只增不减;授信优先(阶段二规则):授信部分只写授信出入表(remark 前缀=追加保证金,position_id 空、冗余 trade_id)。
|
||
/// 由 EOD 在客户资金计算之前调用:当日新记录计入当日出入金窗口并翻"已结算",重跑时 目标/已补足 不变 → 新增为 0 不重复写。
|
||
/// 客户维度(MarginWatchRule=1/NULL)不产生资金记录(§0 占用口径),不在本服务范围。
|
||
/// </summary>
|
||
public class SwapAdditionalMarginService : YLBaseService
|
||
{
|
||
public SwapAdditionalMarginService(OptUserInfo userInfo) : base(userInfo)
|
||
{
|
||
}
|
||
|
||
public SwapAdditionalMarginService(YLBaseService baseService) : base(baseService)
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结算日逐客户逐交易产生追加保证金(clientFilter 为部分结算的客户过滤,与 EOD 请求一致)。
|
||
/// </summary>
|
||
public void SettleAdditionalMargin(DateTime settleDate, List<int> clientFilter = null)
|
||
{
|
||
//合约维度盯市客户
|
||
var watchClientIds = DbContextFactory.GetClientDbContext(OptUser).client.AsNoTracking()
|
||
.Where(t => t.ProcessStatus != "未提交" && t.MarginWatchRule == 0)
|
||
.Select(t => t.id)
|
||
.ToList();
|
||
if (clientFilter != null && clientFilter.Any())
|
||
{
|
||
watchClientIds = watchClientIds.Where(t => clientFilter.Contains(t)).ToList();
|
||
}
|
||
if (watchClientIds.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//存续中的互换交易(状态口径与 eodSwapQuery 一致,含当日已了结)
|
||
var tradeStatuses = ConsTrade.TradeStatusAfterConfirmed;
|
||
var trades = DbContext.trade.AsNoTracking()
|
||
.Where(t => tradeStatuses.Contains(t.TradeStatus)
|
||
&& t.ValidState != ConsGlobal.InValid
|
||
&& t.TradeType == "收益互换"
|
||
&& t.TradeDate <= settleDate
|
||
&& watchClientIds.Contains(t.ClientId))
|
||
.ToList();
|
||
if (trades.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//规则15(区间追保结构)交易:R1 三层级解析(BUG-02 修正,与引擎/确认书同口径)——
|
||
//交易绑定→客户默认→全局默认 找到即停,只配客户/全局默认模板的交易同样纳入追保结算
|
||
var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(trades, settleDate, DbContext);
|
||
trades = trades.Where(t => templatesByTrade.TryGetValue(t.id, out var tpl)
|
||
&& tpl.RuleType == (int)MarginRuleTypeEnum.区间追保结构).ToList();
|
||
var tradeIds = trades.Select(t => t.id).ToList();
|
||
if (tradeIds.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//当日维持保证金(引擎产出,我方净收取为正),按交易合计(与 SwapSpanBalanceQueryService 缺口口径一致)
|
||
var maintenanceByTrade = DbContext.trade_span.AsNoTracking()
|
||
.Where(x => x.ValueDate == settleDate && tradeIds.Contains(x.TradeId) && x.Spv != null)
|
||
.GroupBy(x => x.TradeId)
|
||
.Select(g => new { TradeId = g.Key, Spv = g.Sum(x => x.Spv ?? 0d) })
|
||
.ToDictionary(x => x.TradeId, x => x.Spv);
|
||
|
||
//应付预付金净收额(客户付钱记负 → 取反为正;平仓返还自动冲减;口径与 EOD canonical 一致:非作废+已确认/已结算)
|
||
var payableNetByTrade = DbContext.ClientCashInCashOut.AsNoTracking()
|
||
.Where(x => x.TradeId != null && tradeIds.Contains(x.TradeId ?? 0)
|
||
&& x.Action == ClientCashInCashOut.系统操作_应付预付金
|
||
&& x.HappenDate <= settleDate
|
||
&& 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);
|
||
|
||
//追加保证金资金记录累计值(逐日增量记录求和即累计,BUG-03;Deal=0 交易级,口径与 EOD canonical 一致:非作废+已确认/已结算)
|
||
var addRecordByTrade = DbContext.ClientCashInCashOut.AsNoTracking()
|
||
.Where(x => x.TradeId != null && tradeIds.Contains(x.TradeId ?? 0)
|
||
&& x.Action == ClientCashInCashOut.系统操作_追加保证金
|
||
&& x.ValidState != ConsGlobal.InValid
|
||
&& (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算)
|
||
&& x.Deal == 0
|
||
&& x.Money != null)
|
||
.GroupBy(x => x.TradeId)
|
||
.Select(g => new { TradeId = g.Key ?? 0, Funded = -g.Sum(x => x.Money ?? 0d) })
|
||
.ToDictionary(x => x.TradeId, x => x.Funded);
|
||
|
||
//追加保证金授信占用累计(amount 占用记正 → 直接求和(BUG-01 修正口径);remark 前缀标识,见 ClientCreditInoutService)
|
||
var addCreditByTrade = DbContext.client_credit_inout.AsNoTracking()
|
||
.Where(x => x.trade_id != null && tradeIds.Contains(x.trade_id ?? 0)
|
||
&& x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark))
|
||
.GroupBy(x => x.trade_id)
|
||
.Select(g => new { TradeId = g.Key ?? 0, Funded = g.Sum(x => x.amount) })
|
||
.ToDictionary(x => x.TradeId, x => x.Funded);
|
||
|
||
var fundTagService = new SwapFundTagService(this);
|
||
var cashService = new ClientCashInCashOutService(this);
|
||
var creditService = new ClientCreditInoutService(this);
|
||
//客户剩余可用授信逐笔扣减缓存(同一次结算内多笔追加按顺序消耗额度,与阶段二逐腿分配同语义)
|
||
var creditRemaining = new Dictionary<int, double>();
|
||
|
||
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)
|
||
{
|
||
continue;
|
||
}
|
||
var target = SwapAdditionalMarginCalc.CalcTarget(maintenance,
|
||
payableNetByTrade.TryGetValue(td.id, out var payableNet) ? payableNet : 0);
|
||
if (target <= 0)
|
||
{
|
||
continue;
|
||
}
|
||
var fundedCash = addRecordByTrade.TryGetValue(td.id, out var cash) ? cash : 0;
|
||
var fundedCredit = addCreditByTrade.TryGetValue(td.id, out var credit) ? credit : 0;
|
||
var increment = Math.Round(target - fundedCash - fundedCredit, 2, MidpointRounding.AwayFromZero);
|
||
if (increment <= 0)
|
||
{
|
||
//已补足;追保回落(目标下降)不返还——负缺口在可用资金公式(Σ维持−累计)体现
|
||
continue;
|
||
}
|
||
|
||
if (!creditRemaining.TryGetValue(td.ClientId, out var remain))
|
||
{
|
||
remain = fundTagService.GetAvailableCredit(td.ClientId, settleDate);
|
||
creditRemaining[td.ClientId] = remain;
|
||
}
|
||
var (creditPart, cashPart) = SwapAdditionalMarginCalc.Allocate(increment, remain);
|
||
if (creditPart > 0)
|
||
{
|
||
//授信部分不产生资金流水,只写授信出入表占用(占用记正数,BUG-01 修正口径)
|
||
creditService.Occupy(td.ClientId, null, td.id, creditPart, settleDate,
|
||
ClientCreditInoutService.AdditionalMarginRemark + "占用");
|
||
creditRemaining[td.ClientId] = Math.Round(remain - creditPart, 2, MidpointRounding.AwayFromZero);
|
||
}
|
||
if (cashPart > 0)
|
||
{
|
||
//现金部分按结算日逐笔增量记录(BUG-03 修正:每结算日一条、Money=−increment、键含日期幂等),
|
||
//避免单条累计值覆盖 + HappenDate 前移使 EOD 差分窗口跨日全额重复计入;负数=客户应付追加
|
||
cashService.SaveSwapTradeClientCash(td, -cashPart, settleDate, 0,
|
||
ClientCashInCashOut.系统操作_追加保证金, matchDate: true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|