def portfolio(weights, latest_prices, min_allocation=0.01, total_portfolio_value=10000):
    """
    For a long only portfolio, convert the continuous weights to a discrete allocation
    in a greedy iterative approach. This can be thought of as a clever way to round
    the continuous weights to an integer number of shares

    :param weights: continuous weights generated from the ``efficient_frontier`` module
    :type weights: dict
    :param latest_prices: the most recent price for each asset
    :type latest_prices: pd.Series or dict
    :param min_allocation: any weights less than this number are considered negligible,
                           defaults to 0.01
    :type min_allocation: float, optional
    :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
    :raises TypeError: if ``latest_prices`` isn't a series
    :raises ValueError: if not ``0 < min_allocation < 0.3``
    :return: the number of shares of each ticker that should be purchased, along with the amount
             of funds leftover.
    :rtype: (dict, float)
    """
    if not isinstance(weights, dict):
        raise TypeError("weights should be a dictionary of {ticker: weight}")
    if not isinstance(latest_prices, (pd.Series, dict)):
        raise TypeError("latest_prices should be a pd.Series")
    if min_allocation > 0.3:
        raise ValueError("min_allocation should be a small float")
    if total_portfolio_value <= 0:
        raise ValueError("total_portfolio_value must be greater than zero")

    # Drop any companies with negligible weights. We use a tuple because order matters.
    nonzero_weights = [(k, v) for k, v in weights.items() if v > min_allocation]
    print(
        "{} out of {} tickers were removed".format(
            len(weights) - len(nonzero_weights), len(weights)
        )
    )
    # Sort in descending order of weight
    nonzero_weights.sort(key=lambda x: x[1], reverse=True)
    available_funds = total_portfolio_value
    shares_bought = []
    buy_prices = []

    # First round
    for ticker, weight in nonzero_weights:
        price = latest_prices[ticker]
        # Attempt to buy the lower integer number of shares
        n_shares = int(weight * total_portfolio_value / price)
        cost = n_shares * price
        if cost > available_funds:
            # Buy as many as possible
            n_shares = available_funds // price
            if n_shares == 0:
                print("Insufficient funds")
        available_funds -= cost
        shares_bought.append(n_shares)
        buy_prices.append(price)

    # Second round
    while available_funds > 0:
        # Calculate the equivalent continuous weights of the shares that
        # have already been bought
        current_weights = np.array(buy_prices) * np.array(shares_bought)
        current_weights /= current_weights.sum()
        ideal_weights = np.array([i[1] for i in nonzero_weights])
        deficit = ideal_weights - current_weights

        # Attempt to buy the asset whose current weights deviate the most
        idx = np.argmax(deficit)
        ticker, weight = nonzero_weights[idx]
        price = latest_prices[ticker]

        # If we can't afford this asset, search for the next highest deficit that we
        # can purchase.
        counter = 0
        while price > available_funds:
            deficit[idx] = 0  # we can no longer purchase the asset at idx
            idx = np.argmax(deficit)  # find the next most deviant asset

            # If either of these conditions is met, we break out of both while loops
            # hence the repeated statement below
            if deficit[idx] < 0 or counter == 10:
                break

            ticker, weight = nonzero_weights[idx]
            price = latest_prices[ticker]
            counter += 1

        if deficit[idx] <= 0 or counter == 10:
            # See https://stackoverflow.com/questions/189645/
            break

        # Buy one share at a time
        shares_bought[idx] += 1
        available_funds -= price

    print("Funds remaining: {:.2f}".format(available_funds))

    num_shares = dict(zip([i[0] for i in nonzero_weights], shares_bought))
    return num_shares, available_funds
def portfolio ( weights , latest_prices , min_allocation = 0.01 , total_portfolio_value = 10000 ) : if not isinstance ( weights , dict ) : raise TypeError ( "weights should be a dictionary of {ticker: weight}" ) if not isinstance ( latest_prices , ( pd . Series , dict ) ) : raise TypeError ( "latest_prices should be a pd.Series" ) if min_allocation > 0.3 : raise ValueError ( "min_allocation should be a small float" ) if total_portfolio_value <= 0 : raise ValueError ( "total_portfolio_value must be greater than zero" ) nonzero_weights = [ ( k , v ) for k , v in weights . items ( ) if v > min_allocation ] print ( "{} out of {} tickers were removed" . format ( len ( weights ) - len ( nonzero_weights ) , len ( weights ) ) ) nonzero_weights . sort ( key = lambda x : x [ 1 ] , reverse = True ) available_funds = total_portfolio_value shares_bought = [ ] buy_prices = [ ] for ticker , weight in nonzero_weights : price = latest_prices [ ticker ] n_shares = int ( weight * total_portfolio_value / price ) cost = n_shares * price if cost > available_funds : n_shares = available_funds // price if n_shares == 0 : print ( "Insufficient funds" ) available_funds -= cost shares_bought . append ( n_shares ) buy_prices . append ( price ) while available_funds > 0 : current_weights = np . array ( buy_prices ) * np . array ( shares_bought ) current_weights /= current_weights . sum ( ) ideal_weights = np . array ( [ i [ 1 ] for i in nonzero_weights ] ) deficit = ideal_weights - current_weights idx = np . argmax ( deficit ) ticker , weight = nonzero_weights [ idx ] price = latest_prices [ ticker ] counter = 0 while price > available_funds : deficit [ idx ] = 0 idx = np . argmax ( deficit ) if deficit [ idx ] < 0 or counter == 10 : break ticker , weight = nonzero_weights [ idx ] price = latest_prices [ ticker ] counter += 1 if deficit [ idx ] <= 0 or counter == 10 : break shares_bought [ idx ] += 1 available_funds -= price print ( "Funds remaining: {:.2f}" . format ( available_funds ) ) num_shares = dict ( zip ( [ i [ 0 ] for i in nonzero_weights ] , shares_bought ) ) return num_shares , available_funds
For a long only portfolio, convert the continuous weights to a discrete allocation
    in a greedy iterative approach. This can be thought of as a clever way to round
    the continuous weights to an integer number of shares

    :param weights: continuous weights generated from the ``efficient_frontier`` module
    :type weights: dict
    :param latest_prices: the most recent price for each asset
    :type latest_prices: pd.Series or dict
    :param min_allocation: any weights less than this number are considered negligible,
                           defaults to 0.01
    :type min_allocation: float, optional
    :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
    :raises TypeError: if ``latest_prices`` isn't a series
    :raises ValueError: if not ``0 < min_allocation < 0.3``
    :return: the number of shares of each ticker that should be purchased, along with the amount
             of funds leftover.
    :rtype: (dict, float)