lib: clarify price types (#131)
dropped journalPrices renamed Price to AmountPrice, AKA "transaction price" renamed MarketPrice to PriceDirective. added new MarketPrice (more pure form of PriceDirective without the amount style information) Prices is now a more efficient data structure, but not used yet.
This commit is contained in:
parent
e24c6292d0
commit
adb6ee40eb
@ -414,7 +414,7 @@ Posting -- MixedAmount
|
|||||||
MixedAmount *-- "*" Amount
|
MixedAmount *-- "*" Amount
|
||||||
Amount -- CommoditySymbol
|
Amount -- CommoditySymbol
|
||||||
Amount -- Quantity
|
Amount -- Quantity
|
||||||
Amount -- Price
|
Amount -- AmountPrice
|
||||||
Amount -- AmountStyle
|
Amount -- AmountStyle
|
||||||
</uml>
|
</uml>
|
||||||
-->
|
-->
|
||||||
|
|||||||
@ -152,7 +152,7 @@ hledgerApiApp staticdir j = Servant.serve api server
|
|||||||
where
|
where
|
||||||
accountnamesH = return $ journalAccountNames j
|
accountnamesH = return $ journalAccountNames j
|
||||||
transactionsH = return $ jtxns j
|
transactionsH = return $ jtxns j
|
||||||
pricesH = return $ jmarketprices j
|
pricesH = return $ jpricedirectives j
|
||||||
commoditiesH = return $ (M.keys . jinferredcommodities) j
|
commoditiesH = return $ (M.keys . jinferredcommodities) j
|
||||||
accountsH = return $ ledgerTopAccounts $ ledgerFromJournal Hledger.Query.Any j
|
accountsH = return $ ledgerTopAccounts $ ledgerFromJournal Hledger.Query.Any j
|
||||||
accounttransactionsH (a::AccountName) = do
|
accounttransactionsH (a::AccountName) = do
|
||||||
@ -176,7 +176,7 @@ hledgerApiApp staticdir j = Servant.serve api server
|
|||||||
--instance ToJSON DigitGroupStyle where toJSON = genericToJSON defaultOptions
|
--instance ToJSON DigitGroupStyle where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON MixedAmount where toJSON = genericToJSON defaultOptions
|
--instance ToJSON MixedAmount where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON BalanceAssertion where toJSON = genericToJSON defaultOptions
|
--instance ToJSON BalanceAssertion where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON Price where toJSON = genericToJSON defaultOptions
|
--instance ToJSON AmountPrice where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON MarketPrice where toJSON = genericToJSON defaultOptions
|
--instance ToJSON MarketPrice where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON PostingType where toJSON = genericToJSON defaultOptions
|
--instance ToJSON PostingType where toJSON = genericToJSON defaultOptions
|
||||||
--instance ToJSON Posting where
|
--instance ToJSON Posting where
|
||||||
@ -216,7 +216,7 @@ instance ToJSON Side
|
|||||||
instance ToJSON DigitGroupStyle
|
instance ToJSON DigitGroupStyle
|
||||||
instance ToJSON MixedAmount
|
instance ToJSON MixedAmount
|
||||||
instance ToJSON BalanceAssertion
|
instance ToJSON BalanceAssertion
|
||||||
instance ToJSON Price
|
instance ToJSON AmountPrice
|
||||||
instance ToJSON MarketPrice
|
instance ToJSON MarketPrice
|
||||||
instance ToJSON PostingType
|
instance ToJSON PostingType
|
||||||
instance ToJSON Posting where
|
instance ToJSON Posting where
|
||||||
@ -262,7 +262,7 @@ instance ToSchema Side
|
|||||||
instance ToSchema DigitGroupStyle
|
instance ToSchema DigitGroupStyle
|
||||||
instance ToSchema MixedAmount
|
instance ToSchema MixedAmount
|
||||||
instance ToSchema BalanceAssertion
|
instance ToSchema BalanceAssertion
|
||||||
instance ToSchema Price
|
instance ToSchema AmountPrice
|
||||||
#if MIN_VERSION_swagger2(2,1,5)
|
#if MIN_VERSION_swagger2(2,1,5)
|
||||||
where declareNamedSchema = genericDeclareNamedSchemaUnrestricted defaultSchemaOptions
|
where declareNamedSchema = genericDeclareNamedSchemaUnrestricted defaultSchemaOptions
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -302,7 +302,7 @@ setMinimalPrecision a = setAmountPrecision normalprecision a
|
|||||||
-- appropriate to the current debug level. 9 shows maximum detail.
|
-- appropriate to the current debug level. 9 shows maximum detail.
|
||||||
showAmountDebug :: Amount -> String
|
showAmountDebug :: Amount -> String
|
||||||
showAmountDebug Amount{acommodity="AUTO"} = "(missing)"
|
showAmountDebug Amount{acommodity="AUTO"} = "(missing)"
|
||||||
showAmountDebug Amount{..} = printf "Amount {acommodity=%s, aquantity=%s, aprice=%s, astyle=%s}" (show acommodity) (show aquantity) (showPriceDebug aprice) (show astyle)
|
showAmountDebug Amount{..} = printf "Amount {acommodity=%s, aquantity=%s, aprice=%s, astyle=%s}" (show acommodity) (show aquantity) (showAmountPriceDebug aprice) (show astyle)
|
||||||
|
|
||||||
-- | Get the string representation of an amount, without any \@ price.
|
-- | Get the string representation of an amount, without any \@ price.
|
||||||
showAmountWithoutPrice :: Amount -> String
|
showAmountWithoutPrice :: Amount -> String
|
||||||
@ -341,15 +341,15 @@ cshowAmountWithoutPrice a = cshowAmount a{aprice=NoPrice}
|
|||||||
showAmountWithoutPriceOrCommodity :: Amount -> String
|
showAmountWithoutPriceOrCommodity :: Amount -> String
|
||||||
showAmountWithoutPriceOrCommodity a = showAmount a{acommodity="", aprice=NoPrice}
|
showAmountWithoutPriceOrCommodity a = showAmount a{acommodity="", aprice=NoPrice}
|
||||||
|
|
||||||
showPrice :: Price -> String
|
showAmountPrice :: AmountPrice -> String
|
||||||
showPrice NoPrice = ""
|
showAmountPrice NoPrice = ""
|
||||||
showPrice (UnitPrice pa) = " @ " ++ showAmount pa
|
showAmountPrice (UnitPrice pa) = " @ " ++ showAmount pa
|
||||||
showPrice (TotalPrice pa) = " @@ " ++ showAmount pa
|
showAmountPrice (TotalPrice pa) = " @@ " ++ showAmount pa
|
||||||
|
|
||||||
showPriceDebug :: Price -> String
|
showAmountPriceDebug :: AmountPrice -> String
|
||||||
showPriceDebug NoPrice = ""
|
showAmountPriceDebug NoPrice = ""
|
||||||
showPriceDebug (UnitPrice pa) = " @ " ++ showAmountDebug pa
|
showAmountPriceDebug (UnitPrice pa) = " @ " ++ showAmountDebug pa
|
||||||
showPriceDebug (TotalPrice pa) = " @@ " ++ showAmountDebug pa
|
showAmountPriceDebug (TotalPrice pa) = " @@ " ++ showAmountDebug pa
|
||||||
|
|
||||||
-- | Given a map of standard amount display styles, apply the appropriate one to this amount.
|
-- | Given a map of standard amount display styles, apply the appropriate one to this amount.
|
||||||
-- If there's no standard style for this amount's commodity, return the amount unchanged.
|
-- If there's no standard style for this amount's commodity, return the amount unchanged.
|
||||||
@ -385,7 +385,7 @@ showAmountHelper showzerocommodity a@Amount{acommodity=c, aprice=p, astyle=Amoun
|
|||||||
(quantity',c') | displayingzero && not showzerocommodity = ("0","")
|
(quantity',c') | displayingzero && not showzerocommodity = ("0","")
|
||||||
| otherwise = (quantity, quoteCommoditySymbolIfNeeded c)
|
| otherwise = (quantity, quoteCommoditySymbolIfNeeded c)
|
||||||
space = if not (T.null c') && ascommodityspaced then " " else "" :: String
|
space = if not (T.null c') && ascommodityspaced then " " else "" :: String
|
||||||
price = showPrice p
|
price = showAmountPrice p
|
||||||
|
|
||||||
-- | Like showAmount, but show a zero amount's commodity if it has one.
|
-- | Like showAmount, but show a zero amount's commodity if it has one.
|
||||||
showAmountWithZeroCommodity :: Amount -> String
|
showAmountWithZeroCommodity :: Amount -> String
|
||||||
|
|||||||
@ -16,7 +16,7 @@ other data format (see "Hledger.Read").
|
|||||||
|
|
||||||
module Hledger.Data.Journal (
|
module Hledger.Data.Journal (
|
||||||
-- * Parsing helpers
|
-- * Parsing helpers
|
||||||
addMarketPrice,
|
addPriceDirective,
|
||||||
addTransactionModifier,
|
addTransactionModifier,
|
||||||
addPeriodicTransaction,
|
addPeriodicTransaction,
|
||||||
addTransaction,
|
addTransaction,
|
||||||
@ -61,7 +61,7 @@ module Hledger.Data.Journal (
|
|||||||
journalNextTransaction,
|
journalNextTransaction,
|
||||||
journalPrevTransaction,
|
journalPrevTransaction,
|
||||||
journalPostings,
|
journalPostings,
|
||||||
journalPrices,
|
-- journalPrices,
|
||||||
-- * Standard account types
|
-- * Standard account types
|
||||||
journalBalanceSheetAccountQuery,
|
journalBalanceSheetAccountQuery,
|
||||||
journalProfitAndLossAccountQuery,
|
journalProfitAndLossAccountQuery,
|
||||||
@ -116,7 +116,7 @@ import Hledger.Data.Types
|
|||||||
import Hledger.Data.AccountName
|
import Hledger.Data.AccountName
|
||||||
import Hledger.Data.Amount
|
import Hledger.Data.Amount
|
||||||
import Hledger.Data.Dates
|
import Hledger.Data.Dates
|
||||||
import Hledger.Data.Prices
|
-- import Hledger.Data.Prices
|
||||||
import Hledger.Data.Transaction
|
import Hledger.Data.Transaction
|
||||||
import Hledger.Data.TransactionModifier
|
import Hledger.Data.TransactionModifier
|
||||||
import Hledger.Data.Posting
|
import Hledger.Data.Posting
|
||||||
@ -154,7 +154,7 @@ instance Show Journal where
|
|||||||
-- ,show (jtxnmodifiers j)
|
-- ,show (jtxnmodifiers j)
|
||||||
-- ,show (jperiodictxns j)
|
-- ,show (jperiodictxns j)
|
||||||
-- ,show $ jparsetimeclockentries j
|
-- ,show $ jparsetimeclockentries j
|
||||||
-- ,show $ jmarketprices j
|
-- ,show $ jpricedirectives j
|
||||||
-- ,show $ jfinalcommentlines j
|
-- ,show $ jfinalcommentlines j
|
||||||
-- ,show $ jparsestate j
|
-- ,show $ jparsestate j
|
||||||
-- ,show $ map fst $ jfiles j
|
-- ,show $ map fst $ jfiles j
|
||||||
@ -184,7 +184,7 @@ instance Sem.Semigroup Journal where
|
|||||||
,jdeclaredaccounttypes = jdeclaredaccounttypes j1 <> jdeclaredaccounttypes j2
|
,jdeclaredaccounttypes = jdeclaredaccounttypes j1 <> jdeclaredaccounttypes j2
|
||||||
,jcommodities = jcommodities j1 <> jcommodities j2
|
,jcommodities = jcommodities j1 <> jcommodities j2
|
||||||
,jinferredcommodities = jinferredcommodities j1 <> jinferredcommodities j2
|
,jinferredcommodities = jinferredcommodities j1 <> jinferredcommodities j2
|
||||||
,jmarketprices = jmarketprices j1 <> jmarketprices j2
|
,jpricedirectives = jpricedirectives j1 <> jpricedirectives 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
|
||||||
@ -213,7 +213,7 @@ nulljournal = Journal {
|
|||||||
,jdeclaredaccounttypes = M.empty
|
,jdeclaredaccounttypes = M.empty
|
||||||
,jcommodities = M.empty
|
,jcommodities = M.empty
|
||||||
,jinferredcommodities = M.empty
|
,jinferredcommodities = M.empty
|
||||||
,jmarketprices = []
|
,jpricedirectives = []
|
||||||
,jtxnmodifiers = []
|
,jtxnmodifiers = []
|
||||||
,jperiodictxns = []
|
,jperiodictxns = []
|
||||||
,jtxns = []
|
,jtxns = []
|
||||||
@ -240,8 +240,8 @@ addTransactionModifier mt j = j { jtxnmodifiers = mt : jtxnmodifiers j }
|
|||||||
addPeriodicTransaction :: PeriodicTransaction -> Journal -> Journal
|
addPeriodicTransaction :: PeriodicTransaction -> Journal -> Journal
|
||||||
addPeriodicTransaction pt j = j { jperiodictxns = pt : jperiodictxns j }
|
addPeriodicTransaction pt j = j { jperiodictxns = pt : jperiodictxns j }
|
||||||
|
|
||||||
addMarketPrice :: MarketPrice -> Journal -> Journal
|
addPriceDirective :: PriceDirective -> Journal -> Journal
|
||||||
addMarketPrice h j = j { jmarketprices = h : jmarketprices j } -- XXX #999 keep sorted
|
addPriceDirective h j = j { jpricedirectives = h : jpricedirectives j } -- XXX #999 keep sorted
|
||||||
|
|
||||||
-- | Get the transaction with this index (its 1-based position in the input stream), if any.
|
-- | Get the transaction with this index (its 1-based position in the input stream), if any.
|
||||||
journalTransactionAt :: Journal -> Integer -> Maybe Transaction
|
journalTransactionAt :: Journal -> Integer -> Maybe Transaction
|
||||||
@ -556,7 +556,7 @@ journalReverse j =
|
|||||||
,jtxns = reverse $ jtxns j
|
,jtxns = reverse $ jtxns j
|
||||||
,jtxnmodifiers = reverse $ jtxnmodifiers j
|
,jtxnmodifiers = reverse $ jtxnmodifiers j
|
||||||
,jperiodictxns = reverse $ jperiodictxns j
|
,jperiodictxns = reverse $ jperiodictxns j
|
||||||
,jmarketprices = reverse $ jmarketprices j
|
,jpricedirectives = reverse $ jpricedirectives j
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Set this journal's last read time, ie when its files were last read.
|
-- | Set this journal's last read time, ie when its files were last read.
|
||||||
@ -908,16 +908,16 @@ checkBalanceAssignmentUnassignableAccountB p = do
|
|||||||
-- a commodity format directive, or otherwise inferred from posting
|
-- a commodity format directive, or otherwise inferred from posting
|
||||||
-- amounts as in hledger < 0.28.
|
-- amounts as in hledger < 0.28.
|
||||||
journalApplyCommodityStyles :: Journal -> Journal
|
journalApplyCommodityStyles :: Journal -> Journal
|
||||||
journalApplyCommodityStyles j@Journal{jtxns=ts, jmarketprices=mps} = j''
|
journalApplyCommodityStyles j@Journal{jtxns=ts, jpricedirectives=pds} = j''
|
||||||
where
|
where
|
||||||
j' = journalInferCommodityStyles j
|
j' = journalInferCommodityStyles j
|
||||||
styles = journalCommodityStyles j'
|
styles = journalCommodityStyles j'
|
||||||
j'' = j'{jtxns=map fixtransaction ts, jmarketprices=map fixmarketprice mps}
|
j'' = j'{jtxns=map fixtransaction ts, jpricedirectives=map fixpricedirective pds}
|
||||||
fixtransaction t@Transaction{tpostings=ps} = t{tpostings=map fixposting ps}
|
fixtransaction t@Transaction{tpostings=ps} = t{tpostings=map fixposting ps}
|
||||||
fixposting p = p{pamount=styleMixedAmount styles $ pamount p
|
fixposting p = p{pamount=styleMixedAmount styles $ pamount p
|
||||||
,pbalanceassertion=fixbalanceassertion <$> pbalanceassertion p}
|
,pbalanceassertion=fixbalanceassertion <$> pbalanceassertion p}
|
||||||
fixbalanceassertion ba = ba{baamount=styleAmount styles $ baamount ba}
|
fixbalanceassertion ba = ba{baamount=styleAmount styles $ baamount ba}
|
||||||
fixmarketprice mp@MarketPrice{mpamount=a} = mp{mpamount=styleAmount styles a}
|
fixpricedirective pd@PriceDirective{pdamount=a} = pd{pdamount=styleAmount styles a}
|
||||||
|
|
||||||
-- | Get all the amount styles defined in this journal, either declared by
|
-- | Get all the amount styles defined in this journal, either declared by
|
||||||
-- a commodity directive or inferred from amounts, as a map from symbol to style.
|
-- a commodity directive or inferred from amounts, as a map from symbol to style.
|
||||||
@ -963,8 +963,8 @@ canonicalStyleFrom ss@(first:_) = first {asprecision = prec, asdecimalpoint = md
|
|||||||
-- [] -> (Just '.', 0)
|
-- [] -> (Just '.', 0)
|
||||||
|
|
||||||
-- -- | Apply this journal's historical price records to unpriced amounts where possible.
|
-- -- | Apply this journal's historical price records to unpriced amounts where possible.
|
||||||
-- journalApplyMarketPrices :: Journal -> Journal
|
-- journalApplyPriceDirectives :: Journal -> Journal
|
||||||
-- journalApplyMarketPrices j@Journal{jtxns=ts} = j{jtxns=map fixtransaction ts}
|
-- journalApplyPriceDirectives j@Journal{jtxns=ts} = j{jtxns=map fixtransaction ts}
|
||||||
-- where
|
-- where
|
||||||
-- fixtransaction t@Transaction{tdate=d, tpostings=ps} = t{tpostings=map fixposting ps}
|
-- fixtransaction t@Transaction{tdate=d, tpostings=ps} = t{tpostings=map fixposting ps}
|
||||||
-- where
|
-- where
|
||||||
@ -972,14 +972,14 @@ canonicalStyleFrom ss@(first:_) = first {asprecision = prec, asdecimalpoint = md
|
|||||||
-- fixmixedamount (Mixed as) = Mixed $ map fixamount as
|
-- fixmixedamount (Mixed as) = Mixed $ map fixamount as
|
||||||
-- fixamount = fixprice
|
-- fixamount = fixprice
|
||||||
-- fixprice a@Amount{price=Just _} = a
|
-- fixprice a@Amount{price=Just _} = a
|
||||||
-- fixprice a@Amount{commodity=c} = a{price=maybe Nothing (Just . UnitPrice) $ journalMarketPriceFor j d c}
|
-- fixprice a@Amount{commodity=c} = a{price=maybe Nothing (Just . UnitPrice) $ journalPriceDirectiveFor j d c}
|
||||||
|
|
||||||
-- -- | Get the price for a commodity on the specified day from the price database, if known.
|
-- -- | Get the price for a commodity on the specified day from the price database, if known.
|
||||||
-- -- Does only one lookup step, ie will not look up the price of a price.
|
-- -- Does only one lookup step, ie will not look up the price of a price.
|
||||||
-- journalMarketPriceFor :: Journal -> Day -> CommoditySymbol -> Maybe MixedAmount
|
-- journalPriceDirectiveFor :: Journal -> Day -> CommoditySymbol -> Maybe MixedAmount
|
||||||
-- journalMarketPriceFor j d CommoditySymbol{symbol=s} = do
|
-- journalPriceDirectiveFor j d CommoditySymbol{symbol=s} = do
|
||||||
-- let ps = reverse $ filter ((<= d).mpdate) $ filter ((s==).hsymbol) $ sortBy (comparing mpdate) $ jmarketprices j
|
-- let ps = reverse $ filter ((<= d).pddate) $ filter ((s==).hsymbol) $ sortBy (comparing pddate) $ jpricedirectives j
|
||||||
-- case ps of (MarketPrice{mpamount=a}:_) -> Just a
|
-- case ps of (PriceDirective{pdamount=a}:_) -> Just a
|
||||||
-- _ -> Nothing
|
-- _ -> 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.
|
||||||
@ -1037,12 +1037,12 @@ traverseJournalAmounts
|
|||||||
=> (Amount -> f Amount)
|
=> (Amount -> f Amount)
|
||||||
-> Journal -> f Journal
|
-> Journal -> f Journal
|
||||||
traverseJournalAmounts f j =
|
traverseJournalAmounts f j =
|
||||||
recombine <$> (traverse . mpa) f (jmarketprices j)
|
recombine <$> (traverse . mpa) f (jpricedirectives j)
|
||||||
<*> (traverse . tp . traverse . pamt . maa . traverse) f (jtxns j)
|
<*> (traverse . tp . traverse . pamt . maa . traverse) f (jtxns j)
|
||||||
where
|
where
|
||||||
recombine mps txns = j { jmarketprices = mps, jtxns = txns }
|
recombine mps txns = j { jpricedirectives = mps, jtxns = txns }
|
||||||
-- a bunch of traversals
|
-- a bunch of traversals
|
||||||
mpa g mp = (\amt -> mp { mpamount = amt }) <$> g (mpamount mp)
|
mpa g pd = (\amt -> pd { pdamount = amt }) <$> g (pdamount pd)
|
||||||
tp g t = (\ps -> t { tpostings = ps }) <$> g (tpostings t)
|
tp g t = (\ps -> t { tpostings = ps }) <$> g (tpostings t)
|
||||||
pamt g p = (\amt -> p { pamount = amt }) <$> g (pamount p)
|
pamt g p = (\amt -> p { pamount = amt }) <$> g (pamount p)
|
||||||
maa g (Mixed as) = Mixed <$> g as
|
maa g (Mixed as) = Mixed <$> g as
|
||||||
@ -1098,17 +1098,19 @@ postingPivot fieldortagname p = p{paccount = pivotedacct, poriginal = Just $ ori
|
|||||||
postingFindTag :: TagName -> Posting -> Maybe (TagName, TagValue)
|
postingFindTag :: TagName -> Posting -> Maybe (TagName, TagValue)
|
||||||
postingFindTag tagname p = find ((tagname==) . fst) $ postingAllTags p
|
postingFindTag tagname p = find ((tagname==) . fst) $ postingAllTags p
|
||||||
|
|
||||||
-- | Convert a journal's market price declarations
|
-- -- | Build a database of market prices in effect on the given date,
|
||||||
journalPrices :: Journal -> Prices
|
-- -- from the journal's price directives.
|
||||||
journalPrices = toPrices . jmarketprices
|
-- journalPrices :: Day -> Journal -> Prices
|
||||||
|
-- journalPrices d = toPrices d . jpricedirectives
|
||||||
|
|
||||||
-- -- | Render a market price as a P directive.
|
-- -- | Render a market price as a P directive.
|
||||||
-- showMarketPriceDirective :: MarketPrice -> String
|
-- showPriceDirectiveDirective :: PriceDirective -> String
|
||||||
-- showMarketPriceDirective mp = unwords
|
-- showPriceDirectiveDirective pd = unwords
|
||||||
-- [ "P"
|
-- [ "P"
|
||||||
-- , showDate (mpdate mp)
|
-- , showDate (pddate pd)
|
||||||
-- , T.unpack (mpcommodity mp)
|
-- , T.unpack (pdcommodity pd)
|
||||||
-- , (showAmount . setAmountPrecision maxprecision) (mpamount mp)
|
-- , (showAmount . setAmountPrecision maxprecision) (pdamount pd
|
||||||
|
-- )
|
||||||
-- ]
|
-- ]
|
||||||
|
|
||||||
-- Misc helpers
|
-- Misc helpers
|
||||||
|
|||||||
@ -350,7 +350,7 @@ aliasReplace (RegexAlias re repl) a = T.pack $ regexReplaceCIMemo re repl $ T.un
|
|||||||
-- Apply a specified valuation to this posting's amount, using the provided
|
-- Apply a specified valuation to this posting's amount, using the provided
|
||||||
-- prices db, commodity styles, period-end/current dates, and whether
|
-- prices db, commodity styles, period-end/current dates, and whether
|
||||||
-- this is for a multiperiod report or not.
|
-- this is for a multiperiod report or not.
|
||||||
postingApplyValuation :: Prices -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> Posting -> ValuationType -> Posting
|
postingApplyValuation :: [PriceDirective] -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> Posting -> ValuationType -> Posting
|
||||||
postingApplyValuation prices styles periodend today ismultiperiod p v =
|
postingApplyValuation prices styles periodend today ismultiperiod p v =
|
||||||
case v of
|
case v of
|
||||||
AtCost Nothing -> postingToCost styles p
|
AtCost Nothing -> postingToCost styles p
|
||||||
@ -370,7 +370,7 @@ postingToCost styles p@Posting{pamount=a} = p{pamount=mixedAmountToCost styles a
|
|||||||
-- using the given market prices.
|
-- using the given market prices.
|
||||||
-- When market prices available on that date are not sufficient to
|
-- When market prices available on that date are not sufficient to
|
||||||
-- calculate the value, amounts are left unchanged.
|
-- calculate the value, amounts are left unchanged.
|
||||||
postingValueAtDate :: Prices -> Maybe CommoditySymbol -> Day -> Posting -> Posting
|
postingValueAtDate :: [PriceDirective] -> Maybe CommoditySymbol -> Day -> Posting -> Posting
|
||||||
postingValueAtDate prices mc d p = postingTransformAmount (mixedAmountValueAtDate prices mc d) p
|
postingValueAtDate prices mc d p = postingTransformAmount (mixedAmountValueAtDate prices mc d) p
|
||||||
|
|
||||||
-- | Apply a transform function to this posting's amount.
|
-- | Apply a transform function to this posting's amount.
|
||||||
|
|||||||
@ -5,19 +5,18 @@ convert amounts to value in various ways.
|
|||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
module Hledger.Data.Prices (
|
module Hledger.Data.Prices (
|
||||||
Prices
|
Prices
|
||||||
,nullPrices
|
|
||||||
,toPrices
|
|
||||||
,priceLookup
|
|
||||||
,amountValueAtDate
|
,amountValueAtDate
|
||||||
,amountApplyValuation
|
,amountApplyValuation
|
||||||
,mixedAmountValueAtDate
|
,mixedAmountValueAtDate
|
||||||
,mixedAmountApplyValuation
|
,mixedAmountApplyValuation
|
||||||
|
,priceLookup
|
||||||
,tests_Prices
|
,tests_Prices
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
@ -34,39 +33,69 @@ import Hledger.Data.Types
|
|||||||
import Hledger.Data.Amount
|
import Hledger.Data.Amount
|
||||||
import Hledger.Data.Dates (parsedate)
|
import Hledger.Data.Dates (parsedate)
|
||||||
|
|
||||||
-- | A database of historical market prices for multiple commodites,
|
|
||||||
-- allowing fast lookup of exchange rates between commodity pairs on a
|
|
||||||
-- given date.
|
|
||||||
data Prices = Prices {
|
|
||||||
prPrices :: [MarketPrice] -- ^ For now, just a list of price declarations,
|
|
||||||
-- sorted by date then parse order, then reversed.
|
|
||||||
}
|
|
||||||
|
|
||||||
nullPrices = toPrices []
|
|
||||||
|
|
||||||
-- | Convert a list of market prices in declaration order to a 'Prices' db.
|
|
||||||
toPrices :: [MarketPrice] -> Prices
|
|
||||||
toPrices declaredprices = Prices{prPrices = reverse $ sortOn mpdate declaredprices}
|
|
||||||
|
|
||||||
-- | Reverse a market price from A to B, so that it becomes an equivalent price from B to A.
|
|
||||||
marketPriceInvert :: MarketPrice -> MarketPrice
|
|
||||||
marketPriceInvert p@MarketPrice{mpcommodity, mpamount} =
|
|
||||||
p{ mpcommodity = acommodity mpamount
|
|
||||||
, mpamount = setMinimalPrecision mpamount{acommodity=mpcommodity, aquantity=1 / aquantity mpamount}
|
|
||||||
}
|
|
||||||
|
|
||||||
tests_marketPriceInvert = tests "marketPriceInvert" [
|
|
||||||
marketPriceInvert (MarketPrice{mpdate=d "2019-06-01", mpcommodity="A", mpamount=amt "B" 2})
|
|
||||||
`is` (MarketPrice{mpdate=d "2019-06-01", mpcommodity="B", mpamount=amt "A" 0.5 `withPrecision` 1})
|
|
||||||
]
|
|
||||||
|
|
||||||
d = parsedate
|
d = parsedate
|
||||||
amt c q = nullamt{acommodity=c, aquantity=q}
|
-- amt c q = nullamt{acommodity=c, aquantity=q}
|
||||||
|
|
||||||
-- | Using the market prices in effect at the given date, find the
|
tests_Prices = tests "Prices" [
|
||||||
-- market value of one unit of a given commodity, in a different
|
tests_priceLookup
|
||||||
-- specified valuation commodity, defaulting to the commodity of the
|
]
|
||||||
-- most recent applicable price.
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Valuation
|
||||||
|
|
||||||
|
-- Apply a specified valuation to this mixed amount, using the provided
|
||||||
|
-- prices db, commodity styles, period-end/current dates,
|
||||||
|
-- and whether this is for a multiperiod report or not.
|
||||||
|
-- Currently ignores the specified valuation commodity and always uses
|
||||||
|
-- the default valuation commodity.
|
||||||
|
mixedAmountApplyValuation :: [PriceDirective] -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> ValuationType -> MixedAmount -> MixedAmount
|
||||||
|
mixedAmountApplyValuation prices styles periodend today ismultiperiod v (Mixed as) =
|
||||||
|
Mixed $ map (amountApplyValuation prices styles periodend today ismultiperiod v) as
|
||||||
|
|
||||||
|
-- | Find the market value of each component amount in the given
|
||||||
|
-- commodity, or its default valuation commodity, at the given
|
||||||
|
-- valuation date, using the given market prices.
|
||||||
|
-- When market prices available on that date are not sufficient to
|
||||||
|
-- calculate the value, amounts are left unchanged.
|
||||||
|
mixedAmountValueAtDate :: [PriceDirective] -> Maybe CommoditySymbol -> Day -> MixedAmount -> MixedAmount
|
||||||
|
mixedAmountValueAtDate prices mc d (Mixed as) = Mixed $ map (amountValueAtDate prices mc d) as
|
||||||
|
|
||||||
|
-- | Apply a specified valuation to this amount, using the provided
|
||||||
|
-- prices db, commodity styles, period-end/current dates,
|
||||||
|
-- and whether this is for a multiperiod report or not.
|
||||||
|
amountApplyValuation :: [PriceDirective] -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> ValuationType -> Amount -> Amount
|
||||||
|
amountApplyValuation prices styles periodend today ismultiperiod v a =
|
||||||
|
case v of
|
||||||
|
AtCost Nothing -> amountToCost styles a
|
||||||
|
AtCost mc -> amountValueAtDate prices mc periodend $ amountToCost styles a
|
||||||
|
AtEnd mc -> amountValueAtDate prices mc periodend a
|
||||||
|
AtNow mc -> amountValueAtDate prices mc today a
|
||||||
|
AtDefault mc | ismultiperiod -> amountValueAtDate prices mc periodend a
|
||||||
|
AtDefault mc -> amountValueAtDate prices mc today a
|
||||||
|
AtDate d mc -> amountValueAtDate prices mc d a
|
||||||
|
|
||||||
|
-- | Find the market value of this amount in the given valuation
|
||||||
|
-- commodity if any, otherwise the default valuation commodity, at the
|
||||||
|
-- given valuation date. (The default valuation commodity is the
|
||||||
|
-- commodity of the latest applicable market price before the
|
||||||
|
-- valuation date.)
|
||||||
|
-- If the market prices available on that date are not sufficient to
|
||||||
|
-- calculate this value, the amount is left unchanged.
|
||||||
|
amountValueAtDate :: [PriceDirective] -> Maybe CommoditySymbol -> Day -> Amount -> Amount
|
||||||
|
amountValueAtDate pricedirectives mc d a =
|
||||||
|
case priceLookup pricedirectives d mc (acommodity a) of
|
||||||
|
Just v -> v{aquantity=aquantity v * aquantity a}
|
||||||
|
Nothing -> a
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Market price lookup, naive version
|
||||||
|
|
||||||
|
-- | Given a list of price directives in parse order, find the market
|
||||||
|
-- value at the given date of one unit of a given commodity, in a
|
||||||
|
-- different specified valuation commodity, defaulting to the
|
||||||
|
-- commodity of the most recent applicable price.
|
||||||
|
-- This might be slow if there are many price declarations.
|
||||||
--
|
--
|
||||||
-- When the valuation commodity is specified, this looks for, in order:
|
-- When the valuation commodity is specified, this looks for, in order:
|
||||||
--
|
--
|
||||||
@ -91,8 +120,8 @@ amt c q = nullamt{acommodity=c, aquantity=q}
|
|||||||
-- if the source commodity and the valuation commodity are the same,
|
-- if the source commodity and the valuation commodity are the same,
|
||||||
-- this returns Nothing.
|
-- this returns Nothing.
|
||||||
--
|
--
|
||||||
priceLookup :: Prices -> Day -> Maybe CommoditySymbol -> CommoditySymbol -> Maybe Amount
|
priceLookup :: [PriceDirective] -> Day -> Maybe CommoditySymbol -> CommoditySymbol -> Maybe Amount
|
||||||
priceLookup Prices{prPrices} d mto from
|
priceLookup pricedirectives d mto from
|
||||||
| mto == Just from = Nothing
|
| mto == Just from = Nothing
|
||||||
| otherwise = mdirectprice <|> mreverseprice
|
| otherwise = mdirectprice <|> mreverseprice
|
||||||
where
|
where
|
||||||
@ -100,84 +129,38 @@ priceLookup Prices{prPrices} d mto from
|
|||||||
dbg4With ( ((lbl++" for "++T.unpack from++" at "++show d++": ") ++)
|
dbg4With ( ((lbl++" for "++T.unpack from++" at "++show d++": ") ++)
|
||||||
. maybe "none" showAmount )
|
. maybe "none" showAmount )
|
||||||
|
|
||||||
|
latestfirst = reverse $ sortOn pddate pricedirectives -- sortOn will preserve parse order within the same date I think
|
||||||
|
|
||||||
-- Key to commodity symbols:
|
-- Key to commodity symbols:
|
||||||
-- from - commodity we are converting from (looking up a price for)
|
-- from - commodity we are converting from (looking up a price for)
|
||||||
-- mto - commodity we want to convert to, or Nothing meaning use default
|
-- mto - commodity we want to convert to, or Nothing meaning use default
|
||||||
-- pfrom - commodity that this market price converts from
|
-- pfrom - commodity that this market price converts from
|
||||||
-- pto - commodity that this market price converts to
|
-- pto - commodity that this market price converts to
|
||||||
|
|
||||||
-- prPrices is sorted by date then parse order, reversed. So the
|
-- prPriceDirectives is sorted by date then parse order, reversed. So the
|
||||||
-- first price on or before the valuation date is the effective one.
|
-- first price on or before the valuation date is the effective one.
|
||||||
|
|
||||||
mdirectprice =
|
mdirectprice =
|
||||||
dbgprice "direct market price" $
|
dbgprice "direct market price" $
|
||||||
headMay [mpamount | MarketPrice{mpdate, mpcommodity=pfrom, mpamount} <- prPrices
|
headMay [pdamount | PriceDirective{pddate, pdcommodity=pfrom, pdamount} <- latestfirst
|
||||||
, let pto = acommodity mpamount
|
, let pto = acommodity pdamount
|
||||||
, mpdate <= d
|
, pddate <= d
|
||||||
, pfrom == from
|
, pfrom == from
|
||||||
, maybe True (== pto) mto
|
, maybe True (== pto) mto
|
||||||
]
|
]
|
||||||
mreverseprice =
|
mreverseprice =
|
||||||
dbgprice "reverse market price" $
|
dbgprice "reverse market price" $
|
||||||
headMay [ priceamt
|
headMay [ priceamt
|
||||||
| mp@MarketPrice{mpdate, mpcommodity=pfrom, mpamount} <- prPrices
|
| PriceDirective{pddate, pdcommodity=pfrom, pdamount} <- latestfirst
|
||||||
, let pto = acommodity mpamount
|
, let pto = acommodity pdamount
|
||||||
, mpdate <= d
|
, pddate <= d
|
||||||
, pto == from
|
, pto == from
|
||||||
, maybe False (== pfrom) mto -- use reverse prices only when target commodity is explicitly specified
|
, maybe False (== pfrom) mto -- use reverse prices only when target commodity is explicitly specified
|
||||||
, let MarketPrice{mpamount=priceamt} = marketPriceInvert mp
|
, let PriceDirective{pdamount=priceamt} = undefined -- marketPriceInvert mp
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_priceLookup = tests "priceLookup" [
|
tests_priceLookup = tests "priceLookup" [
|
||||||
priceLookup (Prices []) (d "2019-06-01") Nothing "" `is` Nothing
|
priceLookup [] (d "2019-06-01") Nothing "" `is` Nothing
|
||||||
]
|
|
||||||
|
|
||||||
-- Apply a specified valuation to this mixed amount, using the provided
|
|
||||||
-- prices db, commodity styles, period-end/current dates,
|
|
||||||
-- and whether this is for a multiperiod report or not.
|
|
||||||
-- Currently ignores the specified valuation commodity and always uses
|
|
||||||
-- the default valuation commodity.
|
|
||||||
mixedAmountApplyValuation :: Prices -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> ValuationType -> MixedAmount -> MixedAmount
|
|
||||||
mixedAmountApplyValuation prices styles periodend today ismultiperiod v (Mixed as) =
|
|
||||||
Mixed $ map (amountApplyValuation prices styles periodend today ismultiperiod v) as
|
|
||||||
|
|
||||||
-- | Find the market value of each component amount in the given
|
|
||||||
-- commodity, or its default valuation commodity, at the given
|
|
||||||
-- valuation date, using the given market prices.
|
|
||||||
-- When market prices available on that date are not sufficient to
|
|
||||||
-- calculate the value, amounts are left unchanged.
|
|
||||||
mixedAmountValueAtDate :: Prices -> Maybe CommoditySymbol -> Day -> MixedAmount -> MixedAmount
|
|
||||||
mixedAmountValueAtDate prices mc d (Mixed as) = Mixed $ map (amountValueAtDate prices mc d) as
|
|
||||||
|
|
||||||
-- | Apply a specified valuation to this amount, using the provided
|
|
||||||
-- prices db, commodity styles, period-end/current dates,
|
|
||||||
-- and whether this is for a multiperiod report or not.
|
|
||||||
amountApplyValuation :: Prices -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Bool -> ValuationType -> Amount -> Amount
|
|
||||||
amountApplyValuation prices styles periodend today ismultiperiod v a =
|
|
||||||
case v of
|
|
||||||
AtCost Nothing -> amountToCost styles a
|
|
||||||
AtCost mc -> amountValueAtDate prices mc periodend $ amountToCost styles a
|
|
||||||
AtEnd mc -> amountValueAtDate prices mc periodend a
|
|
||||||
AtNow mc -> amountValueAtDate prices mc today a
|
|
||||||
AtDefault mc | ismultiperiod -> amountValueAtDate prices mc periodend a
|
|
||||||
AtDefault mc -> amountValueAtDate prices mc today a
|
|
||||||
AtDate d mc -> amountValueAtDate prices mc d a
|
|
||||||
|
|
||||||
-- | Find the market value of this amount in the given valuation
|
|
||||||
-- commodity if any, otherwise the default valuation commodity, at the
|
|
||||||
-- given valuation date. (The default valuation commodity is the
|
|
||||||
-- commodity of the latest applicable market price before the
|
|
||||||
-- valuation date.)
|
|
||||||
-- If the market prices available on that date are not sufficient to
|
|
||||||
-- calculate this value, the amount is left unchanged.
|
|
||||||
amountValueAtDate :: Prices -> Maybe CommoditySymbol -> Day -> Amount -> Amount
|
|
||||||
amountValueAtDate prices mc d a =
|
|
||||||
case priceLookup prices d mc (acommodity a) of
|
|
||||||
Just v -> v{aquantity=aquantity v * aquantity a}
|
|
||||||
Nothing -> a
|
|
||||||
|
|
||||||
tests_Prices = tests "Prices" [
|
|
||||||
tests_marketPriceInvert
|
|
||||||
,tests_priceLookup
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -154,12 +154,13 @@ instance ToMarkup Quantity
|
|||||||
where
|
where
|
||||||
toMarkup = toMarkup . show
|
toMarkup = toMarkup . show
|
||||||
|
|
||||||
-- | An amount's price (none, per unit, or total) in another commodity.
|
-- | An amount's per-unit or total cost/selling price in another
|
||||||
-- The price amount should always be positive.
|
-- commodity, as recorded in the journal entry eg with @ or @@.
|
||||||
data Price = NoPrice | UnitPrice Amount | TotalPrice Amount
|
-- Docs call this "transaction price". The amount is always positive.
|
||||||
|
data AmountPrice = NoPrice | UnitPrice Amount | TotalPrice Amount
|
||||||
deriving (Eq,Ord,Typeable,Data,Generic,Show)
|
deriving (Eq,Ord,Typeable,Data,Generic,Show)
|
||||||
|
|
||||||
instance NFData Price
|
instance NFData AmountPrice
|
||||||
|
|
||||||
-- | Display style for an amount.
|
-- | Display style for an amount.
|
||||||
data AmountStyle = AmountStyle {
|
data AmountStyle = AmountStyle {
|
||||||
@ -207,7 +208,7 @@ data Amount = Amount {
|
|||||||
aismultiplier :: Bool, -- ^ kludge: a flag marking this amount and posting as a multiplier
|
aismultiplier :: Bool, -- ^ kludge: a flag marking this amount and posting as a multiplier
|
||||||
-- in a TMPostingRule. In a regular Posting, should always be false.
|
-- in a TMPostingRule. In a regular Posting, should always be false.
|
||||||
astyle :: AmountStyle,
|
astyle :: AmountStyle,
|
||||||
aprice :: Price -- ^ the (fixed, transaction-specific) price for this amount, if any
|
aprice :: AmountPrice -- ^ the (fixed, transaction-specific) price for this amount, if any
|
||||||
} deriving (Eq,Ord,Typeable,Data,Generic,Show)
|
} deriving (Eq,Ord,Typeable,Data,Generic,Show)
|
||||||
|
|
||||||
instance NFData Amount
|
instance NFData Amount
|
||||||
@ -420,17 +421,40 @@ data TimeclockEntry = TimeclockEntry {
|
|||||||
|
|
||||||
instance NFData TimeclockEntry
|
instance NFData TimeclockEntry
|
||||||
|
|
||||||
-- | A historical exchange rate between two commodities, eg published
|
-- | A market price declaration made by the journal format's P directive.
|
||||||
-- by a stock exchange or the foreign exchange market.
|
-- It declares two things: a historical exchange rate between two commodities,
|
||||||
|
-- and an amount display style for the second commodity.
|
||||||
|
data PriceDirective = PriceDirective {
|
||||||
|
pddate :: Day
|
||||||
|
,pdcommodity :: CommoditySymbol
|
||||||
|
,pdamount :: Amount
|
||||||
|
} deriving (Eq,Ord,Typeable,Data,Generic,Show)
|
||||||
|
-- Show instance derived in Amount.hs (XXX why ?)
|
||||||
|
|
||||||
|
instance NFData PriceDirective
|
||||||
|
|
||||||
|
-- | A historical market price (exchange rate) from one commodity to another.
|
||||||
|
-- A more concise form of a PriceDirective, without the amount display info.
|
||||||
data MarketPrice = MarketPrice {
|
data MarketPrice = MarketPrice {
|
||||||
mpdate :: Day,
|
mpdate :: Day -- ^ Date on which this price becomes effective.
|
||||||
mpcommodity :: CommoditySymbol,
|
,mpfrom :: CommoditySymbol -- ^ The commodity being converted from.
|
||||||
mpamount :: Amount
|
,mpto :: CommoditySymbol -- ^ The commodity being converted to.
|
||||||
} deriving (Eq,Ord,Typeable,Data,Generic)
|
,mprate :: Quantity -- ^ One unit of the "from" commodity is worth this quantity of the "to" commodity.
|
||||||
-- Show instance derived in Amount.hs
|
} deriving (Eq,Ord,Typeable,Data,Generic)
|
||||||
|
-- Show instance derived in Amount.hs (XXX why ?)
|
||||||
|
|
||||||
instance NFData MarketPrice
|
instance NFData MarketPrice
|
||||||
|
|
||||||
|
-- | A database of the exchange rates between commodity pairs at a given date,
|
||||||
|
-- organised as maps for efficient lookup.
|
||||||
|
data Prices = Prices {
|
||||||
|
prDeclaredPrices ::
|
||||||
|
M.Map CommoditySymbol -- from commodity A
|
||||||
|
(M.Map CommoditySymbol -- to commodity B
|
||||||
|
Quantity) -- exchange rate from A to B (one A is worth this many B)
|
||||||
|
-- ^ Explicitly declared market prices, as { FROMCOMM : { TOCOMM : RATE } }.
|
||||||
|
}
|
||||||
|
|
||||||
-- | What kind of value conversion should be done on amounts ?
|
-- | What kind of value conversion should be done on amounts ?
|
||||||
-- UI: --value=cost|end|now|DATE[,COMM]
|
-- UI: --value=cost|end|now|DATE[,COMM]
|
||||||
data ValuationType =
|
data ValuationType =
|
||||||
@ -465,9 +489,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
|
||||||
,jmarketprices :: [MarketPrice] -- ^ All market price declarations (P directives), in parse order (after journal finalisation).
|
,jpricedirectives :: [PriceDirective] -- ^ All market price declarations (P directives), in parse order (after journal finalisation).
|
||||||
-- These will be converted to a Prices db for looking up prices by date.
|
-- These will be converted to a Prices db for looking up prices by date.
|
||||||
-- (This field is not date-sorted, to allow monoidally combining finalised journals.)
|
|
||||||
,jtxnmodifiers :: [TransactionModifier]
|
,jtxnmodifiers :: [TransactionModifier]
|
||||||
,jperiodictxns :: [PeriodicTransaction]
|
,jperiodictxns :: [PeriodicTransaction]
|
||||||
,jtxns :: [Transaction]
|
,jtxns :: [Transaction]
|
||||||
|
|||||||
@ -43,7 +43,7 @@ module Hledger.Query (
|
|||||||
matchesMixedAmount,
|
matchesMixedAmount,
|
||||||
matchesAmount,
|
matchesAmount,
|
||||||
matchesCommodity,
|
matchesCommodity,
|
||||||
matchesMarketPrice,
|
matchesPriceDirective,
|
||||||
words'',
|
words'',
|
||||||
-- * tests
|
-- * tests
|
||||||
tests_Query
|
tests_Query
|
||||||
@ -639,15 +639,15 @@ matchesTags namepat valuepat = not . null . filter (match namepat valuepat)
|
|||||||
match npat (Just vpat) (n,v) = regexMatchesCI npat (T.unpack n) && regexMatchesCI vpat (T.unpack v)
|
match npat (Just vpat) (n,v) = regexMatchesCI npat (T.unpack n) && regexMatchesCI vpat (T.unpack v)
|
||||||
|
|
||||||
-- | Does the query match this market price ?
|
-- | Does the query match this market price ?
|
||||||
matchesMarketPrice :: Query -> MarketPrice -> Bool
|
matchesPriceDirective :: Query -> PriceDirective -> Bool
|
||||||
matchesMarketPrice (None) _ = False
|
matchesPriceDirective (None) _ = False
|
||||||
matchesMarketPrice (Not q) p = not $ matchesMarketPrice q p
|
matchesPriceDirective (Not q) p = not $ matchesPriceDirective q p
|
||||||
matchesMarketPrice (Or qs) p = any (`matchesMarketPrice` p) qs
|
matchesPriceDirective (Or qs) p = any (`matchesPriceDirective` p) qs
|
||||||
matchesMarketPrice (And qs) p = all (`matchesMarketPrice` p) qs
|
matchesPriceDirective (And qs) p = all (`matchesPriceDirective` p) qs
|
||||||
matchesMarketPrice q@(Amt _ _) p = matchesAmount q (mpamount p)
|
matchesPriceDirective q@(Amt _ _) p = matchesAmount q (pdamount p)
|
||||||
matchesMarketPrice q@(Sym _) p = matchesCommodity q (mpcommodity p)
|
matchesPriceDirective q@(Sym _) p = matchesCommodity q (pdcommodity p)
|
||||||
matchesMarketPrice (Date span) p = spanContainsDate span (mpdate p)
|
matchesPriceDirective (Date span) p = spanContainsDate span (pddate p)
|
||||||
matchesMarketPrice _ _ = True
|
matchesPriceDirective _ _ = True
|
||||||
|
|
||||||
|
|
||||||
-- tests
|
-- tests
|
||||||
|
|||||||
@ -714,7 +714,7 @@ quotedcommoditysymbolp =
|
|||||||
simplecommoditysymbolp :: TextParser m CommoditySymbol
|
simplecommoditysymbolp :: TextParser m CommoditySymbol
|
||||||
simplecommoditysymbolp = takeWhile1P Nothing (not . isNonsimpleCommodityChar)
|
simplecommoditysymbolp = takeWhile1P Nothing (not . isNonsimpleCommodityChar)
|
||||||
|
|
||||||
priceamountp :: JournalParser m Price
|
priceamountp :: JournalParser m AmountPrice
|
||||||
priceamountp = option NoPrice $ do
|
priceamountp = option NoPrice $ do
|
||||||
char '@'
|
char '@'
|
||||||
priceConstructor <- char '@' *> pure TotalPrice <|> pure UnitPrice
|
priceConstructor <- char '@' *> pure TotalPrice <|> pure UnitPrice
|
||||||
|
|||||||
@ -143,7 +143,7 @@ addJournalItemP =
|
|||||||
, transactionp >>= modify' . addTransaction
|
, transactionp >>= modify' . addTransaction
|
||||||
, transactionmodifierp >>= modify' . addTransactionModifier
|
, transactionmodifierp >>= modify' . addTransactionModifier
|
||||||
, periodictransactionp >>= modify' . addPeriodicTransaction
|
, periodictransactionp >>= modify' . addPeriodicTransaction
|
||||||
, marketpricedirectivep >>= modify' . addMarketPrice
|
, marketpricedirectivep >>= modify' . addPriceDirective
|
||||||
, void (lift emptyorcommentlinep)
|
, void (lift emptyorcommentlinep)
|
||||||
, void (lift multilinecommentp)
|
, void (lift multilinecommentp)
|
||||||
] <?> "transaction or directive"
|
] <?> "transaction or directive"
|
||||||
@ -486,7 +486,7 @@ defaultcommoditydirectivep = do
|
|||||||
then customFailure $ parseErrorAt off pleaseincludedecimalpoint
|
then customFailure $ parseErrorAt off pleaseincludedecimalpoint
|
||||||
else setDefaultCommodityAndStyle (acommodity, astyle)
|
else setDefaultCommodityAndStyle (acommodity, astyle)
|
||||||
|
|
||||||
marketpricedirectivep :: JournalParser m MarketPrice
|
marketpricedirectivep :: JournalParser m PriceDirective
|
||||||
marketpricedirectivep = do
|
marketpricedirectivep = do
|
||||||
char 'P' <?> "market price"
|
char 'P' <?> "market price"
|
||||||
lift (skipMany spacenonewline)
|
lift (skipMany spacenonewline)
|
||||||
@ -496,7 +496,7 @@ marketpricedirectivep = do
|
|||||||
lift (skipMany spacenonewline)
|
lift (skipMany spacenonewline)
|
||||||
price <- amountp
|
price <- amountp
|
||||||
lift restofline
|
lift restofline
|
||||||
return $ MarketPrice date symbol price
|
return $ PriceDirective date symbol price
|
||||||
|
|
||||||
ignoredpricecommoditydirectivep :: JournalParser m ()
|
ignoredpricecommoditydirectivep :: JournalParser m ()
|
||||||
ignoredpricecommoditydirectivep = do
|
ignoredpricecommoditydirectivep = do
|
||||||
@ -917,10 +917,10 @@ tests_JournalReader = tests "JournalReader" [
|
|||||||
|
|
||||||
,test "marketpricedirectivep" $ expectParseEq marketpricedirectivep
|
,test "marketpricedirectivep" $ expectParseEq marketpricedirectivep
|
||||||
"P 2017/01/30 BTC $922.83\n"
|
"P 2017/01/30 BTC $922.83\n"
|
||||||
MarketPrice{
|
PriceDirective{
|
||||||
mpdate = fromGregorian 2017 1 30,
|
pddate = fromGregorian 2017 1 30,
|
||||||
mpcommodity = "BTC",
|
pdcommodity = "BTC",
|
||||||
mpamount = usd 922.83
|
pdamount = usd 922.83
|
||||||
}
|
}
|
||||||
|
|
||||||
,test "tagdirectivep" $ do
|
,test "tagdirectivep" $ do
|
||||||
|
|||||||
@ -64,7 +64,7 @@ flatShowsExclusiveBalance = True
|
|||||||
-- This is like PeriodChangeReport with a single column (but more mature,
|
-- This is like PeriodChangeReport with a single column (but more mature,
|
||||||
-- eg this can do hierarchical display).
|
-- eg this can do hierarchical display).
|
||||||
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
|
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
|
||||||
balanceReport ropts@ReportOpts{..} q j =
|
balanceReport ropts@ReportOpts{..} q j@Journal{..} =
|
||||||
(if invert_ then brNegate else id) $
|
(if invert_ then brNegate else id) $
|
||||||
(sorteditems, total)
|
(sorteditems, total)
|
||||||
where
|
where
|
||||||
@ -73,7 +73,6 @@ balanceReport ropts@ReportOpts{..} q j =
|
|||||||
|
|
||||||
today = fromMaybe (error' "balanceReport: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
today = fromMaybe (error' "balanceReport: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
||||||
multiperiod = interval_ /= NoInterval
|
multiperiod = interval_ /= NoInterval
|
||||||
prices = journalPrices j
|
|
||||||
styles = journalCommodityStyles j
|
styles = journalCommodityStyles j
|
||||||
|
|
||||||
-- Get all the summed accounts & balances, according to the query, as an account tree.
|
-- Get all the summed accounts & balances, according to the query, as an account tree.
|
||||||
@ -85,7 +84,7 @@ balanceReport ropts@ReportOpts{..} q j =
|
|||||||
where
|
where
|
||||||
valueaccount a@Account{..} = a{aebalance=val aebalance, aibalance=val aibalance}
|
valueaccount a@Account{..} = a{aebalance=val aebalance, aibalance=val aibalance}
|
||||||
where
|
where
|
||||||
val = maybe id (mixedAmountApplyValuation prices styles periodlastday today multiperiod) value_
|
val = maybe id (mixedAmountApplyValuation jpricedirectives styles periodlastday today multiperiod) value_
|
||||||
where
|
where
|
||||||
periodlastday =
|
periodlastday =
|
||||||
fromMaybe (error' "balanceReport: expected a non-empty journal") $ -- XXX shouldn't happen
|
fromMaybe (error' "balanceReport: expected a non-empty journal") $ -- XXX shouldn't happen
|
||||||
|
|||||||
@ -38,10 +38,9 @@ entriesReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
sortBy (comparing datefn) $ filter (q `matchesTransaction`) $ map tvalue jtxns
|
sortBy (comparing datefn) $ filter (q `matchesTransaction`) $ map tvalue jtxns
|
||||||
where
|
where
|
||||||
datefn = transactionDateFn ropts
|
datefn = transactionDateFn ropts
|
||||||
prices = journalPrices j
|
|
||||||
styles = journalCommodityStyles j
|
styles = journalCommodityStyles j
|
||||||
tvalue t@Transaction{..} = t{tpostings=map pvalue tpostings}
|
tvalue t@Transaction{..} = t{tpostings=map pvalue tpostings}
|
||||||
pvalue p = maybe p (postingApplyValuation prices styles end today False p) value_
|
pvalue p = maybe p (postingApplyValuation jpricedirectives styles end today False p) value_
|
||||||
where
|
where
|
||||||
today = fromMaybe (error' "erValue: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
today = fromMaybe (error' "erValue: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
||||||
end = fromMaybe (postingDate p) mperiodorjournallastday
|
end = fromMaybe (postingDate p) mperiodorjournallastday
|
||||||
|
|||||||
@ -158,7 +158,6 @@ multiBalanceReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
-- date: summed/averaged row amounts
|
-- date: summed/averaged row amounts
|
||||||
today = fromMaybe (error' "postingsReport: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
today = fromMaybe (error' "postingsReport: ReportOpts today_ is unset so could not satisfy --value=now") today_
|
||||||
-- Market prices, commodity display styles.
|
-- Market prices, commodity display styles.
|
||||||
prices = journalPrices j
|
|
||||||
styles = journalCommodityStyles j
|
styles = journalCommodityStyles j
|
||||||
-- The last day of each column subperiod.
|
-- The last day of each column subperiod.
|
||||||
lastdays :: [Day] =
|
lastdays :: [Day] =
|
||||||
@ -274,7 +273,7 @@ multiBalanceReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
CumulativeChange -> drop 1 $ scanl (+) 0 changes
|
CumulativeChange -> drop 1 $ scanl (+) 0 changes
|
||||||
HistoricalBalance -> drop 1 $ scanl (+) (startingBalanceFor a) changes
|
HistoricalBalance -> drop 1 $ scanl (+) (startingBalanceFor a) changes
|
||||||
-- The row amounts valued according to --value if needed.
|
-- The row amounts valued according to --value if needed.
|
||||||
, let val end = maybe id (mixedAmountApplyValuation prices styles end today multiperiod) value_
|
, let val end = maybe id (mixedAmountApplyValuation jpricedirectives styles end today multiperiod) value_
|
||||||
, let valuedrowbals = dbg1 "valuedrowbals" $ [val periodlastday amt | (amt,periodlastday) <- zip rowbals lastdays]
|
, let valuedrowbals = dbg1 "valuedrowbals" $ [val periodlastday amt | (amt,periodlastday) <- zip rowbals lastdays]
|
||||||
-- The total and average for the row, and their values.
|
-- The total and average for the row, and their values.
|
||||||
-- Total for a cumulative/historical report is always zero.
|
-- Total for a cumulative/historical report is always zero.
|
||||||
|
|||||||
@ -73,7 +73,6 @@ postingsReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
reportspan = adjustReportDates ropts q j
|
reportspan = adjustReportDates ropts q j
|
||||||
whichdate = whichDateFromOpts ropts
|
whichdate = whichDateFromOpts ropts
|
||||||
depth = queryDepth q
|
depth = queryDepth q
|
||||||
prices = journalPrices j
|
|
||||||
styles = journalCommodityStyles j
|
styles = journalCommodityStyles j
|
||||||
|
|
||||||
-- postings to be included in the report, and similarly-matched postings before the report start date
|
-- postings to be included in the report, and similarly-matched postings before the report start date
|
||||||
@ -100,7 +99,7 @@ postingsReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
reportPeriodOrJournalLastDay ropts j
|
reportPeriodOrJournalLastDay ropts j
|
||||||
multiperiod = interval_ /= NoInterval
|
multiperiod = interval_ /= NoInterval
|
||||||
showempty = empty_ || average_
|
showempty = empty_ || average_
|
||||||
pvalue p end = maybe p (postingApplyValuation prices styles end today multiperiod p) value_
|
pvalue p end = maybe p (postingApplyValuation jpricedirectives styles end today multiperiod p) value_
|
||||||
|
|
||||||
-- Postings, or summary postings with their subperiod's end date, to be displayed.
|
-- Postings, or summary postings with their subperiod's end date, to be displayed.
|
||||||
displayps :: [(Posting, Maybe Day)]
|
displayps :: [(Posting, Maybe Day)]
|
||||||
@ -122,7 +121,7 @@ postingsReport ropts@ReportOpts{..} q j@Journal{..} =
|
|||||||
-- For --value=end/now/DATE, convert the initial running total/average to value.
|
-- For --value=end/now/DATE, convert the initial running total/average to value.
|
||||||
startbalvalued = val startbal
|
startbalvalued = val startbal
|
||||||
where
|
where
|
||||||
val = maybe id (mixedAmountApplyValuation prices styles daybeforereportstart today multiperiod) value_
|
val = maybe id (mixedAmountApplyValuation jpricedirectives styles daybeforereportstart today multiperiod) value_
|
||||||
where
|
where
|
||||||
daybeforereportstart = maybe
|
daybeforereportstart = maybe
|
||||||
(error' "postingsReport: expected a non-empty journal") -- XXX shouldn't happen
|
(error' "postingsReport: expected a non-empty journal") -- XXX shouldn't happen
|
||||||
|
|||||||
@ -69,7 +69,7 @@ getPricesR = do
|
|||||||
VD{caps, j} <- getViewData
|
VD{caps, j} <- getViewData
|
||||||
when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")
|
when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")
|
||||||
selectRep $ do
|
selectRep $ do
|
||||||
provideJson $ jmarketprices j
|
provideJson $ jpricedirectives j
|
||||||
|
|
||||||
getCommoditiesR :: Handler TypedContent
|
getCommoditiesR :: Handler TypedContent
|
||||||
getCommoditiesR = do
|
getCommoditiesR = do
|
||||||
|
|||||||
@ -51,7 +51,7 @@ instance ToJSON Side
|
|||||||
instance ToJSON DigitGroupStyle
|
instance ToJSON DigitGroupStyle
|
||||||
instance ToJSON MixedAmount
|
instance ToJSON MixedAmount
|
||||||
instance ToJSON BalanceAssertion
|
instance ToJSON BalanceAssertion
|
||||||
instance ToJSON Price
|
instance ToJSON AmountPrice
|
||||||
instance ToJSON MarketPrice
|
instance ToJSON MarketPrice
|
||||||
instance ToJSON PostingType
|
instance ToJSON PostingType
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ instance FromJSON Side
|
|||||||
instance FromJSON DigitGroupStyle
|
instance FromJSON DigitGroupStyle
|
||||||
instance FromJSON MixedAmount
|
instance FromJSON MixedAmount
|
||||||
instance FromJSON BalanceAssertion
|
instance FromJSON BalanceAssertion
|
||||||
instance FromJSON Price
|
instance FromJSON AmountPrice
|
||||||
instance FromJSON MarketPrice
|
instance FromJSON MarketPrice
|
||||||
instance FromJSON PostingType
|
instance FromJSON PostingType
|
||||||
instance FromJSON Posting
|
instance FromJSON Posting
|
||||||
|
|||||||
@ -28,20 +28,20 @@ prices opts j = do
|
|||||||
let
|
let
|
||||||
q = queryFromOpts d (reportopts_ opts)
|
q = queryFromOpts d (reportopts_ opts)
|
||||||
ps = filter (matchesPosting q) $ allPostings j
|
ps = filter (matchesPosting q) $ allPostings j
|
||||||
mprices = jmarketprices j
|
mprices = jpricedirectives j
|
||||||
cprices = concatMap postingCosts ps
|
cprices = concatMap postingCosts ps
|
||||||
icprices = concatMap postingCosts . mapAmount invertPrice $ ps
|
icprices = concatMap postingCosts . mapAmount invertPrice $ ps
|
||||||
allprices = mprices ++ ifBoolOpt "costs" cprices ++ ifBoolOpt "inverted-costs" icprices
|
allprices = mprices ++ ifBoolOpt "costs" cprices ++ ifBoolOpt "inverted-costs" icprices
|
||||||
mapM_ (putStrLn . showPrice) $
|
mapM_ (putStrLn . showPriceDirective) $
|
||||||
sortOn mpdate $
|
sortOn pddate $
|
||||||
filter (matchesMarketPrice q) $
|
filter (matchesPriceDirective q) $
|
||||||
allprices
|
allprices
|
||||||
where
|
where
|
||||||
ifBoolOpt opt | boolopt opt $ rawopts_ opts = id
|
ifBoolOpt opt | boolopt opt $ rawopts_ opts = id
|
||||||
| otherwise = const []
|
| otherwise = const []
|
||||||
|
|
||||||
showPrice :: MarketPrice -> String
|
showPriceDirective :: PriceDirective -> String
|
||||||
showPrice mp = unwords ["P", show $ mpdate mp, T.unpack . quoteCommoditySymbolIfNeeded $ mpcommodity mp, showAmountWithZeroCommodity $ mpamount mp]
|
showPriceDirective mp = unwords ["P", show $ pddate mp, T.unpack . quoteCommoditySymbolIfNeeded $ pdcommodity mp, showAmountWithZeroCommodity $ pdamount mp]
|
||||||
|
|
||||||
divideAmount' :: Quantity -> Amount -> Amount
|
divideAmount' :: Quantity -> Amount -> Amount
|
||||||
divideAmount' n a = a' where
|
divideAmount' n a = a' where
|
||||||
@ -50,7 +50,9 @@ divideAmount' n a = a' where
|
|||||||
extPrecision = (1+) . floor . logBase 10 $ (realToFrac n :: Double)
|
extPrecision = (1+) . floor . logBase 10 $ (realToFrac n :: Double)
|
||||||
precision' = extPrecision + asprecision (astyle a)
|
precision' = extPrecision + asprecision (astyle a)
|
||||||
|
|
||||||
-- | Invert an amount's price for --invert-cost, somehow (? unclear XXX)
|
-- XXX
|
||||||
|
|
||||||
|
-- | Invert an amount's price for --invert-cost, somehow ? Unclear.
|
||||||
invertPrice :: Amount -> Amount
|
invertPrice :: Amount -> Amount
|
||||||
invertPrice a =
|
invertPrice a =
|
||||||
case aprice a of
|
case aprice a of
|
||||||
@ -63,16 +65,16 @@ invertPrice a =
|
|||||||
a { aquantity = aquantity pa * signum (aquantity a), acommodity = acommodity pa, aprice = TotalPrice pa' } where
|
a { aquantity = aquantity pa * signum (aquantity a), acommodity = acommodity pa, aprice = TotalPrice pa' } where
|
||||||
pa' = pa { aquantity = abs $ aquantity a, acommodity = acommodity a, aprice = NoPrice, astyle = astyle a }
|
pa' = pa { aquantity = abs $ aquantity a, acommodity = acommodity a, aprice = NoPrice, astyle = astyle a }
|
||||||
|
|
||||||
amountCost :: Day -> Amount -> Maybe MarketPrice
|
amountCost :: Day -> Amount -> Maybe PriceDirective
|
||||||
amountCost d a =
|
amountCost d a =
|
||||||
case aprice a of
|
case aprice a of
|
||||||
NoPrice -> Nothing
|
NoPrice -> Nothing
|
||||||
UnitPrice pa -> Just
|
UnitPrice pa -> Just
|
||||||
MarketPrice { mpdate = d, mpcommodity = acommodity a, mpamount = pa }
|
PriceDirective { pddate = d, pdcommodity = acommodity a, pdamount = pa }
|
||||||
TotalPrice pa -> Just
|
TotalPrice pa -> Just
|
||||||
MarketPrice { mpdate = d, mpcommodity = acommodity a, mpamount = abs (aquantity a) `divideAmount'` pa }
|
PriceDirective { pddate = d, pdcommodity = acommodity a, pdamount = abs (aquantity a) `divideAmount'` pa }
|
||||||
|
|
||||||
postingCosts :: Posting -> [MarketPrice]
|
postingCosts :: Posting -> [PriceDirective]
|
||||||
postingCosts p = mapMaybe (amountCost date) . amounts $ pamount p where
|
postingCosts p = mapMaybe (amountCost date) . amounts $ pamount p where
|
||||||
date = fromMaybe (tdate . fromJust $ ptransaction p) $ pdate p
|
date = fromMaybe (tdate . fromJust $ ptransaction p) $ pdate p
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user