diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,14 @@
 Internal/api/developer-ish changes in the hledger-lib (and hledger) packages.
 For user-visible changes, see the hledger package changelog.
 
+# 1.32.3 2024-01-28
+
+- Some API renames ended up in this release, including
+
+  - amountStripPrices    -> amountStripCost
+  - showAmountPrice      -> showAmountCostB
+  - showAmountPriceDebug -> showAmountCostDebug
+
 # 1.32.2 2023-12-31
 
 Breaking changes
diff --git a/Hledger/Data/AccountName.hs b/Hledger/Data/AccountName.hs
--- a/Hledger/Data/AccountName.hs
+++ b/Hledger/Data/AccountName.hs
@@ -44,6 +44,7 @@
   ,parentAccountNames
   ,subAccountNamesFrom
   ,topAccountNames
+  ,topAccountName
   ,unbudgetedAccountName
   ,accountNamePostingType
   ,accountNameWithoutPostingType
@@ -67,13 +68,14 @@
 import qualified Data.Set as S
 import Data.Text (Text)
 import qualified Data.Text as T
-import Data.Tree (Tree(..))
+import Data.Tree (Tree(..), unfoldTree)
 import Safe
 import Text.DocLayout (realLength)
 
-import Hledger.Data.Types
+import Hledger.Data.Types hiding (asubs)
 import Hledger.Utils
 import Data.Char (isDigit, isLetter)
+import Data.List (partition)
 
 -- $setup
 -- >>> :set -XOverloadedStrings
@@ -234,6 +236,10 @@
 topAccountNames :: [AccountName] -> [AccountName]
 topAccountNames = filter ((1==) . accountNameLevel) . expandAccountNames
 
+-- | "a:b:c" -> "a"
+topAccountName :: AccountName -> AccountName
+topAccountName = T.takeWhile (/= acctsepchar)
+
 parentAccountName :: AccountName -> AccountName
 parentAccountName = accountNameFromComponents . init . accountNameComponents
 
@@ -249,24 +255,28 @@
 
 isSubAccountNameOf :: AccountName -> AccountName -> Bool
 s `isSubAccountNameOf` p =
-    (p `isAccountNamePrefixOf` s) && (accountNameLevel s == (accountNameLevel p + 1))
+  (p `isAccountNamePrefixOf` s) && (accountNameLevel s == (accountNameLevel p + 1))
 
 -- | From a list of account names, select those which are direct
 -- subaccounts of the given account name.
 subAccountNamesFrom :: [AccountName] -> AccountName -> [AccountName]
 subAccountNamesFrom accts a = filter (`isSubAccountNameOf` a) accts
 
--- | Convert a list of account names to a tree.
+-- | Convert a list of account names to a tree, efficiently.
 accountNameTreeFrom :: [AccountName] -> Tree AccountName
-accountNameTreeFrom accts =
-    Node "root" (accounttreesfrom (topAccountNames accts))
-        where
-          accounttreesfrom :: [AccountName] -> [Tree AccountName]
-          accounttreesfrom [] = []
-          accounttreesfrom as = [Node a (accounttreesfrom $ subs a) | a <- as]
-          subs = subAccountNamesFrom (expandAccountNames accts)
-
---nullaccountnametree = Node "root" []
+accountNameTreeFrom accts = unfoldTree grow ("root", expandAccountNames accts)
+  where
+    -- unfoldTree :: (b -> (a, [b])) -> b -> Tree a
+    -- grow :: (b -> (a, [b]))
+    -- a = AccountName                  - the label at each node of the tree
+    -- b = (AccountName, [AccountName]) - the next node's account, and the accounts remaining to consume under it
+    grow :: ((AccountName, [AccountName]) -> (AccountName, [(AccountName, [AccountName])]))
+    grow (a,[])   = (a,[])
+    grow (a,rest) = (a, [(s, filter (s `isAccountNamePrefixOf`) deepersubs) | s <- asubs])
+      where
+        (asubs, deepersubs) = partition (isChildOf a) rest
+        isChildOf "root" = (1==) . accountNameLevel
+        isChildOf acct   = (`isSubAccountNameOf` acct)
 
 -- | Elide an account name to fit in the specified width.
 -- From the ledger 2.6 news:
