fix:cli:roi: report TWR per period and overall TWR for multi-period reports (fix #2068)

This commit is contained in:
Dmitry Astapov 2023-08-01 14:39:53 +01:00 committed by Simon Michael
parent f7dbfcf657
commit 588d0dfd26
2 changed files with 133 additions and 119 deletions

View File

@ -89,65 +89,78 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
putStrLn "No relevant transactions found. Check your investments query"
exitFailure
let spans = snd $ reportSpan filteredj rspec
let (fullPeriod, spans) = reportSpan filteredj rspec
let priceDirectiveDates = dbg3 "priceDirectiveDates" $ map pddate $ jpricedirectives j
tableBody <- forM spans $ \spn@(DateSpan (Just begin) (Just end)) -> do
-- Spans are [begin,end), and end is 1 day after the actual end date we are interested in
let
b = fromEFDay begin
e = fromEFDay end
cashFlowApplyCostValue = map (\(d,amt) -> (d,mixedAmountValue e d amt))
let processSpan (DateSpan Nothing _) = error "Undefined start of the period - will be unable to compute the rates of return"
processSpan (DateSpan _ Nothing) = error "Undefined end of the period - will be unable to compute the rates of return"
processSpan spn@(DateSpan (Just begin) (Just end)) = do
-- Spans are [begin,end), and end is 1 day after the actual end date we are interested in
let
b = fromEFDay begin
e = fromEFDay end
cashFlowApplyCostValue = map (\(d,amt) -> (d,mixedAmountValue e d amt))
valueBefore =
mixedAmountValue e b $
total trans (And [ investmentsQuery
, Date (DateSpan Nothing (Just begin))])
valueBefore =
mixedAmountValue e b $
total trans (And [ investmentsQuery
, Date (DateSpan Nothing (Just begin))])
valueAfter =
mixedAmountValue e e $
total trans (And [investmentsQuery
, Date (DateSpan Nothing (Just end))])
valueAfter =
mixedAmountValue e e $
total trans (And [investmentsQuery
, Date (DateSpan Nothing (Just end))])
priceDates = dbg3 "priceDates" $ nub $ filter (spanContainsDate spn) priceDirectiveDates
cashFlow =
((map (,nullmixedamt) priceDates)++) $
cashFlowApplyCostValue $
calculateCashFlow wd trans (And [ Not investmentsQuery
, Not pnlQuery
, Date spn ] )
priceDates = dbg3 "priceDates" $ nub $ filter (spanContainsDate spn) priceDirectiveDates
cashFlow =
((map (,nullmixedamt) priceDates)++) $
cashFlowApplyCostValue $
calculateCashFlow wd trans (And [ Not investmentsQuery
, Not pnlQuery
, Date spn ] )
pnl =
cashFlowApplyCostValue $
calculateCashFlow wd trans (And [ Not investmentsQuery
, pnlQuery
, Date spn ] )
pnl =
cashFlowApplyCostValue $
calculateCashFlow wd trans (And [ Not investmentsQuery
, pnlQuery
, Date spn ] )
thisSpan = dbg3 "processing span" $
OneSpan b e valueBefore valueAfter cashFlow pnl
thisSpan = dbg3 "processing span" $
OneSpan b e valueBefore valueAfter cashFlow pnl
irr <- internalRateOfReturn showCashFlow prettyTables thisSpan
(periodTwr, annualizedTwr) <- timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountValue thisSpan
let cashFlowAmt = maNegate . maSum $ map snd cashFlow
let smallIsZero x = if abs x < 0.01 then 0.0 else x
return [ showDate b
, showDate (addDays (-1) e)
, T.pack $ showMixedAmount valueBefore
, T.pack $ showMixedAmount cashFlowAmt
, T.pack $ showMixedAmount valueAfter
, T.pack $ showMixedAmount (valueAfter `maMinus` (valueBefore `maPlus` cashFlowAmt))
, T.pack $ printf "%0.2f%%" $ smallIsZero irr
, T.pack $ printf "%0.2f%%" $ smallIsZero periodTwr
, T.pack $ printf "%0.2f%%" $ smallIsZero annualizedTwr ]
irr <- internalRateOfReturn showCashFlow prettyTables thisSpan
twr <- timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountValue thisSpan
let cashFlowAmt = maNegate . maSum $ map snd cashFlow
let smallIsZero x = if abs x < 0.01 then 0.0 else x
return [ showDate b
, showDate (addDays (-1) e)
, T.pack $ showMixedAmount valueBefore
, T.pack $ showMixedAmount cashFlowAmt
, T.pack $ showMixedAmount valueAfter
, T.pack $ showMixedAmount (valueAfter `maMinus` (valueBefore `maPlus` cashFlowAmt))
, T.pack $ printf "%0.2f%%" $ smallIsZero irr
, T.pack $ printf "%0.2f%%" $ smallIsZero twr ]
periodRows <- forM spans processSpan
totalRow <- processSpan fullPeriod
let rowTitles = Tab.Group Tab.NoLine (map (Header . T.pack . show) (take (length periodRows) [1..]))
let isSingleSpan = length spans == 1
let table = Table
(Tab.Group Tab.NoLine (map (Header . T.pack . show) (take (length tableBody) [1..])))
(if isSingleSpan
then rowTitles
else Tab.Group Tab.SingleLine [ rowTitles, Tab.Group Tab.NoLine [ Header "Total" ]]
)
(Tab.Group Tab.DoubleLine
[ Tab.Group Tab.SingleLine [Header "Begin", Header "End"]
, Tab.Group Tab.SingleLine [Header "Value (begin)", Header "Cashflow", Header "Value (end)", Header "PnL"]
, Tab.Group Tab.SingleLine [Header "IRR", Header "TWR"]])
tableBody
, Tab.Group Tab.SingleLine [Header "IRR"]
, Tab.Group Tab.SingleLine [Header "TWR/period", Header "TWR/year"]])
(if isSingleSpan then periodRows else periodRows ++ [totalRow])
TL.putStrLn $ Tab.render prettyTables id id id table
@ -174,7 +187,7 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountV
where
zeroUnitsNeedsCashflowAtTheFront changes1 =
if initialUnits > 0 then changes1
else
else
let (leadingEmptyCashFlows, rest) = span isEmptyCashflow changes1
(leadingPnls, rest') = span (isLeft . snd) rest
(firstCashflow, rest'') = splitAt 1 rest'
@ -185,10 +198,10 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountV
Left _ -> False
datedPnls = map (second Left) $ aggregateByDate pnl
datedCashflows = map (second Right) $ aggregateByDate cashFlow
aggregateByDate datedAmounts =
aggregateByDate datedAmounts =
-- Aggregate all entries for a single day, assuming that intraday interest is negligible
sort
$ map (\date_cash -> let (dates, cash) = unzip date_cash in (head dates, maSum cash))
@ -223,7 +236,8 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountV
else (unMix valueAfter) / finalUnitBalance
-- Technically, totalTWR should be (100*(finalUnitPrice - initialUnitPrice) / initialUnitPrice), but initalUnitPrice is 100, so 100/100 == 1
totalTWR = roundTo 2 $ (finalUnitPrice - initialUnitPrice)
years = fromIntegral (diffDays end begin) / 365 :: Double
(startYear, _, _) = toGregorian begin
years = fromIntegral (diffDays end begin) / (if isLeapYear startYear then 366 else 365) :: Double
annualizedTWR = 100*((1+(realToFrac totalTWR/100))**(1/years)-1) :: Double
when showCashFlow $ do
@ -257,7 +271,7 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans mixedAmountV
printf "Final unit price: %s/%s units = %s\nTotal TWR: %s%%.\nPeriod: %.2f years.\nAnnualized TWR: %.2f%%\n\n"
(showMixedAmount valueAfter) (showDecimal finalUnitBalance) (showDecimal finalUnitPrice) (showDecimal totalTWR) years annualizedTWR
return annualizedTWR
return ((realToFrac totalTWR) :: Double, annualizedTWR)
internalRateOfReturn showCashFlow prettyTables (OneSpan begin end valueBefore valueAfter cashFlow _pnl) = do
let prefix = (begin, maNegate valueBefore)
@ -314,5 +328,3 @@ showDecimal :: Decimal -> String
showDecimal d = if d == rounded then show d else show rounded
where
rounded = roundTo 2 d

View File

@ -8,11 +8,11 @@
assets:cash -$100
investment
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
+---++------------+------------++---------------+----------+-------------+-----++-------+-------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=======+=======+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $200 | 0 || 0.00% | 0.00% |
+---++------------+------------++---------------+----------+-------------+-----++-------+-------+
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=======++============+==========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $200 | 0 || 0.00% || 0.00% | 0.00% |
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
>= 0
@ -26,11 +26,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
investment = $112
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++========+========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% | 12.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++========++============+==========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% || 12.00% | 12.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
>= 0
@ -89,11 +89,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++========+========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% | 12.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++========++============+==========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% || 12.00% | 12.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
>= 0
@ -111,11 +111,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
investment = $220
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++========+========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $220 | $20 || 12.72% | 10.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------+--------+
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++========++============+==========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $220 | $20 || 12.72% || 10.00% | 10.00% |
+---++------------+------------++---------------+----------+-------------+-----++--------++------------+----------+
>= 0
@ -133,11 +133,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
investment = $20
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
+---++------------+------------++---------------+----------+-------------+-------++---------+---------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=======++=========+=========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $20 | $-180 || -95.73% | -90.00% |
+---++------------+------------++---------------+----------+-------------+-------++---------+---------+
+---++------------+------------++---------------+----------+-------------+-------++---------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=======++=========++============+==========+
| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $20 | $-180 || -95.73% || -90.00% | -90.00% |
+---++------------+------------++---------------+----------+-------------+-------++---------++------------+----------+
>= 0
@ -163,14 +163,16 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y
investment = $155
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Q
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=========+=========+
| 1 || 2017-01-01 | 2017-03-31 || 0 | $100 | $100 | 0 || 0.00% | 0.00% |
| 2 || 2017-04-01 | 2017-06-30 || $100 | 0 | $110 | $10 || 46.56% | 46.56% |
| 3 || 2017-07-01 | 2017-09-30 || $110 | $100 | $210 | 0 || 0.00% | 0.00% |
| 4 || 2017-10-01 | 2017-12-31 || $210 | $-50 | $155 | $-5 || -11.83% | -11.82% |
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
+-------++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+=======++============+============++===============+==========+=============+=====++=========++============+==========+
| 1 || 2017-01-01 | 2017-03-31 || 0 | $100 | $100 | 0 || 0.00% || 0.00% | 0.00% |
| 2 || 2017-04-01 | 2017-06-30 || $100 | 0 | $110 | $10 || 46.56% || 10.00% | 46.56% |
| 3 || 2017-07-01 | 2017-09-30 || $110 | $100 | $210 | 0 || 0.00% || 0.00% | 0.00% |
| 4 || 2017-10-01 | 2017-12-31 || $210 | $-50 | $155 | $-5 || -11.83% || -3.12% | -11.82% |
+-------++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
| Total || 2017-01-01 | 2017-12-31 || 0 | $150 | $155 | $5 || 3.64% || 6.56% | 6.56% |
+-------++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
>= 0
@ -196,11 +198,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Q
investment = $155
pnl
$ hledger -f- roi --inv investment --pnl pnl -b 2017-06 -e 2018
+---++------------+------------++---------------+----------+-------------+-----++-------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=======+========+
| 1 || 2017-06-01 | 2017-12-31 || $100 | $50 | $155 | $5 || 5.24% | 11.45% |
+---++------------+------------++---------------+----------+-------------+-----++-------+--------+
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=======++============+==========+
| 1 || 2017-06-01 | 2017-12-31 || $100 | $50 | $155 | $5 || 5.24% || 6.56% | 11.45% |
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
>= 0
@ -210,11 +212,11 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017-06 -e 2018
Assets:Checking 1
Income:Salary -1
$ hledger -f- roi -p 2019-11
+---++------------+------------++---------------+----------+-------------+-----++-------+-------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=======+=======+
| 1 || 2019-11-01 | 2019-11-30 || 0 | 0 | 0 | 0 || 0.00% | 0.00% |
+---++------------+------------++---------------+----------+-------------+-----++-------+-------+
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=======++============+==========+
| 1 || 2019-11-01 | 2019-11-30 || 0 | 0 | 0 | 0 || 0.00% || 0.00% | 0.00% |
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
>= 0
@ -246,11 +248,11 @@ For example, "--cost --value=end,<commodity> --infer-market-prices", where commo
Assets:Checking 101 A
Unrealized PnL
$ hledger -f- roi -p 2019-11 --inv Investment --pnl PnL --cost --value=then,A --infer-market-prices
+---++------------+------------++---------------+----------+-------------+-----++----------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++==========+========+
| 1 || 2019-11-01 | 2019-11-30 || 0 | -1 A | 0 | 1 A || 3678.34% | 12.87% |
+---++------------+------------++---------------+----------+-------------+-----++----------+--------+
+---++------------+------------++---------------+----------+-------------+-----++----------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++==========++============+==========+
| 1 || 2019-11-01 | 2019-11-30 || 0 | -1 A | 0 | 1 A || 3678.34% || 1.00% | 12.87% |
+---++------------+------------++---------------+----------+-------------+-----++----------++------------+----------+
>= 0
@ -267,11 +269,11 @@ P 2021-01-01 $ 73.88
assets:investment =11000
income:investment
$ hledger -f - roi --inv assets:investment --pnl income:investment --value=then,'$'
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=========+=========+
| 1 || 2020-12-02 | 2021-01-02 || 0 | $131 | $149 | $18 || 321.99% | 321.81% |
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
+---++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=========++============+==========+
| 1 || 2020-12-02 | 2021-01-02 || 0 | $131 | $149 | $18 || 321.99% || 13.45% | 323.47% |
+---++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
>= 0
@ -288,11 +290,11 @@ P 2021-01-01 $ 73.88
assets:investment =11000
income:investment
$ hledger -f - roi --inv assets:investment --pnl income:investment --value=end,'$'
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=====++=========+=========+
| 1 || 2020-12-02 | 2021-01-02 || 0 | $135 | $149 | $14 || 196.58% | 196.58% |
+---++------------+------------++---------------+----------+-------------+-----++---------+---------+
+---++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=========++============+==========+
| 1 || 2020-12-02 | 2021-01-02 || 0 | $135 | $149 | $14 || 196.58% || 10.00% | 197.46% |
+---++------------+------------++---------------+----------+-------------+-----++---------++------------+----------+
>= 0
@ -326,11 +328,11 @@ $ hledger -f - roi --inv assets:investment --pnl income:investment --value=end,'
saving -100.00 €
checking
$ hledger -f - roi --inv saving --pnl dividend
+---++------------+------------++---------------+----------+-------------+---------++-------+-------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+=========++=======+=======+
| 1 || 2010-01-01 | 2012-03-29 || 0 | -16.00 € | 0 | 16.00 € || 3.52% | 3.49% |
+---++------------+------------++---------------+----------+-------------+---------++-------+-------+
+---++------------+------------++---------------+----------+-------------+---------++-------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=========++=======++============+==========+
| 1 || 2010-01-01 | 2012-03-29 || 0 | -16.00 € | 0 | 16.00 € || 3.52% || 8.00% | 3.49% |
+---++------------+------------++---------------+----------+-------------+---------++-------++------------+----------+
>= 0
@ -346,10 +348,10 @@ P 2022-07-31 A € 1
P 2022-08-02 A € 2
$ hledger -f - roi --inv stocks --pnl expenses --value=then,€ -Y
+---++------------+------------++---------------+----------+-------------+------++---------+--------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR |
+===++============+============++===============+==========+=============+======++=========+========+
| 1 || 2022-01-01 | 2022-12-31 || 0 | € 101 | € 200 | € 99 || 410.31% | 98.02% |
+---++------------+------------++---------------+----------+-------------+------++---------+--------+
+---++------------+------------++---------------+----------+-------------+------++---------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+======++=========++============+==========+
| 1 || 2022-01-01 | 2022-12-31 || 0 | € 101 | € 200 | € 99 || 410.31% || 98.02% | 98.02% |
+---++------------+------------++---------------+----------+-------------+------++---------++------------+----------+
>= 0