Пример #1
0
def calculate_net_worths(entries, options_map):
    holdings_list, price_map = holdings.get_assets_holdings(
        entries, options_map)
    net_worths = []
    for currency in options_map['operating_currency']:

        # Convert holdings to a unified currency.
        #
        # Note: It's entirely possible that the price map does not have all
        # the necessary rate conversions here. The resulting holdings will
        # simply have no cost when that is the case. We must handle this
        # gracefully below.
        currency_holdings_list = holdings.convert_to_currency(
            price_map, currency, holdings_list)
        if not currency_holdings_list:
            continue

        aggregated_holdings_list = holdings.aggregate_holdings_by(
            currency_holdings_list, lambda holding: holding.cost_currency)

        aggregated_holdings_list = [
            holding for holding in aggregated_holdings_list
            if holding.currency and holding.cost_currency
        ]

        # If after conversion there are no valid holdings, skip the currency
        # altogether.
        if not aggregated_holdings_list:
            continue

        net_worths.append((currency, aggregated_holdings_list[0].market_value))
    return net_worths
Пример #2
0
    def _net_worth_in_intervals(self, interval):
        interval_tuples = self._interval_tuples(interval, self.entries)
        interval_totals = []
        end_dates = [p[1] for p in interval_tuples]

        for (begin_date, end_date), holdings_list in \
                zip(interval_tuples,
                    holdings_at_dates(self.entries, end_dates,
                                      self.price_map, self.options)):
            totals = {}
            for currency in self.options['operating_currency']:
                currency_holdings_list = \
                    holdings.convert_to_currency(self.price_map, currency,
                                                 holdings_list)
                if not currency_holdings_list:
                    continue

                holdings_ = holdings.aggregate_holdings_by(
                    currency_holdings_list,
                    operator.attrgetter('cost_currency'))

                holdings_ = [holding
                             for holding in holdings_
                             if holding.currency and holding.cost_currency]

                # If after conversion there are no valid holdings, skip the
                # currency altogether.
                if holdings_:
                    totals[currency] = holdings_[0].market_value

            interval_totals.append({
                'date': end_date - datetime.timedelta(1),
                'totals': totals
            })
        return interval_totals
Пример #3
0
    def generate_table(self, entries, errors, options_map):
        holdings_list, price_map = holdings.get_assets_holdings(
            entries, options_map)
        holdings_list_orig = holdings_list

        # Keep only the holdings where currency is the same as the cost-currency.
        holdings_list = [
            holding for holding in holdings_list
            if (holding.currency == holding.cost_currency
                or holding.cost_currency is None)
        ]

        # Keep only those holdings held in one of the operating currencies.
        if self.args.operating_only:
            operating_currencies = set(options_map['operating_currency'])
            holdings_list = [
                holding for holding in holdings_list
                if holding.currency in operating_currencies
            ]

        # Compute the list of ignored holdings and optionally report on them.
        if self.args.ignored:
            ignored_holdings = set(holdings_list_orig) - set(holdings_list)
            holdings_list = ignored_holdings

        # Convert holdings to a unified currency.
        if self.args.currency:
            holdings_list = holdings.convert_to_currency(
                price_map, self.args.currency, holdings_list)

        return table.create_table(holdings_list, FIELD_SPEC)
Пример #4
0
    def _net_worth_in_periods(self):
        month_tuples = self._interval_tuples('month', self.entries)
        monthly_totals = []
        end_dates = [p[1] + timedelta(days=1) for p in month_tuples]

        for (begin_date, end_date), holdings_list in zip(month_tuples,
                                                        holdings_at_dates(entries=self.entries,
                                                                          dates=end_dates,
                                                                          options_map=self.options,
                                                                          price_map=self.price_map)):
            totals = dict()
            for currency in self.options['operating_currency']:
                total = ZERO
                for holding in holdings.convert_to_currency(self.price_map, currency, holdings_list):
                    if holding.cost_currency == currency and holding.market_value:
                        total += holding.market_value
                if total != ZERO:
                    totals[currency] = total

            monthly_totals.append({
                'begin_date': begin_date,
                'end_date': end_date,
                'totals': totals
            })
        return monthly_totals
