;roi: more input sanity checks (fixes #2505)

This commit is contained in:
Dmitry Astapov 2025-12-19 23:18:33 +00:00 committed by Simon Michael
parent 2fb09f0d1e
commit 1c37dc44a7
2 changed files with 25 additions and 9 deletions

View File

@ -23,6 +23,7 @@ import Data.Function (on)
import Data.List
import Numeric.RootFinding
import Data.Decimal
import Data.Set qualified as S
import Data.Text qualified as T
import Data.Text.Lazy.IO qualified as TL
import Safe (headDef)
@ -83,10 +84,14 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
makeQuery flag = do
q <- either usageError (return . fst) . parseQuery today . T.pack $ stringopt flag rawopts
return . simplifyQuery $ And [queryFromFlags ropts{period_=PeriodAll}, q]
cantCompute msg = error' $ msg ++ " - will be unable to compute the rates of return"
investmentsQuery <- makeQuery "investment"
pnlQuery <- makeQuery "pnl"
when (pnlQuery == Any) $
cantCompute "Need some transactions classed as investment and not pnl, but the pnl query matches any transaction"
let
filteredj = filterJournalTransactions investmentsQuery j
trans = dbg3 "investments" $ jtxns filteredj
@ -96,7 +101,7 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
let (fullPeriodDateSpan, mspans) = reportSpan filteredj rspec
let err = error' "Undefined start or end of the period - will be unable to compute the rates of return"
let err = cantCompute "Undefined start or end of the period"
spans = maybe err (map (second (addDays 1)) . dayPartitionToList) mspans
fullPeriod = case fullPeriodDateSpan of
DateSpan (Just b) (Just e) -> (fromEFDay b, fromEFDay e)
@ -125,15 +130,21 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
, Not pnlQuery
, Date spn ] )
pnl =
pnl = dbg3 "pnl" $
cashFlowApplyCostValue $
calculateCashFlow wd trans (And [ Not investmentsQuery
, pnlQuery
, Date spn ] )
spanCommodities = dbg3 "spanCommodities" $
S.unions $ (maCommodities valueBefore) : (maCommodities valueAfter) : map (maCommodities.snd) (cashFlow ++ pnl)
thisSpan = dbg3 "processing span" $
OneSpan b e valueBefore valueAfter cashFlow pnl
when (S.size spanCommodities > 1) $
multiCommodityError $ "Period " ++ show (b,e) ++ " has multiple commodities: " ++ (T.unpack $ T.intercalate ", " $ map showCommoditySymbol (S.toList spanCommodities))
irr <- internalRateOfReturn styles showCashFlow prettyTables thisSpan
(periodTwr, annualizedTwr) <- timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixedAmountValue thisSpan
let cashFlowAmt = maNegate . maSum $ map snd cashFlow
@ -318,9 +329,11 @@ unMix :: MixedAmount -> Quantity
unMix a =
case (unifyMixedAmount a) of
Just a' -> aquantity a'
Nothing -> error' $ "Amounts could not be converted to a single commodity: " ++ show (map showAmount $ amounts a) ++
Nothing -> multiCommodityError $ "Amounts could not be converted to a single commodity: " ++ show (map showAmount $ amounts a)
multiCommodityError msg = error' $ msg ++
"\nConsider using --value to force all costs to be in a single commodity." ++
"\nFor example, \"--value=end,<commodity> --infer-market-prices\", where commodity is the one that was used for investment valuations."
"\nFor example, \"--value=end,<commodity> --infer-market-prices\", where commodity is the one that you want to value the investment in."
-- Show Decimal rounded to two decimal places, unless it has less places already. This ensures that "2" won't be shown as "2.00"
showDecimal :: Decimal -> String

View File

@ -212,7 +212,7 @@ $ hledger -f- roi --inv investment --pnl pnl -b 2017-06 -e 2018
2019/11/01 Example
Assets:Checking 1
Income:Salary -1
$ hledger -f- roi -p 2019-11
$ hledger -f- roi -p 2019-11 --pnl "^$"
+---++------------+------------++---------------+----------+-------------+-----++-------++------------+----------+
| || Begin | End || Value (begin) | Cashflow | Value (end) | PnL || IRR || TWR/period | TWR/year |
+===++============+============++===============+==========+=============+=====++=======++============+==========+
@ -236,7 +236,10 @@ $ hledger -f- roi -p 2019-11
Assets:Checking 101 A
Unrealized PnL
$ hledger -f- roi -p 2019-11 --inv Investment --pnl PnL
>2 /Error: Amounts could not be converted to a single commodity: \["10 B","-9 B @@ 100 A","100 C"\]/
>2
hledger: Error: Period (2019-11-01,2019-12-01) has multiple commodities: A, B, C
Consider using --value to force all costs to be in a single commodity.
For example, "--value=end,<commodity> --infer-market-prices", where commodity is the one that you want to value the investment in.
>= 1
# ** 10. Forcing valuation via --value