simonmichael / hledger

Robust, fast, intuitive plain text accounting tool with CLI, TUI and web interfaces.
https://hledger.org
GNU General Public License v3.0
3.01k stars 321 forks source link

Report realized capital gains #488

Closed matze closed 2 years ago

matze commented 7 years ago

[Original title: How to realize capital gains or set lot price?. See newer discussions starting at #1015. Depends on #1023.]

This is a very simple example of a use case ledger gets right but I cannot model appropriately in hledger. I googled quite a bit but I am having a hard time understanding getting that right. Suppose, we bought 10 shares with our entire cash

2017/01/01  Start
    Cash                                               1000.00 EUR
    Equity  
2017/01/01  Buy
    Cash
    Assets                                    10 AAPL @ 100.00 EUR

and suppose we sell them with a profit of 100 EUR per share. In ledger we can set the original lot price

2017/01/02  Sell
    Cash                                               2000.00 EUR
    Capital Gain                                      -1000.00 EUR
    Assets                      -10 AAPL {100.00 EUR} @ 200.00 EUR

and realize capital gains correctly. Of course, hledger fails with a syntax error or is unable to realize gains correctly when setting the lot price with {= 100.00 EUR}.

So, how could I model this fact correctly?

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/41047681-how-to-realize-capital-gains-or-set-lot-price?utm_campaign=plugin&utm_content=tracker%2F536505&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F536505&utm_medium=issues&utm_source=github).
simonmichael commented 7 years ago

Thanks! I love simple examples. In hledger, I think @ PRICE ("transaction price") is equivalent to Ledger's {= PRICE} (lot price). I don't know if we can report what you're getting from Ledger though. Can you show that too (command and output) ?

matze commented 7 years ago

This is what I get from hledger:

$ hledger -f test.hledger.ledger bal
hledger: could not balance this transaction (real postings are off by -1000.00 EUR)
2017/01/02 Sell
    Cash                    2000.00 EUR
    Capital Gain           -1000.00 EUR
    Assets        -10 AAPL @ 200.00 EUR

and this is what I get from ledger:

$ ledger -f test.ledger.ledger bal
        -1000.00 EUR  Capital Gain
         2000.00 EUR  Cash
        -1000.00 EUR  Equity
--------------------
                   0
simonmichael commented 7 years ago

What's in test.hledger.ledger

matze commented 7 years ago

Pretty much what's suggested elsewhere:

2017/01/01  Start
    Cash                                               1000.00 EUR
    Equity  
2017/01/01  Buy
    Cash
    Assets                                    10 AAPL @ 100.00 EUR
2017/01/02  Sell
    Cash                                               2000.00 EUR
    Capital Gain                                      -1000.00 EUR
    Assets                                   -10 AAPL @ 200.00 EUR {=100.00 EUR}

As expected, the {=100.00 EUR} is ignored thus the additional 1000.00 EUR come out of nowhere.

simonmichael commented 7 years ago

Got it. So:

2017/01/01  Start
    Cash                                               1000.00 EUR
    Equity  

2017/01/01  Buy
    Cash
    Assets                                    10 AAPL @ 100.00 EUR

2017/01/02  Sell
    Cash                                               2000.00 EUR
    Capital Gain                                      -1000.00 EUR
    Assets                                   -10 AAPL @ 200.00 EUR {=100.00 EUR}
        ; specifying lot price should generate capital gain  ^^

hledger doesn't have this feature. As you say, it ignores the {= } syntax, doesn't notice the capital gain and so the transaction doesn't balance as written. In the Ledger example, balances are:

$ ledger -f 488.ledger bal
        -1000.00 EUR  Capital Gain
         2000.00 EUR  Cash
        -1000.00 EUR  Equity
--------------------
                   0

This report on the hledger example looks rather similar, but I don't know if that means anything interesting:

