1.5.1 — 2026-07-05
Added
- Cash-flow amounts may now be
ex_money%Money{}values, alongside numbers andDecimal. Finance takes theDecimalamount without depending on ex_money, and rejects a series that mixes currencies with{:error, :mixed_currencies}(plain numbers andDecimalare currency-neutral and never conflict).
Changed
- The optional
decimalrequirement is relaxed to~> 2.0 or ~> 3.0, so finance can share a project with libraries pinned to Decimal 2.x — ex_money among them.
1.5.0 — 2026-07-05
Added
Finance.CashFlow.irr_many/2andxirr_many/2— solve a whole batch of independent series in one call, returning a list of{:ok, rate}/{:error, reason}in the same order (one bad series doesn't sink the batch). They run on the configured solver: the default pure-Elixir solver parallelizes across schedulers withTask.async_stream, while a native (Rustler) or Nx backend can run the whole batch in a single call.Finance.Solvergains asolve_many/2callback for that batch seam.
Changed
- Custom
Finance.Solverimplementations must now providesolve_many/2alongsidesolve/2. The shippedFinance.Solver.Newtonimplements it (the parallel default), so the built-in behaviour is unchanged.
Fixed
financenow compiles when the optionaldecimaldependency is absent. Two functions matched%Decimal{}in their head, and a struct pattern is resolved at compile time — so a consumer who depended onfinancewithout also addingdecimalhitstruct Decimal is undefinedat compile. The two heads now useis_struct(value, Decimal)guards (runtime, no compile-time module needed), so the Decimal support is genuinely optional. Behaviour withdecimalpresent is unchanged.- The solver now converges over very long horizons that a high-rate probe would
overflow. Bracketing evaluates the NPV at rate
1.0, where(1 + 1)^toverflows oncetis large (e.g. a 2000-period flow) — and Erlang's:math.powraises on overflow, which aborted the whole solve to:did_not_converge.present_valueand the solver's derivative now discount with a negative exponent (amount * (1 + rate)^-t), so the factor underflows to a negligible0instead of overflowing. Results for normal flows are unchanged.
1.4.3 — 2026-07-05
Changed
- The default solver is now a safeguarded Newton (the classic
rtsafe): it brackets the root, then each step takes a Newton step when that step lands inside the bracket and is converging fast enough, and a bisection step otherwise — all in one pass, rather than running Newton to exhaustion and then bisecting separately. Results are unchanged, but long-horizon flows are much faster (a 480-period loan'sratesolves ~9× quicker), and because the maintained bracket always encloses a sign change the solver can no longer return a stalled non-root. The bracket-membership test compares the Newton point against the bracket instead of multiplying two net present values, which would overflow in the steep zone near the bracket's floor for long-dated flows.
1.4.2 — 2026-07-05
Fixed
- The rate solver no longer reports a non-root as converged. Newton's step-size
termination could accept a rate where the NPV was still large — near a steep
NPV the step
f / f'shrinks below the tolerance even whilefdoes not — so convergence now rests solely on the NPV threshold, and a stalled Newton falls through to bisection. amortization_schedulekeeps the balance retiring monotonically within[0, opening]. Once(1 + rate)^nperis large a cent-rounded level payment can no longer tame the balance: rounded a hair high it overshot below zero, a hair low it grew the balance back (negative amortization) before the final row absorbed the difference. Each period now clamps so the schedule stays monotonic and bounded, with a final row that clears whatever remains.
Added
- Property-based stress tests for the solver and TVM: IRR root-finding across
extreme rates and long horizons, a no-crash / real-root contract on arbitrary
cash-flow signs,
TVM.rateround-trips,pv/fvinversion, and amortization balance invariants. These surfaced the two fixes above.
1.4.1 — 2026-07-05
Fixed
- The solver now converges for long-maturity, low-rate flows — for example the
ytmof a deep-discount 28-year bond, or a 30-year monthly amortizationrate. Previously a Newton step could overflow on such flows and abort the whole solve instead of falling through to bisection, and the bisection bracket itself overflowed for long-dated flows. Now a Newton overflow falls through to bisection, the bracket floor adapts to the longest flow, and the bracketing sign check compares signs instead of multiplying (which could overflow).
1.4.0 — 2026-07-04
Added
Finance.Returnsreturn metrics:cagr/4(compound annual growth rate),payback_period/2anddiscounted_payback_period/3(with fractional-period interpolation),profitability_index/3(reusingCashFlow.npv), andtwr/2(time-weighted return, with an optional:periods_per_yearto annualise) — each with a!variant.
1.3.0 — 2026-07-04
Added
Finance.TVM.ipmt/6andppmt/6— the interest and principal portions of a given period's payment; together they add up topmt/5.Finance.TVM.amortization_schedule/3,4— the full repayment schedule as a list of%{period, payment, interest, principal, balance}rows, with the final row absorbing rounding so the balance ends at exactly0. Computed in integer minor units (10^:precision), so every row is exact to the requested precision; returnsDecimalrows when givenDecimalinputs, floats otherwise.Finance.Rates—effective_annual_rate/2,nominal_rate/2, andcontinuous_to_periodic/2for converting between rate quotations.Finance.Bonds— fixed income:price/5,ytm/5(reuses theFinance.Solverseam, mirroringTVM.rate),duration/4(Macaulay),modified_duration/4, andconvexity/4, each with a!variant. Maturity is given in years with a configurable coupon frequency (freq, default2); settlement is assumed to fall on a coupon date (clean price, no accrued interest).
1.2.0 — 2026-07-04
Reorganised the flat Finance module into domain modules. No behaviour
change — every function keeps its signature and result.
Added
- Domain modules:
Finance.CashFlow(npv/xnpv/irr/xirr/mirr),Finance.TVM(pv/fv/pmt/nper/rate),Finance.Depreciation(sln/syd/ddb/db), andFinance.Returns(volatility). Finance.Solverbehaviour with the defaultFinance.Solver.Newton. The solver is swappable per call with the:solveroption or globally withconfig :finance, solver: MySolver— a seam for a future Nx/GPU solver.
Deprecated
- The flat
Finance.foofunctions (e.g.Finance.xirr/1) now delegate to their domain module and are deprecated. They still work in the 1.x line and will be removed in 2.0.
1.1.0 — 2026-07-04
Added
volatility/2andvolatility!/2— annualised volatility of a price series, the standard deviation of its period returns scaled by√periods_per_year. Supports simple or log returns and a configurable period count. Resolves the long-standing volatility request (issue #7), with the submitted snippet's crashes on short input and zero prices fixed.
1.0.0 — 2026-07-04
A complete rewrite of the library. Breaking changes — some return values differ from 0.x because intermediate rounding was removed and the solver was replaced, and errors are now atoms rather than strings.
Rate of return
xirr/1,2,3andxirr!— internal rate of return for dated cash flows, given as{date, amount}pairs or two parallel lists. Dates may beDatestructs or{year, month, day}tuples.irr/1,2andirr!— internal rate of return for periodic (equally spaced) flows.mirr/3,4andmirr!— modified internal rate of return.- Newton-Raphson solver with an analytic derivative, a bracketing bisection fallback, and a hard iteration cap.
Present / future value
xnpv/2,3andxnpv!— net present value of dated cash flows.npv/2,3andnpv!— net present value of periodic flows. The first amount sits at period 0 (sonpv(irr(a), a) ≈ 0), which differs from spreadsheetNPV(first amount at period 1).
Time value of money
fv/5,pv/5,pmt/5,nper/5,rate/6and their!variants — solve the annuity equation for one unknown, with annuity-due (type: 1) support.
Depreciation
sln/3,syd/4,ddb/5,db/5and their!variants — straight-line, sum-of-years'-digits, double-declining and fixed-declining depreciation.
Amounts and options
- Amounts may be integers, floats, or
Decimalvalues (via the optionaldecimaldependency); results are always floats. - Options (
:guess,:tolerance,:max_iterations,:precision) are validated withnimble_options; unknown keys and out-of-type values raise.
Removed
- The
timexruntime dependency (now uses the standard-libraryDate). - The
spawn_link-per-cash-flow "parallel" mapping, which was slower and unsafe.
Tooling
- Requires Elixir
~> 1.18; tested on Elixir 1.18/OTP 27 and 1.20/OTP 29. - Travis CI replaced with GitHub Actions.