On Sat, Jan 19, 2019 at 7:07 PM Martin Michlmayr <[email protected]> wrote:

> ledger has the concept of fixated prices/costs which tells it not to
> revalue it according to the current pricedb but to always use the
> specified cost/price; see
> https://www.ledger-cli.org/3.0/doc/ledger3.html#Fixated-prices-and-costs
>
> I am trying to figure out how to handle these in ledger2beancount.
> Obviously, beancount doesn't have a similar concept, so we need to strip
> the fixated prices/costs and document how to interact with beancount to
> get the expected behaviour.
>
> If something is a fixated cost, it's easy.  This:
>
> 2012-04-10 My Broker
>     Assets:Brokerage            10 AAPL {=$50.00}
>     Assets:Brokerage:Cash       $750.00
>
> becomes:
>
> 2012-04-10 txn "My Broker"
>     Assets:Brokerage            10 AAPL {50.00 USD}
>     Assets:Brokerage:Cash       750.00 USD
>

> we convert everything on its original date and sum it up, we should get
> the same value.
>

No.
The implicit_prices plugin only inserts a Price directive corresponding to
the @ <price> annotation on postings.
That's all it does.
By default these price annotations aren't used, except by the sellgains
plugin, and they're available for your custom scripts to access on the
Posting object.
The Price directives (optionally inserted) are used to fill in the price
database.


However, this doesn't work in practice, at least not in all cases.
>

It would fail if you have multiple postings with different @ <price>
annotations on the same day, as the price database has a daily resolution,
and there's a single price available for that day.



> I have an example where the two do not match:
>
> beancount> SELECT SUM(CONVERT(position, 'USD', date)) WHERE date < 2018

