using YLErp.DBModels; using YLErp.DBModels.Consts; using YLErp.DBModels.Enums; using YLErp.Models; using YLErp.Modules.SwapModule; using YLErp.Modules.SwapModule.ReturnLegs; namespace YLErp.Modules.TradeModule.DocGenerateModule; /// /// 构造普通收益互换结算单单行数据所需的业务输入。 /// 数据来自平仓事件及其关联交易。 /// public sealed class SwapSettlementBillRowInput { /// 有效交易确认书编号。 public string ConfirmNo { get; set; } /// 结算单所属交易对手名称。 public string ClientName { get; set; } /// 平仓事件关联的收益互换交易。 public trade Trade { get; set; } /// 当前生成行对应的平仓流水。 public swap_flow_event CloseFlow { get; set; } /// 与当前平仓事件同 EventId 的利息、预付金等流水。 public IReadOnlyList EventFlows { get; set; } /// 交易的初始持仓腿,用于识别融资腿、浮动腿和预付金。 public IList Positions { get; set; } /// 标的品种,用于区分现券付息、ETF 分红和收益率字段。 public string UnderlyingInstrumentType { get; set; } /// 本次平仓标的名义本金。 public decimal CloseNotionalValue { get; set; } /// 平仓事件浮动腿记录的期末结算收益率(展示态数值)。 public decimal? ExitYtm { get; set; } /// 期间付息或分红是否计入本次净额结算。 public bool IncludePeriodPaymentInNetting { get; set; } } /// /// 将平仓事件数据按国联普通结算单口径计算并格式化为模板行。 /// public static class SwapSettlementBillRowBuilder { /// /// 计算一条结算记录。金额方向统一转换为客户视角,返回值已按模板精度格式化。 /// public static ExcelReportModel Build(SwapSettlementBillRowInput input) { ArgumentNullException.ThrowIfNull(input); ArgumentNullException.ThrowIfNull(input.Trade); ArgumentNullException.ThrowIfNull(input.CloseFlow); var positions = input.Positions ?? Array.Empty(); var eventFlows = input.EventFlows ?? Array.Empty(); var startDate = input.Trade.StartDate ?? throw new ServiceException("结算单缺少交易起始日"); var eventDate = input.CloseFlow.UnwindDate ?? throw new ServiceException("结算单缺少结算日"); var payDate = input.CloseFlow.PayDate ?? throw new ServiceException("结算单缺少支付日"); var settlementPosition = positions.FirstOrDefault(x => x.id == input.CloseFlow.PositionId); var interestEvents = eventFlows .Where(x => ConsTrade.InterestModels.Contains(x.InterestMode)) .ToList(); var marginEvents = eventFlows .Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode)) .ToList(); var effectiveMargins = positions .Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode) && (!x.HappenDate.HasValue || x.HappenDate.Value <= eventDate)) .ToList(); // 数据库存储的是系统/券商视角金额,结算单统一取反为客户视角;预付金本金按收付方向换算。 var interestAmount = -interestEvents.Sum(x => x.InterestClosePnL); 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 .Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金) .ToList(); var additionalMargin = SumMargin(additionalMarginPositions, InterestModeEnum.追加预付金); var netSettlementAmount = -eventFlows.Sum(x => x.InterestClosePnL) - input.CloseFlow.FloatPnlSum; var maturitySettlementAmount = netSettlementAmount + initialMargin + additionalMargin; var floatRateAbs = input.CloseNotionalValue == 0m ? 0m : interestAmount / input.CloseNotionalValue; var isCashBond = ConsGlobal.InstrumentType.IsBond(input.UnderlyingInstrumentType); var isEtf = ConsGlobal.InstrumentType.Fund.Equals( input.UnderlyingInstrumentType, StringComparison.OrdinalIgnoreCase); return new ExcelReportModel { TradeNumber = input.ConfirmNo, ClientName = input.ClientName, StartDate = startDate.ToString("yyyy-MM-dd"), EventDate = eventDate.ToString("yyyy-MM-dd"), PayDate = payDate.ToString("yyyy-MM-dd"), UnderlyingCode = input.CloseFlow.UnderlyingCode, BaseRate = ResolveBaseRate(positions), InterestRate = interestEvents.Sum(x => x.InterestRate).ToString("0.00%"), PosiNotionalValue = input.CloseNotionalValue.ToString("0.00"), Quantity = input.CloseFlow.Quantity.ToString("0.00"), DividendIn = isCashBond ? periodAmount.ToString("0.00") : string.Empty, PeriodDividend = isEtf ? periodAmount.ToString("0.00") : string.Empty, PosiNetPrice = ((settlementPosition?.PosiGrossPrice ?? 0m) * 100m).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"), ExitYtm = input.ExitYtm.HasValue ? input.ExitYtm.Value.ToString("0.0000") : string.Empty, RateDays = Math.Max(0, (eventDate - startDate).Days).ToString(), FloatRateAbs = floatRateAbs.ToString("0.0000%"), FloatRate = floatRateAbs.ToString("0.0000%"), InterestAmount = interestAmount.ToString("0.00"), PosiPnl = floatingAmount.ToString("0.00"), Fee = fee.ToString("0.00"), MarginRebateRate = EodPnlCalculator.CalculateWeightedMarginRate(effectiveMargins) .ToString("0.0000%"), MarginInterestAmount = marginInterest.ToString("0.00"), InitialMargin = initialMargin.ToString("0.00"), AdditionalMargin = additionalMarginPositions.Count > 0 ? additionalMargin.ToString("0.00") : string.Empty, MarginAmout = Math.Abs(initialMargin).ToString("0.00"), MarkClosePnl = (-input.CloseFlow.FloatPnlSum).ToString("0.00"), NetSettleAmout = netSettlementAmount.ToString("0.00"), MaturitySettlementAmount = maturitySettlementAmount.ToString("0.00") }; } /// /// 校验同一附件中的支付日唯一,并返回用于文件名的支付日。 /// public static DateTime GetSinglePayDate(IEnumerable payDates) { var dates = payDates?.ToList() ?? throw new ServiceException("结算单缺少支付日"); if (dates.Count == 0 || dates.Any(x => !x.HasValue)) { throw new ServiceException("结算单缺少支付日"); } var distinctDates = dates.Select(x => x.Value.Date).Distinct().OrderBy(x => x).ToList(); if (distinctDates.Count != 1) { throw new ServiceException( $"同一结算单存在多个支付日:{string.Join("、", distinctDates.Select(x => x.ToString("yyyy-MM-dd")))}"); } return distinctDates[0]; } /// /// 按交易对手和支付日生成普通结算单文件名;重复生成时追加顺序号。 /// public static string BuildOutputFileName(string clientName, DateTime payDate, int count) { var suffix = count == 0 ? string.Empty : $"-{count + 1}"; return $"结算单-{clientName}-{payDate:yyyy-MM-dd}{suffix}.xlsx"; } /// /// 按预付金类型汇总本金,并根据收付方向转换为结算方向金额。 /// private static decimal SumMargin( IEnumerable margins, InterestModeEnum interestMode) { return margins .Where(x => x.InterestMode == (int)interestMode) .Sum(x => x.InterestPrincipalFix * DirectionRatio.ReceivePay(x.InterestDirection)); } /// /// 从非预付金融资腿读取挂钩基准;未配置浮动基准时按固定利率展示。 /// private static string ResolveBaseRate(IEnumerable positions) { return positions .Where(x => !ConsTrade.InterestMarginModels.Contains(x.InterestMode)) .Select(x => x.FloatRateUnderlyingCode) .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x)) ?? "固定利率"; } }