feat(swap): 添加互换合约估值功能和优化数据模型
- 在ClientSwapPositionRequest中新增BookId字段用于资产分类查询 - 新增FloatingUnrealizedPnl、InterestPaymentMethod、MaturityNettingValuation等估值相关字段 - 添加MaturitySettlementDate、DividendAmount、AdditionalMarginAmount等持仓详情字段 - 更新前端表格列模型,调整浮动端和估值实现收益分组显示 - 实现框架合约导出功能,支持完整数据筛选结果导出 - 优化名义本金计算逻辑,修复长短仓数值处理问题 - 完善付息方式和估值口径的业务逻辑处理 - 添加TRS估值、期间分红等关键业务字段计算 - 实现Excel导出时分组表头格式化功能 - 修复框架合约列排序和分组表头显示问题
This commit is contained in:
@@ -1902,8 +1902,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.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValue = eod_Swap.NotionalValueLong + eod_Swap.NotionalValueShort;
|
||||
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
|
||||
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
|
||||
eod_Swap.SwapTradeId = td.id;
|
||||
eod_Swap.SwapTradeNo = td.TradeNumber;
|
||||
eod_Swap.ClientId = td.ClientId;
|
||||
@@ -1972,9 +1972,9 @@ namespace YLErp.Modules.SwapModule
|
||||
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
|
||||
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
eod_Swap.NotionalValue = Convert.ToDecimal(td.StockEqvNotional);
|
||||
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
|
||||
eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
|
||||
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);
|
||||
@@ -2164,13 +2164,23 @@ namespace YLErp.Modules.SwapModule
|
||||
var retListResult = query.ToSearchList(req);
|
||||
var tradeIds = retListResult.rows.Select(x => x.position.SwapTradeId).Distinct().ToList();
|
||||
var valueDates = retListResult.rows.Select(x => x.position.ValueDate).Distinct().ToList();
|
||||
var tradeNotionals = DbContext.trade
|
||||
.Where(x => tradeIds.Contains(x.id))
|
||||
.Select(x => new { x.id, x.OriginalStockEqvNotional, x.StockEqvNotional })
|
||||
.ToDictionary(x => x.id);
|
||||
var eodPositionDetails = DbContext.eod_swap_position
|
||||
.Where(x => tradeIds.Contains(x.SwapTradeId) && valueDates.Contains(x.ValueDate) && !x.Invalid)
|
||||
.ToList();
|
||||
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
||||
var underlyingDataSource = DataCacheProvider.GetUnderlyingDataSource();
|
||||
var varietyDataSource = DataCacheProvider.GetVarietyDataSource();
|
||||
foreach (var item in retListResult.rows)
|
||||
{
|
||||
item.position.NotionalValueShort = -Math.Abs(item.position.NotionalValueShort);
|
||||
if (tradeNotionals.TryGetValue(item.position.SwapTradeId, out var tradeNotional))
|
||||
{
|
||||
item.position.NotionalValue = Convert.ToDecimal(tradeNotional.OriginalStockEqvNotional ?? tradeNotional.StockEqvNotional);
|
||||
}
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(item.ClientId);
|
||||
item.SwapTradeTypeStr = client?.SwapTradeTypeStr;
|
||||
var details = eodPositionDetails
|
||||
@@ -2178,6 +2188,8 @@ namespace YLErp.Modules.SwapModule
|
||||
.ToList();
|
||||
var floatingLegs = details.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();
|
||||
var marginLegs = details.Where(x => marginTypes.Contains(x.InterestMode)).ToList();
|
||||
var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == item.position.SwapTradeId);
|
||||
var dividendPayDate = tradeExtend?.ExtendObj?.DividendPayDate ?? 1;
|
||||
|
||||
item.UnderlyingType = string.Join(",", floatingLegs
|
||||
.Select(x =>
|
||||
@@ -2189,7 +2201,17 @@ namespace YLErp.Modules.SwapModule
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct());
|
||||
item.PeriodAmount = floatingLegs.Sum(x => x.PosiDividendSum); // 分红 - 为实现
|
||||
item.PeriodAmount = floatingLegs.Sum(x => x.RealizedDividend + x.PosiDividendSum);
|
||||
item.FloatingUnrealizedPnl = floatingLegs.Sum(x => x.PosiMtmPnL);
|
||||
item.InterestPaymentMethod = dividendPayDate == 0 ? "到期轧差" : "派息日支付";
|
||||
if (dividendPayDate == 0)
|
||||
{
|
||||
item.MaturityNettingValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL + item.PeriodAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.PeriodPaymentValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL;
|
||||
}
|
||||
item.MarginInterestGain = marginLegs
|
||||
.Where(x => x.InterestDirection == (int)SwapDirectionEnum.收取)
|
||||
.Sum(x => Math.Abs(x.InterestIncomeSum));
|
||||
@@ -2340,6 +2362,10 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
predicate = predicate.And(x => x.ClientId == req.ClientId);
|
||||
}
|
||||
if (req.BookId > 0)
|
||||
{
|
||||
tradePredicate = tradePredicate.And(x => x.AssetId == req.BookId.Value);
|
||||
}
|
||||
if (req.ValueDateFrom != null)
|
||||
{
|
||||
predicate = predicate.And(x => x.ValueDate >= req.ValueDateFrom);
|
||||
@@ -2373,8 +2399,13 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
var retListResult = query.ToSearchList(req);
|
||||
var tradeIds = retListResult.rows.Select(s => s.position.SwapTradeId).ToList();
|
||||
if (!tradeIds.Any())
|
||||
{
|
||||
return retListResult;
|
||||
}
|
||||
interestPredicate = interestPredicate.And(x => tradeIds.Contains(x.SwapTradeId));
|
||||
var valueDates = retListResult.rows.Select(s => s.position.ValueDate).Distinct().ToList();
|
||||
interestPredicate = interestPredicate.And(x => valueDates.Contains(x.ValueDate));
|
||||
var eodPositions = DbContext.eod_swap_position.Where(interestPredicate).ToList();
|
||||
var positions = DbContext.swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && x.InterestMode == (int)InterestModeEnum.初始预付金 && x.IsInitial && !x.Invalid).ToList();
|
||||
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
||||
@@ -2386,6 +2417,14 @@ namespace YLErp.Modules.SwapModule
|
||||
if (tradeExtend != null)
|
||||
{
|
||||
eventDate = QdpCalendarHelper.GetNonHoliday(eventDate.AddDays(tradeExtend.ExtendObj.SettlementRules));
|
||||
if (item.position.PosiMatuirityDate.HasValue)
|
||||
{
|
||||
item.MaturitySettlementDate = QdpCalendarHelper.GetNonHoliday(item.position.PosiMatuirityDate.Value.AddDays(tradeExtend.ExtendObj.SettlementRules));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.MaturitySettlementDate = item.position.PosiMatuirityDate;
|
||||
}
|
||||
item.DayCount = Math.Max(0, (eventDate - item.position.PosiStartDate).Days + 1);
|
||||
//item.position.PosiProfitSum += item.position.VTradingFee-item.position.PosiFeePending;
|
||||
@@ -2393,23 +2432,41 @@ namespace YLErp.Modules.SwapModule
|
||||
//item.position.PosiProfitSum += item.TradingFee;
|
||||
var posiProfitSum = item.position.PosiProfitSum;
|
||||
//item.position.PosiProfitSum 不需要加交易费用
|
||||
item.position.PosiProfitSum = item.position.PosiProfitSum - item.position.PosiFeePending - item.position.PosiDividendSum;
|
||||
item.NetSettmentAmount = item.position.PosiProfitSum + item.position.PosiDividendSum + item.position.PosiFeePending;
|
||||
item.PeriodAmount = item.position.PosiDividendSum;
|
||||
var pendingDividend = item.position.PosiDividendSum;
|
||||
item.position.PosiProfitSum = item.position.PosiProfitSum - item.position.PosiFeePending - pendingDividend;
|
||||
if (ConsGlobal.InstrumentType.IsBond(item.position.UnderlyingInstrumentType))
|
||||
{
|
||||
item.PeriodAmount = pendingDividend;
|
||||
item.DividendAmount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.PeriodAmount = 0;
|
||||
item.DividendAmount = pendingDividend;
|
||||
}
|
||||
item.NetSettmentAmount = item.position.PosiProfitSum + item.PeriodAmount + item.DividendAmount + item.position.PosiFeePending;
|
||||
var margins = positions.Where(x => x.SwapTradeId == item.position.SwapTradeId);
|
||||
var interests = eodPositions.Where(x => x.SwapTradeId == item.position.SwapTradeId && x.ValueDate == item.position.ValueDate);
|
||||
var eodMargins = interests.Where(x => marginTypes.Contains(x.InterestMode));
|
||||
var eodInterests = interests.Where(x => !marginTypes.Contains(x.InterestMode));
|
||||
var initialMargins = eodMargins.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金);
|
||||
var additionalMargins = eodMargins.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金);
|
||||
var floatRateInterest = eodInterests.Where(x => !string.IsNullOrEmpty(x.FloatRateUnderlyingCode)).FirstOrDefault();
|
||||
item.position.FloatRateUnderlyingCode = floatRateInterest?.FloatRateUnderlyingCode;
|
||||
item.position.FloatRate = floatRateInterest?.FloatRate ?? 0;
|
||||
item.OpenMarginAmount = margins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.OpenMarginRate = margins.Sum(s => s.InterestRateDefault * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.OpenMarginAmount = initialMargins.Any()
|
||||
? initialMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1))
|
||||
: margins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.OpenMarginRate = initialMargins.Any()
|
||||
? initialMargins.Sum(s => s.InterestRateDefault * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1))
|
||||
: margins.Sum(s => s.InterestRateDefault * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.AdditionalMarginAmount = additionalMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.MarginInterestAmount = eodMargins.Sum(s => s.InterestIncomeSum * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.InterestAmount = eodInterests.Sum(s => s.InterestIncomeSum * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1));
|
||||
item.InterestRate = eodInterests.Sum(s => s.InterestRateDefault);
|
||||
item.NetSettmentAmount += item.InterestAmount + item.MarginInterestAmount + eodMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
item.NetSettmentAmount += item.InterestAmount + item.MarginInterestAmount;
|
||||
item.NetSettmentAmount = Math.Round(item.NetSettmentAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
item.TrsValue = Math.Round(item.NetSettmentAmount + item.OpenMarginAmount + item.AdditionalMarginAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
if (item.position.PosiNotionalValue != 0 && item.position.PosiNetPrice != 0)
|
||||
{
|
||||
item.FloatRateAbs = item.position.PosiNotionalValue == 0 ? 0 : item.InterestAmount / item.position.PosiNotionalValue;
|
||||
|
||||
Reference in New Issue
Block a user