Пример #1
0
    def set_constraints(self, constraints: ConstraintMap, eps: float):
        assert self.x is not None

        for name, f in constraints.inequality.items():
            value = abs(f(self.x))
            if value <= eps:
                self.tight_hin.append(name)
            elif value > eps:
                self.violations.append(name)

        for name, f in constraints.equality.items():
            if abs(f(self.x)) > eps:
                self.violations.append(name)

        for eq, c_map in [("<=", constraints.inequality),
                          ("=", constraints.equality)]:
            for name, f in c_map.items():
                if len(inspect.signature(f).parameters) == 1:
                    v = f(self.x) / get_option("C.SCALE")
                else:  # at most 2 parameters
                    v = f(self.x, np.ones(
                        (len(self.x), len(self.x)))) / get_option("C.SCALE")

                self.constraint_values.append({
                    "Name": name,
                    "Equality": eq,
                    "Value": v
                })
Пример #2
0
    def __init__(self,
                 mb: ModelBuilder,
                 solution: np.ndarray,
                 scenario_solutions: np.ndarray,
                 proportions: Optional[np.ndarray],
                 dist_func: Callable[[np.ndarray], np.ndarray],
                 probability: np.ndarray,
                 eps: float = get_option("EPS.CONSTRAINT")):
        self.num_assets = mb.num_assets
        self.num_scenarios = mb.num_scenarios
        self.solution = np.asarray(solution)
        self.proportions = np.asarray(
            proportions) if proportions is not None else None
        self.scenario_solutions = np.asarray(scenario_solutions)
        self.probability = probability

        self._assets = [f"Asset_{i + 1}" for i in range(mb.num_assets)]
        self._scenarios = [
            f"Scenario_{i + 1}" for i in range(mb.num_scenarios)
        ]

        self.tight_constraint: List[str] = []
        self.violations: List[str] = []

        self._check_matrix_constraints(mb.constraints, eps)
        self._check_functional_constraints(mb.constraints, eps)

        self.scenario_objective_values = self._derive_scenario_objective_values(
            mb)
        self.regret_value = self._derive_regret_value(mb, dist_func)
        self.constraint_values = self._derive_constraint_values(mb)
Пример #3
0
def scenario_solution_equal_or_better(obj_funcs, solutions, expected):
    results = []
    for f, w, t, in zip(obj_funcs, solutions, expected):
        diff = (f(w) - f(t)) / get_option("F.SCALE")
        results.append(round(diff, 3) >= 0)

    return np.alltrue(results)
Пример #4
0
    def _derive_regret_value(
            self, mb: ModelBuilder, dist_func: Callable[[np.ndarray],
                                                        np.ndarray]) -> float:
        f_values = np.array(
            [f(s) for f, s in zip(mb.obj_funcs, self.scenario_solutions)])
        curr_f_values = np.array([f(self.solution) for f in mb.obj_funcs])

        cost = dist_func(f_values - curr_f_values) / get_option("F.SCALE")
        return sum(self.probability * cost)
Пример #5
0
    def _derive_scenario_objective_values(self, mb: ModelBuilder):
        values = []
        for f, s in zip(mb.obj_funcs, self.scenario_solutions):
            if len(inspect.signature(f).parameters) == 1:
                v = f(s)
            else:  # number of parameters can only be 2 in this case
                grad = np.ones(
                    (self.num_assets,
                     self.num_assets))  # filler gradient, not necessary
                v = f(s, grad)
            values.append(v / get_option("F.SCALE"))

        return np.array(values)
Пример #6
0
    def add_inequality_matrix_constraint(self, A, b):
        r"""
        Sets inequality constraints in standard matrix form.

        For inequality, :math:`\mathbf{A} \cdot \mathbf{x} \leq \mathbf{b}`

        Parameters
        ----------
        A: {iterable float, ndarray}
            Inequality matrix. Must be 2 dimensional.

        b: {scalar, ndarray}
            Inequality vector or scalar. If scalar, it will be propagated.
        """
        s = get_option("C.SCALE")
        self._mb.add_inequality_matrix_constraints(A * s, b * s)
        return self
