示例#1
0
def analyze(df, portfolio_option, weight_by=None, default_correlation=1):
    ''' analyze portfolio_option and return the annual_ret, std_dev, sharpe_ratio
        portfolio_option may also be modified if weight_by is not None
        default_correlation - correlation to use when no correlation has been
        specified between two asset classes.  If you use only the Asset Classes
        defined in universe/asset-classes.csv, then this will never happen.
    '''

    # pop the title
    __m.portfolio_title = portfolio_option.pop('Title', __m.portfolio_title)

    # set default correlation
    __m.default_correlation = default_correlation

    # get metric_lists
    ml = _get_metric_lists(df, portfolio_option)
    bb.DBG('ml = {}'.format(ml))

    # assign weights
    _assign_weights(df, ml, portfolio_option, weight_by)

    # make sure total adds to 100
    _check_allocation(portfolio_option, __m.portfolio_title)

    # update metric_lists
    ml = _get_metric_lists(df, portfolio_option)

    # compute metrics
    annual_return = _expected_return(ml.annual_returns, ml.weights)
    std_dev = _standard_deviation(ml.weights, ml.std_devs, ml.asset_classes)
    sharpe_ratio = _sharpe_ratio(annual_return, std_dev, __m.risk_free_rate)

    return annual_return, std_dev, sharpe_ratio
示例#2
0
def _get_metric_lists(df, portfolio_option):
    ''' creates lists for investment option, std_dev, asset_class, etc...
        for each investment option for the specified portfolio.
        returns a cache copy unless refreshset is True
    '''

    # check for valid investment options
    available_inv_opts = list(df['Investment Option'])
    bb.DBG('available_inv_opts', available_inv_opts)
    bb.DBG('portfolio_option', portfolio_option)
    for key in portfolio_option.keys():
        if key not in available_inv_opts:
            raise Exception("Error: Portfolio option '{}' not in {}!!!".format(
                key, __m.investment_universe))
    ml = dotdict()

    ml.inv_opts = \
        [key for key in portfolio_option.keys()]
    ml.weights = \
        [value for value in portfolio_option.values()]
    ml.asset_classes = \
        [df.loc[df['Investment Option'] == inv_opt,
        'Asset Class'].values[0] for inv_opt in ml.inv_opts]
    ml.volas = \
        [df.loc[df['Investment Option'] == inv_opt,
        __m.vola_column].values[0] for inv_opt in ml.inv_opts]
    ml.ds_volas = \
        [df.loc[df['Investment Option'] == inv_opt,
        __m.ds_vola_column].values[0] for inv_opt in ml.inv_opts]
    ml.std_devs = \
        [df.loc[df['Investment Option'] == inv_opt,
        'Std Dev'].values[0] for inv_opt in ml.inv_opts]
    ml.annual_returns = \
        [df.loc[df['Investment Option'] == inv_opt,
        'Annual Returns'].values[0] for inv_opt in ml.inv_opts]
    ml.sharpe_ratios = \
        [df.loc[df['Investment Option'] == inv_opt,
        'Sharpe Ratio'].values[0] for inv_opt in ml.inv_opts]

    return ml
