//otcformat禁止千分位分组 window.otcformat.options.disableGrouping = true; const inputFormatSwapRate = swapPricePrecision.getCommonInputFormat('rate', { append: '%' }); const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, append: '' }); const inputFormatEqvNotional = swapPricePrecision.getCommonInputFormat('amount', { append: '', negative: true }); const inputFormatInterestAmount = swapPricePrecision.getCommonInputFormat('amount', { append: '', negative: true }); const inputFormatDividend = swapPricePrecision.getCommonInputFormat('amount', { append: '', negative: true }); const swapInstrumentType = (model.FlowEvents || []).find(item => item && item.UnderlyingInstrumentType)?.UnderlyingInstrumentType || model.UnderlyingInstrumentType || ''; const formatSwapAmount = value => swapPricePrecision.normalizeCommon('amount', value); const formatSwapQuantity = value => swapPricePrecision.normalizeCommon('quantity', value, swapInstrumentType); const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' }); const inputFormatMarginRateNoPercent = Object.freeze({ precision: otcformat.trading.umpriceP.precision, append: '', percent: true }); const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, append: '', negative: true }); let ValueDate = model.ValueDate; let MaxIncomeValueDate = model.MaxIncomeValueDate ? model.MaxIncomeValueDate.substr(0, 10) : ValueDate; function parseLocalDate(value) { if (!value) { return null; } var parts = value.substr(0, 10).split('-'); if (parts.length !== 3) { return null; } return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2])); } const vue = new Vue({ el: '#vueDiv', data: { deal: model, floatPosition: null, interestList: [], marginList: [], multiplier: 1, initPosiNetPrice: 0, initPosiGrossPrice:0 }, computed: { maxUnwindDate() { return MaxIncomeValueDate; }, minStartDate() { return this.deal.StartDate; } }, created() { this.multiplier = this.deal.StructureType == '普通债券类收益互换' ? 100 : 1; this.initDeal(); this.setValueDate(); }, mounted() { this.$nextTick(() => { var $incomeValueDatePicker = $(this.$refs.incomeValueDatePicker.$el); $incomeValueDatePicker.datepicker("option", "maxDate", parseLocalDate(MaxIncomeValueDate)); if (this.isAfterMaxIncomeValueDate(this.deal.ValueDate)) { this.validateIncomeValueDate(this.deal.ValueDate); } else { $incomeValueDatePicker.val(this.deal.ValueDate); } }); }, methods: { formatAmount(value) { return swapPricePrecision.formatCommon('amount', value); }, formatQuantity(value) { return swapPricePrecision.formatCommon('quantity', value, swapInstrumentType); }, isAfterMaxIncomeValueDate(valueDate) { return valueDate && MaxIncomeValueDate && valueDate > MaxIncomeValueDate; }, validateIncomeValueDate(valueDate) { if (!this.isAfterMaxIncomeValueDate(valueDate)) { return true; } main.message("手动互换结算日期不能晚于当前交易结束日期T-1:" + MaxIncomeValueDate); this.deal.ValueDate = MaxIncomeValueDate; this.deal.UnwindDate = MaxIncomeValueDate; this.floatPosition.UnwindDate = MaxIncomeValueDate; $(this.$refs.incomeValueDatePicker.$el).val(MaxIncomeValueDate); return false; }, getDeliveryPriceInputFormat() { return swapPricePrecision.getInputFormat( this.floatPosition && this.floatPosition.UnderlyingInstrumentType, 'grossPrice', inputFormatSwapDeliveryPrice); }, getStorageDeliveryPrice() { return swapPricePrecision.roundForSubmit( swapPricePrecision.shiftDecimal(this.floatPosition.TradingAmountAvg, this.multiplier === 100 ? -2 : 0), this.floatPosition && this.floatPosition.UnderlyingInstrumentType, 'grossPrice', this.multiplier === 100 ? 2 : 0); }, initDeal() { var positions = model.FlowEvents.filter((item) => { return item.UnderlyingCode; }); this.floatPosition = positions[0]; this.initPosiNetPrice = this.floatPosition.PosiNetPrice; this.initPosiGrossPrice = this.floatPosition.PosiGrossPrice; // 互换标的价格固定为期初净价,与平仓不同不需要用户填写 // 期初净价入库为相对价(如1.02),需转换为界面百分比形态(102),与平仓页保持一致 // 普通打开时用期初全价作为默认值;审批打开时必须保留已提交的期末全价,不能再被期初全价覆盖。 // 两种来源入库时都是相对价,债券统一 ×100 还原为界面百分比态。 this.floatPosition.TradingAmountAvg = SwapCalc.resolveIncomeTradingAmountAvg( this.initPosiGrossPrice, this.floatPosition.TradingAmountAvg, this.multiplier, isUseApproval); this.interestList = model.FlowEvents.filter((item) => { return item.InterestMode == 1 || item.InterestMode == 2 || item.InterestMode == 7 || item.InterestMode == 8 || item.InterestMode == 9; }); this.marginList = model.FlowEvents.filter((item) => { return item.InterestMode == 5 || item.InterestMode == 6; }); this.TradeStartDate = model.TradeStartDate; }, IsBond(instType) { return tradeHelper.IsBond(instType); }, priceFormat(price) { price = swapPricePrecision.shiftDecimal(price, this.multiplier === 100 ? 2 : 0); var pricef = swapPricePrecision.format(price, this.floatPosition && this.floatPosition.UnderlyingInstrumentType, 'grossPrice'); return pricef; }, dataFormat() { this.deal.NotionalValue = formatSwapAmount(this.deal.NotionalValue); this.deal.PosiNotionalValue = formatSwapAmount(this.deal.PosiNotionalValue); this.deal.NotionalQty = formatSwapQuantity(this.deal.NotionalQty); this.deal.PositionQty2 = formatSwapQuantity(this.deal.PositionQty); this.floatPosition.Quantity = formatSwapQuantity(this.floatPosition.Quantity); this.floatPosition.PositionQty = formatSwapQuantity(this.floatPosition.PositionQty); this.deal.SwapCloseAmount = formatSwapAmount(this.deal.SwapCloseAmount); //this.floatPosition.PosiNetPrice = otcformat.trading.umprice(this.floatPosition.PosiNetPrice); //this.floatPosition.PosiGrossPrice = otcformat.trading.umprice(this.floatPosition.PosiGrossPrice); this.floatPosition.TradingFee = formatSwapAmount(this.floatPosition.TradingFee); this.floatPosition.DividendIn = formatSwapAmount(this.floatPosition.DividendIn); this.floatPosition.MarkClosePnl = formatSwapAmount(this.floatPosition.MarkClosePnl); this.interestList.forEach(x => { //x.Principal = formatSwapAmount(x.Principal); //x.Rate = otcformat.fixed6(x.Rate); x.InterestFee = formatSwapAmount(x.InterestFee); x.InterestAmount = formatSwapAmount(x.InterestAmount); x.InterestClosePnL = formatSwapAmount(x.InterestClosePnL); //x.InterestStartDate = x.InterestStartDate ? x.InterestStartDate.substr(0, 10) : ""; //x.InterestEndDate = x.InterestEndDate ? x.InterestEndDate.substr(0, 10) : ""; }); this.marginList.forEach(x => { x.InterestFee = formatSwapAmount(x.InterestFee); x.InterestAmount = formatSwapAmount(x.InterestAmount); x.InterestClosePnL = formatSwapAmount(x.InterestClosePnL); }); }, setValueDate(e) {//修改平仓日期 if (e) { if (!this.validateIncomeValueDate(e)) { e = MaxIncomeValueDate; } this.deal.ValueDate = e; this.deal.UnwindDate = e; this.floatPosition.UnwindDate = e; } if (!isUseApproval) { this.getInterestList(); } else { this.dataFormat(); } }, changeUnderlyingPrice() {//修改标的价格 this.calcFloatClosePnl(); }, refreshUnderlyingPrice() {//刷新标的价格 var thisObj = this; main.post("/underlying_manager/GetUnderlyingPriceByCode", { code: thisObj.floatPosition.UnderlyingCode, valuedate: thisObj.deal.ValueDate }) .done(function (res) { thisObj.floatPosition.TradingAmountAvg = swapPricePrecision.roundForSubmit( swapPricePrecision.shiftDecimal(res.obj, thisObj.multiplier === 100 ? 2 : 0), thisObj.floatPosition.UnderlyingInstrumentType, 'grossPrice'); thisObj.calcFloatClosePnl(); }); }, changeTradingFee() {//修改交易费用 this.calcFloatClosePnl(); }, changeInterestAmount(item) {//修改利息金额 let InterestFee = item.InterestFee == "" ? 0 : parseFloat(item.InterestFee); let interestRatio = item.InterestDirection == 1 ? 1 : -1; item.InterestClosePnL = formatSwapAmount(parseFloat(item.InterestAmount) * interestRatio + InterestFee * interestRatio); this.calcCloseAmount(); }, calcFloatClosePnl() {//计算浮动端平仓盈亏 var thisObj = this; let floatRatio = thisObj.floatPosition.PayDirection == 1 ? 1 : -1; let longRatio = thisObj.floatPosition.PositionType == 1 ? 1 : -1; let TradingFee = thisObj.floatPosition.TradingFee == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFee); let TradingFeePending = thisObj.floatPosition.TradingFeePending == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFeePending); let DividendIn = thisObj.floatPosition.DividendIn == "" ? 0 : parseFloat(thisObj.floatPosition.DividendIn ?? 0); let deliveryPrice = thisObj.getStorageDeliveryPrice(); // 债券全价是单位价格,价差盈亏应按持仓数量×合约乘数计算; // CloseNotionalValue 是期初全价折算后的名义本金,直接乘价差会重复包含期初价格。 let positionAmount = parseFloat(thisObj.floatPosition.Quantity) * parseFloat(thisObj.floatPosition.ContractSize || 1); thisObj.floatPosition.MarkClosePnl = positionAmount * (deliveryPrice - thisObj.initPosiGrossPrice) * floatRatio * longRatio; thisObj.floatPosition.MarkClosePnl = formatSwapAmount(thisObj.floatPosition.MarkClosePnl);//MarkClosePnl 纯盯市不要计算交易费用和分红 // 守卫: 浮动盈亏合计必须保留 2 位小数 → 对应历史 bug 3c5f25a5(原代码缺精度保留) // 数值由 swapCalc.calcFloatPnlSum 计算, 此处 .toFixed(2) 仅保留字符串类型以兼容下游 thisObj.floatPosition.FloatPnlSum = SwapCalc.calcFloatPnlSum(thisObj.floatPosition.MarkClosePnl, TradingFee, TradingFeePending, DividendIn).toFixed(2); thisObj.calcCloseAmount(); }, //calcClosePnL() {//计算浮动端平仓盈亏 // let pnl = parseFloat(this.floatPosition.ClosePnL) - parseFloat(this.floatPosition.TradingFee); // this.floatPosition.ClosePnL = formatSwapAmount(pnl); // this.calcCloseAmount(); //}, calcCloseAmount() {//计算平仓总额=浮动收取+利息收取-浮动支付-利息支付 var floatRatio = this.floatPosition.PayDirection == 1 ? 1 : -1; var thisObj = this; let pnl = parseFloat(this.floatPosition.FloatPnlSum); let TradingFee = thisObj.floatPosition.TradingFee == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFee); thisObj.deal.SwapCloseAmount = pnl; thisObj.deal.SwapRealizedPnL = pnl; thisObj.deal.SwapMarginRebatePnl = 0; thisObj.deal.SwapMarginAmount = 0; let deliveryPrice = thisObj.getStorageDeliveryPrice(); thisObj.floatPosition.TradingAmount = deliveryPrice * parseFloat(thisObj.deal.CloseNotionalValue); thisObj.floatPosition.CloseFee = TradingFee; if (thisObj.deal.CloseQty > 0) { thisObj.floatPosition.TradingAmountFeeAvg = deliveryPrice + (TradingFee / thisObj.deal.CloseQty) * floatRatio; } else { thisObj.floatPosition.TradingAmountFeeAvg = deliveryPrice; } this.interestList.forEach(x => { //let interestRatio = x.InterestDirection == 1 ? 1 : -1; let interestAmount = parseFloat(x.InterestClosePnL) ; thisObj.deal.SwapCloseAmount = parseFloat(thisObj.deal.SwapCloseAmount) + interestAmount; thisObj.deal.SwapRealizedPnL = parseFloat(thisObj.deal.SwapRealizedPnL) + interestAmount; }); this.marginList.forEach(x => { let interestAmount = parseFloat(x.InterestClosePnL) ; thisObj.deal.SwapCloseAmount = parseFloat(thisObj.deal.SwapCloseAmount) + interestAmount; thisObj.deal.SwapMarginRebatePnl = parseFloat(thisObj.deal.SwapMarginRebatePnl) + interestAmount; thisObj.deal.SwapRealizedPnL = parseFloat(thisObj.deal.SwapRealizedPnL) + interestAmount; }); thisObj.deal.SwapCloseAmount = formatSwapAmount(thisObj.deal.SwapCloseAmount); thisObj.deal.SwapRealizedPnL = formatSwapAmount(thisObj.deal.SwapRealizedPnL); thisObj.deal.SwapMarginRebatePnl = formatSwapAmount(thisObj.deal.SwapMarginRebatePnl); }, getInterestList() {//根据平仓日期获取利息腿信息 var thisObj = this; var postData = { ValueDate: thisObj.deal.ValueDate, unwindDate: thisObj.deal.UnwindDate, tradeId: thisObj.deal.SwapTradeId, closePercent: 1, eventType:3 } main.post("/swaptrade2/GetUnwindInterestList", postData, { async: true }).done(function (resp) { thisObj.interestList = resp.obj.filter((item) => { return item.InterestMode == 1 || item.InterestMode == 2 || item.InterestMode == 7 || item.InterestMode == 8 || item.InterestMode == 9; }); thisObj.marginList = resp.obj.filter((item) => { return item.InterestMode == 5 || item.InterestMode == 6; }); thisObj.calcCloseAmount(); thisObj.dataFormat(); thisObj.getDivindIn(); }); }, getDivindIn() { var thisObj = this; // 方案C:分红改由后端 InitIncome 读 EOD PosiDividendSum 填入 floatPosition.DividendIn(单一可信源)。 // 前端不再调用 GetBondPayMentInterest 自算——消除"期初持仓×totalInterest"对已平仓部分的重复计入。 // floatPosition.DividendIn 保持后端返回值不动(=本次互换要落袋的全量待实现)。 // DividendPending(待结算)互换页保持 0:互换=全量结清,结清后账上无待结算。 // ↳ 与平仓页不同:平仓只拿走一部分,剩余持仓仍有待结算 → 平仓页 DividendPending=PosiDividendSum(后端值)。 thisObj.floatPosition.DividendPending = 0; thisObj.calcFloatClosePnl(); thisObj.dataFormat(); }, incomeTrade() {//互换 var thisObj = this; if (main.isEmpty(thisObj.deal.ValueDate)) { main.message("请输入平仓日期"); return; } if (!thisObj.validateIncomeValueDate(thisObj.deal.ValueDate)) { return; } let reqObj = _.cloneDeep(thisObj.deal); let marginCloneList = _.cloneDeep(thisObj.marginList); reqObj.FlowEvents = _.cloneDeep(thisObj.interestList); marginCloneList.forEach((item) => { reqObj.FlowEvents.push(item); }) thisObj.floatPosition.EventDate = thisObj.deal.ValueDate; let floatPosition = _.cloneDeep(thisObj.floatPosition); floatPosition.Quantity = 0; floatPosition.TradingAmountAvg = thisObj.getStorageDeliveryPrice(); reqObj.FlowEvents.push(floatPosition); var postData = { unwindData: reqObj }; var msg = "确认提交收益结算?"; var postUrl = "/swaptrade2/SwapIncomeJson"; if (g_isShowReCheckClose) { msg = "确认提交收益结算审核?"; postUrl = "/swaptrade2/ApplyUnwind"; postData.eventType = 3;//互换3,平仓2 } main.confirm(msg, function () { //重新计算百分比 var thisObj2 = thisObj; main.post(postUrl, postData).done(function (res) { if (res.success) { thisObj2.closetrade_cashWindow(); } else { try { thisObj2.closetrade_cashWindow(); } catch (e) { } } }); }); }, getSumbitText: function () { return g_isShowReCheckClose ? "审核提交" : "保存"; }, submitApproval(status) { var pop = ''; if (status === 'pass') { pop = "确认通过审批?"; } if (status === 'reject') { pop = "确认拒绝?"; } let thisObj = this; var confirmFunc = function (additionalProcessing) { var pData = { tradeId: thisObj.deal.SwapTradeId, status: status, text: "" }; if (!main.isEmpty(additionalProcessing)) { pData.additionalProcessing = additionalProcessing; } var thisObj2 = thisObj; main.post("/processtradelog/UpdateTradeProcessLog", pData).done( function (data) { if (data.obj && data.obj.proccessType == "AdditionalProcessing") { if (data.obj.type == "LackOfMoney") { var htmlContent = `