$ hledger -f 488.hledger bal -B
        -1000.00 EUR  Assets
         2000.00 EUR  Cash
        -1000.00 EUR  Equity
--------------------
                   0

As a workaround, you can record the capital gain manually with an unbalanced posting:

2017/01/02  Sell
    Cash                                               2000.00 EUR
    (Capital Gain)                                    -1000.00 EUR
   Assets                                   -10 AAPL @ 200.00 EUR {=100.00 EUR}

which is not ideal, but is equivalent to Ledger's automatic calculation, I think:

$ hledger -f 488.hledger bal 
        -1000.00 EUR  Capital Gain
         2000.00 EUR  Cash
        -1000.00 EUR  Equity
--------------------
                   0

Should hledger implement this ? Should it follow Ledger's syntax exactly ? Can that be improved ? I realised I don't fully understand it yet, eg the difference between { } and {= }.

matze commented 7 years ago

As a workaround, you can record the capital gain manually with an unbalanced posting.

Not ideal, but perfectly sufficient for me right now! Thanks.

Should hledger implement this ? Should it follow Ledger's syntax exactly ?

In my perfect world yes because that would improve interoperability between different ledger-likes. Ideally, there would be a formal specification (that might also include ways to extend the specification in a compatible way) that every program would implement. But I think, this is far from being realistic right now.

ony commented 7 years ago

I don't understand this either. Though in near future I'll plan to start tracking that.

% COLUMNS=80 hledger bal -HD -f -           
2017/1/1 Buy
    Cash  -1000 EUR
    Assets  10 AAPL @ 100 EUR

2017/1/2 Sell
    Cash  2000 EUR
    Assets  -10 AAPL @ 200 EUR
Ending balances (historical) in 2017/01/01-2017/01/02:

        ||         2017/01/01  2017/01/02 
========++================================
 Assets ||            10 AAPL           0 
 Cash   ||          -1000 EUR    1000 EUR 
--------++--------------------------------
        || 10 AAPL, -1000 EUR    1000 EUR 

It is clear that we've gained 1000 EUR.

Maybe you want something like:

2017/1/1 Gain
    Assets  -10 AAPL  ; take our stock
    Capital Gain  500 EUR @@ 10 AAPL  ; put it under last price
    Capital Gain  -1000 EUR @@ 10 AAPL  ; pull out under new price
    Assets  10 AAPL  ; put it back 

2017/1/2 Sell
    Assets  -10 AAPL  ; take our stock
    Capital Gain  1000 EUR @@ 10 AAPL  ; put it under last price
    Capital Gain  -2000 EUR @@ 10 AAPL  ; pull out under new price
    Cash  2000 EUR @@ 10 AAPL  ; actually sell it under same new price

With --cost we can switch our view from left side of @ to right side where in terms of AAPL nothing changes and balance of Capital Gain is 0 EUR.

P.S. With C++ ledger I get

% ledger reg -f -
2017/01/02  Sell
    Cash                                               2000.00 EUR
    Capital Gain                                      -1000.00 EUR
    Assets                                   -10 AAPL @ 200.00 EUR {=100.00 EUR}
While parsing file "", line 4:
While balancing transaction from streamed input:
Unbalanced remainder is:
         1000.00 EUR
-2000.00 EUR {=100.00 EUR}
Amount to balance against:
         2000.00 EUR
Error: Transaction does not balance
simonmichael commented 7 years ago

Also related: #377

simonmichael commented 7 years ago

And #82.

alensiljak commented 5 years ago

For me this is absolutely critical. I've just started adjusting my ledger book by adding the lot specifications in the sale transaction, so that correct capital gains can be calculated by ledger. However, this has now broken the compatibility and hledger reports an error while reading the {xx xxx} syntax. i.e.

     |
1569 |         Assets:Shares:ETF     -5.0000 ETF {6.00 EUR} [2013-06-25] @@ 30.00 EUR
     |                                            ^
unexpected '6'
expecting '='

