docs: def tangency_portfolio(cov_mat, exp_rets, allow_short=False):
      """
      Computes a tangency portfolio, i.e. a maximum Sharpe ratio portfolio.
      
      Note: As the Sharpe ratio is not invariant with respect
      to leverage, it is not possible to construct non-trivial
      market neutral tangency portfolios. This is because for
      a positive initial Sharpe ratio the sharpe grows unbound
      with increasing leverage.
      
      Parameters
      ----------
      cov_mat: pandas.DataFrame
          Covariance matrix of asset returns.
      exp_rets: pandas.Series
          Expected asset returns (often historical returns).
      allow_short: bool, optional
          If 'False' construct a long-only portfolio.
          If 'True' allow shorting, i.e. negative weights.
  
      Returns
      -------
      weights: pandas.Series
          Optimal asset weights.
      """
      if not isinstance(cov_mat, pd.DataFrame):
          raise ValueError("Covariance matrix is not a DataFrame")
  
      if not isinstance(exp_rets, pd.Series):
          raise ValueError("Expected returns is not a Series")
  
      if not cov_mat.index.equals(exp_rets.index):
          raise ValueError("Indices do not match")
  
      n = len(cov_mat)
  
      P = opt.matrix(cov_mat.values)
      q = opt.matrix(0.0, (n, 1))
  
      # Constraints Gx <= h
      if not allow_short:
          # exp_rets*x >= 1 and x >= 0
          G = opt.matrix(np.vstack((-exp_rets.values,
                                    -np.identity(n))))
          h = opt.matrix(np.vstack((-1.0,
                                    np.zeros((n, 1)))))
      else:
          # exp_rets*x >= 1
          G = opt.matrix(-exp_rets.values).T
          h = opt.matrix(-1.0)
  
      # Solve
      optsolvers.options['show_progress'] = False
      sol = optsolvers.qp(P, q, G, h)
  
      if sol['status'] != 'optimal':
          warnings.warn("Convergence problem")
  
      # Put weights into a labeled series
      weights = pd.Series(sol['x'], index=cov_mat.index)
  
      # Rescale weights, so that sum(weights) = 1
      weights /= weights.sum()
      return weights