计算器 结果还是会被 不断 /100

This commit is contained in:
hjhan
2026-07-22 14:48:52 +08:00
parent 3cf06c2be7
commit e331c15a15
2 changed files with 81 additions and 27 deletions
+56 -13
View File
@@ -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,
// 模拟 calcBondForItemproxy 统一用【展示态】,模型字段是【存储态】,回写时 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();
});
},