docs: def calc_rets(returns, weights):
      """
      Calculate continuous return series for futures instruments. These consist
      of weighted underlying instrument returns, who's weights can vary over
      time.
  
      Parameters
      ----------
      returns: pandas.Series or dict
          A Series of instrument returns with a MultiIndex where the top level is
          pandas.Timestamps and the second level is instrument names. Values
          correspond to one period instrument returns. returns should be
          available for all for all Timestamps and instruments provided in
          weights. If dict is given this should be a dict of pandas.Series in the
          above format, with keys which are a subset of the keys given in weights
      weights: pandas.DataFrame or dict
          A DataFrame of instrument weights with a MultiIndex where the top level
          contains pandas.Timestamps and the second level is instrument names.
          The columns consist of generic names. If dict is given this should be
          a dict of pandas.DataFrame in the above format, with keys for different
          root generics, e.g. 'CL'
  
      Returns
      -------
      A pandas.DataFrame of continuous returns for generics. The index is
      pandas.Timestamps and the columns is generic names, corresponding to
      weights.columns
  
      Examples
      --------
      >>> import pandas as pd
      >>> import mapping.util as util
      >>> idx = pd.MultiIndex.from_tuples([(pd.Timestamp('2015-01-02'), 'CLF5'),
      ...                                  (pd.Timestamp('2015-01-03'), 'CLF5'),
      ...                                  (pd.Timestamp('2015-01-03'), 'CLG5'),
      ...                                  (pd.Timestamp('2015-01-04'), 'CLF5'),
      ...                                  (pd.Timestamp('2015-01-04'), 'CLG5'),
      ...                                  (pd.Timestamp('2015-01-05'), 'CLG5')])
      >>> price = pd.Series([45.63, 45.85, 46.13, 46.05, 46.25, 46.20], index=idx)
      >>> vals = [1, 1/2, 1/2, 1]
      >>> widx = pd.MultiIndex.from_tuples([(pd.Timestamp('2015-01-03'), 'CLF5'),
      ...                                   (pd.Timestamp('2015-01-04'), 'CLF5'),
      ...                                   (pd.Timestamp('2015-01-04'), 'CLG5'),
      ...                                   (pd.Timestamp('2015-01-05'), 'CLG5')])
      >>> weights = pd.DataFrame(vals, index=widx, columns=["CL1"])
      >>> irets = price.groupby(level=-1).pct_change()
      >>> util.calc_rets(irets, weights)
      """  # NOQA
  
      if not isinstance(returns, dict):
          returns = {"": returns}
      if not isinstance(weights, dict):
          weights = {"": weights}
  
      generic_superset = []
      for root in weights:
          generic_superset.extend(weights[root].columns.tolist())
      if len(set(generic_superset)) != len(generic_superset):
          raise ValueError("Columns for weights must all be unique")
  
      _check_indices(returns, weights)
  
      grets = []
      cols = []
      for root in returns:
          root_wts = weights[root]
          root_rets = returns[root]
          for generic in root_wts.columns:
              gnrc_wts = root_wts.loc[:, generic]
              # drop generics where weight is 0, this avoids potential KeyError
              # in later indexing of rets even when ret has weight of 0
              gnrc_wts = gnrc_wts.loc[gnrc_wts != 0]
              rets = root_rets.loc[gnrc_wts.index]
              # groupby time
              group_rets = (rets * gnrc_wts).groupby(level=0)
              grets.append(group_rets.apply(pd.DataFrame.sum, skipna=False))
          cols.extend(root_wts.columns.tolist())
  
      rets = pd.concat(grets, axis=1, keys=cols).sort_index(axis=1)
      return rets