Optimización de Portafolio – Modelo de Markowitz
Optimización de Portafolio – Modelo de Markowitz
El modelo de Markowitz, desarrollado en 1952, tiene como objetivo identificar la cartera óptima que maximiza el rendimiento esperado para un nivel de riesgo dado. Se basa en la teoría moderna de carteras y utiliza la varianza como medida de riesgo. Sin embargo, este modelo presenta limitaciones debido a su suposición de información perfecta.
Esta suposición implica que los inversionistas tienen acceso a información completa y precisa sobre los rendimientos futuros de los activos y sus covarianzas, lo cual a menudo no se cumple en la realidad. Para abordar esta limitación, se han propuesto varios enfoques para modificar la teoría moderna de carteras y hacerla más aplicable en la práctica. A continuación, algunas estrategias comunes:
1. Incorporación de incertidumbre y errores de estimación
- Análisis de escenarios: Consiste en construir múltiples escenarios que representen distintas condiciones futuras del mercado y evaluar el desempeño de la cartera en cada uno.
- Optimización robusta: Busca carteras menos sensibles a errores de estimación o eventos de mercado inesperados.
- Programación dinámica: Permite rebalancear continuamente la cartera en función de la información actualizada y los cambios en el mercado.
2. Uso de métodos bayesianos y creencias subjetivas
- Optimización bayesiana de carteras: Incorpora las creencias subjetivas de los inversionistas sobre rendimientos y covarianzas en el proceso de optimización.
- Modelos jerárquicos: Combinan diferentes fuentes de información —como datos históricos, pronósticos de mercado y opiniones de expertos— para estimar los parámetros de los activos.
3. Empleo de aprendizaje automático e inteligencia artificial
- Modelado predictivo: Los algoritmos de machine learning pueden utilizarse para pronosticar rendimientos futuros de activos y sus covarianzas, aportando insumos más realistas para la optimización.
- Construcción de carteras basada en datos: Las técnicas basadas en IA pueden identificar patrones y relaciones en datos históricos que los modelos tradicionales no capturan.
En este proyecto utilizaremos las librerías yfinance y PyPortfolioOpt para desarrollar el modelo y hallar los porcentajes óptimos de inversión en cada activo.
1# Install the necessary libraries
2!pip install PyPortfolioOpt
3!pip install yfinance
4
5import pandas as pd
6import numpy as np
7import matplotlib.pyplot as plt
8import yfinance as yf
9import seaborn as sns
10sns.set_style('whitegrid')
11
12from pypfopt import EfficientFrontier
13from pypfopt import risk_models
14from pypfopt import expected_returns
15from pypfopt import objective_functions
16from pypfopt_re import plot_covariance, plot_dendrogram, plot_efficient_frontier, plot_weights
17from pypfopt import HRPOpt
Obtener datos de activos de yfinance y visualizarlos
1# Asset tickets in BVC (Colombia)
2tickets = ["AAPL", "MSFT", "TSLA", "META", "NVDA"]
3
4# Download data
5data = yf.download(tickets, start='2022-01-01', end='2023-12-31', rounding=True)
6
7# Get the closing price
8price = data['Close']
9
10plt.figure(figsize=(13, 6))
11sns.lineplot(data=price)
12plt.legend(title='Assets')
13plt.title('Closing Prices of Selected Assets')
14plt.xlabel('Date')
15plt.ylabel('Price (USD)')
16plt.show()
Calcular el riesgo y la rentabilidad
El módulo expected_returns de la biblioteca PyPortfolioOpt proporciona las funciones para estimar las rentabilidades esperadas de las acciones. Todas las funciones transforman internamente los precios de las acciones en rendimientos porcentuales antes de calcular sus respectivas estimaciones de rendimientos esperados.
- Rentabilidad histórica media.
- Rentabilidad histórica media ponderada exponencialmente
- CAPM
Por defecto, la salida de estos métodos son rendimientos esperados
1# Average historical yield
2avYield = expected_returns.mean_historical_return(price, returns_data=False, compounding=True, frequency=252, log_returns=False)
3print(avYield) # Output:
4# Ticker
5# AAPL 0.028725
6# META 0.022703
7# MSFT 0.060373
8# NVDA 0.284778
9# TSLA -0.213268
10# dtype: float64
11
12# Get organized entries
13organized_entries = avYield.index.unique()
14
15sns.barplot(x=avYield, y=organized_entries, palette='Paired')
16plt.xlabel('Average Yield')
17plt.ylabel('Category')
18plt.title('Average Yield by Category')
19plt.gca().invert_yaxis() # Invert y-axis to match Matplotlib's behavior
20plt.show()
21
22print(organized_entries)
Obtener el riesgo
El módulo risk_models de la bibliotecaPyPortfolioOpt proporciona las funciones para estimar el riesgo de las acciones. El método más utilizado es el que estima la matriz de varianzas y covarianzas a partir de datos históricos. Esta matriz es una representación del riesgo de las acciones individuales (diagonal) y de la covariación entre las acciones.
1matrix = risk_models.CovarianceShrinkage(price).ledoit_wolf()
2
3sns.heatmap(matrix)
4plt.title('Ledoit-Wolf Covariance Matrix Heatmap')
5plt.xlabel('Assets')
6plt.ylabel('Assets')
7plt.show()
Determinar las inversiones y la frontera eficiente
Determinar la partición de los activos en la cartera utilizando el método de rendimiento-riesgo. Aquí el objetivo es minimizar el riesgo con un rendimiento específico. La función EfficientFrontier permite definir la frontera eficiente, es decir, el conjunto de carteras que maximiza la rentabilidad para niveles de riesgo dados o minimiza el riesgo para niveles de rentabilidad dados.
1# Create object
2fe = EfficientFrontier(avYield, matrix, weight_bounds=(0, 1)) # weight_bounds => Portfolio restrictions
3
4# Returns the stock weights for the portfolio that minimizes volatility.
5min_risk = fe.min_volatility()
6
7# Round the weight and remove values approaching zero.
8weight = fe.clean_weights()
9print(weight) # Output:
10# AAPL 0.58697
11# META 0.00000
12# MSFT 0.41303
13# NVDA 0.00000
14# TSLA 0.00000
15# dtype: float64
16
17# Function for plotting weights
18def plotWeight(x, name_model):
19 # Filter non-zero weights
20 non_zero_weights = x[x > 0] # Filter for weights greater than 0
21
22 # Create pie chart (if there are non-zero weights)
23 if not non_zero_weights.empty:
24 plt.figure(figsize=(7, 5))
25
26 # Create pie chart with 'Paired' color palette
27 plt.pie(non_zero_weights, labels=non_zero_weights.index, autopct='%1.1f%%', startangle=90, colors=sns.color_palette('Paired'))
28
29 # Customize pie chart appearance
30 plt.axis('equal')
31 plt.title(name_model)
32 plt.legend(title="Action")
33 plt.tight_layout()
34 plt.show()
35 else:
36 print("No non-zero weights found. Pie chart cannot be created.")
37
38plotWeight(weight, 'Minimum Risk')
39
40weight = pd.Series(weight)
41weight
1# Calculate portfolio performance metrics
2fe.portfolio_performance(verbose=True) # This prints expected annual return, volatility, and Sharpe ratio
Ahora Max Sharpe Ratio
Este método trata de determinar las ponderaciones o participaciones de valores que maximizan la rentabilidad por unidad de riesgo. Esta cartera se conoce como cartera de tangencia, es la cartera en la que la línea del mercado de capitales es tangente a la frontera eficiente.
1# Create object for maximizing Sharpe ratio
2feSharpe = EfficientFrontier(avYield, matrix, weight_bounds=(0, 1))
3
4# Maximize Sharpe ratio using US10Y risk-free rate
5max_sharpe = feSharpe.max_sharpe(risk_free_rate=0.04637)
6
7# Clean weights
8weight_sharpe = feSharpe.clean_weights()
9print(weight_sharpe) # Output:
10# AAPL 0.58697
11# META 0.00000
12# MSFT 0.41303
13# NVDA 0.00000
14# TSLA 0.00000
15# dtype: float64
16
17# Convert to Series
18weight_sharpe = pd.Series(weight_sharpe)
19
20# Plot weights
21plotWeight(weight_sharpe, 'Max Sharpe Ratio')
22
23# Portfolio performance
24feSharpe.portfolio_performance(verbose=True) # Output:
25# Expected annual return: 28.5%
26# Annual volatility: 56.4%
27# Sharpe Ratio: 0.42
28# (0.28477805532121625, 0.5636022680619506, 0.42300762227417205)
Máxima rentabilidad dado un nivel de riesgo (Insaciabilidad)
1# Create object for specific level of risk
2feLevel = EfficientFrontier(avYield, matrix)
3
4# Optimize for maximum return at a given risk level (target volatility)
5feLevel.efficient_risk(target_volatility=0.299999) # Level of risk => 0.3
6
7# Clean and convert weights to Series
8weight_level = feLevel.clean_weights()
9print(weight_level) # Output:
10# AAPL 0.41207
11# META 0.00000
12# MSFT 0.46412
13# NVDA 0.12382
14# TSLA 0.00000
15# dtype: float64
16
17weight_level = pd.Series(weight_level)
18
19# Plot pie chart
20plotWeight(weight_level, 'Maximum return given a level of risk (Insatiability)')
21
22# Portfolio performance
23feLevel.portfolio_performance(verbose=True) # Output:
24# Expected annual return: 7.5%
25# Annual volatility: 30.0%
26# Sharpe Ratio: 0.18
27# (0.0751171020675928, 0.2999990000406189, 0.18372428594805354)
Riesgo mínimo dado un nivel de rentabilidad
Determina las participaciones que minimizan el riesgo para un nivel de rentabilidad dado. Es uno de los supuestos básicos de la teoría de Markowitz (aversión al riesgo).
1# Create object for minimum risk given a target return
2feMinRisk = EfficientFrontier(avYield, matrix)
3
4# Optimize for minimum volatility at a target return
5feMinRisk.efficient_return(target_return=0.25)
6
7# Clean and convert weights to Series
8weight_min_risk = feMinRisk.clean_weights()
9print(weight_min_risk) # Output:
10# AAPL 0.00000
11# META 0.00000
12# MSFT 0.15498
13# NVDA 0.84502
14# TSLA 0.00000
15# dtype: float64
16
17weight_min_risk = pd.Series(weight_min_risk)
18
19# Plot pie chart
20plotWeight(weight_min_risk, 'Minimum risk given a level of return')
21
22# Portfolio performance
23feMinRisk.portfolio_performance(verbose=True) # Output:
24# Expected annual return: 25.0%
25# Annual volatility: 51.0%
26# Sharpe Ratio: 0.45
27# (0.25, 0.5102518800556977, 0.4507577707991862)
Trazado de carteras
La frontera eficiente se trazará utilizando el módulo pypfopt_re.py y la simulación Monte Carlo para generar carteras aleatoriamente.
1# Create Efficient Frontier object
2feP = EfficientFrontier(avYield, matrix)
3
4# Plot the efficient frontier
5fig, ax = plt.subplots()
6plot_efficient_frontier(feP, show_assets=False)
7
8# Generate and plot random portfolios
9n_portofolios = 10000
10w = np.random.dirichlet(np.ones(len(price.columns)), n_portofolios)
11yieldPort = w.dot(avYield)
12risk = np.sqrt(np.diag(w @ matrix @ w.T))
13sharpes = yieldPort / risk
14
15# Scatter plot of random portfolios
16ax.scatter(risk, yieldPort, marker=".", c=sharpes, cmap="viridis_r")
17
18# Find and plot the minimum risk (tangent) portfolio
19feP1 = EfficientFrontier(avYield, matrix)
20feP1.min_volatility()
21yield_tangente, risk_tangente, _ = feP1.portfolio_performance()
22ax.scatter(risk_tangente, yield_tangente, marker="o", s=200, c="r", label="Tangent portfolio")
23
24# Customize plot
25ax.set_title("Efficient frontier with tangency portfolio")
26ax.legend()
27plt.ylabel("Yield")
28plt.xlabel("Risk")
29plt.show()
Explora el Proyecto
Contenido Relacionado
¿Qué es el modelo de Markowitz?
El modelo trata de aproximar la gestión de carteras hacía la curva de eficiencia para intentar maximizar el beneficio, pero minimizando el riesgo dentro de los mismos tipos de activos.
Optimización de Portafolios con Python
Optimización de Portafolios (Gestión de Carteras) de Inversión en Python
Comentarios
Cargando comentarios...