fava-envelope
fava-envelope copied to clipboard
Multi-currency budgeting
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?
I would love to bring that feature in, it is on my list just not a top priority
I've posted the code as a pull request, if you feel like giving it a look.
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?
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:
- option 1 as the price at the time of the transaction will not necessarily be the price you actually pay for the currency (back to my credit` card example, you usually pay those at 45 days). It depends on real-life usage, but this could be offset by having an Expenses:ForeignExchangeRisk category to absorb the offset, although this is going to be hard to calculate if one doesn't pay their credit card in full or a new expense cycle has started by the time you pay the previous month.
- option 2 is the worst, as it will create fluctuations based on foreign exchange variations long after the transaction has taken place. Option 3 and 4 are fairly equivalent, and could, actually, be implemented concurrently, with 3 as the default behaviour and 4 as an override. 3 would give you an idea of the costs whilst the bill is running, and you could set 4 once you know your actual price.
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).
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
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:
- I personally would prefer to have everything on one page, similar to what fava itself does - listing the different currencies below each other but in each category.
- As mentioned above, it would also make sense to me to convert all foreign currency entries to one operating currency -> perhaps via drop-down, similar to other fava controls.
- When assigning money to the envelopes, I would prefer to use the same beancount language, i.e. have the currency follow the amount. instead of
"envelopeUSD" "allocate" "account" 10
,"envelope" "allocate" "account" 10 USD
.
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)..