Пример #5
0
    def _net_worth_in_periods(self):
        month_tuples = self._interval_tuples('month', self.entries)
        monthly_totals = []
        end_dates = [p[1] + timedelta(days=1) for p in month_tuples]

        for (begin_date, end_date), holdings_list in zip(
                month_tuples,
                holdings_at_dates(entries=self.entries,
                                  dates=end_dates,
                                  options_map=self.options,
                                  price_map=self.price_map)):
            totals = dict()
            for currency in self.options['operating_currency']:
                total = ZERO
                for holding in holdings.convert_to_currency(
                        self.price_map, currency, holdings_list):
                    if holding.cost_currency == currency and holding.market_value:
                        total += holding.market_value
                if total != ZERO:
                    totals[currency] = total

            monthly_totals.append({
                'begin_date': begin_date,
                'end_date': end_date,
                'totals': totals
            })
        return monthly_totals
Пример #6
0
def load_csv_and_prices(holdings_filename, prices_filename, currency):
    """Load the holdings and prices from filenames and convert to a common currency.

    Args:
      holdings_filename: A string, the name of a CSV file containing the list of Holdings.
      prices_filename: A string, the name of a Beancount file containing price directives.
      currency: A string, the target currency to convert all the holdings to.
    Returns:
      Two lists of holdings: a list in the original currencies, and a list all
      converted to the target currency.
    """
    # Load the price database.
    # Generate with "bean-query LEDGER holdings"
    price_entries, errors, options_map = loader.load(prices_filename)
    price_map = prices.build_price_map(price_entries)

    # Load the holdings list.
    # Generate with "bean-query LEDGER print_prices"
    mixed_holdings_list = list(
        holdings_reports.load_from_csv(open(holdings_filename)))

    # Convert all the amounts to a common currency (otherwise summing market
    # values makes no sense).
    holdings_list = holdings.convert_to_currency(price_map, currency,
                                                 mixed_holdings_list)

    return mixed_holdings_list, holdings_list
Пример #7
0
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
Пример #8
0
def get_networth(entries, options_map, args):
    net_worths = []
    index = 0
    current_entries = []

    dtend = datetime.date.today()
    period = rrule.rrule(rrule.MONTHLY, bymonthday=1, dtstart=args.min_date, until=dtend)

    for dtime in period:
        date = dtime.date()

        # Append new entries until the given date.
        while True:
            entry = entries[index]
            if entry.date >= date:
                break
            current_entries.append(entry)
            index += 1

        # Get the list of holdings.
        raw_holdings_list, price_map = holdings.get_assets_holdings(current_entries,
                                                                            options_map)

        # Remove any accounts we don't in our final total
        filtered_holdings_list = [n for n in raw_holdings_list if n.account not in args.ignore_account]

        # Convert the currencies.
        holdings_list = holdings.convert_to_currency(price_map,
                                                        args.currency,
                                                        filtered_holdings_list)

        holdings_list = holdings.aggregate_holdings_by(
            holdings_list, lambda holding: holding.cost_currency)

        holdings_list = [holding
                            for holding in holdings_list
                            if holding.currency and holding.cost_currency]

        # If after conversion there are no valid holdings, skip the currency
        # altogether.
        if not holdings_list:
            continue

        # TODO: How can something have a book_value but not a market_value?
        value = holdings_list[0].market_value or holdings_list[0].book_value
        net_worths.append((date, float(value)))
        logging.debug("{}: {:,.2f}".format(date, value))

    return pandas.Series(dict(net_worths))