Пример #7
0
    def add_equality_matrix_constraint(self, Aeq, beq):
        r"""
        Sets equality constraints in standard matrix form.

        For equality, :math:`\mathbf{A} \cdot \mathbf{x} = \mathbf{b}`

        Parameters
        ----------
        Aeq: {iterable float, ndarray}
            Equality matrix. Must be 2 dimensional

        beq: {scalar, ndarray}
            Equality vector or scalar. If scalar, it will be propagated
        """
        s = get_option("C.SCALE")
        self._mb.add_equality_matrix_constraints(Aeq * s, beq * s)
        return self
Пример #8
0
    def __init__(self, n: int, algorithm=LD_SLSQP, *args, **kwargs):
        """
        The BaseOptimizer is the raw optimizer with minimal support. For advanced users, this class will provide
        the most flexibility. The default algorithm used is Sequential Least Squares Quadratic Programming.

        Parameters
        ----------
        n: int
            number of assets

        algorithm: int or str
            the optimization algorithm

        args
            other arguments to setup the optimizer

        kwargs
            other keyword arguments
        """
        if isinstance(algorithm, str):
            algorithm = map_algorithm(algorithm)

        self._n = n
        self._model = nl.opt(algorithm, n, *args)

        has_grad = has_gradient(algorithm)
        if has_grad == 'NOT COMPILED':
            raise NotImplementedError(f"Cannot use '{nl.algorithm_name(algorithm)}' as it is not compiled")

        self._auto_grad: bool = kwargs.get('auto_grad', has_grad)
        self._eps = get_option('EPS.STEP')
        self._c_eps = get_option('EPS.CONSTRAINT')
        self.set_xtol_abs(get_option('EPS.X_ABS'))
        self.set_xtol_rel(get_option('EPS.X_REL'))
        self.set_ftol_abs(get_option('EPS.F_ABS'))
        self.set_ftol_rel(get_option('EPS.F_REL'))
        self.set_maxeval(get_option('MAX.EVAL'))

        self._cmap = ConstraintMap()
        self._result = Result()
        self._max_or_min = None
        self._verbose = kwargs.get('verbose', False)
Пример #9
0
    def _derive_constraint_values(self, mb: ModelBuilder):
        constraints = []
        for eq, constraint_map in [("<=", mb.constraints.m_inequality),
                                   ("<=", mb.constraints.inequality),
                                   ("=", mb.constraints.m_equality),
                                   ("=", mb.constraints.equality)]:
            for name, fns in constraint_map.items():
                for f, s in zip(fns, self.scenarios):
                    if len(inspect.signature(f).parameters) == 1:
                        v = f(self.solution)
                    else:  # number of parameters can only be 2 in this case
                        grad = np.ones((
                            self.num_assets,
                            self.num_assets))  # filler gradient, not necessary
                        v = f(self.solution, grad)

                    constraints.append({
                        "Name": name,
                        "Scenario": s,
                        "Equality": eq,
                        "Value": v / get_option("C.SCALE")
                    })

        return constraints
Пример #10
0
 def objective(w):
     return (self.cvar_data.cvar(w, self.rebalance, percentile) -
             self.penalty(w)) * get_option("F.SCALE")
Пример #11
0
 def add_equality_matrix_constraint(self, Aeq, beq, tol=None):
     s = get_option("C.SCALE")
     return super().add_equality_matrix_constraint(Aeq * s, beq * s, tol)
Пример #12
0
 def objective(w):
     return (self.data.sharpe_ratio(w, self.rebalance) -
             self.penalty(w)) * get_option("F.SCALE")
Пример #13
0
 def objective(w):
     return (self.data.volatility(w) +
             self.penalty(w)) * get_option("F.SCALE")
Пример #14
0
 def _ctr_min_returns(w):
     return get_option("C.SCALE") * (
         min_ret - self.data.expected_return(w, self.rebalance))
Пример #15
0
 def objective(w):
     w = format_weights(w, True)
     return (d.sharpe_ratio(w, self.rebalance) -
             self.penalties[i](w)) * get_option("F.SCALE")
