feat(swap): 添加互换合约估值功能和优化数据模型

- 在ClientSwapPositionRequest中新增BookId字段用于资产分类查询
- 新增FloatingUnrealizedPnl、InterestPaymentMethod、MaturityNettingValuation等估值相关字段
- 添加MaturitySettlementDate、DividendAmount、AdditionalMarginAmount等持仓详情字段
- 更新前端表格列模型,调整浮动端和估值实现收益分组显示
- 实现框架合约导出功能,支持完整数据筛选结果导出
- 优化名义本金计算逻辑,修复长短仓数值处理问题
- 完善付息方式和估值口径的业务逻辑处理
- 添加TRS估值、期间分红等关键业务字段计算
- 实现Excel导出时分组表头格式化功能
- 修复框架合约列排序和分组表头显示问题
This commit is contained in:
张名锐
2026-07-10 18:20:20 +08:00
parent 5f27c85ae9
commit 7df9e8060e
8 changed files with 445 additions and 52 deletions
+20
View File
@@ -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("结束日期不能大于当前系统日期!");
+18 -2
View File
@@ -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 让金额不被科学计数法破坏