Should hledger implement this ? Should it follow Ledger's syntax exactly ? Can that be improved ?

:+1: It would be useful it ledger's functionality would be copied for a start. The important part is to have a way of matching the lots during sale. What I'm currently doing is

2019-02-01 Buying some stocks
    Assets:Broker:ETF    10 ETF @ 2 EUR
    Assets:Cash               -20 EUR

2019-03-01 Selling those stocks
    Assets:Broker:ETF    -10 ETF { 2 EUR } [2019-02-01] @ 3 EUR
    Income:Capital Gains    -10 EUR
    Assets:Cash               30 EUR

I realised I don't fully understand it yet, eg the difference between { } and {= }.

https://www.ledger-cli.org/3.0/doc/ledger3.html#Fixing-Lot-Prices

Commodities that you keep in order to sell at a later time have a variable value that fluctuates with the market prices. Commodities that you consume should not fluctuate in value, but stay at the lot price they were purchased at. As an extension of “lot pricing”, you can fix the per-unit price of a commodity.

For example, say you buy 10 gallons of gas at $1.20. In future “value” reports, you don’t want these gallons reported in terms of today’s price, but rather the price when you bought it. At the same time, you also want other kinds of commodities—like stocks— reported in terms of today’s price.

I find the difference significant. The {= xy} syntax keeps the lots at "xy" value always, disregarding the price changes. For investments like stocks or mutual funds, this is meaningless. One of the main goals for purchasing them is their price appreciation over time and this is what most want to see.

I.e. ledger has a very useful feature: ledger register BYE returns

2013-02-25 Buy BYE     Assets:Shares:BYE    10595.0000 BYE   10595.0000 BYE
2014-06-02 Sell BYE    Assets:Shares:BYE    -5000.0000 BYE    5595.0000 BYE
2017-08-29 sold BYE    Assets:Shares:BYE    -5595.0000 BYE                0

while ledger register BYE -X EUR gives

2013-02-25 Buy BYE                  Assets:Shares:BYE      3,733.62 EUR   3,733.62 EUR
2014-06-02 Commodities revalued     <Revalued>             3,813.06 EUR   7,546.67 EUR
2014-06-02 Sell BYE                 Assets:Shares:BYE     -3,561.43 EUR   3,985.24 EUR
2017-07-19 Commodities revalued     <Revalued>              -448.12 EUR   3,537.12 EUR
2017-07-20 Commodities revalued     <Revalued>                57.53 EUR   3,594.65 EUR
2017-08-08 Commodities revalued     <Revalued>               -89.32 EUR   3,505.33 EUR
2017-08-17 Commodities revalued     <Revalued>                 7.86 EUR   3,513.19 EUR
2017-08-29 Commodities revalued     <Revalued>            -3,217.34 EUR     295.85 EUR
2017-08-29 sold BYE                 Assets:Shares:BYE       -295.85 EUR              0

which includes tracking the balance over time. In the second report, we can see the original three transactions (one purchase and two sales) and several repricings due to the existence of P records in price-db.

Edit: Actually, I just checked. There are only the prices extracted from the transactions:

P 2013/02/25 00:00:00 BYE 0.47 AUD
P 2014/06/02 00:00:00 BYE 0.95 AUD
P 2017/08/29 00:00:00 BYE 0.08 AUD

The repricings come from the fact that the stock price is in AUD and I'm looking at it in EUR now. So the remaining price records are for AUD/EUR conversion, i.e. P 2019-03-26 00:00:00 AUD 0.631397 EUR

alensiljak commented 5 years ago

The lot-date syntax is also unsupported:

   |
20 |     Assets:Investments:BOND    -15 BOND [2018-03-01] @ 12 AUD
   |                                         ^
unexpected '['
expecting ';', '=', '@', '{', end of input, or newline
simonmichael commented 2 years ago

Closing this 2017 discussion, see the 2019 issues starting at #1015.