Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapFrontendPnlValidator.cs
T
hjhan a179675efd refactor(swap-deal): 前端盈亏校验抽为 SwapFrontendPnlValidator
BuildFrontendValidationDiffs/FrontendPnlDiff/AddDiffIfOverThreshold 从2043行 SwapDealService 移至新文件,本文件仅留调用+日志。纯位置迁移,零行为变更。
2026-07-21 15:28:14 +08:00

111 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 单条前后端盈亏差异(纯数据,便于单测断言)。
/// 原内嵌于 SwapDealService,因 SwapDealService 已属超大文件(2000+ 行),
/// 将其与本校验逻辑一并抽离,降低对超大文件的改动面。
/// </summary>
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;
}
/// <summary>
/// 前端盈亏只读校验:用 FrontendCalcReference 公式重算盈亏,与前端传来的 unwindData 逐字段比对。
/// 纯函数(无副作用、无 DB/日志依赖),便于无库单测(见 SwapFrontendPnlValidateTest)。
/// 返回 null 表示前置条件不满足(无浮动腿或 PosiGrossPrice=0),调用方应跳过。
///
/// 从 SwapDealService.ValidateFrontendPnL 抽出,原方法仅保留调用 + 日志。
/// </summary>
public static class SwapFrontendPnlValidator
{
/// <param name="unwindData">前端算好传入的结算数据</param>
/// <param name="isIncome">true=结息页(income公式)false=平仓页(unwind公式)</param>
/// <param name="threshold">差异阈值,默认 0.01</param>
public static List<FrontendPnlDiff>? 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<FrontendPnlDiff>(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<FrontendPnlDiff> 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
});
}
}
}
}