Entiendo el concepto de frontera eficiente y soy capaz de calcularlo en Python. Pero incluso al generar 50'000 carteras aleatorias de 10 activos, las carteras individuales no se acercan siquiera a la frontera eficiente.
Veo que, por ejemplo, la cartera con máximo ratio de sharpe tiene una asignación muy pronunciada (la mayoría de los 10 activos tienen asignación 0).
Como este trabajo es muy crítico para mí, quería preguntar a la comunidad si habéis experimentado un comportamiento similar. ¿Es normal que al generar carteras aleatorias ni siquiera una se acerque a la frontera eficiente?
A continuación encontrará el código:
def portfolio_annualised_performance(weights, mean_returns, cov_matrix):
returns = np.sum(mean_returns*weights )
std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
return std, returns
def random_portfolios(num_portfolios, mean_returns, cov_matrix, risk_free_rate):
results = np.zeros((3,num_portfolios))
weights_record = []
for i in range(num_portfolios):
weights = abs(np.random.randn(len(mean_returns)))
weights /= np.sum(weights)
weights_record.append(weights)
portfolio_std_dev, portfolio_return = portfolio_annualised_performance(weights, mean_returns, cov_matrix)
results[0,i] = portfolio_std_dev
results[1,i] = portfolio_return
results[2,i] = (portfolio_return - risk_free_rate) / portfolio_std_dev
return results, weights_record
def neg_sharpe_ratio(weights, mean_returns, cov_matrix, risk_free_rate):
p_var, p_ret = portfolio_annualised_performance(weights, mean_returns, cov_matrix)
return -(p_ret - risk_free_rate) / p_var
def max_sharpe_ratio(mean_returns, cov_matrix, risk_free_rate):
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix, risk_free_rate)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bound = (0.0,1.0)
bounds = tuple(bound for asset in range(num_assets))
result = sco.minimize(neg_sharpe_ratio, num_assets*[1./num_assets,], args=args,
method='SLSQP', bounds=bounds, constraints=constraints)
return result
def portfolio_volatility(weights, mean_returns, cov_matrix):
return portfolio_annualised_performance(weights, mean_returns, cov_matrix)[0]
def min_variance(mean_returns, cov_matrix):
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bound = (0.0,1.0)
bounds = tuple(bound for asset in range(num_assets))
result = sco.minimize(portfolio_volatility, num_assets*[1./num_assets,], args=args,
method='SLSQP', bounds=bounds, constraints=constraints)
return result
def efficient_return(mean_returns, cov_matrix, target):
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix)
def portfolio_return(weights):
return portfolio_annualised_performance(weights, mean_returns, cov_matrix)[1]
constraints = ({'type': 'eq', 'fun': lambda x: portfolio_return(x) - target},
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bounds = tuple((0.0,1) for asset in range(num_assets))
result = sco.minimize(portfolio_volatility, num_assets*[1./num_assets,], args=args, method='SLSQP', bounds=bounds, constraints=constraints)
return result
def efficient_frontier(mean_returns, cov_matrix, returns_range):
efficients = []
for ret in returns_range:
efficients.append(efficient_return(mean_returns, cov_matrix, ret))
return efficients
def display_calculated_ef_with_random(mean_returns, cov_matrix, num_portfolios, risk_free_rate):
results, _ = random_portfolios(num_portfolios,mean_returns, cov_matrix, risk_free_rate)
max_sharpe = max_sharpe_ratio(mean_returns, cov_matrix, risk_free_rate)
sdp, rp = portfolio_annualised_performance(max_sharpe['x'], mean_returns, cov_matrix)
max_sharpe_allocation = pd.DataFrame(max_sharpe.x,index=curr_w_terms,columns=['allocation'])
max_sharpe_allocation.allocation = [round(i*100,4)for i in max_sharpe_allocation.allocation]
max_sharpe_allocation = max_sharpe_allocation.T
min_vol = min_variance(mean_returns, cov_matrix)
sdp_min, rp_min = portfolio_annualised_performance(min_vol['x'], mean_returns, cov_matrix)
min_vol_allocation = pd.DataFrame(min_vol.x,index=curr_w_terms,columns=['allocation'])
min_vol_allocation.allocation = [round(i*100,4)for i in min_vol_allocation.allocation]
min_vol_allocation = min_vol_allocation.T
print("-"*80)
print("Maximum Sharpe Ratio Portfolio Allocation\n")
print("Annualised Return:", round(rp,4))
print("Annualised Volatility:", round(sdp,4))
print("\n")
print(max_sharpe_allocation)
print("-"*80)
print("Minimum Volatility Portfolio Allocation\n")
print("Annualised Return:", round(rp_min,4))
print("Annualised Volatility:", round(sdp_min,4))
print("\n")
print(min_vol_allocation)
plt.figure(figsize=(10, 7))
plt.scatter(results[0,:],results[1,:],c=results[2,:],cmap='YlGnBu', marker='o', s=10, alpha=0.3)
plt.colorbar()
plt.scatter(sdp,rp,marker='*',color='r',s=500, label='Maximum Sharpe ratio')
plt.scatter(sdp_min,rp_min,marker='*',color='g',s=500, label='Minimum volatility')
target = np.linspace(rp_min, 0.05, 20)
efficient_portfolios = efficient_frontier(mean_returns, cov_matrix, target)
plt.plot([p['fun'] for p in efficient_portfolios], target, linestyle='-.', color='black', label='efficient frontier')
plt.title('Calculated Portfolio Optimization based on Efficient Frontier')
plt.xlabel('annualised volatility')
plt.ylabel('annualised returns')
plt.legend(labelspacing=0.8)
plt.ylim([-0.005,0.03])
plt.xlim([0.0,0.05])
display_calculated_ef_with_random(log_ret, new_cov, 50000, 0)
No he anualizado la matriz Covar porque ya tengo estimaciones de rentabilidad anual además de las estimaciones Covar.
Mi pregunta es: ¿es esto plausible o no?
EDITAR Como el proceso de generación de pesos de mis carteras aleatorias parece preferir una cartera demasiado similar, he cambiado la siguiente función:
def random_portfolios(num_portfolios, mean_returns, cov_matrix, risk_free_rate):
results = np.zeros((3,num_portfolios))
weights_record = []
for i in range(num_portfolios):
weights = abs(np.random.randn(len(mean_returns)))
weights[weights<1] = 0
if sum(weights)==0:
print("sum=0")
indexes = np.unique(np.random.randint(0,10,3)).tolist()
weights[indexes] = abs(np.random.randn(len(indexes)))
weights /= np.sum(weights)
weights_record.append(weights)
portfolio_std_dev, portfolio_return = portfolio_annualised_performance(weights, mean_returns, cov_matrix)
results[0,i] = portfolio_std_dev
results[1,i] = portfolio_return
results[2,i] = (portfolio_return - risk_free_rate) / portfolio_std_dev
return results, weights_record
Después de hacerlo, las carteras están mucho mejor distribuidas:
Entonces, ¿podemos estar de acuerdo en que el código anterior hace lo que debe y puedo continuar desde aquí?
0 votos
No me gusta su método para generar pesos aleatorios. Está sesgado hacia la cartera de igual peso y no cubre todo el simplex de manera uniforme.
0 votos
¿alguna sugerencia de cómo superar esto?
0 votos
¿Se trata únicamente de una cartera de bonos?
0 votos
@Dave Harris: sí lo es. ¿Hay alguna diferencia?
2 votos
Sí, es una diferencia. La frontera eficiente ha sido falsificada, ampliamente, para todos los valores de renta variable. Los rendimientos de las acciones carecen de un primer momento. Si yo hiciera lo mismo que tú, en lugar de una covarianza de precios, que de todas formas es una afirmación histórica. Me fijaría en la probabilidad de impago y en la matriz de covarianza a partir de ella. Al fin y al cabo, para los bonos de tipo fijo, lo único que importa es el impago. El riesgo de impago a plazo es el verdadero riesgo de los bonos, suponiendo que no haya crisis de liquidez.
0 votos
Lo entiendo perfectamente. En mi situación especial asumo que no habrá impagos. Pero sin embargo necesito esta optimización ya que voy a asignar el capital a lo largo de diferentes inversiones en bonos. Lo bueno aquí es que conozco muy bien los rendimientos esperados (lo que obviamente es un problema con la renta variable) y debido al comportamiento histórico de las diferentes curvas de swap soy capaz de calcular una Matriz de Covarianza fiable. Al fin y al cabo, a mí me funciona. Pero claro, soy muy consciente de que no hay un mundo libre de impagos.