diff --git a/beancount/core/data.py b/beancount/core/data.py index c6532ce81..d041fb81e 100644 --- a/beancount/core/data.py +++ b/beancount/core/data.py @@ -59,13 +59,6 @@ class Booking(enum.Enum): # Highest-in first-out in the case of ambiguity. HIFO = "HIFO" - - # Least tax first out, with transfer awareness - LTFO = 'LTFO' - - # HIFO with transfer awareness - HIFO_XFER_AWARE = 'HIFO_XFER_AWARE' - # Extension point for Magicbeans. MAGICBEANS = 'MAGICBEANS' diff --git a/beancount/parser/booking_full_test.py b/beancount/parser/booking_full_test.py index f67e1de0f..94723ad15 100644 --- a/beancount/parser/booking_full_test.py +++ b/beancount/parser/booking_full_test.py @@ -1980,31 +1980,6 @@ def test_reduce__multiple_reductions_hifo(self, _, __): Assets:Account 45 HOOL {114.00 USD, 2016-01-17} """ - @book_test(Booking.LTFO) - def test_reduce__multiple_reductions_ltfo(self, _, __): - """ - 2016-01-01 * #ante - ;; Sell LT @ 140 USD = 60 USD * 20% = 12 USD tax ea - Assets:Account 50 HOOL {80.00 USD, 2016-01-01} - ;; Sell LT @ 140 USD = 20 USD * 20% = 4 USD tax ea - Assets:Account 50 HOOL {120.00 USD, 2016-01-01} - ;; Sell ST @ 140 USD = 25 USD * 40% = 10 USD tax ea - Assets:Account 50 HOOL {115.00 USD, 2017-01-01} - - 2017-03-01 * #apply - Assets:Account -40 HOOL {} @ 140.00 USD - Assets:Account -40 HOOL {} @ 140.00 USD - - 2017-03-01 * #booked - Assets:Account -40 HOOL {120.00 USD, 2016-01-01} @ 140.00 USD - Assets:Account -10 HOOL {120.00 USD, 2016-01-01} @ 140.00 USD - Assets:Account -30 HOOL {115.00 USD, 2017-01-01} @ 140.00 USD - - 2016-01-01 * #ex - Assets:Account 50 HOOL {80.00 USD, 2016-01-01} - Assets:Account 20 HOOL {115.00 USD, 2017-01-01} - """ - @book_test(Booking.STRICT) def test_reduce__multiple_reductions__competing__with_error(self, _, __): """ diff --git a/beancount/parser/booking_method.py b/beancount/parser/booking_method.py index a5d93f4d7..06a7cc024 100644 --- a/beancount/parser/booking_method.py +++ b/beancount/parser/booking_method.py @@ -158,79 +158,6 @@ def booking_method_HIFO(entry, posting, matches): lambda m: m.cost and getattr(m.cost, "number"), reverse_order=True) -def booking_method_LowIFO(entry, posting, matches): - """LowIFO booking method implementation. Used internally in obscure cases - for transfers, see LTFO""" - return _booking_method_xifo(entry, posting, matches, - lambda m: m.cost and getattr(m.cost, "number"), - reverse_order=False) - - -def booking_method_LTFO(entry, posting, matches): - """LTFO (least tax first out, ish) booking method implementation. - - This will attempt to minimize the tax liability of the sale by considering - both gain/loss and the long/short term holding period (including preferring - losses over gains, i.e. tax loss harvesting). It will also use some - heuristics on transfers. (TODO: What heuristics?) - - This is a bit special cased for crypto. - """ - # US tax rules - lt_rate = Decimal("0.2") - st_rate = Decimal("0.4") - def lt_thresh(match: Position): - return match.cost.date.replace(year=match.cost.date.year + 1) - - # If we have a price on the posting, then we can compute the tax liability - # for each match and select the one with the lowest tax liability. - - if posting.price: - def us_tax_liability(match: Position): - """Compute the US tax liability (per unit) of a given match.""" - gain_per_unit = posting.price.number - match.cost.number - lt_threshold = lt_thresh(match) - tax_rate = lt_rate if entry.date >= lt_threshold else st_rate - return gain_per_unit * tax_rate - - return _booking_method_xifo(entry, posting, matches, - us_tax_liability, - reverse_order=False) - - # If there's no price, it's probably a transfer rather than a sale. The - # posting we get is the reduction (donating account). - # - # If the posting is on a wallet account, it's probably a transfer to a - # trading account in preparation for sale, so we would want to pick the - # lot with minimal tax liability. - # - # If the posting is on a trading account, then it's probably a transfer to a - # wallet and we want the inverse -- to stash away the lots least desirable - # to sell. - # - # Now, how do we estimate the tax liability without a price? Well, if - # all matches have the same short/long term status, we can just use the - # cost basis (highest, or lowest, depending on the account). - - sale_prep = "Wallet" in posting.account # Total hack - - def is_lt(match: Position): - return entry.date > lt_thresh(match) - - if all(is_lt(m) for m in matches) or all(not is_lt(m) for m in matches): - if sale_prep: - return booking_method_HIFO(entry, posting, matches) - else: - return booking_method_LowIFO(entry, posting, matches) - - # OK, this is the hard case, it's a transfer, we don't know the price, - # and we have a mix of long and short term lots. For now, do the same - # as when there's no short/long mix. TODO: be smarter? - if sale_prep: - return booking_method_HIFO(entry, posting, matches) - else: - return booking_method_LowIFO(entry, posting, matches) - def _booking_method_xifo(entry, posting, matches, key, reverse_order): """FIFO and LIFO booking method implementations.""" booked_reductions = [] @@ -405,8 +332,6 @@ def booking_method_magicbeans_stub(entry, posting, matches): Booking.FIFO: booking_method_FIFO, Booking.LIFO: booking_method_LIFO, Booking.HIFO: booking_method_HIFO, - Booking.LTFO: booking_method_LTFO, - Booking.HIFO_XFER_AWARE: booking_method_HIFO_XFER_AWARE, Booking.NONE: booking_method_NONE, Booking.AVERAGE: booking_method_AVERAGE, Booking.MAGICBEANS: booking_method_magicbeans_stub, # Should be redefined by Magicbeans package