def test_single_closed(self): intervals = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 10))] expected = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 10))] trimmed = lifetimes.trim_intervals(intervals, None, None) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 7), datetime.date(2014, 3, 10))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 2, 7), None) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 3))] trimmed = lifetimes.trim_intervals(intervals, None, datetime.date(2014, 3, 3)) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 7), datetime.date(2014, 3, 3))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 2, 7), datetime.date(2014, 3, 3)) self.assertEqual(expected, trimmed) expected = [] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 1, 2), datetime.date(2014, 2, 2)) self.assertEqual(expected, trimmed) expected = [] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 3, 11), datetime.date(2014, 3, 20)) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 3))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 1, 2), datetime.date(2014, 3, 3)) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 7), datetime.date(2014, 3, 10))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 2, 7), datetime.date(2014, 3, 20)) self.assertEqual(expected, trimmed) expected = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 10))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 1, 2), datetime.date(2014, 3, 20)) self.assertEqual(expected, trimmed)
def test_multiple(self): intervals = [(datetime.date(2014, 2, 3), datetime.date(2014, 3, 10)), (datetime.date(2014, 3, 20), datetime.date(2014, 7, 1))] expected = [(datetime.date(2014, 2, 7), datetime.date(2014, 3, 10)), (datetime.date(2014, 3, 20), datetime.date(2014, 5, 1))] trimmed = lifetimes.trim_intervals(intervals, datetime.date(2014, 2, 7), datetime.date(2014, 5, 1)) self.assertEqual(expected, trimmed)
def get_price_jobs_up_to_date(entries, date_last=None, inactive=False, undeclared_source=None, update_rate='weekday', compress_days=1): """Get a list of trailing prices to fetch from a stream of entries. The list of dates runs from the latest available price up to the latest date. Args: entries: list of Beancount entries date_last: The date up to where to find prices to as an exclusive range end. inactive: Include currencies with no balance at the given date. The default is to only include those currencies which have a non-zero balance. undeclared_source: A string, the name of the default source module to use to pull prices for commodities without a price source metadata on their Commodity directive declaration. Returns: A list of DatedPrice instances. """ price_map = prices.build_price_map(entries) # Find the list of declared currencies, and from it build a mapping for # tickers for each (base, quote) pair. This is the only place tickers # appear. declared_triples = find_currencies_declared(entries, date_last) currency_map = {(base, quote): psources for base, quote, psources in declared_triples} # Compute the initial list of currencies to consider. if undeclared_source: # Use the full set of possible currencies. cur_at_cost = find_prices.find_currencies_at_cost(entries) cur_converted = find_prices.find_currencies_converted( entries, date_last) cur_priced = find_prices.find_currencies_priced(entries, date_last) currencies = cur_at_cost | cur_converted | cur_priced log_currency_list("Currency held at cost", cur_at_cost) log_currency_list("Currency converted", cur_converted) log_currency_list("Currency priced", cur_priced) default_source = import_source(undeclared_source) else: # Use the currencies from the Commodity directives. currencies = set(currency_map.keys()) default_source = None log_currency_list("Currencies in primary list", currencies) # By default, restrict to only the currencies with non-zero balances # up to the given date. # Also, find the earliest start date to fetch prices from. # Look at both latest prices and start dates. lifetimes_map = lifetimes.get_commodity_lifetimes(entries) commodity_map = getters.get_commodity_directives(entries) price_start_dates = {} stale_currencies = set() if inactive: for base_quote in currencies: if lifetimes_map[base_quote]: # Use first date from lifetime lifetimes_map[base_quote] = [(lifetimes_map[base_quote][0][0], None)] else: # Insert never active commodities into lifetimes # Start from date of currency directive base, _ = base_quote commodity_entry = commodity_map.get(base, None) lifetimes_map[base_quote] = [(commodity_entry.date, None)] else: #Compress any lifetimes based on compress_days lifetimes_map = lifetimes.compress_lifetimes_days( lifetimes_map, compress_days) #Trim lifetimes based on latest price dates. for base_quote in lifetimes_map: intervals = lifetimes_map[base_quote] result = prices.get_latest_price(price_map, base_quote) if (result is None or result[0] is None): lifetimes_map[base_quote] = \ lifetimes.trim_intervals(intervals, None, date_last) else: latest_price_date = result[0] date_first = latest_price_date + datetime.timedelta(days=1) if date_first < date_last: lifetimes_map[base_quote] = \ lifetimes.trim_intervals(intervals, date_first, date_last) else: # We don't need to update if we're already up to date. lifetimes_map[base_quote] = [] # Remove currency pairs we can't fetch any prices for. if not default_source: keys = list(lifetimes_map.keys()) for key in keys: if not currency_map.get(key, None): del lifetimes_map[key] # Create price jobs based on fetch rate if update_rate == 'daily': required_prices = lifetimes.required_daily_prices(lifetimes_map, date_last, weekdays_only=False) elif update_rate == 'weekday': required_prices = lifetimes.required_daily_prices(lifetimes_map, date_last, weekdays_only=True) elif update_rate == 'weekly': required_prices = lifetimes.required_weekly_prices( lifetimes_map, date_last) else: raise ValueError('Invalid Update Rate') jobs = [] # Build up the list of jobs to fetch prices for. for key in required_prices: date, base, quote = key psources = currency_map.get((base, quote), None) if not psources: psources = [PriceSource(default_source, base, False)] jobs.append(DatedPrice(base, quote, date, psources)) return sorted(jobs)