docs: def factor_weights(factor_data,
                     demeaned=True,
                     group_adjust=False,
                     equal_weight=False):
      """
      Computes asset weights by factor values and dividing by the sum of their
      absolute value (achieving gross leverage of 1). Positive factor values will
      results in positive weights and negative values in negative weights.
  
      Parameters
      ----------
      factor_data : pd.DataFrame - MultiIndex
          A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
          containing the values for a single alpha factor, forward returns for
          each period, the factor quantile/bin that factor value belongs to, and
          (optionally) the group the asset belongs to.
          - See full explanation in utils.get_clean_factor_and_forward_returns
      demeaned : bool
          Should this computation happen on a long short portfolio? if True,
          weights are computed by demeaning factor values and dividing by the sum
          of their absolute value (achieving gross leverage of 1). The sum of
          positive weights will be the same as the negative weights (absolute
          value), suitable for a dollar neutral long-short portfolio
      group_adjust : bool
          Should this computation happen on a group neutral portfolio? If True,
          compute group neutral weights: each group will weight the same and
          if 'demeaned' is enabled the factor values demeaning will occur on the
          group level.
      equal_weight : bool, optional
          if True the assets will be equal-weighted instead of factor-weighted
          If demeaned is True then the factor universe will be split in two
          equal sized groups, top assets with positive weights and bottom assets
          with negative weights
  
      Returns
      -------
      returns : pd.Series
          Assets weighted by factor value.
      """
  
      def to_weights(group, _demeaned, _equal_weight):
  
          if _equal_weight:
              group = group.copy()
  
              if _demeaned:
                  # top assets positive weights, bottom ones negative
                  group = group - group.median()
  
              negative_mask = group < 0
              group[negative_mask] = -1.0
              positive_mask = group > 0
              group[positive_mask] = 1.0
  
              if _demeaned:
                  # positive weights must equal negative weights
                  if negative_mask.any():
                      group[negative_mask] /= negative_mask.sum()
                  if positive_mask.any():
                      group[positive_mask] /= positive_mask.sum()
  
          elif _demeaned:
              group = group - group.mean()
  
          return group / group.abs().sum()
  
      grouper = [factor_data.index.get_level_values('date')]
      if group_adjust:
          grouper.append('group')
  
      weights = factor_data.groupby(grouper)['factor'] \
          .apply(to_weights, demeaned, equal_weight)
  
      if group_adjust:
          weights = weights.groupby(level='date').apply(to_weights, False, False)
  
      return weights