using System; using System.Collections.Generic; using System.Linq; using MoreLinq.Extensions; using Newtonsoft.Json; using Qdp.Pricing.Library.Base.Utilities; using System.Linq.Expressions; using YLErp.BLL; using YLErp.BLL.Eod; using YLErp.DBModels.Enums; using YLErp.Helpers; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule; using YLErp.Modules.TradeModule; using YLErp.Modules.TradeModule.DealModule; using YLErp.QdpModule; namespace YLErp.Modules.SwapModule { /// /// 单条前后端盈亏差异(纯数据,便于单测断言)。 /// 原内嵌于 SwapDealService,因 SwapDealService 已属超大文件(2000+ 行), /// 将其与本校验逻辑一并抽离,降低对超大文件的改动面。 /// public sealed class FrontendPnlDiff { public string Field { get; init; } = string.Empty; public decimal FrontendValue { get; init; } public decimal BackendValue { get; init; } public decimal Delta => FrontendValue - BackendValue; } /// /// 前端盈亏只读校验:用 FrontendCalcReference 公式重算盈亏,与前端传来的 unwindData 逐字段比对。 /// 纯函数(无副作用、无 DB/日志依赖),便于无库单测(见 SwapFrontendPnlValidateTest)。 /// 返回 null 表示前置条件不满足(无浮动腿或 PosiGrossPrice=0),调用方应跳过。 /// /// 从 SwapDealService.ValidateFrontendPnL 抽出,原方法仅保留调用 + 日志。 /// public static class SwapFrontendPnlValidator { /// 前端算好传入的结算数据 /// true=结息页(income公式),false=平仓页(unwind公式) /// 差异阈值,默认 0.01 public static List? BuildFrontendValidationDiffs( UnwindData unwindData, bool isIncome, decimal threshold = 0.01m) { // 取浮动腿(有 UnderlyingCode 的),与前端 initDeal 取法一致 var floatLeg = unwindData.FlowEvents?.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode)); // PosiGrossPrice 是 [NotMapped],前端可能没传;为空/0 时跳过(避免误报) if (floatLeg == null || floatLeg.PosiGrossPrice == 0) { return null; } // 用 UnderlyingInstrumentType 推 Multiplier(债券=100,否则1) bool isBond = ConsGlobal.InstrumentType.IsBond(floatLeg.UnderlyingInstrumentType); int multiplier = isBond ? 100 : 1; // 分类利息腿/预付金腿(InterestMode 初始预付金/追加预付金→Margin,否则→Interest) var input = new UnwindInput { Multiplier = multiplier, PosiGrossPrice = floatLeg.PosiGrossPrice, // EntryDirtyPrice TradingAmountAvg = floatLeg.TradingAmountAvg, // ExitDirtyPrice(界面×multiplier形态) CloseQty = unwindData.CloseQty, PositionQty = unwindData.PositionQty, ContractSize = floatLeg.ContractSize, CloseNotionalValue = unwindData.CloseNotionalValue, PayDirection = floatLeg.PayDirection, PositionType = floatLeg.PositionType, TradingFee = floatLeg.TradingFee.ToString(), TradingFeePending = floatLeg.TradingFeePending.ToString(), DividendIn = floatLeg.DividendIn.ToString(), }; foreach (var leg in unwindData.FlowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode))) { var target = (leg.InterestMode == (int)InterestModeEnum.初始预付金 || leg.InterestMode == (int)InterestModeEnum.追加预付金) ? input.MarginLegs : input.InterestLegs; target.Add(new LegInput { InterestClosePnL = leg.InterestClosePnL }); } var recalc = isIncome ? FrontendCalcReference.CalcIncome(input) : FrontendCalcReference.CalcUnwind(input); var diffs = new List(3); AddDiffIfOverThreshold(diffs, nameof(recalc.SwapRealizedPnL), unwindData.SwapRealizedPnL, recalc.SwapRealizedPnL, threshold); AddDiffIfOverThreshold(diffs, nameof(recalc.SwapCloseAmount), unwindData.SwapCloseAmount, recalc.SwapCloseAmount, threshold); AddDiffIfOverThreshold(diffs, "MarkClosePnl", floatLeg.MarkClosePnl, recalc.MarkClosePnl, threshold); return diffs; } private static void AddDiffIfOverThreshold( List diffs, string field, decimal frontendVal, decimal backendVal, decimal threshold) { decimal diff = frontendVal - backendVal; if (Math.Abs(diff) > threshold) { diffs.Add(new FrontendPnlDiff { Field = field, FrontendValue = frontendVal, BackendValue = backendVal }); } } } }