Files
zszq-trs/YLErpWeb/wwwroot/Scripts/app/pricing/structure_dz.js
T
hjhan a7babb95a4 fix(EQD-7049): 期权定价首腿预载默认标的失败不再弹"标的信息缺失"
- 现象(国联民生反馈):新建定价页第一次新增单腿即弹"标的信息缺失",
  接口返回 {success:false,obj:null,msg:"标的信息缺失"}
- 根因:首腿 initFlag='first' 挂载后自动预载默认类型标的(Stock/CommodityFutures,
  由 OtcConfig.StockFirst 决定),固收环境下该类型无"已上线且未到期"标的时
  后端 FirstOrDefault 落空返回 JsonError;1c19d2f7 的"三者皆空跳过"守卫
  挡不住这条带默认类型的请求
- 修复:首腿预载改走 preloadDefaultUnderlying,以 silent 调 updateUnderlying——
  main.post 传 {alertFn:$.noop, suppressError:true} 静默失败(仅 console.warn),
  标的留空待用户自选;用户主动切换类型/选标的的查询路径完全不变
- 注:changeInstrumentType 被 v-on:input 绑定(首参为选中值),不能加位置参数,
  故预载单独成方法
- 同步修改东莞定制版 structure_dz.js
- 验证:mock 后端返回 success:false,断言静默选项传递/用户路径不变/旧守卫保留/
  首腿接线,两文件 12 项全过;下拉套件 20 项回归全过
2026-08-27 15:21:20 +08:00