示例#3
0
def analyze(df, portfolio_option, weight_by=None, default_correlation=1):
    """
    Analyze Portfolio.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of investment options with columns for asset class,
        description, and performace metrics.
    portfolio_option : dict
        Dictionary of investment options along with their weights.  The
        keys are the investment options and the values are the weights.
        The first entry in the dict must be the title of the portfolio.
        `portfolio_option` may be modified if `weight_by` is not None,
        i.e. the weights for each investment option might be adjusted.
    weight_by : dict of dicts, optional
        Specify the weighting scheme.  If not None, it will replace the
        weights specified in the portfolio.  You can also fix the
        weights on some Investent Options, Asset Classes, and Asset
        Subclasses, while the others are automatically calculated
        (default is None, which implies use the user specified weights
        specified in `portfolio_option`).

        'Equal' - use equal weights.

        'Sharpe Ratio' - use proportionally weighted allocations
        based on the percent of an investment option's sharpe ratio to
        the sum of all the sharpe ratios in the portfolio.

        'Std Dev' - use standard deviation adjusted weights.

        'Annual Returns' - use return adjusted weights.

        'Vola' - use volatility adjusted weights.

        'DS Vola' - use downside volatility adjusted weights.

        None:   'Investment Option' means use user specified weights
                'Asset Class' means do not group by Asset Class
                'Asset Subclass' means do not group by Asset Subclass

        Example:

        At the Asset Class level, explicitly specify
        US Stock, US Bonds, and Risk-Free Asset weights, then equally
        allocate among any remaining asset classes.  Next, do not
        consider the Asset Subclass within an Asset Class.  Finally,
        weight the Investment Options within each Asset Class by
        Annual Return.

        >>> weight_by = {  
        >>>     'Asset Class':       {'weight_by': 'Equal',  
        >>>                           'US Stocks': 0.40,  
        >>>                           'US Bonds': 0.40,  
        >>>                           'Risk-Free Asset': 0.10},  
        >>>     'Asset Subclass':    {'weight_by': None},  
        >>>     'Investment Option': {'weight_by': 'Annual Returns'},  
        >>> }  

        default_correlation : int, optional
            Correlation to use when no correlation has been specified
            between two asset classes.  If you use only the Asset
            Classes defined in universe/asset-classes.csv, then this
            will never happen. (default is 1, which assumes that the
            assets are perfectly coorelated, i.e. worst case for
            asset diversification).

    Returns
    -------
    annual_return : float
        The expected annualized return of the portfolio.

    std_dev : float
        The standard deviation of the portfolio.

    sr : float
        The overall sharpe ratio of the portfolio.
    """

    # Pop the title.
    PORT.portfolio_title = \
        portfolio_option.pop('Title', PORT.portfolio_title)

    # Set default correlation.
    PORT.default_correlation = default_correlation

    # Get metric_lists.
    ml = get_metric_lists(df, portfolio_option)
    bb.DBG(f'ml = {ml}')

    # Assign weights.
    assign_weights(df, ml, portfolio_option, weight_by)

    # Make sure total adds to 100 percent.
    check_allocation(portfolio_option, PORT.portfolio_title)

    # Update metric_lists.
    ml = get_metric_lists(df, portfolio_option)

    # Compute metrics.
    annual_return = expected_return(ml.annual_returns, ml.weights)
    std_dev = standard_deviation(ml.weights, ml.std_devs, ml.asset_classes)
    sr = sharpe_ratio(annual_return, std_dev, PORT.risk_free_rate)

    return annual_return, std_dev, sr
