744 lines
28 KiB
JavaScript
744 lines
28 KiB
JavaScript
|
||
const RiskFreeRateFormat = Object.freeze({ append: '%' });
|
||
const GreekFormat = main.numberFormat({ grouping: true });
|
||
|
||
//标的树形
|
||
const pageMgr = (new function () {
|
||
|
||
var _self = this, _zTreeObj, _exportHeadCompiled, _popDiyId = '';
|
||
|
||
const DiyFields = ['UnderlyingPrice', 'DividenRate', 'ConstVol'];
|
||
|
||
function formatRate(val) {
|
||
if (typeof (val) === 'undefined' || val === null || val === false) {
|
||
return " ";
|
||
}
|
||
return (val * 100).toFixed(4).replace(/0{2}$/, "") + "%";
|
||
}
|
||
|
||
//标的--获取计算所需标的信息
|
||
this.underlying_forCalc = function () {
|
||
if (!_zTreeObj) return [];
|
||
let nodes = _zTreeObj.getNodes();
|
||
let underlyings = nodes.reduce((acc, cur) => {
|
||
return acc.concat(cur.children.map(x => x.mydata));
|
||
}, []);
|
||
return underlyings;
|
||
};
|
||
|
||
//标的--获取所有被选中的标的信息
|
||
this.underlying_checked = function () {
|
||
if (!_zTreeObj) return [];
|
||
let nodes = _zTreeObj.getNodes();
|
||
let underlyings = nodes.reduce((acc, cur) => {
|
||
return acc.concat(cur.children.filter(x => x.checked).map(x => x.mydata));
|
||
}, []);
|
||
return underlyings;
|
||
};
|
||
|
||
//标的--获取选择项并转换为对象返回
|
||
this.underlying_getSelectedObj = function () {
|
||
return _zTreeObj ? _zTreeObj.getCheckedNodes(true).reduce((acc, cur) => {
|
||
!acc && (acc = {});
|
||
if (cur.mytype === 'underlying') {
|
||
acc[cur.mydata.UnderlyingCode.toUpperCase()] = cur.mydata;
|
||
}
|
||
return acc;
|
||
}, null) : null;
|
||
};
|
||
|
||
//标的--价格/分红率/波动率自定义视图呈现
|
||
function underlying_diyDom(treeId, treeNode) {
|
||
|
||
if (!DiyFields.includes(treeNode.mytype)) return;
|
||
|
||
let diyId = 'diy_' + treeNode.mytype + '_' + treeNode.tId;
|
||
|
||
if ($("#" + diyId).length) {
|
||
$("#" + diyId).remove();
|
||
}
|
||
|
||
let aObj = $("#" + treeNode.tId + "_a");
|
||
|
||
let className = 'tree-diy-field ' + treeNode.mytype;
|
||
|
||
let html = `<a id="${diyId}" data-tid="${treeNode.tId}" href="javascript:;" class="${className}"> ${treeNode.mydata} </a>`;
|
||
|
||
aObj.after(html);
|
||
};
|
||
|
||
//标的--价格/分红率/波动率自定义视图-更新
|
||
function underlying_diyDom_update(underlyings) {
|
||
if (!_zTreeObj || !underlyings) return;
|
||
let nodes = _zTreeObj.getNodes();
|
||
nodes = _.flatMap(nodes, x => x.children);
|
||
nodes.forEach(x => {
|
||
let data = underlyings.find(y => y.UnderlyingCode === x.name);
|
||
let constVol = x.mydata.ConstVol = data ? data.ConstVol : "";
|
||
let dividenRate = x.mydata.DividenRate = data ? data.DividenRate : "";
|
||
let volNode = x.children.find(y => y.name.includes('波动率'));
|
||
if (volNode) {
|
||
volNode.mydata = formatRate(constVol);
|
||
underlying_diyDom("", volNode);
|
||
}
|
||
let divNode = x.children.find(y => y.name.includes('分红率'));
|
||
if (divNode) {
|
||
divNode.mydata = formatRate(dividenRate);
|
||
underlying_diyDom("", divNode);
|
||
}
|
||
});
|
||
}
|
||
|
||
//标的--更新树控件
|
||
this.underlying_updateTree = function (underlyings) {
|
||
if (_zTreeObj) {
|
||
underlying_diyDom_update(underlyings);
|
||
return $('.tree-diy-changed').removeClass('tree-diy-changed');
|
||
}
|
||
|
||
$.fn.zTree.destroy();
|
||
|
||
let treeNodeObj = _.reduce(underlyings, (acc, cur) => {
|
||
let varietyCode = cur.VarietyCode || '未知';
|
||
if (!(varietyCode in acc)) {
|
||
acc[varietyCode] = { name: varietyCode, open: true, checked: true, mytype: 'variety', children: [] };
|
||
}
|
||
|
||
let node = {
|
||
name: cur.UnderlyingCode, open: true, checked: true, mytype: 'underlying', mydata: cur, children: [
|
||
{ name: '价格:', mydata: cur.UnderlyingPrice, nocheck: true, mytype: 'UnderlyingPrice' },
|
||
{ name: '分红率:', mydata: formatRate(cur.DividenRate), nocheck: true, mytype: 'DividenRate' },
|
||
{ name: '波动率:', mydata: formatRate(cur.ConstVol), nocheck: true, mytype: 'ConstVol' },
|
||
]
|
||
};
|
||
|
||
acc[varietyCode].children.push(node)
|
||
|
||
return acc;
|
||
}, {});
|
||
|
||
let treeSettings = {
|
||
view: {
|
||
dblClickExpand: true,
|
||
selectedMulti: true,
|
||
showLine: true,
|
||
showTitle: false,
|
||
addDiyDom: underlying_diyDom
|
||
},
|
||
check: {
|
||
enable: true,
|
||
chkStyle: "checkbox"
|
||
},
|
||
callback: {
|
||
onCheck(event, treeId, treeNode) {
|
||
resultVue.updateKey++;
|
||
}
|
||
}
|
||
};
|
||
|
||
let treeNodes = _.sortBy(Object.values(treeNodeObj), ['name']);
|
||
|
||
_zTreeObj = $.fn.zTree.init($("#underlyingTree"), treeSettings, treeNodes);
|
||
};
|
||
|
||
//标的--更新树控件--清除波动率
|
||
this.underlying_updateTree_clearVol = function () {
|
||
if (!_zTreeObj) return;
|
||
let nodes = _zTreeObj.getNodes();
|
||
nodes = _.flatMap(nodes, x => x.children);
|
||
nodes.forEach(x => {
|
||
x.mydata.ConstVol = '';
|
||
let volNode = x.children.find(y => y.name.includes('波动率'));
|
||
if (volNode) {
|
||
volNode.mydata = '';
|
||
underlying_diyDom("", volNode);
|
||
}
|
||
});
|
||
};
|
||
|
||
//标的--更新树控件--清除分红率
|
||
this.underlying_updateTree_clearDividenRate = function () {
|
||
if (!_zTreeObj) return;
|
||
let nodes = _zTreeObj.getNodes();
|
||
nodes = _.flatMap(nodes, x => x.children);
|
||
nodes.forEach(x => {
|
||
x.mydata.DividenRate = '';
|
||
let divNode = x.children.find(y => y.name.includes('分红率'));
|
||
if (divNode) {
|
||
divNode.mydata = '';
|
||
underlying_diyDom("", divNode);
|
||
}
|
||
});
|
||
};
|
||
|
||
//标的--初始化编辑探测
|
||
function underlying_initPopEdit() {
|
||
|
||
$('body').popover({
|
||
selector: ".tree-diy-field", placement: 'bottom', html: true, sanitize: false,
|
||
content: document.getElementById('underlyingPopEditTpl').innerHTML
|
||
}).on('show.bs.popover', function (e) {
|
||
$(_popDiyId).popover('hide');
|
||
_popDiyId = '#' + $(e.target).attr('id');
|
||
}).on('shown.bs.popover', function (e) {
|
||
let tid = $(e.target).data('tid');
|
||
let tnode = _zTreeObj.getNodeByTId(tid);
|
||
$('#underlying_tid').val(tid);
|
||
$('#underlying_edit').val(tnode.mydata).on('keydown', function (event) {
|
||
let charCode = event.which || event.keyCode;
|
||
if (charCode === 13 || charCode === 108) {
|
||
pageMgr.underlying_updatePopEdit();
|
||
}
|
||
});
|
||
let isUnderlyingPrice = tnode.mytype === 'UnderlyingPrice';
|
||
FastVue.numberInput('underlying_edit', { append: isUnderlyingPrice ? "" : "%", negative: isUnderlyingPrice, precision: 4 });
|
||
$('#underlying_edit').focus();
|
||
});
|
||
}
|
||
|
||
//标的--更新编辑弹窗
|
||
this.underlying_updatePopEdit = function () {
|
||
if (!$('#underlying_edit').length) return;
|
||
let val = $('#underlying_edit').val();
|
||
let tnode = _zTreeObj.getNodeByTId($('#underlying_tid').val());
|
||
tnode.mydata = val && val !== '%' ? val : ' ';
|
||
$(_popDiyId).html(" " + tnode.mydata + " ").addClass("tree-diy-changed").popover('hide');
|
||
let pnode = tnode.getParentNode();
|
||
pnode.mydata[tnode.mytype] = FastVue.parseNumber(val) || '';
|
||
};
|
||
|
||
//标的--关闭编辑弹窗
|
||
this.underlying_closePopEdit = function () {
|
||
$(_popDiyId).popover('hide');
|
||
};
|
||
|
||
//导出--获取导出头部
|
||
this.export_getHead = function () {
|
||
let data = _.clone(searchVue.CalcData);
|
||
data.Norms = searchVue.Norms;
|
||
data.Underlyings = pageMgr.underlying_checked();
|
||
!_exportHeadCompiled && (_exportHeadCompiled = _.template($('#exportHeaderTpl').html()));
|
||
let html = _exportHeadCompiled(data);
|
||
let $table = $(html);
|
||
$table.find('th').css('background', '#6F9EBA');
|
||
let $tbody = $table.children('tbody');
|
||
return ($tbody.length ? $tbody : $table).html();
|
||
};
|
||
|
||
//导出--汇总标的导出
|
||
function export_underlying() {
|
||
let $table = $('<table>').html($("#excelTable").html())
|
||
$table.find('a').replaceWith(function (i, cont) {
|
||
return cont;
|
||
});
|
||
$table.find('.tdstrip').css('background', '#C6E2FF');
|
||
$table.find('thead th').css('color', '#fff').css('background', '#3f484d');
|
||
let head = _self.export_getHead();
|
||
let content = _.map($table.children(), x => x.innerHTML).join();
|
||
$("#excelTable_export").html(head + content);
|
||
ExcellentExport.excel(this, 'excelTable_export', '情景计算');
|
||
}
|
||
|
||
//导出--按标的品种导出
|
||
function export_variety() {
|
||
let $table = $('<table>').html($("#excelTable2").html())
|
||
$table.find('.tdstrip').css('background', '#C6E2FF');
|
||
$table.find('th').css('color', '#fff').css('background', '#3f484d');
|
||
let head = _self.export_getHead();
|
||
let content = $table.html();
|
||
$("#excelTable_export").html(head + content);
|
||
ExcellentExport.excel(this, 'excelTable_export', '情景计算');
|
||
}
|
||
//导出--按标的导出
|
||
function export_underlyings() {
|
||
let $table = $('<table>').html($("#excelTable3").html())
|
||
$table.find('.tdstrip').css('background', '#C6E2FF');
|
||
$table.find('th').css('color', '#fff').css('background', '#3f484d');
|
||
let head = _self.export_getHead();
|
||
let content = $table.html();
|
||
$("#excelTable_export").html(head + content);
|
||
ExcellentExport.excel(this, 'excelTable_export', '情景计算');
|
||
}
|
||
$(function () {
|
||
underlying_initPopEdit();
|
||
$('#export_excel_byVariety').on('click', export_variety);
|
||
$('#export_excel_byUnderlying').on('click', export_underlying);
|
||
$('#export_excel_byUnderlyings').on('click', export_underlyings);
|
||
});
|
||
|
||
}());
|
||
|
||
//查询视图
|
||
const searchVue = new Vue({
|
||
el: '#searchdiv',
|
||
data: {
|
||
Norms: ['Pv'],
|
||
VolType: '对冲',
|
||
ValueDate: pageData.ValueDate,
|
||
RiskFreeRate: pageData.SysRiskFreeRate,
|
||
UseTradeDivendRate: true,
|
||
IsFullyHedged: true,
|
||
ConfigId: 0,
|
||
CalcData: {
|
||
updateKey: 0, xType: '', yType: '', xRates: [], yRates: [],
|
||
ConfigName: '', ValueDate: '', RiskFreeRate: '', VolType: '', Underlyings: null
|
||
}
|
||
},
|
||
mounted() {
|
||
this.ConfigId = pageData.ScenConfigs.length ? pageData.ScenConfigs[0].id : 0;
|
||
},
|
||
methods: {
|
||
scenChange() {
|
||
resultVue.scenChange(this.ConfigId);
|
||
},
|
||
calculate() {
|
||
let data = {
|
||
ConfigId: this.ConfigId,
|
||
SearchModel: pageData.SearchModel,
|
||
VolType: this.VolType,
|
||
RiskFreeRate: this.RiskFreeRate,
|
||
ValueDate: this.ValueDate,
|
||
UseTradeDivendRate: this.UseTradeDivendRate,
|
||
IsFullyHedged: this.IsFullyHedged,
|
||
Underlyings: pageMgr.underlying_forCalc()
|
||
};
|
||
let self = this;
|
||
main.post('/ScenarioAnalysis/AjaxScenarioCalc', data).done(function (resp) {
|
||
Object.assign(self.CalcData, data, resp);
|
||
pageMgr.underlying_updateTree(resp.Underlyings);
|
||
resultVue.xType = resp.xType;
|
||
resultVue.yType = resp.yType;
|
||
resultVue.xRates = resp.xRates;
|
||
resultVue.yRates = resp.yRates;
|
||
resultVue.calcResults = resp.ResultItems;
|
||
resultVue.updateKey++;
|
||
});
|
||
},
|
||
changeVolType() {
|
||
pageMgr.underlying_updateTree_clearVol();
|
||
},
|
||
changeUseTradeDivendRate() {
|
||
pageMgr.underlying_updateTree_clearDividenRate();
|
||
},
|
||
changeIsFullyHedged() {
|
||
this.calculate();
|
||
}
|
||
},
|
||
components: {
|
||
'vue-number-input': FastVue.vueNumberInput()
|
||
}
|
||
});
|
||
|
||
//指标折线图
|
||
const chartMgr = (new function () {
|
||
var myChart, _xtype = "", _ytype = "";
|
||
|
||
//初始化chart控件
|
||
function __init() {
|
||
let data = {
|
||
labels: [],
|
||
datasets: [
|
||
{
|
||
label: 'PV', data: [],
|
||
borderColor: '#FF6384',
|
||
backgroundColor: '#FF6384'
|
||
}
|
||
]
|
||
};
|
||
let config = {
|
||
type: 'line',
|
||
data: data,
|
||
options: {
|
||
responsive: false,
|
||
plugins: {
|
||
legend: {
|
||
position: 'right', title: {
|
||
display: true, text: '', font: { size: 20, weight: 'bold', lineHeight: 1.2 }
|
||
}
|
||
},
|
||
title: { display: true, text: '' }
|
||
},
|
||
scales: {
|
||
x: {
|
||
display: true,
|
||
title: {
|
||
display: true, text: '', color: '#911',
|
||
font: { size: 20, weight: 'bold', lineHeight: 1.2 },
|
||
padding: { top: 4, left: 0, right: 0, bottom: 0 }
|
||
}
|
||
},
|
||
y: {
|
||
display: true,
|
||
title: {
|
||
display: true, text: '', color: '#191',
|
||
font: { size: 20, weight: 'bold', lineHeight: 1.2 }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
var ctx = document.getElementById("myChart").getContext("2d");
|
||
myChart = new Chart(ctx, config);
|
||
};
|
||
|
||
function __showLayer() {
|
||
layer.open({
|
||
type: 1,
|
||
title: '指标折线图',
|
||
closeBtn: 1,
|
||
area: ['900px', '700px'],
|
||
shadeClose: true,
|
||
content: $('#myChartDiv')
|
||
});
|
||
}
|
||
|
||
const consColors = ["#1DC9B7", "#FFC241", "#0E52BE", "#51ADF6", "#FF7270", "#965AFF", "#387EF0", "#FD3995"];
|
||
|
||
function getRandomColor() {
|
||
var letters = '0123456789ABCDEF';
|
||
var color = '#';
|
||
for (var i = 0; i < 6; i++) {
|
||
color += letters[Math.floor(Math.random() * 16)];
|
||
}
|
||
return color;
|
||
}
|
||
|
||
//在弹窗上显示图表
|
||
function __showChart(resultRows, norm, byClick) {
|
||
if (byClick) {
|
||
$(this).parent().children().removeClass('active');
|
||
$(this).addClass('active');
|
||
}
|
||
!norm && (norm = resultRows[0].norm);
|
||
let datasets = [], index = 0;
|
||
for (let dataRow of resultRows) {
|
||
if (dataRow.norm === norm) {
|
||
let color = consColors[index++] || getRandomColor();
|
||
datasets.push({
|
||
label: dataRow.label,
|
||
data: dataRow.values.map(x => parseFloat(x.replaceAll(',', ''))),
|
||
borderColor: color, backgroundColor: color
|
||
});
|
||
}
|
||
}
|
||
myChart.data.datasets = datasets;
|
||
myChart.config.options.scales.x.title.text = _xtype;
|
||
myChart.config.options.scales.y.title.text = norm;
|
||
//myChart.config.options.plugins.title.text = _ytype;
|
||
myChart.config.options.plugins.legend.title.text = _ytype;
|
||
myChart.update();
|
||
}
|
||
|
||
//在弹窗上显示指标按钮
|
||
function __showNormBtns(resultRows) {
|
||
let firstRow = resultRows[0];
|
||
let $s = $('#myChartDiv').children(':last'); $s.html('');
|
||
for (var i = 0; i < firstRow.rowspan; i++) {
|
||
let row = resultRows[i];
|
||
let $h = $('<a class="chart-action">' + row.norm + '</a>')
|
||
$h.on('click', __showChart.bind($h.get(0), resultRows, row.norm, true));
|
||
(i === 0) && ($h.addClass('active'));
|
||
$h.appendTo($s);
|
||
}
|
||
}
|
||
|
||
//颠倒XY位置
|
||
function __invertXY(resultRows, xRates) {
|
||
let objArr = xRates.map(x => {
|
||
return { label: x };
|
||
});
|
||
resultRows.forEach(row => {
|
||
for (let x = 0; x < objArr.length; x++) {
|
||
let obj = objArr[x];
|
||
if (!(row.norm in obj)) {
|
||
obj[row.norm] = [];
|
||
}
|
||
obj[row.norm].push(row.values[x]);
|
||
}
|
||
});
|
||
|
||
let mapArr = objArr.map(x => {
|
||
let arr = [];
|
||
for (let [k, v] of Object.entries(x)) {
|
||
k !== 'label' && arr.push({ norm: k, values: v, label: x.label });
|
||
}
|
||
arr[0].rowspan = arr.length;
|
||
return arr;
|
||
});
|
||
|
||
return mapArr.flat();
|
||
}
|
||
|
||
this.setXyTypeDesc = function (xtype, ytype) {
|
||
_xtype = xtype;
|
||
_ytype = ytype;
|
||
};
|
||
|
||
//显示Y轴指标线图
|
||
this.showChartY = function (resultRows, xRates, yRates) {
|
||
!myChart && __init();
|
||
|
||
resultRows = resultRows.map(x => {
|
||
let clone = Object.assign({}, x);
|
||
clone.label = clone.yRate;
|
||
return clone;
|
||
});
|
||
|
||
__showNormBtns(resultRows);
|
||
|
||
myChart.data.labels = xRates;
|
||
|
||
__showChart(resultRows)
|
||
|
||
__showLayer();
|
||
};
|
||
|
||
//显示X轴指标线图
|
||
this.showChartX = function (resultRows, xRates, yRates) {
|
||
!myChart && __init();
|
||
|
||
resultRows = __invertXY(resultRows, xRates);
|
||
|
||
__showNormBtns(resultRows);
|
||
|
||
myChart.data.labels = yRates;
|
||
|
||
__showChart(resultRows)
|
||
|
||
__showLayer();
|
||
};
|
||
}());
|
||
|
||
//结果表格
|
||
const resultVue = new Vue({
|
||
el: '#resultdiv',
|
||
data: {
|
||
xType: '标的价格',
|
||
yType: '波动率',
|
||
xRates: [],
|
||
yRates: [],
|
||
calcResults: null,
|
||
updateKey: 0,
|
||
vResults: [],
|
||
vUnderlyingResults:[]
|
||
},
|
||
computed: {
|
||
resultRows() {
|
||
let norms = searchVue.Norms, xyMap = null;
|
||
if (this.updateKey && this.calcResults && this.calcResults.length) {
|
||
let selectedUnderlyingObj = pageMgr.underlying_getSelectedObj();
|
||
xyMap = selectedUnderlyingObj ? this.calcResults.reduce((acc, cur) => {
|
||
if (cur.UnderlyingCode.toUpperCase() in selectedUnderlyingObj) {
|
||
let key = cur.xIndex + '_' + cur.yIndex;
|
||
if (key in acc) {
|
||
let item = acc[key];
|
||
for (let [k, v] of Object.entries(item)) {
|
||
item[k] += cur[k];
|
||
}
|
||
} else {
|
||
acc[key] = _.cloneDeep(cur);
|
||
}
|
||
}
|
||
return acc;
|
||
}, {}) : null;
|
||
}
|
||
let results = [], yIndex = 0;
|
||
for (let yRate of this.yRates) {
|
||
let normIndex = 0;
|
||
for (let norm of norms) {
|
||
let data = {
|
||
norm: norm,
|
||
yRate: yRate,
|
||
rowspan: normIndex > 0 ? 0 : norms.length,
|
||
values: new Array(this.xRates.length),
|
||
tdStyle: normIndex % 2 === 1 ? 'tdstrip' : ''
|
||
};
|
||
if (xyMap) {
|
||
this.xRates.forEach((value, xIndex) => {
|
||
let key = xIndex + '_' + yIndex;
|
||
if (norm === 'PnLPercent') {
|
||
let pnl = xyMap[key]['PnL'];
|
||
let zeroPv = xyMap[key]['ZeroPv'];
|
||
data.values[xIndex] = Math.abs(zeroPv) > 0 ? (pnl / zeroPv * 100).toFixed(2) + '%' : '0%';
|
||
} else {
|
||
data.values[xIndex] = GreekFormat(xyMap[key][norm]);
|
||
}
|
||
});
|
||
}
|
||
normIndex++;
|
||
results.push(data);
|
||
}
|
||
yIndex++;
|
||
}
|
||
if (results.length > 0 && window.parent.getTestStatus()) {
|
||
var arr = [];
|
||
results.map((item) => item.values.map((item2) => arr.push(item2)));
|
||
if (arr.length > 0) {
|
||
window.parent.setTestValue(arr);
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
},
|
||
mounted() {
|
||
this.scenChange(searchVue.ConfigId);
|
||
},
|
||
methods: {
|
||
scenChange(configId) {
|
||
if (this.calcResults) return;
|
||
let config = pageData.ScenConfigs.find(x => x.id === searchVue.ConfigId);
|
||
if (!config) return;
|
||
let xRates = _.split(config.xRates, /[,,]/);
|
||
let yRates = _.split(config.yRates, /[,,]/);
|
||
|
||
this.xType = config.xType;
|
||
this.yType = config.yType;
|
||
this.xRates = _.drop(xRates, x => !_.trim(x));
|
||
this.yRates = _.drop(yRates, x => !_.trim(x));
|
||
},
|
||
exportByUnderlying() {
|
||
let el = document.getElementById('export_excel_byUnderlying');
|
||
el.setAttribute('download', "情景分析_" + searchVue.CalcData.ConfigName);
|
||
el.click();
|
||
},
|
||
exportByVariety() {
|
||
this.sumResultByVariety();
|
||
this.$nextTick(function () {
|
||
let el = document.getElementById('export_excel_byVariety');
|
||
el.setAttribute('download', "情景分析_品种_" + searchVue.CalcData.ConfigName);
|
||
el.click();
|
||
});
|
||
},
|
||
exportByUnderlyings() {
|
||
this.sumResultByUnderlying();
|
||
this.$nextTick(function () {
|
||
let el = document.getElementById('export_excel_byUnderlyings');
|
||
el.setAttribute('download', "情景分析_标的_" + searchVue.CalcData.ConfigName);
|
||
el.click();
|
||
});
|
||
},
|
||
sumResultByVariety() {
|
||
let norms = searchVue.Norms, vxyMap = null;
|
||
if (this.calcResults && this.calcResults.length) {
|
||
let selectedUnderlyingObj = pageMgr.underlying_getSelectedObj();
|
||
vxyMap = selectedUnderlyingObj ? this.calcResults.reduce((acc, cur) => {
|
||
if (cur.UnderlyingCode.toUpperCase() in selectedUnderlyingObj) {
|
||
let vaCode = selectedUnderlyingObj[cur.UnderlyingCode.toUpperCase()].VarietyCode || '未知';
|
||
let accItem = vaCode in acc ? acc[vaCode] : (acc[vaCode] = {});
|
||
let xyKey = cur.xIndex + '_' + cur.yIndex;
|
||
if (xyKey in accItem) {
|
||
let item = accItem[xyKey];
|
||
for (let [k, v] of Object.entries(item)) {
|
||
item[k] += cur[k];
|
||
}
|
||
} else {
|
||
accItem[xyKey] = _.cloneDeep(cur);
|
||
}
|
||
}
|
||
return acc;
|
||
}, {}) : null;
|
||
}
|
||
if (!vxyMap) return null;
|
||
let results = [];
|
||
for (let [vCode, xyMap] of Object.entries(vxyMap)) {
|
||
let vResult = { VarietyCode: vCode, rows: [] }, yIndex = 0;
|
||
for (let yRate of this.yRates) {
|
||
let normIndex = 0;
|
||
for (let norm of norms) {
|
||
let data = {
|
||
norm: norm,
|
||
yRate: normIndex > 0 ? "" : yRate,
|
||
rowspan: normIndex > 0 ? '' : norms.length,
|
||
values: new Array(this.xRates.length),
|
||
tdStyle: normIndex % 2 === 1 ? 'tdstrip' : ''
|
||
};
|
||
this.xRates.forEach((value, xIndex) => {
|
||
let xyKey = xIndex + '_' + yIndex;
|
||
if (norm === 'PnLPercent') {
|
||
let pnl = xyMap[xyKey]['PnL'];
|
||
let zeroPv = xyMap[xyKey]['ZeroPv'];
|
||
data.values[xIndex] = Math.abs(zeroPv) > 0 ? (pnl / zeroPv * 100).toFixed(2) + '%' : '0%';
|
||
} else {
|
||
data.values[xIndex] = GreekFormat(xyMap[xyKey][norm]);
|
||
}
|
||
});
|
||
normIndex++;
|
||
vResult.rows.push(data);
|
||
}
|
||
yIndex++;
|
||
}
|
||
results.push(vResult);
|
||
}
|
||
return this.vResults = results;
|
||
},
|
||
sumResultByUnderlying() {
|
||
let norms = searchVue.Norms, udyMap = null;
|
||
if (this.calcResults && this.calcResults.length) {
|
||
let selectedUnderlyingObj = pageMgr.underlying_getSelectedObj();
|
||
udyMap = selectedUnderlyingObj ? this.calcResults.reduce((acc, cur) => {
|
||
if (cur.UnderlyingCode.toUpperCase() in selectedUnderlyingObj) {
|
||
let vaCode = selectedUnderlyingObj[cur.UnderlyingCode.toUpperCase()].UnderlyingCode || '未知';
|
||
let accItem = vaCode in acc ? acc[vaCode] : (acc[vaCode] = {});
|
||
let xyKey = cur.xIndex + '_' + cur.yIndex;
|
||
if (xyKey in accItem) {
|
||
let item = accItem[xyKey];
|
||
for (let [k, v] of Object.entries(item)) {
|
||
item[k] += cur[k];
|
||
}
|
||
} else {
|
||
accItem[xyKey] = _.cloneDeep(cur);
|
||
}
|
||
}
|
||
return acc;
|
||
}, {}) : null;
|
||
}
|
||
if (!udyMap) return null;
|
||
let results = [];
|
||
for (let [vCode, dyMap] of Object.entries(udyMap)) {
|
||
let vResult = { UnderlyingCode: vCode, rows: [] }, yIndex = 0;
|
||
for (let yRate of this.yRates) {
|
||
let normIndex = 0;
|
||
for (let norm of norms) {
|
||
let data = {
|
||
norm: norm,
|
||
yRate: normIndex > 0 ? "" : yRate,
|
||
rowspan: normIndex > 0 ? '' : norms.length,
|
||
values: new Array(this.xRates.length),
|
||
tdStyle: normIndex % 2 === 1 ? 'tdstrip' : ''
|
||
};
|
||
this.xRates.forEach((value, xIndex) => {
|
||
let xyKey = xIndex + '_' + yIndex;
|
||
if (norm === 'PnLPercent') {
|
||
let pnl = dyMap[xyKey]['PnL'];
|
||
let zeroPv = dyMap[xyKey]['ZeroPv'];
|
||
data.values[xIndex] = Math.abs(zeroPv) > 0 ? (pnl / zeroPv * 100).toFixed(2) + '%' : '0%';
|
||
} else {
|
||
data.values[xIndex] = GreekFormat(dyMap[xyKey][norm]);
|
||
}
|
||
});
|
||
normIndex++;
|
||
vResult.rows.push(data);
|
||
}
|
||
yIndex++;
|
||
}
|
||
results.push(vResult);
|
||
}
|
||
return this.vUnderlyingResults = results;
|
||
},
|
||
showChartY(yIndex) {
|
||
chartMgr.setXyTypeDesc(this.xType, this.yType);
|
||
this.resultRows.length && chartMgr.showChartY(this.resultRows, this.xRates, this.yRates);
|
||
},
|
||
showChartX(xIndex) {
|
||
chartMgr.setXyTypeDesc(this.yType, this.xType);
|
||
this.resultRows.length && chartMgr.showChartX(this.resultRows, this.xRates, this.yRates);
|
||
}
|
||
}
|
||
});
|
||
|
||
$(function () {
|
||
if (window.parent.getTestStatus()) {
|
||
searchVue.Norms[0] = "PnL";
|
||
searchVue.calculate();
|
||
}
|
||
}); |