diff --git a/hledger/hledger.1 b/hledger/hledger.1 index 255c489be..00bcbdb51 100644 --- a/hledger/hledger.1 +++ b/hledger/hledger.1 @@ -4469,14 +4469,295 @@ At a minimum, you need to supply a query (which could be just an account name) to select your investments with \f[C]--inv\f[R], and another query to identify your profit and loss transactions with \f[C]--pnl\f[R]. .PP -It will compute and display the internalized rate of return (IRR) and -time-weighted rate of return (TWR) for your investments for the time -period requested. +This command will compute and display the internalized rate of return +(IRR) and time-weighted rate of return (TWR) for your investments for +the time period requested. Both rates of return are annualized before display, regardless of the length of reporting interval. .PP -An example: +Note, in some cases this report can fail, for these reasons: +.IP \[bu] 2 +Error (NotBracketed): No solution for Internal Rate of Return (IRR). +Possible causes: IRR is huge (>1000000%), balance of investment becomes +negative at some point in time. +.IP \[bu] 2 +Error (SearchFailed): Failed to find solution for Internal Rate of +Return (IRR). +Either search does not converge to a solution, or converges too slowly. +.PP +Examples: +.IP \[bu] 2 +Using roi to report unrealised gains: https://github.com/simonmichael/hledger/blob/master/examples/roi-unrealised.ledger +.PP +More background: +.PP +\[dq]ROI\[dq] stands for \[dq]return on investment\[dq]. +Traditionally this was computed as a difference between current value of +investment and its initial value, expressed in percentage of the initial +value. +.PP +However, this approach is only practical in simple cases, where +investments receives no in-flows or out-flows of money, and where rate +of growth is fixed over time. +For more complex scenarios you need different ways to compute rate of +return, and this command implements two of them: IRR and TWR. +.PP +Internal rate of return, or \[dq]IRR\[dq] (also called +\[dq]money-weighted rate of return\[dq]) takes into account effects of +in-flows and out-flows. +Naively, if you are withdrawing from your investment, your future gains +would be smaller (in absolute numbers), and will be a smaller percentage +of your initial investment, and if you are adding to your investment, +you will receive bigger absolute gains (but probably at the same rate of +return). +IRR is a way to compute rate of return for each period between in-flow +or out-flow of money, and then combine them in a way that gives you an +annual rate of return that investment is expected to generate. +.PP +As mentioned before, in-flows and out-flows would be any cash that you +personally put in or withdraw, and for the \[dq]roi\[dq] command, these +are transactions that involve account(s) matching \f[C]--inv\f[R] +argument and NOT involve account(s) matching \f[C]--pnl\f[R] argument. +.PP +Presumably, you will also record changes in the value of your +investment, and balance them against \[dq]profit and loss\[dq] (or +\[dq]unrealized gains\[dq]) account. +Note that in order for IRR to compute the precise effect of your +in-flows and out-flows on the rate of return, you will need to record +the value of your investement on or close to the days when in- or +out-flows occur. +.PP +Implementation of IRR in hledger should match the \f[C]XIRR\f[R] formula +in Excel. +.PP +Second way to compute rate of return that \f[C]roi\f[R] command +implements is called \[dq]time-weighted rate of return\[dq] or +\[dq]TWR\[dq]. +Like IRR, it will also break the history of your investment into periods +between in-flows and out-flows to compute rate of return per each period +and then a compound rate of return. +However, internal workings of TWR are quite different. +.PP +In technical terms, IRR uses the same approach as computation of net +present value, and tries to find a discount rate that makes net present +value of all the cash flows of your investment to add up to zero. +This could be hard to wrap your head around, especially if you +haven\[aq]t done discounted cash flow analysis before. +.PP +TWR represents your investment as an imaginary \[dq]unit fund\[dq] where +in-flows/ out-flows lead to buying or selling \[dq]units\[dq] of your +investment and changes in its value change the value of \[dq]investment +unit\[dq]. +Change in \[dq]unit price\[dq] over the reporting period gives you rate +of return of your investment. +.PP +References: * Explanation of rate of return * Explanation of IRR * +Explanation of TWR * Examples of computing IRR and TWR and discussion of +the limitations of both metrics +.PP +More examples: +.PP +Lets say that we found an investment in Snake Oil that is proising to +give us 10% annually: +.IP +.nf +\f[C] +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-12-24 Recording the growth of Snake Oil + investment:snake oil = $110 + equity:unrealized gains +\f[R] +.fi +.PP +For now, basic computation of the rate of return, as well as IRR and +TWR, gives us the expected 10%: +.IP +.nf +\f[C] +$ hledger roi -Y --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+-----++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++========+========+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 110 | 10 || 10.00% | 10.00% | ++---++------------+------------++---------------+----------+-------------+-----++--------+--------+ +\f[R] +.fi +.PP +However, lets say that shorty after investing in the Snake Oil we +started to have second thoughs, so we prompty withdrew $90, leaving only +$10 in. +Before Christmas, though, we started to get the \[dq]fear of mission +out\[dq], so we put the $90 back in. +So for most of the year, our investment was just $10 dollars, and it +gave us just $1 in growth: +.IP +.nf +\f[C] +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + +2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + +2019-12-31 Recording the growth of Snake Oil + investment:snake oil = $101 + equity:unrealized gains +\f[R] +.fi +.PP +Now IRR and TWR are drastically different: +.IP +.nf +\f[C] +$ hledger roi -Y --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+-----++-------+-------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++=======+=======+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101 | 1 || 9.32% | 1.00% | ++---++------------+------------++---------------+----------+-------------+-----++-------+-------+ +\f[R] +.fi +.PP +Here, IRR tells us that we made close to 10% on the $10 dollars that we +had in the account most of the time. +And TWR is ... +just 1%? +Why? +.PP +Based on the transactions in our journal, TWR \[dq]think\[dq] that we +are buying back $90 worst of Snake Oil at the same price that it had at +the beginning of they year, and then after that our $100 investment gets +$1 increase in value, or 1% of $100. +Let\[aq]s take a closer look at what is happening here by asking for +quarterly reports instead of annual: +.IP +.nf +\f[C] +$ hledger roi -Q --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+-----++--------+-------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++========+=======+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10 | 0 || 0.00% | 0.00% | +| 2 || 2019-04-01 | 2019-06-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | +| 3 || 2019-07-01 | 2019-09-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | +| 4 || 2019-10-01 | 2019-12-31 || 10 | 90 | 101 | 1 || 37.80% | 4.03% | ++---++------------+------------++---------------+----------+-------------+-----++--------+-------+ +\f[R] +.fi +.PP +Now both IRR and TWR are thrown off by the fact that all of the growth +for our investment happens in Q4 2019. +This happes because IRR computation is still yielding 9.32% and TWR is +still 1%, but this time these are rates for three month period instead +of twelve, so in order to get an annual rate they should be multiplied +by four! +.PP +Let\[aq]s try to keep a better record of how Snake Oil grew in value: +.IP +.nf +\f[C] +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + +2019-02-28 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-06-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-09-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + +2019-12-31 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 +\f[R] +.fi +.PP +Would our quartery report look better now? +Almost: +.IP +.nf +\f[C] +$ hledger roi -Q --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++========+========+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | +| 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | +| 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | +| 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 1.00% | ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +\f[R] +.fi +.PP +Something is still wrong with TWR computation for Q4, and if you have +been paying attention you know what it is already: big $90 buy-back is +recorded prior to the only transaction that captures the change of value +of Snake Oil that happened in this time period. +Lets combine transactions from 30th and 31st of Dec into one: +.IP +.nf +\f[C] +2019-12-30 Fear of missing out and growth of Snake Oil + assets:cash -$90 + investment:snake oil + equity:unrealized gains -$0.25 +\f[R] +.fi +.PP +Now growth of investment properly affects its price at the time of +buy-back: +.IP +.nf +\f[C] +$ hledger roi -Q --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++========+========+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | +| 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | +| 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | +| 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 9.57% | ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +\f[R] +.fi +.PP +And for annual report, TWR now reports the exact profitability of our +investment: +.IP +.nf +\f[C] +$ hledger roi -Y --inv investment --pnl \[dq]unrealized\[dq] ++---++------------+------------++---------------+----------+-------------+------++-------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++=======+========+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101.00 | 1.00 || 9.32% | 10.00% | ++---++------------+------------++---------------+----------+-------------+------++-------+--------+ +\f[R] +.fi .SS stats .PP stats diff --git a/hledger/hledger.info b/hledger/hledger.info index 52fed26e1..c40822d2a 100644 --- a/hledger/hledger.info +++ b/hledger/hledger.info @@ -3798,13 +3798,238 @@ your investments or withdrawals. account name) to select your investments with '--inv', and another query to identify your profit and loss transactions with '--pnl'. - It will compute and display the internalized rate of return (IRR) and -time-weighted rate of return (TWR) for your investments for the time -period requested. Both rates of return are annualized before display, -regardless of the length of reporting interval. + This command will compute and display the internalized rate of return +(IRR) and time-weighted rate of return (TWR) for your investments for +the time period requested. Both rates of return are annualized before +display, regardless of the length of reporting interval. - An example: -https://github.com/simonmichael/hledger/blob/master/examples/roi-unrealised.ledger + Note, in some cases this report can fail, for these reasons: + + * Error (NotBracketed): No solution for Internal Rate of Return + (IRR). Possible causes: IRR is huge (>1000000%), balance of + investment becomes negative at some point in time. + * Error (SearchFailed): Failed to find solution for Internal Rate of + Return (IRR). Either search does not converge to a solution, or + converges too slowly. + + Examples: + + * Using roi to report unrealised gains: + https://github.com/simonmichael/hledger/blob/master/examples/roi-unrealised.ledger + + More background: + + "ROI" stands for "return on investment". Traditionally this was +computed as a difference between current value of investment and its +initial value, expressed in percentage of the initial value. + + However, this approach is only practical in simple cases, where +investments receives no in-flows or out-flows of money, and where rate +of growth is fixed over time. For more complex scenarios you need +different ways to compute rate of return, and this command implements +two of them: IRR and TWR. + + Internal rate of return, or "IRR" (also called "money-weighted rate +of return") takes into account effects of in-flows and out-flows. +Naively, if you are withdrawing from your investment, your future gains +would be smaller (in absolute numbers), and will be a smaller percentage +of your initial investment, and if you are adding to your investment, +you will receive bigger absolute gains (but probably at the same rate of +return). IRR is a way to compute rate of return for each period between +in-flow or out-flow of money, and then combine them in a way that gives +you an annual rate of return that investment is expected to generate. + + As mentioned before, in-flows and out-flows would be any cash that +you personally put in or withdraw, and for the "roi" command, these are +transactions that involve account(s) matching '--inv' argument and NOT +involve account(s) matching '--pnl' argument. + + Presumably, you will also record changes in the value of your +investment, and balance them against "profit and loss" (or "unrealized +gains") account. Note that in order for IRR to compute the precise +effect of your in-flows and out-flows on the rate of return, you will +need to record the value of your investement on or close to the days +when in- or out-flows occur. + + Implementation of IRR in hledger should match the 'XIRR' formula in +Excel. + + Second way to compute rate of return that 'roi' command implements is +called "time-weighted rate of return" or "TWR". Like IRR, it will also +break the history of your investment into periods between in-flows and +out-flows to compute rate of return per each period and then a compound +rate of return. However, internal workings of TWR are quite different. + + In technical terms, IRR uses the same approach as computation of net +present value, and tries to find a discount rate that makes net present +value of all the cash flows of your investment to add up to zero. This +could be hard to wrap your head around, especially if you haven't done +discounted cash flow analysis before. + + TWR represents your investment as an imaginary "unit fund" where +in-flows/ out-flows lead to buying or selling "units" of your investment +and changes in its value change the value of "investment unit". Change +in "unit price" over the reporting period gives you rate of return of +your investment. + + References: * Explanation of rate of return * Explanation of IRR * +Explanation of TWR * Examples of computing IRR and TWR and discussion of +the limitations of both metrics + + More examples: + + Lets say that we found an investment in Snake Oil that is proising to +give us 10% annually: + +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-12-24 Recording the growth of Snake Oil + investment:snake oil = $110 + equity:unrealized gains + + For now, basic computation of the rate of return, as well as IRR and +TWR, gives us the expected 10%: + +$ hledger roi -Y --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+-----++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++========+========+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 110 | 10 || 10.00% | 10.00% | ++---++------------+------------++---------------+----------+-------------+-----++--------+--------+ + + However, lets say that shorty after investing in the Snake Oil we +started to have second thoughs, so we prompty withdrew $90, leaving only +$10 in. Before Christmas, though, we started to get the "fear of +mission out", so we put the $90 back in. So for most of the year, our +investment was just $10 dollars, and it gave us just $1 in growth: + +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + +2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + +2019-12-31 Recording the growth of Snake Oil + investment:snake oil = $101 + equity:unrealized gains + + Now IRR and TWR are drastically different: + +$ hledger roi -Y --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+-----++-------+-------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++=======+=======+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101 | 1 || 9.32% | 1.00% | ++---++------------+------------++---------------+----------+-------------+-----++-------+-------+ + + Here, IRR tells us that we made close to 10% on the $10 dollars that +we had in the account most of the time. And TWR is ... just 1%? Why? + + Based on the transactions in our journal, TWR "think" that we are +buying back $90 worst of Snake Oil at the same price that it had at the +beginning of they year, and then after that our $100 investment gets $1 +increase in value, or 1% of $100. Let's take a closer look at what is +happening here by asking for quarterly reports instead of annual: + +$ hledger roi -Q --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+-----++--------+-------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+=====++========+=======+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10 | 0 || 0.00% | 0.00% | +| 2 || 2019-04-01 | 2019-06-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | +| 3 || 2019-07-01 | 2019-09-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | +| 4 || 2019-10-01 | 2019-12-31 || 10 | 90 | 101 | 1 || 37.80% | 4.03% | ++---++------------+------------++---------------+----------+-------------+-----++--------+-------+ + + Now both IRR and TWR are thrown off by the fact that all of the +growth for our investment happens in Q4 2019. This happes because IRR +computation is still yielding 9.32% and TWR is still 1%, but this time +these are rates for three month period instead of twelve, so in order to +get an annual rate they should be multiplied by four! + + Let's try to keep a better record of how Snake Oil grew in value: + +2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + +2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + +2019-02-28 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-06-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-09-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + +2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + +2019-12-31 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + + Would our quartery report look better now? Almost: + +$ hledger roi -Q --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++========+========+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | +| 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | +| 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | +| 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 1.00% | ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ + + Something is still wrong with TWR computation for Q4, and if you have +been paying attention you know what it is already: big $90 buy-back is +recorded prior to the only transaction that captures the change of value +of Snake Oil that happened in this time period. Lets combine +transactions from 30th and 31st of Dec into one: + +2019-12-30 Fear of missing out and growth of Snake Oil + assets:cash -$90 + investment:snake oil + equity:unrealized gains -$0.25 + + Now growth of investment properly affects its price at the time of +buy-back: + +$ hledger roi -Q --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++========+========+ +| 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | +| 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | +| 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | +| 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 9.57% | ++---++------------+------------++---------------+----------+-------------+------++--------+--------+ + + And for annual report, TWR now reports the exact profitability of our +investment: + +$ hledger roi -Y --inv investment --pnl "unrealized" ++---++------------+------------++---------------+----------+-------------+------++-------+--------+ +| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | ++===++============+============++===============+==========+=============+======++=======+========+ +| 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101.00 | 1.00 || 9.32% | 10.00% | ++---++------------+------------++---------------+----------+-------------+------++-------+--------+  File: hledger.info, Node: stats, Next: tags, Prev: roi, Up: COMMANDS @@ -4290,30 +4515,30 @@ Node: rewrite vs print --auto133861 Ref: #rewrite-vs.-print---auto134040 Node: roi134596 Ref: #roi134694 -Node: stats135805 -Ref: #stats135904 -Node: tags136692 -Ref: #tags136790 -Node: test137309 -Ref: #test137417 -Node: Add-on commands138164 -Ref: #add-on-commands138281 -Node: ui139624 -Ref: #ui139712 -Node: web139766 -Ref: #web139869 -Node: iadd139985 -Ref: #iadd140096 -Node: interest140178 -Ref: #interest140285 -Node: ENVIRONMENT140525 -Ref: #environment140637 -Node: FILES141622 -Ref: #files-1141725 -Node: LIMITATIONS141938 -Ref: #limitations142057 -Node: TROUBLESHOOTING142799 -Ref: #troubleshooting142912 +Node: stats146904 +Ref: #stats147003 +Node: tags147791 +Ref: #tags147889 +Node: test148408 +Ref: #test148516 +Node: Add-on commands149263 +Ref: #add-on-commands149380 +Node: ui150723 +Ref: #ui150811 +Node: web150865 +Ref: #web150968 +Node: iadd151084 +Ref: #iadd151195 +Node: interest151277 +Ref: #interest151384 +Node: ENVIRONMENT151624 +Ref: #environment151736 +Node: FILES152721 +Ref: #files-1152824 +Node: LIMITATIONS153037 +Ref: #limitations153156 +Node: TROUBLESHOOTING153898 +Ref: #troubleshooting154011  End Tag Table diff --git a/hledger/hledger.txt b/hledger/hledger.txt index df8474cec..dd0ef6e41 100644 --- a/hledger/hledger.txt +++ b/hledger/hledger.txt @@ -3230,20 +3230,247 @@ COMMANDS count name) to select your investments with --inv, and another query to identify your profit and loss transactions with --pnl. - It will compute and display the internalized rate of return (IRR) and - time-weighted rate of return (TWR) for your investments for the time - period requested. Both rates of return are annualized before display, - regardless of the length of reporting interval. + This command will compute and display the internalized rate of return + (IRR) and time-weighted rate of return (TWR) for your investments for + the time period requested. Both rates of return are annualized before + display, regardless of the length of reporting interval. - An example: https://github.com/simonmichael/hledger/blob/master/exam- - ples/roi-unrealised.ledger + Note, in some cases this report can fail, for these reasons: + + o Error (NotBracketed): No solution for Internal Rate of Return (IRR). + Possible causes: IRR is huge (>1000000%), balance of investment be- + comes negative at some point in time. + + o Error (SearchFailed): Failed to find solution for Internal Rate of + Return (IRR). Either search does not converge to a solution, or con- + verges too slowly. + + Examples: + + o Using roi to report unrealised gains: https://github.com/simon- + michael/hledger/blob/master/examples/roi-unrealised.ledger + + More background: + + "ROI" stands for "return on investment". Traditionally this was com- + puted as a difference between current value of investment and its ini- + tial value, expressed in percentage of the initial value. + + However, this approach is only practical in simple cases, where invest- + ments receives no in-flows or out-flows of money, and where rate of + growth is fixed over time. For more complex scenarios you need differ- + ent ways to compute rate of return, and this command implements two of + them: IRR and TWR. + + Internal rate of return, or "IRR" (also called "money-weighted rate of + return") takes into account effects of in-flows and out-flows. + Naively, if you are withdrawing from your investment, your future gains + would be smaller (in absolute numbers), and will be a smaller percent- + age of your initial investment, and if you are adding to your invest- + ment, you will receive bigger absolute gains (but probably at the same + rate of return). IRR is a way to compute rate of return for each pe- + riod between in-flow or out-flow of money, and then combine them in a + way that gives you an annual rate of return that investment is expected + to generate. + + As mentioned before, in-flows and out-flows would be any cash that you + personally put in or withdraw, and for the "roi" command, these are + transactions that involve account(s) matching --inv argument and NOT + involve account(s) matching --pnl argument. + + Presumably, you will also record changes in the value of your invest- + ment, and balance them against "profit and loss" (or "unrealized + gains") account. Note that in order for IRR to compute the precise ef- + fect of your in-flows and out-flows on the rate of return, you will + need to record the value of your investement on or close to the days + when in- or out-flows occur. + + Implementation of IRR in hledger should match the XIRR formula in Ex- + cel. + + Second way to compute rate of return that roi command implements is + called "time-weighted rate of return" or "TWR". Like IRR, it will also + break the history of your investment into periods between in-flows and + out-flows to compute rate of return per each period and then a compound + rate of return. However, internal workings of TWR are quite different. + + In technical terms, IRR uses the same approach as computation of net + present value, and tries to find a discount rate that makes net present + value of all the cash flows of your investment to add up to zero. This + could be hard to wrap your head around, especially if you haven't done + discounted cash flow analysis before. + + TWR represents your investment as an imaginary "unit fund" where in- + flows/ out-flows lead to buying or selling "units" of your investment + and changes in its value change the value of "investment unit". Change + in "unit price" over the reporting period gives you rate of return of + your investment. + + References: * Explanation of rate of return * Explanation of IRR * Ex- + planation of TWR * Examples of computing IRR and TWR and discussion of + the limitations of both metrics + + More examples: + + Lets say that we found an investment in Snake Oil that is proising to + give us 10% annually: + + 2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + + 2019-12-24 Recording the growth of Snake Oil + investment:snake oil = $110 + equity:unrealized gains + + For now, basic computation of the rate of return, as well as IRR and + TWR, gives us the expected 10%: + + $ hledger roi -Y --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+-----++--------+--------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+=====++========+========+ + | 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 110 | 10 || 10.00% | 10.00% | + +---++------------+------------++---------------+----------+-------------+-----++--------+--------+ + + However, lets say that shorty after investing in the Snake Oil we + started to have second thoughs, so we prompty withdrew $90, leaving + only $10 in. Before Christmas, though, we started to get the "fear of + mission out", so we put the $90 back in. So for most of the year, our + investment was just $10 dollars, and it gave us just $1 in growth: + + 2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + + 2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + + 2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + + 2019-12-31 Recording the growth of Snake Oil + investment:snake oil = $101 + equity:unrealized gains + + Now IRR and TWR are drastically different: + + $ hledger roi -Y --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+-----++-------+-------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+=====++=======+=======+ + | 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101 | 1 || 9.32% | 1.00% | + +---++------------+------------++---------------+----------+-------------+-----++-------+-------+ + + Here, IRR tells us that we made close to 10% on the $10 dollars that we + had in the account most of the time. And TWR is ... just 1%? Why? + + Based on the transactions in our journal, TWR "think" that we are buy- + ing back $90 worst of Snake Oil at the same price that it had at the + beginning of they year, and then after that our $100 investment gets $1 + increase in value, or 1% of $100. Let's take a closer look at what is + happening here by asking for quarterly reports instead of annual: + + $ hledger roi -Q --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+-----++--------+-------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+=====++========+=======+ + | 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10 | 0 || 0.00% | 0.00% | + | 2 || 2019-04-01 | 2019-06-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | + | 3 || 2019-07-01 | 2019-09-30 || 10 | 0 | 10 | 0 || 0.00% | 0.00% | + | 4 || 2019-10-01 | 2019-12-31 || 10 | 90 | 101 | 1 || 37.80% | 4.03% | + +---++------------+------------++---------------+----------+-------------+-----++--------+-------+ + + Now both IRR and TWR are thrown off by the fact that all of the growth + for our investment happens in Q4 2019. This happes because IRR compu- + tation is still yielding 9.32% and TWR is still 1%, but this time these + are rates for three month period instead of twelve, so in order to get + an annual rate they should be multiplied by four! + + Let's try to keep a better record of how Snake Oil grew in value: + + 2019-01-01 Investing in Snake Oil + assets:cash -$100 + investment:snake oil + + 2019-01-02 Buyers remorse + assets:cash $90 + investment:snake oil + + 2019-02-28 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + + 2019-06-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + + 2019-09-30 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + + 2019-12-30 Fear of missing out + assets:cash -$90 + investment:snake oil + + 2019-12-31 Recording the growth of Snake Oil + investment:snake oil + equity:unrealized gains -$0.25 + + Would our quartery report look better now? Almost: + + $ hledger roi -Q --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+------++--------+--------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+======++========+========+ + | 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | + | 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | + | 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | + | 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 1.00% | + +---++------------+------------++---------------+----------+-------------+------++--------+--------+ + + Something is still wrong with TWR computation for Q4, and if you have + been paying attention you know what it is already: big $90 buy-back is + recorded prior to the only transaction that captures the change of + value of Snake Oil that happened in this time period. Lets combine + transactions from 30th and 31st of Dec into one: + + 2019-12-30 Fear of missing out and growth of Snake Oil + assets:cash -$90 + investment:snake oil + equity:unrealized gains -$0.25 + + Now growth of investment properly affects its price at the time of buy- + back: + + $ hledger roi -Q --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+------++--------+--------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+======++========+========+ + | 1 || 2019-01-01 | 2019-03-31 || 0 | 10 | 10.25 | 0.25 || 9.53% | 10.53% | + | 2 || 2019-04-01 | 2019-06-30 || 10.25 | 0 | 10.50 | 0.25 || 10.15% | 10.15% | + | 3 || 2019-07-01 | 2019-09-30 || 10.50 | 0 | 10.75 | 0.25 || 9.79% | 9.78% | + | 4 || 2019-10-01 | 2019-12-31 || 10.75 | 90 | 101.00 | 0.25 || 8.05% | 9.57% | + +---++------------+------------++---------------+----------+-------------+------++--------+--------+ + + And for annual report, TWR now reports the exact profitability of our + investment: + + $ hledger roi -Y --inv investment --pnl "unrealized" + +---++------------+------------++---------------+----------+-------------+------++-------+--------+ + | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | + +===++============+============++===============+==========+=============+======++=======+========+ + | 1 || 2019-01-01 | 2019-12-31 || 0 | 100 | 101.00 | 1.00 || 9.32% | 10.00% | + +---++------------+------------++---------------+----------+-------------+------++-------+--------+ stats stats Show some journal statistics. - The stats command displays summary information for the whole journal, - or a matched part of it. With a reporting interval, it shows a report + The stats command displays summary information for the whole journal, + or a matched part of it. With a reporting interval, it shows a report for each report period. Example: @@ -3261,35 +3488,35 @@ COMMANDS Commodities : 1 ($) Market prices : 12 ($) - This command also supports output destination and output format selec- + This command also supports output destination and output format selec- tion. tags tags - List the unique tag names used in the journal. With a TAGREGEX argu- + List the unique tag names used in the journal. With a TAGREGEX argu- ment, only tag names matching the regular expression (case insensitive) - are shown. With QUERY arguments, only transactions matching the query + are shown. With QUERY arguments, only transactions matching the query are considered. With the --values flag, the tags' unique values are listed instead. - With --parsed flag, all tags or values are shown in the order they are + With --parsed flag, all tags or values are shown in the order they are parsed from the input data, including duplicates. - With -E/--empty, any blank/empty values will also be shown, otherwise + With -E/--empty, any blank/empty values will also be shown, otherwise they are omitted. test test Run built-in unit tests. - This command runs the unit tests built in to hledger and hledger-lib, - printing the results on stdout. If any test fails, the exit code will + This command runs the unit tests built in to hledger and hledger-lib, + printing the results on stdout. If any test fails, the exit code will be non-zero. - This is mainly used by hledger developers, but you can also use it to - sanity-check the installed hledger executable on your platform. All - tests are expected to pass - if you ever see a failure, please report + This is mainly used by hledger developers, but you can also use it to + sanity-check the installed hledger executable on your platform. All + tests are expected to pass - if you ever see a failure, please report as a bug! This command also accepts tasty test runner options, written after a -- @@ -3298,35 +3525,35 @@ COMMANDS $ hledger test -- -pData.Amount --color=never - For help on these, see https://github.com/feuerbach/tasty#options (-- + For help on these, see https://github.com/feuerbach/tasty#options (-- --help currently doesn't show them). Add-on commands - hledger also searches for external add-on commands, and will include + hledger also searches for external add-on commands, and will include these in the commands list. These are programs or scripts in your PATH - whose name starts with hledger- and ends with a recognised file exten- + whose name starts with hledger- and ends with a recognised file exten- sion (currently: no extension, bat,com,exe, hs,lhs,pl,py,rb,rkt,sh). - Add-ons can be invoked like any hledger command, but there are a few + Add-ons can be invoked like any hledger command, but there are a few things to be aware of. Eg if the hledger-web add-on is installed, - o hledger -h web shows hledger's help, while hledger web -h shows + o hledger -h web shows hledger's help, while hledger web -h shows hledger-web's help. - o Flags specific to the add-on must have a preceding -- to hide them - from hledger. So hledger web --serve --port 9000 will be rejected; + o Flags specific to the add-on must have a preceding -- to hide them + from hledger. So hledger web --serve --port 9000 will be rejected; you must use hledger web -- --serve --port 9000. o You can always run add-ons directly if preferred: hledger-web --serve --port 9000. - Add-ons are a relatively easy way to add local features or experiment - with new ideas. They can be written in any language, but haskell - scripts have a big advantage: they can use the same hledger (and - haskell) library functions that built-in commands do, for command-line + Add-ons are a relatively easy way to add local features or experiment + with new ideas. They can be written in any language, but haskell + scripts have a big advantage: they can use the same hledger (and + haskell) library functions that built-in commands do, for command-line options, journal parsing, reporting, etc. - Two important add-ons are the hledger-ui and hledger-web user inter- + Two important add-ons are the hledger-ui and hledger-web user inter- faces. These are maintained and released along with hledger: ui @@ -3345,20 +3572,20 @@ COMMANDS hledger-interest generates interest transactions for an account accord- ing to various schemes. - A few more experimental or old add-ons can be found in hledger's bin/ + A few more experimental or old add-ons can be found in hledger's bin/ directory. These are typically prototypes and not guaranteed to work. ENVIRONMENT LEDGER_FILE The journal file path when not specified with -f. Default: - ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.jour- + ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.jour- nal). - A typical value is ~/DIR/YYYY.journal, where DIR is a version-con- - trolled finance directory and YYYY is the current year. Or ~/DIR/cur- + A typical value is ~/DIR/YYYY.journal, where DIR is a version-con- + trolled finance directory and YYYY is the current year. Or ~/DIR/cur- rent.journal, where current.journal is a symbolic link to YYYY.journal. On Mac computers, you can set this and other environment variables in a - more thorough way that also affects applications started from the GUI + more thorough way that also affects applications started from the GUI (say, an Emacs dock icon). Eg on MacOS Catalina I have a ~/.MacOSX/en- vironment.plist file containing @@ -3368,21 +3595,21 @@ ENVIRONMENT To see the effect you may need to killall Dock, or reboot. - COLUMNS The screen width used by the register command. Default: the + COLUMNS The screen width used by the register command. Default: the full terminal width. - NO_COLOR If this variable exists with any value, hledger will not use - ANSI color codes in terminal output. This overrides the + NO_COLOR If this variable exists with any value, hledger will not use + ANSI color codes in terminal output. This overrides the --color/--colour option. FILES - Reads data from one or more files in hledger journal, timeclock, time- - dot, or CSV format specified with -f, or $LEDGER_FILE, or - $HOME/.hledger.journal (on windows, perhaps + Reads data from one or more files in hledger journal, timeclock, time- + dot, or CSV format specified with -f, or $LEDGER_FILE, or + $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal). LIMITATIONS - The need to precede addon command options with -- when invoked from + The need to precede addon command options with -- when invoked from hledger is awkward. When input data contains non-ascii characters, a suitable system locale @@ -3398,36 +3625,36 @@ LIMITATIONS In a Cygwin/MSYS/Mintty window, the tab key is not supported in hledger add. - Not all of Ledger's journal file syntax is supported. See file format + Not all of Ledger's journal file syntax is supported. See file format differences. - On large data files, hledger is slower and uses more memory than + On large data files, hledger is slower and uses more memory than Ledger. TROUBLESHOOTING - Here are some issues you might encounter when you run hledger (and re- - member you can also seek help from the IRC channel, mail list or bug + Here are some issues you might encounter when you run hledger (and re- + member you can also seek help from the IRC channel, mail list or bug tracker): Successfully installed, but "No command 'hledger' found" stack and cabal install binaries into a special directory, which should - be added to your PATH environment variable. Eg on unix-like systems, + be added to your PATH environment variable. Eg on unix-like systems, that is ~/.local/bin and ~/.cabal/bin respectively. I set a custom LEDGER_FILE, but hledger is still using the default file - LEDGER_FILE should be a real environment variable, not just a shell - variable. The command env | grep LEDGER_FILE should show it. You may + LEDGER_FILE should be a real environment variable, not just a shell + variable. The command env | grep LEDGER_FILE should show it. You may need to use export. Here's an explanation. - Getting errors like "Illegal byte sequence" or "Invalid or incomplete - multibyte or wide character" or "commitAndReleaseBuffer: invalid argu- + Getting errors like "Illegal byte sequence" or "Invalid or incomplete + multibyte or wide character" or "commitAndReleaseBuffer: invalid argu- ment (invalid character)" Programs compiled with GHC (hledger, haskell build tools, etc.) need to have a UTF-8-aware locale configured in the environment, otherwise they - will fail with these kinds of errors when they encounter non-ascii + will fail with these kinds of errors when they encounter non-ascii characters. - To fix it, set the LANG environment variable to some locale which sup- + To fix it, set the LANG environment variable to some locale which sup- ports UTF-8. The locale you choose must be installed on your system. Here's an example of setting LANG temporarily, on Ubuntu GNU/Linux: @@ -3442,8 +3669,8 @@ TROUBLESHOOTING POSIX $ LANG=en_US.utf8 hledger -f my.journal print # ensure it is used for this command - If available, C.UTF-8 will also work. If your preferred locale isn't - listed by locale -a, you might need to install it. Eg on Ubuntu/De- + If available, C.UTF-8 will also work. If your preferred locale isn't + listed by locale -a, you might need to install it. Eg on Ubuntu/De- bian: $ apt-get install language-pack-fr @@ -3463,8 +3690,8 @@ TROUBLESHOOTING $ echo "export LANG=en_US.utf8" >>~/.bash_profile $ bash --login - Exact spelling and capitalisation may be important. Note the differ- - ence on MacOS (UTF-8, not utf8). Some platforms (eg ubuntu) allow + Exact spelling and capitalisation may be important. Note the differ- + ence on MacOS (UTF-8, not utf8). Some platforms (eg ubuntu) allow variant spellings, but others (eg macos) require it to be exact: $ locale -a | grep -iE en_us.*utf @@ -3474,7 +3701,7 @@ TROUBLESHOOTING REPORTING BUGS - Report bugs at http://bugs.hledger.org (or on the #hledger IRC channel + Report bugs at http://bugs.hledger.org (or on the #hledger IRC channel or hledger mail list) @@ -3488,7 +3715,7 @@ COPYRIGHT SEE ALSO - hledger(1), hledger-ui(1), hledger-web(1), hledger-api(1), + hledger(1), hledger-ui(1), hledger-web(1), hledger-api(1), hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_time- dot(5), ledger(1)