diff --git a/README.md b/README.md index 29cbfe2..becb97a 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ There's no `default` export in the ESM implementation, so you have to explicitel - [X] `pv` - [X] `rate` - [X] `irr` - - [ ] `npv` + - [X] `npv` - [ ] `mirr` diff --git a/package.json b/package.json index ba339b7..d7edf95 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "financial", "description": "A Zero-dependency TypeScript/JavaScript port of numpy-financial", "author": "Luciano Mammino (https://loige.co)", - "version": "0.0.17", + "version": "0.0.18", "repository": { "type": "git", "url": "https://github.com/lmammino/financial.git" diff --git a/src/financial.ts b/src/financial.ts index 54f78bc..374bbd5 100644 --- a/src/financial.ts +++ b/src/financial.ts @@ -502,6 +502,75 @@ export function irr (values: number[], guess = 0.1, tol = 1e-6, maxIter = 100): return resultRate } +/** + * Returns the NPV (Net Present Value) of a cash flow series. + * + * @param rate - The discount rate + * @param values - The values of the time series of cash flows. The (fixed) time + * interval between cash flow "events" must be the same as that for + * which `rate` is given (i.e., if `rate` is per year, then precisely + * a year is understood to elapse between each cash flow event). By + * convention, investments or "deposits" are negative, income or + * "withdrawals" are positive; `values` must begin with the initial + * investment, thus `values[0]` will typically be negative. + * @returns The NPV of the input cash flow series `values` at the discount `rate`. + * + * ## Warnings + * + * `npv considers a series of cashflows starting in the present (t = 0). + * NPV can also be defined with a series of future cashflows, paid at the + * end, rather than the start, of each period. If future cashflows are used, + * the first cashflow `values[0]` must be zeroed and added to the net + * present value of the future cashflows. This is demonstrated in the + * examples. + * + * ## Notes + * + * Returns the result of: + * + * ``` + * \\sum_{t=0}^{M-1}{\\frac{values_t}{(1+rate)^{t}}} + * ``` + * + * ## Examples + * + * Consider a potential project with an initial investment of $40 000 and + * projected cashflows of $5 000, $8 000, $12 000 and $30 000 at the end of + * each period discounted at a rate of 8% per period. To find the project's + * net present value: + * + * ```javascript + * import {npv} from 'financial' + * + * const rate = 0.08 + * const cashflows = [-40_000, 5000, 8000, 12000, 30000] + * npv(rate, cashflows) // 3065.2226681795255 + * ``` + * + * It may be preferable to split the projected cashflow into an initial + * investment and expected future cashflows. In this case, the value of + * the initial cashflow is zero and the initial investment is later added + * to the future cashflows net present value: + * + * ```javascript + * const initialCashflow = cashflows[0] + * cashflows[0] = 0 + * + * npv(rate, cashflows) + initialCashflow // 3065.2226681795255 + * ``` + * + * ## References + * + * L. J. Gitman, "Principles of Managerial Finance, Brief," + * 3rd ed., Addison-Wesley, 2003, pg. 346. + */ +export function npv (rate: number, values: number[]) : number { + return values.reduce( + (acc, curr, i) => acc + (curr / (1 + rate) ** i), + 0 + ) +} + /** * This function is here to simply have a different name for the 'fv' * function to not interfere with the 'fv' keyword argument within the 'ipmt' diff --git a/test/examples.test.ts b/test/examples.test.ts index 1c08fd5..a51b454 100644 --- a/test/examples.test.ts +++ b/test/examples.test.ts @@ -46,4 +46,14 @@ describe('Source code docs examples', () => { expect(f.irr([-100, 100, 0, 7])).toBeCloseTo(0.0620584, 6) expect(f.irr([-5, 10.5, 1, -8, 1])).toBeCloseTo(0.088598, 6) }) + + test('npv()', () => { + const rate = 0.08 + const cashflows = [-40_000, 5000, 8000, 12000, 30000] + expect(f.npv(rate, cashflows)).toBeCloseTo(3065.22266817, 6) + + const initialCashflow = cashflows[0] + cashflows[0] = 0 + expect(f.npv(rate, cashflows) + initialCashflow).toBeCloseTo(3065.22266817, 6) + }) }) diff --git a/test/financial.test.ts b/test/financial.test.ts index 65b0136..65cc69b 100644 --- a/test/financial.test.ts +++ b/test/financial.test.ts @@ -1,6 +1,7 @@ -import { fv, pmt, nper, ipmt, ppmt, pv, rate, irr, PaymentDueTime } from '../src/financial' +import { fv, pmt, nper, ipmt, ppmt, pv, rate, irr, npv, PaymentDueTime } from '../src/financial' -// Based on https://github.com/numpy/numpy-financial/blob/master/numpy_financial/tests/test_financial.py +// Mostly based on +// https://github.com/numpy/numpy-financial/blob/master/numpy_financial/tests/test_financial.py describe('fv()', () => { it('calculates float when is end', () => { @@ -154,3 +155,9 @@ describe('irr()', () => { expect(irr([-5, 10.5, 1, -8, 1], 0.1, 1e-10, 2)).toBeNaN() }) }) + +describe('npv()', () => { + it('calculates float', () => { + expect(npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000])).toBeCloseTo(122.894855, 6) + }) +})