def load_file(self, beancount_file_path=None): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" if beancount_file_path: self.beancount_file_path = beancount_file_path self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.dcontext = self.options['dcontext'] self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = self._entries_filter_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = self._all_accounts() self.all_accounts_leaf_only = self._all_accounts(leaf_only=True) self._apply_filters()
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_leaf_only = _list_accounts(self.all_root_account, leaf_only=True) self.sidebar_links = _sidebar_links(self.all_entries) self._apply_filters() self.budgets = Budgets(self.entries) self.errors.extend(self.budgets.errors)
def __init__(self, entries, options_map, currency): self.entries = entries self.options_map = options_map self.currency = currency if self.currency: self.etype = "envelope" + self.currency else: self.etype = "envelope" self.start_date, self.budget_accounts, self.mappings, self.income_accounts = self._find_envelop_settings( ) if not self.currency: self.currency = self._find_currency(options_map) decimal_precison = '0.00' self.Q = Decimal(decimal_precison) # Compute start of period # TODO get start date from journal today = datetime.date.today() self.date_start = datetime.datetime.strptime(self.start_date, '%Y-%m').date() # TODO should be able to assert errors # Compute end of period self.date_end = datetime.date(today.year, today.month, today.day) self.price_map = prices.build_price_map(entries) self.acctypes = options.get_account_types(options_map)
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_leaf_only = _list_accounts( self.all_root_account, leaf_only=True) self.sidebar_links = _sidebar_links(self.all_entries) self._apply_filters() self.budgets = Budgets(self.entries) self.errors.extend(self.budgets.errors)
def render_mini_balances(entries, options_map, conversion=None, price_map=None): """Render a treeified list of the balances for the given transactions. Args: entries: A list of selected transactions to render. options_map: The parsed options. conversion: Conversion method string, None, 'value' or 'cost'. price_map: A price map from the original entries. If this isn't provided, the inventories are rendered directly. If it is, their contents are converted to market value. """ # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(entries) dformat = options_map['dcontext'].build(alignment=Align.DOT, reserved=2) # TODO(blais): I always want to be able to convert at cost. We need # arguments capability. # # TODO(blais): Ideally this conversion inserts a new transactions to # 'Unrealized' to account for the difference between cost and market value. # Insert one and update the realization. Add an update() method to the # realization, given a transaction. acctypes = options.get_account_types(options_map) if conversion == 'value': assert price_map is not None # Warning: Mutate the inventories in-place, converting them to market # value. balance_diff = inventory.Inventory() for real_account in realization.iter_children(real_root): balance_cost = real_account.balance.reduce(convert.get_cost) balance_value = real_account.balance.reduce(convert.get_value, price_map) real_account.balance = balance_value balance_diff.add_inventory(balance_cost) balance_diff.add_inventory(-balance_value) if not balance_diff.is_empty(): account_unrealized = account.join(acctypes.income, options_map["account_unrealized_gains"]) unrealized = realization.get_or_create(real_root, account_unrealized) unrealized.balance.add_inventory(balance_diff) elif conversion == 'cost': for real_account in realization.iter_children(real_root): real_account.balance = real_account.balance.reduce(convert.get_cost) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))
def divert_expenses(entries, options_map, config_str): """Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. options_map: A parser options dict. config_str: A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. """ # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError( "Invalid plugin configuration: should be a single dict.") tag = config_obj['tag'] replacement_account = config_obj['account'] acctypes = options.get_account_types(options_map) new_entries = [] errors = [] for entry in entries: if isinstance(entry, Transaction) and tag in entry.tags: entry = replace_diverted_accounts(entry, replacement_account, acctypes) new_entries.append(entry) return new_entries, errors
def do_linked(filename, args): """Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. """ from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit("Missing line number argument.") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. links = closest_entry.links if closest_entry is None: raise SystemExit("No entry could be found before {}:{}".format( filename, lineno)) if not links: return # Find all linked entries. linked_entries = [ entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links) ] # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) realization.dump_balances(real_root, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" with open(self.beancount_file_path, encoding='utf8') as f: self.source = f.read() self.entries, self._errors, self.options = loader.load_file(self.beancount_file_path) self.all_entries = self.entries self.price_map = prices.build_price_map(self.all_entries) self.title = self.options['title'] self.errors = [] for error in self._errors: self.errors.append({ 'file': error.source['filename'], 'line': error.source['lineno'], 'error': error.message, 'entry': error.entry # TODO render entry }) self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.account_types = options.get_account_types(self.options) self.real_accounts = realization.realize(self.entries, self.account_types) self.all_accounts = self._account_components()
def render_real_htmldiv(self, real_root, options_map, file): account_types = options.get_account_types(options_map) for root in (account_types.assets, account_types.liabilities): oss = io.StringIO() table = tree_table.tree_table(oss, realization.get(real_root, root), None, ['Account', 'Last Entry']) num_cells = 0 for real_account, cells, row_classes in table: if not isinstance(real_account, realization.RealAccount): continue last_posting = realization.find_last_active_posting( real_account.txn_postings) # Don't render updates to accounts that have been closed. # Note: this is O(N), maybe we can store this at realization. if last_posting is None or isinstance(last_posting, data.Close): continue last_date = data.get_entry(last_posting).date # Skip this posting if a cutoff date has been specified and the # account has been updated to at least the cutoff date. if self.args.cutoff and self.args.cutoff <= last_date: continue # Okay, we need to render this. Append. cells.append( data.get_entry(last_posting).date if real_account. txn_postings else '-') num_cells += 1 if num_cells: file.write(oss.getvalue())
def holdings_at_dates(entries, dates, price_map, options_map): """Computes aggregate holdings at mulitple dates. Yields for each date the list of Holding objects. The holdings are aggregated across accounts; the Holding objects will have the account field set to None. :param entries: The list of entries. :param dates: The list of dates. :param price_map: A dict of prices, as built by prices.build_price_map(). :param options_map: The account options. """ transactions = [entry for entry in entries if (isinstance(entry, Transaction) and entry.flag != flags.FLAG_UNREALIZED)] types = options.get_account_types(options_map) def posting_predicate(posting): account_type = account_types.get_account_type(posting.account) if account_type in (types.assets, types.liabilities): return True for date, inventory in zip(dates, inventory_at_dates(transactions, dates, posting_predicate)): yield [get_holding_from_position(position, price_map, date) for position in inventory]
def get_assets_holdings(entries, options_map, currency=None): """Return holdings for all assets and liabilities. Args: entries: A list of directives. options_map: A dict of parsed options. currency: If specified, a string, the target currency to convert all holding values to. Returns: A list of Holding instances and a price-map. """ # Compute a price map, to perform conversions. price_map = prices.build_price_map(entries) # Get the list of holdings. account_types = options.get_account_types(options_map) holdings_list = holdings.get_final_holdings( entries, (account_types.assets, account_types.liabilities), price_map) # Convert holdings to a unified currency. if currency: holdings_list = holdings.convert_to_currency(price_map, currency, holdings_list) return holdings_list, price_map
def _initialize(self, options_map): """Compute the list of filtered entries and realization trees.""" # Get the filtered list of entries. self.entries, self.begin_index, self.price_date = self.apply_filter( self.all_entries, options_map) # Compute the list of entries for the opening balances sheet. self.opening_entries = (self.entries[:self.begin_index] if self.begin_index is not None else []) # Compute the list of entries that includes transfer entries of the # income/expenses amounts to the balance sheet's equity (as "net # income"). This is used to render the end-period balance sheet, with # the current period's net income, closing the period. self.closing_entries = summarize.cap_opt(self.entries, options_map) # Realize the three sets of entries. account_types = options.get_account_types(options_map) with misc_utils.log_time('realize_opening', logging.info): self.opening_real_accounts = realization.realize(self.opening_entries, account_types) with misc_utils.log_time('realize', logging.info): self.real_accounts = realization.realize(self.entries, account_types) with misc_utils.log_time('realize_closing', logging.info): self.closing_real_accounts = realization.realize(self.closing_entries, account_types) assert self.real_accounts is not None assert self.closing_real_accounts is not None
def open_opt(entries, date, options_map): """Convenience function to open() using an options map. """ account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return open(entries, date, account_types, conversion_currency, *previous_accounts)
def get_matching_entries(entries, options_map, query): query_text = 'SELECT * ' + query parser = query_parser.Parser() parsed_query = parser.parse(query_text) c_from = None if parsed_query.from_clause: c_from = query_compile.compile_from(parsed_query.from_clause, query_env.FilterEntriesEnvironment()) c_where = None if parsed_query.where_clause: c_where = query_compile.compile_expression(parsed_query.where_clause, query_env.FilterPostingsEnvironment()) # Figure out if we need to compute balance. balance = None if c_where and query_execute.uses_balance_column(c_where): balance = inventory.Inventory() context = query_execute.RowContext() context.balance = balance # Initialize some global properties for use by some of the accessors. context.options_map = options_map context.account_types = options.get_account_types(options_map) context.open_close_map = getters.get_account_open_close(entries) #context.commodity_map = getters.get_commodity_map(entries) context.price_map = prices.build_price_map(entries) if c_from is not None: filtered_entries = query_execute.filter_entries(c_from, entries, options_map) else: filtered_entries = entries return filtered_entries
def holdings_at_dates(entries, dates, price_map, options_map): """Computes aggregate holdings at mulitple dates. Yields for each date the list of Holding objects. The holdings are aggregated across accounts; the Holding objects will have the account field set to None. :param entries: The list of entries. :param dates: The list of dates. :param price_map: A dict of prices, as built by prices.build_price_map(). :param options_map: The account options. """ account_types = options.get_account_types(options_map) FLAG_UNREALIZED = flags.FLAG_UNREALIZED transaction_predicate = lambda e: e.flag != FLAG_UNREALIZED account_re = re.compile(account_descendants_re_pattern( account_types.assets, account_types.liabilities)) posting_predicate = lambda e, p: account_re.match(p.account) for date, inventory in zip(dates, inventory_at_dates( entries, dates, transaction_predicate = transaction_predicate, posting_predicate = posting_predicate)): yield [get_holding_from_position(lot, number, price_map=price_map, date=date) for lot, number in inventory.items()]
def compute_returns_with_regexp(entries, options_map, transfer_account, assets_regexp, intflows_regexp, internalize_regexp=None, date_begin=None, date_end=None): """Compute the returns of a portfolio of accounts defined by a regular expression. Args: entries: A list of directives. options_map: An options dict as produced by the loader. transfer_account: A string, the name of an account to use for internalizing entries which need to be split between internal and external flows. assets_regexp: A regular expression string that matches names of asset accounts to value for the portfolio. intflows_regexp: A regular expression string that matches names of accounts considered internal flows to the portfolio (typically income and expenses accounts). internalize_regexp: A regular expression string that matches names of accounts to force internalization of. See internalize() for details. date_begin: A datetime.date instance, the beginning date of the period to compute returns over. date_end: A datetime.date instance, the end date of the period to compute returns over. Returns: See compute_returns(). """ acc_types = options.get_account_types(options_map) price_map = prices.build_price_map(entries) # Fetch the matching entries and figure out account name groups. matching_entries, (accounts_value, accounts_intflows, accounts_extflows, accounts_internalize) = find_matching( entries, acc_types, assets_regexp, intflows_regexp, internalize_regexp) logging.info('Asset accounts:') for account in sorted(accounts_value): logging.info(' %s', account) logging.info('Internal flows:') for account in sorted(accounts_intflows): logging.info(' %s', account) logging.info('External flows:') for account in sorted(accounts_extflows): logging.info(' %s', account) logging.info('') if accounts_internalize: logging.info('Explicitly internalized accounts:') for account in sorted(accounts_internalize): logging.info(' %s', account) logging.info('') return compute_returns(entries, transfer_account, accounts_value, accounts_intflows, accounts_internalize, price_map, date_begin, date_end)
def process_account_entries(entries: data.Entries, options_map: data.Options, account: Account) -> AccountData: """Process a single account.""" logging.info("Processing account: %s", account) # Extract the relevant transactions. transactions = transactions_for_account(entries, account) if not transactions: logging.warning("No transactions for %s; skipping.", account) return transactions, None, None # Categorize the set of accounts encountered in the filtered transactions. seen_accounts = { posting.account for entry in transactions for posting in entry.postings } atypes = options.get_account_types(options_map) catmap = categorize_accounts(account, seen_accounts, atypes) # Process each of the transactions, adding derived values as metadata. cash_flows = [] balance = Inventory() decorated_transactions = [] for entry in transactions: # Update the total position in the asset we're interested in. positions = [] for posting in entry.postings: category = catmap[posting.account] if category is Cat.ASSET: balance.add_position(posting) positions.append(posting) # Compute the signature of the transaction. entry = copy_and_normalize(entry) signature = compute_transaction_signature(catmap, entry) entry.meta["signature"] = signature entry.meta["description"] = KNOWN_SIGNATURES[signature] # Compute the cash flows associated with the transaction. flows = produce_cash_flows(entry) entry.meta['cash_flows'] = flows cash_flows.extend( flow._replace(balance=copy.deepcopy(balance)) for flow in flows) decorated_transactions.append(entry) currency = accountlib.leaf(account) cost_currencies = set(cf.amount.currency for cf in cash_flows) assert len(cost_currencies) == 1, str(cost_currencies) cost_currency = cost_currencies.pop() commodity_map = getters.get_commodity_directives(entries) comm = commodity_map[currency] return AccountData(account, currency, cost_currency, comm, cash_flows, decorated_transactions, catmap)
def test_get_account_types(self): options_ = options.OPTIONS_DEFAULTS.copy() result = options.get_account_types(options_) expected = account_types.AccountTypes(assets='Assets', liabilities='Liabilities', equity='Equity', income='Income', expenses='Expenses') self.assertEqual(expected, result)
def load_file(self): with open(self.beancount_file_path, encoding='utf8') as f: self._source = f.read() self.entries, self._errors, self.options_map = loader.load_file(self.beancount_file_path) self.all_entries = self.entries self.account_types = options.get_account_types(self.options_map) self.real_accounts = realization.realize(self.entries, self.account_types)
def forward_method(self, entries, errors, options_map, file, fwdfunc=value): account_types = options.get_account_types(options_map) real_root = realization.realize(entries, account_types) return fwdfunc(self, real_root, options_map, file)
def load_file(self): """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) self.account_types = get_account_types(self.options) # TODO: implement watcher for S3 #self._watcher.update(*self.paths_to_watch()) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.account_types = get_account_types(self.options) self.price_map = prices.build_price_map(self.all_entries) self.all_root_account = realization.realize(self.all_entries, self.account_types) entries_by_type = collections.defaultdict(list) for entry in self.all_entries: entries_by_type[type(entry)].append(entry) self.all_entries_by_type = entries_by_type self.accounts = _AccountDict() for entry in entries_by_type[Open]: self.accounts.setdefault(entry.account).meta = entry.meta for entry in entries_by_type[Close]: self.accounts.setdefault(entry.account).close_date = entry.date self.fava_options, errors = parse_options(entries_by_type[Custom]) self.errors.extend(errors) for mod in MODULES: getattr(self, mod).load_file() self._filters = { 'account': AccountFilter(self.options, self.fava_options), 'filter': AdvancedFilter(self.options, self.fava_options), 'time': TimeFilter(self.options, self.fava_options), } self.filter(True)
def load_file(self): """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) self.account_types = get_account_types(self.options) self._watcher.update(*self.paths_to_watch()) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.account_types = get_account_types(self.options) self.price_map = prices.build_price_map(self.all_entries) self.all_root_account = realization.realize(self.all_entries, self.account_types) entries_by_type = collections.defaultdict(list) for entry in self.all_entries: entries_by_type[type(entry)].append(entry) self.all_entries_by_type = entries_by_type self.accounts = _AccountDict() for entry in entries_by_type[Open]: self.accounts.setdefault(entry.account).meta = entry.meta for entry in entries_by_type[Close]: self.accounts.setdefault(entry.account).close_date = entry.date self.fava_options, errors = parse_options(entries_by_type[Custom]) self.errors.extend(errors) for mod in MODULES: getattr(self, mod).load_file() self._filters = { 'account': AccountFilter(self.options, self.fava_options), 'filter': AdvancedFilter(self.options, self.fava_options), 'time': TimeFilter(self.options, self.fava_options), } self.filter(True)
def forward_method(self, entries, errors, options_map, file, fwdfunc=value): account_types = options.get_account_types(options_map) real_root = realization.realize(entries, account_types) price_map = prices.build_price_map(entries) # Note: When we forward, use the latest date (None). return fwdfunc(self, real_root, price_map, None, options_map, file)
def create_row_context(entries, options_map): """Create the context container which we will use to evaluate rows.""" context = RowContext() context.balance = inventory.Inventory() # Initialize some global properties for use by some of the accessors. context.options_map = options_map context.account_types = options.get_account_types(options_map) context.open_close_map = getters.get_account_open_close(entries) context.commodity_map = getters.get_commodity_directives(entries) context.price_map = prices.build_price_map(entries) return context
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" # use the internal function to disable cache if not self.is_encrypted: self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) include_path = os.path.dirname(self.beancount_file_path) self.watcher.update(self.options['include'], [ os.path.join(include_path, path) for path in self.options['documents']]) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self._format_string = '{:,f}' self._default_format_string = '{:,.2f}' else: self._format_string = '{:f}' self._default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.custom_entries = _filter_entries_by_type(self.all_entries, Custom) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_active = _list_accounts( self.all_root_account, active_only=True) self.fava_options, errors = parse_options(self.custom_entries) self.errors.extend(errors) self.sidebar_links = _sidebar_links(self.custom_entries) self.upcoming_events = _upcoming_events( self.all_entries, self.fava_options['upcoming-events']) self.budgets, errors = parse_budgets(self.custom_entries) self.errors.extend(errors) self._apply_filters()
def find_accounts(entries: data.Entries, options_map: data.Options, start_date: Optional[Date]) -> List[Account]: """Return a list of account names from the balance sheet which either aren't closed or are closed now but were still open at the given start date. """ commodities = getters.get_commodity_directives(entries) open_close_map = getters.get_account_open_close(entries) atypes = options.get_account_types(options_map) return sorted( account for account, (_open, _close) in open_close_map.items() if (accountlib.leaf(account) in commodities and acctypes.is_balance_sheet_account(account, atypes) and not acctypes.is_equity_account(account, atypes) and ( _close is None or (start_date and _close.date > start_date))))
def get_postings_table(entries: data.Entries, options_map: Dict, accounts_map: Dict[str, data.Open], threshold: Decimal = D('0.01')) -> Table: """Enumerate all the postings.""" header = ['account', 'account_abbrev', 'number', 'currency', 'cost_number', 'cost_currency', 'cost_date'] balances, _ = summarize.balance_by_account(entries) acctypes = options.get_account_types(options_map) rows = [] for acc, balance in sorted(balances.items()): # Keep only the balance sheet accounts. acctype = account_types.get_account_type(acc) if not acctype in (acctypes.assets, acctypes.liabilities): continue # If the account has "NONE" booking method, merge all its postings # together in order to obtain an accurate cost basis and balance of # units. # # (This is a complex issue.) If you accrued positions without having them # booked properly against existing cost bases, you have not properly accounted # for the profit/loss to other postings. This means that the resulting # profit/loss is merged in the cost basis of the positive and negative # postings. dopen = accounts_map.get(acc, None) if dopen is not None and dopen.booking is data.Booking.NONE: average_balance = balance.average() balance = inventory.Inventory(pos for pos in average_balance if pos.units.number >= threshold) # Create a posting for each of the positions. for pos in balance: acc_abbrev = abbreviate_account(acc, accounts_map) row = [acc, acc_abbrev, pos.units.number, pos.units.currency, pos.cost.number if pos.cost else ONE, pos.cost.currency if pos.cost else pos.units.currency, pos.cost.date if pos.cost else None] rows.append(row) return Table(header, rows)
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" # use the internal function to disable cache if not self.is_encrypted: self.all_entries, self.errors, self.options = loader._load( [(self.beancount_file_path, True)], None, None, None ) include_path = os.path.dirname(self.beancount_file_path) self.watcher.update( self.options["include"], [os.path.join(include_path, path) for path in self.options["documents"]] ) else: self.all_entries, self.errors, self.options = loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options["title"] if self.options["render_commas"]: self._format_string = "{:,f}" self._default_format_string = "{:,.2f}" else: self._format_string = "{:f}" self._default_format_string = "{:.2f}" self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.custom_entries = _filter_entries_by_type(self.all_entries, Custom) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_active = _list_accounts(self.all_root_account, active_only=True) self.fava_options, errors = parse_options(self.custom_entries) self.errors.extend(errors) self.sidebar_links = _sidebar_links(self.custom_entries) self.upcoming_events = _upcoming_events(self.all_entries, self.fava_options["upcoming-events"]) self.budgets, errors = parse_budgets(self.custom_entries) self.errors.extend(errors) self._apply_filters()
def net_worth_at_dates(entries, dates, price_map, options_map): transactions = [entry for entry in entries if (isinstance(entry, Transaction) and entry.flag != flags.FLAG_UNREALIZED)] types = options.get_account_types(options_map) def _posting_predicate(posting): account_type = account_types.get_account_type(posting.account) if account_type in (types.assets, types.liabilities): return True inventories = inventory_at_dates(transactions, dates, _posting_predicate) return [{ 'date': date, 'balance': { currency: convert_inventory(price_map, currency, inv, date) for currency in options_map['operating_currency'] } } for date, inv in zip(dates, inventories)]
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" self.entries, self._errors, self.options = loader.load_file(self.beancount_file_path) self.all_entries = self.entries self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] self.errors = [] for error in self._errors: self.errors.append({ 'file': error.source['filename'], 'line': error.source['lineno'], 'error': error.message }) self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.apply_filters()
def add_unrealized_gains(entries, options_map, subaccount=None): """Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. subaccount: A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. """ errors = [] meta = data.new_metadata('<unrealized_gains>', 0) account_types = options.get_account_types(options_map) # Assert the subaccount name is in valid format. if subaccount: validation_account = account.join(account_types.assets, subaccount) if not account.is_valid(validation_account): errors.append( UnrealizedError(meta, "Invalid subaccount name: '{}'".format(subaccount), None)) return entries, errors if not entries: return (entries, errors) # Group positions by (account, cost, cost_currency). price_map = prices.build_price_map(entries) new_entries = [] # Start at the first month after our first transaction date = date_utils.next_month(entries[0].date) last_month = date_utils.next_month(entries[-1].date) last_holdings_with_currencies = None while date <= last_month: date_entries, holdings_with_currencies, date_errors = add_unrealized_gains_at_date( entries, new_entries, account_types.income, price_map, date, meta, subaccount) new_entries.extend(date_entries) errors.extend(date_errors) if last_holdings_with_currencies: for account_, cost_currency, currency in last_holdings_with_currencies - holdings_with_currencies: # Create a negation transaction specifically to mark that all gains have been realized if subaccount: account_ = account.join(account_, subaccount) latest_unrealized_entry = find_previous_unrealized_transaction(new_entries, account_, cost_currency, currency) if not latest_unrealized_entry: continue entry = data.Transaction(data.new_metadata(meta["filename"], lineno=999, kvlist={'prev_currency': currency}), date, flags.FLAG_UNREALIZED, None, 'Clear unrealized gains/losses of {}'.format(currency), set(), set(), []) # Negate the previous transaction because of unrealized gains are now 0 for posting in latest_unrealized_entry.postings[:2]: entry.postings.append( data.Posting( posting.account, -posting.units, None, None, None, None)) new_entries.append(entry) last_holdings_with_currencies = holdings_with_currencies date = date_utils.next_month(date) # Ensure that the accounts we're going to use to book the postings exist, by # creating open entries for those that we generated that weren't already # existing accounts. new_accounts = {posting.account for entry in new_entries for posting in entry.postings} open_entries = getters.get_account_open_close(entries) new_open_entries = [] for index, account_ in enumerate(sorted(new_accounts)): if account_ not in open_entries: meta = data.new_metadata(meta["filename"], index) open_entry = data.Open(meta, new_entries[0].date, account_, None, None) new_open_entries.append(open_entry) return (entries + new_open_entries + new_entries, errors)