diff --git a/Hledger/Data/Amount.hs b/Hledger/Data/Amount.hs
--- a/Hledger/Data/Amount.hs
+++ b/Hledger/Data/Amount.hs
@@ -85,7 +85,8 @@
   csvDisplay,
   showAmountB,
   showAmount,
-  showAmountPrice,
+  showAmountWith,
+  showAmountCostB,
   cshowAmount,
   showAmountWithZeroCommodity,
   showAmountDebug,
@@ -103,7 +104,7 @@
   withInternalPrecision,
   setAmountDecimalPoint,
   withDecimalPoint,
-  amountStripPrices,
+  amountStripCost,
 
   -- * MixedAmount
   nullmixedamt,
@@ -144,6 +145,7 @@
   mixedAmountUnstyled,
   -- ** rendering
   showMixedAmount,
+  showMixedAmountWith,
   showMixedAmountOneLine,
   showMixedAmountDebug,
   showMixedAmountWithoutPrice,
@@ -335,6 +337,10 @@
       Just (UnitPrice  p@Amount{aquantity=pq}) -> p{aquantity=pq * q}
       Just (TotalPrice p@Amount{aquantity=pq}) -> p{aquantity=pq}
 
+-- | Strip all prices from an Amount
+amountStripCost :: Amount -> Amount
+amountStripCost a = a{aprice=Nothing}
+
 -- | Apply a function to an amount's quantity (and its total price, if it has one).
 transformAmount :: (Quantity -> Quantity) -> Amount -> Amount
 transformAmount f a@Amount{aquantity=q,aprice=p} = a{aquantity=f q, aprice=f' <$> p}
@@ -613,21 +619,17 @@
 
 -- Amount rendering
 
--- | Strip all prices from an Amount
-amountStripPrices :: Amount -> Amount
-amountStripPrices a = a{aprice=Nothing}
-
-showAmountPrice :: Amount -> WideBuilder
-showAmountPrice amt = case aprice amt of
+showAmountCostB :: Amount -> WideBuilder
+showAmountCostB amt = case aprice amt of
     Nothing              -> mempty
     Just (UnitPrice  pa) -> WideBuilder (TB.fromString " @ ")  3 <> showAmountB noColour{displayZeroCommodity=True} pa
     Just (TotalPrice pa) -> WideBuilder (TB.fromString " @@ ") 4 <> showAmountB noColour{displayZeroCommodity=True} (sign pa)
   where sign = if aquantity amt < 0 then negate else id
 
-showAmountPriceDebug :: Maybe AmountPrice -> String
-showAmountPriceDebug Nothing                = ""
-showAmountPriceDebug (Just (UnitPrice pa))  = " @ "  ++ showAmountDebug pa
-showAmountPriceDebug (Just (TotalPrice pa)) = " @@ " ++ showAmountDebug pa
+showAmountCostDebug :: Maybe AmountPrice -> String
+showAmountCostDebug Nothing                = ""
+showAmountCostDebug (Just (UnitPrice pa))  = " @ "  ++ showAmountDebug pa
+showAmountCostDebug (Just (TotalPrice pa)) = " @@ " ++ showAmountDebug pa
 
 -- | Get the string representation of an amount, based on its
 -- commodity's display settings. String representations equivalent to
@@ -638,6 +640,10 @@
 showAmount :: Amount -> String
 showAmount = wbUnpack . showAmountB noColour
 
+-- | Like showAmount but uses the given amount display options.
+showAmountWith :: AmountDisplayOpts -> Amount -> String
+showAmountWith fmt = wbUnpack . showAmountB fmt
+
 -- | General function to generate a WideBuilder for an Amount, according the
 -- supplied AmountDisplayOpts. This is the main function to use for showing
 -- Amounts, constructing a builder; it can then be converted to a Text with
@@ -667,7 +673,7 @@
       | amountLooksZero a && not displayZeroCommodity = (WideBuilder (TB.singleton '0') 1, "")
       | otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a)
     space = if not (T.null comm) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty
