def weighted_returns(returns, weights, cash):
    """
    Calculate a portfolio's cumulative weighted returns. The assets
    are weighted at each time-step using the given weights, and
    a part of the portfolio can be held in cash (with zero return).

    :param returns:
        Pandas DataFrame with the asset-returns. These are +1
        so e.g. 1.05 is a +5% return and 0.9 is a -10% return.
        Rows are for the time-steps. Columns are for the assets.

    :param weights:
        Pandas DataFrame with the asset-weights.
        Rows are for the time-steps. Columns are for the assets.

    :param cash:
        Pandas Series with the cash-fraction of the portfolio.

    :return:
        Pandas Series with the cumulative portfolio returns.
    """
    # Weighted returns for individual assets at each time-step.
    # This is a DataFrame. Rows are time-steps. Columns are assets.
    weighted_rets = weights * returns

    # The portfolio's return for each time-step.
    # This is a Pandas Series.
    port_rets = weighted_rets.sum(axis=1)

    # Cumulative portfolio returns.
    # This is a Pandas Series.
    port_cum_rets = (port_rets + cash).cumprod()

    return port_cum_rets