Untitled

 avatar
unknown
plain_text
a year ago
3.6 kB
6
Indexable
from scipy.optimize import minimize


# Function to Optimize
def minimize_return_variance_joint(weights, covariance_matrix, expected_returns, gamma=0.2):
    return -np.sum(expected_returns * weights) + gamma * weights.T.dot(covariance_matrix).dot(weights)

def minimize_neg_returns(weights, covariance_matrix, expected_returns):
    return -np.sum(expected_returns * weights)

def negative_sharpe_ratio(weights, covariance_matrix, expected_returns):
    portfolio_return = np.dot(weights, expected_returns)
    portfolio_variance = np.dot(weights.T, np.dot(covariance_matrix, weights))
    sharpe_ratio = (portfolio_return) / np.sqrt(portfolio_variance)
    return -sharpe_ratio

def compute_optimal_weights(covariance_matrix, expected_returns,
                            optimization_fn=minimize_neg_returns):
    """
    Compute the optimal weights for a given covariance matrix and expected returns.

    :param covariance_matrix: The covariance matrix of the assets.
    :param expected_returns: The expected returns of the assets.
    :return: The mean-variance optimal weights subject to constraints on the sum of weights and individual weights.
    """
    num_assets = len(expected_returns)

    objective_fn = lambda weights: optimization_fn(weights, covariance_matrix, expected_returns)

    # Constraints
    # -1 <= w_i <= 1 for all i
    # sum(w_i) = 1
    weight_sum_constraint = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1.0}
    bounds = tuple((-1, 1) for _ in range(num_assets))

    initial_weights = np.ones(num_assets) / num_assets

    # Sequential Least SQuares Programming (SLSQP).
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
    # https://docs.scipy.org/doc/scipy/reference/optimize.minimize-slsqp.html

    results = minimize(objective_fn,
                       initial_weights,
                       method='SLSQP',
                       bounds=bounds,
                       constraints=[weight_sum_constraint],
                       options={'disp': False})
    return results


def compute_returns_info(allocation_mat_df, holdout_df, N):
    predicted_indicies = [r.rstrip('_hat').lstrip('A_') for r in allocation_mat_df.columns]

    # NOTE: We don't need to shift the dataframe because
    # at construction we stored the "forward returns" in the dataframe, and are using the
    # stock "forward returns" to predict the "index forward returns"
    realized_returns = holdout_df[predicted_indicies]

    portfolio_returns = np.sum(allocation_mat_df.iloc[:N, :].values * realized_returns.iloc[:N, :].values, axis=1)

    portfolio_returns_df = pd.DataFrame(portfolio_returns, columns=['Portfolio_Returns'])

    # Compute average returns, std, and sharpe
    avg_returns = portfolio_returns_df.mean().values[0]
    std_returns = portfolio_returns_df.std().values[0]
    sharpe_ratio = avg_returns / std_returns

    print(f"Portfolio Average Returns (BPS): {avg_returns : .3f}")
    print(f"Portfolio Std Returns: {std_returns : .3f}")
    print(f"Portfolio Sharpe Ratio: {sharpe_ratio : .3f}")

    # Convert values to annualized
    # Assuming t is in days, which may not be true
    anual_returns_bps = avg_returns * 252
    anual_std_returns = std_returns * np.sqrt(252)
    anual_sharpe_ratio = anual_returns_bps / anual_std_returns
    print(f"In Annualized Terms (BPS): mu={anual_returns_bps : .3f}, sigma={anual_std_returns : .3f}, Sharpe={anual_sharpe_ratio : .3f}")

    portfolio_returns_df.hist(bins=100)
    plt.show()


Editor is loading...
Leave a Comment