-    price = if displayCost then showAmountPrice a else mempty
+    price = if displayCost then showAmountCostB a else mempty
 
 -- | Colour version. For a negative amount, adds ANSI codes to change the colour,
 -- currently to hard-coded red.
@@ -694,7 +700,7 @@
 showAmountDebug Amount{acommodity="AUTO"} = "(missing)"
 showAmountDebug Amount{..} =
       "Amount {acommodity=" ++ show acommodity ++ ", aquantity=" ++ show aquantity
-   ++ ", aprice=" ++ showAmountPriceDebug aprice ++ ", astyle=" ++ show astyle ++ "}"
+   ++ ", aprice=" ++ showAmountCostDebug aprice ++ ", astyle=" ++ show astyle ++ "}"
 
 -- | Get a Text Builder for the string representation of the number part of of an amount,
 -- using the display settings from its commodity. Also returns the width of the number.
@@ -1036,6 +1042,11 @@
 -- > showMixedAmount = wbUnpack . showMixedAmountB noColour
 showMixedAmount :: MixedAmount -> String
 showMixedAmount = wbUnpack . showMixedAmountB noColour
+
+-- | Like showMixedAmount but uses the given amount display options.
+-- See showMixedAmountB for special cases.
+showMixedAmountWith :: AmountDisplayOpts -> MixedAmount -> String
+showMixedAmountWith fmt = wbUnpack . showMixedAmountB fmt
 
 -- | Get the one-line string representation of a mixed amount (also showing any costs).
 --
diff --git a/Hledger/Data/Balancing.hs b/Hledger/Data/Balancing.hs
--- a/Hledger/Data/Balancing.hs
+++ b/Hledger/Data/Balancing.hs
@@ -46,7 +46,6 @@
 import qualified Data.Text as T
 import Data.Time.Calendar (fromGregorian)
 import qualified Data.Map as M
-import Safe (headDef)
 import Text.Printf (printf)
 
 import Hledger.Utils
