Conversation
xirr/xnpv take a :basis option selecting how the year fraction between two dates is measured. Five conventions ship built in: actual_365 (the default), actual_360, actual_actual (ISDA), thirty_360 (US/NASD), and thirty_e_360 (Eurobond). :basis also accepts any module implementing the new Finance.DayCount behaviour, so a calendar-based convention this dependency-free library can't carry — Brazilian Business/252, say — can live in the caller's app instead. The default actual_365 reproduces the previous Date.diff/365, so existing results are unchanged. Reference year fractions are cross-checked against Excel YEARFRAC. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
xnfv/2,3 (+ xnfv!) is the net future value of dated cash flows — the mirror of xnpv, valuing them at the latest date rather than the earliest. conventional?/1 reports whether a series changes sign exactly once, so it has a single unambiguous IRR; a false flags that it may admit several valid rates before the caller solves for one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
- year_fraction is now /3 (from, to, opts): conventions that need context — Actual/Actual ICMA's coupon boundaries, 30E/360 ISDA's maturity carve-out — fit later without a breaking change. The built-in conventions ignore opts. - Name the conventions precisely: actual_actual is Actual/Actual (ISDA), not Excel's basis-1 average-year method; thirty_360 is basic US/NASD with no end-of-February rule; thirty_e_360's Feb 28 -> Mar 1 counts 3 days (doctest'd). - Pin the ACT/ACT per-year sum with a multi-year leap-straddling test. - The business/252 recipe references ex_tempo (not the unrelated `tempo` package) and materializes a static ANBIMA business-day set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
- The CashFlow moduledoc and xnpv docs described Actual/365 as fixed; it is the default, selectable via :basis. xnpv/3's doc and the :basis schema doc now name :basis and xnfv. - Under a 30/360 basis two distinct dates can share a period and merge; the normalize comment and a DayCount note now say so. - Document xnfv's forward-compounding overflow, split the DayCount type into builtin | module, and pin conventional?([]) / [0, 0]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
Options:
- Four compiled schemas (dated rate, rate, dated value, value): each function
validates only the options it uses, so an inapplicable option — :basis on
periodic irr, :guess on xnpv or a Bonds metric — raises instead of being
silently ignored. npv validates options before rejecting empty data.
- :precision is 0..15 in the Returns and amortization schemas too, and
Finance.option includes {:basis, _}.
Behavior:
- xnfv returns {:error, :undefined} when forward compounding overflows.
- The Returns cash-flow metrics coerce amounts via Shared.to_amount, so
Decimal and Money inputs work there like they do in CashFlow.
Tests: Decimal/Money value-transparency property; cross-basis contracts
(irr == xirr, xnfv/xnpv ratio, batch with basis + Brent together); a 30/360
Feb-29 no-EOM pin; fractional bond years.
Docs: restore the 1.6.1 CHANGELOG heading; README pins ~> 1.7 and gains
DayCount, xnfv/conventional?, and :undefined in the error table; ExDoc gets
source_ref and module groups; the first xirr doctest shows a simple 20% rate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
Cases drawn from other XIRR/financial libraries' issue trackers, pinned so a solver or day-count change can't silently regress them: - XIRR: java-xirr #5b (a yield below -83%) and #20 (a series with no real IRR, which returns a clean :did_not_converge where java-xirr spun to its cap). - DayCount: the Actual/Actual (ISDA) 2006 gold values, and a 30/360 case pinning that we follow Excel DAYS360 rather than the NASD end-of-February rule. - Bonds: a discounted zero-coupon yield that QuantLib #256 underestimated. - TVM.rate: the 30-year low-rate loan from POI bug 65988, Microsoft's 48-month example, and a deep-negative guess-selected root. - IRR: the 46-flow near-zero-rate series POI bug 64137 could not converge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn
tubedude
marked this pull request as draft
July 7, 2026 13:48
tubedude
marked this pull request as ready for review
July 7, 2026 13:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
xirr/xnpv take a :basis option selecting how the year fraction between two dates
is measured. Five conventions ship built in: actual_365 (the default), actual_360,
actual_actual (ISDA), thirty_360 (US/NASD), and thirty_e_360 (Eurobond).
:basis also accepts any module implementing the new Finance.DayCount behaviour, so
a calendar-based convention this dependency-free library can't carry — Brazilian
Business/252, say — can live in the caller's app instead.
The default actual_365 reproduces the previous Date.diff/365, so existing results
are unchanged. Reference year fractions are cross-checked against Excel YEARFRAC.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01AVibXT2w1dEdVtMHqffFKn