Пример #9
0
    def _holdings_to_net_worth(self, holdings_list):
        totals = {}
        for currency in self.options['operating_currency']:
            currency_holdings_list = \
                holdings.convert_to_currency(self.price_map, currency,
                                             holdings_list)

            holdings_ = holdings.aggregate_holdings_by(
                currency_holdings_list,
                operator.attrgetter('cost_currency'))

            holdings_ = [holding
                         for holding in holdings_
                         if holding.currency and holding.cost_currency]

            if holdings_:
                totals[currency] = holdings_[0].market_value
            else:
                totals[currency] = 0
        return totals
Пример #10
0
    def _holdings_to_net_worth(self, holdings_list):
        totals = {}
        for currency in self.options['operating_currency']:
            currency_holdings_list = \
                holdings.convert_to_currency(self.price_map, currency,
                                             holdings_list)
            if not currency_holdings_list:
                continue

            holdings_ = holdings.aggregate_holdings_by(
                currency_holdings_list,
                operator.attrgetter('cost_currency'))

            holdings_ = [holding
                         for holding in holdings_
                         if holding.currency and holding.cost_currency]

            # If after conversion there are no valid holdings, skip the
            # currency altogether.
            if holdings_:
                totals[currency] = holdings_[0].market_value
        return totals
Пример #11
0
    def generate_table(self, entries, errors, options_map):
        holdings_list, price_map = get_assets_holdings(entries, options_map)

        net_worths = []
        for currency in options_map['operating_currency']:

            # Convert holdings to a unified currency.
            #
            # Note: It's entirely possible that the price map does not have all
            # the necessary rate conversions here. The resulting holdings will
            # simply have no cost when that is the case. We must handle this
            # gracefully below.
            currency_holdings_list = holdings.convert_to_currency(
                price_map, currency, holdings_list)
            if not currency_holdings_list:
                continue

            holdings_list = holdings.aggregate_holdings_by(
                currency_holdings_list, lambda holding: holding.cost_currency)

            holdings_list = [
                holding for holding in holdings_list
                if holding.currency and holding.cost_currency
            ]

            # If after conversion there are no valid holdings, skip the currency
            # altogether.
            if not holdings_list:
                continue

            net_worths.append((currency, holdings_list[0].market_value))

        field_spec = [
            (0, 'Currency'),
            (1, 'Net Worth', '{:,.2f}'.format),
        ]
        return table.create_table(net_worths, field_spec)
Пример #12
0
    def _net_worth_in_periods(self):
        month_tuples = self._interval_tuples('month', self.entries)
        monthly_totals = []
        end_dates = [p[1] for p in month_tuples]

        for (begin_date, end_date), holdings_list in \
                zip(month_tuples,
                    holdings_at_dates(self.entries, end_dates,
                                      self.price_map, self.options)):
            totals = {}
            for currency in self.options['operating_currency']:
                currency_holdings_list = \
                    holdings.convert_to_currency(self.price_map, currency,
                                                 holdings_list)
                if not currency_holdings_list:
                    continue

                holdings_list = holdings.aggregate_holdings_by(
                    currency_holdings_list,
                    operator.attrgetter('cost_currency'))

                holdings_list = [holding
                                 for holding in holdings_list
                                 if holding.currency and holding.cost_currency]

                # If after conversion there are no valid holdings, skip the
                # currency altogether.
                if holdings_list:
                    totals[currency] = holdings_list[0].market_value

            monthly_totals.append({
                'begin_date': begin_date,
                'end_date': end_date,
                'totals': totals
            })
        return monthly_totals
