// Segmented, year-end cash-flow DCF. Rates are decimals; cash amounts are 亿元, shares 亿股. export function calculateDCF(p) { const finite = (value, label) => { if (typeof value !== 'number' || !Number.isFinite(value)) throw new Error(`${label}须为有效数字`); }; if (!['fcff', 'fcfe'].includes(p.basis)) throw new Error('请选择现金流口径'); for (const [key, label] of Object.entries({fcf: '基期自由现金流', shares: '当前总股本', rate: '折现率', terminal: '永续增长率', margin: '安全边际'})) finite(p[key], label); if (p.fcf <= 0) throw new Error('本模型要求基期可持续自由现金流大于 0'); if (p.shares <= 0) throw new Error('请填写该公司的当前总股本(亿股),且须大于 0'); if (!Number.isInteger(p.year) || p.year < 1900 || p.year > 2200) throw new Error('基期年份须为 1900—2200 的整数'); if (p.rate <= -1 || p.terminal <= -1 || p.terminal >= p.rate) throw new Error('永续增长率须小于折现率,且两者均须大于 −100%'); if (p.margin < 0 || p.margin >= 1) throw new Error('安全边际须大于等于 0% 且小于 100%'); if (!Array.isArray(p.stages) || p.stages.length < 1 || p.stages.length > 5) throw new Error('请设置 1—5 个增长阶段'); let years = 0; for (const stage of p.stages) { if (!Number.isInteger(stage.years) || stage.years < 1) throw new Error('每阶段年数须为正整数'); finite(stage.growth, '阶段增长率'); if (stage.growth <= -1) throw new Error('阶段增长率须大于 −100%'); years += stage.years; } if (years > 30) throw new Error('显式预测期合计最多 30 年'); let adjustment = 0; if (p.basis === 'fcff') { for (const [key, label] of Object.entries({debt: '净债务', assets: '非经营资产净额', minority: '少数股东权益'})) finite(p[key], label); adjustment = -p.debt + p.assets - p.minority; } let cash = p.fcf, t = 0, forecastPV = 0; const rows = []; p.stages.forEach((stage, i) => { for (let y = 0; y < stage.years; y++) { t++; cash *= 1 + stage.growth; const factor = (1 + p.rate) ** -t; const presentValue = cash * factor; forecastPV += presentValue; rows.push({year: p.year + t, stage: i + 1, growth: stage.growth, cash, factor, presentValue}); } }); const terminalCash = cash * (1 + p.terminal); const terminalValue = terminalCash / (p.rate - p.terminal); const terminalPV = terminalValue / (1 + p.rate) ** years; const operatingValue = forecastPV + terminalPV; const equityValue = operatingValue + adjustment; const perShare = equityValue / p.shares; const entryPrice = perShare * (1 - p.margin); const terminalWeight = terminalPV / operatingValue; const result = {years, rows, terminalCash, terminalValue, forecastPV, terminalPV, operatingValue, adjustment, equityValue, perShare, entryPrice, terminalWeight}; if (![terminalCash, terminalValue, forecastPV, terminalPV, operatingValue, equityValue, perShare, entryPrice, terminalWeight, ...rows.flatMap(row => [row.cash, row.factor, row.presentValue])].every(Number.isFinite)) { throw new Error('参数超出有效计算范围,请缩小参数'); } return result; } // The price bands are user-defined discounts/premiums around this DCF scenario. export function calculateDCFPriceRange({perShare, margin, premium}) { if (!Number.isFinite(perShare) || perShare <= 0) throw new Error('无正的每股股权价值,无法划分股价区间'); if (!Number.isFinite(margin) || margin < 0 || margin >= 1) throw new Error('低估折价须大于等于 0% 且小于 100%'); if (!Number.isFinite(premium) || premium < 0 || premium > 10) throw new Error('高估溢价阈值须为 0%—1000%'); const low = perShare * (1 - margin); const high = perShare * (1 + premium); if (![low, high].every(Number.isFinite)) throw new Error('区间超出有效数值范围'); return {low, high, perShare}; } export function classifyDCFPrice(price, range) { if (!Number.isFinite(price) || price <= 0) throw new Error('当前股价须为大于 0 的有效数字'); const zone = price < range.low ? '低估' : price > range.high ? '高估' : '合理'; return {zone, deviation: price / range.perShare - 1}; }