beancount / fava

Fava - web interface for Beancount
https://beancount.github.io/fava/
MIT License
1.97k stars 285 forks source link

Total cost not respected - uses price #1827

Closed mankoff closed 4 months ago

mankoff commented 4 months ago

I've set a cost for something with:

2024-01-01 price POINT 0.01 USD
2024-01-01 price MILES 0.01 USD

But today when I purchased something I got a 50 % bonus. I recorded the transaction like this:

2024-06-21 * "Delta BOS>SEA" Expenses:Travel:Air
Assets:Other:MiscPoints -363.48 USD @@ 24232 POINT

So the 24000 points got me 360 USD (50 % bonus). However, when I view this in Fava I see:

image

That is, it's using the historical price, not the total cost. Is there some other syntax I should be using?

yagebu commented 4 months ago

If you want to track these inventory positions with the cost attached, you probably want to use the cost syntax (-363.48 USD {# 24232 POINT}).

mankoff commented 4 months ago

That syntax does not work. It may be that I'm using Beancount and not Ledger? Anyway, this syntax seems to work:

2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air                                     363.48 USD
  Assets:Other:MiscPoints                                 -24232 POINT {0.015 USD}

However, it is always reported in USD now. I would like to be able to see it at cost - this trip did not cost me 363.48 USD, it cost POINT. I would like the option to convert to USD to see the effective cost. The original syntax I posted Assets:Other:MiscPoints -363.48 USD @@ 24232 POINT supports this (just at an exchange rate I do not understand).

korrat commented 4 months ago

I believe that this is not specific to Fava.

First off, what settings are you using in the Fava UI, especially for conversions (“at cost”, “at market value”, …). From your description, I believe you're using “convert to USD”.

From your description, Assets:Other:MiscPoints is tracked in POINTs, correct? If so, you shouldn't use the -… USD @@ … POINT syntax, as that removes USD from the account.

I can see a few options here.

  1. You turn the price annotation around:

    2024-06-21 * "Delta BOS>SEA"
     Expenses:Travel:Air
     Assets:Other:MiscPoints  -24232 POINT @@ 363.48 USD

    This removes 24232 POINT from Assets:Other:MiscPoints, converts them to USD, and puts these into Expenses:Travel:Air.

    From your second comment, you don't seem to like this option, and prefer to track the amount in Expenses:Travel:Air in POINTs as well.

    Also note that if you're using the implicit_price plugin, this creates a price statement of 2024-06-21 price POINT 0.015 USD, which would change the valuation of later transactions.

  2. You can discard the price annotation completely.

    2024-06-21 * "Delta BOS>SEA"
     Expenses:Travel:Air
     Assets:Other:MiscPoints  -24232 POINT

    This removes 24232 POINT from Assets:Other:MiscPoints, and then puts them into Expenses:Travel:Air directly.

    This comes back to your original problem: Because beancount (and thus fava) does not know about the promotion, in Fava, it reports the price as 0.01 USD. I would argue that this matches your way to look at the problem: 1 POINT is worth 0.01 USD, so, since the trip cost you 24232 POINTS, that means it's worth 243.32 USD.

  3. You could use a cost-basis conversion and an income account.

    2024-06-21 * "Delta BOS>SEA"
     Expenses:Travel:Air       24232 POINT {0.015 USD}
     Assets:Other:MiscPoints  -24232 POINT {0.01 USD}
     Income:Other:Promotion

    This reads like: At some point in the past, you got 24232 POINT at a cost of 0.01 USD (so 242.32 USD of POINTs). Now, you're spending these 24232 POINT at a value of 0.015 USD (so 363.48 USD of POINTs) on air travel. Since you “gained” 121.16 USD, they have to come somewhere, which is a new income account, in this example.

Personally, I would likely go with option 1. My reasoning: You're buying 363.48 USD of air travel, so that amount should go into expenses. That you're paying with 24232 POINT does not change the air travel in any way.

mankoff commented 4 months ago

I may still have an incorrect mental model. Or some plugin or option set incorrectly. I would like to see the what did I spend (USD & POINT) vs what did it cost (USD). Or maybe my nomenclature here is wrong as in Fava Cost is (POINT,USD,etc.) and then there is Convert to USD but there is not Spend. Regardless, none of the three suggestions above show me what I'm trying to display.

Does the plugin "beancount.plugins.implicit_prices" update the price before or after the transaction? I feel like if it did it before the transaction, things would work. For example, with this, when I manually update the price before the transaction, everything works.

2024-06-20 price POINT 0.015 USD
2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air
  Assets:Other:MiscPoints                                 -363.48 USD @@ 24232 POINT

USD is 363, and COST is 24232 POINT. I know people keep saying "that's backward", but it shows correctly in Fava. If I do it the way people suggest, which is

2024-06-20 price POINT 0.015 USD
2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air
  Assets:Other:MiscPoints                                 -24232 POINT @@ 363.48 USD

I can't see it at Cost (POINT), only USD.

The other two suggestions above also do not provide the information I'm looking for, which is:

1) This is what I spent in USD and POINT 2) This is what you would have had to spend if it had all been USD (to me, this is the actual cost of the trip).

korrat commented 4 months ago

I would like to see the what did I spend (USD & POINT) vs what did it cost (USD).

In that case, you should track the POINT amounts with a cost basis. Let's talk about a larger example:

option "operating_currency" "USD"

plugin "beancount.plugins.auto_accounts"

2024-01-01 price POINT 0.01 USD

2024-05-30 * "Earn some points"
  Assets:Other:MiscPoints   24232 POINT {0.01 USD}
  Income:MiscPoints

