Hi everyone
I am trying to create open source version of optimization module provided by the Q team. I have modeled the problem but it is giving me some matrix issue (related to rank). I am not great at python but I am sure that optimization setup is correct (or may be not).
Can someone please help me debug the issue?
Backtest Error
ValueError:Rank(A) < p or Rank([G; A]) < n
Above error happens when there are redundant constraints (and/or infinite solution choice) but I am unable to find any. Please help!
Below is the problem that I am trying to solve, it's math and code
Maximize c'w (maximize alpha)
where -0.01 < w < 0.01 (Position Concentration Limit)
0.925 < Sum(abs(w)) < 1.075 (Leverage/Gross Exposure limit)
-0.075 < Sum(w) < 0.075 (Net Exposure Limit)
Sum(abs(w-wo)) < 0.95 (Turnover Limit)
-0.375 < A'w < 0.375 (Style Exposure)
-0.175 < B'w < 0.175 (Sector Exposure)
Below, create new variables to convert all non-linear restrictions to linear restrictions
w = wL + wS (Rewriting portfolio weights as sum of long and short weights)
0 < wL < U
-U < wS < 0
Rewriting Leverage Constraint
lLeverage < Sum(wL) - Sum(wS) < uLeverage
Rewriting Net Exposure Constraint
lNetExposure < Sum(wL) + Sum(wS) < uNetExposure
Rewriting Turnover Constraint (creating new variable tw)
tw = abs(w - w0)
tw > w - w0
tw > - (w - w0)
tw > 0Sum(tw) < MaxTurnover
Rewriting Style Exposure
Min Style Exposure < A'wL + A'wS < Max Style Exposure
Rewriting Sector Exposure
Min Sector Exposure < B'wL + B'wS < Max Sector Exposure
Python Code (More in attached backtest)
def optimize(context):
alpha = context.alpha
factor_loadings = context.risk_factor_betas.loc[alpha.index.tolist(), :]
beta = context.beta_pipeline[['beta']].loc[alpha.index.tolist(), :]
nstocks = alpha.size
initial_portfolio = context.initial_portfolio
min_turnover = context.min_turnover
max_turnover = context.max_turnover - min_turnover
# w = wl + ws
# Number of variables =
# 2*nStocks(weights) +
# nStocks(turnover link vars)
#Number of Inequality group restrictions =
# 2*nSector +
# 2*nStyles +
# 2*1 (net exposure restrictions)
# 2*1 (gross exposure restriction) +
# 1 (turnover restriction)
# 2*nStocks(turnover link vars) +
# 2*1 (market beta exposure) +
# 2*nStocks (Weight long bounds)
# 2*nStocks (Weight short bounds)
# nStocks (turnover link vars lower bound)
ones_matrix = matrix(1.0, (nstocks, 1), 'd')
zeros_matrix = matrix(0.0, (nstocks, 1), 'd')
# Group Constraints - 1/2
# min_exposure < Risk Loading transpose * W < max_exposure
sector_exp_matrix = matrix()
nsectors = len(context.sectors)
sector_exp_bounds = matrix(context.max_sector_exposure, (nsectors,1), 'd')
for col_name in context.sectors:
_loadings = sparse([
matrix(factor_loadings[col_name].values, (nstocks, 1), 'd'),
matrix(factor_loadings[col_name].values, (nstocks, 1), 'd'),
zeros_matrix]).T
if sector_exp_matrix:
sector_exp_matrix = sparse([sector_exp_matrix, _loadings])
else:
sector_exp_matrix = _loadings
style_exp_matrix = matrix()
nstyles = len(context.styles)
style_exp_bounds = matrix(context.max_style_exposure, (nstyles,1), 'd')
for col_name in context.styles:
_loadings = sparse([
matrix(factor_loadings[col_name].values, (nstocks, 1), 'd'),
matrix(factor_loadings[col_name].values, (nstocks, 1), 'd'),
zeros_matrix]).T
if style_exp_matrix:
style_exp_matrix = sparse([style_exp_matrix, _loadings])
else:
style_exp_matrix = _loadings
# Group Constraints - 3/4
# Dollar Neutral or min_net_exp < sum of weights < max_net_exp
net_exp_matrix = sparse([
ones_matrix,
ones_matrix,
zeros_matrix]).T
net_exp_bounds = matrix(context.max_net_exposure, (1,1), 'd')
# Group Constraints - 5/6
# Gross Exposure or min_gross_exp < sum of abs weights < max_gross_exp
gross_exp_matrix = sparse([
ones_matrix,
-1*ones_matrix,
zeros_matrix]).T
gross_exp_bounds_u = matrix(context.max_leverage, (1,1), 'd')
gross_exp_bounds_l = -1*matrix(context.min_leverage, (1,1), 'd')
# Group Constraints - 8
# Turnover < max_turnover
# Sum of abs(w-w0) < ma_turnover
# dw = abs(w-w0) (new variable)
# dw >= w - wo
# dw >= -(w - wo)
# dw >= 0 variable constraint
# dw1 + dw2 + ... + dwn < max_turnover (Number of Group Restriction = 1)
# Total Number of Group Restriction = nFactors + nStocks
turnover_matrix = sparse([
zeros_matrix,
zeros_matrix,
ones_matrix]).T
turnover_bound = matrix(max_turnover, (1,1), 'd')
turnover_lnk_bounds = matrix(initial_portfolio, (nstocks,1), 'd')
# d = |wl + ws - w0|
# d > wl + ws - w0 => wl + ws - d < w0
# d > -wl -ws + w0 => -wl -ws -d < -w0
# Group Constraints - 9 (turnover link vars)
turnover_lnk_matrix_1 = matrix()
for i in range(nstocks):
var_indicator = np.zeros(nstocks)
var_indicator[i] = 1.0
_var_link_matrix = sparse([
matrix(var_indicator, (nstocks,1), 'd'),
matrix(var_indicator, (nstocks,1), 'd'),
matrix(-var_indicator, (nstocks,1), 'd')]).T
if turnover_lnk_matrix_1:
turnover_lnk_matrix_1 = sparse([turnover_lnk_matrix_1, _var_link_matrix])
else:
turnover_lnk_matrix_1 = _var_link_matrix
# Group Constraints - 10 (turnover link vars)
turnover_lnk_matrix_2 = matrix()
for i in range(nstocks):
var_indicator = np.zeros(nstocks)
var_indicator[i] = 1.0
_var_link_matrix = sparse([
matrix(-var_indicator, (nstocks,1), 'd'),
matrix(-var_indicator, (nstocks,1), 'd'),
matrix(-var_indicator, (nstocks,1), 'd')]).T
if turnover_lnk_matrix_2:
turnover_lnk_matrix_2 = sparse([turnover_lnk_matrix_2, _var_link_matrix])
else:
turnover_lnk_matrix_2 = _var_link_matrix
# Group constraints - 11/12
# Market beta exposure
# lBeta < (B1*W1 + B2*W2 + ... + BnWn) < uBeta
market_beta_exp_matrix = sparse([
matrix(beta.values, (nstocks,1), 'd'),
matrix(beta.values, (nstocks,1), 'd'),
zeros_matrix]).T
market_beta_exp_bound = matrix(context.max_beta, (1,1), 'd')
# Group Restrictions - 13/14
# Portfolio Weight Upper Bound
weight_var_matrix_long = matrix()
for i in range(nstocks):
var_indicator = np.zeros(nstocks)
var_indicator[i] = 1.0
_var_matrix = sparse([
matrix(var_indicator, (nstocks,1), 'd'),
zeros_matrix,
zeros_matrix]).T
if weight_var_matrix_long:
weight_var_matrix_long = sparse([weight_var_matrix_long, _var_matrix])
else:
weight_var_matrix_long = _var_matrix
weight_var_matrix_short = matrix()
for i in range(nstocks):
var_indicator = np.zeros(nstocks)
var_indicator[i] = 1.0
_var_matrix = sparse([
zeros_matrix,
matrix(var_indicator, (nstocks,1), 'd'),
zeros_matrix]).T
if weight_var_matrix_short:
weight_var_matrix_short = sparse([weight_var_matrix_short, _var_matrix])
else:
weight_var_matrix_short = _var_matrix
turnover_var_matrix = matrix()
for i in range(nstocks):
var_indicator = np.zeros(nstocks)
var_indicator[i] = -1.0
_var_matrix = sparse([
zeros_matrix,
zeros_matrix,
matrix(var_indicator, (nstocks,1), 'd')]).T
if turnover_var_matrix:
turnover_var_matrix = sparse([turnover_var_matrix, _var_matrix])
else:
turnover_var_matrix = _var_matrix
# Combine all Group Restrictons
G = sparse([
sector_exp_matrix,
-1*sector_exp_matrix,
style_exp_matrix,
-1*style_exp_matrix,
net_exp_matrix,
-1*net_exp_matrix,
gross_exp_matrix,
-1*gross_exp_matrix,
turnover_matrix,
turnover_lnk_matrix_1,
turnover_lnk_matrix_2,
market_beta_exp_matrix,
-1*market_beta_exp_matrix,
-1*weight_var_matrix_long,
weight_var_matrix_long,
-1*weight_var_matrix_short,
weight_var_matrix_short,
turnover_var_matrix
])
h = matrix([
sector_exp_bounds,
sector_exp_bounds,
style_exp_bounds,
style_exp_bounds,
net_exp_bounds,
net_exp_bounds,
gross_exp_bounds_u,
gross_exp_bounds_l,
turnover_bound,
turnover_lnk_bounds,
-1*turnover_lnk_bounds,
market_beta_exp_bound,
market_beta_exp_bound,
zeros_matrix, # xL > 0
matrix(context.max_pos_size, (nstocks, 1), 'd'), #xL < U
matrix(context.max_pos_size, (nstocks, 1), 'd'), #-xS < U
zeros_matrix, #xS < 0
zeros_matrix
])
#Objective Function - Maximize Alpha
c = matrix([
matrix(alpha.values, (nstocks,1)),
matrix(alpha.values, (nstocks,1)),
zeros_matrix])
sol = solvers.lp(-c, G, h)
print sol['x']