balance: add -V/--value to show as market value
Initial support of market value reporting and currency conversion, similar in spirit to Ledger's. The balance command now has a -V/--value flag that converts all the reported amounts using their "default market price". That is the latest market price (P directive, formerly called "historical prices") found in the journal for their commodity that is on or before the report end date. Unlike Ledger, hledger's -V only uses the market prices recorded with P directives, ignoring transaction prices recorded as part of posting amounts (which -B/--cost uses). Using -B and -V together is allowed.
This commit is contained in:
		
							parent
							
								
									040d00e8fb
								
							
						
					
					
						commit
						49be1f646e
					
				| @ -180,7 +180,7 @@ data HistoricalPrice = HistoricalPrice { | ||||
|       hdate :: Day, | ||||
|       hcommodity :: Commodity, | ||||
|       hamount :: Amount | ||||
|     } deriving (Eq,Typeable,Data) -- & Show (in Amount.hs) | ||||
|     } deriving (Eq,Ord,Typeable,Data) -- & Show (in Amount.hs) | ||||
| 
 | ||||
| type Year = Integer | ||||
| 
 | ||||
|  | ||||
| @ -88,6 +88,7 @@ data ReportOpts = ReportOpts { | ||||
|     ,drop_           :: Int | ||||
|     ,row_total_      :: Bool | ||||
|     ,no_total_       :: Bool | ||||
|     ,value_          :: Bool | ||||
|  } deriving (Show, Data, Typeable) | ||||
| 
 | ||||
| instance Default ReportOpts where def = defreportopts | ||||
| @ -121,6 +122,7 @@ defreportopts = ReportOpts | ||||
|     def | ||||
|     def | ||||
|     def | ||||
|     def | ||||
| 
 | ||||
| rawOptsToReportOpts :: RawOpts -> IO ReportOpts | ||||
| rawOptsToReportOpts rawopts = do | ||||
| @ -153,6 +155,7 @@ rawOptsToReportOpts rawopts = do | ||||
|     ,drop_        = intopt "drop" rawopts | ||||
|     ,row_total_   = boolopt "row-total" rawopts | ||||
|     ,no_total_    = boolopt "no-total" rawopts | ||||
|     ,value_       = boolopt "value" rawopts | ||||
|     } | ||||
| 
 | ||||
| accountlistmodeopt :: RawOpts -> AccountListMode | ||||
|  | ||||
| @ -242,6 +242,9 @@ module Hledger.Cli.Balance ( | ||||
|  ,tests_Hledger_Cli_Balance | ||||
| ) where | ||||
| 
 | ||||
| import Data.List (sort) | ||||
| import Data.Time.Calendar (Day) | ||||
| import Data.Maybe (fromMaybe) | ||||
| import System.Console.CmdArgs.Explicit as C | ||||
| import Text.CSV | ||||
| import Test.HUnit | ||||
| @ -270,6 +273,7 @@ balancemode = (defCommandMode $ ["balance"] ++ aliases) { -- also accept but don | ||||
|      ,flagNone ["average","A"] (\opts -> setboolopt "average" opts) "multicolumn mode: show a row average column" | ||||
|      ,flagNone ["row-total","T"] (\opts -> setboolopt "row-total" opts) "multicolumn mode: show a row total column" | ||||
|      ,flagNone ["no-total","N"] (\opts -> setboolopt "no-total" opts) "don't show the final total row" | ||||
|      ,flagNone ["value","V"] (setboolopt "value") "show amounts as their market value in their default valuation commodity" | ||||
|      ] | ||||
|      ++ outputflags | ||||
|     ,groupHidden = [] | ||||
| @ -288,25 +292,73 @@ balance opts@CliOpts{reportopts_=ropts} j = do | ||||
|       let format   = outputFormatFromOpts opts | ||||
|           interval = intervalFromOpts ropts | ||||
|           baltype  = balancetype_ ropts | ||||
|           valuedate = fromMaybe d $ queryEndDate False $ queryFromOpts d ropts | ||||
|       case interval of | ||||
|         NoInterval -> do | ||||
|           let report = balanceReport ropts (queryFromOpts d ropts) j | ||||
|               convert | value_ ropts = balanceReportValue j valuedate | ||||
|                       | otherwise    = id | ||||
|               render = case format of | ||||
|                 "csv" -> \ropts r -> (++ "\n") $ printCSV $ balanceReportAsCsv ropts r | ||||
|                 _     -> balanceReportAsText | ||||
|           writeOutput opts $ render ropts report | ||||
|           writeOutput opts $ render ropts $ convert report | ||||
|         _ -> do | ||||
|           let report = multiBalanceReport ropts (queryFromOpts d ropts) j | ||||
|               convert | value_ ropts = multiBalanceReportValue j valuedate | ||||
|                       | otherwise    = id | ||||
|               render = case format of | ||||
|                 "csv" -> \ropts r -> (++ "\n") $ printCSV $ multiBalanceReportAsCsv ropts r | ||||
|                 _     -> case baltype of | ||||
|                   PeriodBalance     -> periodBalanceReportAsText | ||||
|                   CumulativeBalance -> cumulativeBalanceReportAsText | ||||
|                   HistoricalBalance -> historicalBalanceReportAsText | ||||
|           writeOutput opts $ render ropts report | ||||
|           writeOutput opts $ render ropts $ convert report | ||||
| 
 | ||||
