def convert_inventory(price_map, target_currency, inventory, date): """Convert and sum an inventory to a common currency. Returns: A Decimal, the sum of all positions that could be converted. """ total = ZERO for pos in inventory: # Fetch the price in the cost currency if there is one. if pos.cost: base_quote = (pos.units.currency, pos.cost.currency) _, cost_number = prices.get_price(price_map, base_quote, date) if cost_number is None: cost_number = pos.cost.number currency = pos.cost.currency # Otherwise, price it in its own currency. else: cost_number = 1 currency = pos.units.currency if currency == target_currency: total += pos.units.number * cost_number else: base_quote = (currency, target_currency) # Get the conversion rate. _, price = prices.get_price(price_map, base_quote, date) if price is not None: total += pos.units.number * cost_number * price return total
def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): """Get a list of holdings by account (as Postings).""" simple_entries = [ entry for entry in entries if (not isinstance(entry, Transaction) or entry.flag != flags.FLAG_UNREALIZED) ] root_account = realization.realize(simple_entries) holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): account_type = account_types.get_account_type(real_account.account) if (included_account_types and account_type not in included_account_types): continue for pos in real_account.balance: price = None if pos.cost and price_map: base_quote = (pos.units.currency, pos.cost.currency) _, price = prices.get_price(price_map, base_quote, date) holdings.append( Posting(real_account.account, pos.units, pos.cost, price, None, None)) return holdings
def get_holding_from_position(lot, number, account=None, price_map=None, date=None): """Compute a Holding corresponding to the specified position 'pos'. :param lot: A Lot object. :param number: The number of units of 'lot' in the position. :param account: A str, the name of the account, or None if not needed. :param price_map: A dict of prices, as built by prices.build_price_map(). :param date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. :return: A Holding object. """ if lot.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (lot.currency, lot.cost.currency) price_date, price_number = prices.get_price( price_map, base_quote, date) if price_number is not None: market_value = number * price_number else: price_date, price_number = None, None return Holding(account, number, lot.currency, lot.cost.number, lot.cost.currency, number * lot.cost.number, market_value, price_number, price_date) else: return Holding(account, number, lot.currency, None, lot.currency, number, number, None, None)
def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): """Get a list of holdings by account (as Postings).""" simple_entries = [entry for entry in entries if (not isinstance(entry, Transaction) or entry.flag != flags.FLAG_UNREALIZED)] root_account = realization.realize(simple_entries) holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): account_type = account_types.get_account_type(real_account.account) if (included_account_types and account_type not in included_account_types): continue for pos in real_account.balance: price = None if pos.cost and price_map: base_quote = (pos.units.currency, pos.cost.currency) _, price = prices.get_price(price_map, base_quote, date) holdings.append(Posting(real_account.account, pos.units, pos.cost, price, None, None)) return holdings
def test_get_price(self, entries, _, __): """ 2013-06-01 price USD 1.00 CAD 2013-06-10 price USD 1.50 CAD 2013-07-01 price USD 2.00 CAD """ price_map = prices.build_price_map(entries) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 5, 15)) self.assertEqual(None, price) self.assertEqual(None, date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 6, 1)) self.assertEqual(D('1.00'), price) self.assertEqual(datetime.date(2013, 6, 1), date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 6, 5)) self.assertEqual(D('1.00'), price) self.assertEqual(datetime.date(2013, 6, 1), date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 6, 10)) self.assertEqual(D('1.50'), price) self.assertEqual(datetime.date(2013, 6, 10), date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 6, 20)) self.assertEqual(D('1.50'), price) self.assertEqual(datetime.date(2013, 6, 10), date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 7, 1)) self.assertEqual(D('2.00'), price) self.assertEqual(datetime.date(2013, 7, 1), date) date, price = prices.get_price(price_map, 'USD/CAD', datetime.date(2013, 7, 15)) self.assertEqual(D('2.00'), price) self.assertEqual(datetime.date(2013, 7, 1), date) # With no date, should devolved to get_latest_price(). date, price = prices.get_price(price_map, 'USD/CAD', None) self.assertEqual(D('2.00'), price) self.assertEqual(datetime.date(2013, 7, 1), date) # Test not found. result = prices.get_price(price_map, ('EWJ', 'JPY')) self.assertEqual((None, None), result)
def get_holding_from_position(lot, number, account=None, price_map=None, date=None): """Compute a Holding corresponding to the specified position 'pos'. :param lot: A Lot object. :param number: The number of units of 'lot' in the position. :param account: A str, the name of the account, or None if not needed. :param price_map: A dict of prices, as built by prices.build_price_map(). :param date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. :return: A Holding object. """ if lot.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (lot.currency, lot.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = number * price_number else: price_date, price_number = None, None return Holding(account, number, lot.currency, lot.cost.number, lot.cost.currency, number * lot.cost.number, market_value, price_number, price_date) else: return Holding(account, number, lot.currency, None, lot.currency, number, number, None, None)
def get_holding_from_position(position, price_map=None, date=None): """Compute a Holding corresponding to the specified position 'pos'. :param position: A Position object. :param price_map: A dict of prices, as built by prices.build_price_map(). :param date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. :return: A Holding object. """ if position.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (position.units.currency, position.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = position.units.number * price_number else: price_date, price_number = None, None return Holding(None, position.units.number, position.units.currency, position.cost.number, position.cost.currency, position.units.number * position.cost.number, market_value, price_number, price_date) else: return Holding(None, position.units.number, position.units.currency, None, position.units.currency, position.units.number, position.units.number, None, None)
def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): """Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Args: entries: A list of directives. included_account_types: A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields: """ # Remove the entries inserted by unrealized gains/losses. Those entries do # affect asset accounts, and we don't want them to appear in holdings. # # Note: Perhaps it would make sense to generalize this concept of "inserted # unrealized gains." simple_entries = [ entry for entry in entries if (not isinstance(entry, data.Transaction) or entry.flag != flags.FLAG_UNREALIZED) ] # Realize the accounts into a tree (because we want the positions by-account). root_account = realization.realize(simple_entries) # For each account, look at the list of positions and build a list. holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): if included_account_types: # Skip accounts of invalid types, we only want to reflect the requested # account types, typically assets and liabilities. account_type = account_types.get_account_type(real_account.account) if account_type not in included_account_types: continue for pos in real_account.balance.get_positions(): if pos.lot.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (pos.lot.currency, pos.lot.cost.currency) price_date, price_number = prices.get_price( price_map, base_quote, date) if price_number is not None: market_value = pos.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.number, pos.lot.currency, pos.lot.cost.number, pos.lot.cost.currency, pos.number * pos.lot.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.number, pos.lot.currency, None, pos.lot.currency, pos.number, pos.number, None, None) holdings.append(holding) return holdings