Note that this transaction doesn't balance, it is missing an income posting.
(That's not related to the problem you describe, but worthy of mention.)


and you can run: SUM(COST(position)) to get the value.  This will always
> return the "fixated" value (i.e. the cost).
>
> I thought that if you add:
>
> plugin "beancount.plugins.implicit_prices"
>
> you could alternatively do SUM(CONVERT(position, 'USD', date)), i.e.
> if-03-01 AND account ~ '^Assets:Accounts-Receivable'

sum_convert_position
> --------------------
> 508739.717262620 USD
> beancount> SELECT SUM(COST(position)) WHERE date < 2018-03-01 AND account
> ~ '^Assets:Accounts-Receivable'
>  sum_cost_position
> -------------------
> 507589.87445810 USD
> beancount>
>
> Martin, is this expected or a bug? Maybe this happens when you have

multiple price entries on the same day and beancount only has one price
> per day instead of taking the price from the transaction?
>

I believe that is correct and not-a-bug.
Calling CONVERT() does not use the postings' price annotation to make the
conversion.
It is called from here in your SQL statement:
https://bitbucket.org/blais/beancount/src/487cb9b0248427ac6882889b6b8bcc1665ce393e/beancount/query/query_env.py#lines-519
that function:
https://bitbucket.org/blais/beancount/src/487cb9b0248427ac6882889b6b8bcc1665ce393e/beancount/core/convert.py#lines-146
If you inspect that function, you will find that it looks for a price
annotation (the hasattr() call) to find out the currency, if present, but
it uses the price map to make the conversion, without access to the price
annotation from the posting.

Now, I traced the call and it indeed receives a Position instance, not a
Posting.
If you're not familiar with the codebase, a Position is contained within a
Posting.
A Posting is like a Position which has an account, a price annotation, and
some metadata.
In fact, a number of functions accept either one of those.
(If you're familiar with OO, imagine that a Position is a (concrete) base
class, and a Posting derived from it, though in this case it's not strictly
true, it's all done via duck-typing, that is, Position just happens to have
a strict subset of the attributes of Posting.)

If we drill down a bit, we will find that the SQL accessor for the
"position" field synthesizes a Position object from the posting:
https://bitbucket.org/blais/beancount/src/487cb9b0248427ac6882889b6b8bcc1665ce393e/beancount/query/query_env.py#lines-1609
There is no corresponding accessor for Posting.

Now, it would be unwise to change the semantics of "position" here and
potentially mess with everyone's numbers so I won't do that.
But I /could/ add a "posting" accessor very easily, which would allow you
to choose.
(Documenting this would be no fun...)

Uh-oh... an hour later...

I started to quickly prototype this and an hour after I'm realizing it's
more complicated than I though to add it, it's not a simple matter of
adding accessors for posting. The behavior of the conversion in all
possible cases is non-trivial and would require a lot of testing work -- I
already sank an hour into this, this is probably worth a good afternoon's
if not an entire day's work. Can't do it quickly right now, but I think
it's doable. May need some serious revisiting of the overall semantics.)

Also, when CONVERT() converts through the cost basis (that is, units to
cost to then foreign price), that's entirely done through the price
database too, so potentially you're not getting the right value for cost,
if you acquired multiple lots at different costs.

;; Showcasing that conversion via cost uses the price db.

plugin "beancount.plugins.auto_accounts"
plugin "beancount.plugins.sellgains"
plugin "beancount.plugins.implicit_prices"

2012-04-10 txn "My Broker"
  Assets:Brokerage             5 AAPL {50.00 USD}
  Assets:Brokerage:Cash      -250.00 USD

2012-04-10 txn "My Broker"
  Assets:Brokerage             5 AAPL {55.00 USD}
  Assets:Brokerage:Cash      -275.00 USD

2018-01-01 query "detail" "
SELECT
  date,
  account,
  position,
  COST(position),
  CONVERT(position, 'USD', date),
  CONVERT(COST(position), 'USD', date)
WHERE
  date < 2018-03-01 AND
  account ~ '^Assets:Brokerage$'
"


Output:

bean-query twocostssameday.beancount run detail
   date        account           position      cost_posit convert_po
convert_co
---------- ---------------- ------------------ ---------- ----------
----------
2012-04-10 Assets:Brokerage 5 AAPL {50.00 USD} 250.00 USD 275.00 USD 250.00
USD
2012-04-10 Assets:Brokerage 5 AAPL {55.00 USD} 275.00 USD 275.00 USD 275.00
USD

Notice how the middle column has the same cost basis here, because it was
automatically converted via CONVERT() (you didn't ask for the cost basis
when you said just CONVERT()).


Overall, there are many cases to handle and it's a fair amount of
complexity to handle it all "as automatically as possible."
I think I would restrict the semantics in a reimplementation.



And then my other question is what to do about fixated prices, e.g.:
>
> 2012-04-10 My Broker
>     Assets:Brokerage            10 AAPL @ =$50.00
>     Assets:Brokerage:Cash       $750.00
>
> ledger considers a fixated cost and a fixated price the same and this
> transaction will actually lead to 10 APPL {=$50}.  ledger2beancount has
> some logic to figure out when a price should be converted to a cost
> rather than a price.  I'm wondering if in the case of a fixated price,
> we should *always* convert to a cost so the "fixated" value is kept
> around.
>

I have no idea.



>
> Any opinions on this?
>

I'm annoyed at the discrepancies above.
I'm thinking about removing the conversion via cost done implicitly in
CONVERT(), so that if you have cost basis, you need to do COST() explicitly
to convert via it.
This would also make the change easier to implement.

(So little time... Bedtime now, back to research for four days... it all
goes so fast)



> --
> Martin Michlmayr
> https://www.cyrius.com/
>
> --
> You received this message because you are subscribed to the Google Groups
> "Beancount" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/beancount/20190120000737.diia64cksupwrmok%40jirafa.cyrius.com
> .
> For more options, visit https://groups.google.com/d/optout.
>

>

-- 

--- 
You received this message because you are subscribed to the Google Groups 
"Ledger" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to