From e4266f908728e5aaf65d60149531880d2ec5c138 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Mon, 29 Jun 2026 01:39:46 -0700 Subject: fix: remove erroneous frontend/ from gitignore --- .gitignore | 1 - frontend/lib/dcf.ts | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6d60df3..2e35f21 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ backend/data/*.db-* node_modules/ .next/ .omo/ -frontend/ out/ dist/ *.tsbuildinfo diff --git a/frontend/lib/dcf.ts b/frontend/lib/dcf.ts index 04f1d6c..51483a8 100644 --- a/frontend/lib/dcf.ts +++ b/frontend/lib/dcf.ts @@ -20,6 +20,24 @@ export type DcfComputed = { terminalValuePv: number; }; +export type AdvancedDcfInputs = { + baseRevenue: number; + revenueGrowth: number[]; + ebitdaMargin: number[]; + dnaPctRevenue: number[]; + capexPctRevenue: number[]; + nwcPctDeltaRev: number[]; + taxRate: number[]; +}; + +export type AdvancedDcfParams = { + wacc: number; + terminalGrowth: number; + projectionYears: number; + sharesOutstanding: number; + equityBridge: number; +}; + export function computeDcf( inputs: DcfInputs, params: DcfParams @@ -36,13 +54,13 @@ export function computeDcf( (_, i) => baseFcf * (1 + growthRate) ** (i + 1) ); const fcfPvSum = projected.reduce( - (sum, fcf, i) => sum + fcf / (1 + wacc) ** (i + 1), + (sum, fcf, i) => sum + fcf / (1 + wacc) ** (i + 0.5), 0 ); const terminalFcf = projected[projected.length - 1] * (1 + terminalGrowth); const terminalValue = terminalFcf / (wacc - terminalGrowth); - const terminalValuePv = terminalValue / (1 + wacc) ** projectionYears; + const terminalValuePv = terminalValue / (1 + wacc) ** (projectionYears - 0.5); const enterpriseValue = fcfPvSum + terminalValuePv; const equityValue = enterpriseValue - equityBridge; @@ -50,3 +68,106 @@ export function computeDcf( return { intrinsicValuePerShare, enterpriseValue, equityValue, fcfPvSum, terminalValuePv }; } + +export function computeAdvancedDcf( + inputs: AdvancedDcfInputs, + params: AdvancedDcfParams +): DcfComputed | { error: string } { + const { + baseRevenue, + revenueGrowth, + ebitdaMargin, + dnaPctRevenue, + capexPctRevenue, + nwcPctDeltaRev, + taxRate, + } = inputs; + const { wacc, terminalGrowth, projectionYears, sharesOutstanding, equityBridge } = params; + + if (wacc <= 0) return { error: "WACC must be greater than 0%." }; + if (terminalGrowth >= wacc) return { error: "Terminal growth must be lower than WACC." }; + if (baseRevenue <= 0) return { error: "Base revenue must be greater than 0." }; + if (sharesOutstanding <= 0) return { error: "Shares outstanding must be greater than 0." }; + + const assumptions = [ + revenueGrowth, + ebitdaMargin, + dnaPctRevenue, + capexPctRevenue, + nwcPctDeltaRev, + taxRate, + ]; + if (assumptions.some((arr) => arr.length < projectionYears)) { + return { error: "Projection assumptions must cover every projection year." }; + } + + let revenue = baseRevenue; + const projectedFcf: number[] = []; + for (let year = 0; year < projectionYears; year++) { + const priorRevenue = revenue; + revenue = priorRevenue * (1 + revenueGrowth[year]); + const ebitda = revenue * ebitdaMargin[year]; + const dna = revenue * dnaPctRevenue[year]; + const ebit = ebitda - dna; + const nopat = ebit * (1 - taxRate[year]); + const capex = revenue * capexPctRevenue[year]; + const deltaRev = year === 0 ? 0 : revenue - priorRevenue; + const nwcChange = deltaRev * nwcPctDeltaRev[year]; + projectedFcf.push(nopat + dna - capex - nwcChange); + } + + const fcfPvSum = projectedFcf.reduce( + (sum, fcf, i) => sum + fcf / (1 + wacc) ** (i + 0.5), + 0 + ); + + const terminalFcf = projectedFcf[projectedFcf.length - 1] * (1 + terminalGrowth); + const terminalValue = terminalFcf / (wacc - terminalGrowth); + const terminalValuePv = terminalValue / (1 + wacc) ** (projectionYears - 0.5); + + const enterpriseValue = fcfPvSum + terminalValuePv; + const equityValue = enterpriseValue - equityBridge; + const intrinsicValuePerShare = equityValue / sharesOutstanding; + + return { intrinsicValuePerShare, enterpriseValue, equityValue, fcfPvSum, terminalValuePv }; +} + +export type SensitivityGrid = { + wacc: number[]; + terminalGrowth: number[]; + impliedPrices: (number | null)[][]; +}; + +export function computeSensitivityGrid( + baseWacc: number, + baseTerminalGrowth: number, + priceFor: (wacc: number, terminalGrowth: number) => number | null +): SensitivityGrid { + const waccValues = [ + Math.max(0.03, baseWacc - 0.02), + Math.max(0.03, baseWacc - 0.01), + baseWacc, + baseWacc + 0.01, + baseWacc + 0.02, + ]; + const terminalGrowthValues = [ + Math.max(0, baseTerminalGrowth - 0.015), + Math.max(0, baseTerminalGrowth - 0.01), + baseTerminalGrowth, + baseTerminalGrowth + 0.01, + baseTerminalGrowth + 0.015, + ]; + + const impliedPrices = waccValues.map((w) => + terminalGrowthValues.map((g) => { + if (g >= w) return null; + try { + return priceFor(w, g); + } catch { + return null; + } + }) + ); + + return { wacc: waccValues, terminalGrowth: terminalGrowthValues, impliedPrices }; +} -- cgit v1.3-2-g0d8e