robertmartin8 / PyPortfolioOpt

Financial portfolio optimisation in python, including classical efficient frontier, Black-Litterman, Hierarchical Risk Parity
https://pyportfolioopt.readthedocs.io/
MIT License
4.38k stars 940 forks source link

Portfolio Turnover #350

Closed amos-peter closed 3 years ago

amos-peter commented 3 years ago

Hi Robert, may I know if there are any example of portfolio turnover constraints within this module?

phschiele commented 3 years ago

Hi @amos-peter, does this snippet help?

import cvxpy
import matplotlib.pyplot as plt
import numpy as np

from tests.utilities_for_tests import setup_efficient_frontier

turnovers = []
sigmas = []
turnover_constraints = np.arange(0, 2.01, 0.1)
weight_matrix = []

for turnover_constraint in turnover_constraints:
    ef = setup_efficient_frontier()
    equal_weights = np.ones(ef.n_assets) / ef.n_assets
    ef.add_constraint(lambda w: cvxpy.sum(cvxpy.abs(w - equal_weights)) <= turnover_constraint)
    ef.min_volatility()
    weights = np.array(list(ef.clean_weights().values()))
    turnover = np.sum(np.abs(weights - equal_weights))
    _, sigma, _ = ef.portfolio_performance()
    turnovers.append(turnover)
    sigmas.append(sigma)
    weight_matrix.append(weights)

plt.plot(sigmas, turnovers)
plt.xlabel("Sigma")
plt.ylabel("Turnover")
plt.show()

plt.plot(turnover_constraints, turnovers)
plt.xlabel("Turnover Budget")
plt.ylabel("Turnover")
plt.show()

plt.stackplot(turnovers, np.vstack(weight_matrix).T)
plt.xlabel("Turnover")
plt.ylabel("Weight")
plt.show()

image

image

image

amos-peter commented 3 years ago

Hi @phschiele. Thank you for your help! I will try to implement the code. I am still quite poor in convex optimization code. Do you have any example for convex optimization with different constraints such maximize a set of the score, min-max sector allocation and etc.

amos-peter commented 3 years ago

Hi All

I manage to set the turnover constraints via cvxpy. I think I got an idea to set the sum of within weight size to be reference as sector and set it min, max.

from pypfopt import EfficientFrontier
from pypfopt import risk_models
from pypfopt import plotting
from pypfopt import expected_returns
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
import cvxpy

#get previous port weight for 2001-01-30
current_w = latest_w.values
current_w = current_w.reshape(20,)
current_w

data = df[(df.index >= '2001-01-31') & (df.index <= '2001-02-28')]
mu = expected_returns.capm_return(data)
S = risk_models.CovarianceShrinkage(data).ledoit_wolf()

ef = EfficientFrontier(mu, S,(0,1))  # weight_bounds automatically set to (0, 1)

ef.add_constraint(lambda w: cvxpy.sum(cvxpy.abs(w - current_w)) <= 0.50)
ef.add_constraint(lambda w: cvxpy.sum((w[0:5])) <= 0.50) #to be explored (sector allocation)
ef.add_constraint(lambda w: cvxpy.sum((w[0:5])) >= 0.40) #to be explored (sector allocation)
ef.min_volatility()
weights = ef.clean_weights()
turnover = np.sum(np.abs(weights - latest_w))

latest_prices = get_latest_prices(data[0:1])
latest_prices = latest_prices.fillna(0)
da = DiscreteAllocation(weights, latest_prices, total_portfolio_value=final_value)
allocation, leftover = da.lp_portfolio()
w = dict.fromkeys(weights, 0)
w.update(allocation)
print(w)

initial_value = final_value
temp = data*w
cash = initial_value - temp.iloc[0].sum() 
print(cash)
final_value = cash + temp.iloc[-2].sum() 
print(final_value)
temp['Cash'] = cash
port = port.append(temp[:-1])
port
phschiele commented 3 years ago

@amos-peter The sector constraints look good. There is an even simple way to implement those, though:

sector_mapper = {
    "GOOG": "tech",
    "FB": "tech",,
    "XOM": "Oil/Gas",
    "RRC": "Oil/Gas",
    "MA": "Financials",
    "JPM": "Financials",
}

sector_lower = {"tech": 0.1}  # at least 10% to tech
sector_upper = {
    "tech": 0.4, # less than 40% tech
    "Oil/Gas": 0.1 # less than 10% oil and gas
}
ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)
amos-peter commented 3 years ago

Yes, I agree. I have done some workaround to combine the sector allocation with turnover constraint. loop through monthly data to get optimal weight. My next step would be to try to go deeper in convex optimization within pyportfolioopt to maximize the set of composite scores for my universe. Thank you!

if first_round == 1:
  prev_port = port[port.index<=prev_m].iloc[[-2]]
  current_value = prev_port.iloc[0].sum() - prev_port.Cash.iloc[0]
  latest_w = prev_port.drop('Cash',
    axis='columns')
  latest_w = latest_w[-1:]/current_value
  latest_w = latest_w.fillna(0)
  current_w = latest_w.values
  current_w = current_w.reshape(20,)

#print(current_w)

if current_w.sum() > 0:
  ef.add_constraint(lambda w: cvxpy.sum(cvxpy.abs(w - current_w)) <= 0.60)
ef.add_sector_constraints(group_mapper, group_lower, group_upper)
ef.add_sector_constraints(group_mapper2, group_lower2, group_upper2)

ef.min_volatility()
weights = ef.clean_weights()
weights

cash_adj = initial_value - (initial_value * 0.025)
latest_prices = get_latest_prices(data[0:1])
latest_prices = latest_prices.fillna(0)
da = DiscreteAllocation(weights, latest_prices, total_portfolio_value=cash_adj)
allocation, leftover = da.lp_portfolio()
w = dict.fromkeys(weights, 0)
w.update(allocation)
#print(w)
robertmartin8 commented 3 years ago

I'm glad you've got it working!

@phschiele Nice work with the transition map (stackplot) – will include that in plotting at some point!