Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XIRR table #94

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

XIRR table #94

wants to merge 6 commits into from

Conversation

sohamshanbhag
Copy link

This PR provides a XIRR table per investment account and a summary of all investments in the performance tab in fava_investor.

I'm using newton method for optimization with 10 steps for speed. In my experience, this gives a good enough accuracy of the XIRR value and is fast enough.

@redstreet
Copy link
Owner

Nice, and thank you! A performance module has been the one big missing piece of fava_investor. I travelling, but please bear with me and I should be able to take a look later this week.

@sohamshanbhag
Copy link
Author

No issues. I have only added an XIRR table yet, which is very basic, let me know if something else is needed.

@redstreet redstreet force-pushed the main branch 2 times, most recently from 1b7a44c to 8fff365 Compare January 19, 2025 21:42
@nirajd
Copy link

nirajd commented Feb 4, 2025

I'd be very interested in seeing this merged. Any chance of that in the near future?

@redstreet
Copy link
Owner

redstreet commented Feb 4, 2025

Thank you for the ping @nirajd and apologies for the delay to the OP. I've had very little time over the last few months.

Let's see if we can resolve this now. I think my main concern with this is, it calculates per-position XIRR. Most people instead typically want to ask "what was my rate of return for this account?" Where an account (let's assume an account has only a single ticker) includes transactions such as buys and sells, and importantly, dividend reinvestments.

In effect, this code only works if the position never paid out dividends, and if one never sold the position or bought more of it.

That's not to say it's not useful, but if you could perhaps give me more context about where you see this calculation as useful, I'm happy to see where we could take this.

@redstreet
Copy link
Owner

So the core of the problem of computing XIRR is figuring out and collecting the right transaction chain, from what I've seen. Beangrow solves that problem, which you might've seen.

@sohamshanbhag
Copy link
Author

here all line numbers refer to https://github.com/sohamshanbhag/fava_investor/blob/b94b239d51b009d99a68f09433133faa01aa985a/fava_investor/modules/performance/libperformance.py

it calculates per-position XIRR

No, it calculates per account XIRR. The signature of the function is (L30)

calculate_xirr(investments: list[date, Decimal], accuracy) -> Decimal

and I create the list as (L81)

investments[row.account].append([row.date, row.market_value.number])

Moreover, you can give it any custom group by creating the appropriate list, like I have done with the summary list (L82).

If you can give me an example file for dividends, I can try the same.

The code does require some error handling though.

@redstreet
Copy link
Owner

This doc should help. Thoughts?

@sohamshanbhag
Copy link
Author

sohamshanbhag commented Feb 5, 2025

I was away from my pc so couldn't upload an example before.

As an example, with the huge-example.beancount in this repo, adding

  'performance': {
    'account_field': 'account',
    'accounts_pattern': 'Assets:US:(Vanguard|ETrade):(?!Cash)',
    'accuracy': 2,
  },

to fava-investor, you get the following performance table
2025-02-05_23-08-36_screenshot

Is this what you are looking for?


Edit:

Libreoffice gives the following values:

Account XIRR
Summary 4.1995937663929
VBMPX 3.469519184415
RGAGX 5.0551310485712
ITOT 2.29516730638006
VEA 3.36810062712599
VHT 4.30271481463399
GLD 5.15807793314151

So close, but not quite. I need to debug this more.


Edit 2:

The values I inputted into libreoffice were wrong, the correct XIRR values are

Account XIRR
Summary 4.16209356626117
VBMPX 3.77604809803889
RGAGX 4.72026069508571
ITOT 2.42432265735827
VEA 3.49646163249443
VHT 4.34294221379249
GLD 5.44064662365185

which imply my estimates are correct upto 2 decimal places, as requested in the config.

First letter should be capital.
@sohamshanbhag
Copy link
Author

I just checked the document, and I have not accounted for dividends. I'll look at adding them over the weekend.

@redstreet
Copy link
Owner

redstreet commented Feb 5, 2025

I just checked the document, and I have not accounted for dividends. I'll look at adding them over the weekend.

Great! The extraction code in beangrow might even be worth using, or taking inspiration from. It's solves for an overly generic use case IMHO, but is still designed very well and works well.

Please disregard my text about "per-commodity", that was poorly expressed on my part. I do see the code does account level reporting. What I meant is the above: dividends and capital gains distributions, are not accounted for; and the below.

Speaking about which, are cash infusions accounted for? If I open an account on Jan 1, invest 10k in AAPL, then invest an additional 5k in it every month in AAPL for a year, how would the returns be calculated?

How would it work if I sold AAPL five months later, recognize the capital gain or loss, and purchased it again 8 months later?

Also, certain instruments pay out interest and distribute capital gains. Would these be accounted for (should be very similar to dividends calculation).

