def test_get_all_prices(self, entries, _, __): """ 2013-06-01 price USD 1.01 CAD 2013-06-03 price USD 1.03 CAD 2013-06-05 price USD 1.05 CAD 2013-06-07 price USD 1.07 CAD 2013-06-09 price USD 1.09 CAD 2013-06-11 price USD 1.11 CAD """ price_map = prices.build_price_map(entries) price_list = prices.get_all_prices(price_map, ('USD', 'CAD')) expected = [(datetime.date(2013, 6, 1), D('1.01')), (datetime.date(2013, 6, 3), D('1.03')), (datetime.date(2013, 6, 5), D('1.05')), (datetime.date(2013, 6, 7), D('1.07')), (datetime.date(2013, 6, 9), D('1.09')), (datetime.date(2013, 6, 11), D('1.11'))] self.assertEqual(expected, price_list) inv_price_list = prices.get_all_prices(price_map, ('CAD', 'USD')) self.assertEqual(len(price_list), len(inv_price_list)) # Test not found. with self.assertRaises(KeyError): prices.get_all_prices(price_map, ('EWJ', 'JPY'))
def generate_price_pages(account_data_map: Dict[Account, AccountData], price_map: prices.PriceMap, output_dir: str): """Produce renders of price time series for each currency. This should help us debug issues with price recording, in particulawr, with respect to stock splits.""" pricer = returnslib.Pricer(price_map) # Write out a returns file for every account. os.makedirs(output_dir, exist_ok=True) pairs = set((ad.currency, ad.cost_currency) for ad in account_data_map.values() if ad.currency and ad.cost_currency) for base_quote in sorted(pairs): logging.info("Producing price page for %s", base_quote) all_prices = prices.get_all_prices(price_map, base_quote) if not all_prices: continue dates = np.array([date for date, _ in all_prices]) prices_ = np.array([price for _, price in all_prices]) fig, ax = plt.subplots(1, 1, figsize=[10, 4]) set_axis(ax, dates[0], dates[-1]) ax.set_title("Prices for {} ({})".format(*base_quote)) ax.plot(dates, prices_, linewidth=0.5) ax.scatter(dates, prices_, s=2.0) fig.autofmt_xdate() fig.tight_layout() filename = path.join(output_dir, "{}_{}.svg".format(*base_quote)) plt.savefig(filename) plt.close(fig)
def plot_prices(output_dir: str, price_map: prices.PriceMap, pairs: List[CurrencyPair]) -> Dict[str, str]: """Render one or more plots of prices.""" # Group by quote currencies. outplots = {} series = collections.defaultdict(list) for c, qc in pairs: series[qc].append(c) fig, axs = plt.subplots(len(series), 1, sharex=True, figsize=[10, 2 * len(series)]) if len(series) == 1: axs = [axs] for index, (qc, currencies) in enumerate(sorted(series.items())): ax = axs[index] for currency in currencies: price_points = prices.get_all_prices(price_map, (currency, qc)) # Render cash flows. dates = [date for date, _ in price_points] prices_ = [float(price) for _, price in price_points] set_axis(ax, dates[0], dates[-1]) ax.plot(dates, prices_, linewidth=0.3) ax.scatter(dates, prices_, s=1.2) fig.autofmt_xdate() fig.tight_layout() filename = outplots["price"] = path.join(output_dir, "price.svg") plt.savefig(filename) plt.close(fig) return outplots
def prices(self, base, quote): """List all prices.""" all_prices = prices.get_all_prices(self.price_map, (base, quote)) if self._filters["time"]: return [(date, price) for date, price in all_prices if self._filters["time"].begin_date <= date < self._filters["time"].end_date] return all_prices
def prices(self, base, quote): """List all prices.""" all_prices = prices.get_all_prices(self.price_map, (base, quote)) if self._filters['time']: return [(date, price) for date, price in all_prices if (date >= self._filters['time'].begin_date and date < self._filters['time'].end_date)] return all_prices
def prices(self, base, quote): """List all prices.""" all_prices = prices.get_all_prices(self.price_map, (base, quote)) if self._filters['time']: return [(date, price) for date, price in all_prices if self._filters['time'].begin_date <= date < self._filters['time'].end_date] return all_prices
def prices(self, base: str, quote: str) -> List[Tuple[datetime.date, Decimal]]: """List all prices.""" all_prices = prices.get_all_prices(self.price_map, (base, quote)) if self.filters.time: return [(date, price) for date, price in all_prices if self.filters.time.begin_date <= date < self.filters.time.end_date] return all_prices
def prices(self, base: str, quote: str) -> list[tuple[datetime.date, Decimal]]: """List all prices.""" all_prices = get_all_prices(self.ledger.price_map, (base, quote)) if (self.filters.time and self.filters.time.begin_date is not None and self.filters.time.end_date is not None): return [(date, price) for date, price in all_prices if self.filters.time.begin_date <= date < self.filters.time.end_date] return all_prices
def test_project(self, entries, _, __): """ 2013-06-01 price USD 1.12 CAD 2013-06-15 price HOOL 1000.00 USD 2013-06-15 price MFFT 200.00 USD 2013-07-01 price USD 1.13 CAD 2013-07-15 price HOOL 1010.00 USD """ price_map = prices.build_price_map(entries) # Check that a degenerate request does nothing. noop_price_map = prices.project(price_map, "USD", "USD") self.assertIs(noop_price_map, price_map) # Project and make sure the old price map hasn't been mutated. new_price_map = prices.project(price_map, "USD", "CAD") self.assertFalse(("HOOL", "CAD") in price_map) # Check that the prices were converted. Note that this also checks that # no price was synthesized at 2013-07-01 (see {c1bd24f8d4b7}). self.assertEqual([(datetime.date(2013, 6, 15), D('1120.0000')), (datetime.date(2013, 7, 15), D('1141.3000'))], prices.get_all_prices(new_price_map, ("HOOL", "CAD"))) # Make sure the inverted database has been updated. self.assertEqual([(datetime.date( 2013, 6, 15), D('0.0008928571428571428571428571429')), (datetime.date(2013, 7, 15), D('0.0008761938140716726539910628231'))], prices.get_all_prices(new_price_map, ("CAD", "HOOL"))) # Check constraint on currencies. {4bb702d82c8a} cons_price_map = prices.project(price_map, "USD", "CAD", {"MFFT"}) self.assertTrue(("HOOL", "CAD") in new_price_map) self.assertFalse(("HOOL", "CAD") in cons_price_map) # Check that the resulting price mapis sorted. for prices_ in new_price_map.values(): self.assertTrue( all(x <= y for x, y in zip(prices_[:-1], prices_[1:])))
def test_project_missing(self, entries, _, __): """ 2013-06-15 price HOOL 1000.00 USD 2013-07-01 price USD 1.12 CAD 2013-07-15 price HOOL 1100.00 USD """ price_map = prices.build_price_map(entries) new_price_map = prices.project(price_map, "USD", "CAD") # Check that there haven't been conversions before a price was # available. {b2b23353275d} self.assertEqual([(datetime.date(2013, 7, 15), D('1232.0000'))], prices.get_all_prices(new_price_map, ("HOOL", "CAD")))
def test_project_collisions(self, entries, _, __): """ 2013-06-01 price USD 1.12 CAD 2013-06-15 price HOOL 1000.00 USD 2013-06-15 price HOOL 1125.00 CAD """ price_map = prices.build_price_map(entries) new_price_map = prices.project(price_map, "USD", "CAD") # Check that the original prices in the database were not overridden. # See {97a5703ac517}. self.assertEqual([(datetime.date(2013, 6, 15), D('1120.0000')), (datetime.date(2013, 6, 15), D('1125.00'))], prices.get_all_prices(new_price_map, ("HOOL", "CAD")))
def get_date_rates(self, entries): if not self.args.commodity: self.parser.error( "Commodity pair must be specified (in BASE/QUOTE format)") if not re.match('{ccy}/{ccy}$'.format(ccy=amount.CURRENCY_RE), self.args.commodity): self.parser.error( ('Invalid commodity pair "{}"; ' 'must be in BASE/QUOTE format').format(self.args.commodity)) price_map = prices.build_price_map(entries) try: date_rates = prices.get_all_prices(price_map, self.args.commodity) except KeyError: self.parser.error("Commodity not present in database: {}".format( self.args.commodity)) return date_rates
def compute_portfolio_values( price_map: prices.PriceMap, transactions: data.Entries) -> Tuple[List[Date], List[float]]: """Compute a serie of portfolio values over time.""" # Infer the list of required prices. currency_pairs = set() for entry in transactions: for posting in entry.postings: if posting.meta["category"] is Cat.ASSET: if posting.cost: currency_pairs.add( (posting.units.currency, posting.cost.currency)) first = lambda x: x[0] price_dates = sorted(itertools.chain( ((date, None) for pair in currency_pairs for date, _ in prices.get_all_prices(price_map, pair)), ((entry.date, entry) for entry in transactions)), key=first) # Iterate computing the balance. value_dates = [] value_values = [] balance = Inventory() for date, group in itertools.groupby(price_dates, key=first): # Update balances. for _, entry in group: if entry is None: continue for posting in entry.postings: if posting.meta["category"] is Cat.ASSET: balance.add_position(posting) # Convert to market value. value_balance = balance.reduce(convert.get_value, price_map, date) cost_balance = value_balance.reduce(convert.convert_position, "USD", price_map) pos = cost_balance.get_only_position() value = pos.units.number if pos else ZERO # Add one data point. value_dates.append(date) value_values.append(value) return value_dates, value_values