Beispiel #1
0
 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("")
Beispiel #3
0
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