A do-not-increase-deviation constraint prevents optimized positions from moving farther from their target weights than the current portfolio. It blocks purchases in currently overweight assets and sales in currently underweight assets. section 13.3.2
AGG below target
Suppose a target portfolio holds $4,000 of AGG while the current portfolio holds $3,800. The constraint prevents selling AGG, imposing a lower bound of $3,800 on its optimized position. section 13.3.2
The implementation aggregates current holdings by ticker, calculates target dollar positions from target weights and total investment value, then adds asset-level buy or sell restrictions based on each deviation. section 13.3.2
Listing 13.6 DoNotIncreaseDeviationConstraint
class DoNotIncreaseDeviationConstraint(Constraint):
def __init__(self,
target_weights: pd.Series):
""" Constraint that prohibits buying in currently overweight
assets and selling in currently underweight assets """
self.target_weights = target_weights
def generate_constraint(self, date, holdings, variables, port_info):
all_assets = variables['buys'].keys()
current_port = holdings[['ticker', 'value']]. \ #1
groupby(['ticker']). \
sum()['value']. \
reindex(list(all_assets)). \
fillna(0.0)
target_port = self.target_weights * port_info['investment_value']
cons = []
for asset in all_assets:
if current_port[asset] >= target_port[asset]:
cons.append(variables['buys'][asset] == 0)
if asset not in variables['sells']:
continue
if current_port[asset] <= target_port[asset]:
for sell in variables['sells'][asset].values():
cons.append(sell == 0)
return cons
Inspect the Python constraint class and its portfolio-level restriction logic. section 13.3.2
