New issue 184: Inventory averaging with can results in division by zero https://bitbucket.org/blais/beancount/issues/184/inventory-averaging-with-can-results-in
Stephan Müller: Not 100% sure whether this is a bug (or just an incoherent inventory), but in my mind, averaging should still work even if the inventory is atypical. Alternatively, one could assert that `total_units != ZERO` and show a meaningful message otherwise. With this preamble: if there are offsetting amounts of the same currency with different costs (eg a different date), the division in `cost = Cost(total_cost / total_units, cost_currency, None, None)` in the method [`Inventory.average`](https://bitbucket.org/blais/beancount/src/cbb3f348ec5e23f4d224f2b2f7a5811958d87873/beancount/core/inventory.py?at=default&fileviewer=file-view-default#inventory.py-302) (module `beancount.core.inventory`) results in a division by zero. The following extension to [`TestInventory.test_average`](https://bitbucket.org/blais/beancount/src/cbb3f348ec5e23f4d224f2b2f7a5811958d87873/beancount/core/inventory_test.py?at=default&fileviewer=file-view-default#inventory_test.py-278) shows the problem (last test fails because the +/- 2 HOOL have different costs, so don't offset in the inventory but the overall position is 0): def test_average(self): # Identity, no aggregation. inv = I('40.50 JPY, 40.51 USD {1.01 CAD}, 40.52 CAD') self.assertEqual(inv.average(), inv) # Identity, no aggregation, with a mix of lots at cost and without # cost. inv = I('40 USD {1.01 CAD}, 40 USD') self.assertEqual(inv.average(), inv) # Aggregation. inv = I('40 USD {1.01 CAD}, 40 USD {1.02 CAD}') self.assertEqual(inv.average(), I('80.00 USD {1.015 CAD}')) # Aggregation, more units. inv = I('2 HOOL {500 USD}, 3 HOOL {520 USD}, 4 HOOL {530 USD}') self.assertEqual(inv.average(), I('9 HOOL {520 USD}')) # ADDED-------------------------------------------------------- # Average on zero amount, same costs inv = I('2 HOOL {500 USD}') inv.add_amount(A('-2 HOOL'), Cost(D('500'), 'USD', None, None)) self.assertEqual(inv.average(), I('')) # Average on zero amount, different costs inv = I('2 HOOL {500 USD}') inv.add_amount(A('-2 HOOL'), Cost(D('500'), 'USD', datetime.date(2000, 1, 1), None)) self.assertEqual(inv.average(), I('')) A simple fix would be to add the line `if total_units == ZERO: continue` in the method `Inventory.average` as per below: def average(self): """Average all lots of the same currency together. Returns: An instance of Inventory. """ groups = collections.defaultdict(list) for position in self: key = (position.units.currency, position.cost.currency if position.cost else None) groups[key].append(position) average_inventory = Inventory() for (currency, cost_currency), positions in groups.items(): total_units = sum(position.units.number for position in positions) if total_units == ZERO: continue # ADDED----------------------------------- units_amount = Amount(total_units, currency) if cost_currency: total_cost = sum(convert.get_cost(position).number for position in positions) cost = Cost(total_cost / total_units, cost_currency, None, None) else: cost = None average_inventory.add_amount(units_amount, cost) return average_inventory -- You received this message because you are subscribed to the Google Groups "Beancount" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/beancount/20170803170057.1471.25351%40celery-worker-106.ash1.bb-inf.net. For more options, visit https://groups.google.com/d/optout.
