Files
zszq-trs/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
T
tengyufan 4987962aca #EQD-6597 国联民生-簿记时新增基本费率字段(开平仓费用),并且自动计算
feat: 完善互换平仓交易费用自动计算

- 支持百分比和单位数量模式自动计算平仓交易费用
- 支持全部平仓和部分平仓联动重算
- 保持手动互换不自动填充平仓交易费用
- 补充前后端相关单测
2026-07-27 11:57:20 +08:00

1866 lines
88 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//otcformat禁止千分位分组
window.otcformat.options.disableGrouping = true;
const consClients = ylotc.clients;
const consTraders = ylotc.traders;
const consAssetUnits = page.canAddNewTrader ? ylotc.assetunits
: ylotc.assetunits.filter(x => x.TraderIds.includes(page.Trade.TraderId));
const inputFormatInteger = Object.freeze({ precision: 0, append: '' });
const inputFormatEqvNotional = Object.freeze({ precision: otcformat.trading.StockEqvNotional.precision, append: '' });
const inputFormatTradeAmount = Object.freeze({ precision: otcformat.trading.notional.precision, append: '' });
const inputFormatSwapRate = Object.freeze({ precision: otcformat.trading.premiumRateP.precision, negative: true, append: '%' });
const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' });
const inputFormatTradeSinglePrice = Object.freeze({ precision: otcformat.trading.tradeSinglePrice.precision, negative: true, append: '', percent: false });
const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '' });
const inputFormatPosiFeeUnit = Object.freeze({ precision: 2, negative: true, append: '' });
const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' });
const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: false });
const inputFormatSwapBondDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: true });
const inputFormatSwapBondNetPriceAndYtm = Object.freeze({ precision: 9, negative: true, append: '', percent: true });
const swapBondStoragePricePrecision = inputFormatSwapBondDeliveryPrice.precision + 2;
const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 });
const swapPosiFeeCalc = Object.freeze({
normalizeFeeType(feeType) {
return Number(feeType) === consPosiFeeType.Unit ? consPosiFeeType.Unit : consPosiFeeType.Percent;
},
calcPending(feeType, feeUnit, stockEqvNotional, quantity) {
const normalizedFeeType = this.normalizeFeeType(feeType);
const normalizedFeeUnit = Number(feeUnit) || 0;
const normalizedNotional = Number(stockEqvNotional) || 0;
const normalizedQuantity = Number(quantity) || 0;
const tradingFeePending = normalizedFeeType === consPosiFeeType.Percent
? normalizedFeeUnit / 100 * normalizedNotional
: normalizedFeeUnit * normalizedQuantity;
return otcformat.trading.tradeSinglePrice(tradingFeePending);
},
calcFeeUnit(feeType, tradingFeePending, stockEqvNotional, quantity) {
const normalizedFeeType = this.normalizeFeeType(feeType);
const normalizedTradingFeePending = Number(tradingFeePending) || 0;
const normalizedNotional = Number(stockEqvNotional) || 0;
const normalizedQuantity = Number(quantity) || 0;
if (normalizedFeeType === consPosiFeeType.Percent) {
return normalizedNotional === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedNotional * 100, inputFormatPosiFeePercent.precision);
}
return normalizedQuantity === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedQuantity, inputFormatPosiFeeUnit.precision);
}
});
const consUnderlyingFlagBase = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
return unSelFlag.UseForTrading | unSelFlag.IncludeMatured | unSelFlag.UsePinYinFilter | unSelFlag.IncludeBasket | unSelFlag.IncludeSynthetic | unSelFlag.CheckLaunch;
}());
const consUnderlyingFlagNonBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
//临时注释,保证业务不受影响
//return consUnderlyingFlagBase | unSelFlag.Stock | unSelFlag.StockIndex | unSelFlag.StockIF | unSelFlag.Fund | unSelFlag.CommodityFutures | unSelFlag.CommoditySpot;
return consUnderlyingFlagBase;
}());
const consUnderlyingFlagBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
const consUnderlyingFlagNonBond = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
//临时注释,保证业务不受影响
//return consUnderlyingFlagBase | unSelFlag.Stock | unSelFlag.StockIndex | unSelFlag.StockIF | unSelFlag.Fund | unSelFlag.CommodityFutures | unSelFlag.CommoditySpot;
return consUnderlyingFlagBase;
}());
}());
const inputFormatDouble2 = Object.freeze({ precision: 2, append: '' });
var autoMarginTemplateName;
//定价格式化(来自配置)
const InputFormatPercent = Object.freeze({ precision: 2, append: '%' });
const consMetaDic = {
'组合标的': JSON.parse(page.Trade.MetaDic['组合标的'] || '{}'),
'组合标的2': JSON.parse(page.Trade.MetaDic['组合标的2'] || '{}')
};
//定价格式化(来自配置)
const pricingFormat = otcformat.trading;
//数字格式化
const consNumberFormat = Object.freeze(new function () {
this.umpriceP = pricingFormat.umpriceP;
this.umprice = pricingFormat.umprice;
this.premiumRateP = pricingFormat.premiumRateP;
return this;
}());
const createVueUnderlying = function (selectFlag) {
return {
props: ['value', 'index'],
data() {
return { autoUnderlying: null };
},
mounted() {
var selFlag = tradeHelper.UnderlyingSelectFlag;
this.autoUnderlying = tradeHelper.UnderlyingAutoComplete(this.$el, { SelectFlag: selFlag.UsePinYinFilter |
selFlag.UseForTrading |
selFlag.IncludeSynthetic |
selFlag.IncludeMatured |
selFlag.IncludeBasket |
selFlag.CheckLaunch, BlackLimit: 2, });
this.autoUnderlying.onSelect(this.onchange);
this.value && this.autoUnderlying.selectByCode(this.value);
},
methods: {
onchange(data) {
this.$emit('change', data);
this.$emit('input', data.Code);
}
},
destroyed() {
this.autoUnderlying && this.autoUnderlying.dispose();
},
template: '<input type="text" v-model="value" />'
};
};
const vueUnderlyingNonBond = function () {
return createVueUnderlying(consUnderlyingFlagNonBond);
};
const vueUnderlyingBond = function () {
return createVueUnderlying(consUnderlyingFlagBond);
};
//标的选择组件 银行间回购定盘&其他利率
const vueUnderlyingRate = function () {
return {
props: ['value', 'index'],
data() {
return { autoUnderlying: null };
},
mounted() {
var selFlag = tradeHelper.UnderlyingSelectFlag;
var instrumentTypes = ["FixingRepoRate", "OtherRate"];
this.autoUnderlying = tradeHelper.UnderlyingAutoComplete(this.$el,
{ SelectFlag: selFlag.UsePinYinFilter |
selFlag.UseForTrading |
selFlag.IncludeSynthetic |
selFlag.IncludeMatured |
selFlag.IncludeBasket |
selFlag.CheckLaunch, BlackLimit: 2, InstrumentTypes: instrumentTypes, UseAll: true, UseSignle: true });
this.autoUnderlying.onSelect(this.onchange);
this.value && this.autoUnderlying.selectByCode(this.value);
},
methods: {
onchange(data) {
data.index = this.index;
if (this.value !== data.Code) {
this.$emit('change', data);
this.$emit('input', data.Code);
}
}
},
destroyed() {
this.autoUnderlying && this.autoUnderlying.dispose();
},
template: '<input type="text" v-model="value" />'
};
};
//ESC
$(document).keyup(function (event) {
if (event.keyCode === 27) {
window.parent.layer.closeAll();
}
});
const vue = new Vue({
el: '#tradeEdit',
data: {
page: page,
trade: _.cloneDeep(page.Trade),
viewState: {
variety: tradeHelper.getEmptyVariety(),
underlying: tradeHelper.getEmptyUnderlying(),
synthetic: null,
Extend: { TradingPlace: "柜台市场", ClearingAgency: "交易员方" }
},
currencys: page.currencys,
getNotionalSingleFee: 0,
isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0,
posiFeeModePercent: true,
observation: {//互换观察日
ObservationInterval: "",
IntervalList: [],
ObservationNum: 1,
ObservationUnit: 'D',
ObservationHolidayType: 'Following',
ObservationAlignEnd: true,
ObservationCalendar: 'Chn',
ObservationSettlementRules: 0,
DefaultTitle1Value: 0.1,
ObservationDataList: [],
CheckedAll: false,
index: 0,
IsDeductPrincipal: true,
ObservationStart: page.Trade.TradeDate
},
observationHolidayTypes: [
{ text: "向后调整", value: "Following" },
{ text: "向前调整", value: "Previous" },
{ text: "不调整", value: "None" }
],
alignEndTypes: [
{ text: "向到期日对齐", value: true },
{ text: "向开始日对齐", value: false }
],
marginSwapList: [],
getSwapList: [],//利息端
paySwapList: [],//浮动端,
observationType: 0,
client: null,//客户信息
StockEqvNotional: 0,
underlying: {
UnderlyingCode: '',
UnderlyingName: '',
UnderlyingIssuer: '',
IssueSize: '',
MaturityDate: '',
Price: '',
UnderlyingInstrumentType: '',
QuoteUnitString: ''
}
},
watch: {
},
created: function () {
if (this.trade.MetaDic['组合标的']) {
this.viewState.synthetic = JSON.parse(this.trade.MetaDic['组合标的']);
}
if (this.trade.MetaDic["交易场所"]) {
this.viewState.Extend.TradingPlace = this.trade.MetaDic["交易场所"];
}
if (this.trade.MetaDic["清算机构"]) {
this.viewState.Extend.ClearingAgency = this.trade.MetaDic["清算机构"];
}
},
mounted() {
__init(this);
$("#MainProtocolCode").val(this.trade.MetaDic["主协议编号"]);
$("#SupProtocolCode").val(this.trade.MetaDic["补充协议编号"]);
this.initSwapRateList();
// 初始化到期日期选择器下限
this.$nextTick(() => {
if (this.$refs.exerciseDatePicker) {
var minDate = this.trade.StartDate > this.trade.TradeDate ? this.trade.StartDate : this.trade.TradeDate;
this.$refs.exerciseDatePicker.refresh(minDate, null);
}
});
},
methods: {
roundStorageDeliveryPrice(item, price) {
const precision = tradeHelper.IsBond(item && item.UnderlyingInstrumentType)
? swapBondStoragePricePrecision
: inputFormatSwapDeliveryPrice.precision;
return _.round(Number(price), precision);
},
roundStorageBondNetPriceAndYtm(value) {
return value == null ? value : _.round(Number(value), swapBondStoragePricePrecision);
},
getPosiPriceFormatKey(item, field) {
const index = item && item.index != null ? item.index : '';
const isBond = tradeHelper.IsBond(item && item.UnderlyingInstrumentType);
return `${index}-${field}-${isBond ? 'bond' : 'other'}`;
},
getCurrentPosiFeeType() {
return this.posiFeeModePercent ? consPosiFeeType.Percent : consPosiFeeType.Unit;
},
normalizePosiFeeType(feeType) {
return swapPosiFeeCalc.normalizeFeeType(feeType);
},
syncPosiFeeModeByItem(item) {
this.posiFeeModePercent = this.normalizePosiFeeType(item && item.PosiFeeType) !== consPosiFeeType.Unit;
},
syncPayItemFeeType(item) {
item.PosiFeeType = this.getCurrentPosiFeeType();
},
refreshTradingFeePendingByUnit(item) {
this.syncPayItemFeeType(item);
item.PosiTradingFeePending = swapPosiFeeCalc.calcPending(
item.PosiFeeType,
item.PosiTradingFeeUnit,
this.trade.StockEqvNotional,
item.PosiQuantity
);
},
refreshTradingFeeUnitByPending(item) {
this.syncPayItemFeeType(item);
item.PosiTradingFeeUnit = swapPosiFeeCalc.calcFeeUnit(
item.PosiFeeType,
item.PosiTradingFeePending,
this.trade.StockEqvNotional,
item.PosiQuantity
);
},
refreshPayTradingFeesByUnit() {
this.paySwapList.forEach(item => {
this.refreshTradingFeePendingByUnit(item);
});
},
changeStructureType() {
this.trade.StockEqvNotional = 0;
let direction = this.trade.trade_extend.ExtendObj.Direction;
if (direction !== 1 && direction !== 2) {
direction = 2;
this.trade.trade_extend.ExtendObj.Direction = direction;
}
this.paySwapList = [];
this.addSwapFloat(direction);
//this.trade.trade_extend.ExtendObj.FlowBookMode = 0;
},
//变更初始预付金收取方向
changeInitialMargin() {
if (this.trade.trade_Initial_Margin.Direction == 1) {
this.trade.trade_Initial_Margin.Direction = 2;
} else {
this.trade.trade_Initial_Margin.Direction = 1;
}
},
//变更交易场所
changeTradingPlace() {
this.trade.MetaDic["交易场所"] = this.viewState.Extend.TradingPlace;
},
//变更清算机构
changeClearingAgency() {
this.trade.MetaDic["清算机构"] = this.viewState.Extend.ClearingAgency;
},
//全价(DP)列输入:先按标的单价重算名义本金(非债券标的也需要),再对债券触发三字段反算。
// 组件 vue-number-input 只 $emit('input')(失焦/回车时由底层 numberInput.onchange 触发),
// 故此列绑 v-on:input,一个入口同时覆盖普通标的与债券两种情形。
onDpPriceInput(item) {
this.changeSpotPrice(item); // 名义本金随全价变化(非债券标的的既有逻辑)
this.onBondPriceInput(item, 'DP'); // 债券:以全价为源反算净价/收益率(非债券内部会早返回)
},
//债券收益互换:编辑某一浮动腿的净价/全价/收益率之一 → 该字段标记为"已手动设过"(锁定)
// 并以它为源调计算器反算"尚未手动设过"的另两个字段(已手动设过的一律不回写)
onBondPriceInput(item, type) { // type: 'CP'净价 / 'DP'全价 / 'YD'收益率
if (!item.isBond) return;
var m = item.bondManual || { CP: false, DP: false, YD: false };
m[type] = true;
this.$set(item, 'bondManual', m); // 锁定用户手填字段
this.$set(item, 'bondDriverType', type); // 以该字段为本次计算源
this.calcBondForItem(item);
},
//放弃手动覆盖、以当前某字段为源重新自动反算(清空所有手动锁定标记)
resetBondCalc(item) {
if (!item.isBond) return;
this.$set(item, 'bondManual', { CP: false, DP: false, YD: false });
// 优先沿用用户最后一次编辑锁定的源(bondDriverType),避免"净价残留脏值"时误以净价为源反算出连锁错误;
// 无历史源时再按 净价→全价→收益率 回退挑一个有值的字段。
var hasVal = function (v) { return v !== null && v !== '' && !isNaN(v); };
var driver = item.bondDriverType
|| (hasVal(item.PosiNetNoFeePrice) ? 'CP'
: hasVal(item.PosiGrossPrice) ? 'DP'
: hasVal(item.InitYtm) ? 'YD' : null);
this.$set(item, 'bondDriverType', driver);
this.calcBondForItem(item);
},
//以该浮动腿的 bondDriverType 为源调 /Bond/CalcBond;回写时跳过"已手动设过的字段"(bondManual)
//仅反向填充用户尚未手动设过的字段——故用户可逐个手填全部三个而互算不会冲掉任一个
//错误处理(对齐 bond-oms-ui / zszq-bond-oms):
// - 业务/参数错误(success=false 或 errCode!=0,如债券不存在/信息不全/参数非法)
// 以非阻塞 toast(main.message) 反馈真实原因,绝不回写、不弹模态框、不阻止手工输入;
// - 网络/服务异常:框架 main.post 已弹"请求失败",此处不再二次提示。
calcBondForItem(item) {
if (!item.isBond || !item.bondDriverType) return;
if (!item.UnderlyingCode) { main.message("请先选择债券标的"); return; }
var price = item.bondDriverType === 'CP' ? item.PosiNetNoFeePrice
: item.bondDriverType === 'DP' ? item.PosiGrossPrice
: item.InitYtm;
if (price === null || price === '' || isNaN(price)) return; // 编辑中(空/非数)静默,不提示
var priceType = item.bondDriverType === 'CP' ? 'CP' : item.bondDriverType === 'DP' ? 'DP' : 'YD';
var self = this;
// 估值日:债券互换的"期初"净价/全价/收益率应以【互换起始日 StartDate】估值。
// 该字段在页面上本就是可选择的日期控件(vue-datepicker),用户可改;现已默认即有。
// 不再"悄悄回退到交易日(TradeDate)"——真的为空时直接报错提示,由用户填写/手动填三数(需求2)。
var targetDate = (this.trade && this.trade.StartDate) ? this.trade.StartDate : null;
var sdMsg = SwapCalc.getBondStartDateMissingMsg(targetDate);
if (sdMsg) {
// D3 同款去重:开始日缺失时用户在价格格逐键输入会连刷相同提示,故同一文案只弹一次
if (SwapCalc.shouldShowBondErr(item, sdMsg)) main.message(sdMsg);
return; // 估值日缺失:不调用计算器、不覆盖任何手工输入
}
main.post('/Bond/CalcBond', {
underlyingCode: item.UnderlyingCode,
// 模型 item.PosiGrossPrice/PosiNetNoFeePrice/InitYtm 存的是【存储态小数】(如 0.995),
// 而 bond-calc 要的是【展示态/每百元百分比】(如 99.5)。这俩靠 BondPriceConverter.ToDisplay(×100)/ToStorage(÷100) 对齐。
// 此处传给计算器前必须 bondPriceToCalc(存储→展示);回写时再 bondCalcPriceToStorage(展示→存储),
// 否则计算器拿到 0.995≈1 当 1% of par 直接发散成离谱值。
price: SwapCalc.bondPriceToCalc(price),
priceType: priceType,
targetDate: targetDate
}, { alertFn: main.message }).done(function (resp) {
if (!resp || !resp.obj) return;
// 业务层错误(债券不存在/信息不全/参数非法):仅提示,绝不覆盖手工输入
var err = SwapCalc.getBondCalcErrorMessage(resp.obj);
if (err) {
// D3 修复:不可算债券上用户逐键手填时,每次按键都触发一次失败计算并弹 toast,会连刷相同提示;
// 同一错误文案连续出现只弹一次(shouldShowBondErr 维护 item._lastBondErr),抑制噪声、不影响任何计算逻辑。
if (SwapCalc.shouldShowBondErr(item, err)) main.message(err);
return;
}
item._lastBondErr = null; // 成功则清标记,便于下次真出不同错误时仍能提示
// 以既有三字段为代理,调用纯函数(已手动设过的字段不被覆盖),再写回。
// 关键:proxy 内必须统一为【展示态】(per-100-face),因为 applyBondCalcResult 写入的是计算器返回的展示态。
// 模型字段是【存储态小数】(percent:true 下 1.00 对应界面 100),所以初始化时要 bondPriceToCalc(×100)
// 若直接用存储态初始化,则用户手填字段被 applyBondCalcResult 跳过后,proxy 中仍残留存储态,
// 后续再 ÷100 就会导致该字段被二次缩小(如输入 100 变成 1)。
var proxy = {
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice),
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice),
ytm: SwapCalc.bondPriceToCalc(item.InitYtm)
};
var manual = item.bondManual || { CP: false, DP: false, YD: false };
SwapCalc.applyBondCalcResult(proxy, resp.obj, manual);
// 回写前 bondCalcPriceToStorage(÷100)(展示态→存储态小数):proxy 里均为展示态;
// 模型字段存存储态(0.995),须 ÷100 落回模型,否则配合 percent:true 显示会 ×100 成离谱值。
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice);
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice);
item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm);
// 名义本金依赖全价(PosiGrossPrice):以净价/收益率为源反算出的全价被回写后,
// 直接赋值不会触发组件 input 事件,需在此显式重算,保持名义本金与全价一致。
if (self.calcNotional) self.calcNotional();
if (self.$forceUpdate) self.$forceUpdate();
});
},
//变更收费基本单位
changeOpenFeeType() {
var thisObj = this;
thisObj.paySwapList.forEach(item => {
thisObj.changeTradingFeeUnit(item);
})
},
//变更数量
changeQuantity(item) {
this.calcNotional();
},
//变更标的单价
changeSpotPrice(item) {
this.calcNotional();
},
//变更标的合约乘数
changeContractSize(item) {
this.calcNotional();
},
//变更名义本金(仅格式化,不反算数量)
changeStockEqvNotional() {
this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional);
this.refreshPayTradingFeesByUnit();
//计算数量
// if (this.paySwapList.length > 0) {
// var item = this.paySwapList[0];
// var deliveryPrice = this.roundStorageDeliveryPrice(item, item.PosiGrossPrice);
// var notional = deliveryPrice * item.ContractSize;
// item.PosiQuantity = notional == 0 ? 0 : _.round(this.trade.StockEqvNotional / notional, page.otcFormatConfig.StockEqvNotional.precision);
// this.calcNotional();
// }
},
//变更初始预付金 为¥
showAbsPrice() {
this.trade.trade_Initial_Margin.MarginType = 1;
this.trade.trade_Initial_Margin.MarginValue = this.trade.trade_Initial_Margin.MarginValue * this.trade.StockEqvNotional;
},
//变更单位交易费用 为%
showPercentPrice() {
this.trade.trade_Initial_Margin.MarginType = 0;
this.trade.trade_Initial_Margin.MarginValue = this.trade.StockEqvNotional == 0 ? 0 : this.trade.trade_Initial_Margin.MarginValue / this.trade.StockEqvNotional;
},
//成交日期变更时自动修正联动日期
onTradeDateChange() {
if (this.trade.StartDate < this.trade.TradeDate) {
// 开始日期 = 成交日期 + 1天,跳过节假日
var d = new Date(this.trade.TradeDate);
d.setDate(d.getDate() + 1);
while (ylotc.isHoliday(d)) {
d.setDate(d.getDate() + 1);
}
this.trade.StartDate = this.formatDate(d);
}
// 到期日期必须 ≥ 成交日期 且 ≥ 开始日期
if (this.trade.ExerciseDate < this.trade.TradeDate || this.trade.ExerciseDate < this.trade.StartDate) {
// 到期日期 = 开始日期 + 14天,跳过节假日
var sd = new Date(this.trade.StartDate);
sd.setDate(sd.getDate() + 14);
while (ylotc.isHoliday(sd)) {
sd.setDate(sd.getDate() + 1);
}
this.trade.ExerciseDate = this.formatDate(sd);
}
this.changeTradeDate();
},
//开始日期变更时检查到期日期
onStartDateChange() {
if (this.trade.ExerciseDate < this.trade.StartDate) {
var sd = new Date(this.trade.StartDate);
sd.setDate(sd.getDate() + 14);
while (ylotc.isHoliday(sd)) {
sd.setDate(sd.getDate() + 1);
}
this.trade.ExerciseDate = this.formatDate(sd);
}
this.changeTradeDate();
},
//变更交易日期
changeTradeDate(force) {
this.changeObservationStart();
if (this.Obervation) {
this.Obervation.ObservationStart = this.trade.StartDate;
}
this.calcNotional();
this.refreshDatepicker();
this.changeMarginDate();
// 动态更新开始日期和到期日期的下限
this.$nextTick(() => {
if (this.$refs.startDatePicker) {
this.$refs.startDatePicker.refresh(this.trade.TradeDate, null);
}
if (this.$refs.exerciseDatePicker) {
var minExerciseDate = this.trade.StartDate > this.trade.TradeDate ? this.trade.StartDate : this.trade.TradeDate;
this.$refs.exerciseDatePicker.refresh(minExerciseDate, null);
}
});
//this.initMarginRate();
},
calcNotional(calcPrice) {
if (this.paySwapList.length > 0) {
var payItem = this.paySwapList[0];
if (calcPrice) {
this.getSpotPrice(payItem.UnderlyingCode, this.trade.StartDate, payItem);
}
var national = payItem.PosiQuantity * payItem.ContractSize;
// 守卫: 名义本金必须 round 到 2 位 → 对应历史 bug f873239a(缺 _.round); 外置到 swapCalc.calcStockEqvNotional
var deliveryPrice = this.roundStorageDeliveryPrice(payItem, payItem.PosiGrossPrice);
var stockEqvNotional = SwapCalc.calcStockEqvNotional(deliveryPrice, national);//名义本金=期初价格*数量*乘数
this.trade.StockEqvNotional = otcformat.trading.stockEqvNotional(stockEqvNotional);
payItem.PosiNotionalValue = this.trade.StockEqvNotional;
this.refreshPayTradingFeesByUnit();
}
},
//变更到期日
changeExerciseDate() {
this.refreshDatepicker();
this.changeMarginDate();
},
//变更预付金生效日期
changeMarginDate() {
var thisObj = this;
thisObj.observation.ObservationStart = thisObj.trade.StartDate;
var startDate = thisObj.trade.StartDate;
var endDate = thisObj.trade.ExerciseDate;
if (thisObj.marginSwapList) {
thisObj.marginSwapList.forEach((item, index) => {
if (item.HappenDate < startDate) {
thisObj.marginSwapList[index].HappenDate = startDate;
thisObj.changeStartMarinRateDate(thisObj.marginSwapList[index]);
}
else if (item.HappenDate > endDate) {
thisObj.marginSwapList[index].HappenDate = endDate;
thisObj.changeStartMarinRateDate(thisObj.marginSwapList[index]);
}
});
}
},
// 变更预付金结算规则起息日期
changeStartMarinRateDate(item) {
if (item.Obervation) {
item.Obervation.ObservationStart = item.HappenDate;
}
},
//变更单位交易费用
changeTradingFeeUnit(item) {
this.refreshTradingFeePendingByUnit(item);
},
//变更交易费用
changeTradingFee(item) {
this.refreshTradingFeeUnitByPending(item);
},
showPayAbsPrice() {
this.posiFeeModePercent = false;
this.paySwapList.forEach(item => {
item.PosiFeeType = consPosiFeeType.Unit;
item.PosiTradingFeeUnit = 0;
item.PosiTradingFeePending = 0;
});
},
showPayPercentPrice() {
this.posiFeeModePercent = true;
this.paySwapList.forEach(item => {
item.PosiFeeType = consPosiFeeType.Percent;
item.PosiTradingFeeUnit = 0;
item.PosiTradingFeePending = 0;
});
},
savetrade() {
if (!this.checkSubmitData()) {
return false;
}
this.savetrade_inner();
},
savetrade_inner() {
var thisObj = this;
//this.paySwapList.forEach(x => {
// x.SwapIntervals = this.trade.ExerciseDate + ";" + x.InterestRate;
//});
this.trade.IsUsePremiumRate = true;
this.trade.IsTradePricePayType = true;
this.trade.UnderlyingInstrumentType = "Stock";
this.trade.swap_positions = [];
var date = thisObj.trade.ExerciseDate;
var errorcount = 0;
if (this.trade.trade_extend.ExtendObj.InterestCalcMode == "00" || this.trade.trade_extend.ExtendObj.InterestCalcMode == "10") {
date = moment(date).add(-1, 'days').format('YYYY-MM-DD');
}
this.getSwapList.forEach((x,index) => {
if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.SwapIntervalList.length == 0) {
var interval = {
Date: date,
Rate: x.InterestRateDefault,
Settlement: 0
}
x.SwapIntervalList.push(interval);
}
if (x.interest_rest_days != null && x.interest_rest_days <= 0) {
main.message("利息端第" + (index + 1) + "行重置频率必须大于0");
errorcount++;
return false;
}
x.FloatRateUnderlyingCode = x.FloatRateUnderlyingCode == "--" ? null : x.FloatRateUnderlyingCode;
x.InterestSwapInterval = JSON.stringify(x.SwapIntervalList);
thisObj.trade.swap_positions.push(x);
});
if (errorcount > 0) {
return;
}
var margin = 0;
this.marginSwapList.forEach((x, index) => {
if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.SwapIntervalList.length == 0) {
var interval = {
Date: date,
Rate: x.InterestRateDefault,
Settlement: 0
}
x.SwapIntervalList.push(interval);
}
if (!x.HappenDate) {
main.message("预付金/预付金第" + (index + 1) + "行生效日期不能为空");
errorcount++;
return false;
}
x.FloatRateUnderlyingCode = x.FloatRateUnderlyingCode == "--" ? null : x.FloatRateUnderlyingCode;
margin = margin + x.InterestPrincipalFix * (x.InterestDirection == 1 ? 1 : -1);
x.InterestSwapInterval = JSON.stringify(x.SwapIntervalList);
thisObj.trade.swap_positions.push(x);
});
if (errorcount > 0) {
return;
}
thisObj.trade.trade_Initial_Margin.MarginType = 1;
thisObj.trade.trade_Initial_Margin.MarginValue = margin;
thisObj.trade.trade_Initial_Margin.Direction = margin > 0 ? 1 : 2;
if (this.trade.StructureType != "多空组合") {
this.paySwapList.forEach((x, index) => {
if (!x.UnderlyingCode) {
main.message("浮动端第" + (index + 1) + "行标的不能为空");
errorcount++;
return false;
}
if (!x.PosiQuantity > 0) {
main.message("浮动端第" + (index + 1) + "数量不能为0");
errorcount++;
return false;
}
if (!x.PosiGrossPrice) {
main.message("浮动端第" + (index + 1) + "期初价格不能为空");
errorcount++;
return false;
}
x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice);
x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice);
x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm);
x.PosiFeeType = thisObj.normalizePosiFeeType(x.PosiFeeType);
thisObj.trade.swap_positions.push(x);
});
} else {
this.trade.trade_extend.ExtendObj.Direction = this.paySwapList[0].PosiDirection;
}
if (errorcount > 0) {
return;
}
this.trade.MetaDic = {
'组合标的': consMetaDic["组合标的"] ? JSON.stringify(consMetaDic["组合标的"]) : '',
'组合标的2': consMetaDic["组合标的2"] ? JSON.stringify(consMetaDic["组合标的2"]) : '',
'交易场所': this.viewState.Extend.TradingPlace,
'清算机构': this.viewState.Extend.ClearingAgency,
"主协议编号": $("#MainProtocolCode").val(),
"补充协议编号": $("#SupProtocolCode").val()
};
this.trade.trade_extend.ExtendJson = JSON.stringify(this.trade.trade_extend.ExtendObj);
main.post("/swaptrade2/tradeEditJson", this.trade).done(function (resp) {
window.location.href = "/swaptrade2/tradeview?enid=" + resp.obj.EncryptId;
if (window.parent && window.parent.reloadtrade) {
window.parent.reloadtrade();
}
});
},
changeMainProtocolCode() {
this.getSupProtocolCode();
},
getMainProtocolCode() {
var thisObj = this;
$("#MainProtocolCode option").remove();
main.post("/Client/getMainProtocolCodes", { clientId: $("#ClientId").val() }, { async: false }).done(function (resp) {
var obj = $("#MainProtocolCode");
if (thisObj.client.MainProtocolCode != null && thisObj.client.MainProtocolCode != '') {
obj.append("<option >" + thisObj.client.MainProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v.Text + "</option>");
})
});
this.getSupProtocolCode()
},
getSupProtocolCode() {
$("#SupProtocolCode option").remove();
if ($("#MainProtocolCode").val()) {
main.post("/Client/getSideProtocols", { mainProtocol: $("#MainProtocolCode").val() }, { async: false }).done(function (resp) {
var obj = $("#SupProtocolCode");
if (thisObj.client.SupProtocolCode != null && thisObj.client.SupProtocolCode != '') {
obj.append("<option >" + thisObj.client.SupProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v + "</option>");
})
});
}
},
changeInterestMode(item) {
if (item.InterestMode == 7 || item.InterestMode == 8) {
item.InterestType = 0;
}
},
changeInterestType(item) {
if (!item.interest_rest_days) {
item.interest_rest_days = 7;
}
},
setUnderlyingCode(data, item) {
var underlyingCode = item.UnderlyingCode;
if (underlyingCode != data.Code) {
this.getSpotPrice(data.Code, this.trade.TradeDate, item);
} else {
item.PosiGrossPrice = item.PosiGrossPrice;
item.PosiNetNoFeePrice = item.PosiNetNoFeePrice;
}
item.CountRatio = data.CountRatio;
item.ContractSize = data.ContractSize;
item.UnderlyingInstrumentType = data.InstrumentType;
item.UnderlyingCode = data.Code;
item.underlying = { ...this.underlying };
item.underlying.UnderlyingCode = data.Code;
item.underlying.UnderlyingName = data.Name;
item.underlying.UnderlyingIssuer = data.UnderlyingIssuer;
item.underlying.IssueSize = data.IssueSize;
item.underlying.MaturityDate = data.MaturityDate;
item.underlying.Price = data.Price;
item.underlying.UnderlyingInstrumentType = data.InstrumentType;
item.underlying.QuoteUnitString = data.QuoteUnitString;
this.trade.UnderlyingCode = underlyingCode;
// 债券标的:标记该浮动腿可启用净价/全价/收益率互算
this.$set(item, 'isBond', tradeHelper.IsBond(data.InstrumentType));
// 切换标的时清空债券三字段互算的手动/源标志(D1 修复:避免旧债券手填状态污染新债券)
SwapCalc.clearBondCalcFlags(item);
//this.initMarginRate();
},
setFloatRateUnderlyingCode(data, item) {
item.FloatRateUnderlyingCode = data.Code;
if (data.Code.length > 0 && data.Code != '--') {
item.IsAnnualized = true;
item.interest_rest_days = 7;
item.interest_rule = data.Code === 'FR007' ? -1 : 0;
} else {
item.interest_rule = null;
}
//this.getFloatRate(item);
},
getFloatRate(item) {
var thisObj = this;
if (item.FloatRateUnderlyingCode == null || item.FloatRateUnderlyingCode == '' || item.FloatRateUnderlyingCode == '--' || thisObj.paySwapList.length == 0) {
item.InterestRateDefault = 0;
blurSwapRate(item);
return;
}
var floatPositionType = thisObj.paySwapList[0].PositionType;
main.post("/SwapFloatRate/MatchRate", { clientId: thisObj.trade.ClientId, underlyingCode: item.FloatRateUnderlyingCode, startDate: thisObj.trade.StartDate, endDate: thisObj.trade.ExerciseDate })
.done(function (resp) {
if (resp.success) {
if (resp.obj != null) {
item.InterestRateDefault = floatPositionType == 1 ? resp.obj.LongPricePoint : resp.obj.ShortPricePoint;
item.InterestRateDefault = item.InterestRateDefault * 0.0001;
} else {
item.InterestRateDefault = 0;
}
thisObj.blurSwapRate(item);
}
});
},
getSpotPrice(underlyingCode, StartDate, item) {
var thisObj = this;
main.post("/pricing/AjaxGetUnderlyingPrice", { underlyingCode: underlyingCode, tradeDate: StartDate })
.done(function (resp) {
// 行情接口(AjaxGetUnderlyingPrice)对债券返回的 price/netPrice 本就是【存储态小数】(1.0 代表 100 元),
// 与 PosiGrossPrice/PosiNetNoFeePrice 模型字段同量纲(见 PricingController: EodPrice 直接返回、
// 非 EodPrice 分支 ×bondPriceMultiple=0.01)。故此处仅做精度格式化,**不可**再 bondCalcPriceToStorage(÷100)
// 否则默认价 1.0 被除成 0.01,界面 percent:true 再 ×100 显示为 1"被自动除以100"bug)。
// 计算器(/Bond/CalcBond)返回的才是展示态,其 ÷100 落库逻辑在 calcBondForItem 内处理。
item.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(resp.obj.netPrice);
item.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(item, resp.obj.price);
thisObj.calcNotional();
});
},
//年化利率变更同步修改 同行观察日利率
blurSwapRate(item) {
//if (item.InterestRateDefault != 0) {
// item.IsAnnualized = true;
//}
if (item.SwapIntervalList.length > 0) {
var swapRate = item.InterestRateDefault;
var same = true;
var rate1 = item.SwapIntervalList[0].Rate;
item.SwapIntervalList.forEach(function (item) {
if (item.Rate != rate1) {
same = false;
}
});
if (same) {
var confirmMsg = "检测到互换利率发生变化,将自动覆盖观察列表";
main.confirm(confirmMsg, function () {
item.SwapIntervalList.forEach(function (element, index, arr) {
arr[index].Rate = swapRate;
});
var observationDates = JSON.stringify(item.SwapIntervalList);
item.InterestSwapInterval = observationDates;
});
} else {
main.message("检测到互换利率与互换利率列表不一致,请手动重新生成互换列表");
}
}
},
changeObservationStart() {
var thisObj = this;
thisObj.getSwapList.forEach((item, index) => {
if (item.Obervation) {
thisObj.getSwapList[index].Obervation.ObservationStart = thisObj.trade.TradeDate;
}
});
},
//设置观察日弹框
initObservationDates(item, type) {
this.observationType = type;
var thisObj = this;
var observation = item.Obervation;
thisObj.observation.index = item.index;
thisObj.observation.IntervalList = item.SwapIntervalList;
thisObj.observation.DefaultTitle1Value = item.InterestRateDefault;
thisObj.observation.ObservationNum = observation ? observation.ObservationNum : 1;
thisObj.observation.ObservationUnit = observation ? observation.ObservationUnit : 'D';
thisObj.observation.ObservationHolidayType = observation ? observation.ObservationHolidayType : 'Following';
thisObj.observation.ObservationAlignEnd = observation ? observation.ObservationAlignEnd : true;
if (type == 1) {
thisObj.observation.ObservationCalendar = observation ? observation.ObservationCalendar : 'Chn';
thisObj.observation.ObservationSettlementRules = observation ? observation.ObservationSettlementRules : 0;
}
thisObj.observation.IsDeductPrincipal = observation ? observation.IsDeductPrincipal : true;
if (type == 1) {
thisObj.observation.IsDeductPrincipal = false;
}
var startTime = thisObj.trade.TradeDate;
var endTime = thisObj.trade.ExerciseDate;
if (main.isEmpty(startTime)) {
main.message("请输入开始日期");
return false;
}
if (main.isEmpty(endTime)) {
main.message("请输入到期日期");
return false;
}
if (thisObj.observation.ObservationStart < startTime) {
thisObj.observation.ObservationStart = startTime;
}
if (thisObj.observation.ObservationStart > endTime) {
thisObj.observation.ObservationStart = endTime;
}
thisObj.observation.ObservationDataList = [];
var floatRateCode = type == 1 ? (item.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.IntervalList.forEach((value, num, arr) => {
var val = value.Rate;
var _date = "";
var m = new moment(value.Date);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var itemChecked = thisObj.observation.IntervalList.length >= num ? (value.Settlement == 1 ? true : false) : false;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {};
if (type == 1) {
var settlementDate = value.Date;
if (value.SettlementDate!=null) {
settlementDate = value.SettlementDate;
}
var m = new moment(settlementDate);
obdate = {
date: _date,
SettlementDate: m.format("YYYY-MM-DD"),
floatRateCode: floatRateCode,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
};
} else {
obdate = {
date: _date,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
};
}
thisObj.observation.ObservationDataList.push(obdate);
});
var area = type == 1 ? ['800px', '600px'] : ['750px', '600px'];
layer.open({
type: 1,
area: area,
title: "设置互换日期",
shadeClose: false,
shade: 0.4,
content: $("#observationInfosEdit")
});
},
//计算结算日期
calcSettleDate(observationDate, settlementRules) {
if (!observationDate) return "";
var m = new moment(observationDate);
var settleDate = m.add(settlementRules, 'days').format("YYYY-MM-DD");
return settleDate;
},
//生成观察日操作
GetObservationDates() {
var thisObj = this;
var observationNum = thisObj.observation.ObservationNum;
var observationUnit = thisObj.observation.ObservationUnit;
var observationHolidayType = thisObj.observation.ObservationHolidayType;
var alignEnd = thisObj.observation.ObservationAlignEnd;
var settlementRules = thisObj.observation.ObservationSettlementRules;
var calendar = thisObj.observation.ObservationCalendar;
var floatRateCode = this.observationType == 1 ? (thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.ObservationDataList = [];
if (this.observationType == 1) {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode,
calendar: calendar,
settlementRules: settlementRules
};
main.post("/trade/GetSwapObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this.ObservationDate);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var _settleDate = "";
var sm = new moment(this.SettleDate);
if (!isNaN(sm.date())) {
_settleDate = sm.format("YYYY-MM-DD");
}
var val = parseFloat(consNumberFormat.umpriceP(thisObj.observation.DefaultTitle1Value));
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
SettlementDate: _settleDate,
floatRateCode: floatRateCode,
val: val,
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
} else {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode
};
main.post("/trade/GetObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var val = parseFloat(consNumberFormat.umpriceP(thisObj.observation.DefaultTitle1Value));
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
val: val,
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
}
},
//编辑观察日功能数据处理
SetObservationDates() {
var observationStr = "";
if (this.observation.ObservationDataList != null) {
this.observation.ObservationDataList.forEach(item => {
observationStr = observationStr + item.date + ", " + item.SettlementDate + ", " + (item.val * 0.01).toFixed(6) + ", " + item.itemChecked + ";\n";
});
}
this.observation.ObservationInterval = observationStr;
$("#myModal").modal("show");
},
// 粘贴功能
pasteMe(e, rowData, index) {
let copyData = '';
var thisObj = this;
if (e.clipboardData || e.originalEvent) {
var clipboardData = (e.clipboardData || e.originalEvent.clipboardData);
copyData = clipboardData.getData('Text');
var arr = copyData.replace(/\t/g, ', ').replace(/\r/g, '').split('\n');
arr.forEach((v, i) => {
if (v && !v.trim().endsWith(';')) {
arr[i] = v + ";";
}
});
thisObj.observation.ObservationInterval = arr.join('\r\n');
e.preventDefault();
}
},
//编辑观察日模态框保存操作
saveObservationModal() {
this.initObservationDataList();
$("#myModal").modal("hide");
},
//编辑观察日模态框保存 数据转换
initObservationDataList() {
var thisObj = this;
thisObj.observation.ObservationInterval = thisObj.observation.ObservationInterval.trim().replaceAll("\n", "").replaceAll(" ", "");
var observationDates = thisObj.observation.ObservationInterval;
var items = observationDates.split(";").filter(o => o);
thisObj.observation.ObservationDataList = [];
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
items.forEach(function (item) {
if (item) {
var values = item.split(",");
var itemChecked = JSON.parse(values[3].trim());
var disabled = false;
if (values[0] == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var val = values[2].trim();
var obdate = {
date: values[0],
SettlementDate: values[1] || "",
floatRateCode: floatRateCode,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
}
thisObj.observation.ObservationDataList.push(obdate);
}
});
thisObj.initObservationCheckedAll();
},
//观察日列表初始化是否全选
initObservationCheckedAll() {
var thisObj = this;
var noCheckedItems = thisObj.observation.ObservationDataList.filter(item => {
if ((item.itemChecked == false) && item.date != thisObj.trade.ExerciseDate) {
return item;
}
});
this.observation.CheckedAll = noCheckedItems.length == 0 && thisObj.observation.ObservationDataList.length > 0;
},
//删除单行观察日
deleteObItem(item) {
var thisObj = this;
var curindex = 0;
for (var i = 0; i < thisObj.observation.ObservationDataList.length; i++) {
if (thisObj.observation.ObservationDataList[i].date == item.date) {
curindex = i;
break;
}
}
thisObj.observation.ObservationDataList.splice(curindex, 1);
},
//添加观察日
addnewitem() {
var thisObj = this;
var obdate = {};
if (this.observationType == 1) {
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
obdate = {
date: '',
SettlementDate: '',
floatRateCode: floatRateCode,
val: 0,
itemChecked: true,
disabled: false
};
} else {
obdate = {
date: '',
val: 0,
itemChecked: true,
disabled: false
};
}
thisObj.observation.ObservationDataList.push(obdate);
},
//编辑观察日模态框关闭
closeObservationModal() {
$('#myModal').modal('hide');
},
//关闭互换layer层
closeParentWindow() {
layer.closeAll();
},
//观察日列表确认
ObservationConfirm() {
var thisObj = this;
var hasTimeError = false;
var hasNumberError = false;
var observationArr = [];
thisObj.observation.ObservationDataList.forEach(item => {
var m = new moment(item.date);
if (isNaN(m.date())) {
hasTimeError = true;
}
var dateFormat = /^(-?\d+)(\.\d+)?$/;
if (!dateFormat.test(item.val)) {
hasNumberError = true;
}
var observation = {
Date: item.date,
Rate: item.val * 0.01,
Settlement: item.itemChecked ? 1 : 0,
SettlementDate: item.SettlementDate || null // 结算日期
}
observationArr.push(observation);
});
if (hasTimeError) {
alert("日期格式有误,请输入正确格式");
return false;
}
if (hasNumberError) {
alert("请输入正确的数字格式");
return false;
}
if (this.observationType == 1) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
});
} else {
thisObj.marginSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
});
}
this.closeParentWindow();
},
//互换提交数据有效性检查
checkSubmitData() {
//验证输入的日期格式是否正确(包括交易日期、开始日、到期日)
var ExerciseDate = this.trade.ExerciseDate;
var regTime = /^[0-9]{4}-[0-1]?[0-9]{1}-[0-3]?[0-9]{1}$/;
if (main.isEmpty(ExerciseDate)) {
main.message("请输入到期日期");
return false;
}
if (!regTime.test(ExerciseDate)) {
main.message("请输入正确的到期日期格式");
return false;
}
var TradeDate = this.trade.TradeDate;
if (main.isEmpty(TradeDate)) {
main.message("请输入成交日期");
return false;
}
if (!regTime.test(TradeDate)) {
main.message("请输入正确的成交日期格式");
return false;
}
if (TradeDate > ExerciseDate) {
main.message("到期日期不能早于成交日期");
return false;
}
var StartDate = this.trade.StartDate;
if (main.isEmpty(StartDate)) {
main.message("请输入开始日期");
return false;
}
if (!regTime.test(StartDate)) {
main.message("请输入正确的开始日期格式");
return false;
}
if (StartDate < TradeDate) {
main.message("开始日期不能早于成交日期");
return false;
}
if (StartDate > ExerciseDate) {
main.message("到期日期不能早于开始日期");
return false;
}
if (!this.trade.AssetId) {
main.message("请设置簿记账户");
return false;
}
if (!this.trade.ClientId) {
main.message("请设置交易对手方");
return false;
}
if (this.trade.StructureType!="多空组合"&&(!this.trade.StockEqvNotional > 0 || this.trade.StockEqvNotional == null)) {
main.message("请设置名义本金");
return false;
}
if (!this.trade.trade_extend.ExtendObj.AnnualDays > 0) {
main.message("请设置年化天数");
return false;
}
if (this.trade.StructureType == "多空组合"&&!this.checkSwapRateList()) {
return false;
}
if (!this.checkObservationDates()) {
return false;
}
return true;
},
//观察日校验
checkObservationDates() {
var checkGet = true;
var thisObj = this;
if (thisObj.getSwapList != null) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (checkGet && val.SwapIntervalList.length > 0) {
checkGet = this.checkSwapTimeRates(val.SwapIntervalList, num + 1, '利息端');
}
});
}
if (!checkGet) {
return false;
}
if (thisObj.marginSwapList != null) {
thisObj.marginSwapList.forEach((val, num, arr) => {
if (checkGet && val.SwapIntervalList.length > 0) {
checkGet = this.checkSwapTimeRates(val.SwapIntervalList, num + 1, '预付金/预付金');
}
});
}
if (!checkGet) {
return false;
}
return true;
},
// 多空组合利息腿校验
checkSwapRateList() {
var thisObj = this;
var longInterestModelCount = 0;
var shortInterestModelCount = 0;
var check = true;
if (thisObj.getSwapList != null) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (val.InterestMode==7) {
longInterestModelCount++;
}
if (val.InterestMode == 8) {
shortInterestModelCount++;
}
});
}
if (longInterestModelCount > 1) {
check = false;
main.message("计息基本类型为多头存续名义本金的利息腿只能有一条");
}
if (shortInterestModelCount > 1) {
check = false;
main.message("计息基本类型为空头存续名义本金的利息腿只能有一条");
}
return check;
},
//观察日起始日期跟交易起始日期检查
checkSwapTimeRates(SwapIntervalList, num, title) {
var StartDate = this.trade.StartDate;
var ExerciseDate = this.trade.ExerciseDate;
var check = true;
SwapIntervalList.forEach(item => {
var _date = "";
var m = new moment(item.Date);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
if (_date < StartDate) {
main.message(title + "第" + num + "行互换观察日不可以早于交易开始日");
check = false;
} else if (_date > ExerciseDate) {
main.message(title + "第" + num + "行互换观察日不可以晚于交易到期日");
check = false;
}
});
return check;
},
changeClient: function (client) {
this.trade.ClientId = client.id;
this.client = client;
//if (this.client.SwapTradeType == 0) {//非DMA客户
// this.trade.trade_extend.ExtendObj.FlowBookMode = 3;//流水簿记模式为重置
//} else {
// this.trade.trade_extend.ExtendObj.FlowBookMode = 2;//流水簿记模式为加权平均 暂定
//}
//this.initMarginRate();
},
initMarginRate() { //初始预付金 初始话
_that = this;
var swapType = this.trade.StructureType;
var UnderlyingCode = "";
if (swapType != "多空组合") {
swapType = "普通";
UnderlyingCode = this.paySwapList[0].UnderlyingCode;
}
if (page.isAdd && this.trade.ClientId != null && ((swapType == "普通" && UnderlyingCode != "") || this.trade.trade_swap.SwapType == "多空组合")) {
main.post("/swapTrade/GetInitMarginRate", { ClientId: this.trade.ClientId, Type: swapType, UnderlyingCode: UnderlyingCode, tradeDate: this.trade.TradeDate })
.done(function (resp) {
if (resp.obj > 0) {
_that.trade.trade_Initial_Margin.MarginValue = resp.obj
}
});
}
},
//交易审批
submit(status) {
var pop = '';
if (status === 'pass') {
pop = "确认通过审批?";
}
if (status === 'reject') {
pop = "确认拒绝?";
}
var confirmFunc = function (additionalProcessing) {
var pData = { tradeId: page.Trade.id, status: status, text: "" };
if (!main.isEmpty(additionalProcessing)) {
pData.additionalProcessing = additionalProcessing;
}
main.post("/processtradelog/UpdateTradeProcessLog", pData).done(
function (data) {
if (data.obj && data.obj.proccessType == "AdditionalProcessing") {
if (data.obj.type == "LackOfMoney") {
var htmlContent = `<div style="padding:10px">${data.obj.message}</div>`;
var lackMoneyConfirmLayer = main.open2("提示",
htmlContent,
{
area: ["430px", "175px"],
btn: ['交易特批', '取消'],
yes: function (index, layero) {
var layerIndex = lackMoneyConfirmLayer;
main.confirm("客户资金或授信不足,强制成交会导致本机构产生风险!要继续审批通过?", function () {
layer.close(layerIndex);
confirmFunc("LackOfMoney");
});
},
cancel: function (index, layero) {
if (window.parent && window.parent.reloadtrade) {
window.parent.reloadtrade();
}
(parent || window).layer.closeAll();
}
});
}
return;
}
(parent || window).main.message(data.msg);
if (page.generateAmendDoc) {
if (data.obj && data.obj.generateChangeSuccess) {
main.downloadFiles(data.obj.url);
try { window.parent.reloadtrade(); } catch (e) { }
}
}
if (page.needSendChangeEmail) {
if (data.obj && data.obj.generateChangeSuccess) {
main.confirm("确认发送变更确认书?",
function () {
main.post("/trade/SendChangeConfirmEmails", { tradeids: tradeId }).done(
function (data) {
if (data.success) {
main.message("发送成功");
} else {
main.message(data.msg);
}
try {
window.parent.reloadtrade();
} catch (e) {
}
});
});
} else {
try {
window.parent.reloadtrade();
} catch (e) {
}
}
}
else {
try { window.parent.reloadtrade(); } catch (e) { }
}
if (parent) {
parent.layer.closeAll();
}
});
}
main.confirm(pop, confirmFunc);
},
//初始化利息端列表
initSwapRateList() {
var thisObj = this;
thisObj.getSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 1 || x.InterestMode == 2 || x.InterestMode == 7 || x.InterestMode == 8 || x.InterestMode == 9)) return x; });
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
arr[num].category_tag = arr[num].category_tag || '互换利率';
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
thisObj.marginSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 5 || x.InterestMode == 6)) return x; });
thisObj.marginSwapList.forEach((val, num, arr) => {
arr[num].index = 1000 + num; // 保证金列表使用 1000+ 偏移,避免与利息腿冲突
arr[num].HappenDate = thisObj.formatDate(arr[num].HappenDate);
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
if (thisObj.trade.StructureType == '多空组合') {
thisObj.paySwapList = [];
} else {
thisObj.paySwapList = thisObj.trade.swap_positions.filter(x => { if (x.UnderlyingCode != null && x.UnderlyingCode.length != 0 && x.IsInitial) return x; });
thisObj.paySwapList.forEach((val, num, arr) => {
arr[num].index = num;
arr[num].PosiFeeType = thisObj.normalizePosiFeeType(arr[num].PosiFeeType);
this.StockEqvNotional = val.ContractSize * val.PosiQuantity * val.PosiGrossPrice;
// D2 修复:重开(审批重开/刷新)已保存的债券成交单时,三字段互算的手动标志随页面重置而丢失;
// 若不锁,用户一旦编辑任一价格字段就会以它为源重新反算、覆盖当初保存的其他两格。
// 故在加载路径对"债券且三字段齐全(净价/全价/收益率均有值)"的标的,直接把三格标为手动锁定,
// 计算器在重开期间不自动推导;用户点"重算"才清除并重新计算。
var hasV = function (v) { return v !== null && v !== undefined && v !== '' && !isNaN(Number(v)); };
var isBond = val.isBond || tradeHelper.IsBond(val.UnderlyingInstrumentType);
if (isBond && hasV(val.PosiNetNoFeePrice) && hasV(val.PosiGrossPrice) && hasV(val.InitYtm)) {
thisObj.$set(arr[num], 'bondManual', { CP: true, DP: true, YD: true });
}
});
if (thisObj.paySwapList.length > 0) {
thisObj.syncPosiFeeModeByItem(thisObj.paySwapList[0]);
}
}
if (thisObj.paySwapList.length == 0) {
thisObj.trade.trade_extend.ExtendObj.Direction = thisObj.trade.trade_extend.ExtendObj.Direction == 0 ? 2 : thisObj.trade.trade_extend.ExtendObj.Direction;
var direction = thisObj.trade.trade_extend.ExtendObj.Direction;
thisObj.addSwapFloat(direction);
}
if (thisObj.getSwapList.length == 0) {
thisObj.addGetSwapRate();
}
//if (thisObj.marginSwapList.length == 0) {
// thisObj.addMarginSwapRate();
//}
},
//利息端添加行
addGetSwapRate() {
var InterestMode = 9;
var thisObj = this;
if (thisObj.trade.StructureType == '多空组合') {
InterestMode = 1;
}
var getSwap = {
index: thisObj.getSwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: 0,//浮动收支方式
PositionType: 0,//多空方向 1long2short
UnderlyingCode: "",//标的代码
UnderlyingInstrumentType: "",
CountRatio: 0,//乘积因子
ContractSize: 0,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFee: 0,//交易费用
PosiTradingFeeUnit: 0,//单位交易费用
InterestDirection: 1,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: InterestMode,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 1,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息,
IsAnnualized: true,//是否年化
HappenDate: null,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
interest_rule: null,//利率准则
category_tag: '互换利率'//类别
}
thisObj.getSwapList.push(getSwap);
},
//预付金添加行
addMarginSwapRate() {
var thisObj = this;
var getSwap = {
index: thisObj.marginSwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: 0,//浮动收支方式
PositionType: 0,//多空方向 1long2short
UnderlyingCode: "",//标的代码
UnderlyingInstrumentType:"",
CountRatio: 0,//乘积因子
ContractSize: 0,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFee: 0,//交易费用
PosiTradingFeeUnit: 0,//单位交易费用
InterestDirection: 1,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: 5,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 0,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息,
IsAnnualized: true,//是否年化,
HappenDate: thisObj.trade.TradeDate,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
interest_rule: null//利率准则
}
thisObj.marginSwapList.push(getSwap);
},
//浮动端添加行
addSwapFloat(direction) {
var thisObj = this;
var petSwap = {
index: thisObj.paySwapList.length,//利息序号,做删除以及观察日用
id: 0,
PosiDirection: direction,//浮动收支方式
PositionType: 1,//多空方向 1long2short
UnderlyingCode: "",//标的代码
underlying: {
UnderlyingCode: '',
UnderlyingName: '',
UnderlyingIssuer: '',
IssueSize: '',
MaturityDate: '',
Price: '',
UnderlyingInstrumentType: '',
QuoteUnitString: ''
},//标的信息
UnderlyingInstrumentType: "",
CountRatio: 1,//乘积因子
ContractSize: 1,//合约乘数
PosiNetPrice: 0,//期初标的价格含费
PosiGrossPrice: 0,//期初标的价格不含费
PosiNetFeePrice: 0,//净价含费
PosiNetNoFeePrice: 0,//净价不含费
PosiQuantity: 0,//持仓数量
PosiTradingFee: 0,//交易费用
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFeeUnit: 0,//单位交易费用
PosiFeeType: thisObj.getCurrentPosiFeeType(),//单位交易费用模式
InterestDirection: 0,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: 0,//计息基本类型
InterestPrincipalFix: 0,//计息基准
FloatRate: 0,// 浮动利率
FloatRateUnderlyingCode: "",//浮动利率标的
InterestType: 0,//计息方式 0 单利,1:复利
InterestSwapInterval: "",//观察间隔
SwapIntervalList: [],//观察间隔集合
observation: null,//观察信息
IsAnnualized: false,//是否年化
HappenDate: null,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
interest_rule: null//利率准则
}
thisObj.paySwapList.push(petSwap);
},
//利息端删除行
deleteSwapRate(index) {
var thisObj = this;
thisObj.getSwapList.splice(index, 1);
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
//利息端删除行
deleteMarginSwapRate(index) {
var thisObj = this;
thisObj.marginSwapList.splice(index, 1);
thisObj.marginSwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
//浮动端删除行
deleteSwapFloat(index) {
var thisObj = this;
thisObj.paySwapList.splice(index, 1);
thisObj.paySwapList.forEach((val, num, arr) => {
arr[num].index = num;
});
},
formatDate(time, formatStr) {
if (!formatStr) {
formatStr = "YYYY-MM-DD";
}
var momDate = new moment(time);
if (!momDate.isValid() || typeof time === "undefined") return "/";
return momDate.format(formatStr);
},
showUnderlyingInfo(item) {
var underlying = item.underlying;
var html = '<div class="row no-gutters">'
+ '<ul> '
+ '<li class="font16" ><span class="font_title">' + underlying.UnderlyingCode + '</span><span class="font_desc"> ' + underlying.UnderlyingName + ' </span>'
+ '<a href="javascript:void(0)" onclick="hideUnderlyingInfo(' + item.index + ')"><span class="glyphicon glyphicon-remove" style="color:#ff0000"></span></a></li>'
+ '<li class="font_desc"> 发行人:' + underlying.UnderlyingIssuer + '</li>'
+ '<li class="font_desc">面额:' + underlying.Price + '元</li>'
+ '<li class="font_desc">债券规模:' + (underlying.IssueSize ? underlying.IssueSize : '') + '亿</li>'
+ '<li class="font_desc">到期日:' + this.formatDate(underlying.MaturityDate, 'YYYY年MM月DD日') + '</li>'
+ '</ul>'
+ '</div>'
$(".un").popover('show');
$(".popover-body").html(html);
//$(".un").popover('show');
},
refreshDatepicker() {
var thisObj = this;
var startDate = thisObj.trade.StartDate;
var endDate = thisObj.trade.ExerciseDate;
if (this.$refs.happenDateRefs) {
for (let i = 0; i < this.$refs.happenDateRefs.length; i++) {
this.$refs.happenDateRefs[i].refresh(startDate, endDate);
}
}
this.$refs.observationStartRefs.refresh(startDate, endDate);
},
IsBond(instType) {
return tradeHelper.IsBond(instType);
}
},
computed: {
maxTradeDate() {
let exerciseDate = this.trade.ExerciseDate;
return exerciseDate && exerciseDate <= page.valuedate ? exerciseDate : page.valuedate;
},
},
components: {
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput(),
'vue-underlying-nonbond': vueUnderlyingNonBond(),
'vue-underlying-bond': vueUnderlyingBond(),
'vue-underlying-rate': vueUnderlyingRate()
},
destroyed() {
}
});
function hideUnderlyingInfo(index) {
$(".un").popover('hide');
}
function __init(vue) {
//交易员
if (page.canAddNewTrader) {
autoTrader = FastVue.autocomplete(document.getElementById('TraderId'), {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'], lookup: consTraders,
onSelect(data) {
vue.trade.TraderId = data.id;
vue.trade.TraderName = data.Name;
}
});
let traderId = page.Trade.TraderId;
let trader = traderId ? consTraders.find(x => x.id === traderId) : null;
if (!trader && page.IsNew) {
trader = consTraders[0];
trader && (vue.trade.TraderId = trader.id);
}
autoTrader.setData(trader);
}
//簿记账户
let autoAssetUnit = FastVue.autocomplete(document.getElementById('AssetId'), {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'], lookup: consAssetUnits,
onSelect(data) {
vue.trade.AssetId = data.id;
}
});
let assetId = page.Trade.AssetId;
let asset = assetId ? consAssetUnits.find(x => x.id === assetId) : null;
if (!asset && page.IsNew) {
asset = consAssetUnits[0];
asset && (vue.trade.AssetId = asset.id);
}
autoAssetUnit.setData(asset);
//客户名称
if (page.canChangeClient) {
let autoClient = FastVue.autocomplete(document.getElementById('ClientId'), {
nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'],
lookup: consClients, onSelect: function (rep) {
vue.changeClient(rep);
_getMainProtocolCode(rep);
}
});
let clientId = page.Trade.ClientId;
let client = clientId ? consClients.find(x => x.id === clientId) : null;
!client && page.IsNew && (client = consClients[0]);
autoClient.setData(client);
_getMainProtocolCode(client);
}
}
function _getMainProtocolCode(client) {
$("#MainProtocolCode option").remove();
if (client) {
let clientId = typeof client === 'object' ? client.id : client;
main.post("/Client/getMainProtocolCodes", { clientId: clientId }, { async: false }).done(function (resp) {
var obj = $("#MainProtocolCode");
if (client.MainProtocolCode != null && client.MainProtocolCode != '') {
obj.append("<option >" + client.MainProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v.Text + "</option>");
})
});
}
$("#SupProtocolCode option").remove();
if ($("#MainProtocolCode").val()) {
main.post("/Client/getSideProtocols", { mainProtocol: $("#MainProtocolCode").val() }, { async: false }).done(function (resp) {
var obj = $("#SupProtocolCode");
if (client.SupProtocolCode != null && client.SupProtocolCode != '') {
obj.append("<option >" + client.SupProtocolCode + "</option>");
}
_.forEach(resp, (v) => {
obj.append("<option >" + v + "</option>");
})
});
}
}
var getObservationDatesSetting = vue.getObservationDatesSetting;
$(function () {
$(".un").popover({
html: true,
title: function () {
return "发行情况";
},
content: function () {
return "";
},
placement: "right"
});
$(".datepicker").change(function () {
var dateVal = $(this).val();
if (!dateVal) return;
dateVal = dateVal.replace(/\D/g, '').padStart(4, '0');
switch (dateVal.length) {
case 4:
dateVal = new Date().getFullYear() + dateVal; break;
case 6:
dateVal = '20' + dateVal; break;
case 8: break;
default:
dateVal = dateVal.length > 8 ? dateVal.substring(0, 8) : ''; break;
}
if (dateVal) {
dateVal = moment(dateVal).format('YYYY-MM-DD');
!/^\d+/.test(dateVal) && (dateVal = '');
}
$(this).datepicker("setDate", dateVal);
});
});