andreasgerstmayr / fava-portfolio-returns

fava-portfolio-returns shows portfolio returns in Fava
GNU General Public License v2.0
35 stars 2 forks source link

Portfolio Returns dies if an account doesn't exist #16

Open fdw opened 3 months ago

fdw commented 3 months ago

First off, sorry that I open that many issues. I've just discovered this plugin and play with it, so I'm running into bugs more often than usual. Thanks for your effort with this, it's great!

I've noticed that you can use the "time" option of Fava to only show graphs for this time frame. However, it crashes if an account doesn't exist (yet). For example, I have a second depot opened in 2024 and configured that in Beangrow as well. If I now try to show a timeframe where it doesn't exist yet, it crashes with a KeyError:

  File "/app/lib/python3.9/site-packages/beangrow/investments.py", line 402, in process_account_entries
    opn, cls = open_close_map[account]
andreasgerstmayr commented 2 months ago

I tried a few different years with the testdata in https://github.com/andreasgerstmayr/fava-portfolio-returns/tree/main/example, but I couldn't reproduce this crash. Could you prepare a minimal reproducible example?

fdw commented 2 months ago

Hey, I've managed to create a reproduction: https://gist.github.com/fdw/822cf84ed78d323b9017f8664e25c5bc

If I narrow the time to, for example, 2012 (or 2018, 2015), I get an error and this stacktrace:

Exception on /beancount/extension/FavaPortfolioReturns/ [GET]
Traceback (most recent call last):
  File "/app/lib/python3.9/site-packages/flask/app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
  File "/app/lib/python3.9/site-packages/flask/app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/app/lib/python3.9/site-packages/flask/app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
  File "/app/lib/python3.9/site-packages/flask/app.py", line 865, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
  File "/app/lib/python3.9/site-packages/fava/application.py", line 344, in extension_report
    content = Markup(template.render(ledger=g.ledger, extension=ext))
  File "/app/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/app/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/templates/FavaPortfolioReturns.html", line 7, in top-level template code
    {% include '_report.html' %}
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/templates/_report.html", line 26, in top-level template code
    {% set report = extension.report(group) %}
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/__init__.py", line 412, in report
    return self.generate_report(
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/__init__.py", line 349, in generate_report
    cash_flows = returnslib.truncate_and_merge_cash_flows(
  File "/app/lib/python3.9/site-packages/beangrow/returns.py", line 189, in truncate_and_merge_cash_flows
    cash_flows.extend(truncate_cash_flows(pricer, ad, date_start, date_end))
  File "/app/lib/python3.9/site-packages/beangrow/returns.py", line 159, in truncate_cash_flows
    cost_position = cost_balance.get_only_position()
  File "/app/lib/python3.9/site-packages/beancount/core/inventory.py", line 269, in get_only_position
    raise AssertionError("Inventory has more than one expected "
AssertionError: Inventory has more than one expected position: (15.0000 EUR, -100.00 FOOBAR)
andreasgerstmayr commented 2 months ago

Thanks for the reproducer! It complains that there are two different currencies in the same inventory.

The problem is in line 26, when you move FOOBAR without cost basis. I suggest enabling the plugin beancount.plugins.coherent_cost to show an error in this case.

For more details, you can check https://groups.google.com/g/beancount/c/vDX1oA2mJXA/m/08dg_fnIBAAJ about transferring lots.

fdw commented 2 months ago

You're right, that was a problem. I've updated the file accordingly (and also the gist), but I still see an error:

Traceback (most recent call last):
  File "/app/lib/python3.9/site-packages/flask/app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
  File "/app/lib/python3.9/site-packages/flask/app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/app/lib/python3.9/site-packages/flask/app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
  File "/app/lib/python3.9/site-packages/flask/app.py", line 865, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
  File "/app/lib/python3.9/site-packages/fava/application.py", line 344, in extension_report
    content = Markup(template.render(ledger=g.ledger, extension=ext))
  File "/app/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/app/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/templates/FavaPortfolioReturns.html", line 7, in top-level template code
    {% include '_report.html' %}
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/templates/_report.html", line 26, in top-level template code
    {% set report = extension.report(group) %}
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/__init__.py", line 412, in report
    return self.generate_report(
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/__init__.py", line 360, in generate_report
    plots = self.create_plots(
  File "/app/lib/python3.9/site-packages/fava_portfolio_returns/__init__.py", line 322, in create_plots
    market_value = market_values[market_values_idx][1] if not is_closed else 0
IndexError: list index out of range