Core Math API¶
Time value of money, discount rate construction, and statistical functions.
Time Value of Money¶
time_value
¶
Time value of money functions for valuation calculations.
Implements present value, future value, annuity, perpetuity, growing annuity, and terminal value calculations from Chapter 2 of the Ascent Partners textbook.
All functions return a ValuationResult dict with
- value: The computed result
- method: The calculation method used
- formula_reference: Reference to the mathematical formula
- steps: Step-by-step calculation breakdown
- assumptions: List of assumptions made during calculation
Classes¶
ValuationResult(**data: Any)
¶
TVMInputs
¶
Bases: BaseModel
Validated inputs for time value of money calculations.
Functions¶
present_value(future_value: float, discount_rate: float, periods: int) -> ValuationResult
¶
Calculate the present value of a single future cash flow.
Formula
PV = FV / (1 + r)^n
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
future_value
|
float
|
The future cash flow amount (FV) |
required |
discount_rate
|
float
|
The discount rate per period (r), as a decimal |
required |
periods
|
int
|
Number of periods (n) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed present value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If future_value is negative, discount_rate < -1, or periods < 0 |
Book Reference
Chapter 2, Section 2.1 — Present Value of a Single Sum Chapter 2, Basic Exercise Q1: PV of $500,000 in 8 years at 10% = $233,253
Source code in src/core/time_value.py
future_value(present_value: float, discount_rate: float, periods: int) -> ValuationResult
¶
Calculate the future value of a present amount.
Formula
FV = PV * (1 + r)^n
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
present_value
|
float
|
The present amount (PV) |
required |
discount_rate
|
float
|
The growth/discount rate per period (r), as a decimal |
required |
periods
|
int
|
Number of periods (n) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed future value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If present_value is negative, discount_rate < -1, or periods < 0 |
Book Reference
Chapter 2, Section 2.2 — Future Value of a Single Sum Chapter 2, Basic Exercise Q2: FV $1M, PV $620,921, 5 years -> discount rate = 10%
Source code in src/core/time_value.py
annuity_pv(payment: float, discount_rate: float, periods: int) -> ValuationResult
¶
Calculate the present value of an ordinary annuity.
Formula
PV = PMT * [1 - (1 + r)^(-n)] / r
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment
|
float
|
The periodic payment amount (PMT) |
required |
discount_rate
|
float
|
The discount rate per period (r), as a decimal |
required |
periods
|
int
|
Number of periods (n) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed annuity present value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If payment is negative, discount_rate <= -1 or == 0, or periods < 0 |
Book Reference
Chapter 2, Section 2.3 — Present Value of an Annuity Chapter 2, Intermediate Exercise Q1: Annuity $50,000 for 10 years at 15% = $250,937
Source code in src/core/time_value.py
perpetuity_pv(payment: float, discount_rate: float) -> ValuationResult
¶
Calculate the present value of a perpetuity.
Formula
PV = PMT / r
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment
|
float
|
The periodic payment amount (PMT) |
required |
discount_rate
|
float
|
The discount rate per period (r), as a decimal |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed perpetuity present value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If payment is negative or discount_rate <= 0 |
Book Reference
Chapter 2, Section 2.4 — Present Value of a Perpetuity Chapter 3, Royalty Relief: $10M revenue, 4% royalty, 15% discount = $2,666,667
Source code in src/core/time_value.py
growing_annuity_pv(payment: float, discount_rate: float, growth_rate: float, periods: int) -> ValuationResult
¶
Calculate the present value of a growing annuity.
Formula (when r != g): PV = PMT * [1 - ((1 + g) / (1 + r))^n] / (r - g)
Formula (when r == g): PV = PMT * n / (1 + r)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment
|
float
|
The first period payment amount (PMT) |
required |
discount_rate
|
float
|
The discount rate per period (r), as a decimal |
required |
growth_rate
|
float
|
The growth rate of payments per period (g), as a decimal |
required |
periods
|
int
|
Number of periods (n) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed growing annuity present value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If payment is negative, periods < 0, or r < -1, g < -1 |
Book Reference
Chapter 2, Section 2.5 — Present Value of a Growing Annuity
Source code in src/core/time_value.py
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | |
terminal_value(final_year_cashflow: float, perpetual_growth_rate: float, discount_rate: float, method: Literal['gordon_growth', 'exit_multiple'] = 'gordon_growth', exit_multiple: float | None = None) -> ValuationResult
¶
Calculate terminal value using Gordon Growth or Exit Multiple method.
Gordon Growth Formula
TV = FCF * (1 + g) / (r - g)
Exit Multiple Formula
TV = FCF * Exit Multiple
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
final_year_cashflow
|
float
|
The final year projected cash flow (FCF) |
required |
perpetual_growth_rate
|
float
|
The perpetual growth rate (g), as a decimal |
required |
discount_rate
|
float
|
The discount rate (r), as a decimal |
required |
method
|
Literal['gordon_growth', 'exit_multiple']
|
Calculation method ("gordon_growth" or "exit_multiple") |
'gordon_growth'
|
exit_multiple
|
float | None
|
Required when method="exit_multiple" |
None
|
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed terminal value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If parameters are invalid for the chosen method |
Book Reference
Chapter 2, Section 2.6 — Terminal Value Calculations Chapter 5, DCF Methodology — Terminal Value in Multi-Period DCF
Source code in src/core/time_value.py
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 | |
present_value_of_series(cash_flows: list[float], discount_rate: float) -> ValuationResult
¶
Calculate present value of a series of uneven cash flows.
Formula
PV = sum(CF_t / (1 + r)^t) for t = 1 to n
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flows
|
list[float]
|
List of cash flows for each period (index 0 = period 1) |
required |
discount_rate
|
float
|
The discount rate (r), as a decimal |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed present value and per-period breakdown |
Raises:
| Type | Description |
|---|---|
ValueError
|
If cash_flows is empty or discount_rate is invalid |
Book Reference
Chapter 2, Section 2.1 — Present Value (general case) Chapter 4 — Used in all income-based methods
Source code in src/core/time_value.py
Discount Rates¶
discount_rates
¶
Discount rate construction functions for valuation calculations.
Implements build-up method, CAPM, WACC, tax amortization benefit, control premium, DLOM (Finnerty), and currency-adjusted discount rates from Chapters 2, 3, and 4 of the Ascent Partners textbook.
All functions return a ValuationResult dict with
- value: The computed discount rate or premium
- method: The calculation method used
- formula_reference: Reference to the mathematical formula
- steps: Step-by-step calculation breakdown
- assumptions: List of assumptions made during calculation
Classes¶
DiscountRateInputs
¶
Bases: BaseModel
Validated inputs for discount rate calculations.
Functions¶
build_up_discount_rate(risk_free_rate: float, equity_risk_premium: float, size_premium: float = 0.0, industry_risk_premium: float = 0.0, specific_risk_premium: float = 0.0) -> ValuationResult
¶
Calculate discount rate using the build-up method.
Formula
r = Rf + ERP + Size Premium + Industry Risk Premium + Specific Risk Premium
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
risk_free_rate
|
float
|
Risk-free rate (Rf), typically government bond yield |
required |
equity_risk_premium
|
float
|
Equity risk premium (ERP) |
required |
size_premium
|
float
|
Additional premium for company size risk (default 0) |
0.0
|
industry_risk_premium
|
float
|
Additional premium for industry-specific risk (default 0) |
0.0
|
specific_risk_premium
|
float
|
Company-specific risk premium (default 0) |
0.0
|
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed discount rate |
Raises:
| Type | Description |
|---|---|
ValueError
|
If risk_free_rate or equity_risk_premium is negative |
Book Reference
Chapter 2, Section 2.7 — Build-Up Method for Discount Rate Commonly used for private company valuations where beta is unavailable
Source code in src/core/discount_rates.py
capm_discount_rate(risk_free_rate: float, beta: float, market_return: float) -> ValuationResult
¶
Calculate discount rate using the Capital Asset Pricing Model (CAPM).
Formula
r = Rf + beta * (Rm - Rf)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
risk_free_rate
|
float
|
Risk-free rate (Rf) |
required |
beta
|
float
|
Systematic risk coefficient (beta) |
required |
market_return
|
float
|
Expected market return (Rm) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed cost of equity |
Raises:
| Type | Description |
|---|---|
ValueError
|
If inputs are invalid |
Book Reference
Chapter 2, Section 2.8 — Capital Asset Pricing Model (CAPM) Standard approach for publicly traded company cost of equity
Source code in src/core/discount_rates.py
wacc(equity_value: float, debt_value: float, cost_of_equity: float, cost_of_debt: float, tax_rate: float) -> ValuationResult
¶
Calculate the Weighted Average Cost of Capital (WACC).
Formula
WACC = (E / V) * Re + (D / V) * Rd * (1 - Tc) where V = E + D
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
equity_value
|
float
|
Market value of equity (E) |
required |
debt_value
|
float
|
Market value of debt (D) |
required |
cost_of_equity
|
float
|
Cost of equity (Re) |
required |
cost_of_debt
|
float
|
Pre-tax cost of debt (Rd) |
required |
tax_rate
|
float
|
Corporate tax rate (Tc) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed WACC |
Raises:
| Type | Description |
|---|---|
ValueError
|
If equity_value + debt_value is zero or values are negative |
Book Reference
Chapter 2, Section 2.9 — Weighted Average Cost of Capital Used for enterprise value and firm-level discount rates
Source code in src/core/discount_rates.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | |
tax_amortization_benefit(discount_rate: float, useful_life: int, tax_rate: float, asset_value: float) -> ValuationResult
¶
Calculate the present value of the tax amortization benefit (TAB).
The TAB represents the present value of tax savings from amortizing an intangible asset over its useful life.
Formula
TAB = Asset Value * Tax Rate * [1 - (1 + r)^(-n)] / (r * n)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
discount_rate
|
float
|
The discount rate (r), as a decimal |
required |
useful_life
|
int
|
Amortization period in years (n) |
required |
tax_rate
|
float
|
Corporate tax rate (Tc) |
required |
asset_value
|
float
|
The value of the intangible asset |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed TAB value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If inputs are invalid |
Book Reference
Chapter 3, Section 3.4 — Tax Amortization Benefit Important adjustment in relief-from-royalty and multi-period excess earnings methods
Source code in src/core/discount_rates.py
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
control_premium(minority_price: float, control_price: float) -> ValuationResult
¶
Calculate the control premium percentage.
Formula
Control Premium = (Control Price - Minority Price) / Minority Price
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
minority_price
|
float
|
The trading price of minority shares |
required |
control_price
|
float
|
The price paid for a controlling interest |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed control premium as a decimal |
Raises:
| Type | Description |
|---|---|
ValueError
|
If prices are non-positive or control_price < minority_price |
Book Reference
Chapter 4, Section 4.3 — Control Premium Used to convert minority interest value to controlling interest value
Source code in src/core/discount_rates.py
dlom_finnerty(restricted_period: float, volatility: float, risk_free_rate: float) -> ValuationResult
¶
Calculate Discount for Lack of Marketability (DLOM) using Finnerty model.
The Finnerty model uses an average strike put option approach to estimate the DLOM based on the cost of hedging the restriction period.
Formula
DLOM = (1 / (r * T)) * [r * T * N(-d2) - (e^(rT) - 1) * N(-d1)] where: d1 = sigma * sqrt(T) / 2 d2 = -sigma * sqrt(T) / 2
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
restricted_period
|
float
|
Restriction period in years (T) |
required |
volatility
|
float
|
Annualized stock volatility (sigma) |
required |
risk_free_rate
|
float
|
Risk-free rate (r) |
required |
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed DLOM as a decimal |
Raises:
| Type | Description |
|---|---|
ValueError
|
If inputs are invalid |
Book Reference
Chapter 4, Section 4.4 — Discount for Lack of Marketability (DLOM) Finnerty, J.D. "An Average-Strike Put Option Model for DLOM"
Source code in src/core/discount_rates.py
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 | |
currency_adjusted_discount_rate(base_rate: float, currency_risk_premium: float = 0.0, country_risk_premium: float = 0.0) -> ValuationResult
¶
Calculate a discount rate adjusted for currency and country risk.
Formula
r_adjusted = base_rate + Currency Risk Premium + Country Risk Premium
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_rate
|
float
|
The base discount rate (e.g., from CAPM or build-up) |
required |
currency_risk_premium
|
float
|
Additional premium for currency risk |
0.0
|
country_risk_premium
|
float
|
Additional premium for country/sovereign risk |
0.0
|
Returns:
| Type | Description |
|---|---|
ValuationResult
|
ValuationResult with computed currency-adjusted discount rate |
Raises:
| Type | Description |
|---|---|
ValueError
|
If base_rate < -1 or premiums are negative |
Book Reference
Chapter 4, Section 4.5 — International/Currency Risk Adjustments Used for cross-border valuations and emerging market assets
Source code in src/core/discount_rates.py
Statistics¶
statistics
¶
Statistical functions for valuation analysis.
Implements Monte Carlo simulation and decision tree analysis from Chapter 6 and Appendix B of the Ascent Partners textbook.
All functions return structured dicts with
- value: The primary result
- method: The calculation method used
- formula_reference: Reference to the methodology
- steps: Step-by-step calculation breakdown
- assumptions: List of assumptions made during calculation
Classes¶
DistributionInput
¶
Bases: BaseModel
Validated input distribution for Monte Carlo simulation.
TreeNode
¶
Bases: BaseModel
Validated node in a decision tree.
TreeEdge
¶
Bases: BaseModel
Validated edge in a decision tree.
DecisionTreeInput
¶
Bases: BaseModel
Validated decision tree input.
Functions¶
monte_carlo_valuation(valuation_fn: Callable[..., float], input_distributions: list[dict[str, Any]], iterations: int = 10000, seed: int | None = None) -> dict[str, Any]
¶
Perform Monte Carlo simulation for valuation uncertainty analysis.
Runs the valuation function multiple times with randomly sampled inputs from specified distributions to produce a probability distribution of valuation outcomes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
valuation_fn
|
Callable[..., float]
|
Function that takes named parameters and returns a float value |
required |
input_distributions
|
list[dict[str, Any]]
|
List of dicts with keys: - name: Parameter name passed to valuation_fn - distribution: "normal", "uniform", or "triangular" - params: Distribution-specific parameters - normal: {"mean": float, "std": float} - uniform: {"low": float, "high": float} - triangular: {"low": float, "high": float, "mode": float} |
required |
iterations
|
int
|
Number of simulation runs (default 10000) |
10000
|
seed
|
int | None
|
Random seed for reproducibility (default None) |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with keys: - mean: Mean of simulated valuations - median: Median of simulated valuations - std: Standard deviation - percentile_5: 5th percentile - percentile_25: 25th percentile - percentile_75: 75th percentile - percentile_95: 95th percentile - min: Minimum value - max: Maximum value - method: "Monte Carlo Simulation" - formula_reference: Reference description - iterations: Number of iterations performed - seed: Random seed used - steps: Description of the simulation - assumptions: List of assumptions |
Raises:
| Type | Description |
|---|---|
ValueError
|
If input_distributions is empty or iterations < 1 |
Book Reference
Chapter 6, Section 6.2 — Monte Carlo Simulation for Valuation Appendix B — Statistical Methods in Valuation
Source code in src/core/statistics.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | |
decision_tree_valuation(tree: dict[str, Any]) -> dict[str, Any]
¶
Evaluate a decision tree to compute expected values at each node.
The tree consists of three node types: - decision: A choice point where the optimal branch is selected (max value) - chance: A probabilistic branch where expected value is computed - terminal: An endpoint with a fixed value
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
dict[str, Any]
|
Dict with keys: - nodes: List of node dicts with keys: id, type, label, value - edges: List of edge dicts with keys: from, to, probability, cost |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with keys: - expected_value: Expected value at the root node - node_values: Dict mapping node ID to expected value - optimal_path: List of node IDs representing the optimal path - method: "Decision Tree Analysis" - formula_reference: Reference description - steps: Step-by-step evaluation - assumptions: List of assumptions |
Raises:
| Type | Description |
|---|---|
ValueError
|
If tree structure is invalid |
Book Reference
Chapter 6, Section 6.3 — Decision Tree Analysis for Valuation Used for valuing assets with contingent outcomes (e.g., R&D projects, litigation)
Source code in src/core/statistics.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | |
Constants¶
constants
¶
Industry default rates and constants for valuation calculations.