For a long only portfolio, convert the continuous weights to a discrete
    allocation using Mixed Integer Linear Programming. This function assumes
    that we buy some asset based on value instead of shares, and there is a
    limit of minimum value and increasing step.

    :param weights: continuous weights generated from the ``efficient_frontier`` module
    :type weights: dict
    :param min_values: the minimum value for each asset
    :type min_values: int/float or dict
    :param max_values: the maximum value for each asset
    :type max_values: int/float or dict
    :param steps: the minimum value increase for each asset
    :type steps: int/float or dict
    :param total_portfolio_value: the desired total value of the portfolio, defaults to 10000
    :type total_portfolio_value: int/float, optional
    :raises TypeError: if ``weights`` is not a dict
    :return: the number of value of each ticker that should be purchased,
    along with the amount of funds leftover.
    :rtype: (dict, float)

def portfolio_byvalue(
    weights, steps, min_values, max_values=1e9, total_portfolio_value=10000
):
    """
    For a long only portfolio, convert the continuous weights to a discrete
    allocation using Mixed Integer Linear Programming. This function assumes
    that we buy some asset based on value instead of shares, and there is a
    limit of minimum value and increasing step.

    :param weights: continuous weights generated from the ``efficient_frontier`` module
    :type weights: dict
    :param min_values: the minimum value for each asset
    :type min_values: int/float or dict
    :param max_values: the maximum value for each asset
    :type max_values: int/float or dict
    :param steps: the minimum value increase for each asset
    :type steps: int/float or dict
    :param total_portfolio_value: the desired total value of the portfolio, defaults to 10000
    :type total_portfolio_value: int/float, optional
    :raises TypeError: if ``weights`` is not a dict
    :return: the number of value of each ticker that should be purchased,
    along with the amount of funds leftover.
    :rtype: (dict, float)
    """
    import pulp

    if not isinstance(weights, dict):
        raise TypeError("weights should be a dictionary of {ticker: weight}")
    if total_portfolio_value <= 0:
        raise ValueError("total_portfolio_value must be greater than zero")

    if isinstance(steps, numbers.Real):
        steps = {k: steps for k in weights}
    if isinstance(min_values, numbers.Real):
        min_values = {k: min_values for k in weights}
    if isinstance(max_values, numbers.Real):
        max_values = {k: max_values for k in weights}

    m = pulp.LpProblem("PfAlloc", pulp.LpMinimize)
    vals = {}
    realvals = {}
    usevals = {}
    etas = {}
    abss = {}
    remaining = pulp.LpVariable("remaining", 0)
    for k, w in weights.items():
        if steps.get(k):
            vals[k] = pulp.LpVariable("x_%s" % k, 0, cat="Integer")
            realvals[k] = steps[k] * vals[k]
            etas[k] = w * total_portfolio_value - realvals[k]
        else:
            realvals[k] = vals[k] = pulp.LpVariable("x_%s" % k, 0)
            etas[k] = w * total_portfolio_value - vals[k]
        abss[k] = pulp.LpVariable("u_%s" % k, 0)
        usevals[k] = pulp.LpVariable("b_%s" % k, cat="Binary")
        m += etas[k] <= abss[k]
        m += -etas[k] <= abss[k]
        m += realvals[k] >= usevals[k] * min_values.get(k, steps.get(k, 0))
        m += realvals[k] <= usevals[k] * max_values.get(k, 1e18)
    m += remaining == total_portfolio_value - pulp.lpSum(realvals.values())
    m += pulp.lpSum(abss.values()) + remaining
    m.solve()
    results = {k: pulp.value(val) for k, val in realvals.items()}
    return results, remaining.varValue