From 07a9f119628c99284cf580b5d4da72f8ac1b8660 Mon Sep 17 00:00:00 2001 From: Dmitry Astapov Date: Tue, 12 Jan 2021 22:51:59 +0000 Subject: [PATCH] roi: use MixedAmount more and keep styles when reporting commodities --- hledger/Hledger/Cli/Commands/Roi.hs | 47 +++++++++++++++-------------- hledger/test/roi.test | 40 ++++++++++++------------ 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/hledger/Hledger/Cli/Commands/Roi.hs b/hledger/Hledger/Cli/Commands/Roi.hs index 4be6b9ad6..ba6b806da 100644 --- a/hledger/Hledger/Cli/Commands/Roi.hs +++ b/hledger/Hledger/Cli/Commands/Roi.hs @@ -49,10 +49,10 @@ roimode = hledgerCommandMode data OneSpan = OneSpan Day -- start date, inclusive Day -- end date, exclusive - Quantity -- value of investment at the beginning of day on spanBegin_ - Quantity -- value of investment at the end of day on spanEnd_ - [(Day,Quantity)] -- all deposits and withdrawals (but not changes of value) in the DateSpan [spanBegin_,spanEnd_) - [(Day,Quantity)] -- all PnL changes of the value of investment in the DateSpan [spanBegin_,spanEnd_) + MixedAmount -- value of investment at the beginning of day on spanBegin_ + MixedAmount -- value of investment at the end of day on spanEnd_ + [(Day,MixedAmount)] -- all deposits and withdrawals (but not changes of value) in the DateSpan [spanBegin_,spanEnd_) + [(Day,MixedAmount)] -- all PnL changes of the value of investment in the DateSpan [spanBegin_,spanEnd_) deriving (Show) @@ -131,10 +131,10 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{rsOpts=ReportOpts{..} let smallIsZero x = if abs x < 0.01 then 0.0 else x return [ showDate spanBegin , showDate (addDays (-1) spanEnd) - , T.pack $ showDecimal valueBefore - , T.pack $ showDecimal cashFlowAmt - , T.pack $ showDecimal valueAfter - , T.pack $ showDecimal (valueAfter - (valueBefore + cashFlowAmt)) + , T.pack $ showMixedAmount valueBefore + , T.pack $ showMixedAmount cashFlowAmt + , T.pack $ showMixedAmount valueAfter + , T.pack $ showMixedAmount (valueAfter - (valueBefore + cashFlowAmt)) , T.pack $ printf "%0.2f%%" $ smallIsZero irr , T.pack $ printf "%0.2f%%" $ smallIsZero twr ] @@ -148,8 +148,9 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{rsOpts=ReportOpts{..} TL.putStrLn $ Ascii.render prettyTables id id id table -timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spanBegin spanEnd valueBefore valueAfter cashFlow pnl) = do - let initialUnitPrice = 100 +timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spanBegin spanEnd valueBeforeAmt valueAfter cashFlow pnl) = do + let valueBefore = unMix valueBeforeAmt + let initialUnitPrice = 100 :: Decimal let initialUnits = valueBefore / initialUnitPrice let changes = -- If cash flow and PnL changes happen on the same day, this @@ -170,16 +171,16 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spa tail $ scanl (\(_, _, unitPrice, unitBalance) (date, amt) -> - let valueOnDate = total trans (And [investmentsQuery, Date (DateSpan Nothing (Just date))]) + let valueOnDate = unMix $ total trans (And [investmentsQuery, Date (DateSpan Nothing (Just date))]) in case amt of Right amt -> -- we are buying or selling - let unitsBoughtOrSold = amt / unitPrice + let unitsBoughtOrSold = unMix amt / unitPrice in (valueOnDate, unitsBoughtOrSold, unitPrice, unitBalance + unitsBoughtOrSold) Left pnl -> -- PnL change - let valueAfterDate = valueOnDate + pnl + let valueAfterDate = valueOnDate + unMix pnl unitPrice' = valueAfterDate/unitBalance in (valueOnDate, 0, unitPrice', unitBalance)) (0, 0, initialUnitPrice, initialUnits) @@ -187,7 +188,7 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spa let finalUnitBalance = if null units then initialUnits else let (_,_,_,u) = last units in u finalUnitPrice = if finalUnitBalance == 0 then initialUnitPrice - else valueAfter / finalUnitBalance + 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 spanEnd spanBegin) / 365 :: Double @@ -201,7 +202,7 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spa (valuesOnDate',unitsBoughtOrSold', unitPrices', unitBalances') = unzip4 units add x lst = if valueBefore/=0 then x:lst else lst dates = add spanBegin dates' - cashflows = add valueBefore cashflows' + cashflows = add valueBeforeAmt cashflows' pnls = add 0 pnls' unitsBoughtOrSold = add initialUnits unitsBoughtOrSold' unitPrices = add initialUnitPrice unitPrices' @@ -218,13 +219,13 @@ timeWeightedReturn showCashFlow prettyTables investmentsQuery trans (OneSpan spa | value <- map showDecimal valuesOnDate | oldBalance <- map showDecimal (0:unitBalances) | balance <- map showDecimal unitBalances - | pnl <- map showDecimal pnls - | cashflow <- map showDecimal cashflows + | pnl <- map showMixedAmount pnls + | cashflow <- map showMixedAmount cashflows | prc <- map showDecimal unitPrices | udelta <- map showDecimal unitsBoughtOrSold ]) printf "Final unit price: %s/%s units = %s\nTotal TWR: %s%%.\nPeriod: %.2f years.\nAnnualized TWR: %.2f%%\n\n" - (showDecimal valueAfter) (showDecimal finalUnitBalance) (showDecimal finalUnitPrice) (showDecimal totalTWR) years annualizedTWR + (showMixedAmount valueAfter) (showDecimal finalUnitBalance) (showDecimal finalUnitPrice) (showDecimal totalTWR) years annualizedTWR return annualizedTWR @@ -242,7 +243,7 @@ internalRateOfReturn showCashFlow prettyTables (OneSpan spanBegin spanEnd valueB (Table (Tbl.Group NoLine (map (Header . showDate) dates)) (Tbl.Group SingleLine [Header "Amount"]) - (map ((:[]) . T.pack . showDecimal) amounts)) + (map ((:[]) . T.pack . showMixedAmount) amounts)) -- 0% is always a solution, so require at least something here case totalCF of @@ -256,11 +257,11 @@ internalRateOfReturn showCashFlow prettyTables (OneSpan spanBegin spanEnd valueB SearchFailed -> error' $ "Error (SearchFailed): Failed to find solution for Internal Rate of Return (IRR).\n" ++ " Either search does not converge to a solution, or converges too slowly." -type CashFlow = [(Day, Quantity)] +type CashFlow = [(Day, MixedAmount)] interestSum :: Day -> CashFlow -> Double -> Double interestSum referenceDay cf rate = sum $ map go cf - where go (t,m) = fromRational (toRational m) * (rate ** (fromIntegral (referenceDay `diffDays` t) / 365)) + where go (t,m) = fromRational (toRational (unMix m)) * (rate ** (fromIntegral (referenceDay `diffDays` t) / 365)) calculateCashFlow :: [Transaction] -> Query -> CashFlow @@ -268,8 +269,8 @@ calculateCashFlow trans query = filter ((/=0).snd) $ map go trans where go t = (transactionDate2 t, total [t] query) -total :: [Transaction] -> Query -> Quantity -total trans query = unMix $ sumPostings $ filter (matchesPosting query) $ concatMap realPostings trans +total :: [Transaction] -> Query -> MixedAmount +total trans query = sumPostings $ filter (matchesPosting query) $ concatMap realPostings trans unMix :: MixedAmount -> Quantity unMix a = diff --git a/hledger/test/roi.test b/hledger/test/roi.test index 2371279b0..3f7f56c47 100644 --- a/hledger/test/roi.test +++ b/hledger/test/roi.test @@ -12,7 +12,7 @@ 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% | +| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $200 | 0 || 0.00% | 0.00% | +---++------------+------------++---------------+----------+-------------+-----++-------+-------+ >>>=0 @@ -31,7 +31,7 @@ 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% | +| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% | 12.00% | +---++------------+------------++---------------+----------+-------------+-----++--------+--------+ >>>=0 @@ -95,7 +95,7 @@ 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% | +| 1 || 2017-01-01 | 2017-12-31 || 0 | $100 | $112 | $12 || 12.00% | 12.00% | +---++------------+------------++---------------+----------+-------------+-----++--------+--------+ >>>=0 @@ -118,7 +118,7 @@ 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% | +| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $220 | $20 || 12.72% | 10.00% | +---++------------+------------++---------------+----------+-------------+-----++--------+--------+ >>>=0 @@ -138,11 +138,11 @@ hledger -f- roi --inv investment --pnl pnl -b 2017 -e 2018 -Y investment = $20 pnl >>> -+---++------------+------------++---------------+----------+-------------+------++---------+---------+ -| || 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 | ++===++============+============++===============+==========+=============+=======++=========+=========+ +| 1 || 2017-01-01 | 2017-12-31 || 0 | $200 | $20 | $-180 || -95.73% | -90.00% | ++---++------------+------------++---------------+----------+-------------+-------++---------+---------+ >>>=0 @@ -172,10 +172,10 @@ 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% | +| 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% | +---++------------+------------++---------------+----------+-------------+-----++---------+---------+ >>>=0 @@ -206,7 +206,7 @@ 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% | +| 1 || 2017-06-01 | 2017-12-31 || $100 | $50 | $155 | $5 || 5.24% | 11.45% | +---++------------+------------++---------------+----------+-------------+-----++-------+--------+ >>>=0 @@ -258,7 +258,7 @@ hledger -f- roi -p 2019-11 --inv Investment --pnl PnL --value cost,A --infer-val +---++------------+------------++---------------+----------+-------------+-----++----------+-------+ | || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | +===++============+============++===============+==========+=============+=====++==========+=======+ -| 1 || 2019-11-01 | 2019-11-30 || 0 | -1 | 0 | 1 || 3678.34% | 0.00% | +| 1 || 2019-11-01 | 2019-11-30 || 0 | -1 A | 0 | 1 A || 3678.34% | 0.00% | +---++------------+------------++---------------+----------+-------------+-----++----------+-------+ >>>=0 @@ -277,10 +277,10 @@ P 2021-01-01 $ 73.88 assets:investment =11000 income:investment >>> -+---++------------+------------++---------------+----------+-------------+-------++---------+---------+ -| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR | TWR | -+===++============+============++===============+==========+=============+=======++=========+=========+ -| 1 || 2020-12-02 | 2021-01-02 || 0 | 135.35 | 148.89 | 13.54 || 196.58% | 196.58% | -+---++------------+------------++---------------+----------+-------------+-------++---------+---------+ ++---++------------+------------++---------------+----------+-------------+-----++---------+---------+ +| || 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% | ++---++------------+------------++---------------+----------+-------------+-----++---------+---------+ >>>=0