Пример #16
0
 def objective(w):
     return (d.sharpe_ratio(w, self.rebalance) -
             self.penalties[i](w)) * get_option("F.SCALE")
Пример #17
0
 def objective(w):
     w = format_weights(w, is_tracking_error)
     return (d.volatility(w) +
             self.penalties[i](w)) * get_option("F.SCALE")
Пример #18
0
 def add_inequality_matrix_constraint(self, A, b, tol=None):
     s = get_option("C.SCALE")
     return super().add_inequality_matrix_constraint(A * s, b * s, tol)
Пример #19
0
 def objective(w):
     w = format_weights(w, active_cvar)
     return (d.cvar(w, self.rebalance, percentile) -
             self.penalties[i](w)) * get_option("F.SCALE")
Пример #20
0
 def constraint(w):
     w = format_weights(w, as_active_returns)
     return get_option("C.SCALE") * (
         ret - d.expected_return(w, self.rebalance))
Пример #21
0
    def __init__(self,
                 num_assets: int,
                 num_scenarios: int,
                 prob: OptArray = None,
                 algorithm=LD_SLSQP,
                 c_eps: Optional[float] = None,
                 xtol_abs: Union[float, np.ndarray, None] = None,
                 xtol_rel: Union[float, np.ndarray, None] = None,
                 ftol_abs: Optional[float] = None,
                 ftol_rel: Optional[float] = None,
                 max_eval: Optional[int] = None,
                 verbose=False,
                 sum_to_1=True,
                 max_attempts=5):
        r"""
        The RegretOptimizer is a convenience class for scenario based optimization.

        Notes
        -----
        The term regret refers to the instance where after having decided on one alternative, the choice
        of a different alternative would have led to a more optimal (better) outcome when the eventual
        scenario transpires.

        The RegretOptimizer employs a 2 stage optimization process. In the first step, the optimizer
        calculates the optimal weights for each scenario. In the second stage, the optimizer minimizes
        the regret function to give the final optimal portfolio weights.

        Assuming the objective is to maximize returns subject to some volatility constraints, the first
        stage optimization will be as listed

        .. math::

            \begin{gather*}
                \underset{w_s}{\max} R_s(w_s)  \forall s \in S \\
                s.t. \\
                \sigma_s(w_s) \leq \Sigma
            \end{gather*}

        where :math:`R_s(\cdot)` is the returns function for scenario :math:`s`, :math:`\sigma_s(\cdot)`
        is the volatility function for scenario :math:`s` and :math:`\Sigma` is the volatility threshold.
        Subsequently, to minimize the regret across all scenarios, :math:`S`,

        .. math::

            \begin{gather*}
                \underset{w}{\min} \sum_{s \in S} p_s \cdot D(R_s(w_s) - R_s(w))
            \end{gather*}

        Where :math:`D(\cdot)` is a distance function (usually quadratic) and :math:`p_s` is the discrete
        probability of scenario :math:`s` occurring.

        Parameters
        ----------
        num_assets: int
            Number of assets

        num_scenarios: int
            Number of scenarios

        prob
            Vector containing probability of each scenario occurring

        algorithm
            The optimization algorithm. Default algorithm is Sequential Least Squares Quadratic Programming.

        c_eps: float, optional
            Constraint epsilon is the tolerance for the inequality and equality constraints functions. Any
            value that is less than the constraint epsilon is considered to be within the boundary.

        xtol_abs: float or np.ndarray, optional
            Set absolute tolerances on optimization parameters. :code:`tol` is an array giving the
            tolerances: stop when an optimization step (or an estimate of the optimum) changes every
            parameter :code:`x[i]` by less than :code:`tol[i]`. For convenience, if a scalar :code:`tol`
            is given, it will be used to set the absolute tolerances in all n optimization parameters to
            the same value. Criterion is disabled if tol is non-positive.

        xtol_rel: float or np.ndarray, optional
            Set relative tolerance on optimization parameters: stop when an optimization step (or an estimate
            of the optimum) causes a relative change the parameters :code:`x` by less than :code:`tol`,
            i.e. :math:`\|\Delta x\|_w < tol \cdot \|x\|_w` measured by a weighted :math:`L_1` norm
            :math:`\|x\|_w = \sum_i w_i |x_i|`, where the weights :math:`w_i` default to 1. (If there is
            any chance that the optimal :math:`\|x\|` is close to zero, you might want to set an absolute
            tolerance with `code:`xtol_abs` as well.) Criterion is disabled if tol is non-positive.

        ftol_abs: float, optional
            Set absolute tolerance on function value: stop when an optimization step (or an estimate of
            the optimum) changes the function value by less than :code:`tol`. Criterion is disabled if
            tol is non-positive.

        ftol_rel: float, optional
            Set relative tolerance on function value: stop when an optimization step (or an estimate of
            the optimum) changes the objective function value by less than :code:`tol` multiplied by the
            absolute value of the function value. (If there is any chance that your optimum function value
            is close to zero, you might want to set an absolute tolerance with :code:`ftol_abs` as well.)
            Criterion is disabled if tol is non-positive.

        max_eval: int, optional
            Stop when the number of function evaluations exceeds :code:`maxeval`. (This is not a strict
            maximum: the number of function evaluations may exceed :code:`maxeval` slightly, depending
            upon the algorithm.) Criterion is disabled if maxeval is non-positive.

        verbose: bool
            If True, the optimizer will report its operations

        sum_to_1: bool
            If true, the optimal weights for each first level scenario must sum to 1.

        max_attempts: int
            Number of times to retry optimization. This is useful when optimization is in a highly unstable
            or non-convex space.

        See Also
        --------
        :class:`DiscreteUncertaintyOptimizer`: Discrete Uncertainty Optimizer
        """
        assert isinstance(
            num_assets, int
        ) and num_assets > 0, "num_assets must be an integer and more than 0"
        assert isinstance(
            num_scenarios, int
        ) and num_scenarios > 0, "num_assets must be an integer and more than 0"
        self._num_assets = num_assets
        self._num_scenarios = num_scenarios

        self._mb = ModelBuilder(
            num_assets, num_scenarios, algorithm, sum_to_1,
            validate_tolerance(xtol_abs or get_option('EPS.X_ABS')),
            validate_tolerance(xtol_rel or get_option('EPS.X_REL')),
            validate_tolerance(ftol_abs or get_option('EPS.F_ABS')),
            validate_tolerance(ftol_rel or get_option('EPS.F_REL')),
            int(max_eval or get_option('MAX.EVAL')), c_eps
            or get_option('EPS.CONSTRAINT'), verbose)

        self._prob = np.ones(
            num_scenarios) / num_scenarios if prob is None else np.asarray(
                prob)

        # result formatting options
        self._result: Optional[RegretResult] = None
        self._solution: Optional[RegretOptimizerSolution] = None

        assert isinstance(
            max_attempts,
            int) and max_attempts > 0, 'max_attempts must be an integer >= 1'
        self._max_attempts = max_attempts
        self._verbose = verbose
Пример #22
0
 def constraint(w):
     return get_option("C.SCALE") * (d.volatility(w) - vol)
Пример #23
0
 def constraint(w):
     return get_option("C.SCALE") * (
         ret - d.expected_return(w, self.rebalance))
Пример #24
0
 def objective(w):
     return (d.expected_return(w, self.rebalance) -
             self.penalties[i](w)) * get_option("F.SCALE")
Пример #25
0
 def constraint(w):
     return get_option("C.SCALE") * (
         cvar - d.cvar(w, self.rebalance, percentile))
Пример #26
0
 def cvar(w):
     return get_option("F.SCALE") * (target - cube.cvar(w, True, 5.0))
Пример #27
0
 def _ctr_max_cvar(w):
     return get_option("C.SCALE") * (
         max_cvar - self.cvar_data.cvar(w, self.rebalance, percentile))
Пример #28
0
 def obj_fun(w):
     return get_option("F.SCALE") * cube.expected_return(w, True)
Пример #29
0
 def _ctr_max_vol(w):
     return get_option("C.SCALE") * (self.data.volatility(w) - max_vol)
Пример #30
0
 def objective(w):
     return (self.data.expected_return(w, self.rebalance) -
             self.penalty(w)) * get_option("F.SCALE")