summaryrefslogtreecommitdiff
path: root/frontend/lib/dcf.ts
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-06-29 01:39:46 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-06-29 01:39:46 -0700
commite4266f908728e5aaf65d60149531880d2ec5c138 (patch)
treec38928c932a67a7d984cc0a0cbeeab91b20a38a1 /frontend/lib/dcf.ts
parent1d97f1b3039d1a3ee7d748a0bf54cc862ca2dc95 (diff)
fix: remove erroneous frontend/ from gitignore
Diffstat (limited to 'frontend/lib/dcf.ts')
-rw-r--r--frontend/lib/dcf.ts125
1 files changed, 123 insertions, 2 deletions
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 };
+}