docs: def positions(weights, period, freq=None):
      """
      Builds net position values time series, the portfolio percentage invested
      in each position.
  
      Parameters
      ----------
      weights: pd.Series
          pd.Series containing factor weights, the index contains timestamps at
          which the trades are computed and the values correspond to assets
          weights
          - see factor_weights for more details
      period: pandas.Timedelta or string
          Assets holding period (1 day, 2 mins, 3 hours etc). It can be a
          Timedelta or a string in the format accepted by Timedelta constructor
          ('1 days', '1D', '30m', '3h', '1D1h', etc)
      freq : pandas DateOffset, optional
          Used to specify a particular trading calendar. If not present
          weights.index.freq will be used
  
      Returns
      -------
      pd.DataFrame
          Assets positions series, datetime on index, assets on columns.
          Example:
              index                 'AAPL'         'MSFT'          cash
              2004-01-09 10:30:00   13939.3800     -14012.9930     711.5585
              2004-01-09 15:30:00       0.00       -16012.9930     411.5585
              2004-01-12 10:30:00   14492.6300     -14624.8700       0.0
              2004-01-12 15:30:00   14874.5400     -15841.2500       0.0
              2004-01-13 10:30:00   -13853.2800    13653.6400      -43.6375
      """
  
      weights = weights.unstack()
  
      if not isinstance(period, pd.Timedelta):
          period = pd.Timedelta(period)
  
      if freq is None:
          freq = weights.index.freq
  
      if freq is None:
          freq = BDay()
          warnings.warn("'freq' not set, using business day calendar",
                        UserWarning)
  
      #
      # weights index contains factor computation timestamps, then add returns
      # timestamps too (factor timestamps + period) and save them to 'full_idx'
      # 'full_idx' index will contain an entry for each point in time the weights
      # change and hence they have to be re-computed
      #
      trades_idx = weights.index.copy()
      returns_idx = utils.add_custom_calendar_timedelta(trades_idx, period, freq)
      weights_idx = trades_idx.union(returns_idx)
  
      #
      # Compute portfolio weights for each point in time contained in the index
      #
      portfolio_weights = pd.DataFrame(index=weights_idx,
                                       columns=weights.columns)
      active_weights = []
  
      for curr_time in weights_idx:
  
          #
          # fetch new weights that become available at curr_time and store them
          # in active weights
          #
          if curr_time in weights.index:
              assets_weights = weights.loc[curr_time]
              expire_ts = utils.add_custom_calendar_timedelta(curr_time,
                                                              period, freq)
              active_weights.append((expire_ts, assets_weights))
  
          #
          # remove expired entry in active_weights (older than 'period')
          #
          if active_weights:
              expire_ts, assets_weights = active_weights[0]
              if expire_ts <= curr_time:
                  active_weights.pop(0)
  
          if not active_weights:
              continue
          #
          # Compute total weights for curr_time and store them
          #
          tot_weights = [w for (ts, w) in active_weights]
          tot_weights = pd.concat(tot_weights, axis=1)
          tot_weights = tot_weights.sum(axis=1)
          tot_weights /= tot_weights.abs().sum()
  
          portfolio_weights.loc[curr_time] = tot_weights
  
      return portfolio_weights.fillna(0)