def join(holdings_list, features_map, keyfun): """Join a list of holdings with a vector of arbitrary features. This function joins a list of holdings with a features_map, where the join key between the two mappings is provided by a configuration callable 'keyfun'. keyfun() is run on every holding, and the resulting key is looked up from features_map. All the values from features_map are first normalized to 100%, and when aggregating, the 'market_value' attribute of the holdings is multiplied by the normalized feature value. Args: holdings_list: A list of holdings.Holding instances. features_map: A dict of some key to a dict of features. The values are dicts of strings to a number. keyfun: A function that produces the join key from a Holding instance. The key is used to look up a feature vector from the features_map dict. Returns: A dict of labels (from the values of features_map) to a pair of: Decimal number of market_value amounts, and a list of corresponding scaled holdings. """ # Get the full list of features. all_labels = set(label for features in features_map.values() for label in features) features_total = {label: D('0') for label in all_labels} features_holdings = {label: [] for label in all_labels} # Normalize the feature vectors. norm_features_map = { key: normalize_features(features) for key, features in features_map.items() } # Accumulate the market value of each holding in buckets for each label. for holding in holdings_list: key = keyfun(holding) if key is None: logging.debug("Key not found: %s, %s, %s", holding.account, holding.currency, holding.cost_currency) try: features = norm_features_map[key] except KeyError as exc: raise KeyError("Key '{}' not found in map {}".format( key, norm_features_map)) for label, fraction in features.items(): if not holding.market_value: continue scaled_holding = holdings.scale_holding(holding, D(fraction)) features_total[label] += scaled_holding.market_value features_holdings[label].append(scaled_holding) return { label: (features_total[label], features_holdings[label]) for label in all_labels }
def test_scale_holding(self): test_holding = holdings.Holding( 'Assets:US:Checking', D('100'), 'MSFT', D('54.34'), 'USD', D('5434.00'), D('6000.00'), D('60'), datetime.date(2012, 5, 2)) expected_holding = holdings.Holding( 'Assets:US:Checking', D('70.0'), 'MSFT', D('54.34'), 'USD', D('3803.80'), D('4200.00'), D('60'), datetime.date(2012, 5, 2)) self.assertEqual(expected_holding, holdings.scale_holding(test_holding, D('0.7')))