示例#4
0
def _assign_weights(df, ml, portfolio_option, weight_by):
    ''' Specify the weighting scheme.  It will replace the weights specified
        in the portfolio.  You can also fix the weights on some
        Investent Options, Asset Classes, and Asset Subclasses while the others
        are automatically calculated.

        'Equal' - will use equal weights.

        'Sharpe Ratio' - will use proportionally weighted allocations based on
        the percent of an investment option's sharpe ratio to the sum of all
        the sharpe ratios in the portfolio.

        'Std Dev' - will use standard deviation adjusted weights

        'Annual Returns' - will use return adjusted weights

        'Vola' - will use volatility adjusted weights

        'DS Vola' - will use downside volatility adjusted weights

        None:   'Investment Option' means use use specified weights
                'Asset Class' means do not group by Asset Class
                'Asset Subclass means do not group by Asset Subclass 
    '''

    # weight by user specified portfolio weights ###############################
    if weight_by is None:
        return

    # unpack weight_by dictionary
    asset_class_weight_by = asset_subclass_weight_by = inv_opt_weight_by = None

    asset_class_weights = weight_by.get('Asset Class')
    asset_subclass_weights = weight_by.get('Asset Subclass')
    inv_opt_weights = weight_by.get('Investment Option')

    if asset_class_weights:
        asset_class_weight_by = asset_class_weights.pop('weight_by', None)
    if asset_subclass_weights:
        asset_subclass_weight_by = asset_subclass_weights.pop(
            'weight_by', None)
    if inv_opt_weights:
        inv_opt_weight_by = inv_opt_weights.pop('weight_by', None)

    # user dict is the user_specified weights; cpmt is the computed weights
    user = dotdict()
    cmpt = dotdict()
    # user initialization
    user.asset_class_weights = asset_class_weights
    user.asset_subclass_weights = asset_subclass_weights
    user.inv_opt_weights = inv_opt_weights
    user.asset_class_weight_by = asset_class_weight_by
    user.asset_subclass_weight_by = asset_subclass_weight_by
    user.inv_opt_weight_by = inv_opt_weight_by
    # cmpt initialization
    cmpt.asset_class_weights = {
        key.split(':')[0]: 0
        for key in ml.asset_classes
    }
    cmpt.asset_subclass_weights = {key: 0 for key in ml.asset_classes}
    cmpt.inv_opt_weights = {key: 0 for key in ml.inv_opts}

    # handle invalid weight_by combinations ####################################
    msg = ('WeightByWarning: A value is set on Asset Class weight_by or'
           ' Asset Subclass weight_by, even though Investment Option weight_by'
           ' is None.  These setting are disabled when Investment Option'
           ' weight_by is None')
    if (user.inv_opt_weight_by is None
            and (user.asset_class_weight_by or user.asset_subclass_weight_by)):
        print(msg)
        return

    # weight by user specified portfolio weights ###############################
    if user.inv_opt_weight_by is None:
        return

    # weight_by inv_opts only ##################################################
    if (user.inv_opt_weight_by and user.asset_class_weight_by is None
            and user.asset_subclass_weight_by is None):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        # use the weights in the dictionary, then the weight_by method for the
        # remaining inv_opts
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        d = cmpt.inv_opt_weights
        w = _get_cmpt_weights(df, d, user.inv_opt_weights,
                              user.inv_opt_weight_by)
        _check_allocation(w, 'Investment Option')
        cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # weight by all ############################################################
    if (user.inv_opt_weight_by and user.asset_class_weight_by
            and user.asset_subclass_weight_by):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_class_weights, user.asset_class_weight_by)
        bb.DBG(user.asset_subclass_weights, user.asset_subclass_weight_by)

        # compute asset class weights within portfolio
        assert(set(user.asset_class_weights).issubset(set(cmpt.asset_class_weights))), \
               "Invalid Asset Class in weight_by!"
        d = cmpt.asset_class_weights
        w = _get_cmpt_weights(__m.asset_class_table, d,
                              user.asset_class_weights,
                              user.asset_class_weight_by)
        _check_allocation(w, 'Asset Class')
        cmpt.asset_class_weights.update(w)
        bb.DBG('cmpt.asset_class_weights', cmpt.asset_class_weights)

        # compute asset subclass weights within each asset class
        assert(set(user.asset_subclass_weights).issubset(set(cmpt.asset_subclass_weights))), \
               "Invalid Asset Sublass in weight_by!"
        for asset_class in cmpt.asset_class_weights.copy():
            # d: get asset subclasses for this asset_class
            d = {
                k: v
                for k, v in cmpt.asset_subclass_weights.items()
                if k.startswith(asset_class)
            }
            # i: get the intersection of d and user specified asset_subclasses
            i = d.keys() & user.asset_subclass_weights.keys()
            user._asset_subclass_weights = {
                k: user.asset_subclass_weights[k]
                for k in i
            }
            w = _get_cmpt_weights(__m.asset_class_table, d,
                                  user._asset_subclass_weights,
                                  user.asset_subclass_weight_by)
            _check_allocation(w, 'Asset Sublass')
            cmpt.asset_subclass_weights.update(w)
        bb.DBG('cmpt.asset_subclass_weights', cmpt.asset_subclass_weights)

        # compute investment option weights within each asset subclass
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_subclass in cmpt.asset_subclass_weights.copy():
            # d: get investment options for this asset_subclass
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i] == asset_subclass}
            # i: get the intersection of d and user specified inv_opts
            i = d.keys() & user.inv_opt_weights.keys()
            user._inv_opt_weights = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user._inv_opt_weights,
                                  user.inv_opt_weight_by)
            _check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # weight by inv_opt and asset_class ########################################
    if (user.inv_opt_weight_by and user.asset_class_weight_by
            and user.asset_subclass_weight_by is None):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_class_weights, user.asset_class_weight_by)

        # compute asset class weights within portfolio
        assert(set(user.asset_class_weights).issubset(set(cmpt.asset_class_weights))), \
               "Invalid Asset Class in weight_by!"
        d = cmpt.asset_class_weights
        w = _get_cmpt_weights(__m.asset_class_table, d,
                              user.asset_class_weights,
                              user.asset_class_weight_by)
        _check_allocation(w, 'Asset Class')
        cmpt.asset_class_weights.update(w)
        bb.DBG('cmpt.asset_class_weights', cmpt.asset_class_weights)

        # compute investment option weights within each asset class
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_class in cmpt.asset_class_weights.copy():
            # d: get investment options for this asset_class
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i].split(':')[0] == asset_class}
            # i: get the intersection of d and user specified inv_opts
            i = d.keys() & user.inv_opt_weights.keys()
            user._inv_opt_weights = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user._inv_opt_weights,
                                  user.inv_opt_weight_by)
            _check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # weight by inv_opt and asset_subclass #####################################
    if (user.inv_opt_weight_by and user.asset_class_weight_by is None
            and user.asset_subclass_weight_by):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_subclass_weights, user.asset_subclass_weight_by)

        # compute asset subclass weights within portfolio
        assert(set(user.asset_subclass_weights).issubset(set(cmpt.asset_subclass_weights))), \
               "Invalid Asset SubClass in weight_by!"
        d = cmpt.asset_subclass_weights
        w = _get_cmpt_weights(__m.asset_class_table, d,
                              user.asset_subclass_weights,
                              user.asset_subclass_weight_by)
        _check_allocation(w, 'Asset SubClass')
        cmpt.asset_subclass_weights.update(w)
        bb.DBG('cmpt.asset_subclass_weights', cmpt.asset_subclass_weights)

        # compute investment option weights within each asset subclass
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_subclass in cmpt.asset_subclass_weights.copy():
            # d: get investment options for this asset_subclass
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i] == asset_subclass}
            # i: get the intersection of d and user specified inv_opts
            i = d.keys() & user.inv_opt_weights.keys()
            user._inv_opt_weights = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user._inv_opt_weights,
                                  user.inv_opt_weight_by)
            _check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return
