import assert from 'node:assert/strict'; import { calculate } from '../js/tools/stock-return-math.js'; const base = {price: 220, base_profit_100m: 860, shares_100m: 46.27, exit_pe: 13, years: 3, base_year: 2026, target_return: .13, growth_rate: .1, safety_margin: 0, annual_dividend_per_share: 0, dividend_growth_rate: 0, annual_dilution_rate: 0}; const near = (actual, expected, tolerance = 1e-8) => assert.ok(Math.abs(actual - expected) < tolerance, `${actual} ≠ ${expected}`); const example = calculate(base); near(example.required_growth.required_growth_rate, .0952, .0001); near(example.required_growth.required_exit_price, 317.43734); near(example.required_growth.required_terminal_profit_100m, 317.43734 * 46.27 / 13); // Reverse/forward cash-flow consistency with dividends, dilution, negative rates and FX-normalized inputs. for (const patch of [{}, {annual_dividend_per_share: 9.3, dividend_growth_rate: .08}, {annual_dilution_rate: .02}, {annual_dilution_rate: -.03, growth_rate: -.08}, {years: 5, target_return: .15}, {target_return: -.15}, {price: 398 * .85619, annual_dividend_per_share: 5.3 * .85619, base_profit_100m: 2300, shares_100m: 90.93, exit_pe: 15}]) { for (const safety_margin of [0, .2, .5]) { const input = {...base, ...patch, safety_margin}; const r = calculate(input); const required = r.required_growth_with_safety_margin.required_growth_rate; const forward = calculate({...input, growth_rate: required}); near(forward.max_purchase_price_with_safety_margin, input.price); near(forward.margin_of_safety_at_purchase, safety_margin); near(r.max_purchase_price_with_safety_margin, r.max_purchase_price_at_target_return * (1 - safety_margin)); near(calculate({...input, safety_margin: 0}).holding_period_irr, r.holding_period_irr); const noMargin = calculate({...input, growth_rate: r.required_growth.required_growth_rate}); near(noMargin.holding_period_irr, input.target_return); } } const covered = calculate({...base, annual_dividend_per_share: 100, safety_margin: .5}); assert.equal(covered.required_growth.required_growth_rate, null); assert.notEqual(covered.required_growth_with_safety_margin.required_growth_rate, null); assert.equal(calculate({...base, annual_dividend_per_share: 500}).required_growth_with_safety_margin.required_growth_rate, null); for (const patch of [{price: 0}, {base_profit_100m: -1}, {shares_100m: 0}, {exit_pe: 0}, {years: 0}, {years: 3.2}, {years: 31}, {target_return: -1}, {annual_dilution_rate: -1}, {annual_dividend_per_share: -2}, {safety_margin: -1}, {safety_margin: 1}, {price: Infinity}]) { assert.throws(() => calculate({...base, ...patch})); } console.log('✓ 估值示例、现金流反向/正向验证、安全边际、多币种、分红、摊薄及边界条件');