Пример #13
0
def main():
    import argparse, logging
    logging.basicConfig(level=logging.INFO,
                        format='%(levelname)-8s: %(message)s')
    parser = argparse.ArgumentParser(description=__doc__.strip())

    parser.add_argument('--min-date',
                        action='store',
                        type=lambda string: parse(string).date(),
                        help="Minimum date")

    parser.add_argument('-o',
                        '--output',
                        action='store',
                        help="Save the figure to the given file")

    parser.add_argument('--hide',
                        action='store_true',
                        help="Mask out the vertical axis")

    parser.add_argument('--period',
                        choices=['weekly', 'monthly', 'daily'],
                        default='weekly',
                        help="Period of aggregation")

    parser.add_argument('filename', help='Beancount input filename')
    args = parser.parse_args()

    entries, errors, options_map = loader.load_file(args.filename)

    if args.min_date:
        dtstart = args.min_date
    else:
        for entry in entries:
            if isinstance(entry, data.Transaction):
                dtstart = entry.date
                break

    net_worths_dict = collections.defaultdict(list)
    index = 0
    current_entries = []

    dtend = datetime.date.today()
    if args.period == 'weekly':
        period = rrule.rrule(rrule.WEEKLY,
                             byweekday=rrule.FR,
                             dtstart=dtstart,
                             until=dtend)
    elif args.period == 'monthly':
        period = rrule.rrule(rrule.MONTHLY,
                             bymonthday=1,
                             dtstart=dtstart,
                             until=dtend)
    elif args.period == 'daily':
        period = rrule.rrule(rrule.DAILY, dtstart=dtstart, until=dtend)

    for dtime in period:
        date = dtime.date()
        logging.info(date)

        # Append new entries until the given date.
        while True:
            entry = entries[index]
            if entry.date >= date:
                break
            current_entries.append(entry)
            index += 1

        # Get the list of holdings.
        raw_holdings_list, price_map = holdings_reports.get_assets_holdings(
            current_entries, options_map)

        # Convert the currencies.
        for currency in options_map['operating_currency']:
            holdings_list = holdings.convert_to_currency(
                price_map, currency, raw_holdings_list)

            holdings_list = holdings.aggregate_holdings_by(
                holdings_list, lambda holding: holding.cost_currency)

            holdings_list = [
                holding for holding in holdings_list
                if holding.currency and holding.cost_currency
            ]

            # If after conversion there are no valid holdings, skip the currency
            # altogether.
            if not holdings_list:
                continue

            net_worths_dict[currency].append(
                (date, holdings_list[0].market_value))

    # Extrapolate milestones in various currencies.
    days_interp = 365
    if args.period == 'weekly':
        num_points = int(days_interp / 7)
    elif args.period == 'monthly':
        num_points = int(days_interp / 30)
    elif args.period == 'daily':
        num_points = 365

    lines = []
    today = datetime.date.today()
    for currency, currency_data in net_worths_dict.items():
        recent_data = currency_data[-num_points:]
        dates = [time.mktime(date.timetuple()) for date, _ in recent_data]
        values = [float(value) for _, value in recent_data]
        poly = numpy.poly1d(numpy.polyfit(dates, values, 1))

        logging.info("Extrapolations based on the last %s data points for %s:",
                     num_points, currency)
        for amount in EXTRAPOLATE_WORTHS:
            try:
                date_reach = date.fromtimestamp(
                    (amount - poly.c[1]) / poly.c[0])
                if date_reach < today:
                    continue
                time_until = (date_reach - today).days / 365.
                logging.info("%10d %s: %s (%.1f years)", amount, currency,
                             date_reach, time_until)
            except OverflowError:
                pass
        logging.info("Time to save 1M %s: %.1f years", currency,
                     (1000000 / poly.c[0]) / (365 * 24 * 60 * 60))

        dates = [today - datetime.timedelta(days=days_interp), today]
        amounts = [
            time.mktime(date.timetuple()) * poly.c[0] + poly.c[1]
            for date in dates
        ]
        lines.append((dates, amounts))

    # Plot each operating currency as a separate curve.
    for currency, currency_data in net_worths_dict.items():
        dates = [date for date, _ in currency_data]
        values = [float(value) for _, value in currency_data]
        pyplot.plot(dates, values, '-', label=currency)
    pyplot.tight_layout()
    pyplot.title("Net Worth")
    pyplot.legend(loc=2)
    if args.hide:
        pyplot.yticks([])

    for dates, amounts in lines:
        pyplot.plot(dates, amounts, 'k--')

    # Output the plot.
    if args.output:
        pyplot.savefig(args.output, figsize=(11, 8), dpi=600)
    pyplot.show()