示例#5
0
def _calc_weights(df, asset_dict, weight_by):
    """ calculate weights for assets in asset_dict using weight_by method """

    weight_by_choices = ('Equal', 'Sharpe Ratio', 'Annual Returns', 'Std Dev',
                         'Vola', 'DS Vola')
    assert weight_by in weight_by_choices, \
        "Invalid weight_by '{}'".format(weight_by)

    ml = _get_metric_lists(df, asset_dict)
    bb.DBG('asset_dict = {}'.format(asset_dict))
    bb.DBG('asset_dict_ml = {}'.format(ml))

    if weight_by == 'Equal':
        n = len(asset_dict)
        weights = [1 / n] * n
        asset_dict.update(zip(asset_dict, weights))

    elif weight_by in ('Sharpe Ratio', 'Annual Returns'):
        # if there are any negative returns, apply unity-based normalization
        # if a return is negative, then sharpe_ratio will also be negative
        numpy.seterr('raise')
        xmin = min(ml.annual_returns)
        if xmin < 0:
            a = 1
            b = 10
            if len(ml.annual_returns) == 1:
                ml.annual_returns[0] = ml.sharpe_ratios[0] = a
            else:
                # Z = a + (x − xmin)*(b − a) (xmax − xmin)
                xmax = max(ml.annual_returns)
                z = [
                    a + (x - xmin) * (b - a) / (xmax - xmin)
                    for x in ml.annual_returns
                ]
                ml.annual_returns = z
                # recalculate sharpe_ratios besed on normalized annual_returns
                ml.sharpe_ratios = [
                    _sharpe_ratio(annual_ret, std_dev, __m.risk_free_rate) for
                    annual_ret, std_dev in zip(ml.annual_returns, ml.std_devs)
                ]

        if weight_by == 'Sharpe Ratio': metric = ml.sharpe_ratios
        else: metric = ml.annual_returns

        metric_sum = sum(metric)
        if not math.isclose(metric_sum, 0):
            weights = [m / metric_sum for m in metric]
        else:
            print(
                'ZeroMetricWarning: All investment options within this group'
                ' have zero {} metric.  Defaulting to Equal Weighting for {}'.
                format(weight_by, asset_dict))
            n = len(asset_dict)
            weights = [1 / n] * n
        asset_dict.update(zip(asset_dict, weights))

    elif weight_by in ('Std Dev', 'Vola', 'DS Vola'):
        if weight_by == 'Std Dev': metric = ml.std_devs
        elif weight_by == 'Vola': metric = ml.volas
        else: metric = ml.ds_volas
        inverse_metric = [1/0.001 if math.isclose(m, 0) else 1/m \
                          for m in metric]
        inverse_metric_sum = sum(inverse_metric)
        weights = [m / inverse_metric_sum for m in inverse_metric]
        asset_dict.update(zip(asset_dict, weights))

    else:
        raise Exception('Error: Invalid weight_by {}'.format(weight_by))