| -- single-column balance reports | ||||
| 
 | ||||
| -- | Convert all the amounts in a single-column balance report to | ||||
| -- their value on the given date in their default valuation | ||||
| -- commodities. | ||||
| balanceReportValue :: Journal -> Day -> BalanceReport -> BalanceReport | ||||
| balanceReportValue j d r = r' | ||||
|   where | ||||
|     (items,total) = r | ||||
|     r' = ([(n, mixedAmountValue j d a) |(n,a) <- items], mixedAmountValue j d total) | ||||
| 
 | ||||
| mixedAmountValue :: Journal -> Day -> MixedAmount -> MixedAmount | ||||
| mixedAmountValue j d (Mixed as) = Mixed $ map (amountValue j d) as | ||||
| 
 | ||||
| -- | Find the market value of this amount on the given date, in it's | ||||
| -- default valuation commodity, based on historical prices. If no | ||||
| -- default valuation commodity can be found, the amount is left | ||||
| -- unchanged. | ||||
| amountValue :: Journal -> Day -> Amount -> Amount | ||||
| amountValue j d a = | ||||
|   case commodityValue j d (acommodity a) of | ||||
|     Just v  -> v{aquantity=aquantity v * aquantity a | ||||
|                 ,aprice=aprice a | ||||
|                 } | ||||
|     Nothing -> a | ||||
| 
 | ||||
| -- | Find the market value, if known, of one unit of this commodity on | ||||
| -- the given date, in the commodity in which it has most recently been | ||||
| -- market-priced (ie the commodity mentioned in the most recent | ||||
| -- applicable historical price directive before this date). | ||||
| commodityValue :: Journal -> Day -> Commodity -> Maybe Amount | ||||
| commodityValue j d c | ||||
|     | null applicableprices = Nothing | ||||
|     | otherwise             = Just $ hamount $ last applicableprices | ||||
|   where | ||||
|     applicableprices = [p | p <- sort $ historical_prices j, hcommodity p == c, hdate p <= d] | ||||
| 
 | ||||
| -- | Find the best commodity to convert to when asked to show the | ||||
| -- market value of this commodity on the given date. That is, the one | ||||
| -- in which it has most recently been market-priced, ie the commodity | ||||
| -- mentioned in the most recent applicable historical price directive | ||||
| -- before this date. | ||||
| -- defaultValuationCommodity :: Journal -> Day -> Commodity -> Maybe Commodity | ||||
| -- defaultValuationCommodity j d c = hamount <$> commodityValue j d c | ||||
| 
 | ||||
| -- | Render a single-column balance report as CSV. | ||||
| balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV | ||||
| balanceReportAsCsv opts (items, total) = | ||||
| @ -393,6 +445,19 @@ formatField opts accountName depth total ljust min max field = case field of | ||||
| 
 | ||||
| -- multi-column balance reports | ||||
| 
 | ||||
| -- | Convert all the amounts in a multi-column balance report to their | ||||
| -- value on the given date in their default valuation commodities | ||||
| -- (which are determined as of that date, not the report interval dates). | ||||
| multiBalanceReportValue :: Journal -> Day -> MultiBalanceReport -> MultiBalanceReport | ||||
| multiBalanceReportValue j d r = r' | ||||
|   where | ||||
|     MultiBalanceReport (spans, rows, (coltotals, rowtotaltotal, rowavgtotal)) = r | ||||
|     r' = MultiBalanceReport | ||||
|          (spans, | ||||
|           [(n, map convert rowamts, convert rowtotal, convert rowavg) | (n, rowamts, rowtotal, rowavg) <- rows], | ||||
|           (map convert coltotals, convert rowtotaltotal, convert rowavgtotal)) | ||||
|     convert = mixedAmountValue j d | ||||
| 
 | ||||
| -- | Render a multi-column balance report as CSV. | ||||
| multiBalanceReportAsCsv :: ReportOpts -> MultiBalanceReport -> CSV | ||||
| multiBalanceReportAsCsv opts (MultiBalanceReport (colspans, items, (coltotals,tot,avg))) = | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user