def test_commonprefix(self): self.assertEqual('Assets:US:TD', account.commonprefix(['Assets:US:TD:Checking', 'Assets:US:TD:Savings'])) self.assertEqual('Assets:US', account.commonprefix(['Assets:US:TD:Checking', 'Assets:US:BofA:Checking'])) self.assertEqual('Assets', account.commonprefix(['Assets:US:TD:Checking', 'Assets:CA:RBC:Savings'])) self.assertEqual('', account.commonprefix(['Assets:US:TD:Checking', 'Liabilities:US:CreditCard'])) self.assertEqual('', account.commonprefix(['']))
def aggregate_holdings_list(holdings): if not holdings: return None units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() for pos in holdings: units += pos.units.number accounts.add(pos.account) currencies.add(pos.units.currency) cost_currencies.add( pos.cost.currency if pos.cost else pos.units.currency) if pos.cost: total_book_value += pos.units.number * pos.cost.number else: total_book_value += pos.units.number if pos.price is not None: total_market_value += pos.units.number * pos.price else: total_market_value += \ pos.units.number * (pos.cost.number if pos.cost else 1) assert len(cost_currencies) == 1 avg_cost = total_book_value / units if units else None avg_price = total_market_value / units if units else None currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) show_cost = bool(avg_cost) and cost_currency != currency return Posting( account_, Amount(units, currency), Cost(avg_cost, cost_currency, None, None) if show_cost else None, avg_price if show_cost else None, None, None )
def aggregate_holdings_list(holdings): if not holdings: return None units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() for pos in holdings: units += pos.units.number accounts.add(pos.account) currencies.add(pos.units.currency) cost_currencies.add( pos.cost.currency if pos.cost else pos.units.currency) if pos.cost: total_book_value += pos.units.number * pos.cost.number else: total_book_value += pos.units.number if pos.price is not None: total_market_value += pos.units.number * pos.price else: total_market_value += \ pos.units.number * (pos.cost.number if pos.cost else 1) assert len(cost_currencies) == 1 avg_cost = total_book_value / units if units else None avg_price = total_market_value / units if units else None currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) show_cost = bool(avg_cost) and cost_currency != currency return Posting( account_, Amount(units, currency), Cost(avg_cost, cost_currency, None, None) if show_cost else None, avg_price if show_cost else None, None, None)
def aggregate_holdings_list(holdings): """Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Args: holdings: A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Raises: ValueError: If multiple cost currencies encountered. """ if not holdings: return None # Note: Holding is a bit overspecified with book and market values. We # recompute them from cost and price numbers here anyhow. units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() price_dates = set() book_value_seen = False market_value_seen = False for holding in holdings: units += holding.number accounts.add(holding.account) price_dates.add(holding.price_date) currencies.add(holding.currency) cost_currencies.add(holding.cost_currency) if holding.book_value is not None: total_book_value += holding.book_value book_value_seen = True elif holding.cost_number is not None: total_book_value += holding.number * holding.cost_number book_value_seen = True if holding.market_value is not None: total_market_value += holding.market_value market_value_seen = True elif holding.price_number is not None: total_market_value += holding.number * holding.price_number market_value_seen = True if book_value_seen: average_cost = total_book_value / units if units else None else: total_book_value = None average_cost = None if market_value_seen: average_price = total_market_value / units if units else None else: total_market_value = None average_price = None if len(cost_currencies) != 1: raise ValueError( "Cost currencies are not homogeneous for aggregation: {}".format( ','.join(map(str, cost_currencies)))) units = units if len(currencies) == 1 else ZERO currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) price_date = price_dates.pop() if len(price_dates) == 1 else None return Holding(account_, units, currency, average_cost, cost_currency, total_book_value, total_market_value, average_price, price_date)