docs: def volatility(weights, cov_matrix, gamma=0):
      """
      Calculate the volatility of a portfolio. This is actually a misnomer because
      the function returns variance, which is technically the correct objective
      function when minimising volatility.
  
      :param weights: asset weights of the portfolio
      :type weights: np.ndarray
      :param cov_matrix: the covariance matrix of asset returns
      :type cov_matrix: pd.DataFrame
      :param gamma: L2 regularisation parameter, defaults to 0. Increase if you want more
                    non-negligible weights
      :type gamma: float, optional
      :return: portfolio variance
      :rtype: float
      """
      L2_reg = gamma * (weights ** 2).sum()
      portfolio_volatility = np.dot(weights.T, np.dot(cov_matrix, weights))
      return portfolio_volatility + L2_reg