;roi: more input sanity checks (fixes #2505)
This commit is contained in:
parent
2fb09f0d1e
commit
1c37dc44a7
@ -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
|
||||
@ -194,7 +205,7 @@ timeWeightedReturn _styles showCashFlow prettyTables investmentsQuery trans mixe
|
||||
|
||||
valueBefore = dbg3 ("value at the start of the interval, "++show begin) $ unMix valueBeforeAmt
|
||||
valueAfter = dbg3 ("value at the end of the interval, "++show end) $ unMix valueAfterAmt
|
||||
|
||||
|
||||
investmentPostings = concatMap (filter (matchesPosting investmentsQuery) . realPostings) trans
|
||||
|
||||
totalInvestmentPostingsTill date = sumPostings $ filter (matchesPosting (Date (DateSpan Nothing (Just $ Exact date)))) investmentPostings
|
||||
@ -207,7 +218,7 @@ timeWeightedReturn _styles showCashFlow prettyTables investmentsQuery trans mixe
|
||||
-- We are dividing the period [begin, end) into subperiods on each cashflow, and then compute
|
||||
-- the rate of return for each subperiod. For this we need to know the value of the investment
|
||||
-- at the beginning and end of each subperiod, adjusted for cashflow.
|
||||
--
|
||||
--
|
||||
-- Subperiods are going to be [valueBefore ... (c_0,v_0)][... (c_1, v_1)][... (c_2,v_2)] ... [... (c_n,v_n)][... valueAfter]
|
||||
-- , where v_i is the value of investment computed immediately after cashflow c_i
|
||||
--
|
||||
@ -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) ++
|
||||
"\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."
|
||||
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 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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user