polarmutex / fava-envelope

A beancount fava extension to add a envelope budgeting capability
MIT License
101 stars 16 forks source link

Multi-currency budgeting #26

Open GuillaumedeVolpiano opened 3 years ago

GuillaumedeVolpiano commented 3 years ago

To sum things up, I live and work in a country and my immediate family lives in another country, with a different currency, so I need to be able to budget in multiple currencies, so I've tweaked the code for my personal use, creating options allowing to have multiple budgets over multiple currencies. This is probably not on the project roadmap for now, but just in case, would you be interested in me sharing my code?

polarmutex commented 3 years ago

I would love to bring that feature in, it is on my list just not a top priority

GuillaumedeVolpiano commented 3 years ago

I've posted the code as a pull request, if you feel like giving it a look.

polarmutex commented 3 years ago

just have a question. for your way of budgeting would you consider converting to one currency? I only budget in one, I am thinking in the future to support having a budget per currency or converting all other currencies to one.

Thoughts?

GuillaumedeVolpiano commented 3 years ago

That's a very good point, and something that shouldn't be to hard to implement in the current code.

As is, some bits are still very much in a "works for me" state, where I have current accounts in the major currencies I operate in, and just transfer from one account to the other (hence the creation of the "income account" option).

But a very real real life scenario, at least in the country I'm currently in, is to have a credit card in USD tied to your account in SFC (some foreign currency), for which you might want to budget. Or you might want to have one budget over multiple currencies.

As the code currently stands, it does "flatten" out spending in other currencies when they have a price, which, I think, covers all spending to an account (lines 223-229 of the current commit):

            if posting.units.currency != self.currency:
                orig=posting.units.number
                if posting.price is not None:
                    converted=posting.price.number*orig
                    posting=data.Posting(posting.account,amount.Amount(converted,self.currency), posting.cost, None, posting.flag,posting.meta)
                else:
                    continue

So it's just a matter of implementing an else clause that fetches the price. There are four options here:

1 - use the price at the time of the transaction (or the closest time to that transaction) 2 - use the price at the time of the generation (or the closest to it) 3 - use an automatically generated average price for the month of the transaction 4 - use an average price, set for that month, as an "envelope" option.

Option 1 and 2 both, in my opinion, have strong issues:

Thoughts?

Before I look into it, though, as you have accepted my code, I'll first document it (once again, up to know, it was mostly "works for me" tweaks, so it's pretty rough).

polarmutex commented 3 years ago

thanks for your input, just want to understand how people use multiple currencies so when I make future changes I do not make it harder for them.

going to keep this open for future discussions

egetzner commented 2 years ago

Great work so far with this feature. I created a PR documenting the usage in a separate example file. Would probably be good to have it in the README as well.

Some notes regarding multi-currency:

valpackett commented 6 months ago

Thanks for the pointer @GuillaumedeVolpiano!

To just get these transactions to not be ignored, I quickly implemented option 1:

--- /home/val/.local/pipx/venvs/beancount/lib/python3.12/site-packages/fava_envelope/modules/beancount_envelope.py.orig 2024-01-10 19:06:04.783010743 -0300
+++ /home/val/.local/pipx/venvs/beancount/lib/python3.12/site-packages/fava_envelope/modules/beancount_envelope.py      2024-01-10 19:18:38.528336092 -0300
@@ -277,18 +277,17 @@
                 account_type = account_types.get_account_type(account)
                 if posting.units.currency != self.currency:
                     orig = posting.units.number
-                    if posting.price is not None:
-                        converted = posting.price.number * orig
-                        posting = data.Posting(
-                            posting.account,
-                            amount.Amount(converted, self.currency),
-                            posting.cost,
-                            None,
-                            posting.flag,
-                            posting.meta,
-                        )
-                    else:
+                    amt = convert.convert_position(posting, self.currency, self.price_map, entry.date)
+                    if amt.currency != self.currency:
                         continue
+                    posting = data.Posting(
+                        posting.account,
+                        amt,
+                        posting.cost,
+                        None,
+                        posting.flag,
+                        posting.meta,
+                    )

                 if account_type == self.acctypes.income or (
                     any(

In my situation (want to budget in USD, most daily transactions are in a local currency with high inflation) price at the time of the transaction (option 1) is the most appropriate. The extra cool dream bonus option for this situation would be to actually track the purchase cost of the local currency, as if the {whatev USD} syntax were used (which is normally not used for regular cash)..

gabrielkoerich commented 1 month ago

any plans on adding this?