Пример #14
0
    def test_convert_to_currency(self, entries, _, __):
        """
        2013-01-01 price CAD 1.1 USD
        ; We don't include a price point for NOK. It's unknown.
        ; 2013-01-01 open Assets:Account2
        """
        test_holdings = list(itertools.starmap(holdings.Holding, [

            # ------------ cost currency == target currency
            # currency != target currency
            (None, D('100.00'), 'IVV', D('200'), 'USD', D('10'), D('11'), D('12'), None),
            # currency == target currency
            (None, D('100.00'), 'USD', D('200'), 'USD', D('10'), D('11'), D('12'), None),
            # currency == None
            (None, D('100.00'), None, D('200'), 'USD', D('10'), D('11'), D('12'), None),

            # ------------ cost currency == other currency
            # cost currency available in price map
            (None, D('100.00'), 'XSP', D('200'), 'CAD', D('10'), D('11'), D('12'), None),
            # cost currency not available in price map
            (None, D('100.00'), 'AGF', D('200'), 'NOK', D('10'), D('11'), D('12'), None),
            # currency == target currency, available in price map
            (None, D('100.00'), 'USD', D('200'), 'CAD', D('10'), D('11'), D('12'), None),
            # currency == target currency, not available in price map
            (None, D('100.00'), 'USD', D('200'), 'NOK', D('10'), D('11'), D('12'), None),
            # cost currency available in price map, and currency == None
            (None, D('100.00'), None, D('200'), 'CAD', D('10'), D('11'), D('12'), None),

            # ------------ cost currency == None
            # currency available in price map
            (None, D('100.00'), 'CAD', D('1.2'), None, D('10'), D('11'), D('12'), None),
            # currency available in price map, with no values (should be filled in)
            (None, D('100.00'), 'CAD', D('1.2'), None, None, None, None, None),
            # currency not available in price map
            (None, D('100.00'), 'EUR', D('1.2'), None, D('10'), D('11'), D('12'), None),
            # currency = target currency
            (None, D('100.00'), 'USD', D('1.2'), None, D('10'), D('11'), D('12'), None),

            ]))

        price_map = prices.build_price_map(entries)
        converted_holdings = holdings.convert_to_currency(price_map, 'USD', test_holdings)
        expected_holdings = list(itertools.starmap(holdings.Holding, [
            (None, D('100.00'), 'IVV', D('200'), 'USD', D('10'), D('11'), D('12'), None),
            (None, D('100.00'), 'USD', D('200'), 'USD', D('10'), D('11'), D('12'), None),
            (None, D('100.00'), None, D('200'), 'USD', D('10'), D('11'), D('12'), None),
            (None, D('100.00'), 'XSP', D('220.0'), 'USD', D('11.0'), D('12.1'), D('13.2'),
             None),
            (None, D('100.00'), 'AGF', None, None, None, None, None, None),
            (None, D('100.00'), 'USD', D('220.0'), 'USD', D('11.0'), D('12.1'), D('13.2'),
             None),
            (None, D('100.00'), 'USD', None, None, None, None, None, None),
            (None, D('100.00'), None, D('220.0'), 'USD', D('11.0'), D('12.1'), D('13.2'),
             None),
            (None, D('100.00'), 'CAD', D('1.32'), 'USD', D('11.0'), D('12.1'), D('13.2'),
             None),
            (None, D('100.00'), 'CAD', D('1.32'), 'USD', D('110.0'), D('110.0'), None,
             None),
            (None, D('100.00'), 'EUR', None, None, None, None, None, None),
            (None, D('100.00'), 'USD', D('1.2'), 'USD', D('10'), D('11'), D('12'), None),
            ]))
        self.assertEqual(expected_holdings, converted_holdings)

        # Fail elegantly if the currency itself is None.
        none_holding = holdings.Holding(None, D('100.00'), None, D('200'), None,
                                        None, None, None, None)
        with self.assertRaises(ValueError):
            converted_holdings = holdings.convert_to_currency(price_map, 'USD',
                                                              [none_holding])