1007 lines
42 KiB
JavaScript
1007 lines
42 KiB
JavaScript
const consStoreKey = "volsurfaceedit" + pageVue.UserId;
|
|
const consDefaultVol = 0.3;
|
|
const consUnderlyingFlag = (function () {
|
|
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
|
|
return unSelFlag.UseForTrading | unSelFlag.IncludeMatured | unSelFlag.UsePinYinFilter;
|
|
}());
|
|
const consPVolPrecision = pageVue.VolMoreAccurate ? 4 : 2;
|
|
const consVolFmt = main.numberFormat(pageVue.VolMoreAccurate ? 6 : 4);
|
|
const consPercentVolFmt = main.numberFormat(pageVue.VolMoreAccurate ? 4 : 2, { percent: true });
|
|
|
|
function upLoadVolsurface() {
|
|
var addurl = "/volatility/volsurfaceUpload/0";
|
|
main.infopage("导入波动率曲面数据", addurl);
|
|
}
|
|
|
|
function downLoadVolsurface() {
|
|
var addurl = "/volatility/volsurfaceDown/0";
|
|
main.infopage("批量导出波动率曲面数据", addurl);
|
|
}
|
|
|
|
var autoVariety, autoUnderlying;
|
|
|
|
function __init(vue) {
|
|
|
|
//标的品种
|
|
autoVariety = FastVue.autocomplete(document.getElementById('VarietyId'), {
|
|
lookup: ylotc.varieties, nameField: 'Name', valueField: 'id', searchField: ['Name', 'Code', 'PinYin'],
|
|
onSelect: function (data) { vue.changeVariety(data, 'select'); }
|
|
});
|
|
|
|
//标的资产
|
|
autoUnderlying = tradeHelper.UnderlyingAutoComplete('UnderlyingCode').setFlag(consUnderlyingFlag);
|
|
autoUnderlying.onSelect(vue.changeUnderlying);
|
|
|
|
//标的控件初始化
|
|
let filterCode = '';
|
|
let storeData = localStorage.getItem(consStoreKey);
|
|
if (storeData) {
|
|
try {
|
|
storeData = JSON.parse(storeData);
|
|
typeof storeData === 'object' && (filterCode = storeData.underlyingCode);
|
|
} catch {
|
|
//
|
|
}
|
|
}
|
|
autoUnderlying.selectFirst({ ExcatCodeFilter: !!filterCode, FilterCode: filterCode, MustRetOne: true });
|
|
}
|
|
|
|
var vue = new Vue({
|
|
el: "#listdiv",
|
|
data: {
|
|
Vollist: [],
|
|
VolTypes: [],
|
|
expireList: [{ Expire: "", Vol: [{ Strike: "", VolValue: "" }] }],
|
|
orderStrikes: [{ Vol: "", Strike: "" }],
|
|
Model: {
|
|
VolSurfaceMode: "MoneynessVol", InterpolationMethod: "BiLinear",
|
|
ReviewDownLimit: 0, ReviewUpLimit: 1, VolType: "交易",
|
|
MaintainDate: pageVue.ValueDate,
|
|
CheckAddExpire: true, CheckAddStrike: true
|
|
},
|
|
Variety: tradeHelper.getEmptyVariety(),
|
|
Underlying: Object.assign(tradeHelper.getEmptyUnderlying(), { ExerciseDate: '' }),
|
|
updateKey: { variety: 0, underlying: 0, volstate: 0 },
|
|
saveVolFlag: pageVue.HasSaveRight ? 1 | 2 : 0
|
|
},
|
|
mounted() {
|
|
__init(this);
|
|
},
|
|
computed: {
|
|
StrikeDesc() {
|
|
return this.Model.VolSurfaceMode === "MoneynessVol" ? "MONEYNESS(K/S)" : "行权价";
|
|
},
|
|
SetSaveVol() {
|
|
return this.saveVolFlag === 7;
|
|
},
|
|
},
|
|
filters: {
|
|
SetVolTypeName(rawName) {
|
|
switch (rawName) {
|
|
case "交易": return "交易Mid";
|
|
case "报价Bid": return "交易Bid";
|
|
case "报价Ask": return "交易Ask";
|
|
default: return rawName;
|
|
}
|
|
},
|
|
toInt(d) {
|
|
return d.toFixed(0);
|
|
},
|
|
toPercent(d) {
|
|
return numeral(d).format("0.0%");
|
|
}
|
|
},
|
|
methods: {
|
|
//变更标的类型
|
|
changeInstrumentType() {
|
|
let nowTime = new Date().getTime();
|
|
if (nowTime < this.updateKey.variety + 300 || nowTime < this.updateKey.underlying + 300) return;
|
|
this.updateKey.instrumentType = nowTime;
|
|
autoUnderlying.selectFirst({ InstrumentTypes: [this.Underlying.InstrumentType] });
|
|
},
|
|
//设置标的品种
|
|
changeVariety(variety, flag) {
|
|
if (flag === 'select') {
|
|
this.updateKey.variety = new Date().getTime();
|
|
this.Underlying.UnderlyingInstrumentType = variety.InstrumentType;
|
|
autoUnderlying.setVarietyId(variety.id);
|
|
Object.assign(this.Underlying, tradeHelper.getEmptyUnderlying(variety.id));
|
|
autoUnderlying.selectFirst(variety.id);
|
|
} else {
|
|
autoVariety.setData(variety);
|
|
}
|
|
Object.assign(this.Variety, variety || tradeHelper.getEmptyVariety());
|
|
},
|
|
//变更标的
|
|
changeUnderlying(data) {
|
|
this.updateKey.underlying = new Date().getTime();
|
|
Object.assign(this.Underlying, data);
|
|
autoUnderlying.setVarietyId(data.VarietyId);
|
|
this.changeVariety(data.VarietyId ? ylotc.varieties.find(x => x.id === data.VarietyId) : null);
|
|
this.updateVolTable();
|
|
this.Model.CheckAddExpire = true;
|
|
this.saveVolFlag |= 4;
|
|
if (["CommodityFutures", "StockIF"].includes(data.InstrumentType) && this.Underlying.MaturityDate < this.Model.MaintainDate) {
|
|
this.saveVolFlag ^= 4;
|
|
}
|
|
},
|
|
//变更维护日期
|
|
changeMaintainDate() {
|
|
this.saveVolFlag |= 2;
|
|
if (pageVue.ValueDate !== this.Model.MaintainDate) {
|
|
this.saveVolFlag ^= 2;
|
|
}
|
|
this.updateVolTable();
|
|
},
|
|
updateVolTable() {
|
|
this.changeVolType();
|
|
},
|
|
excelOutput() {
|
|
var thisObj = this;
|
|
var quotationDate = thisObj.Model.MaintainDate;
|
|
g_v_fileName = "{0}-{1}-{2}".template(thisObj.Underlying.Code, thisObj.Model.VolType, quotationDate);
|
|
g_v_tagArray = [];
|
|
$('#exceloutputWrap').html('');
|
|
$.each(thisObj.VolTypes, function (i, v) {
|
|
var tabName = "{1}".template(thisObj.Underlying.Code, thisObj.$options.filters.SetVolTypeName(v.VolModelName).replace('交易', ''), quotationDate);
|
|
//使用复制的临时表以避免执行导出后导出源表不再跟随标的和波动率类型的改变而改变
|
|
var tempTableName = 'temp_export_' + i;
|
|
var tempTableHtml = $('#table' + v.VolModelName).html();
|
|
var $tempTable = $('<div id="' + tempTableName + '"></div>').append(tempTableHtml).appendTo($('#exceloutputWrap'));
|
|
$tempTable.find('th,td').each(function () {
|
|
$(this).text($.trim($(this).text()));
|
|
});
|
|
var tagV = { name: tabName, from: { table: tempTableName } };
|
|
g_v_tagArray.push(tagV);
|
|
});
|
|
$('#exceloutput').click(
|
|
function () {
|
|
return ExcellentExport.convert({ anchor: this, filename: g_v_fileName, format: 'xlsx' }, g_v_tagArray);
|
|
}
|
|
);
|
|
document.getElementById('exceloutput').click();
|
|
},
|
|
changeVolType() { //
|
|
var thisObj = this;
|
|
var ucode = this.Underlying.Code;
|
|
if (!ucode || !this.Model.MaintainDate) {
|
|
return main.alert("请选择标的和维护日期");
|
|
}
|
|
this.updateKey.volstate++;
|
|
var doneFunction = function (res) {
|
|
res = res.obj;
|
|
thisObj.Underlying.ExerciseDate = moment(res.underlying.ExerciseDate).format("YYYY-MM-DD");
|
|
thisObj.Underlying.MaturityDate = res.underlying.MaturityDate ? moment(res.underlying.MaturityDate).format("YYYY-MM-DD") : '';
|
|
thisObj.Underlying.Price = main.toNumberExcludingNaN(res.underlying.Price);
|
|
thisObj.VolTypes = res.VolTypes;
|
|
thisObj.sortVolTypes();
|
|
//排序 volatity 交易为第一个
|
|
if (res.all_volatilities.length > 1) {
|
|
res.all_volatilities.sort(function (a, b) {
|
|
if (a.VolType == "交易") return -1;
|
|
if (b.VolType == "交易") return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
thisObj.Vollist = res.all_volatilities;
|
|
|
|
if (thisObj.Vollist.length > 0) {
|
|
thisObj.Model.VolSurfaceMode = thisObj.Vollist[0].VolSurfaceMode;
|
|
$.each(thisObj.Vollist, function (i, vol) {
|
|
if (vol.VolTable) {
|
|
$.each(vol.VolTable, function (ii, vv) {
|
|
if (pageVue.VolMoreAccurate) {
|
|
vv.PercentVol = numeral(vv.Vol * 100).format("0.0000");
|
|
}
|
|
else {
|
|
vv.PercentVol = numeral(vv.Vol * 100).format("0.00");
|
|
}
|
|
//补丁
|
|
delete vv.IsNewStrike; //删除以免保存
|
|
delete vv.IsNewExpire;
|
|
});
|
|
vol.Data = JSON.stringify(vol.VolTable);
|
|
}
|
|
});
|
|
thisObj.Model.InterpolationMethod = thisObj.Vollist[0].InterpolationMethod;
|
|
thisObj.Model.ReviewDownLimit = (thisObj.Vollist[0].ReviewDownLimit * 100).toFixed(0);
|
|
thisObj.Model.ReviewUpLimit = (thisObj.Vollist[0].ReviewUpLimit * 100).toFixed(0);
|
|
}
|
|
|
|
thisObj.loadVollist();
|
|
};
|
|
|
|
if (thisObj.Model.VolType === "隐含") {
|
|
main.post("/volatility/GetImpliedVolSurface", { underlyingCode: ucode, valueDate: thisObj.Model.MaintainDate }).done(doneFunction);
|
|
} else {
|
|
main.post("/volatility/get_underlyingvolsurface", { systemdate: thisObj.Model.MaintainDate, ucode: ucode, voltype: thisObj.Model.VolType }).done(doneFunction);
|
|
}
|
|
},
|
|
sortVolTypes() {
|
|
var thisObj = this;
|
|
//VolTypes 显示排序补丁
|
|
var typeSort = ["报价Bid", "交易", "报价Ask"];
|
|
thisObj.VolTypes.sort(function (a, b) {
|
|
var aindex = typeSort.indexOf(a.VolModelName);
|
|
var bindex = typeSort.indexOf(b.VolModelName);
|
|
return aindex - bindex;
|
|
});
|
|
|
|
},
|
|
getUnderlyingPrice() {
|
|
var self = this;
|
|
main.post("/pricing/AjaxGetUnderlyingPrice", { underlyingCode: this.Underlying.Code }).done(function (resp) {
|
|
self.Underlying.Price = consFormat.umprice(resp.obj.price);
|
|
});
|
|
},
|
|
checkvols(event) {
|
|
//检查vol的数字必需为正数.
|
|
//2. 同一个vol grid, 检查所有数字,发现max > 10 * min,就报错,提示交易员有问题。纠正后再保存。
|
|
//如果用户选择忽略,则允许保存。
|
|
var thisObj = this;
|
|
if (event) {
|
|
var vol = $(event.target).val();
|
|
if (vol <= 0) {
|
|
$(event.target).focus();
|
|
main.message("波动率请输入正数");
|
|
return;
|
|
}
|
|
}
|
|
|
|
var maxVol = { Vol: 0 };
|
|
var minVol = { Vol: 99999 };
|
|
$.grep(thisObj.Vollist, function (vdata, vindex) {
|
|
$.grep(vdata.VolTable, function (data, index) {
|
|
data.Vol = data.PercentVol / 100;
|
|
if (data.Vol) {
|
|
if (data.Vol > maxVol.Vol) {
|
|
maxVol = data;
|
|
}
|
|
|
|
if (data.Vol < minVol.Vol) {
|
|
minVol = data;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
if (maxVol.Vol > 10 * minVol.Vol) {
|
|
main.message("警告,最大Vol超过10倍最小Vol");
|
|
}
|
|
},
|
|
getVol(expire, strike, voltype) {
|
|
var thisObj = this;
|
|
var pickVol = null;
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
if (vd.VolType == voltype.VolModelName) {
|
|
$.grep(vd.VolTable, function (d, i) {
|
|
if (d.Strike == strike.Strike && d.Expire == expire.Expire) {
|
|
pickVol = d; return;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
if (pickVol == null) {
|
|
//增加默认值
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
if (vd.VolType == voltype.VolModelName) {
|
|
pickVol = { PercentVol: consDefaultVol * 100, VolModelName: voltype.VolModelName, Strike: strike.Strike, Expire: expire.Expire, Vol: consDefaultVol };
|
|
vd.VolTable.push(pickVol);
|
|
}
|
|
});
|
|
}
|
|
if (!pickVol) return { Vol: 0 };
|
|
|
|
if (pageVue.VolMoreAccurate) {
|
|
//Vol值精确到小数点六位
|
|
let digit = Math.pow(10, 6);
|
|
pickVol.Vol = Math.round(pickVol.PercentVol / 100 * digit) / digit;
|
|
}
|
|
else {
|
|
//Vol值精确到小数点四位
|
|
let digit = Math.pow(10, 4);
|
|
pickVol.Vol = Math.round(pickVol.PercentVol / 100 * digit) / digit;
|
|
}
|
|
return pickVol;
|
|
},
|
|
checkAddExpire(obj) {
|
|
var thisObj = this;
|
|
var newExpire = $("#newAddExpire").val();
|
|
if (!newExpire) {
|
|
main.message("请输入正确的期限");
|
|
thisObj.Model.CheckAddExpire = false;
|
|
return;
|
|
}
|
|
if (this.expireList.some(y => y.Expire == newExpire)) {
|
|
main.message("请输入不同的期限");
|
|
thisObj.Model.CheckAddExpire = false;
|
|
} else {
|
|
thisObj.Model.CheckAddExpire = true;
|
|
}
|
|
this.checkvols();
|
|
},
|
|
checkAddStrike() {
|
|
var thisObj = this;
|
|
var newStrike = $("#newAddStrike").val();
|
|
if (newStrike <= 0) {
|
|
main.alert("请输入正数行权价");
|
|
thisObj.Model.CheckAddStrike = false;
|
|
return false;
|
|
}
|
|
//1.001 最多保留
|
|
newStrike = numeral(newStrike).format("0.000");
|
|
newStrike = numeral(newStrike).value();
|
|
$("#newAddStrike").val(newStrike);
|
|
if (thisObj.orderStrikes.some(function (data, index) { return data.Strike == newStrike })) {
|
|
main.message("已经存在相同行权价");
|
|
thisObj.Model.CheckAddStrike = false;
|
|
$("#newAddStrike").focus();
|
|
return false;
|
|
}
|
|
|
|
thisObj.Model.CheckAddStrike = true;
|
|
},
|
|
addStrike() {
|
|
if (this.Vollist.some(x => x.VolTable.some(y => y.IsNewStrike))) {
|
|
return main.message("已经有新增行权价");
|
|
}
|
|
if (this.Vollist.some(x => x.VolTable.some(y => y.IsNewExpire))) {
|
|
return main.message("请先新增到期日");
|
|
}
|
|
var thisObj = this;
|
|
if (this.Vollist.length < 1) {
|
|
let strike = "";
|
|
this.orderStrikes.push({ ValidState: undefined, IsNewStrike: true, Strike: strike });
|
|
let volTable = [{ Strike: strike, IsNewStrike: true, Expire: "1D", Vol: 0.3, PercentVol: "30.00" }];
|
|
let baseVol = {
|
|
VolTable: volTable, Data: JSON.stringify(volTable),
|
|
VolSurfaceMode: "MoneynessVol",
|
|
UnderlyingId: this.Underlying.id, ContractCode: this.Underlying.Code,
|
|
ReviewDownLimit: this.Model.ReviewDownLimit, ReviewUpLimit: this.Model.ReviewUpLimit,
|
|
InterpolationMethod: this.Model.InterpolationMethod
|
|
};
|
|
this.Vollist = this.VolList.concat(this.VolTypes.map(x => {
|
|
let newVol = _.cloneDeep(baseVol);
|
|
newVol.VolType = x;
|
|
return newVol;
|
|
}));
|
|
this.expireList.push({ ValidState: false, Expire: "1D" });
|
|
}
|
|
else {
|
|
$.each(this.Vollist, function (vi, vd) {
|
|
$.each(thisObj.expireList, function (i, d) {
|
|
//新增行权价时,复制邻近的列的波动率值(这里取第一列)
|
|
var vol = "";
|
|
var percentvol = "";
|
|
$.grep(vd.VolTable, function (value, index) {
|
|
if (value.Strike == thisObj.orderStrikes[0].Strike && value.Expire == d.Expire) {
|
|
vol = value.Vol;
|
|
percentvol = value.PercentVol;
|
|
}
|
|
});
|
|
var addVol = { Expire: d.Expire, IsNewStrike: true, Strike: "", Vol: vol, PercentVol: percentvol };
|
|
vd.VolTable.push(addVol);
|
|
});
|
|
vd.Data = JSON.stringify(vd.VolTable);
|
|
});
|
|
}
|
|
this.loadVollist();
|
|
},
|
|
addExpire() {
|
|
var thisObj = this;
|
|
|
|
if (thisObj.Vollist.some(x => x.VolTable.some(y => y.IsNewExpire))) {
|
|
main.message("已经有新增到期日");
|
|
return;
|
|
}
|
|
if (thisObj.Vollist.some(x => x.VolTable.some(y => y.IsNewStrike))) {
|
|
main.message("请先新增行权价");
|
|
return;
|
|
}
|
|
|
|
if (thisObj.Vollist.length == 0) {
|
|
var table = new Array();
|
|
var strike = 1;
|
|
thisObj.orderStrikes.push({ ValidState: undefined, IsNewStrike: undefined, Strike: strike });
|
|
var addVol = { Strike: strike, IsNewExpire: true, Expire: "", Vol: 0.3, PercentVol: "30.00" };
|
|
table.push(addVol);
|
|
$.each(thisObj.VolTypes, function (vi, vt) {
|
|
thisObj.Vollist.push({
|
|
VolTable: table, VolSurfaceMode: "MoneynessVol", Data: JSON.stringify(table), VolType: vt.VolModelName,
|
|
UnderlyingId: thisObj.Underlying.id, ContractCode: thisObj.Underlying.Code,
|
|
EncryptId: thisObj.Underlying.EncryptId, ReviewDownLimit: thisObj.Model.ReviewDownLimit, ReviewUpLimit: thisObj.Model.ReviewUpLimit,
|
|
InterpolationMethod: thisObj.Model.InterpolationMethod
|
|
});
|
|
})
|
|
thisObj.expireList.push({ ValidState: undefined, Expire: "", IsNewExpire: true });
|
|
}
|
|
else {
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
$.each(thisObj.orderStrikes, function (i, d) {
|
|
var vol = "";
|
|
var percentVol = "";
|
|
$.grep(vd.VolTable, function (value, index) {
|
|
if (value.Strike == d.Strike && value.Expire == thisObj.expireList[0].Expire) {
|
|
vol = value.Vol;
|
|
percentVol = value.PercentVol;
|
|
}
|
|
});
|
|
var addVol = { Strike: d.Strike, IsNewExpire: true, Expire: "", Vol: vol, PercentVol: percentVol };
|
|
vd.VolTable.push(addVol);
|
|
});
|
|
vd.Data = JSON.stringify(vd.VolTable);
|
|
});
|
|
}
|
|
thisObj.loadVollist();
|
|
},
|
|
deleteExpire(expire) {
|
|
var thisObj = this;
|
|
var flag = false;
|
|
$.grep(thisObj.Vollist, function (vd, vi) {
|
|
var grepExpire = [];
|
|
if (!expire.IsNewExpire) { //删除行为新增到期日时
|
|
$.grep(vd.VolTable, function (value) {
|
|
if (value.Expire != expire.Expire) {
|
|
grepExpire.push(value);
|
|
}
|
|
});
|
|
} else {
|
|
$.grep(vd.VolTable, function (value) {
|
|
if (value.Expire != expire.Expire && value.Expire != "") {
|
|
grepExpire.push(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (grepExpire.length == 0) {
|
|
|
|
flag = true;
|
|
}
|
|
if (!flag) {
|
|
if (expire.IsNewExpire) { thisObj.Model.CheckAddExpire = true; }
|
|
vd.VolTable = grepExpire;
|
|
vd.Data = JSON.stringify(vd.VolTable);
|
|
}
|
|
});
|
|
if (flag) {
|
|
main.message("不能删光到期日");
|
|
return;
|
|
}
|
|
thisObj.loadVollist();
|
|
if (expire.IsNewExpire) {
|
|
thisObj.Model.CheckAddExpire = true;
|
|
}
|
|
},
|
|
deleteStrike(strike) {
|
|
var thisObj = this;
|
|
var grepStrike = [];
|
|
var flag = false;
|
|
$.grep(thisObj.Vollist, function (vd, vi) {
|
|
var grepStrike = [];
|
|
$.grep(vd.VolTable, function (value) {
|
|
if (value.Strike != strike.Strike) {
|
|
grepStrike.push(value);
|
|
}
|
|
});
|
|
|
|
if (grepStrike.length == 0) { //长度为零不允许删光行权价
|
|
flag = true;
|
|
return;
|
|
}
|
|
vd.VolTable = grepStrike;
|
|
vd.Data = JSON.stringify(vd.VolTable);
|
|
});
|
|
if (flag) {
|
|
main.message("不能删光行权价");
|
|
return;
|
|
}
|
|
if (strike.IsNewStrike) { thisObj.Model.CheckAddStrike = true; }
|
|
thisObj.loadVollist();
|
|
},
|
|
changeReviewDownLimitMethod() {
|
|
this.Model.ReviewDownLimit = $("#ReviewDownLimit").val();
|
|
},
|
|
changeReviewUpLimitMethod() {
|
|
this.Model.ReviewUpLimit = $("#ReviewUpLimit").val();
|
|
},
|
|
loadVollist() { //为展示分析波动率数据
|
|
var thisObj = this;
|
|
//移除空
|
|
thisObj.expireList = [];
|
|
thisObj.orderStrikes = [];
|
|
var cols = new Set();
|
|
var rows = new Set();
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
if (vd.VolTable && vd.VolTable.length) {
|
|
if (thisObj.VolTypes.some(function (d, i) { return !d.VolDisabled && d.VolModelName == vd.VolType; })) {//debugger;
|
|
$.each(vd.VolTable, function (i, d) {
|
|
|
|
//补丁,有时候会出现Strike为1.0和1.00不是同一个strike情况
|
|
d.Strike = main.toNumber(d.Strike, 4);
|
|
|
|
if (typeof (d) != "function") {
|
|
if (!(cols.has(d.Expire))) {
|
|
cols.add(d.Expire);
|
|
thisObj.expireList.push({ ValidState: d.ExpireValidState, Expire: d.Expire, IsNewExpire: d.IsNewExpire, Vol: [{ VolType: d.VolType, VolValue: d.Vol, PercentVol: d.Vol * 100, Strike: d.Strike }] });
|
|
}
|
|
$.each(thisObj.expireList, function (ii, dd) {
|
|
if (dd.Expire == d.Expire) {
|
|
dd.Vol.push({ VolType: d.VolType, ValidState: d.ExpireValidState, IsNewExpire: d.IsNewExpire, Strike: d.Strike, PercentVol: d.Vol * 100, VolValue: d.Vol });
|
|
}
|
|
});
|
|
|
|
if (!(rows.has(d.Strike))) {
|
|
rows.add(d.Strike);
|
|
thisObj.orderStrikes.push({ ValidState: d.StrikeValidState, IsNewStrike: d.IsNewStrike, Strike: d.Strike });
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
thisObj.orderStrikes.sort(function sortStrike(a, b) { return a.Strike - b.Strike });
|
|
var orderExpire = ["D", "W", "M", "Y"];
|
|
thisObj.expireList.sort(function sortExpire(a, b) {
|
|
//比较到期日字母,比较数字
|
|
var adata = thisObj.getExpireDay(a.Expire);
|
|
var bdata = thisObj.getExpireDay(b.Expire);
|
|
return adata - bdata;
|
|
});
|
|
},
|
|
getExpireDay(expire) {
|
|
var mu = expire.substr(expire.length - 1, 1);
|
|
var data = expire.substr(0, expire.length - 1);
|
|
var drate = 1;
|
|
switch (mu) {
|
|
case "W": drate = 7; break;
|
|
case "M": drate = 30; break;
|
|
case "Y": drate = 365; break;
|
|
}
|
|
return data * drate;
|
|
},
|
|
checkExpire(ex) {
|
|
return /^\d+(W|Y|M|D)$/.test(ex);
|
|
},
|
|
updownVol(expire, voltype, isup) {
|
|
let upval = isup ? 0.1 : -0.1;
|
|
this.Vollist.forEach(x => {
|
|
if (voltype && x.VolType !== voltype.VolModelName) return;
|
|
x.VolTable.forEach(x => {
|
|
if (x.Expire === expire.Expire) {
|
|
let pvol = (parseFloat(x.PercentVol) || 0) + upval;
|
|
x.Vol = pvol / 100;
|
|
x.PercentVol = pvol.toFixed(consPVolPrecision);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
upExpire(expire) {
|
|
this.updownVol(expire, null, true);
|
|
},
|
|
downExpire(expire) {
|
|
this.updownVol(expire, null, false);
|
|
},
|
|
upExpireSubLine(expire, voltype) {
|
|
this.updownVol(expire, voltype, true);
|
|
},
|
|
downExpireSubLine(expire, voltype) {
|
|
this.updownVol(expire, voltype, false);
|
|
},
|
|
submitSurface(item, count) {
|
|
var thisObj = this;
|
|
|
|
if (thisObj.Model.VolType === "隐含") {
|
|
main.message("隐含波动率不允许修改");
|
|
return;
|
|
}
|
|
|
|
//校验数据
|
|
if (!thisObj.Model.CheckAddExpire) {
|
|
main.message("请检查新增到期日是否正确");
|
|
return;
|
|
}
|
|
if (!thisObj.Model.CheckAddStrike) {
|
|
main.message("请检查新增到行权价是否正确");
|
|
return;
|
|
}
|
|
var newStrike = $("#newAddStrike").val();
|
|
var newExpire = $("#newAddExpire").val();
|
|
|
|
var expireFormatOK = true; var strikeFormatOK = true;
|
|
$.each(thisObj.expireList, function (i, d) {
|
|
if (d.IsNewExpire) d.Expire = newExpire;
|
|
expireFormatOK = expireFormatOK && thisObj.checkExpire(d.Expire);
|
|
});
|
|
if (!expireFormatOK) {
|
|
main.message("请检查期限格式是否正确");
|
|
return;
|
|
}
|
|
$.each(thisObj.orderStrikes, function (i, d) {
|
|
if (d.IsNewStrike) d.Strike = newStrike;
|
|
var strikePass = /^\d+[.]*(\d)*$/.test(d.Strike);
|
|
strikeFormatOK = strikeFormatOK && strikePass;
|
|
});
|
|
if (!strikeFormatOK) {
|
|
main.message("请检查行权价格式是否正确");
|
|
return;
|
|
}
|
|
|
|
|
|
var volListList = [];//Vollist 提交
|
|
var im = thisObj.Model.InterpolationMethod;//认为一个界面中波动率模式都一样,都为第一个波动率的模式
|
|
var downLimit = thisObj.Model.ReviewDownLimit / 100;
|
|
var upLimit = thisObj.Model.ReviewUpLimit / 100;
|
|
//验证提交数据
|
|
//行权价只能大于之前的或小于之前的
|
|
//到期日不能和之前的相等
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
if (thisObj.VolTypes.some(function (d, i) { return !d.VolDisabled && d.VolModelName == vd.VolType })) {
|
|
var vols = { InterpolationMethod: im, ReviewDownLimit: downLimit, ReviewUpLimit: upLimit, UnderlyingId: thisObj.Underlying.id, ContractCode: thisObj.Underlying.Code, VolType: vd.VolType, VolSurfaceMode: thisObj.Model.VolSurfaceMode };
|
|
var volList = [];
|
|
$.each(vd.VolTable, function (i, d) {
|
|
if (typeof (d) != "function") {
|
|
if (d.IsNewStrike == true) {
|
|
d.Strike = newStrike;
|
|
}
|
|
if (d.IsNewExpire == true) {
|
|
d.Expire = newExpire;
|
|
}
|
|
d.VolSurfaceMode = thisObj.Model.VolSurfaceMode;
|
|
if (pageVue.VolMoreAccurate) {
|
|
//以PercentVol为主 保留6位
|
|
d.Vol = numeral(d.PercentVol / 100).format("0.000000");
|
|
}
|
|
else {
|
|
//以PercentVol为主 保留4位
|
|
d.Vol = numeral(d.PercentVol / 100).format("0.0000");
|
|
}
|
|
|
|
//检查重复
|
|
var existdata = $.grep(volList, function (ed, i) {
|
|
return ed.Strike == d.Strike && ed.Expire == d.Expire;
|
|
});
|
|
if (existdata.length == 0 && thisObj.checkExpire(d.Expire))
|
|
volList.push(d);
|
|
}
|
|
});
|
|
$.each(volList, function (index, vol) {
|
|
delete vol.IsNewStrike; //删除以免保存
|
|
delete vol.IsNewExpire;
|
|
});
|
|
|
|
|
|
vols.Data = JSON.stringify(volList);
|
|
volListList.push(vols);
|
|
vols.VolTable = eval(vols.Data);
|
|
vd.Data = vols.Data;
|
|
vd.VolTable = vols.VolTable;
|
|
}
|
|
});
|
|
|
|
if (thisObj.Vollist.some(function (vd, vi) {
|
|
var isValidType = thisObj.VolTypes.some(function (d, i) { return !d.VolDisabled && d.VolModelName == vd.VolType });
|
|
var isExistInvalid = vd.VolTable.some(function (vtd, vti) { return (!vtd.Vol || vtd.Vol < 0); });
|
|
return isValidType && isExistInvalid;
|
|
})) {
|
|
main.message("请检查波动率是否正确");
|
|
return;
|
|
}
|
|
//if (thisObj.Vollist.VolTable.some(function (data, index) { return !data.Vol || data.Vol < 0; })) {
|
|
// main.message("请检查波动率是否正确");
|
|
// return;
|
|
//}
|
|
main.post("/volatility/SubmitSurface2", { vols: volListList }).done(function (r) {
|
|
if (parent != window) {
|
|
parent.main.message(r.msg);
|
|
parent.layer.close();
|
|
} else {
|
|
$.each(thisObj.Vollist, function (vi, vd) {
|
|
for (var j = vd.VolTable.length - 1; j >= 0; j--) {
|
|
if (vd.VolTable[j].Expire === "") { vd.VolTable.splice(j, 1); }
|
|
}
|
|
$.each(vd.VolTable, function (i, d) { //貌似没用,以后再修改
|
|
if (d.IsNewStrike == true) {
|
|
d.IsNewStrike = false;
|
|
}
|
|
if (d.IsNewExpire == true) {
|
|
d.IsNewExpire = false;
|
|
}
|
|
if (pageVue.VolMoreAccurate) {
|
|
d.PercentVol = (d.Vol * 100).toFixed(4);
|
|
} else {
|
|
d.PercentVol = (d.Vol * 100).toFixed(2);
|
|
}
|
|
});
|
|
});
|
|
thisObj.loadVollist();
|
|
}
|
|
});
|
|
},
|
|
//从页面的表格中按解析波动率数据
|
|
//不要从后端请求的波动率数据里取数据,因为用户需要在保存数据之前看图形效果
|
|
getVolSurfaceDataFromPage() {
|
|
var volTable = document.getElementById("volsurface");
|
|
var rows = volTable.rows;
|
|
var content, expire, strike, voltype, vol;
|
|
var volDict = {};
|
|
var maxVol = 0.0, minVol = 99999.0;
|
|
|
|
for (var i = rows.length - 1; i > 0; --i) {
|
|
for (var j = 0; j < rows[i].cells.length; ++j) {
|
|
content = rows[i].cells[j];
|
|
if (content.childNodes.length == 2) {
|
|
expire = content.childNodes[0].attributes["expire"].nodeValue;
|
|
strike = content.childNodes[0].attributes["strike"].nodeValue;
|
|
voltype = content.childNodes[0].attributes["voltype"].nodeValue;
|
|
vol = content.childNodes[0].value;
|
|
if (volDict[voltype] == undefined) {
|
|
volDict[voltype] = [];
|
|
}
|
|
|
|
volDict[voltype].push({ Strike: strike * 1.0, Expire: expire, Vol: vol / 100.0 });
|
|
if (vol < minVol) {
|
|
minVol = vol;
|
|
}
|
|
if (vol > maxVol) {
|
|
maxVol = vol;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return [volDict, maxVol, minVol];
|
|
},
|
|
refresh3DChart() {
|
|
var myChart = echarts.init(document.getElementById('volSurfaceChart'));
|
|
var result = this.getVolSurfaceDataFromPage();
|
|
var volDict = result[0];
|
|
var thisObj = this;
|
|
|
|
var volSurfacePoints = [];
|
|
for (var k in volDict) {
|
|
var volSurface = {
|
|
SurfaceName: k,
|
|
Points: volDict[k]
|
|
};
|
|
volSurfacePoints.push(volSurface);
|
|
}
|
|
|
|
main.post("/volatility/FillVolSurfaceInterpolatePoints", { volSurfaces: volSurfacePoints, interpolation: thisObj.Model.InterpolationMethod }).done(function (res) {
|
|
var newVolDict = res;
|
|
|
|
// 如果获取更密集的点失败,还是使用原来的点画曲面
|
|
if (newVolDict == null) {
|
|
newVolDict = volDict;
|
|
}
|
|
|
|
var maxVol = result[1] / 100.0;
|
|
var minVol = result[2] / 100.0;
|
|
|
|
// 指定图表的配置项和数据
|
|
var option = {
|
|
tooltip: {},
|
|
backgroundColor: '#fff',
|
|
visualMap: {
|
|
show: true,
|
|
dimension: 2,
|
|
min: minVol,
|
|
max: maxVol,
|
|
inRange: {
|
|
color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
|
|
}
|
|
},
|
|
xAxis3D: {
|
|
name: '行权价',
|
|
type: 'category'
|
|
},
|
|
yAxis3D: {
|
|
name: '期限',
|
|
type: 'category'
|
|
},
|
|
zAxis3D: {
|
|
type: 'value'
|
|
},
|
|
grid3D: {
|
|
viewControl: {
|
|
// projection: 'orthographic'
|
|
}
|
|
},
|
|
series: []
|
|
};
|
|
|
|
for (var k in newVolDict) {
|
|
var series = {};
|
|
series.type = 'surface';
|
|
series.data = newVolDict[k];
|
|
option.series.push(series);
|
|
}
|
|
|
|
// 使用刚指定的配置项和数据显示图表。
|
|
myChart.clear();
|
|
myChart.setOption(option);
|
|
|
|
layer.open({
|
|
type: 1,
|
|
title: '3D曲面',
|
|
content: $('#volSurfaceChart'),
|
|
area: ['600px', '600px']
|
|
});
|
|
});
|
|
},
|
|
// 刷新波动率微笑曲线图
|
|
refreshVolSmileChart() {
|
|
// 检查是否勾选了某个期限
|
|
var checkBoxes = $(".expireCheckBox");
|
|
var oneSelected = false;
|
|
var expireStr = "";
|
|
for (var i = 0; i < checkBoxes.length; ++i) {
|
|
if (checkBoxes[i].checked) {
|
|
if (oneSelected) {
|
|
main.message("只能选择一个期限作图");
|
|
return;
|
|
}
|
|
oneSelected = true;
|
|
expireStr = checkBoxes[i].nextElementSibling.innerText;
|
|
}
|
|
}
|
|
|
|
if (!oneSelected) {
|
|
main.message("请选择一个期限作图");
|
|
return;
|
|
}
|
|
|
|
var myChart = echarts.init(document.getElementById('volSurfaceChart'));
|
|
var result = this.getVolSurfaceDataFromPage();
|
|
var volDict = result[0];
|
|
var thisObj = this;
|
|
|
|
var volSurfacePoints = [];
|
|
for (var k in volDict) {
|
|
var volSurface = {
|
|
SurfaceName: k,
|
|
Points: volDict[k]
|
|
};
|
|
volSurfacePoints.push(volSurface);
|
|
}
|
|
|
|
main.post("/volatility/Fill2DVolSmileCurvePoints", { volSurfaces: volSurfacePoints, interpolation: thisObj.Model.InterpolationMethod, expire: expireStr }).done(function (res) {
|
|
var volSmileCurve = res;
|
|
|
|
// 指定图表的配置项和数据
|
|
var option = {
|
|
title: {
|
|
text: expireStr + " Vol Smile",
|
|
show: true
|
|
},
|
|
xAxis: {
|
|
type: 'category'
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
axisLabel: {
|
|
formatter: '{value} %'
|
|
}
|
|
},
|
|
series: [],
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
formatter: 'K/S: {b0}</br>'
|
|
}
|
|
};
|
|
|
|
var index = 0;
|
|
for (var k in volSmileCurve) {
|
|
option.xAxis.data = volSmileCurve[k].XPoints;
|
|
var series = {};
|
|
series.type = 'line';
|
|
series.symbol = 'none';
|
|
series.smooth = true;
|
|
series.name = k;
|
|
series.data = [];
|
|
for (var i = 0; i < volSmileCurve[k].YPoints.length; ++i) {
|
|
series.data.push(Math.round(volSmileCurve[k].YPoints[i] * 10000) / 100);
|
|
}
|
|
option.series.push(series);
|
|
option.tooltip.formatter += '{a' + index + '}: {c' + index + '}%</br>';
|
|
++index;
|
|
}
|
|
|
|
option.yAxis.min = Math.round(Math.min.apply(null, option.series[0].data) * 0.8, 2);
|
|
|
|
myChart.clear();
|
|
myChart.setOption(option);
|
|
|
|
layer.open({
|
|
type: 1,
|
|
title: '微笑曲线',
|
|
content: $('#volSurfaceChart'),
|
|
area: ['600px', '600px']
|
|
});
|
|
});
|
|
},
|
|
historicalVolCurve() {
|
|
var ucode = this.Underlying.Code;
|
|
var lookBackDays = $("#lookbackDays :selected").val();
|
|
var thisObj = this;
|
|
main.post("/volatility/GetHistoricalVolCurve", { underlyingCode: ucode, valueDate: thisObj.Model.MaintainDate, lookBackDays: lookBackDays }).done(function (res) {
|
|
var myChart = echarts.init(document.getElementById('historicalVolCurveChart'));
|
|
|
|
var option = {
|
|
xAxis: {
|
|
type: 'category'
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
axisLabel: {
|
|
formatter: '{value} %'
|
|
}
|
|
},
|
|
series: [{
|
|
type: 'line',
|
|
symbol: 'none',
|
|
smooth: true
|
|
}, {
|
|
type: 'line',
|
|
symbol: 'none',
|
|
smooth: true
|
|
}, {
|
|
type: 'line',
|
|
symbol: 'none',
|
|
smooth: true
|
|
}, {
|
|
type: 'line',
|
|
symbol: 'none',
|
|
smooth: true
|
|
}],
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
formatter: '{b0}</br>{c0}%</br>75分位:{c1}%</br>中位数:{c2}%</br>25分位:{c3}%'
|
|
}
|
|
};
|
|
|
|
option.xAxis.data = res.Dates;
|
|
option.series[0].data = [];
|
|
option.series[1].data = [];
|
|
option.series[2].data = [];
|
|
option.series[3].data = [];
|
|
for (var i = 0; i < res.Points.length; ++i) {
|
|
option.series[0].data.push(Math.round(res.Points[i] * 10000) / 100);
|
|
option.series[1].data.push(Math.round(res.PercentileHigh * 10000) / 100);
|
|
option.series[2].data.push(Math.round(res.Median * 10000) / 100);
|
|
option.series[3].data.push(Math.round(res.PercentileLow * 10000) / 100);
|
|
}
|
|
|
|
option.yAxis.min = Math.min.apply(null, option.series[0].data) * 0.95;
|
|
|
|
myChart.clear();
|
|
myChart.setOption(option);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
$(function () {
|
|
$("#maintainDate").datepicker({
|
|
changeMonth: true,
|
|
changeYear: true,
|
|
showButtonPanel: true,
|
|
showOtherMonths: true,
|
|
selectOtherMonths: true,
|
|
maxDate: pageVue.ValueDate
|
|
}).on('change', function () {
|
|
vue.Model.MaintainDate = $(this).val();
|
|
vue.changeMaintainDate();
|
|
});
|
|
|
|
$(window).on('beforeunload', function () {
|
|
localStorage.setItem(consStoreKey, JSON.stringify({ underlyingCode: vue.Underlying.Code }));
|
|
});
|
|
});
|
|
|
|
function ReloadVolsurface(underlyingCode) {
|
|
if (underlyingCode) {
|
|
autoUnderlying.selectByCode(underlyingCode);
|
|
} else {
|
|
vue.updateVolTable();
|
|
}
|
|
} |