def get_market_value(pos, price_map, date=None): """Get the market value of a Position. This differs from the convert.get_value function in Beancount by returning the cost value if no price can be found. Args: pos: A Position. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, with value converted or if the conversion failed just the cost value (or the units if the position has no cost). """ units_ = pos.units cost_ = pos.cost value_currency = cost_.currency if cost_ else None if value_currency: base_quote = (units_.currency, value_currency) _, price_number = get_price(price_map, base_quote, date) if price_number is not None: return Amount(units_.number * price_number, value_currency) return Amount(units_.number * cost_.number, value_currency) return units_
def __call__(self, context): warnings.warn("PRICE() is deprecated; use GETPRICE() instead") args = self.eval_args(context) base, quote, date = args pair = (base.upper(), quote.upper()) _, price = prices.get_price(context.price_map, pair, date) return price
def get_market_value(pos, price_map, date=None): """Get the market value of a Position. This differs from the convert.get_value function in Beancount by returning the cost value if no price can be found. Args: pos: A Position. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, with value converted or if the conversion failed just the cost value (or the units if the position has no cost). """ units_ = pos.units cost_ = pos.cost value_currency = cost_.currency if cost_ else None if value_currency: base_quote = (units_.currency, value_currency) _, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: return Amount(units_.number * price_number, value_currency) return Amount(units_.number * cost_.number, value_currency) return units_
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 convert_amount(amt, target_currency, price_map, date=None, via=None): """Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implieds rates. Args: amt: An instance of Amount. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. via: A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a succesful value currency conversion, or if we could not convert the value, the amount itself, unmodified. """ # First, attempt to convert directly. This should be the most # straightforward conversion. base_quote = (amt.currency, target_currency) _, rate = prices.get_price(price_map, base_quote, date) if rate is not None: # On success, just make the conversion directly. return Amount(amt.number * rate, target_currency) elif via: assert isinstance(via, (tuple, list)) # A price is unavailable, attempt to convert via cost/price currency # hop, if the value currency isn't the target currency. for implied_currency in via: if implied_currency == target_currency: continue base_quote1 = (amt.currency, implied_currency) _, rate1 = prices.get_price(price_map, base_quote1, date) if rate1 is not None: base_quote2 = (implied_currency, target_currency) _, rate2 = prices.get_price(price_map, base_quote2, date) if rate2 is not None: return Amount(amt.number * rate1 * rate2, target_currency) # We failed to infer a conversion rate; return the amt. return amt
def get_value(pos, price_map, date=None, output_date_prices=None): """Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see ``convert_*()`` functions below. However, is the object is a posting and it has a price, we will use that price to infer the target currency and those will be converted. Args: pos: An instance of Position or Posting, equivalently. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. output_date_prices: An optional output list of (date, price). If this list is provided, it will be appended to (mutated) to output the prices pulled in making the conversions. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. """ assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # Try to infer what the cost/price currency should be. value_currency = ((isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) if isinstance(value_currency, str): # We have a value currency; hit the price database. base_quote = (units.currency, value_currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if output_date_prices is not None: output_date_prices.append((price_date, price_number)) if price_number is not None: return Amount(units.number * price_number, value_currency) # We failed to infer a conversion rate; return the units. return units
def generate(entries, options_map, baseCcy): errors = [] priceMap = prices.build_price_map(entries) additionalEntries = [] for entry in entries: if isinstance(entry, data.Price) and entry.amount.currency != baseCcy: fxRate = prices.get_price(priceMap, tuple([entry.amount.currency, baseCcy]), entry.date) priceInBaseCcy = amount.Amount(entry.amount.number * fxRate[1], baseCcy) additionalEntries.append(data.Price( entry.meta, entry.date, entry.currency, priceInBaseCcy )) entries.extend(additionalEntries) return entries, errors
def add_prices_to_postings(entries, postings): """Attach price directives to postings where missing. The prices are fetched from the database of entries. Args: entries: A list of directives containing price directives. postings: A list of Posting instances to be augmented. Yields: A new list of Posting instances with the 'price' attribute filled in. """ # Compute a price map, to extract most current prices. price_map = prices.build_price_map(entries) for posting in postings: if posting.price is None and posting.cost is not None: cbase = posting.units.currency cquote = posting.cost.currency (price_date, price_number) = prices.get_price(price_map, (cbase, cquote), None) posting = posting._replace(price=amount.Amount(price_number, cquote)) yield posting
def _conversion(self, posting): currency = (posting.units.currency if posting.cost is None else posting.cost.currency) if self.exports.get(currency, None) in 'IGNORE': return ZERO currency = self.CURRENCY_MAP.get(currency, currency) if currency == self.cash_currency: # If its already in USD, a noop conversion is x1. return str(ONE) elif re.match('[A-Z]{3}$', currency): # If the instrument is a currency instrument, fetch it live. return '=GOOGLEFINANCE("CURRENCY:{}{}")'.format(currency, self.cash_currency) else: # Otherwise, use the latest value in our file. (_, price_number) = prices.get_price(self.price_map, (currency, self.cash_currency), None) return str(price_number)
def getprice(context, base, quote, date=None): """Fetch a price.""" pair = (base.upper(), quote.upper()) _, price = prices.get_price(context.price_map, pair, date) return price
def fetchPriceAmount(self, instrument, date): price = prices.get_price(self.priceMap, tuple([instrument, self.baseCcy]), date) return price[1]
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.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.units.currency, pos.cost.currency) price_date, price_number = prices.get_price( price_map, base_quote, date) if price_number is not None: market_value = pos.units.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.units.number, pos.units.currency, pos.cost.number, pos.cost.currency, pos.units.number * pos.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.units.number, pos.units.currency, None, pos.units.currency, pos.units.number, pos.units.number, None, None) holdings.append(holding) return holdings