Portfolio optimization

Hi,I am trying to insert min max values of variance and return into the objective function but I could not. No,I reviewed but I want to insert min-max normalization values into the risk adjusted return objective function.I want to define normalization for return and variance into one equation.I wrote
stocks_in_sample = data[stocks][t - tau : t]

n = 19

mu = expected_returns.mean_historical_return(stocks_in_sample,frequency=1)
Sigma = risk_models.sample_cov(stocks_in_sample, frequency=1)
risk_aversion=lr_predictions
weight=generate_random_weights(19, seed=None)
min_return=np.min(np.sum((weight*mu)))
max_return=np.max(np.sum((weight*mu)))
min_variance=np.min(np.sum((weight*mu*np.transpose(weight))))
max_variance=np.max(np.sum((weight*mu*np.transpose(weight)))) 
m = AMPL()
m.eval(
 r"""
        

 set A ordered;
 param risk_aversion;
 param Sigma{A, A};
 param mu{A};
 param lb default 0;
 param ub default 1;
 var w{A} >= lb <= ub;
 param min_return{A};
 param max_return{A};
 param min_variance{A};
 param max_variance{A};
     
 maximize risk_adjusted_return:
     risk_aversion*((sum {i in A} mu[i] * w[i]-min_return)/(max_return-min_return)+(1-risk_aversion)*((sum {i in A, j in A} w[i] * Sigma[i, j] * w[j]-min_variance)/(max_variance-min_variance));
        
 s.t. portfolio_weights:
 sum {i in A} w[i] = 1;
 
"""

)
m.set[“A”] = stocks_in_sample

m.param[“mu”] = mu
m.param[“risk_aversion”] = risk_aversion
m.param[“Sigma”] = Sigma
m.param[“min_return”]=min_return
m.param[“max_return”]=max_return
m.param[“min_variance”]=min_variance
m.param[“max_variance”]=max_variance

m.option[“solver”] = “gurobi”
m.solve()
m.solve(verbose=False)

I got Error: line 17 offset 380 syntax error context: risk_aversion*((sum {i in A} mu[i] * >>> w[i]-min_return) <<< /(max_return-min_return)+(1-risk_aversion)*((sum {i in A, j in A} w[i] * Sigma[i, j] * w[j]-min_variance)/(max_variance-min_variance));

1 Like

Hi @Firdevs_Uykun,

In the objective definition you have some parameters that are not scaler without specifying the index and there were also some missing parenthesis.

You can write that objective as follows:

maximize risk_adjusted_return:
    risk_aversion * (
        (sum {i in A} (mu[i] * w[i] - min_return[i]))
            /
        (sum {i in A} (max_return[i] - min_return[i]))
    ) + (1-risk_aversion) * (
        (sum {i in A, j in A} (w[i] * Sigma[i, j] * w[j] - min_variance[i]))
            /
        (sum {i in A} (max_variance[i] - min_variance[i]))
    );

Hi,I am trying to solve multiperiod markowitz model,
min_var = AMPL()
min_var.eval(
r"“”

 set A ordered;
 
 
 param risk_aversion;
 param risk_free;
 param Sigma{A, A};
 param mu{A};
 param R_min;
 param R_max;
 param Var_min;
 param Var_max;
 var w{A} >=0;
        
 maximize risk_adjusted_return:
     risk_aversion*((sum {i in A} mu[i] * w[i]- R_min)/(R_max-R_min))+
     (1-risk_aversion)*((sum {i in A, j in A} w[i] * Sigma[i, j] * w[j]- Var_min)/(Var_max - Var_min));
    
 
 s.t. sum_of_weights: sum{i in A} w[i] == 1;
 s.t.portfolio_return:
 sum {i in A} mu[i] * w[i]=(sum {i in A} mu[i] * w[i])+(sum {i in A} mu[i] * w[i] - sum {i in A} w[i])*risk_free;

"""

)

min_var.set["A"] = stocks_in_sample
min_var.param["mu"] = mu
min_var.param["risk_aversion"] = risk_aversion
min_var.param["Sigma"] = Sigma
min_var.param["R_min"]=R_min
min_var.param["R_max"]=R_max
min_var.param["Var_min"]=Var_min
min_var.param["Var_max"]=Var_max
min_var.param["risk_free"]=risk_free
min_var.option["solver"] = "gurobi"
min_var.solve()

but I got error:
Warning: presolve, constraint sum_of_weights: all variables eliminated, but lower bound = 1 > 0 Please report any bugs at: GitHub - ampl/amplpy: Python API for AMPL For support/feedback go to https://discuss.ampl.com or e-mail support@ampl.com Infeasible constraints determined by presolve.

1 Like

Hi @Firdevs_Uykun,

The constraint portfolio return fixes all variables w to 0 if rist_free is non-zero:

 s.t. portfolio_return:
     sum {i in A} mu[i] * w[i] =
        (sum {i in A} mu[i] * w[i])+
        (sum {i in A} mu[i] * w[i] - sum {i in A} w[i]) * risk_free;

It is equivalent to:

 s.t. portfolio_return:
     0 = (sum {i in A} mu[i] * w[i] - sum {i in A} w[i]) * risk_free;

To make it quicker to debug these errors in the future, you can add min_var.snapshot("snapshot.run") right before min_var.solve() to produce a file that you can send to us and that allows us to reproduce the error right away.

I am trying to write as a constraint: xt+1 = e0 *t xt + Pt′*ut but when I try to define time as set I got error,my data in the for loop so can I ignore Time in the AMPL syntax expression,I saw MAD portfolio optimization in python-AMPL book,Do I need to write exactly what mathematics expression?


I am trying to write with summation equivalent.

You can add a new index to variable w for the time period. Then sum {i in A} mu[i] * w[i,t] will be the total expected return for time t. You can also have another index for mu so that you can use different expected returns for each time period.

However, are you looking for something like the following?

 s.t. inital_total_weights: sum{i in A} w[i, 0] == 1;
 s.t. portfolio_return_carried_over{t in T: t > 1}:
     sum {i in A} mu[i, t] * w[i, t-1] = sum {i in A} w[i, t];

If mu[i,t] is the expected relative return for asset i in period t, this carries over the returns from period t-1 to t. Note that for all time periods other than 1 the sum of weights will be different from 1 and you may need to be careful if you do anything that only works under that assumption.

If you could provide a reference with the full model you are trying to implement in mathematical notation, it would likely help figuring out the best way to model it.

Okay,thanks for your answers,first of all, I want to write multiperiod variance equation, variance depends on time. In MAD portfolio optimization you wrote var w{ASSETS}; means weights do not change over time,but their optimized value change over time.I define for variance :
for t in range(tau, len(data)):
stocks_in_sample = data[stocks][t - tau : t]
#for time indexing
stocks_in_sample[“Date”] = stocks_in_sample.index.format()
stocks_in_sample.set_index(“Date”, inplace=True)
mu = calculate_returns(stocks_in_sample)
Sigma = risk_models.sample_cov(stocks_in_sample, frequency=1)
TIME=stocks_in_sample.index.values
m = AMPL()
m.eval(
r"“”

 set A ordered;
 set TIME ordered;

 param Sigma{t in TIME,A,A};
 param mu{t in TIME,A};
 param lb default 0;
 param ub default 1;
 var w{t in TIME,A} >= lb <= ub;
        
 minimize portfolio_variance:
    sum {t in TIME,i in A, j in A} w[t,i] * Sigma[t,i, j] * w[t,j];
        
 s.t. portfolio_weights:
 sum {t in TIME,i in A} w[t,i] = 1;

"""

)
m.set[“A”] = stocks_in_sample
m.param[“mu”] = mu
m.param[“Sigma”] = Sigma
m.set[“TIME”] = TIME
m.option[“solver”] = “gurobi”
m.solve()
Var_min=m.get_value(“sum {t in TIME,i in A, j in A} w[t,i] * Sigma[t,i, j] * w[t,j]”)
R_min = m.get_value(“sum {i in A,t in TIME} mu[t,i] * w[t,i]”)

Then I got error:


Time is :

You need to load the indexing set of mu before being able to load values into it. Otherwise you get the error about missing data for set TIME since it is not available when you try to load the values for mu. You can do that as follows:

m.set["TIME"] = TIME
m.param["mu"] = mu

Thanks,that s true,
m = AMPL()
m.eval(
r"“”

 set A;
 set Time;
 

 param Sigma{t in Time,A, A};
 param mu{Time,A};
 param lb default 0;
 param ub default 1;
 var w{Time,A} >= lb <= ub;
        
 minimize portfolio_variance {t in Time}:
    sum {i in A, j in A} w[t,i] * Sigma[t,i, j] * w[t,j];
        
 s.t. portfolio_weights {t in Time}:
 sum {i in A} w[t,i] = 1;

"""

)
m.set[“A”] = list(stocks_in_sample.columns)
m.set[“Time”] = stocks_in_sample.index.values
m.param[“mu”] = mu
m.param[“Sigma”] = Sigma
m.option[“solver”] = “gurobi”
m.solve()
Then I got :


and mu has the form:

I solved the expected return just depend assets, then I got following message:


My sigma for 26 day period: 19*19 matrix:

You have Sigma defined as Sigma{t in Time,A, A}; so you need to have a DataFrame with three indices.

What is the output of display(Sigma.stack())? In order to be able to load it, there must be 3 indices and 1 data column.

I want to sure one thing,I am using for loop for data iteration set,I divide data periods as 26 day,still in the AMLP syntax I have to write Time,right?

To load the data in that dataframe you need to have Sigma defined as param Sigma{A, A};. However, if you want to use Sigma values for multiple time windows you need to build a dataframe with all Sigmas indexed over Time and pairs of assets (i.e., 3 indices).

I am trying to formulate mean variance optimization with rebalancing, first I need minimum variance,your sigma calculates for 26 week time period,also I got optimization solution for variance:
minimize portfolio_variance {t in Time}:
sum {i in A, j in A} w[t,i] * Sigma[i,j] * w[t,j];
In the notation Sigma does not have t,is that true? Sigma recalculated as Sigma = risk_models.sample_cov(stocks_in_sample, frequency=1) and stocks_in_sample = data[stocks][t - tau : t], How can I store the optimized values?

You suggest
s.t. portfolio_return_carried_over {t in Time}:
sum {i in A} mu[i] * w[i,t+1]=(sum {i in A} mu[i] * w[i,t])+(sum {i in A} mu[i] * w[i,t] - sum {i in A} w[i,t])*risk_free;
I tried then get:


How can I solve,I mentioned the equation above as a picture

For the sigma for 26 weeks you can do the following:

n_slices = 26
slices = np.array_split(prices, n_slices)
dfs = []
for i, slice_df in enumerate(slices):
    df = risk_models.risk_matrix(slice_df, method="exp_cov").stack().to_frame()
    df.reset_index(inplace=True) # Turn the index into regular data columns
    df.columns = ["Stock1", "Stock2", "S"] # Adjust column names
    df["Time"] = i  # Add new column with the index of the slice
    dfs.append(df)

df = pd.concat(dfs)  # Concatenate all dataframes
df.set_index(["Time", "Stock1", "Stock2"], inplace=True)  # Set the index to be (Time, Stock1, Stock2)
display(df)

This will produce a dataframe in the right format to be loaded into param Sigma{Time, A, A};:

Regarding the error with portfolio_return_carried_over, you got that error because you used strings for time instead of the week number. So t+1 is invalid. However, you can define Time as an ordered set with set Time ordered; and then instead of t+1 you can use next(t). You can learn more about sets and ordered sets in Chapter 5 of the AMPL Book: AMPL Book - Guide for modelers at all levels of experience

hi,thanks for help,
My code as follows:
tau=26
for t in range(tau, len(data)):
stocks = [
‘HD’, ‘MCD’, ‘NKE’, ‘KO’, ‘PG’, ‘SYY’, ‘WMT’, # Consumer Staples
‘CVX’, ‘XOM’, # Energy
‘AXP’, ‘JPM’, # Financials
‘JNJ’, ‘MRK’, ‘PFE’, ‘WBA’, # Health Care
‘BA’, ‘CAT’, ‘MMM’, # Industrials
‘IRX’,# Information Technology#t-bill
]
stocks_in_sample = data[stocks][t - tau : t]
mu = expected_returns.mean_historical_return(stocks_in_sample,frequency=1)
dfs =
for i, slice_df in enumerate(stocks_in_sample.values):
df = risk_models.risk_matrix(slice_df, method=“sample_cov”).stack().to_frame()
df.reset_index(inplace=True) # Turn the index into regular data columns
df.columns = [ ‘HD’, ‘MCD’, ‘NKE’, ‘KO’, ‘PG’, ‘SYY’, ‘WMT’, # Consumer Staples
‘CVX’, ‘XOM’, # Energy
‘AXP’, ‘JPM’, # Financials
‘JNJ’, ‘MRK’, ‘PFE’, ‘WBA’, # Health Care
‘BA’, ‘CAT’, ‘MMM’, # Industrials
‘IRX’,“S”] # Adjust column names
df[“Time”] = i # Add new column with the index of the slice
dfs.append(df)

df = pd.concat(dfs)  # Concatenate all dataframes
df.set_index(["Time", 'HD', 'MCD', 'NKE', 'KO', 'PG', 'SYY', 'WMT',  # Consumer Staples
    'CVX', 'XOM',  # Energy
    'AXP', 'JPM',  # Financials
    'JNJ', 'MRK', 'PFE', 'WBA',  # Health Care
    'BA', 'CAT', 'MMM',  # Industrials
    'IRX' ], inplace=True)  # Set the index to be (Time, Stock1, Stock2)
display(df)

Then I got following error;



I downloaded weekly data from Yahoo Finance and ,my stocks_in_sample.shape is (26,19).

df.columns = ["Stock1", "Stock2", "S"] sets the names for the three columns. You are trying to assign a list with more than 3 elements and that is why you are getting that error. Right before this line you can use display(df) to see how the dataframe looks like at that point. The line above stacks the dataframe so there are no ticker names across both axis anymore.