示例#6
0
def assign_weights(df, ml, portfolio_option, weight_by):
    """
    Specify the weighting scheme.  It will replace the weights specified
    in the portfolio.  You can also fix the weights on some
    Investent Options, Asset Classes, and Asset Subclasses while the
    others are automatically calculated.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of investment options with columns for asset class,
        description, and performace metrics.
    ml: bb.dotdict of lists
        Creates dict of lists for investment option, std_dev,
        asset_class, etc... for each investment option for the
        specified portfolio.
    portfolio_option : dict
        Dictionary of investment options along with their weights.  The
        keys are the investment options and the values are the weights.
        The first entry in the dict must be the title of the portfolio.
    weight_by : dict of dicts, optional
        Specify the weighting scheme.  If not None, it will replace the
        weights specified in the portfolio.  You can also fix the
        weights on some Investent Options, Asset Classes, and Asset
        Subclasses, while the others are automatically calculated
        (default is None, which implies use the user specified weights
        specified in `portfolio_option`).  See bb.analyze.analyze()
        for more information about this parameter.
    
    Returns
    -------
    None
    """

    # `weight_by` user specified portfolio weights #####################
    if weight_by is None:
        return

    # Unpack `weight_by` dictionary.
    asset_class_weight_by = asset_subclass_weight_by = inv_opt_weight_by = None

    asset_class_weights = weight_by.get('Asset Class')
    asset_subclass_weights = weight_by.get('Asset Subclass')
    inv_opt_weights = weight_by.get('Investment Option')

    if asset_class_weights:
        asset_class_weight_by = asset_class_weights.pop('weight_by', None)
    if asset_subclass_weights:
        asset_subclass_weight_by = asset_subclass_weights.pop(
            'weight_by', None)
    if inv_opt_weights:
        inv_opt_weight_by = inv_opt_weights.pop('weight_by', None)

    # `user` dict is the user_specified weights.
    # `cpmt` is the computed weights.
    user = dotdict()
    cmpt = dotdict()

    # `user` initialization.
    user.asset_class_weights = asset_class_weights
    user.asset_subclass_weights = asset_subclass_weights
    user.inv_opt_weights = inv_opt_weights
    user.asset_class_weight_by = asset_class_weight_by
    user.asset_subclass_weight_by = asset_subclass_weight_by
    user.inv_opt_weight_by = inv_opt_weight_by

    # `cmpt` initialization.
    cmpt.asset_class_weights = {
        key.split(':')[0]: 0
        for key in ml.asset_classes
    }
    cmpt.asset_subclass_weights = {key: 0 for key in ml.asset_classes}
    cmpt.inv_opt_weights = {key: 0 for key in ml.inv_opts}

    # Handle invalid weight_by combinations.
    msg = ('WeightByWarning: A value is set on Asset Class weight_by or'
           ' Asset Subclass weight_by, even though Investment Option weight_by'
           ' is None.  These setting are disabled when Investment Option'
           ' weight_by is None')

    if (user.inv_opt_weight_by is None
            and (user.asset_class_weight_by or user.asset_subclass_weight_by)):
        print(msg)
        return

    # `weight_by` user specified portfolio weights.
    if user.inv_opt_weight_by is None:
        return

    # `weight_by` inv_opts only.
    if (user.inv_opt_weight_by and user.asset_class_weight_by is None
            and user.asset_subclass_weight_by is None):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)

        # Use the weights in the dictionary, then the `weight_by` method
        # for the remaining `inv_opts`.
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        d = cmpt.inv_opt_weights
        w = _get_cmpt_weights(df, d, user.inv_opt_weights,
                              user.inv_opt_weight_by)
        check_allocation(w, 'Investment Option')
        cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # `weight_by` all.
    if (user.inv_opt_weight_by and user.asset_class_weight_by
            and user.asset_subclass_weight_by):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_class_weights, user.asset_class_weight_by)
        bb.DBG(user.asset_subclass_weights, user.asset_subclass_weight_by)

        # Compute asset class weights within portfolio.
        assert(set(user.asset_class_weights).issubset(set(cmpt.asset_class_weights))), \
               "Invalid Asset Class in weight_by!"
        d = cmpt.asset_class_weights
        w = _get_cmpt_weights(PORT.asset_class_table, d,
                              user.asset_class_weights,
                              user.asset_class_weight_by)
        check_allocation(w, 'Asset Class')
        cmpt.asset_class_weights.update(w)
        bb.DBG('cmpt.asset_class_weights', cmpt.asset_class_weights)

        # Compute asset subclass weights within each asset class.
        assert(set(user.asset_subclass_weights).issubset(set(cmpt.asset_subclass_weights))), \
               "Invalid Asset Sublass in weight_by!"
        for asset_class in cmpt.asset_class_weights.copy():
            # d: get asset subclasses for this asset_class.
            d = {
                k: v
                for k, v in cmpt.asset_subclass_weights.items()
                if k.startswith(asset_class)
            }
            # i: get the intersection of d and user specified asset_subclasses.
            i = d.keys() & user.asset_subclass_weights.keys()
            user.asset_subclass_weights_ = {
                k: user.asset_subclass_weights[k]
                for k in i
            }
            w = _get_cmpt_weights(PORT.asset_class_table, d,
                                  user.asset_subclass_weights_,
                                  user.asset_subclass_weight_by)
            check_allocation(w, 'Asset Sublass')
            cmpt.asset_subclass_weights.update(w)
        bb.DBG('cmpt.asset_subclass_weights', cmpt.asset_subclass_weights)

        # Compute investment option weights within each asset subclass.
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_subclass in cmpt.asset_subclass_weights.copy():
            # d: get investment options for this asset_subclass.
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i] == asset_subclass}
            # i: get the intersection of d and user specified inv_opts.
            i = d.keys() & user.inv_opt_weights.keys()
            user.inv_opt_weights_ = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user.inv_opt_weights_,
                                  user.inv_opt_weight_by)
            check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # `weight_by` `inv_opt`` and asset_class.
    if (user.inv_opt_weight_by and user.asset_class_weight_by
            and user.asset_subclass_weight_by is None):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_class_weights, user.asset_class_weight_by)

        # Compute asset class weights within portfolio.
        assert(set(user.asset_class_weights).issubset(set(cmpt.asset_class_weights))), \
               "Invalid Asset Class in weight_by!"
        d = cmpt.asset_class_weights
        w = _get_cmpt_weights(PORT.asset_class_table, d,
                              user.asset_class_weights,
                              user.asset_class_weight_by)
        check_allocation(w, 'Asset Class')
        cmpt.asset_class_weights.update(w)
        bb.DBG('cmpt.asset_class_weights', cmpt.asset_class_weights)

        # Compute investment option weights within each asset class.
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_class in cmpt.asset_class_weights.copy():
            # d: get investment options for this asset_class.
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i].split(':')[0] == asset_class}
            # i: get the intersection of d and user specified `inv_opts`.
            i = d.keys() & user.inv_opt_weights.keys()
            user.inv_opt_weights_ = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user.inv_opt_weights_,
                                  user.inv_opt_weight_by)
            check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return

    # `weight_by` `inv_opt` and asset_subclass.
    if (user.inv_opt_weight_by and user.asset_class_weight_by is None
            and user.asset_subclass_weight_by):

        bb.DBG(user.inv_opt_weights, user.inv_opt_weight_by)
        bb.DBG(user.asset_subclass_weights, user.asset_subclass_weight_by)

        # Compute asset subclass weights within portfolio.
        assert(set(user.asset_subclass_weights).issubset(set(cmpt.asset_subclass_weights))), \
               "Invalid Asset SubClass in weight_by!"
        d = cmpt.asset_subclass_weights
        w = _get_cmpt_weights(PORT.asset_class_table, d,
                              user.asset_subclass_weights,
                              user.asset_subclass_weight_by)
        check_allocation(w, 'Asset SubClass')
        cmpt.asset_subclass_weights.update(w)
        bb.DBG('cmpt.asset_subclass_weights', cmpt.asset_subclass_weights)

        # Compute investment option weights within each asset subclass.
        assert(set(user.inv_opt_weights).issubset(set(cmpt.inv_opt_weights))), \
               "Invalid Investment Option in weight_by!"
        for asset_subclass in cmpt.asset_subclass_weights.copy():
            # d: get investment options for this asset_subclass.
            d = {k: v for i, (k, v) in enumerate(cmpt.inv_opt_weights.items()) \
                      if ml.asset_classes[i] == asset_subclass}
            # i: get the intersection of d and user specified inv_opts.
            i = d.keys() & user.inv_opt_weights.keys()
            user.inv_opt_weights_ = {k: user.inv_opt_weights[k] for k in i}
            w = _get_cmpt_weights(df, d, user.inv_opt_weights_,
                                  user.inv_opt_weight_by)
            check_allocation(w, 'Investment Option')
            cmpt.inv_opt_weights.update(w)
        bb.DBG('cmpt.inv_opt_weights', cmpt.inv_opt_weights)

        _calc_portfolio_option_weights(portfolio_option, ml, cmpt, user)
        bb.DBG('portfolio_option', portfolio_option)
        return