And finally, the traditional way to present irr over a period of time is both as trailing returns, and as a 1yr, 3yr, 5yr, and 10yr returns. Not only is this very useful to compare against a reference ticker (i.e., how's my account doing against the s&p 500), it also makes it possible to verify the irr numbers here against what your brokerage or institution is telling you.

And it'd be great to have test cases for all these.

@sohamshanbhag
Copy link
Author

Speaking about which, are cash infusions accounted for? If I open an account on Jan 1, invest 10k in AAPL, then invest an additional 5k in it every month in AAPL for a year, how would the returns be calculated?
How would it work if I sold AAPL five months later, recognize the capital gain or loss, and purchased it again 8 months later?

These work. These examples are already present in the huge-example.beancount file as far as I can see. The testcase already added also shows this. I have sold all holdings in the testcase, because XIRR depends on the present day for calculating time and holding value, and I need to use assertEqual.

Also, certain instruments pay out interest and distribute capital gains. Would these be accounted for (should be very similar to dividends calculation).

These need to be added. I was thinking of assigning a dividend account to every investment account for this, which the user declares using metadata of the account. How does this sound?

trailing returns

As far as I can see[1], this is

(value today / value x years ago) - 1,

right? This does not seem like a property of a persons portfolio, but of their choice of investments (i.e., not dependent on if you buy 50% AAPL and 50% GOOG, but on if you buy AAPL instead of GOOG). This also does not take into account cash infusions, selling and such. Am I misunderstanding this or shouldn't this better be on the website you check the reference ticker instead?

1yr, 3yr, 5yr, and 10yr

I'd need to research more about how to calculate XIRR for such time periods. Does this mean XIRR calculated using buys only in this time period and their corresponding sells?

And it'd be great to have test cases for all these.

There is one testcase added. I'll look into adding more or modifying it.

@redstreet
Copy link
Owner

redstreet commented Feb 6, 2025

Speaking about which, are cash infusions accounted for? If I open an account on Jan 1, invest 10k in AAPL, then invest an additional 5k in it every month in AAPL for a year, how would the returns be calculated?
How would it work if I sold AAPL five months later, recognize the capital gain or loss, and purchased it again 8 months later?

These work. These examples are already present in the huge-example.beancount file as far as I can see. The testcase already added also shows this. I have sold all holdings in the testcase, because XIRR depends on the present day for calculating time and holding value, and I need to use assertEqual.

Good to know, let me take a look.

Also, certain instruments pay out interest and distribute capital gains. Would these be accounted for (should be very similar to dividends calculation).

These need to be added. I was thinking of assigning a dividend account to every investment account for this, which the user declares using metadata of the account. How does this sound?

Metadata sounds fine to begin with, but how would it work on aggregations -- i.e., parent accounts? I.e., If I have Assets:Investments:Fidelity:AAPL, I typically want to know my returns for Investments:* and Fidelity:*.

Once that problem is solved, perhaps a better idea that metadata is to somehow let the user configure a way to derive the dividends account through the base account. Else, each account would have to have multiple metadata pointers (eg: one each for dividends, capital gains distributions, and interest), which gets messy. But that's probably an easy problem to thoughtfully solve.

trailing returns

As far as I can see[1], this is

(value today / value x years ago) - 1,

right?

Hmm, in theory, but that formula seems to be overly simplistic, as the cashflows have to be accounted for.

This does not seem like a property of a persons portfolio, but of their choice of investments (i.e., not dependent on if you buy 50% AAPL and 50% GOOG, but on if you buy AAPL instead of GOOG). This also does not take into account cash infusions, selling and such. Am I misunderstanding this or shouldn't this better be on the website you check the reference ticker instead?

This would be the value of the account (not the performance of the ticker), and takes the specific cashflows of the account into consideration. In other words, it would indeed be a property of a portfolio.

This would be "Money-Weighted IRR", which I believe XIRR is. However, many brokerages provide both TWR and MWR, but several consider TWR to be the primary, though robo-advisors like Wealthfront (in the US) consider MWR to be primary. IMHO, both are useful, and providing even one of them correctly in this plugin along with documentation would be an excellent addition.

With both TWR and MWR, the set of cashflows in the specific account will have to be extracted and used, which you are already doing. The difference IIRC is only in the computation.

Here's the irr computation in Beangrow. Someone also added a "Dietz" return which some brokerages do. Both are similar, and are TWRs.

1yr, 3yr, 5yr, and 10yr

I'd need to research more about how to calculate XIRR for such time periods. Does this mean XIRR calculated using buys only in this time period and their corresponding sells?

Yes, in that only the cashflows (buys and sells) in that time period are used. The starting and ending values of the account on the starting and ending dates are considered. This means that a price directive for all tickers on those two dates must exist. Beangrow handles this by doing a two-pass run, and outputting the dates for which it is missing price entries in the first pass, and expecting the user to fill them out in the second pass.

Solving the problem of calculating XIRR for any arbitrary time period solves it for all the 6-10 metrics that are commonly used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants