#!/usr/bin/env python3.11# Copyright 2025, Gurobi Optimization, LLC# Portfolio selection: given a sum of money to invest, one must decide how to# spend it amongst a portfolio of financial securities. Our approach is due# to Markowitz (1959) and looks to minimize the risk associated with the# investment while realizing a target expected return. By varying the target,# one can compute an 'efficient frontier', which defines the optimal portfolio# for a given expected return.## Note that this example reads historical return data from a comma-separated# file (../data/portfolio.csv). As a result, it must be run from the Gurobi# examples/python directory.## This example requires the pandas (>= 0.20.3), NumPy, and Matplotlib# Python packages, which are part of the SciPy ecosystem for# mathematics, science, and engineering (http://scipy.org). These# packages aren't included in all Python distributions, but are# included by default with Anaconda Python.importgurobipyasgpfromgurobipyimportGRBfrommathimportsqrtimportpandasaspdimportnumpyasnpimportmatplotlibmatplotlib.use("Agg")importmatplotlib.pyplotasplt# Import (normalized) historical return data using pandasdata=pd.read_csv("../data/portfolio.csv",index_col=0)stocks=data.columns# Calculate basic summary statistics for individual stocksstock_volatility=data.std()stock_return=data.mean()# Turn off all logging and create an empty modelwithgp.Env(params={"OutputFlag":0})asenv,gp.Model("portfolio",env=env)asm:# Add a variable for each stockvars=pd.Series(m.addVars(stocks),index=stocks)# Objective is to minimize risk (squared). This is modeled using the# covariance matrix, which measures the historical correlation between stocks.sigma=data.cov()portfolio_risk=sigma.dot(vars).dot(vars)m.setObjective(portfolio_risk,GRB.MINIMIZE)# Fix budget with a constraintm.addConstr(vars.sum()==1,"budget")# Optimize model to find the minimum risk portfoliom.optimize()# Create an expression representing the expected return for the portfolioportfolio_return=stock_return.dot(vars)# Display minimum risk portfolioprint("Minimum Risk Portfolio:\n")forvinvars:ifv.X>0:print(f"\t{v.VarName}\t: {v.X:g}")minrisk_volatility=sqrt(portfolio_risk.getValue())print(f"\nVolatility = {minrisk_volatility:g}")minrisk_return=portfolio_return.getValue()print(f"Expected Return = {minrisk_return:g}")# Add (redundant) target return constrainttarget=m.addConstr(portfolio_return==minrisk_return,"target")# Solve for efficient frontier by varying target returnfrontier=pd.Series(dtype=np.float64)forrinnp.linspace(stock_return.min(),stock_return.max(),100):target.rhs=rm.optimize()frontier.loc[sqrt(portfolio_risk.getValue())]=r# Plot volatility versus expected return for individual stocksax=plt.gca()ax.scatter(x=stock_volatility,y=stock_return,color="Blue",label="Individual Stocks")forstockinstocks:ax.annotate(stock,(stock_volatility[stock],stock_return[stock]))# Plot volatility versus expected return for minimum risk portfolioax.scatter(x=minrisk_volatility,y=minrisk_return,color="DarkGreen")ax.annotate("Minimum\nRisk\nPortfolio",(minrisk_volatility,minrisk_return),horizontalalignment="right",)# Plot efficient frontierfrontier.plot(color="DarkGreen",label="Efficient Frontier",ax=ax)# Format and display the final plotax.axis((0.005,0.06,-0.02,0.025))ax.set_xlabel("Volatility (standard deviation)")ax.set_ylabel("Expected Return")ax.legend()ax.grid()plt.savefig("portfolio.png")print("Plotted efficient frontier to 'portfolio.png'")