2663 lines
106 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
//客户选择组件
const vueClient = function () {
return {
props: ['value'],
data() {
return { current: { id: 0, Name: '' } };
},
mounted() {
var self = this;
var id = parseInt(this.value);
if (id) {
this.current = _.clone(ylotc.clients.find(x => x.id === id));
}
var width = 152;
for (var i = 0; i < ylotc.clients.length; i++) {
let ele = document.createElement('span')
ele.innerText = ylotc.clients[i].Name;
ele.style.fontSize = '14px';
document.documentElement.append(ele);
var charLength = ele.offsetWidth + 28;//滚动条
document.documentElement.removeChild(ele);
if (charLength > width) {
width = charLength
}
}
FastVue.autocomplete(this.$el, {
valueField: 'Name', searchField: ['Name', 'PinYin'], lookup: ylotc.clients, width: width,
onSelect(data) {
self.current = _.clone(data);
if (self.value !== data.id) {
self.value = data.id;
self.$emit('input', self.value);
}
$(self.$el).blur();
}
});
},
watch: {
value(val, old) {
if (val !== old) {
var id = parseInt(val);
if (id !== this.current.id) {
this.current = _.clone(ylotc.clients.find(x => x.id === id) || { id: 0, Name: '' });
}
$(this.$el).data('select', '').attr("placeholder", '');
}
}
},
destroyed() {
FastVue.autocomplete.dispose(this.$el);
},
template: '<input id="txtCurrentClient" type="text" v-model="current.Name" :title="current.Name" :data-IsCentralClearing="current.IsCentralClearing" :data-CentralClearingPaltform="current.CentralClearingPaltform" :data-TradingPaltform="current.TradingPaltform"/>'
};
};
//品种选择组件
const vueVariety = function () {
return {
props: ['variety'],
mounted() {
var self = this;
FastVue.autocomplete(this.$el, {
valueField: 'Name', searchField: ['Name', 'Code', 'PinYin'],
lookup: ylotc.varieties,
onSelect(data) {
if (self.variety !== data) {
self.$emit('change-variety', data);
}
}
});
},
destroyed() {
FastVue.autocomplete.dispose(this.$el);
},
template: '<input type="text" v-model="variety.Name" />'
};
};
//期权类型选择组件
const vueTradeType = function () {
return {
props: ['value'],
mounted() {
var self = this;
FastVue.autocomplete(this.$el, {
valueField: 'value', searchField: ['value', 'pinyin'],
lookup: optionTradeTypes,
onSelect(data) {
if (self.value !== data.value) {
self.value = data.value;
self.$emit('input', self.value);
}
}
});
this.$el.value = this.value || '';
},
watch: {
value(val, old) {
if (val !== old) {
$(this.$el).val(this.value).data('select', '').attr("placeholder", '');
}
}
},
destroyed() {
FastVue.autocomplete.dispose(this.$el);
},
template: '<input type="text" maxlength="20" />'
};
};
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
const vueUnderlying = function () {
const _suggestionTpl = _.template($('#underlyingSuggestionTpl').html());
// 标的缓存(同品种各实例共享):_cache 为最近一次服务端结果,_fresh 记录其对应的 品种|关键词,
// _seq 单调递增丢弃乱序/过期响应,_inflight 防同关键词重复请求(helper 收在函数内,避免全局绑定冲突)
const _cache = {};
const _fresh = {};
const _seq = {};
const _inflight = {};
function _fetch(varietyId, query, cb) {
var vid = varietyId || 0;
var q = query || '';
var key = vid + '|' + q;
if (_inflight[key]) return;
var seq = (_seq[vid] = (_seq[vid] || 0) + 1);
_inflight[key] = true;
var postData = {
FilterCode: q.toUpperCase(),
VarietyId: vid,
MaxShowLength: 20,
BlackLimit: 1,
UseForTrading: true,
IncludeMatured: true,
CheckLaunch: true
};
main.post('/frontdata/AjaxGetUnderlyingSelect', postData).done(function (res) {
delete _inflight[key];
if (_seq[vid] !== seq) return; // 已有更新的关键词发起请求,丢弃本响应
var arr = (res && (res.obj || res.data)) || [];
var norm = arr.map(function (x) {
return {
Code: x.Code,
Name: x.Name,
InstrumentType: x.InstrumentType,
VarietyId: x.VarietyId,
Disallow: !!x.Disallow,
IsCombined: !!x.IsSynthetic || !!x.IsBasket,
BlackWhiteState: x.BlackWhiteState || 0,
PinYin: x.PinYin || ''
};
});
_cache[vid] = norm;
_fresh[vid] = key;
cb && cb(norm, q);
}).fail(function () {
delete _inflight[key];
});
}
function _filter(list, query, varietyId) {
if (!query) return (list || []).slice(0, 20);
query = query.toUpperCase();
return (list || []).filter(function (x) {
if (varietyId && x.VarietyId !== varietyId) return false;
if (x.IsCombined) return false; // 与原逻辑一致:搜索时排除组合标的
return (x.Code && x.Code.toUpperCase().indexOf(query) !== -1)
|| (x.PinYin && x.PinYin.toUpperCase().indexOf(query) !== -1);
}).slice(0, 20);
}
return {
props: ['underlying'],
data() {
return { jqInput: null, autoctrl: null };
},
mounted() {
var self = this;
this.jqInput = $(this.$el).children(0);
// EQD-7049:预拉默认20条(当前品种),获得焦点时由插件自身的 onValueChange 呈现
_fetch(self.underlying.VarietyId, '');
this.autoctrl = FastVue.autocomplete(this.jqInput, {
valueField: 'Code',
lookup(query) {
var varietyId = self.underlying.VarietyId;
var vid = varietyId || 0;
var key = vid + '|' + (query || '');
if (_fresh[vid] === key) {
// 命中当前关键词的服务端结果:直接展示(服务端已按 StartsWith+品种/黑名单过滤,不再前端二次过滤)
return (_cache[vid] || []).slice(0, 20);
}
// 异步搜索。FastVue 包装的 lookup 只同步取返回值渲染,服务端结果到达后必须重新触发
// onValueChange 才会显示;重走 lookup 时命中上面的 _fresh 分支直接返回,不会循环请求
_fetch(varietyId, query, function (list, q) {
var inst = self.jqInput.autocomplete();
if (!inst || !inst.visible) return; // 下拉已关闭:留待下次获得焦点时呈现
if ((self.jqInput.val() || '').toLowerCase() !== q.toLowerCase()) return; // 输入已变化:等新关键词的响应
inst.onValueChange();
});
// 过渡兜底:服务端响应到达前用旧缓存按关键词过滤,避免搜索期间下拉空白
return _filter(_cache[vid] || [], query, varietyId);
},
onSelect(data) {
if (self.underlying !== data) {
self.$emit('change-underlying', data);
}
self.jqInput.blur();
},
formatResult(suggestion, currentValue) {
return _suggestionTpl(suggestion.data);
}
});
this.jqInput.val(this.underlying.Code);
},
methods: {
showInput() {
this.jqInput.show().focus().next().hide();
},
hideInput() {
this.jqInput.hide().next().show();
}
},
computed: {
showName() {
return (this.underlying.InstrumentType === 'Stock' || this.underlying.InstrumentType === 'StockIndex') ? this.underlying.Name : ' ';
}
},
destroyed() {
FastVue.autocomplete.dispose(this.jqInput);
},
template: '<div v-on:click="showInput"><input v-on:blur="hideInput" type="text" style="display:none" />' +
'<span>{{underlying.Code}}</span><span style="font-size:80%">{{showName}}</span></div>'
};
};
//预付金模板
const vueMarginTemplateName = function () {
return {
props: ['value'],
data() {
return { autoCtrl: null };
},
mounted() {
var self = this;
this.autoctrl = FastVue.autocomplete(this.$el, {
valueField: 'Value',
searchField: ['Value'],
lookup: ylotc.tradeMarginTemplateItems,
onSelect(data) {
if (self.value !== data.Value) {
self.value = data.Value;
self.$emit('input', self.value);
}
}
});
value = self.value || "系统默认";
this.autoctrl.setData({ Text: value, Value: value })
},
watch: {
value(newVal, oldVal) {
if (newVal !== oldVal) {
let data = ylotc.tradeMarginTemplateItems.find(x => x.Value === newVal);
this.autoctrl.setData(data || { Text: "系统默认", Value: "系统默认" })
}
}
},
destroyed() {
FastVue.autocomplete.dispose(this.$el);
},
template: '<input type="text" />'
};
}
//计算结果字段
const consCalcFields = Object.freeze(['TotalTradePrice', 'TotalDay1Pnl', 'TotalMargin', 'Pv', 'Delta', 'GammaCash', 'Vega', 'Theta', 'Rho']);
var _trades, _tradeVues, _salesCommissionCtrl;
//交易保存
const tradeSaver = (function () {
const needClient = !pageVue.ClientUsedForCalc;
function showTradeView(items) {
let item = items.shift();
if (!item) return;
let url = "/trade/tradeview?abstract=1&enid=" + item.EncryptId;
main.open("查看交易", url, {
end: function () {
showTradeView(items);
}
});
}
//保存
function _save() {
if (!_trades) return;
let AssetId = parseInt($('#AssetId').data('id'));
let TraderId = parseInt($('#TraderId').data('id'));
let ClientId = parseInt($('#ClientId').data('id'));
if (!AssetId) {
return main.alert("未设置簿记账户");
}
if (!TraderId) {
return main.alert("未设置交易员");
}
if (needClient && !ClientId) {
return main.alert("未设置客户");
}
let TradeNumber = $('#TradeNumber').val();
let AssetBookName = $('#AssetId').val();
let TraderName = $('#TraderId').val();
let ClientName = $('#ClientId').val();
let TradingPlace = $("#TradingPlace").val();
let ClearingAgency = $("#ClearingAgency").val();
let MainProtocolCode = $("#MainProtocolCode").val();
let SupProtocolCode = $("#SupProtocolCode").val();
let OpponentRole = $("#OpponentRole").val();
let InitialAdvance = parseFloat($("#InitialAdvance").val()) || 0;
let PeriodAdvance = parseFloat($("#PeriodAdvance").val()) || 0;
let FontEarning = parseFloat($("#FontEarning").val()) || 0;
let ConfirmedLine = parseFloat($("#ConfirmedLine").val()) || 0;
let ConfirmedFloor = parseFloat($("#ConfirmedFloor").val()) || 0;
let IsCentralClearing = $("#IsCentralClearing").val();
let CentralClearingPaltform = $("#CentralClearingPaltform").val();
let TradingPaltform = $("#TradingPaltform").val();
let QuoteCurrency = $("#QuoteCurrency").val();
let salesCommission = _salesCommissionCtrl.getValue();
_trades.forEach(x => {
x.AssetId = AssetId;
x.AssetBookName = AssetBookName;
x.TraderId = TraderId;
x.TraderName = TraderName;
x.TradeNumber = TradeNumber;
x.SalesCommission = salesCommission;
if (needClient) {
x.ClientId = ClientId;
x.ClientName = ClientName;
}
if (x.TradePremium) {
x.MetaDic["交易溢价"] = x.TradePremium;
}
x.MetaDic["交易场所"] = TradingPlace;
x.MetaDic["清算机构"] = ClearingAgency;
x.MetaDic["主协议编号"] = MainProtocolCode;
x.MetaDic["补充协议编号"] = SupProtocolCode;
x.MetaDic["中央对手方清算"] = IsCentralClearing;
x.MetaDic["中央清算平台"] = CentralClearingPaltform;
x.MetaDic["交易平台"] = TradingPaltform;
if (pageData.showCCR) {
//if (x.MarginType == 0) {
x.MetaDic["ccr_k"] = x.ccr_k || "0";
//}
x.MetaDic["ignoreRiskExposure"] = x.ignoreRiskExposure || "0";
}
x.TradeType === '现金流交易' && tradeUtils.resetCashFlow(x);
x.OpponentRole = OpponentRole;
x.QuoteCurrency = QuoteCurrency;
x.InitialAdvance = InitialAdvance;
x.PeriodAdvance = PeriodAdvance;
x.FontEarning = FontEarning;
x.ConfirmedLine = ConfirmedLine;
x.ConfirmedFloor = ConfirmedFloor;
x.IsUsePremiumRate = (x.IsUsePremiumRate == "" || x.IsUsePremiumRate == null) ? false : x.IsUsePremiumRate;
});
main.post("/pricing/AjaxSaveTrades", { trades: _trades }).done(function (resp) {
if (pageVue.IsImport) {
let url = "#/trade/tradeview?abstract=1&enid=" + resp.obj[0].EncryptId;
layer.closeMe(url);//必须以#开头
} else {
floatVue.hide();
$('#modalTradeSave').modal('hide');
showTradeView(resp.obj);
_.each(_tradeVues, x => x.remove(true));
}
});
}
//保存
function _saveTrades(tradeVues) {
_tradeVues = tradeVues;
if (!Array.isArray(_tradeVues)) {
throw "参数错误:tradeVues";
}
if (!_tradeVues.length) {
return main.alert("请至少勾选一条交易");
}
_trades = _.flatMap(_tradeVues, x => x.datas).map(x => {
x.viewState.synthetic && (x.trade.MetaDic["组合标的"] = JSON.stringify(x.viewState.synthetic));
return x.trade;
});
_trades = tradeUtils.prepareTrades(_trades, pageVue.getVolType(), true);
if (_trades) {
$('#TradeNumber').val('').parent().toggle(_tradeVues.length === 1);
$('#modalTradeSave').modal('show');
_setSalesman();
if (pageVue.SecuritiesEnvironment && !pageVue.ClientUsedForCalc) {
var clientId = parseInt($('#ClientId').data('id')) || _trades[0].ClientId;
_getMainProtocolCode(clientId);
}
return true;
}
return false;
}
function _setAutocomplete(elId, datas, initData, onSelectFn, width) {
initData = initData || datas[0] || {};
FastVue.autocomplete(document.getElementById(elId), {
valueField: 'Name', searchField: ['Name', 'PinYin'], lookup: datas, width: width,
onSelect(data) {
$('#' + elId).data('id', data.id);
if (elId === "ClientId") {
if (!pageVue.ClientUsedForCalc) {
$("#IsCentralClearing").val(data.IsCentralClearing);
$("#CentralClearingPaltform").val(data.CentralClearingPaltform);
$("#TradingPaltform").val(data.TradingPaltform);
}
_setSalesman();
}
onSelectFn && onSelectFn(data);
},
zIndex: 300000
}).setData(initData);
$('#' + elId).data('id', initData.id);
}
//保存组合交易
function _saveGroupTrades(tradeVues) {
_tradeVues = tradeVues;
if (!Array.isArray(_tradeVues)) {
throw "参数错误:tradeVues";
}
if (!_tradeVues.length) {
return main.alert("请至少勾选一条交易");
}
_trades = _.flatMap(_tradeVues, x => x.datas).map(x => {
x.viewState.synthetic && (x.trade.MetaDic["组合标的"] = JSON.stringify(x.viewState.synthetic));
return x.trade;
});
var viewStates = _.flatMap(_tradeVues, x => x.datas).map(x => x.viewState).filter(x => x.variety && x.variety.Code);
//验证分组交易是否是同标的同期初价格
var underlyingId = 0;
var hasDiffUnderlying = false;
var spotPrice = 0;
var hasDiffPrice = false;
var tradeDate = null;
var exerciseDate = null;
var hasDiffTradeDate = false;
var buySell = groupVue.trade.BuySell;
var tradePrice = 0;
_trades.forEach(x => {
if (x.TradeType != "现金流交易") {
if (underlyingId && underlyingId != x.UnderlyingId) {
hasDiffUnderlying = true;
}
else {
underlyingId = x.UnderlyingId;
}
if (spotPrice && parseFloat(spotPrice) != parseFloat(x.SpotPrice)) {
hasDiffPrice = true;
}
else {
spotPrice = x.SpotPrice;
}
if (!buySell) {
buySell = x.BuySell;
}
}
tradePrice += x.TradePrice * (x.BuySell == "卖出" ? 1 : -1);
if (tradeDate != null && tradeDate != x.TradeDate) {
hasDiffTradeDate = true;
}
else {
tradeDate = x.TradeDate;
}
if (exerciseDate == null || exerciseDate < x.ExerciseDate) {
exerciseDate = x.ExerciseDate;
}
});
if (hasDiffUnderlying) {
main.alert("分组交易必须要保持相同的标的");
return false;
}
if (hasDiffPrice) {
main.alert("分组交易必须要保持相同的期初价格");
return false;
}
if (hasDiffTradeDate) {
main.alert("分组交易必须要保持相同的交易日期");
return false;
}
_trades = tradeUtils.prepareTrades(_trades, pageVue.getVolType(), true);
if (_trades) {
$('#TradeNumber').val('').parent().toggle(_tradeVues.length === 1);
groupVue.viewState = viewStates[0];
groupVue.trade.TradeDate = tradeDate;
groupVue.trade.ExerciseDate = exerciseDate;
groupVue.trade.BuySell = buySell;
if (groupVue.trade.IsUsePremiumRate == null) {
groupVue.trade.IsUsePremiumRate = true
}
if (groupVue.trade.IsMoneynessOption == null) {
groupVue.trade.IsMoneynessOption = "是";
}
groupVue.trade.IsTradePricePayType = true;
groupVue.trade.TradePrice = tradePrice * (buySell == "卖出" ? 1 : -1);
groupVue.changeIsUsePremiumRate();
groupVue.trade.SpotPrice = spotPrice;
if (!groupVue.trade.StructureType) {
groupVue.trade.StructureType = "气囊结构";
groupVue.trade.OptionType = "看涨";
groupVue.trade.trade_airbag.Barrier = 0.8;
groupVue.trade.Strike = 1;
groupVue.trade.trade_airbag.NotKIParticipationRate = 1;
groupVue.trade.trade_airbag.KIParticipationRate = 1;
}
let stockEqvNotionalArrs = _trades.map((value, index, array) => {
return value.StockEqvNotional
});
groupVue.trade.StockEqvNotional = Math.max(...stockEqvNotionalArrs);
groupVue.changeStockEqvNotional();
$('#modalGroupTradeSave').modal('show');
_setSalesman();
return true;
}
return false;
}
return {
saveTrades: _saveTrades,
saveGroupTrades: _saveGroupTrades,
init() {
_setAutocomplete('AssetId', ylotc.assetunits);
if (pageVue.CanSelectTrader) {
let trader = pageVue.Trade.TraderName ? ylotc.traders.find(x => x.Name === pageVue.Trade.TraderName) : null;
if (!trader) {
trader = { Name: "" }
}
_setAutocomplete('TraderId', ylotc.traders, { Name: trader.Name });
}
if (!pageVue.ClientUsedForCalc) {
var width = 300;
for (var i = 0; i < ylotc.clients.length; i++) {
let ele = document.createElement('span')
ele.innerText = ylotc.clients[i].Name;
ele.style.fontSize = '14px';
document.documentElement.append(ele);
var charLength = ele.offsetWidth + 28;//滚动条
document.documentElement.removeChild(ele);
if (charLength > width) {
width = charLength
}
}
_setAutocomplete('ClientId', ylotc.clients, null, (pageVue.SecuritiesEnvironment ? _getMainProtocolCode : null), width);
}
//组合标的控件
synthenticPriceCtrl.init({
getSynthetic(input) {
return consSyntheticMap[input.dataset.calcid];
},
setSynthetic(input, synthetic) {
var calcid = input.dataset.calcid;
_.each(pricingVue.getTradeVues(), x => !x.updateSynthetic(calcid, synthetic));
}
});
//销售提成控件
_salesCommissionCtrl = new SalesCommissionCtrl(document.getElementById('salesCommissionCtrl'), pageVue.SalesCommission);
_setSalesman();
$('#btnSave').on('click', _save);
$('#MainProtocolCode').on('change', _changeMainProtocolCode);
$('#modalTradeSave').on('show.bs.modal', function () {
$('#TradeNumber').val('');
_salesCommissionCtrl.reset();
});
}
};
}());
//截图
const screenshoter = (function () {
var _layerIndex;
function execute(tradeVue) {
if (tradeVue == null) {
return;
}
if (!tradeVue.floating) {
floatVue.show(tradeVue, true);
}
var opts = { bgcolor: '#fff', width: tradeVue.datas.length * 200 + 140 };
var $fs = $('#for-screenshot');
$fs.parent().children().addClass('for-screenshot');
$fs.closest('.modal').css('margin-top', -999999);
domtoimage.toPng($fs[0], opts).then(function (dataUrl) {
$('#screenshot-img').attr('src', dataUrl);
_layerIndex = layer.open({
type: 1, closeBtn: 0, title: false, scrollbar: false, shadeClose: true,
area: ['auto', '50%'], content: $('#screenshot'), offset: '100px',
success: function (layero, index) {
setTimeout(function () {
let height = $('#screenshot-img').height();
height > 100 && layero.children('.layui-layer-content').css('height', height + 100);
}, 100);
}
});
try {
//chrome 定价页面生成截图时自动复制
const blobInput = convertBase64ToBlob(dataUrl.replace("data:image/png;base64,", ""), 'image/png');
const clipboardItemInput = new ClipboardItem({ 'image/png': blobInput });
navigator.clipboard.write([clipboardItemInput]);
}
catch (e) { }
}).catch(function (error) {
main.alert('截图发生错误:' + error);
}).finally(function () {
$fs.closest('.modal').css('margin-top', 0);
$fs.parent().children().removeClass('for-screenshot');
if (!tradeVue.floating) {
$('#floatModal').modal('hide');
}
});
}
function closeShow() {
layer.close(_layerIndex);
}
//保存截图
function download() {
domtoimage.toBlob(document.getElementById('screenshot-img'))
.then(function (blob) {
moment().format();
window.saveAs(blob, '报价' + moment().format('YYYYMMDDHHmmss') + '.png');
});
}
return {
execute: execute,
download: download,
closeShow: closeShow
};
}());
function convertBase64ToBlob(base64, type) {
var bytes = window.atob(base64);
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: type });
}
//#singleprice
const singlePricer = (function () {
const _sumDatas = [];
//datas:[{Pv,Notional}]
function _calcSinglePrice(datas) {
var result = _.reduce(datas, (acc, data) => {
if (Array.isArray(data)) {
_.each(data, item => {
acc.totalPv += parseFloat(item.Pv) || 0;
var notional = parseFloat(item.Notional) || 0;
if (acc.minNotional > notional) {
acc.minNotional = notional;
}
});
} else if (data) {
acc.totalPv += parseFloat(data.Pv) || 0;
var notional = parseFloat(data.Notional) || 0;
if (acc.minNotional > notional) {
acc.minNotional = notional;
}
}
return acc;
}, { totalPv: 0, minNotional: Number.MAX_SAFE_INTEGER });
var singlePrice = Math.abs(result.minNotional) < 1e-5 ? "0.00"
: pricingFormat.tradeSinglePrice(Math.abs(result.totalPv) / result.minNotional);
return pageVue.CompanyName + (result.totalPv >= 0 ? "付" : "收") + singlePrice;
}
//重设统计单价
function _reset() {
var singlePrice = _calcSinglePrice(_sumDatas);
$('#singleprice').text(singlePrice);
}
const _debounceReset = _.debounce(_reset, 300);
return {
//data:[{Pv,Notional}]
reset(index, data) {
_sumDatas[index] = !data || Array.isArray(data) ? data : [data];
_debounceReset();
},
//datas:[{Pv,Notional}]
calcSinglePrice: _calcSinglePrice
};
}());
//pageVue状态
(function () {
const viewState = { VolType: '交易', structureType: '' };
_.extend(pageVue, {
viewState: viewState,
getVolType() { return viewState.VolType; },
setVolType(value) { viewState.VolType = value; },
getStructureType() { return viewState.structureType; },
setStructureType(value) { viewState.structureType = value; }
});
//冻结修改
_.each([pageVue], x => Object.freeze(x));
}());
//一个简单的事件总线
const EventBus = (new function () {
var _bus;
function getBus() {
return _bus || (_bus = document.createElement('div'));
}
//增加事件监听
this.addEventListener = function (event, callback) {
getBus().addEventListener(event, callback);
};
//移除事件监听
this.removeEventListener = function (event, callback) {
getBus().removeEventListener(event, callback);
};
//激发事件
this.triggerEvent = function (event, detail = {}) {
getBus().dispatchEvent(new CustomEvent(event, { detail }));
};
//仅供调试用
this.getEventListeners = function () {
return getEventListeners(getBus());
};
}())
//顶部组件
const topVue = (function () {
return new Vue({
el: '#pricing-top',
data: {
viewState: pageVue.viewState,
simpleMode: pageVue.SimpleMode,
calcMargin: pageVue.CalcMargin && pageVue.ShowInitialMargin,
calcAutocallGreeks: pageVue.CalcAutocallGreeks,
},
mounted() {
},
methods: {
changeVolType() {
pricingVue.changeVolType();
},
addStructure(type, typeCn) {
pageVue.setStructureType(typeCn);
var url = "/trade/structureoptionV2?name=" + type;
main.open("添加策略", url, { area: ['500px', '800px'] });
},
normOtcTrade(td) {
td.PremiumPayDate = '';
td.Notional = pricingFormat.notional(td.Notional);
td.TradeAmount = pricingFormat.notional(td.TradeAmount);
td.TradePrice = pricingFormat.tradePrice(td.TradePrice);
td.Day1Pnl = pricingFormat.tradePrice(td.Day1Pnl);
td.StockEqvNotional = pricingFormat.StockEqvNotional(td.StockEqvNotional);
td.StockEqvNotionalReal = pricingFormat.StockEqvNotional(td.StockEqvNotionalReal);
tradeUtils.normalizeTrade(td);
return td;
},
importTrade() {
let tradeNumber = $('#importTradeNumber').val().trim();
if (!tradeNumber) return;
var self = this;
main.post('/pricing/AjaxGetOtcTradeFull?tradeNumber=' + encodeURIComponent(tradeNumber)).done(function (resp) {
if (resp.obj) {
pricingVue.addTrade(self.normOtcTrade(resp.obj));
} else {
main.alert('没有找到交易数据');
}
}).always(function () {
});
},
toggleSimpleMode() {
this.simpleMode = !this.simpleMode;
EventBus.triggerEvent('simpleMode.changed');
main.post("/sysuserConfig/AjaxSaveOtcWebConfig", { name: 'Pricing_SimpleMode', value: this.simpleMode }, false);
},
toggleCalcMargin() {
this.calcMargin = !this.calcMargin;
main.post("/sysuserConfig/AjaxSaveOtcWebConfig", { name: 'Pricing_CalcMargin', value: this.calcMargin }, false);
},
toggleCalcAutocallGreeks() {
this.calcAutocallGreeks = !this.calcAutocallGreeks;
main.post("/sysuserConfig/AjaxSaveOtcWebConfig", { name: 'Pricing_CalcAutocallGreeks', value: this.calcAutocallGreeks }, false);
}
}
})
}());
//统计组件
const summaryVue = (function () {
const _summaryDatas = [];
const summary = _.reduce(consCalcFields, (acc, cur) => {
acc[cur] = 0;
return acc;
}, {});
//重设统计合计
function _resetSummary() {
var datas = _.flatMap(_summaryDatas, x => x && x.length > 0 ? x : []);
var UnderlyingCode = '';
var isSameCode = _.every(datas, x => {
return UnderlyingCode ? x.UnderlyingCode === UnderlyingCode : UnderlyingCode = x.UnderlyingCode;
});
if (isSameCode) {
_.each(summary, (val, key) => {
summary[key] = 0;
});
_.each(datas, data =>
_.each(consCalcFields, field => summary[field] += parseFloat(data[field]) || 0)
);
} else {
_.each(summary, (val, key) => {
summary[key] = 'NaN';
});
summary.Pv = summary.TotalMargin = summary.TotalTradePrice = summary.TotalDay1Pnl = summary.Rho = 0;
_.each(datas, data => {
summary.Pv += parseFloat(data.Pv) || 0;
summary.Rho += parseFloat(data.Rho) || 0;
summary.TotalMargin += parseFloat(data.TotalMargin) || 0;
summary.TotalTradePrice += parseFloat(data.TotalTradePrice) || 0;
summary.TotalDay1Pnl += parseFloat(data.TotalDay1Pnl) || 0;
});
}
if (!pageVue.TwoSideMargin) {
//如果为负数,则显示0
summary.TotalMargin < 0 && (summary.TotalMargin = 0);
}
}
const _debounceReset = _.debounce(_resetSummary, 300);
const _vue = new Vue({
el: '#pricing-summary',
data: {
summary: summary
},
computed: {
DeltaHands() {
var delta = this.summary.DeltaInLots;
return typeof delta !== 'number' ? delta
: (delta > 0 ? "卖" : "买") + pricingFormat.greek(Math.abs(delta)) + '手';
}
},
filter: {
greekFmt(val) {
return typeof val !== 'number' ? val : pricingFormat.greek(val);
}
}
});
return {
getVue() {
return _vue;
},
//data:consCalcFields
reset(index, data) {
_summaryDatas[index] = !data || Array.isArray(data) ? data : [data];
_debounceReset();
}
};
}());
//视图呈现器
const renders = (function () {
function _getHtml($tpl, removeClass) {
var html = $tpl.html();
var $temp = $('<div>').append(html);
$temp.find(removeClass).remove();
$temp.find('template').each(function () {
html = _getHtml($(this), removeClass);
$(this).html(html);
});
return $temp.html();
}
//定价视图呈现器
const title = Vue.compile(_getHtml($('#pricingItem_tpl'), '.pitem,.pitem-cash'));
const item = Vue.compile(_getHtml($('#pricingItem_tpl'), '.ptitle,.pitem-cash:not(.pitem)'));
const itemCash = Vue.compile(_getHtml($('#pricingItem_tpl'), '.ptitle,.pitem:not(.pitem-cash)'));
const items = Vue.compile($('#pricingItems_tpl').html() || '');
return {
title(forStatic) {
return forStatic ? title.staticRenderFns : title.render;
},
item(forStatic) {
return forStatic ? item.staticRenderFns : item.render;
},
itemCash(forStatic) {
return forStatic ? itemCash.staticRenderFns : itemCash.render;
},
items(forStatic) {
return forStatic ? items.staticRenderFns : items.render;
}
};
}());
//单个定价组件
const vueTrade = function () {
return {
props: ['trade', 'viewState', 'calcResult', 'floating'],
data() {
return {
isTitle: false,
canEditEngineName: true,
...consVueTrade.data(),
fieldState: tradeFieldMgr.getFieldState(this.floating),
tradeMarginTemplates: ylotc.tradeMarginTemplates,
binaryPayoffTypes: this.trade.ExerciseMode === 'European' ?
consBinaryPayoffTypes.European : consBinaryPayoffTypes.American,
engineNames: tradeUtils.getEngineNames(this.trade)
};
},
watch: {
'trade.Notional': {
handler(newVal, oldVal) {
this.resetSummary(resetSummaryFlags.notional);
},
immediate: false
},
'trade.TradeType': {
handler(newVal, oldVal) {
this.fieldState.onTradeTypeChanged(this.trade, oldVal, this);
this.changeEngineName();
},
immediate: true
},
'trade.ExerciseDate': {
handler(newVal, oldVal) {
this.trade.SettlementDate = newVal;
this.changeIsAnnualized();
},
immediate: false
},
'trade.UnderlyingInstrumentType': {
handler(newVal, oldVal) {
this.fieldState.setFieldState('DividendRate', (newVal === 'Stock' || newVal === 'StockIndex'));
},
immediate: true
},
'trade.PayoffType': {
handler(newVal, oldVal) {
this.changeEngineName();
},
immediate: false
},
'trade.EnhancedPrice': {
handler(newVal, oldVal) {
this.changeEngineName();
},
immediate: true
},
'trade.StrikeType': {
handler(newVal, oldVal) {
this.changeEngineName();
},
immediate: false
}
},
created: consVueTrade.created,
mounted() {
consSyntheticMap[this.trade.CalcId] = this.viewState.synthetic;
if (pageVue.ClientUsedForCalc) {
if (!this.trade.ClientId) {
ylotc.clients.length && (this.trade.ClientId = ylotc.clients[0].id);
if (this.trade.ClientId) {
this.changeClient();
}
} else {
this.updateKey.client++;
}
}
this.$nextTick(function () {
switch (this.viewState.initFlag) {
case 'first':
this.viewState.initFlag = '';
this.preloadDefaultUnderlying();
break;
case 'import1':
case 'import2':
case 'template':
!this.floating && this.updateUnderlying({ UnderlyingCode: this.trade.UnderlyingCode });
break;
}
});
topVue.simpleMode && (this.trade.UnderlyingPrice = '');
!this.floating && EventBus.addEventListener('simpleMode.changed', this.onSimpleModeChanged);
},
methods: {
...consVueTrade.methods,
//变更客户
changeClient() {
this.$emit('change-client', this.trade.ClientId);
},
synchTrade(setInitialMarginNull) {
this.$emit('synch-trade', this.trade, setInitialMarginNull);
},
sumTotal() {
this.$emit('sum-total', this.trade);
},
//变更期权类型
changeTradeType() {
this.viewState.Observation.hasValue = false;
this.viewState.KOObservation.hasValue = false;
this.changeExerciseMode('TradeType');
this.refreshEngineNames();
if (this.trade.TradeType === "现金流交易") {
tradeUtils.resetCashFlow(this.trade);
}
},
// EQD-7049:首腿自动预载默认类型标的。固收等环境下默认类型(Stock/CommodityFutures)可能没有
// 已上线标的,后端必返回"标的信息缺失"——属可容忍场景,静默失败不弹窗,留待用户自选;
// 用户主动切换类型仍走 changeInstrumentType,查询失败正常提示
preloadDefaultUnderlying() {
if (new Date().getTime() < this.updateKey.underlying + 300) return;
let instType = this.trade.UnderlyingInstrumentType;
this.viewState.variety = tradeHelper.getEmptyVariety(instType);
this.updateUnderlying({ InstrumentType: instType }, false, true);
},
//变更标的类型
changeInstrumentType() {
if (new Date().getTime() < this.updateKey.underlying + 300) return;
let instType = this.trade.UnderlyingInstrumentType;
this.viewState.variety = tradeHelper.getEmptyVariety(instType);
this.updateUnderlying({ InstrumentType: instType });
this.synchTrade();
},
//变更标的品种
changeVariety(variety) {
this.viewState.variety = variety;
this.updateUnderlying({ VarietyId: variety.id });
this.synchTrade();
},
//变更标的
changeUnderlying(underlying) {
if (underlying.Disallow) {
if (underlying.IsCombined) {
main.alert(underlying.BlackWhiteState === 1 ? "该组合中包含存在于黑名单的标的" : "该组合中包含不在白名单中的标的");
}
else {
main.alert(underlying.BlackWhiteState === 1 ? "该标的存在于黑名单中" : "该标的不在白名单中");
return;
}
}
this.viewState.underlying = underlying;
this.updateUnderlying({ UnderlyingCode: underlying.Code }, true);
this.changeEffectRatio();
this.synchTrade();
},
//变更定价模型
changeEngineName() {
// 增强亚式期权,在执行价格和增强价格不一致的情况下,默认只支持使用蒙特卡洛计算引擎
if (tradeUtils.changeEngineName(this.trade)) {
this.canEditEngineName = false;
} else {
this.canEditEngineName = true;
}
},
//更新标的
updateUnderlying(reqData, fromSelect, silent) {
let self = this;
// EQD-7049:新建空白页未选标的/品种/类型时,跳过必然失败的后端默认标的查询,避免报“标的信息缺失”
var hasQueryKey = !!(reqData.UnderlyingCode || reqData.InstrumentType || reqData.VarietyId > 0);
if (!hasQueryKey) {
!fromSelect && (self.viewState.underlying = tradeHelper.getEmptyUnderlying());
return;
}
var instTypeChanged = !!reqData.InstrumentType;
!fromSelect && (self.viewState.underlying = tradeHelper.getEmptyUnderlying());
// silent:自动预载场景失败不弹窗(alertFn 置空+抑制网络错误提示),用户主动查询仍正常提示
var req = main.post("/pricing/AjaxGetUnderlying", reqData, silent ? { alertFn: $.noop, suppressError: true } : undefined);
silent && req.fail(function (resp) { console.warn('预载默认标的失败(已忽略):', resp && resp.msg); });
req.done(function (resp) {
let trade = self.trade;
let um = resp.obj.underlying;
if (!fromSelect) {
self.viewState.underlying = {
id: um.id, Code: um.UnderlyingCode, Name: um.UnderlyingName,
InstrumentType: um.UnderlyingInstrumentType, VarietyId: um.UnderlyingTypeId,
QuoteUnitString: um.QuoteUnitString
};
}
self.updateKey.underlying = new Date().getTime();
instTypeChanged || (instTypeChanged = trade.UnderlyingInstrumentType !== um.UnderlyingInstrumentType);
trade.UnderlyingId = um.id;
trade.UnderlyingCode = um.UnderlyingCode;
trade.UnderlyingName = um.UnderlyingName;
trade.UnderlyingInstrumentType = um.UnderlyingInstrumentType;
trade.VarietyId = um.UnderlyingTypeId;
trade.CountRatio = um.CountRatio;
self.viewState.variety = ylotc.varieties.find(x => x.id === um.UnderlyingTypeId)
|| tradeHelper.getEmptyVariety(trade.UnderlyingInstrumentType);
self.updateKey.variety = new Date().getTime();
if (self.viewState.initFlag === 'import1') {
self.getTTM(false, false);
return self.viewState.initFlag = '';
}
if (self.viewState.initFlag === 'template') {
return self.viewState.initFlag = '';
}
trade.DividendRate = um.DividendRate;
self.viewState.synthetic = consSyntheticMap[trade.CalcId] = resp.obj.synthetic;
//初始预付金
trade.InitialMargin = 0;
//加载结构化交易
if (self.viewState.initFlag === 'import2') {
trade.SettlementDate = trade.ExerciseDate;
(trade.TradeOpenVolatility !== '') && (self.viewState.VolState = 'user');
(trade.TradeCloseVolatility !== '') && (self.viewState.CloseVolState = 'user');
self.changeTradeDate('import2');
if (trade.PremiumRate !== '' || trade.TradeSinglePrice !== '') {
trade.PremiumRate !== '' ? self.changePremiumRate() : self.changeTradeSinglePrice();
} else {
trade.PremiumRate = trade.TradeSinglePrice = trade.TradePrice = 0;
}
return self.viewState.initFlag = '';
}
//重置权利金
trade.PremiumRate = trade.TradeSinglePrice = trade.TradePrice = 0;
trade.SpotPrice = pricingFormat.umprice(um.Price);
//重置视图状态
_.each(tradeFieldMgr.defaultData.viewState, (val, key) => {
val === 'system' && (self.viewState[key] = "system");
});
//根据标的类型切换,权益类默认名义本金成交方式
if (um.UnderlyingInstrumentType === "Stock" || um.UnderlyingInstrumentType === "StockIndex") {
trade.IsUsePremiumRate = true;
//名义本金影响交易数量
self.changeStockEqvNotional(1e6);
if (trade.IsMoneynessOption !== "是" || !trade.Strike) {
trade.Strike = pricingFormat.umprice(um.Price);
self.showPercentStrike();
instTypeChanged = true;
}
} else {
trade.IsUsePremiumRate = false;
self.changeTradeAmount(instTypeChanged ? 1 : null);
if (trade.IsMoneynessOption === "是" || !trade.Strike) {
trade.Strike = "1";
self.showAbsStrike();
instTypeChanged = true;
}
}
//如果资产大类变更则清除掉IsMoneynessOption和IsUsePremiumRate关联的字段值
instTypeChanged && tradeUtils.onChangeInstrumentType(trade);
//到期日ExerciseDate默认为1个月之后,股票到期日不显示
trade.TradeDate = pageVue.SysDate;
trade.ExerciseDate = new moment(um.ExerciseDate).format("YYYY-MM-DD");
trade.MaturityDate = new moment(um.MaturityDate || "2029-01-01").format("YYYY-MM-DD");
trade.TradeDate > trade.ExerciseDate && (trade.TradeDate = trade.ExerciseDate);
self.changeTradeDate(true);
if (pageData.is厦门象屿) {
trade.PremiumPayDate = trade.ExerciseDate;
}
//重置计算结果
self.calcResult.reset();
if (fromSelect) {
self.resetSummary(resetSummaryFlags.all);
}
});
},
//刷新波动率,影响波动率的属性:SpotPrice,Strike,TradeDate,ExerciseDate,UnderlyingId
getVol(debounce) {
if (this.trade.TradeType === "现金流交易") return;
if (pageVue.IsTradeVol) {
let flag = 0;
if (!pageVue.SkewMapVol) {
this.viewState.VolState !== 'user' && (flag |= 1);
this.viewState.CloseVolState !== 'user' && (flag |= 2);
if (flag === 0) return;
} else {
flag = 1 | 2;
}
if (debounce === true) {
if (!this.debounceGetVol) {
this.debounceGetVol = _.debounce(function (vue) {
(flag & 1) > 0 && vue.getTradeOpenVolatility();
(flag & 2) > 0 && vue.getTradeCloseVolatility();
}, 1000, { trailing: true });
}
this.debounceGetVol(this);
} else {
(flag & 1) > 0 && this.getTradeOpenVolatility();
(flag & 2) > 0 && this.getTradeCloseVolatility();
}
} else if (this.viewState.VolState !== 'user') {
this.getTradeOpenVolatility();
this.getTradeMidVolatility();
}
this.synchTrade();
},
//重置统计数据
resetSummary(flag) {
this.$emit('reset-summary', flag);
},
//获取标的价格(计算用)
getUnderlyingPrice() {
var self = this;
return main.post("/pricing/AjaxGetUnderlyingPrice", { underlyingCode: this.trade.UnderlyingCode, tradeDate: this.trade.ValueDate })
.done(function (resp) {
self.trade.UnderlyingPrice = pricingFormat.umprice(resp.obj.price);
self.synchTrade();
});
},
//精简模式切换时的处理
onSimpleModeChanged() {
this.trade.UnderlyingPrice = '';
}
},
computed: { ...consVueTrade.computed },
components: {
'vue-margintemplatename': vueMarginTemplateName(),
'vue-niceselect': FastVue.vueNiceSelect(),
'vue-variety': vueVariety(),
'vue-tradetype': vueTradeType(),
'vue-underlying': vueUnderlying(),
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput(),
'vue-daycount': vueDayCount(),
'vue-client': vueClient(),
},
render: renders.item(),
staticRenderFns: renders.item(true),
destroyed() {
delete consSyntheticMap[this.trade.calcId];
topVue.simpleMode && (this.trade.UnderlyingPrice = '');
EventBus.removeEventListener('simpleMode.changed', this.onSimpleModeChanged);
}
};
};
//单个定价组件(现金流)
const vueTrade2 = function () {
return {
props: ['trade', 'viewState', 'calcResult', 'floating'],
data() {
return {
isTitle: false,
...consVueTrade.data(),
fieldState: tradeFieldMgr.getFieldState(this.floating),
};
},
watch: {
'trade.ExerciseDate': {
handler(newVal, oldVal) {
this.trade.SettlementDate = newVal;
},
immediate: false
}
},
mounted() {
if (pageVue.ClientUsedForCalc) {
if (!this.trade.ClientId) {
ylotc.clients.length && (this.trade.ClientId = ylotc.clients[0].id);
if (this.trade.ClientId) {
this.changeClient();
}
} else {
this.updateKey.client++;
}
}
this.fieldState.onTradeTypeChanged(this.trade, null, this);
this.trade.IsUsePremiumRate = true;
},
methods: {
...consVueTrade.methods,
//变更客户
changeClient() {
this.$emit('change-client', this.trade.ClientId);
},
synchTrade() {
this.$emit('synch-trade', this.trade);
},
//重置统计数据
resetSummary(flag) {
this.$emit('reset-summary', flag);
}
},
computed: { ...consVueTrade.computed },
components: {
'vue-niceselect': FastVue.vueNiceSelect(),
'vue-client': vueClient(),
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput(),
'vue-daycount': vueDayCount(),
},
render: renders.itemCash(),
staticRenderFns: renders.itemCash(true),
destroyed() {
}
};
};
//主视图定价头部拖动处理
const mainDragScroll = dragScroll.create({ container: '.pricing-main', yscroll: false, throttle: _.throttle });
//定价组件组合
function createVue(index, baseVue, floating) {
var datas = baseVue.datas;
var fromLocal = baseVue.fromLocal === true;
if (datas.length < 1) {
return main.alert('缺少数据');
}
var structureType = baseVue.combining && datas.length > 1 ? '结构化交易' : baseVue.structureType || '';
_.each(datas, (data, index2) => {
if (index2 > 0) {
data.trade.hideCommen = true;
}
data.trade.CalcId = index + '-' + (index2 + 1);
data.trade.ParentIndex = index;
if (!data.calcResult) {
data.calcResult = {
sourceData: null,
reset(data) {
this.sourceData = data;
if (!data) {
_.each(consCalcFields, x => this[x] = '');
}
}
};
_.each(consCalcFields, x => data.calcResult[x] = '');
} else if (baseVue.combining) {
data.calcResult = _.cloneDeep(data.calcResult);
}
if (!data.trade.UnderlyingInstrumentType) {
data.trade.UnderlyingInstrumentType = pageVue.StockFirst ? 'Stock' : 'CommodityFutures';
}
data.trade.StructureType = structureType;
});
if (datas.length === 1) {
datas[0].trade.CalcId = index.toString();
}
var debounceFloatSinglePrice = _.debounce(singlePricer.calcSinglePrice, 300);
var vue = new Vue({
data: {
index: index,
datas: datas,
floating: floating,
isSelected: true,
structureType: structureType,
showFloatingIcon: !structureType || datas.every(x => x.trade.TradeType === '香草期权')
},
computed: {
},
mounted() {
if (this.floating) {
var singlePrice = singlePricer.calcSinglePrice(this.convertForSinglePrice());
singlePrice && $('#singleprice2').text(singlePrice);
} else {
pricingVue.onMountd(this);
}
},
methods: {
getData(index) {
return this.datas[index || 0];
},
getVol() {
_.each(this.$children, x => x.getVol());
},
remove(blRemoveChain) {
this.$destroy();
this.$el.remove();
pricingVue.remove(this, blRemoveChain);
},
saveTrade() {
tradeSaver.saveTrades([this]);
},
showFloatVue() {
floatVue.show(this);
let td = this.getData(0);
setTimeout(function () {//交易详情页-定价增强价格不显示问题
if (td && td.trade.TradeType == "亚式期权" && td.trade.StrikeType != "Floating" && td.trade.PayoffType == 'EnhancedArithmeticAverage') {
$("#EnhancedPriceTitle").show();
}
}, 500);
},
screenshot() {
screenshoter.execute(this);
},
uncouple() {
pricingVue.uncouple(this);
},
convertForSinglePrice() {
return _.reduce(this.datas, (result, data) => {
var sdata = data.calcResult.sourceData;
sdata && (result || (result = [])).push({ Pv: sdata.Pv, Notional: data.trade.Notional });
return result;
}, null);
},
//重置统计数据
resetSummary(flag) {
//浮窗页面
if (this.floating && flag < resetSummaryFlags.ten) {
var singlePrice = singlePricer.calcSinglePrice(this.convertForSinglePrice());
singlePrice && $('#singleprice2').text(singlePrice);
}
//主页面
if (this.isSelected) {
var data = this.datas[this.datas.length - 1];
data.calcResult.TotalTradePrice = pageVue.GetTotalTradePrice(_.map(this.datas, x => x.trade));
data.calcResult.TotalDay1Pnl = pageVue.GetTotalDay1Pnl(_.map(this.datas, x => x.trade));
if (flag === resetSummaryFlags.initialMargin) {
data.calcResult.TotalMargin = pageVue.GetTotalMargin(_.map(this.datas, x => x.trade), this.structureType);
return summaryVue.reset(this.index, this.datas.map(x => x.calcResult));
}
singlePricer.reset(this.index, this.convertForSinglePrice());
if (flag !== resetSummaryFlags.notional) {
summaryVue.reset(this.index, this.datas.map(x => x.calcResult));
}
}
else if (flag === true) {
summaryVue.reset(this.index, null);
singlePricer.reset(this.index, null);
}
},
setCalcResult(result) {
var data = _.find(this.datas, x => x.trade.CalcId === result.CalcId);
if (!data) {
return main.alert('系统错误');
}
var trade = data.trade;
if (topVue.calcMargin && data.viewState.InitialMargin !== 'user') {
if (result.hasInitialMargin) {
trade.InitialMargin = parseFloat(result.initialMargin) + result.AccurateTradePrice * (trade.BuySell == "买入" ? -1 : 1);
} else {
trade.InitialMargin = 0;
}
}
var calcResult = data.calcResult;
var contractSize = result.contractSize;
trade.TTMDays = consNumberFormat.ttmDaysFmt(result.TTMDays);
trade.Day1Pnl = pricingFormat.tradePrice(result.Day1Pnl);
//对象缩小为具体计算结果
result = result.calcResult;
calcResult.reset(result);
//用于统计
calcResult.UnderlyingCode = trade.UnderlyingCode;
if (trade.IsUsePremiumRate) {
trade.TradePrice = result.Pv * (trade.BuySell == "买入" ? 1 : -1);
if (trade.TradeType !== "现金流交易") {
//根据总额算出期权费率
tradePricing.tradeCalc(trade, tradePricing.calcReason.Pv);
//由于期权费率会有精度损失,进行了四舍五入处理,需要再根据期权费率反算出期权费总额
//因为录入交易时是根据期权费率作为标准的
tradePricing.tradeCalc(trade, tradePricing.calcReason.PremiumRate);
}
}
else {
var pv = result.Pv * (trade.BuySell == "买入" ? 1 : -1);
trade.TradeSinglePrice = trade.Notional ? pv / trade.Notional : 0;
tradePricing.tradeCalc(trade, tradePricing.calcReason.TradeSinglePrice);
}
//result.Pv = (result.Pv < 0 ? -1 : 1) * trade.TradePrice;
if (pageObj.IsPVIncludePrincipal && trade.OriginalPrincipalSum > 0) {
result.Pv += trade.OriginalPrincipalSum * (trade.BuySell === '卖出' ? -1 : 1);
}
if (topVue.calcMargin) {
//非用户自己输入和非结构化交易
if (data.viewState.InitialMargin !== 'user' && !trade.StructureType) {
//非双向追保交易员收预付金,预付金不能小于0;交易员买入,付期权费收预付金,预付金不能小于0;交易员卖出,收期权费付预付金,预付金不能大于0
if (pageVue.ClientUsedForCalc && !data.viewState.TwoSideMargin && trade.InitialMargin < 0) {
trade.InitialMargin = 0;
}
else if (trade.BuySell == "卖出" && trade.InitialMargin > 0) {
trade.InitialMargin = 0;
}
else if (trade.BuySell == "买入" && trade.InitialMargin < 0) {
trade.InitialMargin = 0;
}
}
} else {
trade.InitialMargin = '';
}
//todo:光子模式下是否要重设TradeOpenVolatility的值
// delta gamma 用手数,其它份额 Vegapho份额*100 qdp用的是份额计算
//现在计算的是数量 份额数量比
//手数 = 交易数量 / 交易单位 (5吨 / 手)
//交易数量 = 份额 / 比率
//手数 = 份额 / (比率 * 交易单位)
//TradeUnitValue 是手数对份额的比率 鸡蛋时为 10
var variety = data.viewState.variety;
var fe_ration = 1;
!contractSize && (contractSize = variety && variety.ContractSize ? variety.ContractSize : 1);
calcResult.Pv = pricingFormat.tradePrice(result.Pv);
calcResult.Delta = pricingFormat.greek(result.Delta / contractSize);
calcResult.GammaCash = pricingFormat.greek(result.GammaCash);
calcResult.Theta = pricingFormat.greek(fe_ration * result.Theta);
calcResult.Vega = pricingFormat.greek(fe_ration * result.Vega);
calcResult.Rho = pricingFormat.greek(fe_ration * result.Rho * 100);
calcResult.TotalMargin = 0;
calcResult.TotalTradePrice = 0;
calcResult.TotalDay1Pnl = 0;
this.resetSummary(resetSummaryFlags.all);
this.sumTotal(trade);
if (data === this.datas[this.datas.length - 1]) {
calcResult.TotalMargin = pageVue.GetTotalMargin(_.map(this.datas, x => x.trade), this.structureType);
calcResult.TotalTradePrice = pageVue.GetTotalTradePrice(_.map(this.datas, x => x.trade));
calcResult.TotalDay1Pnl = pageVue.GetTotalDay1Pnl(_.map(this.datas, x => x.trade));
}
},
changeClient(clientId) {
let client = null;
if (pageVue.ClientUsedForCalc && pageVue.TwoSideMargin) {
client = ylotc.clients.find(y => y.id === clientId);
}
_.each(this.datas, x => {
x.trade.ClientId = clientId;
x.trade.MarginOptionType = client.MarginOptionType;
x.viewState.TwoSideMargin = client && client.MarginOptionType === 1;
x.trade.MarginTemplateName = client.MarginOptionType === 3 ? "无预付金" : "系统默认";
tradeUtils.changeMarginTemplate(x.trade, []);
});
},
updateSynthetic(calcId, synthetic) {
var index = this.datas.findIndex(x => x.trade.CalcId === calcId);
if (index < 0) return false;
this.datas[index].trade.SpotPrice = synthetic.Price;
this.$children[index].changeSpotPrice(true);
return true;
},
copyThis() {
pricingVue.copyItem(this);
},
synchTrade(trade, setInitialMarginNull) {
_.each(this.datas, x => {
//隐藏的组合公共属性字段赋值处理
if (trade.ParentIndex > 0 && x.trade.ParentIndex == trade.ParentIndex && !trade.hideCommen) {
x.trade.ClientId = trade.ClientId;
x.trade.UnderlyingId = trade.UnderlyingId;
x.trade.UnderlyingCode = trade.UnderlyingCode;
x.trade.UnderlyingName = trade.UnderlyingName;
x.trade.UnderlyingInstrumentType = trade.UnderlyingInstrumentType;
x.trade.VarietyId = trade.VarietyId;
if (trade.StructureType != "蝶式组合") {
x.trade.TradeAmountV = trade.TradeAmountV;
x.trade.TradeAmount = trade.TradeAmount;
x.trade.Notional = trade.Notional;
x.trade.StockEqvNotional = trade.StockEqvNotional;
x.trade.StockEqvNotionalReal = trade.StockEqvNotionalReal;
}
x.trade.ExerciseMode = trade.ExerciseMode;
x.trade.TradeDate = trade.TradeDate;
if (trade.StructureType != "日历价差") {
x.trade.ExerciseDate = trade.ExerciseDate;
x.trade.SettlementDate = trade.SettlementDate;
}
x.trade.SpotPrice = trade.SpotPrice;
x.trade.UnderlyingPrice = trade.UnderlyingPrice;
x.trade.SettlementType = trade.SettlementType;
x.trade.NoRiskRate = trade.NoRiskRate;
x.trade.DividendRate = trade.DividendRate;
x.trade.TTMDays = trade.TTMDays;
x.trade.IsTTMSystem = trade.IsTTMSystem;
x.trade.IsAnnualized2 = trade.IsAnnualized2;
x.trade.MetaDic = trade.MetaDic;
x.trade.EngineName = trade.EngineName;
x.trade.OriginalPrincipalSum = x.trade.IsUsePremiumRate ?
_.round(x.trade.StockEqvNotional * x.trade.PrincipalRateWrite * x.trade.AnnualizeFactor, otcformat.trading.tradePrice.precision)
: _.round(x.trade.Notional * x.trade.SinglePrincipalWrite, otcformat.trading.tradePrice.precision);
x.trade.TradePrice = x.trade.IsUsePremiumRate ?
_.round(tradeHelper.GetTradePriceByPremiumRate(x.trade.PremiumRate, x.trade.StockEqvNotional, x.trade.ParticipationRate,
x.trade.OriginalPrincipalSum, x.trade.AnnualizeFactor, x.trade.BuySell, x.trade.TradeType, true), otcformat.trading.tradePrice.precision)
: _.round(tradeHelper.GetTradePriceBySinglePrice(x.trade.TradeSinglePrice, x.trade.StockEqvNotional, x.trade.Notional,
x.trade.OriginalPrincipalSum, x.trade.AnnualizeFactor, x.trade.BuySell, x.trade.TradeType, true), otcformat.trading.tradePrice.precision);
if (setInitialMarginNull) {
x.trade.InitialMargin = null;
}
}
});
this.sumTotal(trade);
},
sumTotal(trade) {
//第一条腿添加组合成交金额和组合预付金
var totalTradePrice = 0;
var totalInitialMargin = 0;
_.each(this.datas, x => {
if (trade.ParentIndex > 0 && x.trade.ParentIndex == trade.ParentIndex) {
totalTradePrice += parseFloat(x.trade.TradePrice) * (x.trade.BuySell == "买入" ? -1 : 1);
totalInitialMargin += parseFloat(x.trade.InitialMargin);
}
});
_.each(this.datas, x => {
if (x.trade.ParentIndex == trade.ParentIndex) {
x.trade.TotalTradePrice = pricingFormat.tradePrice(totalTradePrice);
x.trade.TotalInitialMargin = pricingFormat.tradePrice(totalInitialMargin);
}
});
}
},
components: {
'vue-trade': vueTrade(),
'vue-trade2': vueTrade2(),
},
destroyed() {
if (!this.floating && this.isSelected) {
summaryVue.reset(this.index, null);
singlePricer.reset(this.index, null);
}
},
render: renders.items(),
staticRenderFns: renders.items(true)
});
if (!baseVue.combining && !baseVue.selfMount) {
if (vue.floating) {
vue.$mount($('<div>').appendTo('#pricing-items2').get(0));
} else if (fromLocal) {
vue.$mount($('<div>').appendTo('#pricing-items').get(0));
} else {
vue.$mount($('<div>').prependTo('#pricing-items').get(0));
}
}
!floating && mainDragScroll.reset();
return vue;
}
function _setAutocomplete(elId, datas, initData, onSelectFn) {
initData = initData || {};
if (elId === "GroupClientId") {
groupVue.trade.ClientId = initData.id;
groupVue.trade.ClientName = initData.Name;
}
else if (elId === "GroupAssetId") {
groupVue.trade.AssetId = initData.id;
groupVue.trade.AssetBookName = initData.Name;
}
else if (elId === "GroupTraderId") {
groupVue.trade.TraderId = initData.id;
groupVue.trade.TraderName = initData.Name;
}
FastVue.autocomplete(document.getElementById(elId), {
valueField: 'Name', searchField: ['Name', 'PinYin'], lookup: datas,
onSelect(data) {
$('#' + elId).data('id', data.id);
if (elId === "ClientId" || elId === "GroupClientId") {
_setSalesman();
}
onSelectFn && onSelectFn(data);
},
zIndex: 300000
}).setData(initData);
$('#' + elId).data('id', initData.id);
}
function _setSalesman() {
if (pageVue.ClientUsedForCalc) {
var IsCentralClearing = $("#txtCurrentClient").attr("data-IsCentralClearing");
var CentralClearingPaltform = $("#txtCurrentClient").attr("data-CentralClearingPaltform");
var TradingPaltform = $("#txtCurrentClient").attr("data-TradingPaltform");
$("#IsCentralClearing").val(IsCentralClearing);
$("#CentralClearingPaltform").val(CentralClearingPaltform);
$("#TradingPaltform").val(TradingPaltform);
}
if ($("#ClientId").data('id') && _salesCommissionCtrl) {
_salesCommissionCtrl.changeSalesmen($("#ClientId").data('id'));
_getMainProtocolCode($("#ClientId").data('id'));
}
}
function _changeMainProtocolCode() {
_getSupProtocolCode();
}
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");
_.forEach(resp, (v) => {
obj.append("<option >" + v.Text + "</option>");
})
});
}
_getSupProtocolCode()
}
function _getSupProtocolCode() {
$("#SupProtocolCode option").remove();
if ($("#MainProtocolCode").val()) {
main.post("/Client/getSideProtocols", { mainProtocol: $("#MainProtocolCode").val() }, { async: false }).done(function (resp) {
var obj = $("#SupProtocolCode");
_.forEach(resp, (v) => {
obj.append("<option >" + v + "</option>");
})
});
}
}
//定价组件
const pricingVue = (function () {
//创建定价标题组件
function createTitleVue(el, floating) {
var vue = new Vue({
el: el,
data: {
trade: {},
isTitle: true,
floating: floating,
fieldState: tradeFieldMgr.getTitleState(floating)
},
render: renders.title(),
staticRenderFns: renders.title(true)
});
return vue;
}
createTitleVue('#pricing-titles', false);
createTitleVue('#pricing-titles2', true);
var _index = 1;
//定价组件集合
const _tradeVues = [];
function __clear() {
if (!_tradeVues.length) return;
_index = 0;
_tradeVues.splice(0, _tradeVues.length).forEach(x => x.remove());
$('#pricing-items').parent().floatingScroll('update');
}
//清空
function clear(force) {
if (!_tradeVues.length) return;
if (force) {
__clear();
} else {
main.confirmV2("确认清空吗?").done(__clear);
}
}
//移除
function remove(tradeVue, blRemoveChain) {
if (blRemoveChain && tradeVue.floating) {
var baseVue = _tradeVues.find(x => x.index === tradeVue.index);
baseVue && baseVue.remove();
}
_.pull(_tradeVues, tradeVue);
$('#pricing-items').parent().floatingScroll('update');
}
//新增单腿
function addTrade(trade, tradeType) {
if (_tradeVues && _tradeVues[0] && _tradeVues[0].getData().trade.StructureType) {
return main.alert('不能同时添加单笔交易和组合交易');
}
_index += 1;
let baseVue = _tradeVues[0] || { datas: [tradeFieldMgr.defaultData] };
if (trade && typeof trade === 'object') {
let viewState = _.cloneDeep(tradeFieldMgr.defaultData.viewState);
viewState.initFlag = 'import1';
baseVue = { datas: [{ trade: trade, viewState: viewState }] };
} else if (tradeType === "现金流交易") {
let data = tradeFieldMgr.defaultData;
data = {
trade: _.cloneDeep(data.trade),
viewState: _.cloneDeep(data.viewState)
};
_tradeVues.length < 1 && (data.viewState.initFlag = 'first');
data.trade.IsUsePremiumRate = true;
baseVue = { datas: [data] };
trade = data.trade;
}
else {
let data = baseVue.datas[0];
data = {
trade: _.cloneDeep(data.trade),
viewState: _.cloneDeep(data.viewState)
};
_tradeVues.length < 1 && (data.viewState.initFlag = 'first');
baseVue = { datas: [data] };
trade = data.trade;
}
if (typeof tradeType === 'string' && trade.TradeType !== tradeType) {
trade.TradeType = tradeType;
tradeFieldMgr.initFieldValue(trade);
} else {
tradeFieldMgr.resetFieldState(trade);
}
if (trade.TradeAmount && !trade.TradeAmountV) {
trade.TradeAmountV = tradeHelper.getTradeAmountV(trade);
}
let vue = createVue(_index, baseVue);
_tradeVues.unshift(vue);
if (pageVue.IsImport || !new Array('香草期权', '合成价差期权').includes(trade.TradeType)) {
floatVue.show(vue);
}
}
//复制项目
function copyItem(tradeVue) {
var arrIndex = _.indexOf(_tradeVues, tradeVue);
if (arrIndex < 0) {
throw '系统错误';
}
_index += 1;
let baseVue = tradeVue;
baseVue = { datas: _.cloneDeep(baseVue.datas), structureType: baseVue.structureType, selfMount: true };
var vue = createVue(_index, baseVue);
vue.$mount($('<div>').insertBefore(tradeVue.$el).get(0));
_tradeVues.splice(arrIndex, 0, vue);
}
//新增策略
function addTrades(trades) {
if (_tradeVues && _tradeVues[0] && !_tradeVues[0].getData().trade.StructureType) {
return main.alert('不能同时添加单笔交易和组合交易');
}
var datas = _.map(trades, trade => {
trade = _.transform(trade, (result, val, key) => {
key in result && (result[key] = val);
}, _.cloneDeep(pageVue.Trade));
if (trade.UnderlyingInstrumentType !== 'Stock' && trade.UnderlyingInstrumentType !== 'StockIndex') {
trade.StockEqvNotionalReal = pricingFormat.StockEqvNotional(trade.Notional * trade.SpotPrice);
trade.StockEqvNotional = trade.StockEqvNotionalReal;
}
else {
trade.StockEqvNotionalReal = trade.StockEqvNotional;
}
trade.SpotPrice = trade.UnderlyingPrice;
trade.NoRiskRate = null;
trade.DividendRate = 0;
trade.ParticipationRate = 1;
trade.AnnualizeFactor = 1;
if (pageVue.NumOfSmoothingDaysCfg === 'ONE' && pageVue.IsTradeVol) {
trade.NumOfSmoothingDays = 1;
}
tradeUtils.normalizeTrade(trade);
let viewState = _.cloneDeep(tradeFieldMgr.defaultData.viewState);
viewState.initFlag = 'import2';
return { trade: trade, viewState: viewState };
});
_index += 1;
var vue = createVue(_index, { datas: datas, structureType: pageVue.getStructureType() });
_tradeVues.unshift(vue);
}
//自由组合
function combine() {
var comVues = _.filter(_tradeVues, x => x.isSelected);
if (comVues.length < 2) {
return main.alert('请选择至少两项进行组合!');
}
var firstVue = comVues[0];
var datas = _.flatMap(comVues.map(x => x.datas));
if (pageVue.ClientUsedForCalc) {
var clientId = parseInt(datas[0].trade.ClientId);
if (!_.every(datas, x => parseInt(x.trade.ClientId) === clientId)) {
return main.alert('所选项客户必须一致!');
}
}
var vue = createVue(firstVue.index, { combining: true, datas: datas, fromLocal: true });
vue.$mount($('<div>').insertBefore(firstVue.$el).get(0));
var index = _.indexOf(_tradeVues, firstVue);
_tradeVues[index] = vue;
_.pullAll(_tradeVues, comVues);
comVues.forEach(x => x.remove());
}
//取消组合
function uncouple(tradeVue) {
if (!tradeVue || tradeVue.floating) {
throw '系统错误';
}
var arrIndex = _.indexOf(_tradeVues, tradeVue);
if (arrIndex < 0) {
throw '系统错误';
}
var index = _index += tradeVue.datas.length;
var map = _.map(tradeVue.datas, data => {
data.trade.hideCommen = false;
var vue = createVue(index--, { combining: true, datas: [data], fromLocal: true });
vue.$mount($('<div>').insertBefore(tradeVue.$el).get(0));
return vue;
});
var args = [arrIndex, 1].concat(map);
Array.prototype.splice.apply(_tradeVues, args);
tradeVue.remove();
}
//波动率类型变更
function changeVolType() {
_tradeVues.forEach(x => x.getVol());
}
//获取所有交易数据
function getTrades() {
return _.flatMap(_tradeVues.map(x => x.datas)).map(x => x.trade);
}
//录入交易
function saveTrades() {
tradeSaver.saveTrades(_tradeVues.filter(x => x.isSelected));
}
//录入组合交易
function saveGroupTrades() {
tradeSaver.saveGroupTrades(_tradeVues.filter(x => x.isSelected));
}
//定价计算
function calcPrice(tradeVues) {
!tradeVues && (tradeVues = _tradeVues);
if (!tradeVues.length) {
return main.alert("请至少录入一个交易");
}
var trades = _.flatMap(tradeVues, x => x.datas).map(x => x.trade);
//检查并转换数据
trades = tradeUtils.prepareTrades(trades, pageVue.getVolType(), false);
if (trades) {
let data = {
version: pageVue.CalcVersion,
trades: trades, calcMargin: topVue.calcMargin,
calcAutocallGreeks: topVue.calcAutocallGreeks
};
main.post("/pricing/ajaxCalcPrices", data).done(function (resp) {
//tradeVues转换为calcId为key的字典
var map = tradeVues.reduce((result, cur) => {
_.each(cur.datas, x => {
result[x.trade.CalcId] = cur;
});
return result;
}, {});
//设置计算结果
_.each(resp.obj, item => {
map[item.CalcId].setCalcResult(item);
});
});
}
}
//显示组合到期收益曲线图形
function showPayoffLineChart(tradeVues) {
var selectVues = _.filter(_tradeVues, x => x.isSelected);
!tradeVues && (tradeVues = selectVues);
if (!tradeVues.length) {
return main.alert("请至少录入一个交易");
}
var trades = _.flatMap(tradeVues, x => x.datas).map(x => x.trade);
//检查并转换数据
trades = tradeUtils.prepareTrades(trades, pageVue.getVolType(), false);
window.sessionStorage.removeItem("pricing_payoff_line");
if (trades) {
main.post("/pricing/GetTradesPayoffLine", { trades: trades }).done(function (resp) {
//resp.obj
window.sessionStorage.setItem("pricing_payoff_line", JSON.stringify(resp));
window.open("/Statics/html/PayoffChart.html");
});
}
}
//显示组合Pv曲线
function showPvChart(tradeVues) {
var selectVues = _.filter(_tradeVues, x => x.isSelected);
!tradeVues && (tradeVues = selectVues);
if (!tradeVues.length) {
return main.alert("请至少录入一个交易");
}
var trades = _.flatMap(tradeVues, x => x.datas).map(x => x.trade);
//检查并转换数据
trades = tradeUtils.prepareTrades(trades, pageVue.getVolType(), false);
window.sessionStorage.removeItem("pricing_pv_line");
if (trades) {
main.post("/pricing/GetTradesPvLine", { trades: trades }).done(function (resp) {
//resp.obj
window.sessionStorage.setItem("pricing_pv_line", JSON.stringify(resp));
window.open("/Statics/html/PvChart.html");
});
}
}
function showLifeLineChart(tradeVues) {
var selectVues = _.filter(_tradeVues, x => x.isSelected);
!tradeVues && (tradeVues = selectVues);
if (!tradeVues.length) {
return main.alert("请至少选择一笔交易");
}
var trades = _.flatMap(tradeVues, x => x.datas).map(x => x.trade);
//检查并转换数据
trades = tradeUtils.prepareTrades(trades, pageVue.getVolType(), false);
window.sessionStorage.removeItem("pricing_life_lines");
if (trades) {
main.post("/pricing/GetTradesLifePvLine", { trades: trades }).done(function (resp) {
//resp.obj
window.sessionStorage.setItem("pricing_life_lines", JSON.stringify(resp));
window.open("/Statics/html/PvLifeChart.html");
});
}
}
//显示Greeks变化曲线
function showGreeksChart(tradeVues) {
var selectVues = _.filter(_tradeVues, x => x.isSelected);
!tradeVues && (tradeVues = selectVues);
if (!tradeVues.length) {
return main.alert("请至少选择一笔交易");
}
var trades = _.flatMap(tradeVues, x => x.datas).map(x => x.trade);
//检查并转换数据
trades = tradeUtils.prepareTrades(trades, pageVue.getVolType(), false);
window.sessionStorage.removeItem("pricing_greeks_line");
if (trades) {
main.post("/pricing/GetTradesGreeksForLifetime", { trades: trades }).done(function (resp) {
//resp.obj
window.sessionStorage.setItem("pricing_greeks_line", JSON.stringify(resp));
window.open("/Statics/html/GreeksChart.html");
});
}
}
//设置观察数据
function setObservation(observationNum, observationUnit, observationHolidayType, observationDates, alignEnd, calcId, couponDayInterval) {
if (!calcId) return;
let _data = null, calcId2 = calcId.replace(/#.*$/, "");
_.each(_tradeVues, vue => {
return !(_data = _.find(vue.datas, data => data.trade.CalcId === calcId2));
});
_data && consVueTrade.methods.setObservation.apply(_data, arguments);
}
function setStructure(value, calcId) {
if (!calcId) return;
let _data = null, calcId2 = calcId + "";
_.each(_tradeVues, vue => {
return !(_data = _.find(vue.datas, data => data.trade.CalcId === calcId2));
});
_data && consVueTrade.methods.setStructure.apply(_data, arguments);
}
//对冲下单
function hedgingOrder() {
var comVues = _.filter(_tradeVues, x => x.isSelected);
if (comVues.length < 1) {
return main.alert('请至少选择一项!');
}
var datas = _.flatMap(comVues.map(x => x.datas));
var instType = datas[0].trade.UnderlyingInstrumentType;
if (!_.every(datas, x => x.trade.UnderlyingInstrumentType === instType)) {
return main.alert('所选项标的类型必须一致!');
}
var rd = _.reduce(datas, (acc, cur) => {
var code = cur.trade.UnderlyingCode;
var delta = acc[code];
acc[code] = (delta || 0) + (parseFloat(cur.calcResult.Delta) || 0);
return acc;
}, {});
var items = _.reduce(rd, (acc, value, key) => {
var index = acc.length / 2;
acc.push('<input type="hidden" name="Items[' + index + '].UnderlyingCode" value="' + key + '" />');
acc.push('<input type="hidden" name="Items[' + index + '].DeltaInLots" value="' + value + '" />');
return acc;
}, []);
items.push('<input type="hidden" name="InstType" value="' + instType + '" />');
$('#orderForm').html(items.join("")).submit();
}
//浏览器本地数据缓存
const localData = (new function () {
if (pageVue.IsImport) {
this.save = $.noop;
this.load = $.noop;
return;
}
this.save = function () {
var vueDatas = _tradeVues.map(x => new Object({
index: x.index,
structureType: x.structureType,
datas: x.datas.map(data => new Object({ trade: data.trade, viewState: data.viewState }))
}));
var data = {
index: _index,
VolType: pageVue.getVolType(),
SysDate: pageVue.SysDate,
vueDatas: vueDatas,
time: new Date().getTime()
};
var json = JSON.stringify(data);
localStorage.setItem("pricing-structure-" + pageVue.UserId, json);
};
this.load = function () {
//设置默认值
var saveKey = "pricing-structure-" + pageVue.UserId;
var storage = localStorage.getItem(saveKey);
if (!storage) return;
var data = JSON.parse(storage);
if (pageVue.SysDate !== data.SysDate ||
new Date().getHours > 18 && new Date(data.time).getHours() < 18) {
//需要清空缓存
localStorage.removeItem(saveKey);
}
else {
_index = data.index;
pageVue.setVolType(data.VolType);
_.each(data.vueDatas, function (x) {
x.fromLocal = true;
var vue = createVue(x.index, x);
_tradeVues.push(vue);
});
}
};
}());
//持久化定价数据
$(window).on('beforeunload', localData.save);
$(localData.load);
return {
clear: clear,
remove: remove,
addTrade: addTrade,
copyItem: copyItem,
combine: combine,
changeVolType: changeVolType,
saveTrades: saveTrades,
saveGroupTrades: saveGroupTrades,
calcPrice: calcPrice,
showPayoffLineChart: showPayoffLineChart,
showPvChart: showPvChart,
showGreeksChart: showGreeksChart,
showLifeLineChart: showLifeLineChart,
getTradeVues(onlySelected) {
return onlySelected ? _tradeVues.filter(x => x.isSelected) : _tradeVues;
},
getTradeVue(index) { return _tradeVues[index || 0]; },
saveLocalData: localData.save,
setOption: addTrades, //用于和老版兼容
setObservation: setObservation,
setStructure: setStructure,
uncouple: uncouple,
hedgingOrder: hedgingOrder,
onMountd(vue) {
_tradeVues.length && $('#pricing-items').parent().floatingScroll();
},
swap(oldIndex, newIndex) {
if (oldIndex === newIndex) return;
let vue = _tradeVues[oldIndex];
_tradeVues.splice(oldIndex, 1);
_tradeVues.splice(newIndex, 0, vue);
},
//新增模板
addTemplate(tplItems) {
tplItems.forEach(x => {
x.datas.forEach(d => {
d.viewState = _.cloneDeep(tradeFieldMgr.defaultData.viewState);
d.viewState.initFlag = 'template';
var client = ylotc.clients.find(y => y.id === d.trade.ClientId);
d.viewState.TwoSideMargin = client && client.MarginOptionType === 1;
});
_index += 1;
var vue = createVue(_index, x);
_tradeVues.unshift(vue);
});
}
};
}());
//浮窗定价组件
const floatVue = (function () {
var _tradeVue;
//关闭浮窗
function hide() {
if (_tradeVue) {
_tradeVue.remove();
_tradeVue = null;
}
$('#floatModal').modal('hide');
$(document.body).removeClass('has-float');
if (pageVue.IsImport) {
layer.closeMe();
}
}
//显示浮窗index, trade, viewState
function show(baseVue, forScreenshot) {
_tradeVue && _tradeVue.remove();
_tradeVue = createVue(baseVue.index, baseVue, true);
$(document.body).toggleClass('has-float', !forScreenshot);
$('#floatModal').modal('show');
}
function screenshot() {
screenshoter.execute(_tradeVue);
}
return {
show: show,
hide: hide,
calcPrice() {
_tradeVue && pricingVue.calcPrice([_tradeVue]);
},
saveTrade() {
_tradeVue && _tradeVue.saveTrade();
},
screenshot: screenshot,
getVue() { return _tradeVue; }
};
}());
//dom ready function
$(function () {
tradeSaver.init();
groupVue.init();
//解决上层popover不能编辑的问题
$('#floatModal').on('shown.bs.modal', function () {
$(document).off('focusin.modal');
});
$(".search-label").addClass("formlabel");
$(".formlabel").addClass("search-label");
var sortable = Sortable.create(document.getElementById('pricing-items'), {
handle: '.pricing-index-drag',
onEnd: function (evt) {
pricingVue.swap(evt.oldIndex, evt.newIndex);
},
});
if (pageVue.IsImport) {
let trade = _.cloneDeep(pageVue.Trade);
pricingVue.addTrade(trade);
setTimeout(function () {//交易详情页-定价增强价格不显示问题
if (trade.TradeType == "亚式期权" && trade.StrikeType != "Floating" && trade.PayoffType == 'EnhancedArithmeticAverage') {
$("#EnhancedPriceTitle").show();
}
}, 500);
}
document.onkeydown = hotkey;
//当onkeydown 事件发生时调用hotkey
function hotkey() {
var a = window.event.keyCode;
if ((window.event.keyCode == 81) && (event.altKey)) {
if (floatVue.getVue() === null || floatVue.getVue() === undefined) {
pricingVue.calcPrice();
} else {
floatVue.calcPrice();
}
}
else if ((window.event.keyCode == 87) && (event.altKey)) {
floatVue.screenshot();
}
}
});
//用于和老版兼容
window.vue = pricingVue;
//兼容定价计算页面观察日设置
var getObservationDatesSetting = pricingVue.setObservation;
var getStructureSetting = pricingVue.setStructure;
//连离类型:障碍(Discrete),二元(MonitorType,美式)
//补偿类型:障碍(RebateType),二元(RebateType,美式)
//定价模板
var templateVue = (function () {
if (pageVue.IsImport) return;
var debounceSearch;
return new Vue({
el: '#modalTemplate',
data: {
saveMode: true,
saveData: {
name: '',
id: 0,
_override: false,
_CommonTemplate: true
},
listData: [],
searchText: ''
},
mounted() {
this.refreshList();
debounceSearch = _.debounce(this.searchInner, 500);
},
methods: {
showSave() {
let selVues = pricingVue.getTradeVues(true);
if (!selVues.length) {
return main.alert('请勾选要保存的定价数据');
}
//模板名称自动赋值
let vueDatas = selVues.map(x => new Object({
structureType: x.structureType,
datas: x.datas.map(data => new Object({ trade: data.trade }))
}));
var client = ylotc.clients.find(y => y.id === vueDatas[0].datas[0].trade.ClientId);
let arr = [vueDatas[0].structureType, client?.Name?.substring(0, 6), vueDatas[0].datas[0].trade.UnderlyingCode, vueDatas[0].datas[0].trade.TradeDate.split('-').join(''), vueDatas[0].datas[0].trade.Strike];
for (var i = 0; i < arr.length; i++) {
//这里为过滤空的值
//这里为过滤空的值
if (!_.trim(_.toString(arr[i]))) {
arr.splice(i, 1);
i = i - 1;
}
}
let str = arr.join("-");
this.saveMode = true;
this.saveData.name = str;
this.saveData.json = '';
$('#modalTemplate').modal('show');
},
showList() {
this.saveMode = false;
$('#modalTemplate').modal('show');
},
saveTemplate() {
let selVues = pricingVue.getTradeVues(true);
if (!selVues.length) {
return main.alert('没有可保存的定价数据');
}
let name = (this.saveData.name).trim();
if (!name) {
return main.alert('请填写模板名称');
}
let self = this;
let vueDatas = selVues.map(x => new Object({
structureType: x.structureType,
datas: x.datas.map(data => new Object({ trade: data.trade }))
}));
let dataJson = JSON.stringify(vueDatas);
main.post('/pricing/AjaxSaveTemplate/v2', { name, dataJson, _override: this.saveData._override, _CommonTemplate: this.saveData._CommonTemplate }).done(function () {
$('#modalTemplate').modal('hide');
self.refreshList();
self.searchInner();
});
},
removeTemplate(sysUserConfigId) {
let $item = $(event.target).closest('.yt-template-item');
if (!$item) {
return main.alert('系统错误,没有找到所选项');
}
let self = this;
let index = $item.prevAll().length;
let names = [$item.children('a').text()];
main.confirmPost('确认要删除所选项"' + names[0] + '"吗?', '/pricing/AjaxRemoveTemplate', { sysUserConfigId }).done(function () {
self.listData.splice(index, 1);
});
},
loadTemplate() {
let name = $(event.target).text();
let TemplateType = $(event.target).next().text();
function __do() {
pricingVue.clear(true);
main.post('/pricing/ajaxGetTemplate/v2', { name, TemplateType }).done(function (resp) {
let items = JSON.parse(resp.obj);
pricingVue.addTemplate(items);
$('#modalTemplate').modal('hide');
});
}
if (pricingVue.getTradeVues().length) {
main.confirm("加载自定义模板将清空页面现有交易,是否继续?", __do);
} else {
__do();
}
},
refreshList() {
let self = this;
this.searchText = '';
main.post('/pricing/AjaxGetTemplateList').done(function (resp) {
self.listData = resp.obj.map(x => new Object({ id: x.id, name: x.ConfigName, show: true, UserId: x.UserId, ConfigType: x.ConfigType }));
});
},
searchInner() {
console.log('searchInner');
let text = this.searchText ? this.searchText.toLowerCase() : '';
this.listData.forEach(x => {
x.show = !text || x.name.toLowerCase().indexOf(text) >= 0;
});
},
searchList(action) {
if (action === 'reset') {
debounceSearch.cancel();
this.searchText = '';
this.searchInner();
} else {
debounceSearch();
}
}
}
});
}());
const customNumberFormat = { precision: 6, negative: true, };
const customNumberPercentFormat = { precision: 6, negative: true, append: '%' };
var groupVue = new Vue({
el: '#modalGroupTradeSave',
data: {
viewState: pageVue.viewState,
...consVueTrade.data(),
trade: ylotc.trade,
options: ylotc.options,
structureTypes: pageObj.structureTypes,
PropertyMap: pageObj.PropertyMap,
},
computed: { ...consVueTrade.computed },
methods: {
...consVueTrade.methods,
closeModal: function () {
$('#modalGroupTradeSave').modal('hide');
},
saveGroupTrade: function () {
if (!_trades) return;
_trades.forEach(x => {
x.AssetId = this.trade.AssetId;
x.AssetBookName = this.trade.AssetBookName;
x.TraderId = this.trade.TraderId;
x.TraderName = this.trade.TraderName;
x.TradeNumber = this.trade.TradeNumber;
x.SalesCommission = this.trade.salesCommission;
x.ClientId = this.trade.ClientId;
x.ClientName = this.trade.ClientName;
if (x.TradePremium) {
x.MetaDic["交易溢价"] = x.TradePremium;
}
x.MetaDic["交易场所"] = this.trade.TradingPlace;
x.MetaDic["清算机构"] = this.trade.ClearingAgency;
x.MetaDic["主协议编号"] = this.trade.MainProtocolCode;
x.MetaDic["补充协议编号"] = this.trade.SupProtocolCode;
x.TradeType === '现金流交易' && tradeUtils.resetCashFlow(x);
});
this.setExtendInfo();
main.post("/pricing/AjaxSaveGroupTrade", { trade: this.trade, subTrades: _trades }).done(function (resp) {
if (pageVue.IsImport) {
let url = "#/trade/tradeview?abstract=1&enid=" + resp.obj[0].EncryptId;
layer.closeMe(url);//必须以#开头
} else {
floatVue.hide();
$('#modalTradeSave').modal('hide');
//showTradeView(resp.obj);
_.each(_tradeVues, x => x.remove(true));
}
});
},
addNewProperty: function () {
this.PropertyMap[this.trade.StructureType].push({ isNew: true, ColumnName: "", ColumnDefaultValue: "", ColumnType: 0 });//0:文本
},
deleteProperty: function (index) {
this.PropertyMap[this.trade.StructureType].splice(index, 1);
},
setExtendInfo: function () {
var propertyList = [];
this.PropertyMap[this.trade.StructureType].forEach((item, index) => {
var note = { name: item.name || item.ColumnName, value: PercentColumnText.displayText(item) };
if (note.name) {
propertyList.push(note);
}
});
this.trade.ExtendInfo = this.trade.StructureType == "气囊结构" ? null : JSON.stringify(propertyList);
return this.trade.ExtendInfo;
},
changeStrucTureType: function () {
if (groupVue.trade.TradeType == "气囊结构") {
groupVue.trade.trade_airbag.KIParticipationRate = 1;
}
else {
groupVue.trade.OptionType = null;
groupVue.trade.Strike = null;
}
},
changeBuySell: function () {
groupVue.trade.TradePrice *= -1;
groupVue.trade.Day1Pnl *= -1;
groupVue.trade.TradeSinglePrice *= -1;
groupVue.trade.PremiumRate *= -1;
},
changeIsUsePremiumRate: function () {
if (groupVue.trade.IsUsePremiumRate) {
groupVue.changeStockEqvNotional();
}
else {
groupVue.changeTradeAmount();
}
},
changeStockEqvNotional: function () {
var notional = Math.abs(groupVue.trade.StockEqvNotional / groupVue.trade.SpotPrice);
groupVue.trade.Notional = pricingFormat.notional(notional);
groupVue.trade.TradeAmount = pricingFormat.notional(notional / groupVue.viewState.variety.CountRatio);
groupVue.trade.TradeSinglePrice = pricingFormat.tradeSinglePrice(groupVue.trade.TradePrice / groupVue.trade.Notional);
groupVue.trade.PremiumRate = pricingFormat.premiumRate(groupVue.trade.TradePrice / groupVue.trade.StockEqvNotional);
},
changeTradeAmount: function () {
var notional = groupVue.trade.TradeAmount * groupVue.viewState.variety.CountRatio;
groupVue.trade.Notional = pricingFormat.notional(notional);
groupVue.trade.StockEqvNotional = pricingFormat.stockEqvNotional(Math.abs(notional * groupVue.trade.SpotPrice));
groupVue.trade.TradeSinglePrice = pricingFormat.tradeSinglePrice(groupVue.trade.TradePrice / groupVue.trade.Notional);
groupVue.trade.PremiumRate = pricingFormat.premiumRate(groupVue.trade.TradePrice / groupVue.trade.StockEqvNotional);
},
init: function () {
_setAutocomplete('GroupAssetId', ylotc.assetunits, null, function (data) {
groupVue.trade.AssetId = data.id;
groupVue.trade.AssetBookName = data.Name;
});
var traders = $.grep(ylotc.traders, function (e) { return e.Name == pageVue.Trade.TraderName; });
_setAutocomplete('GroupTraderId', ylotc.traders, { Name: pageVue.Trade.TraderName, id: traders.length ? traders[0].id : 0 }, function (data) {
groupVue.trade.TraderId = data.id;
groupVue.trade.TraderName = data.Name;
});
_setAutocomplete('GroupClientId', ylotc.clients, null, function (data) {
pageVue.SecuritiesEnvironment ? _getMainProtocolCode : null;
groupVue.trade.ClientId = data.id;
groupVue.trade.ClientName = data.Name;
});
},
changeOption: function () {
this.PropertyMap[this.trade.StructureType].forEach(d => {
if (d.ColumnName != undefined) {
d.name = d.ColumnName;
}
if (d.ColumnDefaultValue != undefined) {
d.value = d.ColumnDefaultValue;
}
});
}
},
components: {
'vue-niceselect': FastVue.vueNiceSelect(),
'vue-client': vueClient(),
'vue-variety': vueVariety(),
'vue-margintemplatename': vueMarginTemplateName(),
'vue-tradetype': vueTradeType(),
'vue-underlying': vueUnderlying(),
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput(),
'vue-daycount': vueDayCount(),
},
});