lib: also infer market prices from transactions, like Ledger (#1239) (WIP)
This commit is contained in:
		
							parent
							
								
									cbacef21eb
								
							
						
					
					
						commit
						e1ddfc3a1b
					
				| @ -21,6 +21,7 @@ module Hledger.Data.Journal ( | |||||||
|   addPeriodicTransaction, |   addPeriodicTransaction, | ||||||
|   addTransaction, |   addTransaction, | ||||||
|   journalBalanceTransactions, |   journalBalanceTransactions, | ||||||
|  |   journalInferMarketPricesFromTransactions, | ||||||
|   journalApplyCommodityStyles, |   journalApplyCommodityStyles, | ||||||
|   commodityStylesFromAmounts, |   commodityStylesFromAmounts, | ||||||
|   journalCommodityStyles, |   journalCommodityStyles, | ||||||
| @ -185,6 +186,7 @@ instance Semigroup Journal where | |||||||
|     ,jcommodities               = jcommodities               j1 <> jcommodities               j2 |     ,jcommodities               = jcommodities               j1 <> jcommodities               j2 | ||||||
|     ,jinferredcommodities       = jinferredcommodities       j1 <> jinferredcommodities       j2 |     ,jinferredcommodities       = jinferredcommodities       j1 <> jinferredcommodities       j2 | ||||||
|     ,jpricedirectives              = jpricedirectives              j1 <> jpricedirectives              j2 |     ,jpricedirectives              = jpricedirectives              j1 <> jpricedirectives              j2 | ||||||
|  |     ,jtransactionimpliedmarketprices = jtransactionimpliedmarketprices j1 <> jtransactionimpliedmarketprices j2 | ||||||
|     ,jtxnmodifiers              = jtxnmodifiers              j1 <> jtxnmodifiers              j2 |     ,jtxnmodifiers              = jtxnmodifiers              j1 <> jtxnmodifiers              j2 | ||||||
|     ,jperiodictxns              = jperiodictxns              j1 <> jperiodictxns              j2 |     ,jperiodictxns              = jperiodictxns              j1 <> jperiodictxns              j2 | ||||||
|     ,jtxns                      = jtxns                      j1 <> jtxns                      j2 |     ,jtxns                      = jtxns                      j1 <> jtxns                      j2 | ||||||
| @ -210,6 +212,7 @@ nulljournal = Journal { | |||||||
|   ,jcommodities               = M.empty |   ,jcommodities               = M.empty | ||||||
|   ,jinferredcommodities       = M.empty |   ,jinferredcommodities       = M.empty | ||||||
|   ,jpricedirectives              = [] |   ,jpricedirectives              = [] | ||||||
|  |   ,jtransactionimpliedmarketprices = [] | ||||||
|   ,jtxnmodifiers              = [] |   ,jtxnmodifiers              = [] | ||||||
|   ,jperiodictxns              = [] |   ,jperiodictxns              = [] | ||||||
|   ,jtxns                      = [] |   ,jtxns                      = [] | ||||||
| @ -1036,6 +1039,32 @@ canonicalStyleFrom ss@(s:_) = | |||||||
| --   case ps of (PriceDirective{pdamount=a}:_) -> Just a | --   case ps of (PriceDirective{pdamount=a}:_) -> Just a | ||||||
| --              _ -> Nothing | --              _ -> Nothing | ||||||
| 
 | 
 | ||||||
|  | -- | Infer transaction-implied market prices from commodity-exchanging | ||||||
|  | -- transactions, if any. It's best to call this after transactions have | ||||||
|  | -- been balanced and posting amounts have appropriate prices attached. | ||||||
|  | journalInferMarketPricesFromTransactions :: Journal -> Journal | ||||||
|  | journalInferMarketPricesFromTransactions j = | ||||||
|  |   j{jtransactionimpliedmarketprices = | ||||||
|  |        dbg4 "jtransactionimpliedmarketprices" $ | ||||||
|  |        mapMaybe postingImpliedMarketPrice $ journalPostings j | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | -- | Make a market price equivalent to this posting's amount's unit | ||||||
|  | -- price, if any. If the posting amount is multicommodity, only the | ||||||
|  | -- first commodity amount is considered. | ||||||
|  | postingImpliedMarketPrice :: Posting -> Maybe MarketPrice | ||||||
|  | postingImpliedMarketPrice p@Posting{pamount} = | ||||||
|  |   -- convert any total prices to unit prices | ||||||
|  |   case mixedAmountTotalPriceToUnitPrice pamount of | ||||||
|  |     Mixed ( Amount{acommodity=fromcomm, aprice = Just (UnitPrice Amount{acommodity=tocomm, aquantity=rate})} : _) -> | ||||||
|  |       Just MarketPrice { | ||||||
|  |          mpdate = postingDate p | ||||||
|  |         ,mpfrom = fromcomm | ||||||
|  |         ,mpto   = tocomm | ||||||
|  |         ,mprate = rate | ||||||
|  |         } | ||||||
|  |     _ -> Nothing | ||||||
|  | 
 | ||||||
| -- | Convert all this journal's amounts to cost using the transaction prices, if any. | -- | Convert all this journal's amounts to cost using the transaction prices, if any. | ||||||
| -- The journal's commodity styles are applied to the resulting amounts. | -- The journal's commodity styles are applied to the resulting amounts. | ||||||
| journalToCost :: Journal -> Journal | journalToCost :: Journal -> Journal | ||||||
|  | |||||||
| @ -471,8 +471,8 @@ data Journal = Journal { | |||||||
|   ,jdeclaredaccounttypes  :: M.Map AccountType [AccountName]        -- ^ Accounts whose type has been declared in account directives (usually 5 top-level accounts) |   ,jdeclaredaccounttypes  :: M.Map AccountType [AccountName]        -- ^ Accounts whose type has been declared in account directives (usually 5 top-level accounts) | ||||||
|   ,jcommodities           :: M.Map CommoditySymbol Commodity        -- ^ commodities and formats declared by commodity directives |   ,jcommodities           :: M.Map CommoditySymbol Commodity        -- ^ commodities and formats declared by commodity directives | ||||||
|   ,jinferredcommodities   :: M.Map CommoditySymbol AmountStyle      -- ^ commodities and formats inferred from journal amounts  TODO misnamed - jusedstyles |   ,jinferredcommodities   :: M.Map CommoditySymbol AmountStyle      -- ^ commodities and formats inferred from journal amounts  TODO misnamed - jusedstyles | ||||||
|   ,jpricedirectives       :: [PriceDirective]                       -- ^ All market price declarations (P directives), in parse order (after journal finalisation). |   ,jpricedirectives       :: [PriceDirective]                       -- ^ Declarations of market prices by P directives, in parse order (after journal finalisation) | ||||||
|                                                                     --   These will be converted to a Prices db for looking up prices by date. |   ,jtransactionimpliedmarketprices :: [MarketPrice]                 -- ^ Market prices implied by transactions, in parse order (after journal finalisation) | ||||||
|   ,jtxnmodifiers          :: [TransactionModifier] |   ,jtxnmodifiers          :: [TransactionModifier] | ||||||
|   ,jperiodictxns          :: [PeriodicTransaction] |   ,jperiodictxns          :: [PeriodicTransaction] | ||||||
|   ,jtxns                  :: [Transaction] |   ,jtxns                  :: [Transaction] | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ import Control.Applicative ((<|>)) | |||||||
| import Control.DeepSeq (NFData) | import Control.DeepSeq (NFData) | ||||||
| import Data.Data | import Data.Data | ||||||
| import Data.Decimal (roundTo) | import Data.Decimal (roundTo) | ||||||
| import Data.Function (on) | import Data.Function ((&), on) | ||||||
| import Data.Graph.Inductive  (Gr, Node, NodeMap, mkMapGraph, mkNode, lab, out, sp) | import Data.Graph.Inductive  (Gr, Node, NodeMap, mkMapGraph, mkNode, lab, out, sp) | ||||||
| import Data.List | import Data.List | ||||||
| import Data.List.Extra (nubSortBy) | import Data.List.Extra (nubSortBy) | ||||||
| @ -58,17 +58,22 @@ data PriceGraph = PriceGraph { | |||||||
|    prGraph   :: Gr CommoditySymbol Quantity |    prGraph   :: Gr CommoditySymbol Quantity | ||||||
|     -- ^ A directed graph of exchange rates between commodity pairs. |     -- ^ A directed graph of exchange rates between commodity pairs. | ||||||
|     -- Node labels are commodities and edge labels are exchange rates, |     -- Node labels are commodities and edge labels are exchange rates, | ||||||
|     -- either explicitly declared (preferred) or inferred by reversing a declared rate. |     -- which were either: | ||||||
|  |     -- declared by P directives, | ||||||
|  |     -- implied by transaction prices, | ||||||
|  |     -- inferred by reversing a declared rate, | ||||||
|  |     -- or inferred by reversing a transaction-implied rate. | ||||||
|     -- There will be at most one edge between each directed pair of commodities, |     -- There will be at most one edge between each directed pair of commodities, | ||||||
|     -- eg there can be one USD->EUR and one EUR->USD. |     -- eg there can be one USD->EUR and one EUR->USD. | ||||||
|   ,prNodemap :: NodeMap CommoditySymbol |   ,prNodemap :: NodeMap CommoditySymbol | ||||||
|     -- ^ Mapping of graph node ids to commodity symbols. |     -- ^ Mapping of graph node ids to commodity symbols. | ||||||
|   ,prDeclaredPairs :: [(Node,Node)] |   ,prDefaultValuationCommodities :: M.Map CommoditySymbol CommoditySymbol | ||||||
|     -- ^ Which of the edges in this graph are declared rates, |     -- ^ The default valuation commodity for each source commodity. | ||||||
|     --   rather than inferred reverse rates. |     --   These are used when a valuation commodity is not specified | ||||||
|     --   A bit ugly. We could encode this in the edges, |     --   (-V). They are the destination commodity of the latest | ||||||
|     --   but those have to be Real for shortest path finding, |     --   (declared or transaction-implied, but not reverse) each | ||||||
|     --   so we'd have to transform them all first. |     --   source commodity's latest market price (on the date of this | ||||||
|  |     --   graph). | ||||||
|   } |   } | ||||||
|   deriving (Show,Generic) |   deriving (Show,Generic) | ||||||
| 
 | 
 | ||||||
| @ -184,17 +189,18 @@ amountValueAtDate priceoracle styles mto d a = | |||||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ||||||
| -- Market price lookup | -- Market price lookup | ||||||
| 
 | 
 | ||||||
| -- From a journal's market price directives, generate a memoising function | -- From a journal's directive-declared and transaction-implied market | ||||||
| -- that efficiently looks up exchange rates between commodities on any date. | -- prices, generate a memoising function that efficiently looks up | ||||||
| -- For best results, you should generate this only once per journal, reusing it | -- exchange rates between commodities on any date. For best performance, | ||||||
| -- across reports if there are more than one (as in compoundBalanceCommand). | -- you should generate this only once per journal, reusing it across | ||||||
|  | -- reports if there are more than one (as in compoundBalanceCommand). | ||||||
| journalPriceOracle :: Journal -> PriceOracle | journalPriceOracle :: Journal -> PriceOracle | ||||||
| journalPriceOracle Journal{jpricedirectives} = | journalPriceOracle Journal{jpricedirectives, jtransactionimpliedmarketprices} = | ||||||
|   -- traceStack "journalPriceOracle" $ |   -- traceStack "journalPriceOracle" $ | ||||||
|   let |   let | ||||||
|     pricesatdate = |     pricesatdate = | ||||||
|       memo $ |       memo $ | ||||||
|       pricesAtDate jpricedirectives |       pricesAtDate jpricedirectives jtransactionimpliedmarketprices | ||||||
|   in |   in | ||||||
|     memo $ |     memo $ | ||||||
|     uncurry3 $ |     uncurry3 $ | ||||||
| @ -205,21 +211,32 @@ journalPriceOracle Journal{jpricedirectives} = | |||||||
| -- a different specified valuation commodity, or a default valuation | -- a different specified valuation commodity, or a default valuation | ||||||
| -- commodity. | -- commodity. | ||||||
| -- | -- | ||||||
| -- When the valuation commodity is specified, this looks for, in order: | -- When the valuation commodity is specified, this looks for an | ||||||
|  | -- exchange rate (market price) calculated in any of the following | ||||||
|  | -- ways, in order of preference: | ||||||
| -- | -- | ||||||
| -- - a price declaration giving the exchange rate from source | -- 1. a declared market price (DMP) - a P directive giving the | ||||||
| --   commodity to valuation commodity ("declared price"). | --    exchange rate from source commodity to valuation commodity | ||||||
| -- | -- | ||||||
| -- - a price declaration from valuation to source commodity, which | -- 2. a transaction-implied market price (TMP) - a market price | ||||||
| --   gets inverted ("reverse price"). | --    equivalent to the transaction price used in the latest | ||||||
|  | --    transaction from source commodity to valuation commodity | ||||||
|  | --    (on or before the valuation date) | ||||||
| -- | -- | ||||||
| -- - the shortest chain of prices (declared or reverse) leading from | -- 3. a reverse declared market price (RDMP) - calculated by inverting | ||||||
| --   source commodity to valuation commodity, which gets collapsed | --    a DMP | ||||||
| --   into a single synthetic exchange rate ("indirect price"). | -- | ||||||
|  | -- 4. a reverse transaction-implied market price (RTMP) - calculated | ||||||
|  | --    by inverting a TMP | ||||||
|  | -- | ||||||
|  | -- 5. an indirect market price (IMP) - calculated by combining the | ||||||
|  | --    shortest chain of market prices (any of the above types) leading | ||||||
|  | --    from source commodity to valuation commodity. | ||||||
| -- | -- | ||||||
| -- When the valuation commodity is not specified, this looks for the | -- When the valuation commodity is not specified, this looks for the | ||||||
| -- latest applicable declared price, and converts to the commodity | -- latest applicable declared or transaction-implied price, and | ||||||
| -- mentioned in that price (the default valuation commodity). | -- converts to the commodity mentioned in that price (the default | ||||||
|  | -- valuation commodity). | ||||||
| -- | -- | ||||||
| -- Note this default valuation commodity can vary across successive | -- Note this default valuation commodity can vary across successive | ||||||
| -- calls for different dates, since it depends on the price | -- calls for different dates, since it depends on the price | ||||||
| @ -237,17 +254,12 @@ priceLookup pricesatdate d from mto = | |||||||
|   let |   let | ||||||
|     -- build a graph of the commodity exchange rates in effect on this day |     -- build a graph of the commodity exchange rates in effect on this day | ||||||
|     -- XXX should hide these fgl details better |     -- XXX should hide these fgl details better | ||||||
|     PriceGraph{prGraph=g, prNodemap=m, prDeclaredPairs=dps} = pricesatdate d |     PriceGraph{prGraph=g, prNodemap=m, prDefaultValuationCommodities=defaultdests} = pricesatdate d | ||||||
|     fromnode = node m from |     fromnode = node m from | ||||||
|     mto' = mto <|> mdefaultto |     mto' = mto <|> mdefaultto | ||||||
|       where |       where | ||||||
|         -- If to is unspecified, try to pick a default valuation commodity from declared prices (only). |         mdefaultto = dbg4 ("default valuation commodity for "++T.unpack from) $ | ||||||
|         -- XXX how to choose ? Take lowest sorted ? |                      M.lookup from defaultdests | ||||||
|         -- Take first, hoping current order is useful ?       <- |  | ||||||
|         -- Keep parse order in label and take latest parsed ? |  | ||||||
|         mdefaultto = |  | ||||||
|           dbg4 ("default valuation commodity for "++T.unpack from) $ |  | ||||||
|           headMay [t | (f,t,_) <- out g fromnode, (f,t) `elem` dps] >>= lab g |  | ||||||
|   in |   in | ||||||
|     case mto' of |     case mto' of | ||||||
|       Nothing            -> Nothing |       Nothing            -> Nothing | ||||||
| @ -283,7 +295,7 @@ tests_priceLookup = | |||||||
|       ,p "2000/01/01" "E"  2 "D" |       ,p "2000/01/01" "E"  2 "D" | ||||||
|       ,p "2001/01/01" "A" 11 "B" |       ,p "2001/01/01" "A" 11 "B" | ||||||
|       ] |       ] | ||||||
|     pricesatdate = pricesAtDate ps1 |     pricesatdate = pricesAtDate ps1 [] | ||||||
|   in test "priceLookup" $ do |   in test "priceLookup" $ do | ||||||
|     priceLookup pricesatdate (d "1999/01/01") "A" Nothing    @?= Nothing |     priceLookup pricesatdate (d "1999/01/01") "A" Nothing    @?= Nothing | ||||||
|     priceLookup pricesatdate (d "2000/01/01") "A" Nothing    @?= Just ("B",10) |     priceLookup pricesatdate (d "2000/01/01") "A" Nothing    @?= Just ("B",10) | ||||||
| @ -293,20 +305,21 @@ tests_priceLookup = | |||||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ||||||
| -- Building the price graph (network of commodity conversions) on a given day. | -- Building the price graph (network of commodity conversions) on a given day. | ||||||
| 
 | 
 | ||||||
| -- | Convert a list of market price directives in parse order to a | -- | Convert a list of market price directives in parse order, and a | ||||||
| -- graph of all prices in effect on a given day, allowing efficient | -- list of transaction-implied market prices in parse order, to a | ||||||
| -- lookup of exchange rates between commodity pairs. | -- graph of the effective exchange rates between commodity pairs on | ||||||
| pricesAtDate :: [PriceDirective] -> Day -> PriceGraph | -- the given day. | ||||||
| pricesAtDate pricedirectives d = | pricesAtDate :: [PriceDirective] -> [MarketPrice] -> Day -> PriceGraph | ||||||
|  | pricesAtDate pricedirectives transactionimpliedmarketprices d = | ||||||
|   -- trace ("pricesAtDate ("++show d++")") $ |   -- trace ("pricesAtDate ("++show d++")") $ | ||||||
|   PriceGraph{prGraph=g, prNodemap=m, prDeclaredPairs=dps} |   PriceGraph{prGraph=g, prNodemap=m, prDefaultValuationCommodities=defaultdests} | ||||||
|   where |   where | ||||||
|     declaredprices = latestPriceForEachPairOn pricedirectives d |     declaredandimpliedprices = latestPriceForEachPairOn pricedirectives transactionimpliedmarketprices d | ||||||
| 
 | 
 | ||||||
|     -- infer additional reverse prices where not already declared |     -- infer any additional reverse prices not already declared or implied | ||||||
|     reverseprices = |     reverseprices = | ||||||
|       dbg5 "reverseprices" $ |       dbg5 "reverseprices" $ | ||||||
|       map marketPriceReverse declaredprices \\ declaredprices |       map marketPriceReverse declaredandimpliedprices \\ declaredandimpliedprices | ||||||
| 
 | 
 | ||||||
|     -- build the graph and associated node map |     -- build the graph and associated node map | ||||||
|     (g, m) = |     (g, m) = | ||||||
| @ -315,23 +328,37 @@ pricesAtDate pricedirectives d = | |||||||
|       (dbg5 "g edges"      $ [(mpfrom, mpto, mprate) | MarketPrice{..} <- prices]) |       (dbg5 "g edges"      $ [(mpfrom, mpto, mprate) | MarketPrice{..} <- prices]) | ||||||
|       :: (Gr CommoditySymbol Quantity, NodeMap CommoditySymbol) |       :: (Gr CommoditySymbol Quantity, NodeMap CommoditySymbol) | ||||||
|       where |       where | ||||||
|         prices   = declaredprices ++ reverseprices |         prices   = declaredandimpliedprices ++ reverseprices | ||||||
|         allcomms = map mpfrom prices |         allcomms = map mpfrom prices | ||||||
| 
 | 
 | ||||||
|     -- remember which edges correspond to declared prices |     -- save the forward prices' destinations as the default valuation | ||||||
|     dps = [(node m mpfrom, node m mpto) | MarketPrice{..} <- declaredprices ] |     -- commodity for those source commodities | ||||||
|  |     defaultdests = M.fromList [(mpfrom,mpto) | MarketPrice{..} <- declaredandimpliedprices] | ||||||
| 
 | 
 | ||||||
| -- From a list of price directives in parse order, get the latest | -- From a list of price directives in parse order, and a list of | ||||||
| -- price declared on or before date d for each commodity pair. | -- transaction-implied market prices in parse order, get the effective | ||||||
| latestPriceForEachPairOn :: [PriceDirective] -> Day -> [MarketPrice] | -- price on the given date for each commodity pair. That is, the | ||||||
| latestPriceForEachPairOn pricedirectives d = | -- latest declared or transaction-implied price dated on or before | ||||||
|  | -- that day, with declared prices taking precedence. | ||||||
|  | latestPriceForEachPairOn :: [PriceDirective] -> [MarketPrice] -> Day -> [MarketPrice] | ||||||
|  | latestPriceForEachPairOn pricedirectives transactionimpliedmarketprices d = | ||||||
|   dbg5 "latestPriceForEachPairOn" $ |   dbg5 "latestPriceForEachPairOn" $ | ||||||
|   nubSortBy (compare `on` (\(MarketPrice{..})->(mpfrom,mpto))) $  -- keep only the first (ie newest and latest parsed) price for each pair |   let | ||||||
|   map snd $  -- discard the parse order label |     -- consider only declarations/transactions before the valuation date | ||||||
|   sortBy (flip compare `on` (\(parseorder,mp)->(mpdate mp,parseorder))) $  -- sort with newest dates and latest parse order first |     declaredprices = map priceDirectiveToMarketPrice $ filter ((<=d).pddate) pricedirectives | ||||||
|   zip [1..] $  -- label with parse order |     transactionimpliedmarketprices' = filter ((<=d).mpdate) transactionimpliedmarketprices | ||||||
|   map priceDirectiveToMarketPrice $ |     -- label the items with their precedence and then their parse order | ||||||
|   filter ((<=d).pddate) pricedirectives  -- consider only price declarations up to the valuation date |     declaredprices'                  = [(1, i, p) | (i,p) <- zip [1..] declaredprices] | ||||||
|  |     transactionimpliedmarketprices'' = [(0, i, p) | (i,p) <- zip [1..] transactionimpliedmarketprices'] | ||||||
|  |   in | ||||||
|  |     -- combine | ||||||
|  |     declaredprices' ++ transactionimpliedmarketprices'' | ||||||
|  |     -- sort by newest date then highest precedence then latest parse order | ||||||
|  |     & sortBy (flip compare `on` (\(precedence,parseorder,mp)->(mpdate mp,precedence,parseorder))) | ||||||
|  |     -- discard the sorting labels | ||||||
|  |     & map third3 | ||||||
|  |     -- keep only the first (ie the newest, highest precedence and latest parsed) price for each pair | ||||||
|  |     & nubSortBy (compare `on` (\(MarketPrice{..})->(mpfrom,mpto))) | ||||||
| 
 | 
 | ||||||
| priceDirectiveToMarketPrice :: PriceDirective -> MarketPrice | priceDirectiveToMarketPrice :: PriceDirective -> MarketPrice | ||||||
| priceDirectiveToMarketPrice PriceDirective{..} = | priceDirectiveToMarketPrice PriceDirective{..} = | ||||||
|  | |||||||
| @ -290,6 +290,8 @@ parseAndFinaliseJournal' parser iopts f txt = do | |||||||
| -- | -- | ||||||
| -- - check balance assertions if enabled. | -- - check balance assertions if enabled. | ||||||
| -- | -- | ||||||
|  | -- - infer transaction-implied market prices from transaction prices | ||||||
|  | -- | ||||||
| journalFinalise :: InputOpts -> FilePath -> Text -> Journal -> ExceptT String IO Journal | journalFinalise :: InputOpts -> FilePath -> Text -> Journal -> ExceptT String IO Journal | ||||||
| journalFinalise iopts f txt pj = do | journalFinalise iopts f txt pj = do | ||||||
|   t <- liftIO getClockTime |   t <- liftIO getClockTime | ||||||
| @ -305,23 +307,25 @@ journalFinalise iopts f txt pj = do | |||||||
|       & journalAddFile (f, txt)  -- save the file path and content |       & journalAddFile (f, txt)  -- save the file path and content | ||||||
|       & journalSetLastReadTime t -- save the last read time |       & journalSetLastReadTime t -- save the last read time | ||||||
|       & journalReverse           -- convert all lists to parse order |       & journalReverse           -- convert all lists to parse order | ||||||
|       & if not (auto_ iopts) || null (jtxnmodifiers pj) |       & (if not (auto_ iopts) || null (jtxnmodifiers pj) | ||||||
|         then |          then | ||||||
|           -- Auto postings are not active. |            -- Auto postings are not active. | ||||||
|           -- Balance all transactions and maybe check balance assertions. |            -- Balance all transactions and maybe check balance assertions. | ||||||
|           journalBalanceTransactions (not $ ignore_assertions_ iopts) |            journalBalanceTransactions (not $ ignore_assertions_ iopts) | ||||||
|         else \j -> do  -- Either monad |          else \j -> do  -- Either monad | ||||||
|           -- Auto postings are active. |            -- Auto postings are active. | ||||||
|           -- Balance all transactions without checking balance assertions, |            -- Balance all transactions without checking balance assertions, | ||||||
|           j' <- journalBalanceTransactions False j |            j' <- journalBalanceTransactions False j | ||||||
|           -- then add the auto postings |            -- then add the auto postings | ||||||
|           -- (Note adding auto postings after balancing means #893b fails; |            -- (Note adding auto postings after balancing means #893b fails; | ||||||
|           -- adding them before balancing probably means #893a, #928, #938 fail.) |            -- adding them before balancing probably means #893a, #928, #938 fail.) | ||||||
|           let j'' = journalModifyTransactions j' |            let j'' = journalModifyTransactions j' | ||||||
|           -- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?) |            -- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?) | ||||||
|           j''' <- journalApplyCommodityStyles j'' |            j''' <- journalApplyCommodityStyles j'' | ||||||
|           -- then check balance assertions. |            -- then check balance assertions. | ||||||
|           journalBalanceTransactions (not $ ignore_assertions_ iopts) j''' |            journalBalanceTransactions (not $ ignore_assertions_ iopts) j''' | ||||||
|  |         ) | ||||||
|  |      & fmap journalInferMarketPricesFromTransactions  -- infer market prices from commodity-exchanging transactions | ||||||
| 
 | 
 | ||||||
| setYear :: Year -> JournalParser m () | setYear :: Year -> JournalParser m () | ||||||
| setYear y = modify' (\j -> j{jparsedefaultyear=Just y}) | setYear y = modify' (\j -> j{jparsedefaultyear=Just y}) | ||||||
|  | |||||||
| @ -76,9 +76,9 @@ $ hledger -f- bal -N -V -e 3000/2 | |||||||
| 
 | 
 | ||||||
| D 1000.00 H                ; declare a default commodity named H | D 1000.00 H                ; declare a default commodity named H | ||||||
| 
 | 
 | ||||||
| P 2015/08/14 EEEE  41.66   ; default commodity H is used for these market prices | P 2015/08/15 EEEE  41.66   ; default commodity H is used for these market prices | ||||||
| P 2015/08/14 FFFF  74.62 | P 2015/08/15 FFFF  74.62 | ||||||
| P 2015/08/14 GGGG  32.39 | P 2015/08/15 GGGG  32.39 | ||||||
| 
 | 
 | ||||||
| 2015/08/15 | 2015/08/15 | ||||||
|     a  2.4120 EEEE @@ 100  ; default commodity H is used for these transaction prices |     a  2.4120 EEEE @@ 100  ; default commodity H is used for these transaction prices | ||||||
|  | |||||||
| @ -221,3 +221,32 @@ P 2002/01/01 A  2 B | |||||||
| # was inclusive. | # was inclusive. | ||||||
| $ hledger -f- bal -N -V -e 2002-01-01 | $ hledger -f- bal -N -V -e 2002-01-01 | ||||||
|                  1 B  a |                  1 B  a | ||||||
|  | 
 | ||||||
|  | # Test market prices inferred from transactions, as in Ledger. | ||||||
|  | 
 | ||||||
|  | < | ||||||
|  | 2020-01-01 | ||||||
|  |   (assets:stock)   1 TSLA @ $500 | ||||||
|  | 
 | ||||||
|  | 2020-03-01 | ||||||
|  |   (assets:stock)   1 TSLA @ $500 | ||||||
|  | 
 | ||||||
|  | P 2020-03-01 TSLA  $600 | ||||||
|  | 
 | ||||||
|  | 2020-05-01 | ||||||
|  |   (assets:stock)   1 TSLA @ $800 | ||||||
|  | 
 | ||||||
|  | # 22. Market price is inferred from a transaction price, | ||||||
|  | # -V works without a P directive. | ||||||
|  | $ hledger -f- bal -N -V -e 2020-01-02 | ||||||
|  |                 $500  assets:stock | ||||||
|  | 
 | ||||||
|  | # 23. A P-declared market price has precedence over a transaction price  | ||||||
|  | # on the same date. | ||||||
|  | $ hledger -f- bal -N -V -e 2020-03-02 | ||||||
|  |                $1200  assets:stock | ||||||
|  | 
 | ||||||
|  | # 24. A transaction-implied market price has precedence  | ||||||
|  | # over an older P-declared market price. | ||||||
|  | $ hledger -f- bal -N -V -e 2020-05-02 | ||||||
|  |                $2400  assets:stock | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user