using System; using System.Collections.Generic; namespace YLErp.Helpers { /// /// 前端互换计算公式的后端参考实现(忠实重写 unwindSwapTrade.js / incomeSwapTrade.js)。 /// ============================================================================ /// 用途: /// 1. 特征化测试的金标准(FrontendCalcCharacterizationTest)—— 冻结前端行为 /// 2. 后端只读校验(SwapDealService.ValidateFrontendPnL)—— 重算盈亏与前端传值比对 /// /// 注:前端保持快速反馈(用户改输入立即算),本类不替代前端,仅作后端校验兜底。 /// 命名规范(见《互换价格字段命名规范决策文档》): /// PosiGrossPrice 现状名,实为"期初全价不含费",规范名 EntryDirtyPrice /// TradingAmountAvg 现状名,实为"期末全价不含费",规范名 ExitDirtyPrice /// ============================================================================ /// public static class FrontendCalcReference { /// 模拟 otcformat.trading.StockEqvNotional(金额类,2位小数) private static decimal StockEqvNotional(decimal v) => Math.Round(v, 2, MidpointRounding.AwayFromZero); /// /// 计算平仓页(unwind)的盯市盈亏与汇总。 /// 对应 unwindSwapTrade.js:196-261(calcFloatClosePnl + calcCloseAmount)。 /// public static UnwindResult CalcUnwind(UnwindInput input) { // 规范名映射:initPosiNetPrice(前端变量) = PosiGrossPrice = EntryDirtyPrice(期初全价不含费) decimal entryPrice = input.PosiGrossPrice; // scale = getPriceScale():债券(multiplier=100)→0.01,非债券→1 decimal scale = input.Multiplier == 100 ? 0.01m : 1m; // 方向因子:PayDirection(1=收取)→+1;PositionType(1=多头)→+1 decimal floatRatio = input.PayDirection == 1 ? 1 : -1; decimal longRatio = input.PositionType == 1 ? 1 : -1; decimal tradingFee = ParseOrZero(input.TradingFee); decimal tradingFeePending = ParseOrZero(input.TradingFeePending); decimal dividendIn = ParseOrZero(input.DividendIn); // MarkClosePnl = round(CloseQty × (TradingAmountAvg × scale − EntryPrice) × floatRatio × longRatio × 10000)/10000 decimal markClosePnl = Math.Round( input.CloseQty * (input.TradingAmountAvg * scale - entryPrice) * floatRatio * longRatio * 10000) / 10000; // toFixed(2) → StockEqvNotional markClosePnl = Math.Round(markClosePnl, 2, MidpointRounding.AwayFromZero); markClosePnl = StockEqvNotional(markClosePnl); // FloatPnlSum = (MarkClosePnl + TradingFee + TradingFeePending + DividendIn).toFixed(2) decimal floatPnlSum = decimal.Parse( (markClosePnl + tradingFee + tradingFeePending + dividendIn).ToString("F2")); // calcCloseAmount:SwapRealizedPnL/SwapCloseAmount = FloatPnlSum + Σ利息腿 + Σ预付金腿 decimal swapCloseAmount = floatPnlSum; decimal swapRealizedPnL = floatPnlSum; decimal swapMarginRebatePnl = 0m; foreach (var interest in input.InterestLegs) { swapCloseAmount += interest.InterestClosePnL; swapRealizedPnL += interest.InterestClosePnL; } foreach (var margin in input.MarginLegs) { swapCloseAmount += margin.InterestClosePnL; swapMarginRebatePnl += margin.InterestClosePnL; swapRealizedPnL += margin.InterestClosePnL; } swapRealizedPnL = StockEqvNotional(swapRealizedPnL); swapCloseAmount = StockEqvNotional(swapCloseAmount); swapMarginRebatePnl = StockEqvNotional(swapMarginRebatePnl); // TradingAmountFeeAvg = CloseQty==0 ? 0 : (TradingAmountAvg×scale + TradingFee/CloseQty × ratio) // 注:unwind 的 ratio = PositionType?1:-1(calcCloseAmount 内重新定义) decimal ratio = input.PositionType == 1 ? 1 : -1; decimal tradingAmountFeeAvg = input.CloseQty == 0 ? 0 : input.TradingAmountAvg * scale + (tradingFee / input.CloseQty) * ratio; return new UnwindResult { MarkClosePnl = markClosePnl, FloatPnlSum = floatPnlSum, SwapRealizedPnL = swapRealizedPnL, SwapCloseAmount = swapCloseAmount, SwapMarginRebatePnl = swapMarginRebatePnl, TradingAmountFeeAvg = tradingAmountFeeAvg }; } /// /// 计算结息页(income)的盯市盈亏与汇总。 /// 对应 incomeSwapTrade.js:128-178。 /// 差异:用剩余持仓数量和合约乘数作量纲,无 longRatio,无 Math.round/10000。 /// public static UnwindResult CalcIncome(UnwindInput input) { // income 页 initPosiGrossPrice = PosiGrossPrice = EntryDirtyPrice decimal entryPrice = input.PosiGrossPrice; decimal scale = input.Multiplier == 100 ? 0.01m : 1m; decimal floatRatio = input.PayDirection == 1 ? 1 : -1; decimal longRatio = input.PositionType == 1 ? 1 : -1; decimal tradingFee = ParseOrZero(input.TradingFee); decimal tradingFeePending = ParseOrZero(input.TradingFeePending); decimal dividendIn = ParseOrZero(input.DividendIn); // MarkClosePnl = PositionQty × ContractSize × (TradingAmountAvg × scale − EntryPrice) × floatRatio × longRatio // (无 Math.round/10000) decimal markClosePnl = input.PositionQty * input.ContractSize * (input.TradingAmountAvg * scale - entryPrice) * floatRatio * longRatio; markClosePnl = StockEqvNotional(markClosePnl); decimal floatPnlSum = decimal.Parse( (markClosePnl + tradingFee + tradingFeePending + dividendIn).ToString("F2")); decimal swapCloseAmount = floatPnlSum; decimal swapRealizedPnL = floatPnlSum; decimal swapMarginRebatePnl = 0m; foreach (var interest in input.InterestLegs) { swapCloseAmount += interest.InterestClosePnL; swapRealizedPnL += interest.InterestClosePnL; } foreach (var margin in input.MarginLegs) { swapCloseAmount += margin.InterestClosePnL; swapMarginRebatePnl += margin.InterestClosePnL; swapRealizedPnL += margin.InterestClosePnL; } // income 页无 SwapMarginAmount 计算(恒为0) swapRealizedPnL = StockEqvNotional(swapRealizedPnL); swapCloseAmount = StockEqvNotional(swapCloseAmount); swapMarginRebatePnl = StockEqvNotional(swapMarginRebatePnl); // TradingAmountFeeAvg = CloseQty>0 ? (TradingAmountAvg×scale + TradingFee/CloseQty × floatRatio) : TradingAmountAvg×scale // 注:income 用 floatRatio(PayDirection),与 unwind 的 ratio(PositionType) 不同 decimal tradingAmountFeeAvg = input.CloseQty > 0 ? input.TradingAmountAvg * scale + (tradingFee / input.CloseQty) * floatRatio : input.TradingAmountAvg * scale; return new UnwindResult { MarkClosePnl = markClosePnl, FloatPnlSum = floatPnlSum, SwapRealizedPnL = swapRealizedPnL, SwapCloseAmount = swapCloseAmount, SwapMarginRebatePnl = swapMarginRebatePnl, TradingAmountFeeAvg = tradingAmountFeeAvg }; } private static decimal ParseOrZero(string s) => string.IsNullOrEmpty(s) ? 0m : decimal.Parse(s); } /// 前端计算输入模型(对应前端可见的原始字段) public class UnwindInput { public int Multiplier; // 债券=100,非债券=1 public decimal PosiGrossPrice; // EntryDirtyPrice(期初全价不含费) public decimal TradingAmountAvg; // 用户可改的期末标的价格(界面×multiplier形态) public decimal CloseQty; // 平仓数量 public decimal PositionQty; // 结息时的剩余持仓数量 public decimal ContractSize = 1m; // 合约乘数 public decimal CloseNotionalValue;// 平仓名义本金 public int PayDirection; // 1=收取,-1=支付 public int PositionType; // 1=多头,2=空头 public string TradingFee; // 交易费用(前端是字符串) public string TradingFeePending; // 待结算费用 public string DividendIn; // 分红 public List InterestLegs = new(); public List MarginLegs = new(); } /// 利息腿/预付金腿输入 public class LegInput { public decimal InterestClosePnL; // 利息腿平仓盈亏(已含方向) } /// 前端计算输出模型(前端算出的衍生字段) public class UnwindResult { public decimal MarkClosePnl; public decimal FloatPnlSum; public decimal SwapRealizedPnL; public decimal SwapCloseAmount; public decimal SwapMarginRebatePnl; public decimal TradingAmountFeeAvg; } }