feat(swap): 添加互换合约估值功能和优化数据模型
- 在ClientSwapPositionRequest中新增BookId字段用于资产分类查询 - 新增FloatingUnrealizedPnl、InterestPaymentMethod、MaturityNettingValuation等估值相关字段 - 添加MaturitySettlementDate、DividendAmount、AdditionalMarginAmount等持仓详情字段 - 更新前端表格列模型,调整浮动端和估值实现收益分组显示 - 实现框架合约导出功能,支持完整数据筛选结果导出 - 优化名义本金计算逻辑,修复长短仓数值处理问题 - 完善付息方式和估值口径的业务逻辑处理 - 添加TRS估值、期间分红等关键业务字段计算 - 实现Excel导出时分组表头格式化功能 - 修复框架合约列排序和分组表头显示问题
This commit is contained in:
@@ -179,6 +179,26 @@ namespace YLErp.DBModels
|
||||
|
||||
public decimal PeriodAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 合约浮动端待实现收益,不包含期间付息/分红
|
||||
/// </summary>
|
||||
public decimal FloatingUnrealizedPnl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 付息方式
|
||||
/// </summary>
|
||||
public string InterestPaymentMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 合约估值(到期轧差口径)
|
||||
/// </summary>
|
||||
public decimal? MaturityNettingValuation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 合约估值(期间支付派息口径)
|
||||
/// </summary>
|
||||
public decimal? PeriodPaymentValuation { get; set; }
|
||||
|
||||
public decimal MarginInterestGain { get; set; }
|
||||
|
||||
public decimal MarginInterestLoss { get; set; }
|
||||
|
||||
@@ -36,6 +36,14 @@ namespace YLErp.DBModels
|
||||
/// 期间付息
|
||||
/// </summary>
|
||||
public decimal PeriodAmount { get; set; }
|
||||
/// <summary>
|
||||
/// 到期结算日
|
||||
/// </summary>
|
||||
public DateTime? MaturitySettlementDate { get; set; }
|
||||
/// <summary>
|
||||
/// 期间分红
|
||||
/// </summary>
|
||||
public decimal DividendAmount { get; set; }
|
||||
|
||||
public decimal? InitYtm { get; set; }
|
||||
/// <summary>
|
||||
@@ -55,6 +63,10 @@ namespace YLErp.DBModels
|
||||
/// </summary>
|
||||
public decimal MarginInterestAmount { get; set; }
|
||||
/// <summary>
|
||||
/// 追加预付金 取轧差
|
||||
/// </summary>
|
||||
public decimal AdditionalMarginAmount { get; set; }
|
||||
/// <summary>
|
||||
/// 浮动利率(绝对)利率端待实现收益/(标的名义金额/期初标的交割价格全价)
|
||||
/// </summary>
|
||||
public decimal FloatRateAbs { get; set; }
|
||||
@@ -71,6 +83,10 @@ namespace YLErp.DBModels
|
||||
/// </summary>
|
||||
public decimal NetSettmentAmount { get; set; }
|
||||
/// <summary>
|
||||
/// TRS估值
|
||||
/// </summary>
|
||||
public decimal TrsValue { get; set; }
|
||||
/// <summary>
|
||||
/// 交易费用
|
||||
/// </summary>
|
||||
public decimal TradingFee { get; set; }
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace YLErp.Model
|
||||
public DateTime? ValueDate { get; set; }
|
||||
public DateTime? ValueDateFrom { get; set; }
|
||||
public int? ClientId { get; set; }
|
||||
public int? BookId { get; set; }
|
||||
|
||||
public string StructureType { get; set; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
}
|
||||
@section JS{
|
||||
<script src="~/Scripts/app/tradeHelper.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Statics/libs/export/excellentexport.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script type="text/javascript">
|
||||
var page = @Json.Serialize(pageObj);
|
||||
if ("@ViewBag.ParentFlag" == "True")
|
||||
|
||||
@@ -456,8 +456,6 @@ function colModelGridEodPosition() {
|
||||
return colModelGrid;
|
||||
}
|
||||
//框架合约table
|
||||
//TODO(估值模块V1-缺失字段): 以下需求字段本轮未实现,待后端确认数据来源后补充:
|
||||
// - 付息方式、合约估值(到期轧差口径/期间支付派息口径)(估值与实现收益分组)
|
||||
function colModelGridEodSwap() {
|
||||
//按需求《估值模块V1》2.2 分组定义排列列顺序,确保组内列连续(setGroupHeaders 要求)
|
||||
var colModelGrid = [{
|
||||
@@ -573,9 +571,9 @@ function colModelGridEodSwap() {
|
||||
},
|
||||
//=== 浮动端 ===
|
||||
{
|
||||
name: 'position.FloatingPnL',
|
||||
name: 'FloatingUnrealizedPnl',
|
||||
label: '合约浮动端待实现收益',
|
||||
index: 'position.FloatingPnL',
|
||||
index: 'FloatingUnrealizedPnl',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
@@ -586,6 +584,9 @@ function colModelGridEodSwap() {
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
cellattr: function () {
|
||||
return ' title="合约期间内的期间付息金额(无关乎派息支付日)"';
|
||||
},
|
||||
},
|
||||
//=== 利息端 ===
|
||||
{
|
||||
@@ -642,27 +643,6 @@ function colModelGridEodSwap() {
|
||||
},
|
||||
//=== 估值与实现收益 ===
|
||||
{
|
||||
name: 'position.PostionValue',
|
||||
label: '合约持仓价值',
|
||||
index: 'position.PostionValue',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat
|
||||
}, {
|
||||
name: 'position.TdRealizedPnL',
|
||||
label: '合约当日实现收益',
|
||||
index: 'position.TdRealizedPnL',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat
|
||||
}, {
|
||||
name: 'position.RealizedPnL',
|
||||
label: '合约已实现收益',
|
||||
index: 'position.RealizedPnL',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
}, {
|
||||
name: 'position.dv01',
|
||||
label: 'DV',
|
||||
index: 'position.dv01',
|
||||
@@ -674,6 +654,33 @@ function colModelGridEodSwap() {
|
||||
if (abs < 0.0001) return "0";
|
||||
return cellvalue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 4 });
|
||||
},
|
||||
}, {
|
||||
name: 'InterestPaymentMethod',
|
||||
label: '付息方式',
|
||||
index: 'InterestPaymentMethod',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
}, {
|
||||
name: 'MaturityNettingValuation',
|
||||
label: '合约估值(到期轧差口径)',
|
||||
index: 'MaturityNettingValuation',
|
||||
width: 190,
|
||||
align: 'center',
|
||||
formatter: NullableStockEqvNotionalFormat,
|
||||
}, {
|
||||
name: 'PeriodPaymentValuation',
|
||||
label: '合约估值(期间支付派息口径)',
|
||||
index: 'PeriodPaymentValuation',
|
||||
width: 210,
|
||||
align: 'center',
|
||||
formatter: NullableStockEqvNotionalFormat,
|
||||
}, {
|
||||
name: 'position.RealizedPnL',
|
||||
label: '合约已实现收益',
|
||||
index: 'position.RealizedPnL',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
}
|
||||
|
||||
];
|
||||
@@ -686,10 +693,10 @@ var eodSwapGroupConfig = [
|
||||
{ title: '基本信息', columns: ['position.ValueDate', 'AssetBookName', 'ClientName', 'SwapTradeNo', 'StructureType', 'SwapTradeTypeStr', 'UnderlyingType'] },
|
||||
{ title: '名义本金', columns: ['position.NotionalValue', 'position.NotionalValueLong', 'position.NotionalValueShort'] },
|
||||
{ title: '标的市值', columns: ['position.MarketValueLong', 'position.MarketValueShort'] },
|
||||
{ title: '浮动端', columns: ['position.FloatingPnL', 'PeriodAmount'] },
|
||||
{ title: '浮动端', columns: ['FloatingUnrealizedPnl', 'PeriodAmount'] },
|
||||
{ title: '利息端', columns: ['position.InterestPnL'] },
|
||||
{ title: '保证金', columns: ['position.InitMarginGain', 'position.PostionMarginGain', 'position.InitMarginLoss', 'position.PostionMarginLoss', 'MarginInterestGain', 'MarginInterestLoss'] },
|
||||
{ title: '估值与实现收益', columns: ['position.PostionValue', 'position.TdRealizedPnL', 'position.RealizedPnL', 'position.dv01'] }
|
||||
{ title: '估值与实现收益', columns: ['position.dv01', 'InterestPaymentMethod', 'MaturityNettingValuation', 'PeriodPaymentValuation', 'position.RealizedPnL'] }
|
||||
];
|
||||
|
||||
|
||||
@@ -703,7 +710,11 @@ function gridComplete() {
|
||||
if (page.tabIndex == 2) {
|
||||
var defer = main.setcolumnChooser(jgrid, page.configcolumn_data);
|
||||
$.when(defer).done(function () {
|
||||
normalizeEodSwapColumnsForGroupHeaders(jgrid, eodSwapGroupConfig);
|
||||
main.initCollapsibleGroupHeaders(jgrid, eodSwapGroupConfig);
|
||||
jgrid.jqGrid('setLabel', 'PeriodAmount', null, null, {
|
||||
title: '合约期间内的期间付息金额(无关乎派息支付日)'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
main.setcolumnChooser(jgrid, page.configcolumn_data);
|
||||
@@ -731,7 +742,68 @@ function exportVisibleColumns() {
|
||||
var tabName = page.tabIndex == 2 ? '框架合约' : '日终持仓';
|
||||
var fileName = '日终持仓风险_互换_' + tabName + (dateStr ? '_' + dateStr : '');
|
||||
var groupConfig = page.tabIndex == 2 ? eodSwapGroupConfig : null;
|
||||
main.exportVisibleColumnsToExcel(jgrid, fileName, groupConfig);
|
||||
if (page.tabIndex != 2) {
|
||||
main.exportVisibleColumnsToExcel(jgrid, fileName, groupConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
var exportPostData = $.extend({}, GetPostData(), {
|
||||
page: 1,
|
||||
rows: 0,
|
||||
sidx: jgrid.jqGrid('getGridParam', 'sortname'),
|
||||
sord: jgrid.jqGrid('getGridParam', 'sortorder')
|
||||
});
|
||||
$.ajax({
|
||||
url: queryurl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
traditional: true,
|
||||
data: exportPostData
|
||||
}).done(function (result) {
|
||||
main.exportVisibleColumnsToExcel(jgrid, fileName, groupConfig, result && result.rows ? result.rows : []);
|
||||
}).fail(function () {
|
||||
main.message && main.message('导出失败,无法获取筛选后的全部数据');
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeEodSwapColumnsForGroupHeaders(jgrid, groupConfig) {
|
||||
var colModel = jgrid.jqGrid('getGridParam', 'colModel') || [];
|
||||
var currentNames = colModel.map(function (c) { return c.name; });
|
||||
var groupedNames = [];
|
||||
var groupedNameMap = {};
|
||||
groupConfig.forEach(function (group) {
|
||||
(group.columns || []).forEach(function (name) {
|
||||
if (!groupedNameMap[name] && currentNames.indexOf(name) >= 0) {
|
||||
groupedNameMap[name] = true;
|
||||
groupedNames.push(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var firstGroupedIndex = currentNames.findIndex(function (name) { return groupedNameMap[name]; });
|
||||
if (firstGroupedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var prefixNames = currentNames.slice(0, firstGroupedIndex).filter(function (name) {
|
||||
return !groupedNameMap[name];
|
||||
});
|
||||
var suffixNames = currentNames.slice(firstGroupedIndex).filter(function (name) {
|
||||
return !groupedNameMap[name];
|
||||
});
|
||||
var desiredNames = prefixNames.concat(groupedNames).concat(suffixNames);
|
||||
if (desiredNames.join('|') === currentNames.join('|')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var perm = desiredNames.map(function (name) {
|
||||
return currentNames.indexOf(name);
|
||||
}).filter(function (idx) {
|
||||
return idx >= 0;
|
||||
});
|
||||
if (perm.length === currentNames.length) {
|
||||
jgrid.jqGrid("remapColumns", perm, true);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------Formatter---------------------------------
|
||||
@@ -746,6 +818,12 @@ function RealizedPnlFormat(cellValue, options, rowObject) {
|
||||
function StockEqvNotionalFormat(cellValue, options, rowObject) {
|
||||
return otcformat.trading.StockEqvNotional(cellValue);
|
||||
}
|
||||
function NullableStockEqvNotionalFormat(cellValue, options, rowObject) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
||||
return '';
|
||||
}
|
||||
return StockEqvNotionalFormat(cellValue, options, rowObject);
|
||||
}
|
||||
function PosiStatusFormat(cellValue, options, rowObject) {
|
||||
return cellValue == 1 ? "已平" : "正常";
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ $(function () {
|
||||
PostData.ValueDateFrom = $("#ValueDateFrom").val();
|
||||
PostData.ValueDate = $("#ValueDate").val();
|
||||
PostData.ClientId = $("#ClientId").val();
|
||||
PostData.BookId = $("#BookId").val();
|
||||
|
||||
var grid = jQuery('#listGrid').jqGrid({
|
||||
url: '/swaptrade2/clientEodSwapPositionQuery',
|
||||
@@ -189,6 +190,15 @@ var colModelGrid = [
|
||||
align: 'center',
|
||||
sortable: false,
|
||||
formatter:'date',
|
||||
}, {
|
||||
name: 'MaturitySettlementDate',
|
||||
label: '到期结算日',
|
||||
index: 'MaturitySettlementDate',
|
||||
sortIndex: i++,
|
||||
width: 100,
|
||||
align: 'center',
|
||||
sortable: false,
|
||||
formatter: 'date',
|
||||
}, {
|
||||
name: 'position.ValueDate',
|
||||
label: '估值日',
|
||||
@@ -212,7 +222,7 @@ var colModelGrid = [
|
||||
width: 120,
|
||||
align: 'center',
|
||||
sortable: false,
|
||||
formatter: RateFormat
|
||||
formatter: SpreadRateFormat
|
||||
}, {
|
||||
name: 'position.FloatRateUnderlyingCode',
|
||||
label: '基准利率',
|
||||
@@ -244,12 +254,21 @@ var colModelGrid = [
|
||||
align: 'center',
|
||||
sortable: false,
|
||||
formatter: StockEqvNotionalFormat,
|
||||
}, {
|
||||
name: 'DividendAmount',
|
||||
label: '期间分红',
|
||||
index: 'DividendAmount',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: AmountFormat,
|
||||
sortable: false
|
||||
}, {
|
||||
name: 'PeriodAmount',
|
||||
label: '期间付息',
|
||||
index: 'PeriodAmount',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: AmountFormat,
|
||||
sortable: false
|
||||
}, {
|
||||
name: 'position.PosiGrossPrice',
|
||||
@@ -306,7 +325,7 @@ var colModelGrid = [
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'position.PosiFeePending',
|
||||
label: '开仓交易费用',
|
||||
label: '开平仓交易费用',
|
||||
index: 'position.PosiFeePending',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
@@ -320,6 +339,38 @@ var colModelGrid = [
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'OpenMarginRate',
|
||||
label: '预付金利率',
|
||||
index: 'OpenMarginRate',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: RateFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'MarginInterestAmount',
|
||||
label: '预付金利息',
|
||||
index: 'MarginInterestAmount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: AmountFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'OpenMarginAmount',
|
||||
label: '期初预付金',
|
||||
index: 'OpenMarginAmount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: AmountFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'AdditionalMarginAmount',
|
||||
label: '追加预付金',
|
||||
index: 'AdditionalMarginAmount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: AmountFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'NetSettmentAmount',
|
||||
label: '净额结算金额',
|
||||
@@ -328,20 +379,26 @@ var colModelGrid = [
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
sortable: false,
|
||||
}, {
|
||||
name: 'TrsValue',
|
||||
label: 'TRS估值',
|
||||
index: 'TrsValue',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: StockEqvNotionalFormat,
|
||||
sortable: false,
|
||||
}
|
||||
//TODO(估值模块V1-缺失字段): 以下需求字段本轮未实现,待后端确认数据来源后补充:
|
||||
// - 期间分红(ETF适用,利率与规模分组)
|
||||
// - 预付金利率、预付金利息、期初预付金、追加预付金(预付金分组)
|
||||
// - TRS估值(汇总分组)
|
||||
];
|
||||
|
||||
//互换估值分组配置(对应需求《估值模块V1》3.2.2 字段定义)
|
||||
//columns 使用 colModel.name;现有列顺序已天然满足分组连续性,无需重排
|
||||
var eodSwapValuationGroupConfig = [
|
||||
{ title: '基本信息', columns: ['TradeNumber', 'ConfrimNo', 'ClientName', 'position.PosiStartDate', 'position.ValueDate', 'position.UnderlyingCode'] },
|
||||
{ title: '利率与规模', columns: ['InterestRate', 'position.FloatRateUnderlyingCode', 'position.FloatRate', 'position.PosiNotionalValue', 'position.PosiQuantity', 'PeriodAmount'] },
|
||||
{ title: '基本信息', columns: ['TradeNumber', 'ConfrimNo', 'ClientName', 'position.PosiStartDate', 'MaturitySettlementDate', 'position.ValueDate', 'position.UnderlyingCode'] },
|
||||
{ title: '利率与规模', columns: ['InterestRate', 'position.FloatRateUnderlyingCode', 'position.FloatRate', 'position.PosiNotionalValue', 'position.PosiQuantity', 'DividendAmount', 'PeriodAmount'] },
|
||||
{ title: '价格', columns: ['position.PosiGrossPrice', 'InitYtm', 'position.UnderlyingPrice', 'DayCount'] },
|
||||
{ title: '收益', columns: ['FloatRateAbs', 'InterestAmount', 'position.PosiFeePending', 'position.PosiProfitSum', 'NetSettmentAmount'] }
|
||||
{ title: '收益', columns: ['FloatRateAbs', 'InterestAmount', 'position.PosiFeePending', 'position.PosiProfitSum'] },
|
||||
{ title: '预付金', columns: ['OpenMarginRate', 'MarginInterestAmount', 'OpenMarginAmount', 'AdditionalMarginAmount'] },
|
||||
{ title: '汇总', columns: ['NetSettmentAmount', 'TrsValue'] }
|
||||
];
|
||||
|
||||
function formatter6(cellvalue, options, rowObject) {
|
||||
@@ -375,6 +432,7 @@ function gridComplete() {
|
||||
//在列设置应用完成后初始化可折叠分组表头(setcolumnChooser 会 remapColumns,须在其 done 回调后建表头)
|
||||
var defer = main.setcolumnChooser(jgrid, page.configcolumn);
|
||||
$.when(defer).done(function () {
|
||||
normalizeColumnsForGroupHeaders(jgrid, eodSwapValuationGroupConfig);
|
||||
main.initCollapsibleGroupHeaders(jgrid, eodSwapValuationGroupConfig);
|
||||
});
|
||||
$(".selftooltip").tooltip({ html: true, show: 50000, trigger: "hover" });
|
||||
@@ -387,8 +445,143 @@ function exportSwapValuationVisibleColumns() {
|
||||
var jgrid = jQuery('#listGrid');
|
||||
var clientName = $("#ClientId :selected").text() || '';
|
||||
var dateStr = $("#ValueDate").val() || '';
|
||||
var fileName = (clientName ? clientName + '_' : '') + '每日估值报告_互换估值' + (dateStr ? '_' + dateStr : '');
|
||||
main.exportVisibleColumnsToExcel(jgrid, fileName);
|
||||
var fileName = (clientName ? clientName + '_' : '') + '每日估值报告' + (dateStr ? '_' + dateStr : '');
|
||||
exportGridVisibleColumnsToXlsx(jgrid, fileName, eodSwapValuationGroupConfig);
|
||||
}
|
||||
|
||||
function normalizeColumnsForGroupHeaders(jgrid, groupConfig) {
|
||||
var colModel = jgrid.jqGrid('getGridParam', 'colModel') || [];
|
||||
var currentNames = colModel.map(function (c) { return c.name; });
|
||||
var groupedNames = [];
|
||||
var groupedNameMap = {};
|
||||
groupConfig.forEach(function (group) {
|
||||
(group.columns || []).forEach(function (name) {
|
||||
if (!groupedNameMap[name] && currentNames.indexOf(name) >= 0) {
|
||||
groupedNameMap[name] = true;
|
||||
groupedNames.push(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var firstGroupedIndex = currentNames.findIndex(function (name) { return groupedNameMap[name]; });
|
||||
if (firstGroupedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var prefixNames = currentNames.slice(0, firstGroupedIndex).filter(function (name) {
|
||||
return !groupedNameMap[name];
|
||||
});
|
||||
var suffixNames = currentNames.slice(firstGroupedIndex).filter(function (name) {
|
||||
return !groupedNameMap[name];
|
||||
});
|
||||
var desiredNames = prefixNames.concat(groupedNames).concat(suffixNames);
|
||||
if (desiredNames.join('|') === currentNames.join('|')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var perm = desiredNames.map(function (name) {
|
||||
return currentNames.indexOf(name);
|
||||
}).filter(function (idx) {
|
||||
return idx >= 0;
|
||||
});
|
||||
if (perm.length === currentNames.length) {
|
||||
jgrid.jqGrid("remapColumns", perm, true);
|
||||
}
|
||||
}
|
||||
|
||||
function exportGridVisibleColumnsToXlsx(jgrid, fileName, groupConfig) {
|
||||
if (typeof ExcellentExport === 'undefined') {
|
||||
main.message && main.message("Excel导出组件未加载");
|
||||
return;
|
||||
}
|
||||
|
||||
var colModel = jgrid.jqGrid('getGridParam', 'colModel') || [];
|
||||
var visibleCols = colModel.filter(function (c) {
|
||||
return c.hidden !== true && c.name !== 'cb' && c.name !== 'rn';
|
||||
});
|
||||
if (!visibleCols.length) {
|
||||
main.message && main.message("没有可导出的列");
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = jgrid.jqGrid('getRowData') || [];
|
||||
var tableId = 'swapValuationExportTable_' + new Date().getTime();
|
||||
var linkId = 'swapValuationExportLink_' + new Date().getTime();
|
||||
var $wrap = $('<div style="display:none;"></div>').appendTo(document.body);
|
||||
var $table = $('<table id="' + tableId + '"></table>').appendTo($wrap);
|
||||
|
||||
appendGroupHeaderRow($table, visibleCols, groupConfig);
|
||||
appendColumnHeaderRow($table, visibleCols);
|
||||
appendDataRows($table, visibleCols, rows);
|
||||
|
||||
var anchor = document.createElement('a');
|
||||
anchor.id = linkId;
|
||||
anchor.style.display = 'none';
|
||||
anchor.onclick = function () {
|
||||
return ExcellentExport.convert({
|
||||
anchor: this,
|
||||
filename: sanitizeFileName(fileName || '每日估值报告'),
|
||||
format: 'xlsx'
|
||||
}, [{ name: '互换估值', from: { table: tableId } }]);
|
||||
};
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(anchor);
|
||||
$wrap.remove();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function appendGroupHeaderRow($table, visibleCols, groupConfig) {
|
||||
if (!groupConfig || !groupConfig.length) {
|
||||
return;
|
||||
}
|
||||
var titleByColumn = {};
|
||||
groupConfig.forEach(function (group) {
|
||||
(group.columns || []).forEach(function (name) {
|
||||
titleByColumn[name] = group.title || '';
|
||||
});
|
||||
});
|
||||
|
||||
var $tr = $('<tr></tr>').appendTo($table);
|
||||
for (var i = 0; i < visibleCols.length; i++) {
|
||||
var title = titleByColumn[visibleCols[i].name] || '';
|
||||
var colspan = 1;
|
||||
while (i + colspan < visibleCols.length && (titleByColumn[visibleCols[i + colspan].name] || '') === title) {
|
||||
colspan++;
|
||||
}
|
||||
$('<th></th>')
|
||||
.attr('colspan', colspan)
|
||||
.css({ 'background-color': '#d9edf7', 'font-weight': 'bold', 'text-align': 'center' })
|
||||
.text(title)
|
||||
.appendTo($tr);
|
||||
i += colspan - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function appendColumnHeaderRow($table, visibleCols) {
|
||||
var $tr = $('<tr></tr>').appendTo($table);
|
||||
visibleCols.forEach(function (col) {
|
||||
$('<th></th>')
|
||||
.css({ 'background-color': '#f0f0f0', 'font-weight': 'bold', 'text-align': 'center' })
|
||||
.text(col.label || col.name)
|
||||
.appendTo($tr);
|
||||
});
|
||||
}
|
||||
|
||||
function appendDataRows($table, visibleCols, rows) {
|
||||
rows.forEach(function (row) {
|
||||
var $tr = $('<tr></tr>').appendTo($table);
|
||||
visibleCols.forEach(function (col) {
|
||||
var val = row[col.name];
|
||||
$('<td></td>').text(val == null ? '' : val).appendTo($tr);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeFileName(fileName) {
|
||||
return String(fileName || '').replace(/[\\/:*?"<>|]/g, '_');
|
||||
}
|
||||
|
||||
|
||||
@@ -483,6 +676,9 @@ function PriceFormat(cellValue, options, rowObject) {
|
||||
function StockEqvNotionalFormat(cellValue, options, rowObject) {
|
||||
return otcformat.trading.StockEqvNotional(cellValue);
|
||||
}
|
||||
function AmountFormat(cellValue, options, rowObject) {
|
||||
return main.formatNumber(cellValue, 2, { trimTailZeros: true });
|
||||
}
|
||||
function RateFormat(cellValue, options, rowObject) {
|
||||
if (cellValue) {
|
||||
var num = new Number(cellValue) * 100;
|
||||
@@ -491,6 +687,14 @@ function RateFormat(cellValue, options, rowObject) {
|
||||
return "0.0000%";
|
||||
}
|
||||
}
|
||||
function SpreadRateFormat(cellValue, options, rowObject) {
|
||||
if (cellValue) {
|
||||
var num = new Number(cellValue) * 100;
|
||||
return num.toFixed(2) + "%";
|
||||
} else {
|
||||
return "0.00%";
|
||||
}
|
||||
}
|
||||
function locationChange(tab) {
|
||||
if ($("#ValueDate").val() > page.valueDate) {
|
||||
main.message("结束日期不能大于当前系统日期!");
|
||||
|
||||
@@ -1210,14 +1210,30 @@ main.refreshCollapsibleGroupHeaders = function (jgrid) {
|
||||
* @param {jQuery} jgrid jqGrid 容器
|
||||
* @param {string} fileName 导出文件名(不含扩展名)
|
||||
* @param {Array} groupConfig 可选,分组表头配置,每项 { title: string, columns: string[] }
|
||||
* @param {Array} exportRows 可选,后端返回的全部筛选结果;未传时导出当前页
|
||||
*/
|
||||
main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig) {
|
||||
main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig, exportRows) {
|
||||
var colModel = jgrid.jqGrid('getGridParam', 'colModel');
|
||||
// 只导出可见列(hidden !== true),与折叠状态联动:收起的列自动不可见
|
||||
var visibleCols = colModel.filter(function (c) { return c.hidden !== true && c.name !== 'cb' && c.name !== 'rn'; });
|
||||
if (!visibleCols.length) { main.message && main.message("没有可导出的列"); return; }
|
||||
|
||||
var rows = jgrid.jqGrid('getRowData'); // 获取当前页数据(已格式化)
|
||||
var rows = exportRows || jgrid.jqGrid('getRowData');
|
||||
if (exportRows) {
|
||||
var gridElement = jgrid[0];
|
||||
rows = exportRows.map(function (row, rowIndex) {
|
||||
var formattedRow = {};
|
||||
visibleCols.forEach(function (col) {
|
||||
var colIndex = colModel.indexOf(col);
|
||||
var rawValue = $.jgrid.getAccessor(row, col.name);
|
||||
var formattedValue = gridElement && gridElement.formatter
|
||||
? gridElement.formatter(rowIndex + 1, rawValue, colIndex, row, 'add')
|
||||
: rawValue;
|
||||
formattedRow[col.name] = $('<div>').html(formattedValue == null ? '' : String(formattedValue)).text().replace(/\u00a0/g, '');
|
||||
});
|
||||
return formattedRow;
|
||||
});
|
||||
}
|
||||
var headerLabels = visibleCols.map(function (c) { return c.label || c.name; });
|
||||
|
||||
// 构建 HTML table,用 style 保持 mso-number-format 让金额不被科学计数法破坏
|
||||
|
||||
Reference in New Issue
Block a user