@@ -527,6 +526,8 @@
         ts' <- lift $ getElems balancedtxns
         return j{jtxns=ts'}
 
+-- Before #2039: "Costs are removed, which helps eg balance-assertions.test: 15. Mix different commodities and assignments."
+
 -- | This function is called statefully on each of a date-ordered sequence of
 -- 1. fully explicit postings from already-balanced transactions and
 -- 2. not-yet-balanced transactions containing balance assignments.
@@ -534,7 +535,6 @@
 -- and checks balance assertions on each posting as it goes.
 -- An error will be thrown if a transaction can't be balanced
 -- or if an illegal balance assignment is found (cf checkIllegalBalanceAssignment).
--- Transaction prices are removed, which helps eg balance-assertions.test: 15. Mix different commodities and assignments.
 -- This stores the balanced transactions in case 2 but not in case 1.
 balanceTransactionAndCheckAssertionsB :: Either Posting Transaction -> Balancing s ()
 balanceTransactionAndCheckAssertionsB (Left p@Posting{}) =
@@ -551,7 +551,7 @@
   ps' <- ps
     & zip [1..]                 -- attach original positions
     & sortOn (postingDate.snd)  -- sort by date
-    & mapM (addOrAssignAmountAndCheckAssertionB)  -- infer amount, check assertion on each one
+    & mapM addOrAssignAmountAndCheckAssertionB  -- infer amount, check assertion on each one
     <&> sortOn fst              -- restore original order
     <&> map snd                 -- discard positions
 
@@ -615,7 +615,7 @@
 -- are ignored; if it is total, they will cause the assertion to fail.
 checkBalanceAssertionB :: Posting -> MixedAmount -> Balancing s ()
 checkBalanceAssertionB p@Posting{pbalanceassertion=Just (BalanceAssertion{baamount,batotal})} actualbal =
-    forM_ (baamount : otheramts) $ \amt -> checkBalanceAssertionOneCommodityB p amt actualbal
+  forM_ (baamount : otheramts) $ \amt -> checkBalanceAssertionOneCommodityB p amt actualbal
   where
     assertedcomm = acommodity baamount
     otheramts | batotal   = map (\a -> a{aquantity=0}) . amountsRaw
@@ -624,15 +624,17 @@
 checkBalanceAssertionB _ _ = return ()
 
 -- | Does this (single commodity) expected balance match the amount of that
--- commodity in the given (multicommodity) actual balance ? If not, returns a
--- balance assertion failure message based on the provided posting.  To match,
--- the amounts must be exactly equal (display precision is ignored here).
+-- commodity in the given (multicommodity) actual balance, ignoring costs ?
+-- If not, returns a balance assertion failure message based on the provided posting.
+-- To match, the amounts must be exactly equal (display precision is ignored here).
 -- If the assertion is inclusive, the expected amount is compared with the account's
 -- subaccount-inclusive balance; otherwise, with the subaccount-exclusive balance.
 checkBalanceAssertionOneCommodityB :: Posting -> Amount -> MixedAmount -> Balancing s ()
-checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedamt actualbal = do
+checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcommbal actualbal = do
   let isinclusive = maybe False bainclusive $ pbalanceassertion p
   let istotal     = maybe False batotal     $ pbalanceassertion p
+  -- mstyles <- R.reader bsStyles
+  -- let styled = maybe id styleAmounts mstyles
   actualbal' <-
     if isinclusive
     then
@@ -645,49 +647,84 @@
           bsBalances
     else return actualbal
   let
-    assertedcomm    = acommodity assertedamt
-    actualbalincomm = headDef nullamt . amountsRaw . filterMixedAmountByCommodity assertedcomm $ actualbal'
+    assertedcomm = acommodity assertedcommbal
+
+    -- The asserted single-commodity balance, without cost
+    assertedcommbalcostless = amountStripCost assertedcommbal
+
+    -- The balance in this commodity, from the current multi-commodity running balance at this point.
+    -- This is unnormalised, and could include one or more different costs.
+    actualcommbal           = filterMixedAmountByCommodity assertedcomm $ actualbal'
+
+    -- The above balance without costs, as a single Amount (Amount's + discards costs).
+    actualcommbalcostless   = sum $ amountsRaw actualcommbal
+
+    -- test the assertion
     pass =
-      aquantity
-        -- traceWith (("asserted:"++).showAmountDebug)
-        assertedamt ==
-      aquantity
-        -- traceWith (("actual:"++).showAmountDebug)
-        actualbalincomm
+      aquantity assertedcommbalcostless
+      ==
+      aquantity actualcommbalcostless
+
     errmsg = chomp $ printf (unlines
       [ "%s:",
         "%s\n",
-        "This balance assertion failed.",
-        -- "date:       %s",
-        "In account:    %s",
-        "and commodity: %s",
-        -- "display precision:  %d",
-        "this balance was asserted:     %s", -- (at display precision: %s)",
-        "but the calculated balance is: %s", -- (at display precision: %s)",
-        "",
-        "To troubleshoot, you can view this account's running balance. Eg:",
-        "",
-        "hledger reg '%s'%s -I  # -f FILE"
+        "Balance assertion failed in %s",
+        "%s at this point, %s, ignoring costs,",
+        "the expected balance is:        %s",
+        "but the calculated balance is:  %s",
+        "(difference: %s)",
+        "To troubleshoot, check this account's running balance with assertions disabled, eg:",
+        "hledger reg -I '%s'%s"
       ])
-      (sourcePosPretty pos)
-      (textChomp ex)
-      -- (showDate $ postingDate p)
-      (if isinclusive then printf "%-30s  (including subaccounts)" acct else acct)
-      (if istotal     then printf "%-30s  (no other commodities allowed)" (T.unpack assertedcomm) else (T.unpack assertedcomm))
-      -- (asprecision $ astyle actualbalincommodity)  -- should be the standard display precision I think
-      (show $ aquantity assertedamt)
-      -- (showAmount assertedamt)
-      (show $ aquantity actualbalincomm)
-      -- (showAmount actualbalincommodity)
-      -- (show $ aquantity assertedamt - aquantity actualbalincomm)
-      (acct ++ if isinclusive then "" else "$")
-      (if istotal then "" else (" cur:" ++ quoteForCommandLine (T.unpack assertedcomm)))
+
+      (sourcePosPretty pos)  -- position
+      (textChomp ex)  -- journal excerpt
+      acct  -- asserted account
+      (if istotal then "Across all commodities" else "In commodity " <> assertedcommstr)  -- asserted commodity or all commodities ?
+      (if isinclusive then "including subaccounts" else "excluding subaccounts" :: String)  -- inclusive or exclusive balance asserted ?
+      (pad assertedstr)  -- asserted amount, without cost
+      (pad actualstr)    -- actual amount, without cost
+        -- <> " (with costs: " <> T.pack (showMixedAmountWith fmt actualcommbal) <> ")" -- debugging
+      diffstr  -- their difference
+      (acct ++ if isinclusive then "" else "$")  -- query matching the account(s) postings
+      (if istotal then "" else (" cur:" ++ quoteForCommandLine (T.unpack assertedcomm)))  -- query matching the commodity(ies)
+
       where
         acct = T.unpack $ paccount p
         ass = fromJust $ pbalanceassertion p  -- PARTIAL: fromJust won't fail, there is a balance assertion
         pos = baposition ass
         (_,_,_,ex) = makeBalanceAssertionErrorExcerpt p
+        assertedcommstr = if T.null assertedcomm then "\"\"" else assertedcomm
+        fmt = oneLine{displayZeroCommodity=True}
+        assertedstr = showAmountWith fmt assertedcommbalcostless
+        actualstr   = showAmountWith fmt actualcommbalcostless
+        diffstr     = showAmountWith fmt $ assertedcommbalcostless - actualcommbalcostless
+        pad = fitText (Just w) Nothing False False . T.pack where w = max (length assertedstr) (length actualstr)
+
+
   unless pass $ throwError errmsg
+{- XXX
+When the posting amount has a cost, the highlight region expands to the full line:
+
+*** Exception: Error: /Users/simon/src/hledger/2024-01-21.j:12:69:
+   | 2023-12-31 closing balances
+12 |     assets:cash:petty:saved:rent       -4.00 EUR @ 2 UAH == 0.00 EUR
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |     equity:opening/closing balances                8 UAH
+
+Maybe it's better than the normal region ?
+
+*** Exception: Error: /Users/simon/src/hledger/2024-01-21.j:12:61:
+   | 2023-12-31 closing balances
+12 |     assets:cash:petty:saved:rent          -4.00 EUR == 0.00 EUR @ 3 UAH
+   |                                                     ^^^^^^^^^^^^^^^^^^^
+   |     equity:opening/closing balances        4.00 EUR
+
+If changed also check flycheck-hledger, which currently highlights the equals:
+
+    assets:cash:petty:saved:rent                  -4.00 EUR @ 2 UAH == 0.00 EUR @ 3 UAH 
+                                                                    --
+-}
 
 -- | Throw an error if this posting is trying to do an illegal balance assignment.
 checkIllegalBalanceAssignmentB :: Posting -> Balancing s ()
diff --git a/Hledger/Data/Journal.hs b/Hledger/Data/Journal.hs
--- a/Hledger/Data/Journal.hs
+++ b/Hledger/Data/Journal.hs
@@ -96,6 +96,7 @@
   journalPostingsAddAccountTags,
   -- journalPrices,
   journalConversionAccount,
+  journalConversionAccounts,
   -- * Misc
   canonicalStyleFrom,
   nulljournal,
@@ -519,13 +520,6 @@
 letterPairs (a:b:rest) = [a,b] : letterPairs (b:rest)
 letterPairs _ = []
 
--- | The 'AccountName' to use for automatically generated conversion postings.
-journalConversionAccount :: Journal -> AccountName
-journalConversionAccount =
-    headDef (T.pack "equity:conversion")
-    . M.findWithDefault [] Conversion
-    . jdeclaredaccounttypes
-
 -- Newer account type code.
 
 journalAccountType :: Journal -> AccountName -> Maybe AccountType
@@ -557,7 +551,7 @@
                             then mparenttype
                             else (,False) <$> accountNameInferType a <|> mparenttype
 
--- | Build a map of the account types explicitly declared.
+-- | Build a map of the account types explicitly declared for each account.
 journalDeclaredAccountTypes :: Journal -> M.Map AccountName AccountType
 journalDeclaredAccountTypes Journal{jdeclaredaccounttypes} =
   M.fromList $ concat [map (,t) as | (t,as) <- M.toList jdeclaredaccounttypes]
@@ -569,6 +563,19 @@
 journalPostingsAddAccountTags j = journalMapPostings addtags j
   where addtags p = p `postingAddTags` (journalInheritedAccountTags j $ paccount p)
 
+-- | The account to use for automatically generated conversion postings in this journal:
+-- the first of the journalConversionAccounts.
+journalConversionAccount :: Journal -> AccountName
+journalConversionAccount = headDef defaultConversionAccount . journalConversionAccounts
+
+-- | All the accounts declared or inferred as Conversion type in this journal.
+journalConversionAccounts :: Journal -> [AccountName]
+journalConversionAccounts = M.keys . M.filter (==Conversion) . jaccounttypes
+
+-- The fallback account to use for automatically generated conversion postings
+-- if no account is declared with the Conversion type.
+defaultConversionAccount = "equity:conversion"
+
 -- Various kinds of filtering on journals. We do it differently depending
 -- on the command.
 
@@ -943,9 +950,11 @@
 -- See hledger manual > Cost reporting.
 journalInferCostsFromEquity :: Journal -> Either String Journal
 journalInferCostsFromEquity j = do
-    ts <- mapM (transactionInferCostsFromEquity False $ jaccounttypes j) $ jtxns j
-    return j{jtxns=ts}
+  ts <- mapM (transactionInferCostsFromEquity False conversionaccts) $ jtxns j
+  return j{jtxns=ts}
+  where conversionaccts = journalConversionAccounts j
 
+-- XXX duplication of the above
 -- | Do just the internal tagging that is normally done by journalInferCostsFromEquity,
 -- identifying equity conversion postings and, in particular, postings which have redundant costs.
 -- Tagging the latter is useful as it allows them to be ignored during transaction balancedness checking.
@@ -953,8 +962,9 @@
 -- when it will have more information (amounts) to work with.
 journalMarkRedundantCosts :: Journal -> Either String Journal
 journalMarkRedundantCosts j = do
-    ts <- mapM (transactionInferCostsFromEquity True $ jaccounttypes j) $ jtxns j
-    return j{jtxns=ts}
+  ts <- mapM (transactionInferCostsFromEquity True conversionaccts) $ jtxns j
+  return j{jtxns=ts}
+  where conversionaccts = journalConversionAccounts j
 
 -- -- | Get this journal's unique, display-preference-canonicalised commodities, by symbol.
 -- journalCanonicalCommodities :: Journal -> M.Map String CommoditySymbol
diff --git a/Hledger/Data/JournalChecks.hs b/Hledger/Data/JournalChecks.hs
--- a/Hledger/Data/JournalChecks.hs
+++ b/Hledger/Data/JournalChecks.hs
@@ -213,11 +213,12 @@
 -- | In each tranaction, check that any conversion postings occur in adjacent pairs.
 journalCheckPairedConversionPostings :: Journal -> Either String ()
 journalCheckPairedConversionPostings j =
-  mapM_ (transactionCheckPairedConversionPostings (jaccounttypes j)) $ jtxns j
+  mapM_ (transactionCheckPairedConversionPostings conversionaccts) $ jtxns j
+  where conversionaccts = journalConversionAccounts j
 
-transactionCheckPairedConversionPostings :: M.Map AccountName AccountType -> Transaction -> Either String ()
-transactionCheckPairedConversionPostings accttypes t =
-  case partitionAndCheckConversionPostings True accttypes (zip [0..] $ tpostings t) of
+transactionCheckPairedConversionPostings :: [AccountName] -> Transaction -> Either String ()
+transactionCheckPairedConversionPostings conversionaccts t =
+  case partitionAndCheckConversionPostings True conversionaccts (zip [0..] $ tpostings t) of
     Left err -> Left $ T.unpack err
     Right _  -> Right ()
 
diff --git a/Hledger/Data/Posting.hs b/Hledger/Data/Posting.hs
--- a/Hledger/Data/Posting.hs
+++ b/Hledger/Data/Posting.hs
@@ -559,7 +559,7 @@
     conversionPostings amt = case aprice amt of
         Nothing -> []
         Just _  -> [ cp{ paccount = accountPrefix <> amtCommodity
-                       , pamount = mixedAmount . negate $ amountStripPrices amt
+                       , pamount = mixedAmount . negate $ amountStripCost amt
                        }
                    , cp{ paccount = accountPrefix <> costCommodity
                        , pamount = mixedAmount cost
diff --git a/Hledger/Data/Transaction.hs b/Hledger/Data/Transaction.hs
--- a/Hledger/Data/Transaction.hs
+++ b/Hledger/Data/Transaction.hs
@@ -274,6 +274,7 @@
 label s = ((s <> ": ")++)
 
 -- | Add costs inferred from equity postings in this transaction.
+-- The name(s) of conversion equity accounts should be provided.
 -- For every adjacent pair of conversion postings, it will first search the postings
 -- with costs to see if any match. If so, it will tag these as matched.
 -- If no postings with costs match, it will then search the postings without costs,
@@ -281,13 +282,13 @@
 -- If it finds a match, it will add a cost and then tag it.
 -- If the first argument is true, do a dry run instead: identify and tag
 -- the costful and conversion postings, but don't add costs.
-transactionInferCostsFromEquity :: Bool -> M.Map AccountName AccountType -> Transaction -> Either String Transaction
-transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTransaction t . T.unpack) $ do
+transactionInferCostsFromEquity :: Bool -> [AccountName] -> Transaction -> Either String Transaction
+transactionInferCostsFromEquity dryrun conversionaccts t = first (annotateErrorWithTransaction t . T.unpack) $ do
   -- number the postings
   let npostings = zip [0..] $ tpostings t
 
   -- Identify all pairs of conversion postings and all other postings (with and without costs) in the transaction.
-  (conversionPairs, otherps) <- partitionAndCheckConversionPostings False acctTypes npostings
+  (conversionPairs, otherps) <- partitionAndCheckConversionPostings False conversionaccts npostings
 
   -- Generate a pure function that can be applied to each of this transaction's postings,
   -- possibly modifying it, to produce the following end result:
@@ -434,13 +435,13 @@
     Precision n      -> show n
     NaturalPrecision -> show $ decimalPlaces $ normalizeDecimal $ aquantity a
 
--- Using the provided account types map, sort the given indexed postings
+-- Given the names of conversion equity accounts, sort the given indexed postings
 -- into three lists of posting numbers (stored in two pairs), like so:
 -- (conversion postings, (costful other postings, costless other postings)).
 -- A true first argument activates its secondary function: check that all
 -- conversion postings occur in adjacent pairs, otherwise return an error.
-partitionAndCheckConversionPostings :: Bool -> M.Map AccountName AccountType -> [IdxPosting] -> Either Text ( [(IdxPosting, IdxPosting)], ([IdxPosting], [IdxPosting]) )
-partitionAndCheckConversionPostings check acctTypes =
+partitionAndCheckConversionPostings :: Bool -> [AccountName] -> [IdxPosting] -> Either Text ( [(IdxPosting, IdxPosting)], ([IdxPosting], [IdxPosting]) )
+partitionAndCheckConversionPostings check conversionaccts =
   -- Left fold processes postings in parse order, so that eg inferred costs
   -- will be added to the first (top-most) posting, not the last one.
   foldlM select (([], ([], [])), Nothing)
@@ -455,7 +456,7 @@
       | isConversion p = Right (((lst, np):cs, others), Nothing)
       | check          = Left "Conversion postings must occur in adjacent pairs"
       | otherwise      = Right ((cs, (ps, np:os)), Nothing)
-    isConversion p = M.lookup (paccount p) acctTypes == Just Conversion
+    isConversion p = paccount p `elem` conversionaccts
     hasCost p = isJust $ aprice =<< postingSingleAmount p
 
 -- | Get a posting's amount if it is single-commodity.
diff --git a/Hledger/Read/Common.hs b/Hledger/Read/Common.hs
--- a/Hledger/Read/Common.hs
+++ b/Hledger/Read/Common.hs
@@ -213,7 +213,7 @@
        mformat_           = Nothing
       ,mrules_file_       = maybestringopt "rules-file" rawopts
       ,aliases_           = listofstringopt "alias" rawopts
-      ,anon_              = boolopt "anon" rawopts
+      ,anon_              = boolopt "obfuscate" rawopts
       ,new_               = boolopt "new" rawopts
       ,new_save_          = True
       ,pivot_             = stringopt "pivot" rawopts
@@ -332,7 +332,9 @@
       &   journalStyleAmounts                            -- Infer and apply commodity styles (but don't round) - should be done early
       <&> journalAddForecast (verbose_tags_) (forecastPeriod iopts pj)   -- Add forecast transactions if enabled
       <&> journalPostingsAddAccountTags                  -- Add account tags to postings, so they can be matched by auto postings.
-      >>= journalMarkRedundantCosts                      -- Mark redundant costs, to help journalBalanceTransactions ignore them
+      >>= journalMarkRedundantCosts                      -- Mark redundant costs, to help journalBalanceTransactions ignore them.
+                                                         -- (Later, journalInferEquityFromCosts will do a similar pass, adding missing equity postings.)
+
       >>= (if auto_ && not (null $ jtxnmodifiers pj)
             then journalAddAutoPostings verbose_tags_ _ioDay balancingopts_  -- Add auto postings if enabled, and account tags if needed. Does preliminary transaction balancing.
             else pure)
diff --git a/Hledger/Read/InputOptions.hs b/Hledger/Read/InputOptions.hs
--- a/Hledger/Read/InputOptions.hs
+++ b/Hledger/Read/InputOptions.hs
@@ -29,7 +29,7 @@
                                                 --   by a filename prefix. Nothing means try all.
     ,mrules_file_       :: Maybe FilePath       -- ^ a conversion rules file to use (when reading CSV)
     ,aliases_           :: [String]             -- ^ account name aliases to apply
-    ,anon_              :: Bool                 -- ^ do light anonymisation/obfuscation of the data
+    ,anon_              :: Bool                 -- ^ do light obfuscation of the data. Now corresponds to --obfuscate, not the old --anon flag.
     ,new_               :: Bool                 -- ^ read only new transactions since this file was last read
     ,new_save_          :: Bool                 -- ^ save latest new transactions state for next time
     ,pivot_             :: String               -- ^ use the given field's value as the account name
diff --git a/hledger-lib.cabal b/hledger-lib.cabal
--- a/hledger-lib.cabal
+++ b/hledger-lib.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hledger-lib
-version:        1.32.2
+version:        1.32.3
 synopsis:       A library providing the core functionality of hledger
 description:    This library contains hledger's core functionality.
                 It is used by most hledger* packages so that they support the same
