refactor(eod): Phase2 提取EodPnlCalculator——11个static方法搬出SwapEodPositionService
SwapEodPositionService的static纯计算方法搬到EodPnlCalculator.cs: - private→internal: RoundMoney/RoundEodInterest/NormalizeEodPositionForStorage/SetFloatingRealizedPnl/FillPositionLegSummary/SumInterestPnL/CalculateWeightedMarginRate - public留转发壳: CalculateSwapRealizedPnl/NormalizeInterestSignForReport/CalculateWeightedMarginInterest/SetFixedLegRealizedPnl SwapEodPositionService内所有调用点加EodPnlCalculator前缀 SwapModule零回归(7基线/510通过)
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using YLErp;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Modules.SwapModule.ReturnLegs;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 互换日终盈亏/精度计算纯函数集合。
|
||||
/// 自 SwapEodPositionService 抽出,支持无库单测;同类内部调用无需前缀。
|
||||
/// </summary>
|
||||
public static class EodPnlCalculator
|
||||
{
|
||||
// 日终利息待实现需跨日累计,按表设计保留 12 位;已实现结算仍按金额两位处理。
|
||||
private const int EodInterestStoragePrecision = 12;
|
||||
|
||||
internal static decimal RoundMoney(decimal value)
|
||||
{
|
||||
return Math.Round(value, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
internal static decimal RoundEodInterest(decimal value)
|
||||
{
|
||||
return Math.Round(value, EodInterestStoragePrecision, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅在写入 eod_swap_position 前统一快照精度。
|
||||
/// 浮动腿收益最终以金额两位展示和存储;利息腿的待实现、计息基数及利率保留 12 位,
|
||||
/// 使部分结算后的尾差可继续参与后续计息。
|
||||
/// </summary>
|
||||
internal static void NormalizeEodPositionForStorage(eod_swap_position position)
|
||||
{
|
||||
if (string.IsNullOrEmpty(position.UnderlyingCode))
|
||||
{
|
||||
// 利息腿没有标的代码:待实现字段保留高精度,已实现结算字段收敛到金额两位。
|
||||
position.InterestPrincipalFix = RoundEodInterest(position.InterestPrincipalFix);
|
||||
position.InterestRateDefault = RoundEodInterest(position.InterestRateDefault);
|
||||
position.InterestFeePending = RoundEodInterest(position.InterestFeePending);
|
||||
position.TdInterestPrincipal = RoundEodInterest(position.TdInterestPrincipal);
|
||||
position.TdInterestRate = RoundEodInterest(position.TdInterestRate);
|
||||
position.TdInterestIncome = RoundEodInterest(position.TdInterestIncome);
|
||||
position.TdInterestFee = RoundEodInterest(position.TdInterestFee);
|
||||
position.InterestIncomeSum = RoundEodInterest(position.InterestIncomeSum);
|
||||
position.InterestFeeSum = RoundEodInterest(position.InterestFeeSum);
|
||||
position.InterestProfitSum = RoundEodInterest(position.InterestProfitSum);
|
||||
position.FloatRate = RoundEodInterest(position.FloatRate);
|
||||
position.SwapPositionValue = RoundEodInterest(position.SwapPositionValue);
|
||||
position.TdCloseInterest = RoundMoney(position.TdCloseInterest);
|
||||
position.TdCloseInterestFee = RoundMoney(position.TdCloseInterestFee);
|
||||
position.RealizedInterest = RoundMoney(position.RealizedInterest);
|
||||
position.RealizedInterestFee = RoundMoney(position.RealizedInterestFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 浮动腿有标的代码:其损益作为金额结果落库,统一按两位四舍五入。
|
||||
position.TdPosiDividend = RoundMoney(position.TdPosiDividend);
|
||||
position.PosiMtmPnL = RoundMoney(position.PosiMtmPnL);
|
||||
position.PosiDividendSum = RoundMoney(position.PosiDividendSum);
|
||||
position.PosiFeePending = RoundMoney(position.PosiFeePending);
|
||||
position.PosiProfitSum = RoundMoney(position.PosiProfitSum);
|
||||
position.TdCloseMtmPnl = RoundMoney(position.TdCloseMtmPnl);
|
||||
position.TdCloseDividend = RoundMoney(position.TdCloseDividend);
|
||||
position.TdCloseFee = RoundMoney(position.TdCloseFee);
|
||||
position.RealizedMtmPnL = RoundMoney(position.RealizedMtmPnL);
|
||||
position.RealizedDividend = RoundMoney(position.RealizedDividend);
|
||||
position.RealizedFee = RoundMoney(position.RealizedFee);
|
||||
position.SwapPositionValue = RoundMoney(position.SwapPositionValue);
|
||||
}
|
||||
position.RealizedPnl = RoundMoney(position.RealizedPnl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动腿累计已实现盈亏由盯市、分红和费用三个已实现组成项汇总。
|
||||
/// 各组成项已经按本方视角落库,此处不再额外转换方向。
|
||||
/// </summary>
|
||||
internal static void SetFloatingRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 汇总单条日终腿的我方已实现收益。
|
||||
/// 浮动腿及普通利息腿维持数据库记录的方向;初始/追加预付金腿的利息
|
||||
/// 则与保证金本金方向相反。这样“收取对手方保证金”产生的利息会作为
|
||||
/// 我方支付给对手方的成本计入,而不会错误增加框架合约已实现收益。
|
||||
/// 抽为静态纯函数以支持无库单测(marginTypes 等价于 ConsTrade.InterestMarginModels)。
|
||||
/// </summary>
|
||||
public static decimal CalculateSwapRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
|
||||
return position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee
|
||||
+ position.RealizedInterest * interestRatio
|
||||
+ position.RealizedInterestFee;
|
||||
}
|
||||
|
||||
/// <summary>填充框架合约的持仓腿汇总字段(多空名义本金/市值/浮动盈亏/dv01/平仓量)。
|
||||
/// SaveEodSwap 与 UpdateEodSwap 共用,消除 ~10 行重复。</summary>
|
||||
internal static void FillPositionLegSummary(eod_swap eod_Swap, List<eod_swap_position> positions)
|
||||
{
|
||||
eod_Swap.NotionalValueLong = Math.Round(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueShort = Math.Round(-Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue)), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
|
||||
eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0);
|
||||
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
|
||||
}
|
||||
|
||||
/// <summary>利息腿 PnL 汇总(按方向比例 + 保证金翻转)。原 SaveEodSwap/UpdateEodSwap 各一段 ForEach。</summary>
|
||||
internal static decimal SumInterestPnL(List<eod_swap_position> interestPositions)
|
||||
{
|
||||
decimal interestPnL = 0;
|
||||
foreach (var x in interestPositions)
|
||||
interestPnL += x.InterestProfitSum * DirectionRatio.InterestLegPnl(x.InterestDirection, x.InterestMode);
|
||||
return interestPnL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 风险报表符号归一化:把历史两种符号口径的 TdCloseInterest/RealizedInterest
|
||||
/// 统一按"绝对金额 × 业务方向"重写。普通利息腿收取为正、支付为负;
|
||||
/// 预付金腿利息方向与保证金本金方向相反。随后重算 RealizedPnl。
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapReportInterestSignNormalizeTest)。
|
||||
/// 仅当 InterestDirection > 0 时执行(与原内联逻辑等价)。
|
||||
/// </summary>
|
||||
public static void NormalizeInterestSignForReport(eod_swap_position position)
|
||||
{
|
||||
if (position.InterestDirection <= 0) return;
|
||||
|
||||
if (position.InterestMode == (int)InterestModeEnum.标的期初全价)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
position.TdCloseInterest = Math.Abs(position.TdCloseInterest) * interestRatio;
|
||||
position.RealizedInterest = Math.Abs(position.RealizedInterest) * interestRatio;
|
||||
// 兼容修复前已落库的利息腿:当时只累计了明细字段,未同步写入 RealizedPnl。
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利率。多条初始/追加预付金腿按本金绝对值加权,
|
||||
/// 不按收付方向轧差,避免相反方向本金抵消后放大利率。
|
||||
/// </summary>
|
||||
internal static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
|
||||
{
|
||||
var marginList = margins.ToList();
|
||||
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
return totalWeight == 0
|
||||
? 0
|
||||
: marginList.Sum(x => x.InterestRateDefault * Math.Abs(x.InterestPrincipalFix)) / totalWeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利息金额。InterestIncomeSum 已是各腿利息金额,
|
||||
/// 按收取为正、支付为负直接轧差求和,不做本金加权。
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapWeightedMarginInterestTest)。
|
||||
/// </summary>
|
||||
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
|
||||
{
|
||||
return margins.Sum(x =>
|
||||
x.InterestIncomeSum * DirectionRatio.ReceivePay(x.InterestDirection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 固定利息腿的累计已实现盈亏 = 累计已实现利息 + 累计已实现利息费用。
|
||||
/// 4 处 SaveAutoEodInterestPosition/SaveEodInterestPosition 路径口径一致,
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapFixedLegRealizedPnlTest),
|
||||
/// 并消除复制粘贴带来的笔误风险(如 L1296 历史双分号)。
|
||||
/// </summary>
|
||||
public static void SetFixedLegRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,72 +48,13 @@ namespace YLErp.Modules.SwapModule
|
||||
: ConsGlobal.SwapDeliveryPriceRound;
|
||||
}
|
||||
|
||||
// 日终利息待实现需跨日累计,按表设计保留 12 位;已实现结算仍按金额两位处理。
|
||||
private const int EodInterestStoragePrecision = 12;
|
||||
|
||||
private static decimal RoundMoney(decimal value)
|
||||
{
|
||||
return Math.Round(value, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private static decimal RoundEodInterest(decimal value)
|
||||
{
|
||||
return Math.Round(value, EodInterestStoragePrecision, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅在写入 eod_swap_position 前统一快照精度。
|
||||
/// 浮动腿收益最终以金额两位展示和存储;利息腿的待实现、计息基数及利率保留 12 位,
|
||||
/// 使部分结算后的尾差可继续参与后续计息。
|
||||
/// </summary>
|
||||
private static void NormalizeEodPositionForStorage(eod_swap_position position)
|
||||
{
|
||||
if (string.IsNullOrEmpty(position.UnderlyingCode))
|
||||
{
|
||||
// 利息腿没有标的代码:待实现字段保留高精度,已实现结算字段收敛到金额两位。
|
||||
position.InterestPrincipalFix = RoundEodInterest(position.InterestPrincipalFix);
|
||||
position.InterestRateDefault = RoundEodInterest(position.InterestRateDefault);
|
||||
position.InterestFeePending = RoundEodInterest(position.InterestFeePending);
|
||||
position.TdInterestPrincipal = RoundEodInterest(position.TdInterestPrincipal);
|
||||
position.TdInterestRate = RoundEodInterest(position.TdInterestRate);
|
||||
position.TdInterestIncome = RoundEodInterest(position.TdInterestIncome);
|
||||
position.TdInterestFee = RoundEodInterest(position.TdInterestFee);
|
||||
position.InterestIncomeSum = RoundEodInterest(position.InterestIncomeSum);
|
||||
position.InterestFeeSum = RoundEodInterest(position.InterestFeeSum);
|
||||
position.InterestProfitSum = RoundEodInterest(position.InterestProfitSum);
|
||||
position.FloatRate = RoundEodInterest(position.FloatRate);
|
||||
position.SwapPositionValue = RoundEodInterest(position.SwapPositionValue);
|
||||
position.TdCloseInterest = RoundMoney(position.TdCloseInterest);
|
||||
position.TdCloseInterestFee = RoundMoney(position.TdCloseInterestFee);
|
||||
position.RealizedInterest = RoundMoney(position.RealizedInterest);
|
||||
position.RealizedInterestFee = RoundMoney(position.RealizedInterestFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 浮动腿有标的代码:其损益作为金额结果落库,统一按两位四舍五入。
|
||||
position.TdPosiDividend = RoundMoney(position.TdPosiDividend);
|
||||
position.PosiMtmPnL = RoundMoney(position.PosiMtmPnL);
|
||||
position.PosiDividendSum = RoundMoney(position.PosiDividendSum);
|
||||
position.PosiFeePending = RoundMoney(position.PosiFeePending);
|
||||
position.PosiProfitSum = RoundMoney(position.PosiProfitSum);
|
||||
position.TdCloseMtmPnl = RoundMoney(position.TdCloseMtmPnl);
|
||||
position.TdCloseDividend = RoundMoney(position.TdCloseDividend);
|
||||
position.TdCloseFee = RoundMoney(position.TdCloseFee);
|
||||
position.RealizedMtmPnL = RoundMoney(position.RealizedMtmPnL);
|
||||
position.RealizedDividend = RoundMoney(position.RealizedDividend);
|
||||
position.RealizedFee = RoundMoney(position.RealizedFee);
|
||||
position.SwapPositionValue = RoundMoney(position.SwapPositionValue);
|
||||
}
|
||||
position.RealizedPnl = RoundMoney(position.RealizedPnl);
|
||||
}
|
||||
|
||||
#region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变
|
||||
|
||||
/// <summary>持久化 eod 持仓记录(生产: DbContext.Add;测试: 收集到列表)</summary>
|
||||
protected virtual void PersistEodSwapPosition(eod_swap_position position)
|
||||
{
|
||||
// 所有新增或更新的日终持仓都经过此入口,避免不同日终分支出现精度差异。
|
||||
NormalizeEodPositionForStorage(position);
|
||||
EodPnlCalculator.NormalizeEodPositionForStorage(position);
|
||||
var storagePriceRound = GetStorageDeliveryPriceRound(position.UnderlyingInstrumentType, position.UnderlyingCode);
|
||||
position.PosiGrossPrice = Math.Round(position.PosiGrossPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
position.UnderlyingPrice = Math.Round(position.UnderlyingPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
@@ -1089,8 +1030,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var interestFeeBeforeSettlement = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee;
|
||||
var isMaturityFinalSettlement = valueDate.Date >= td.ExerciseDate.Value.Date
|
||||
&& flowEvents.Any()
|
||||
&& RoundMoney(interestIncomeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& RoundMoney(interestFeeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterestFee);
|
||||
&& EodPnlCalculator.RoundMoney(interestIncomeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& EodPnlCalculator.RoundMoney(interestFeeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterestFee);
|
||||
|
||||
if (isMaturityFinalSettlement)
|
||||
{
|
||||
@@ -1101,8 +1042,8 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
newEodPayPosition.InterestIncomeSum = RoundEodInterest(interestIncomeBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = RoundEodInterest(interestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
newEodPayPosition.InterestIncomeSum = EodPnlCalculator.RoundEodInterest(interestIncomeBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = EodPnlCalculator.RoundEodInterest(interestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
}
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
//持仓价值
|
||||
@@ -1202,8 +1143,8 @@ namespace YLErp.Modules.SwapModule
|
||||
// 日终快照仍使用上面的高精度应结金额计算待实现尾差,避免把舍入差提前丢掉。
|
||||
interests.ForEach(x =>
|
||||
{
|
||||
x.InterestAmount = RoundMoney(x.InterestAmount);
|
||||
x.InterestClosePnL = RoundMoney(x.InterestClosePnL);
|
||||
x.InterestAmount = EodPnlCalculator.RoundMoney(x.InterestAmount);
|
||||
x.InterestClosePnL = EodPnlCalculator.RoundMoney(x.InterestClosePnL);
|
||||
});
|
||||
decimal settledInterestAmount = interests.Sum(x => x.InterestAmount);
|
||||
|
||||
@@ -1240,10 +1181,10 @@ namespace YLErp.Modules.SwapModule
|
||||
// 到期自动互换是最后一次自动结算:两位实际金额已落流水/资金,待实现不再滚入下一日。
|
||||
newEodPayPosition.InterestIncomeSum = isMaturityFinalAutoSettlement
|
||||
? 0
|
||||
: RoundEodInterest(interestAmountBeforeSettlement - settledInterestAmount);
|
||||
: EodPnlCalculator.RoundEodInterest(interestAmountBeforeSettlement - settledInterestAmount);
|
||||
newEodPayPosition.InterestFeeSum = isMaturityFinalAutoSettlement
|
||||
? 0
|
||||
: RoundEodInterest(eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee);
|
||||
: EodPnlCalculator.RoundEodInterest(eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee);
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
//持仓价值
|
||||
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
|
||||
@@ -1365,7 +1306,7 @@ namespace YLErp.Modules.SwapModule
|
||||
decimal autoSettledInterestAmount = 0m;
|
||||
if (autoSwap && interests.Count > 0)
|
||||
{
|
||||
autoSettledInterestAmount = RoundMoney(interestAmountBeforeSettlement - manualSettledInterestAmount);
|
||||
autoSettledInterestAmount = EodPnlCalculator.RoundMoney(interestAmountBeforeSettlement - manualSettledInterestAmount);
|
||||
var autoInterest = interests[0];
|
||||
autoInterest.InterestAmount = autoSettledInterestAmount;
|
||||
autoInterest.InterestClosePnL = autoSettledInterestAmount
|
||||
@@ -1495,13 +1436,13 @@ namespace YLErp.Modules.SwapModule
|
||||
// InterestIncomeSum 是收盘后仍未结算的尾差/剩余利息。
|
||||
// 部分平仓:扣款前待实现 - TdCloseInterest;最终全平且两位金额已覆盖时直接清零。
|
||||
newEodPayPosition.InterestIncomeSum = closePercent == 1
|
||||
&& RoundMoney(pendingInterestBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& EodPnlCalculator.RoundMoney(pendingInterestBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
? 0m
|
||||
: RoundEodInterest(pendingInterestBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
: EodPnlCalculator.RoundEodInterest(pendingInterestBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = closePercent == 1
|
||||
&& RoundMoney(pendingInterestFeeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterestFee)
|
||||
&& EodPnlCalculator.RoundMoney(pendingInterestFeeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterestFee)
|
||||
? 0m
|
||||
: RoundEodInterest(pendingInterestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
: EodPnlCalculator.RoundEodInterest(pendingInterestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
//持仓内容-利息腿-损益统计(本方视角)
|
||||
// InterestProfitSum 是利息腿待实现总额,包含利息和费用;无费用时等于 InterestIncomeSum。
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
@@ -1736,7 +1677,7 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition.RealizedFee = closeFee;
|
||||
newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl;
|
||||
newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend;
|
||||
SetFloatingRealizedPnl(newEodPayPosition);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(newEodPayPosition);
|
||||
|
||||
newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0;
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
@@ -1810,7 +1751,7 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
|
||||
curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend;
|
||||
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
|
||||
, seekPreday: true, currencyRateType: DirectionRatio.RateType(curretEod.PosiDirection));
|
||||
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
@@ -1825,17 +1766,6 @@ namespace YLErp.Modules.SwapModule
|
||||
return curretEod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动腿累计已实现盈亏由盯市、分红和费用三个已实现组成项汇总。
|
||||
/// 各组成项已经按本方视角落库,此处不再额外转换方向。
|
||||
/// </summary>
|
||||
private static void SetFloatingRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新虚拟交易费用
|
||||
/// </summary>
|
||||
@@ -1911,7 +1841,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
curretEod.PosiDividendSum = 0;
|
||||
}
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
|
||||
|
||||
curretEod.PosiProfitSum = MtmCalc.ReturnLegProfitSum(curretEod.PosiMtmPnL, curretEod.PosiDividendSum, curretEod.PosiFeePending);
|
||||
@@ -2091,7 +2021,7 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl;
|
||||
curretEod.RealizedDividend = curretEod.TdCloseDividend;
|
||||
curretEod.RealizedFee = curretEod.TdCloseFee;
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
|
||||
if (curretEod.PosiStatus == 1)
|
||||
{
|
||||
@@ -2182,8 +2112,8 @@ namespace YLErp.Modules.SwapModule
|
||||
eod_Swap.BookId = td.AssetId;
|
||||
eod_Swap.ValueDate = settleDate;
|
||||
eod_Swap.StructureType = td.StructureType;
|
||||
FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL = SumInterestPnL(interestPositions);
|
||||
EodPnlCalculator.FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL = EodPnlCalculator.SumInterestPnL(interestPositions);
|
||||
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
|
||||
// 保证金腿的利息现金流方向与保证金本金方向相反。
|
||||
// 不能直接汇总 RealizedPnl,否则“收取客户保证金”的腿会把应支付给客户的
|
||||
@@ -2236,8 +2166,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
eod_Swap.NotionalValue = Math.Round(Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL += SumInterestPnL(interestPositions);
|
||||
EodPnlCalculator.FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL += EodPnlCalculator.SumInterestPnL(interestPositions);
|
||||
eodSwapPositions.ForEach(x =>
|
||||
{
|
||||
var ratio = DirectionRatio.InterestLegPnl(x.InterestDirection, x.InterestMode);
|
||||
@@ -2263,38 +2193,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 我方支付给对手方的成本计入,而不会错误增加框架合约已实现收益。
|
||||
/// 抽为静态纯函数以支持无库单测(marginTypes 等价于 ConsTrade.InterestMarginModels)。
|
||||
/// </summary>
|
||||
public static decimal CalculateSwapRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
|
||||
return position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee
|
||||
+ position.RealizedInterest * interestRatio
|
||||
+ position.RealizedInterestFee;
|
||||
}
|
||||
|
||||
/// <summary>填充框架合约的持仓腿汇总字段(多空名义本金/市值/浮动盈亏/dv01/平仓量)。
|
||||
/// SaveEodSwap 与 UpdateEodSwap 共用,消除 ~10 行重复。</summary>
|
||||
private static void FillPositionLegSummary(eod_swap eod_Swap, List<eod_swap_position> positions)
|
||||
{
|
||||
eod_Swap.NotionalValueLong = Math.Round(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueShort = Math.Round(-Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue)), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
|
||||
eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0);
|
||||
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
|
||||
}
|
||||
|
||||
/// <summary>利息腿 PnL 汇总(按方向比例 + 保证金翻转)。原 SaveEodSwap/UpdateEodSwap 各一段 ForEach。</summary>
|
||||
private static decimal SumInterestPnL(List<eod_swap_position> interestPositions)
|
||||
{
|
||||
decimal interestPnL = 0;
|
||||
foreach (var x in interestPositions)
|
||||
interestPnL += x.InterestProfitSum * DirectionRatio.InterestLegPnl(x.InterestDirection, x.InterestMode);
|
||||
return interestPnL;
|
||||
}
|
||||
public static decimal CalculateSwapRealizedPnl(eod_swap_position position) => EodPnlCalculator.CalculateSwapRealizedPnl(position);
|
||||
|
||||
/// <summary>
|
||||
/// 风险报表符号归一化:把历史两种符号口径的 TdCloseInterest/RealizedInterest
|
||||
@@ -2303,20 +2202,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapReportInterestSignNormalizeTest)。
|
||||
/// 仅当 InterestDirection > 0 时执行(与原内联逻辑等价)。
|
||||
/// </summary>
|
||||
public static void NormalizeInterestSignForReport(eod_swap_position position)
|
||||
{
|
||||
if (position.InterestDirection <= 0) return;
|
||||
|
||||
if (position.InterestMode == (int)InterestModeEnum.标的期初全价)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
position.TdCloseInterest = Math.Abs(position.TdCloseInterest) * interestRatio;
|
||||
position.RealizedInterest = Math.Abs(position.RealizedInterest) * interestRatio;
|
||||
// 兼容修复前已落库的利息腿:当时只累计了明细字段,未同步写入 RealizedPnl。
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
public static void NormalizeInterestSignForReport(eod_swap_position position) => EodPnlCalculator.NormalizeInterestSignForReport(position);
|
||||
|
||||
/// <summary>
|
||||
/// 获取多空组合 平仓详细
|
||||
@@ -2806,7 +2692,7 @@ namespace YLErp.Modules.SwapModule
|
||||
item.position.FloatRateUnderlyingCode = floatRateInterest?.FloatRateUnderlyingCode;
|
||||
item.position.FloatRate = floatRateInterest?.FloatRate ?? 0;
|
||||
item.OpenMarginAmount = initialMargins.Sum(s => s.InterestPrincipalFix * DirectionRatio.ReceivePay(s.InterestDirection));
|
||||
item.OpenMarginRate = CalculateWeightedMarginRate(tradeMargins);
|
||||
item.OpenMarginRate = EodPnlCalculator.CalculateWeightedMarginRate(tradeMargins);
|
||||
item.AdditionalMarginAmount = additionalMargins.Sum(s => s.InterestPrincipalFix * DirectionRatio.ReceivePay(s.InterestDirection));
|
||||
item.MarginInterestAmount = CalculateWeightedMarginInterest(eodMargins);
|
||||
item.InterestAmount = eodInterests.Sum(s => s.InterestIncomeSum * (-DirectionRatio.ReceivePay(s.InterestDirection)));
|
||||
@@ -2831,29 +2717,12 @@ namespace YLErp.Modules.SwapModule
|
||||
return retListResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利率。多条初始/追加预付金腿按本金绝对值加权,
|
||||
/// 不按收付方向轧差,避免相反方向本金抵消后放大利率。
|
||||
/// </summary>
|
||||
private static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
|
||||
{
|
||||
var marginList = margins.ToList();
|
||||
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
return totalWeight == 0
|
||||
? 0
|
||||
: marginList.Sum(x => x.InterestRateDefault * Math.Abs(x.InterestPrincipalFix)) / totalWeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利息金额。InterestIncomeSum 已是各腿利息金额,
|
||||
/// 按收取为正、支付为负直接轧差求和,不做本金加权。
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapWeightedMarginInterestTest)。
|
||||
/// </summary>
|
||||
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
|
||||
{
|
||||
return margins.Sum(x =>
|
||||
x.InterestIncomeSum * DirectionRatio.ReceivePay(x.InterestDirection));
|
||||
}
|
||||
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins) => EodPnlCalculator.CalculateWeightedMarginInterest(margins);
|
||||
|
||||
/// <summary>
|
||||
/// 固定利息腿的累计已实现盈亏 = 累计已实现利息 + 累计已实现利息费用。
|
||||
@@ -2861,10 +2730,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapFixedLegRealizedPnlTest),
|
||||
/// 并消除复制粘贴带来的笔误风险(如 L1296 历史双分号)。
|
||||
/// </summary>
|
||||
public static void SetFixedLegRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
public static void SetFixedLegRealizedPnl(eod_swap_position position) => EodPnlCalculator.SetFixedLegRealizedPnl(position);
|
||||
/// <summary>
|
||||
/// 将数据库中以公司/交易簿记方向保存的日终字段转换为客户视角。
|
||||
/// 该转换必须在拆分浮动收益、费用和期间付息/分红之前完成,
|
||||
|
||||
Reference in New Issue
Block a user