2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air       24232 POINT {0.015 USD}
  Assets:Other:MiscPoints  -24232 POINT {0.01 USD} @ 0.015 USD
  Income:Other:Promotion

Here's the narrative for that: On May 30, you earn some POINT, at a current value of 0.01 USD per POINT. This could also be a part of a larger transaction, such as a cashback or something. On June 21, you sell those POINT (which are still worth 0.01 USD) at a price of 0.015 USD per POINT. Since we cannot conjure money out of thin air, we track the Income as a promotion.

In Fava, you have now a couple of different options for showing this information to answer your questions (assuming you are looking at the Expenses:Air:Travel account journal):

How does this work? In Beancount, _prices_ and _costs_ are similar, but different. A price (`… @ … USD`) is an immediate conversion at an exchange rate: You “remove” a specified amount denoted in one commodity and “gain” a different amount (according to the exchange rate) denoted in another commodity. Importantly, the money that you gain is independent of the exchange rate from that point forward. So `1 USD` is usable interchangeably, regardless of whether you gained it from employment, by exchanging `CAD` for it, or exchanging `POINT` for it. In contrast, using a cost (`… {… USD}`) creates a lot, which remembers the exchange rate of the conversion, among other details. Whenever you convert that lot back (into the commodity of the cost basis, here `USD`), it reuses the exchange rate from when the lot was created. Therefore, lots can no longer be used interchangeably: If I have two lots of `POINT` at a rate of `0.01 USD` and `0.02 USD`, respectively, which lot am I using to pay for my trip? In other words, using a cost basis as opposed to a price means the amount gains a sort of “identity”. This means that we can use the cost basis to track both the usual and the promotional value of `POINT`. However, it makes the tracking a bit more effort in general, since you have to track the cost basis all the time. This can be made a bit easier using a [booking method].

Does the plugin "beancount.plugins.implicit_prices" update the price before or after the transaction?

AFAIK, implicit_prices creates a price statement on the day of the transaction. This should then apply for all transactions on that day.

mankoff commented 4 months ago

Dear @korrat, thank you, both for the code and the explanation.

In summary and tabular form,

Fava option Display
Cost USD if no points used
Market Value USD at discount rate
Units Cost as paid (POINT)
Convert to USD USD at discount rate
mankoff commented 4 months ago

Sorry to re-open this, but I have a follow-up question / issue.

An example similar to what we worked on above works, repeated here:

option "operating_currency" "USD"

plugin "beancount.plugins.auto_accounts"

2024-01-01 price POINT 0.01 USD

2024-06-19 pad Assets:Other:MiscPoints Equity:Adjustment
2024-06-20 balance Assets:Other:MiscPoints                  24232 POINT

2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air       24232 POINT {0.015 USD}
  Assets:Other:MiscPoints  -24232 POINT {0.01 USD} @ 0.015 USD
  Income:Other:Promotion

The difference is that I am not tracking POINT earnings rigorously. I just add them via a pad before I use them. This works 1x, but not 2x. The above code passes bean-check. But if I take the same flight next month, just repeating everything but in July rather than June by adding this:

2024-07-19 pad Assets:Other:MiscPoints Equity:Adjustment
2024-07-20 balance Assets:Other:MiscPoints                  24232 POINT

2024-07-21 * "Delta BOS>SEA"
  Expenses:Travel:Air       24232 POINT {0.015 USD}
  Assets:Other:MiscPoints  -24232 POINT {0.01 USD} @ 0.015 USD
  Income:Other:Promotion

Then bean-check fails with this error message

foo.bc:16:      Attempt to pad an entry with cost for balance: (24232 POINT, -24232 POINT {0.01 USD, 2024-06-21})

   2024-07-19 pad Assets:Other:MiscPoints Equity:Adjustment

Is the solution to not use pad and instead insert an explicit (if fake) transaction adding the POINT {price} in the same syntax as your example?

korrat commented 4 months ago

As you suggest, pad does not include a cost basis, therefore it panics. Furthermore, Beancount cannot add amounts with and without balances. As you can see from the bean-check output, there are two lots in Assets:Other:MiscPoints, 24232 POINT (without cost) and -24232 POINT {0.01 USD} (with cost). When padding that balance (also called an inventory), Beancount gets confused due to the cost basis.

Instead of pad, you can insert an explicit transaction and specify the cost basis yourself.

Alternatively, you could track POINT in Assets:Other:MiscPoints without a cost basis. This is a bit unusual (usually you wouldn't use a commodity both with and without a cost basis), but should work. You would have to specify a price when reducing the account, though, as otherwise the transaction does not balance. You can choose which price to use. If you use the normal price (0.01 USD), you do need to specify a third leg to track the income. If you use the promotional price in the reduction, you don't need to give a third leg, since the transaction already balances. Here is an example of both options, choose the one you like more:

option "operating_currency" "USD"

plugin "beancount.plugins.auto_accounts"

2024-01-01 price POINT 0.01 USD

2024-06-19 pad Assets:Other:MiscPoints Equity:Adjustment
2024-06-20 balance Assets:Other:MiscPoints                  24232 POINT

2024-06-21 * "Delta BOS>SEA"
  Expenses:Travel:Air       24232 POINT {0.015 USD}
  Assets:Other:MiscPoints  -24232 POINT             @ 0.01 USD
  Income:Other:Promotion

2024-07-19 pad Assets:Other:MiscPoints Equity:Adjustment
2024-07-20 balance Assets:Other:MiscPoints                  24232 POINT

2024-07-21 * "Delta BOS>SEA"
  Expenses:Travel:Air       24232 POINT {0.015 USD}
  Assets:Other:MiscPoints  -24232 POINT             @ 0.015 USD