Residual-Income Model
Install and import#
npm install fintech-algorithmsimport { residualIncomeModel } from "fintech-algorithms/fundamental-analysis-and-valuation/intrinsic-valuation/residual-income-model";Signature#
residualIncomeModel(data)Rolls per-share book value forward through a forecast, charges each period's opening book value at the cost of equity, discounts the residual income that remains, and adds opening book value plus a growing-perpetuity continuing value to reach intrinsic value per share.
Parameters#
| Name | Type | Notes |
|---|---|---|
data | { opening_book_value_per_share: number; earnings_per_share: number[]; dividends_per_share: number[]; nonowner_equity_adjustments_per_share: number[]; cost_of_equity: number; terminal_residual_income_growth: number } | opening_book_value_per_share starts the roll-forward, which each period adds earnings_per_share, subtracts dividends_per_share and adds nonowner_equity_adjustments_per_share. Those three arrays must be the same length. cost_of_equity sets both the equity charge on opening book value and the discount rate, and terminal_residual_income_growth grows the last period's residual income into the continuing value. Other keys in the record are not read. |
Returns#
{ state: string; method: string; schedule: { period: number; opening_book_value_per_share: number; earnings_per_share: number; dividends_per_share: number; nonowner_equity_adjustment_per_share: number; equity_charge_per_share: number; residual_income_per_share: number; discount_factor: number; present_value: number; closing_book_value_per_share: number }[]; pv_explicit_residual_income: number; terminal_residual_income_per_share: number; continuing_value: number; terminal_discount_factor: number; pv_continuing_value: number; continuing_value_share: number; closing_book_value_per_share: number; intrinsic_value_per_share: number }
A schedule row per period showing the book-value roll-forward, its equity_charge_per_share, residual_income_per_share, discount_factor and present_value. Then pv_explicit_residual_income, the continuing block (terminal_residual_income_per_share, continuing_value, terminal_discount_factor, pv_continuing_value), the final closing_book_value_per_share, and intrinsic_value_per_share — opening book value plus both present values. continuing_value_share is the continuing share of that total, emitted as 0 when the total is 0.
Errors#
- When
datais absent, an array, or not an object — throws Error - When
opening_book_value_per_shareis not a positive finite number — throws Error - When any of the three per-share arrays is not an array holding at least one finite number — throws Error
- When
dividends_per_shareornonowner_equity_adjustments_per_sharediffers in length fromearnings_per_share— throws Error - When
cost_of_equityis not strictly between 0 and 1 — throws Error - When
terminal_residual_income_growthis at or below -1, or at or abovecost_of_equity— throws Error - When a period's
dividends_per_shareis negative — throws Error - When the book-value roll-forward reaches a non-positive closing balance — throws Error
Complexity: time O(n),
space O(n).
Worked example#
executed Captured by running this function on the input its own test provides. Real output of real code — but not asserted against a published figure.
Input#
{
"valuation_date": "2026-08-04",
"currency": "USD",
"opening_book_value_per_share": 30,
"earnings_per_share": [3.6, 4, 4.35, 4.65, 4.9],
"dividends_per_share": [1.2, 1.35, 1.5, 1.65, 1.8],
"nonowner_equity_adjustments_per_share": [0, 0, 0, 0, 0],
"cost_of_equity": 0.1,
"terminal_residual_income_growth": 0.03
}Call#
residualIncomeModel(data)Returns#
object with 11 fields: state, method, schedule, pv_explicit_residual_income, terminal_residual_income_per_share, continuing_value, terminal_discount_factor, pv_continuing_value, …
{
"state": "valued",
"method": "residual-income",
"schedule": [
{
"period": 1,
"opening_book_value_per_share": 30,
"earnings_per_share": 3.6,
"dividends_per_share": 1.2,
"nonowner_equity_adjustment_per_share": 0,
"equity_charge_per_share": 3,
"residual_income_per_share": 0.6000000000000001,
"discount_factor": 0.9090909090909091,
"present_value": 0.5454545454545455,
"closing_book_value_per_share": 32.4
},
{
"period": 2,
"opening_book_value_per_share": 32.4,
"earnings_per_share": 4,
"dividends_per_share": 1.35,
"nonowner_equity_adjustment_per_share": 0,
"equity_charge_per_share": 3.24,
"residual_income_per_share": 0.7599999999999998,
"discount_factor": 0.8264462809917354,
"present_value": 0.6280991735537188,
"closing_book_value_per_share": 35.05
},
{
"period": 3,
"opening_book_value_per_share": 35.05,
"earnings_per_share": 4.35,
"dividends_per_share": 1.5,
"nonowner_equity_adjustment_per_share": 0,
"equity_charge_per_share": 3.505,
"residual_income_per_share": 0.8449999999999998,
"discount_factor": 0.7513148009015775,
"present_value": 0.6348610067618329,
"closing_book_value_per_share": 37.9
}
],
"pv_explicit_residual_income": 2.898752569061974,
"terminal_residual_income_per_share": 0.8343000000000005,
"continuing_value": 11.918571428571434,
"terminal_discount_factor": 0.6209213230591549,
"pv_continuing_value": 7.400495140403617,
"continuing_value_share": 0.18363854317472458,
"closing_book_value_per_share": 44,
"intrinsic_value_per_share": 40.29924770946559
}Other exports#
This module also exports
calculate, freeCashFlowDcf, dividendDiscountModel, gordonGrowthModel, economicValueAdded. Every module additionally exports run as an alias of its
primary function, and a meta object carrying its catalog id, domain, family,
shape and article URL.
Diagrams#
How it works#
This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.
References#
- Earnings, Book Values, and Dividends in Equity Valuation — James A. Ohlson
- Residual Income Valuation — CFA Institute
- Conceptual Framework for Financial Reporting — International Accounting Standards Board
- Evidence boundary