def test_line_file_proxy(self): output = [] fileobj = misc_utils.LineFileProxy(output.append, ' ') fileobj.write('a') fileobj.write('b') fileobj.write('c\n') fileobj.write('d') self.assertEqual([' abc'], output) fileobj.flush() self.assertEqual([' abc'], output) fileobj.write('e\n') self.assertEqual([' abc', ' de'], output) fileobj.close()
def dump_timeline(timeline, price_map, file): """Dump a text rendering of the timeline to a given file output for debugging. This provides all, the detail. Useful just for debugging. Args: timeline: A list of Segment instances. price_map: A mapping of prices as per build_price_map(). file: A file object to write to. """ # FIXME: Convert this to make use of the price map. pr = lambda *args: print(*args, file=file) indfile = misc_utils.LineFileProxy(file.write, ' ', write_newlines=True) for segment in timeline: pr(",-----------------------------------------------------------") pr(" Begin: {}".format(segment.begin.date)) pr(" Balance: {}".format(segment.begin.balance.units())) printer.print_entries(segment.entries, file=indfile) pr("") pr(" Balance: {}".format(segment.end.balance.units())) pr(" End: {}".format(segment.end.date)) pr("`-----------------------------------------------------------") printer.print_entries(segment.external_entries, file=indfile) pr("")
def segment_periods(entries, accounts_value, accounts_intflows, date_begin=None, date_end=None): """Segment entries in terms of piecewise periods of internal flow. This function iterated through the given entries and computes balances at the beginning and end of periods without external flow entries. You should be able to then compute the returns from these informations. Args: entries: A list of directives. The list may contain directives other than than transactions as well as directives with no relation to the assets or internal flow accounts (the function simply ignores that which is not relevant). accounts_value: A set of the asset accounts in the related group. accounts_intflows: A set of the internal flow accounts in the related group. 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: A pair of periods: A list of period tuples, each of which contains: period_begin: A datetime.date instance, the first day of the period. period_end: A datetime.date instance, the last day of the period. balance_begin: An Inventory instance, the balance at the beginning of the period. balance_end: An Inventory instance, the balance at the end of the period. portfolio_entries: A list of the entries that we used in computing the portfolio. Raises: ValueError: If the dates create an impossible situation, the beginning must come before the requested end, if specified. """ logging.info("Segmenting periods.") logging.info("Date begin: %s", date_begin) logging.info("Date end: %s", date_end) if date_begin and date_end and date_begin >= date_end: raise ValueError("Dates are not ordered correctly: {} >= {}".format( date_begin, date_end)) accounts_related = accounts_value | accounts_intflows is_external_flow_entry = lambda entry: (isinstance( entry, data.Transaction) and any(posting.account not in accounts_related for posting in entry.postings)) # Create an iterator over the entries we care about. portfolio_entries = [ entry for entry in entries if getters.get_entry_accounts(entry) & accounts_value ] iter_entries = iter(portfolio_entries) entry = next(iter_entries) # If a beginning cut-off has been specified, skip the entries before then # (and make sure to accumulate the initial balance correctly). balance = inventory.Inventory() if date_begin is not None: period_begin = date_begin try: while True: if entry.date >= date_begin: break if date_end and entry.date >= date_end: break balance = sum_balances_for_accounts(balance, entry, accounts_value) entry = next(iter_entries) except StopIteration: # No periods found! Just return an empty list. return [(date_begin, date_end or date_begin, balance, balance)], [] else: period_begin = entry.date # Main loop over the entries. periods = [] entry_logger = misc_utils.LineFileProxy(logging.debug, ' ') done = False while True: balance_begin = copy.copy(balance) logging.debug( ",-----------------------------------------------------------") logging.debug(" Begin: %s", period_begin) logging.debug(" Balance: %s", balance_begin.units()) logging.debug("") # Consume all internal flow entries, simply accumulating the total balance. while True: period_end = entry.date if is_external_flow_entry(entry): break if date_end and entry.date >= date_end: period_end = date_end done = True break if entry: printer.print_entry(entry, file=entry_logger) balance = sum_balances_for_accounts(balance, entry, accounts_value) try: entry = next(iter_entries) except StopIteration: done = True if date_end: period_end = date_end break else: done = True balance_end = copy.copy(balance) ## FIXME: Bring this back in, this fails for now. Something about the ## initialization fails it. assert period_begin <= period_end, ## (period_begin, period_end) periods.append((period_begin, period_end, balance_begin, balance_end)) logging.debug(" Balance: %s", balance_end.units()) logging.debug(" End: %s", period_end) logging.debug( "`-----------------------------------------------------------") logging.debug("") if done: break # Absorb the balance of the external flow entry. assert is_external_flow_entry(entry), entry if entry: printer.print_entry(entry, file=entry_logger) balance = sum_balances_for_accounts(balance, entry, accounts_value) try: entry = next(iter_entries) except StopIteration: # If there is an end date, insert that final period to cover the end # date, with no changes. if date_end: periods.append((period_end, date_end, balance, balance)) break period_begin = period_end ## FIXME: Bring this back in, this fails for now. # assert all(period_begin <= period_end # for period_begin, period_end, _, _ in periods), periods return periods, portfolio_entries