计算器 结果还是会被 不断 /100
This commit is contained in:
@@ -66,23 +66,66 @@ describe('边界:计算器未返回的值不覆盖、无变化不写', () => {
|
||||
expect(state.ytm).toBe(2.5);
|
||||
});
|
||||
|
||||
// 模拟 calcBondForItem:以既有三字段为代理调纯函数,再写回 item
|
||||
test('浮动腿 item 写回:仅未手填字段被覆盖', () => {
|
||||
const item = { PosiNetNoFeePrice: 99.5, PosiGrossPrice: 100.0, InitYtm: 2.6,
|
||||
// 模拟 calcBondForItem:proxy 统一用【展示态】,模型字段是【存储态】,回写时 bondCalcPriceToStorage。
|
||||
test('浮动腿 item 写回:仅未手填字段被覆盖,且存储态转换正确', () => {
|
||||
// 用户输入净价 99.5 → vue-number-input(percent:true) 存为存储态 0.995
|
||||
const item = { PosiNetNoFeePrice: 0.995, PosiGrossPrice: 1.0, InitYtm: 0.026,
|
||||
bondDriverType: 'CP', bondManual: { CP: true, DP: false, YD: false } };
|
||||
const calc = { cleanPrice: 99, dirtyPrice: 100, ytm: 2.5 };
|
||||
const calc = { cleanPrice: 99, dirtyPrice: 100, ytm: 2.5 }; // 计算器返回展示态
|
||||
const proxy = {
|
||||
cleanPrice: item.PosiNetNoFeePrice, // 净价 = 手填
|
||||
dirtyPrice: item.PosiGrossPrice,
|
||||
ytm: item.InitYtm
|
||||
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice), // 0.995 → 99.5(手填)
|
||||
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice), // 1.0 → 100
|
||||
ytm: SwapCalc.bondPriceToCalc(item.InitYtm) // 0.026 → 2.6
|
||||
};
|
||||
SwapCalc.applyBondCalcResult(proxy, calc, item.bondManual);
|
||||
item.PosiNetNoFeePrice = proxy.cleanPrice;
|
||||
item.PosiGrossPrice = proxy.dirtyPrice;
|
||||
item.InitYtm = proxy.ytm;
|
||||
expect(item.PosiNetNoFeePrice).toBe(99.5); // 手填(净价):保持
|
||||
expect(item.PosiGrossPrice).toBe(100); // 全价:被反算覆盖
|
||||
expect(item.InitYtm).toBe(2.5); // 收益率:被反算覆盖
|
||||
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice);
|
||||
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice);
|
||||
item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm);
|
||||
expect(item.PosiNetNoFeePrice).toBe(0.995); // 手填(净价):存储态保持 0.995(界面仍显示 99.5)
|
||||
expect(item.PosiGrossPrice).toBe(1.0); // 全价:被反算覆盖,存储态 100/100=1.0
|
||||
expect(item.InitYtm).toBe(0.025); // 收益率:被反算覆盖,存储态 2.5/100=0.025
|
||||
});
|
||||
|
||||
// 核心回归:用户输入净价 100(存储态 1.0)时,绝不能因 proxy 残留存储态而再 ÷100 变成 0.01
|
||||
test('核心回归:输入净价 100 不会被二次缩小为 1(存储态 0.01)', () => {
|
||||
const item = { PosiNetNoFeePrice: 1.0, PosiGrossPrice: null, InitYtm: null,
|
||||
bondDriverType: 'CP', bondManual: { CP: true, DP: false, YD: false } };
|
||||
const calc = { cleanPrice: 100, dirtyPrice: 102.17928767123287, ytm: 4.860445236 };
|
||||
const proxy = {
|
||||
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice), // 1.0 → 100
|
||||
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice),
|
||||
ytm: SwapCalc.bondPriceToCalc(item.InitYtm)
|
||||
};
|
||||
SwapCalc.applyBondCalcResult(proxy, calc, item.bondManual);
|
||||
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice);
|
||||
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice);
|
||||
item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm);
|
||||
expect(item.PosiNetNoFeePrice).toBe(1.0); // 存储态仍是 1.0(界面显示 100)
|
||||
expect(item.PosiGrossPrice).toBeCloseTo(1.0217928767123287, 9); // 102.179.../100
|
||||
expect(item.InitYtm).toBeCloseTo(0.04860445236, 9); // 4.860.../100
|
||||
});
|
||||
|
||||
// 多轮不同净价:95 / 105,确保存储态始终与界面输入一致,不会被二次缩小
|
||||
test.each([
|
||||
[95, 97.5, 5.123456],
|
||||
[105, 107.3, 4.567890],
|
||||
[100, 102.17928767123287, 4.860445236]
|
||||
])('多轮净价校验:输入净价 %s 时,存储态保持 %s/100 且不会被二次缩小', (inputClean, calcDirty, calcYtm) => {
|
||||
const item = { PosiNetNoFeePrice: inputClean / 100, PosiGrossPrice: null, InitYtm: null,
|
||||
bondDriverType: 'CP', bondManual: { CP: true, DP: false, YD: false } };
|
||||
const calc = { cleanPrice: inputClean, dirtyPrice: calcDirty, ytm: calcYtm };
|
||||
const proxy = {
|
||||
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice),
|
||||
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice),
|
||||
ytm: SwapCalc.bondPriceToCalc(item.InitYtm)
|
||||
};
|
||||
SwapCalc.applyBondCalcResult(proxy, calc, item.bondManual);
|
||||
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice);
|
||||
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice);
|
||||
item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm);
|
||||
expect(item.PosiNetNoFeePrice).toBe(inputClean / 100);
|
||||
expect(item.PosiGrossPrice).toBeCloseTo(calcDirty / 100, 9);
|
||||
expect(item.InitYtm).toBeCloseTo(calcYtm / 100, 9);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -308,8 +308,9 @@ const vue = new Vue({
|
||||
underlyingCode: item.UnderlyingCode,
|
||||
// 模型 item.PosiGrossPrice/PosiNetNoFeePrice/InitYtm 存的是【存储态小数】(如 0.995),
|
||||
// 而 bond-calc 要的是【展示态/每百元百分比】(如 99.5)。这俩靠 BondPriceConverter.ToDisplay(×100)/ToStorage(÷100) 对齐。
|
||||
// 此处传给计算器前必须 ×100(存储→展示);回写时再 ÷100(展示→存储),否则计算器拿到 0.995≈1 当 1% of par 直接发散成离谱值。
|
||||
price: price * 100,
|
||||
// 此处传给计算器前必须 bondPriceToCalc(存储→展示);回写时再 bondCalcPriceToStorage(展示→存储),
|
||||
// 否则计算器拿到 0.995≈1 当 1% of par 直接发散成离谱值。
|
||||
price: SwapCalc.bondPriceToCalc(price),
|
||||
priceType: priceType,
|
||||
targetDate: targetDate
|
||||
}, { alertFn: main.message }).done(function (resp) {
|
||||
@@ -317,20 +318,23 @@ const vue = new Vue({
|
||||
// 业务层错误(债券不存在/信息不全/参数非法):仅提示,绝不覆盖手工输入
|
||||
var err = SwapCalc.getBondCalcErrorMessage(resp.obj);
|
||||
if (err) { main.message(err); return; }
|
||||
// 以既有三字段为代理,调用纯函数(已手动设过的字段不被覆盖),再写回
|
||||
// 以既有三字段为代理,调用纯函数(已手动设过的字段不被覆盖),再写回。
|
||||
// 关键:proxy 内必须统一为【展示态】(per-100-face),因为 applyBondCalcResult 写入的是计算器返回的展示态。
|
||||
// 模型字段是【存储态小数】(percent:true 下 1.00 对应界面 100),所以初始化时要 bondPriceToCalc(×100);
|
||||
// 若直接用存储态初始化,则用户手填字段被 applyBondCalcResult 跳过后,proxy 中仍残留存储态,
|
||||
// 后续再 ÷100 就会导致该字段被二次缩小(如输入 100 变成 1)。
|
||||
var proxy = {
|
||||
cleanPrice: item.PosiNetNoFeePrice,
|
||||
dirtyPrice: item.PosiGrossPrice,
|
||||
ytm: item.InitYtm
|
||||
cleanPrice: SwapCalc.bondPriceToCalc(item.PosiNetNoFeePrice),
|
||||
dirtyPrice: SwapCalc.bondPriceToCalc(item.PosiGrossPrice),
|
||||
ytm: SwapCalc.bondPriceToCalc(item.InitYtm)
|
||||
};
|
||||
var manual = item.bondManual || { CP: false, DP: false, YD: false };
|
||||
SwapCalc.applyBondCalcResult(proxy, resp.obj, manual);
|
||||
// 回写前 ÷100(展示态→存储态小数):proxy 里的 cleanPrice/dirtyPrice/ytm 均为【展示态】
|
||||
// (bond-calc 返回 per-100-face、代理已把 ytm×100);模型字段存【存储态】(0.995),须 ÷100 落回模型,
|
||||
// 否则模型值量级错(如 97.82 当 0.9782 存,配合 percent:true 显示又 ×100 成 9782)。
|
||||
item.PosiNetNoFeePrice = proxy.cleanPrice / 100;
|
||||
item.PosiGrossPrice = proxy.dirtyPrice / 100;
|
||||
item.InitYtm = proxy.ytm / 100;
|
||||
// 回写前 bondCalcPriceToStorage(÷100)(展示态→存储态小数):proxy 里均为展示态;
|
||||
// 模型字段存存储态(0.995),须 ÷100 落回模型,否则配合 percent:true 显示会 ×100 成离谱值。
|
||||
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice);
|
||||
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice);
|
||||
item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm);
|
||||
// 名义本金依赖全价(PosiGrossPrice):以净价/收益率为源反算出的全价被回写后,
|
||||
// 直接赋值不会触发组件 input 事件,需在此显式重算,保持名义本金与全价一致。
|
||||
if (self.calcNotional) self.calcNotional();
|
||||
@@ -714,8 +718,15 @@ const vue = new Vue({
|
||||
var thisObj = this;
|
||||
main.post("/pricing/AjaxGetUnderlyingPrice", { underlyingCode: underlyingCode, tradeDate: StartDate })
|
||||
.done(function (resp) {
|
||||
item.PosiNetNoFeePrice = otcformat.trading.umprice(resp.obj.netPrice);
|
||||
item.PosiGrossPrice = otcformat.trading.umprice(resp.obj.price);
|
||||
if (item.isBond) {
|
||||
// 债券行情返回的是【展示态/每百元百分比】(如 100),而 PosiNetNoFeePrice/PosiGrossPrice
|
||||
// 模型字段存【存储态小数】(如 1.0)。非债券标的保持原 umprice 格式化即可。
|
||||
item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(resp.obj.netPrice);
|
||||
item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(resp.obj.price);
|
||||
} else {
|
||||
item.PosiNetNoFeePrice = otcformat.trading.umprice(resp.obj.netPrice);
|
||||
item.PosiGrossPrice = otcformat.trading.umprice(resp.obj.price);
|
||||
}
|
||||
thisObj.calcNotional();
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user