#autofetch_prices.py
#input: currencyList as "targetCurrency/baseCurrency", for example: plugin "autofetch_prices" "USD/CAD"
#finds all postings in targetCurrency where the price is not set, fetches the appropriate price for that date and sets it.
#example: for input "USD/CAD", every entry like "10 USD" gets a price and becomes something like "10 USD @ 1.22 CAD"
import beancount
from beancount.core.interpolate import AUTOMATIC_META
from beancount.core.number import MISSING
from beancount.core import data
from beancount.parser import booking
from beancount.ops import validation
from beancount.utils import misc_utils
__plugins__ = ['autofetch_prices']
def autofetch_prices(entries, options_map, currencyList):
targetCurrency, baseCurrency = currencyList.split('/')
priceMap = beancount.core.prices.build_price_map(entries)
for entry in entries:
if isinstance(entry, data.Transaction):
found = False
for k, posting in enumerate(entry.postings):
if posting.price==None and posting.units.currency==targetCurrency and posting.meta and not AUTOMATIC_META in posting.meta:
price = beancount.core.prices.get_price(priceMap, targetCurrency+"/"+baseCurrency, entry.date)[1]
#if there's no price from the priceDB for this posting, do nothing and move over to the next posting.
if price==None:
continue
found = True
priceAmount = beancount.core.amount.Amount(price, baseCurrency)
posting = posting._replace(price=priceAmount)
entry.postings[k] = posting
if found:
for z, posting in enumerate(entry.postings):
if posting.meta and AUTOMATIC_META in posting.meta and posting.meta[AUTOMATIC_META]:
if posting.units.currency==targetCurrency:
entry.postings.remove(posting)
if posting.units.currency==baseCurrency:
posting = posting._replace(units = MISSING)
del posting.meta[AUTOMATIC_META]
entry.postings[z] = posting
#remove this plugin now as it has done it's job and we don't want infinite recursion next
for plugin in options_map["plugin"]:
if plugin[0]==__plugins__[0]:
options_map["plugin"].remove(plugin)
#The following code is from the beancount.loader._load function
entries.sort(key=data.entry_sortkey)
# Run interpolation on incomplete entries.
entries, balance_errors = booking.book(entries, options_map)
parse_errors = []
parse_errors.extend(balance_errors)
# Transform the entries.
entries, errors = beancount.loader.run_transformations(entries, parse_errors, options_map, None)
# Validate the list of entries.
with misc_utils.log_time('beancount.ops.validate', None, indent=1):
valid_errors = validation.validate(entries, options_map, None)
errors.extend(valid_errors)
# Compute the input hash.
options_map['input_hash'] = beancount.loader.compute_input_hash(options_map['include'])
return entries, errors