refactor(swap-eod): 抽出期末头寸纯函数并放宽可见性

SetFixedLegRealizedPnl 替换4处复制粘贴(清理L1296双分号); NormalizeInterestSignForReport 替换16行内联报表分支; CalculateWeightedMarginInterest private->public static。均为纯静态无实例依赖,配套无库单测锁定行为。
This commit is contained in:
hjhan
2026-07-21 15:28:13 +08:00
parent 494a7a06cc
commit a153a1d1a6
4 changed files with 566 additions and 21 deletions
@@ -1023,7 +1023,7 @@ namespace YLErp.Modules.SwapModule
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1157,7 +1157,7 @@ namespace YLErp.Modules.SwapModule
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1293,7 +1293,7 @@ namespace YLErp.Modules.SwapModule
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; ;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1415,7 +1415,7 @@ namespace YLErp.Modules.SwapModule
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;
SetFixedLegRealizedPnl(newEodPayPosition);
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
eodPayPosition.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -2087,6 +2087,28 @@ namespace YLErp.Modules.SwapModule
+ position.RealizedInterestFee;
}
/// <summary>
/// 风险报表符号归一化:把历史两种符号口径的 TdCloseInterest/RealizedInterest
/// 统一按"绝对金额 × 业务方向"重写。普通利息腿收取为正、支付为负;
/// 预付金腿利息方向与保证金本金方向相反。随后重算 RealizedPnl。
/// 抽为 public static 纯函数以支持无库单测(见 SwapReportInterestSignNormalizeTest)。
/// 仅当 InterestDirection > 0 时执行(与原内联逻辑等价)。
/// </summary>
public static void NormalizeInterestSignForReport(eod_swap_position position)
{
if (position.InterestDirection <= 0) return;
var interestRatio = position.InterestDirection == (int)SwapDirectionEnum. ? 1m : -1m;
if (ConsTrade.InterestMarginModels.Contains(position.InterestMode))
{
interestRatio = -interestRatio;
}
position.TdCloseInterest = Math.Abs(position.TdCloseInterest) * interestRatio;
position.RealizedInterest = Math.Abs(position.RealizedInterest) * interestRatio;
// 兼容修复前已落库的利息腿:当时只累计了明细字段,未同步写入 RealizedPnl。
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
}
/// <summary>
/// 获取多空组合 平仓详细
/// </summary>
@@ -2179,22 +2201,9 @@ namespace YLErp.Modules.SwapModule
item.eodPosition.PosiNetFeePrice *= multiplier;
item.eodPosition.PosiNetNoFeePrice *= multiplier;
item.eodPosition.UnderlyingPrice *= multiplier;
if (item.eodPosition.InterestDirection > 0)
{
// 历史数据的 TdCloseInterest、RealizedInterest 存在两种符号口径,
// 风险报表统一按绝对金额和业务方向还原:普通利息腿收取为正、支付为负,
// 预付金腿的利息方向与保证金本金方向相反。
var interestRatio = item.eodPosition.InterestDirection == (int)SwapDirectionEnum. ? 1m : -1m;
if (ConsTrade.InterestMarginModels.Contains(item.eodPosition.InterestMode))
{
interestRatio = -interestRatio;
}
item.eodPosition.TdCloseInterest = Math.Abs(item.eodPosition.TdCloseInterest) * interestRatio;
item.eodPosition.RealizedInterest = Math.Abs(item.eodPosition.RealizedInterest) * interestRatio;
// 兼容修复前已落库的利息腿:当时只累计了明细字段,未同步写入 RealizedPnl。
item.eodPosition.RealizedPnl = item.eodPosition.RealizedInterest
+ item.eodPosition.RealizedInterestFee;
}
// 历史数据的 TdCloseInterest、RealizedInterest 存在两种符号口径,
// 风险报表统一按绝对金额和业务方向还原,并重算 RealizedPnl。
NormalizeInterestSignForReport(item.eodPosition);
}
return retListResult;
@@ -2630,12 +2639,24 @@ namespace YLErp.Modules.SwapModule
/// <summary>
/// 计算预付金利息金额。InterestIncomeSum 已是各腿利息金额,
/// 按收取为正、支付为负直接轧差求和,不做本金加权。
/// 抽为 public static 纯函数以支持无库单测(见 SwapWeightedMarginInterestTest)。
/// </summary>
private static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
{
return margins.Sum(x =>
x.InterestIncomeSum * (x.InterestDirection == (int)SwapDirectionEnum. ? 1 : -1));
}
/// <summary>
/// 固定利息腿的累计已实现盈亏 = 累计已实现利息 + 累计已实现利息费用。
/// 4 处 SaveAutoEodInterestPosition/SaveEodInterestPosition 路径口径一致,
/// 抽为 public static 纯函数以支持无库单测(见 SwapFixedLegRealizedPnlTest),
/// 并消除复制粘贴带来的笔误风险(如 L1296 历史双分号)。
/// </summary>
public static void SetFixedLegRealizedPnl(eod_swap_position position)
{
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
}
/// <summary>
/// 将数据库中以公司/交易簿记方向保存的日终字段转换为客户视角。
/// 该转换必须在拆分浮动收益、费用和期间付息/分红之前完成,