class_docstr: Portfolio Optimizer for Enhanced Indexing
  
  Notations:
      w0: current holding weights
      wb: benchmark weight
      r: expected return
      F: factor exposure
      cov_b: factor covariance
      var_u: residual variance (diagonal)
      lamb: risk aversion parameter
      delta: total turnover limit
      b_dev: benchmark deviation limit
      f_dev: factor deviation limit
  
  Also denote:
      d = w - wb: benchmark deviation
      v = d @ F: factor deviation
  
  The optimization problem for enhanced indexing:
      max_w  d @ r - lamb * (v @ cov_b @ v + var_u @ d**2)
      s.t.   w >= 0
             sum(w) == 1
             sum(|w - w0|) <= delta
             d >= -b_dev
             d <= b_dev
             v >= -f_dev
             v <= f_dev
docstr: Args:
      r (np.ndarray): expected returns
      F (np.ndarray): factor exposure
      cov_b (np.ndarray): factor covariance
      var_u (np.ndarray): residual variance
      w0 (np.ndarray): current holding weights
      wb (np.ndarray): benchmark weights
      mfh (np.ndarray): mask force holding
      mfs (np.ndarray): mask force selling
  
  Returns:
      np.ndarray: optimized portfolio allocation
docstr_tok: ["Args",":","r","(","np.ndarray",")",":","expected","returns","F","(","np.ndarray",")",":","factor","exposure","cov_b","(","np.ndarray",")",":","factor","covariance","var_u","(","np.ndarray",")",":","residual","variance","w0","(","np.ndarray",")",":","current","holding","weights","wb","(","np.ndarray",")",":","benchmark","weights","mfh","(","np.ndarray",")",":","mask","force","holding","mfs","(","np.ndarray",")",":","mask","force","selling","Returns",":","np.ndarray",":","optimized","portfolio","allocation"]
code: def __call__(
      self,
      r: np.ndarray,
      F: np.ndarray,
      cov_b: np.ndarray,
      var_u: np.ndarray,
      w0: np.ndarray,
      wb: np.ndarray,
      mfh: Optional[np.ndarray] = None,
      mfs: Optional[np.ndarray] = None,
  ) -> np.ndarray:
      """
      Args:
          r (np.ndarray): expected returns
          F (np.ndarray): factor exposure
          cov_b (np.ndarray): factor covariance
          var_u (np.ndarray): residual variance
          w0 (np.ndarray): current holding weights
          wb (np.ndarray): benchmark weights
          mfh (np.ndarray): mask force holding
          mfs (np.ndarray): mask force selling
  
      Returns:
          np.ndarray: optimized portfolio allocation
      """
      # scale return to match volatility
      if self.scale_return:
          r = r / r.std()
          r *= np.sqrt(np.mean(np.diag(F @ cov_b @ F.T) + var_u))
  
      # target weight
      w = cp.Variable(len(r), nonneg=True)
      w.value = wb  # for warm start
  
      # precompute exposure
      d = w - wb  # benchmark exposure
      v = d @ F  # factor exposure
  
      # objective
      ret = d @ r  # excess return
      risk = cp.quad_form(v, cov_b) + var_u @ (d**2)  # tracking error
      obj = cp.Maximize(ret - self.lamb * risk)
  
      # weight bounds
      lb = np.zeros_like(wb)
      ub = np.ones_like(wb)
  
      # bench bounds
      if self.b_dev is not None:
          lb = np.maximum(lb, wb - self.b_dev)
          ub = np.minimum(ub, wb + self.b_dev)
  
      # force holding
      if mfh is not None:
          lb[mfh] = w0[mfh]
          ub[mfh] = w0[mfh]
  
      # force selling
      # NOTE: this will override mfh
      if mfs is not None:
          lb[mfs] = 0
          ub[mfs] = 0
  
      # constraints
      # TODO: currently we assume fullly invest in the stocks,
      # in the future we should support holding cash as an asset
      cons = [cp.sum(w) == 1, w >= lb, w <= ub]
  
      # factor deviation
      if self.f_dev is not None:
          cons.extend(
              [v >= -self.f_dev, v <= self.f_dev]
          )  # pylint: disable=E1130
  
      # total turnover constraint
      t_cons = []
      if self.delta is not None:
          if w0 is not None and w0.sum() > 0:
              t_cons.extend([cp.norm(w - w0, 1) <= self.delta])
  
      # optimize
      # trial 1: use all constraints
      success = False
      try:
          prob = cp.Problem(obj, cons + t_cons)
          prob.solve(
              solver=cp.ECOS, warm_start=True, **self.solver_kwargs
          )
          assert prob.status == "optimal"
          success = True
      except Exception as e:
          logger.warning(f"trial 1 failed {e} (status: {prob.status})")
  
      # trial 2: remove turnover constraint
      if not success and len(t_cons):
          logger.info(
              "try removing turnover constraint as the last optimization failed"
          )
          try:
              w.value = wb
              prob = cp.Problem(obj, cons)
              prob.solve(
                  solver=cp.ECOS, warm_start=True, **self.solver_kwargs
              )
              assert prob.status in ["optimal", "optimal_inaccurate"]
              success = True
          except Exception as e:
              logger.warning(
                  f"trial 2 failed {e} (status: {prob.status})"
              )
  
      # return current weight if not success
      if not success:
          logger.warning(
              "optimization failed, will return current holding weight"
          )
          return w0
  
      if prob.status == "optimal_inaccurate":
          logger.warning(f"the optimization is inaccurate")
  
      # remove small weight
      w = np.asarray(w.value)
      w[w < self.epsilon] = 0
      w /= w.sum()
  
      return w
  
file_path: qlib/qlib/contrib/strategy/optimizer/enhanced_indexing.py
imports: import numpy
  import cvxpy
  from typing import Union, Optional, Dict, Any, List
  from qlib.log import get_module_logger
  from .base import BaseOptimizer
label_desc: Use image node_id 2 for calling the EnhancedIndexingOptimizer obj's underlying member method code with example usage: obj.__call__(r, F, cov_b, var_u, w0, wb, mfh, mfs) and returns: w, w0