Construyendo carteras eficientes de acciones de transporte de mercancías y camiones en Python

En publicaciones anteriores he demostrado cómo se pueden consultar los datos del precio de las acciones con, por ejemplo, pandas_datareader en Python.

En esta publicación, presentaré un algoritmo con el que puede construir una cartera eficiente en función de cualquier conjunto de acciones que pueda estar considerando. El algoritmo determinará la participación óptima de cada acción en su cartera, en función del nivel de riesgo que se sienta seguro de asumir.

Más precisamente, presentaré un método para visualizar la frontera eficiente de carteras que consisten en un conjunto específico de acciones. En este ejemplo, trabajaré con las siguientes 8 acciones, que son todas empresas de transporte:

  • Yamato Holdings (YATRY)
  • Knight-Swift Transportation Holdings (KNX)
  • BEST (BEST)
  • YRC Worldwide (YRCW)
  • Schneider National (SNDR)
  • Old Dominion Freight Line (ODFL)
  • Arc Best (ARCB)
  • Werner Enterprises (WERN)

El riesgo se mide como la desviación estándar de los rendimientos históricos. El rendimiento se mide como el rendimiento diario histórico promedio de las acciones (utilizando los precios de cierre).

Empiezo importando algunos módulos relevantes en Python:

# importar módulos relevantes
import pandas as pd
import numpy as np
import pandas_datareader.data as web
import datetime
import matplotlib.pyplot as plt
import statistics as stat
import random as rnd
from matplotlib.ticker import StrMethodFormatter

Quiero recopilar datos históricos sobre el precio de las acciones de los 6 meses anteriores. A continuación, especifico la fecha de inicio y finalización del período relevante para recopilar datos:

# especificar la fecha de inicio y finalización relevante para el período de recopilación de datos de precios de acciones
start_date = datetime.datetime(2020,4,1)
end_date = datetime.datetime(2020,9,30)

A continuación, defino una función auxiliar que tomará un marco de datos de precios de acciones recopilado de Yahoo Finance a través de pandas_datareader y lo traducirá en rendimientos diarios:

# define la función que devuelve una lista de devoluciones diarias
def returns(df):
    prices = df["Close"]
    returns = [0 if i == 0 else (prices[i]-prices[i-1])/(prices[i-1]) for i in range(0,len(prices))]
    return(returns)

Ahora defino otra función auxiliar que tomará una lista de ticks de acciones y calculará su rendimiento diario promedio y su desviación estándar de los rendimientos diarios para un período específico. Esta función hace uso de pandas_datareader para consultar datos de precios de acciones de Yahoo:

# definir la función que puede construir un marco de datos con desviaciones estándar y retornos diarios promedio
def analyzeStocks(tickersArr,start_date,end_date):
    # crear plantilla de marco de datos vacía
    index = ["ticker","return","stdev"]
    muArr = []
    sigmaArr = []
    # recorrer todos los tickers
    for i in range(0,len(tickersArr)):
        # agregar ticker a la tabla
        tick = tickersArr[i]
        # obtener datos de precios de acciones
        data = web.DataReader(tickersArr[i],"yahoo",start_date,end_date)
        # calcular el rendimiento diario promedio
        muArr.append(stat.mean(returns(data)))
        # calcular la desviación estándar
        sigmaArr.append(stat.stdev(returns(data)))
    # devolver un marco de datos
    return(pd.DataFrame(np.array([tickersArr, muArr, sigmaArr]),index=index,columns=tickersArr))

En esta publicación quiero analizar las garrapatas a continuación:

tickersArr = ["YATRY","KNX","BEST","YRCW","SNDR","ODFL","ARCB","WERN"]

Utilizando los tickers anteriores ejecuto analyseStocks para extraer los datos del precio de las acciones y calcular el promedio. rendimiento diario y desviación estándar de los rendimientos diarios:

base_df = analyzeStocks(tickersArr,start_date,end_date)
base_df
YATRYKNXBESTYRCWSNDRODFLARCBWERN
tickerYATRYKNXBESTYRCWSNDRODFLARCBWERN
return0.0046537435231962980.0023175179239564793-0.00341243394859026650.0111591997557838490.0024620517170550630.0033492593161784590.0058616868290849180.0017903742321965712
stdev0.023584636993742740.021140916591625140.0313978411557502770.094552762399063540.0193725719356334160.0233054617384102940.0372340691779706750.02237976138155402

Usando matplotlib hago un diagrama de dispersión simple del rendimiento histórico frente al rendimiento de la volatilidad de las acciones individuales:

plt.figure(figsize=(15,8))
muArr = [float(i) for i in base_df.iloc[1,]]
sigmaArr = [float(i) for i in base_df.iloc[2,]]
sharpeArr = [muArr[i]/sigmaArr[i] for i in range(0,len(muArr))]
plt.scatter(sigmaArr,muArr,c=sharpeArr,cmap="plasma")
plt.title("Historical avg. returns vs. standard deviations [single stocks]",size=22)
plt.xlabel("Standard deviation",size=14)
plt.ylabel("Avg. daily return",size=14)
Text(0, 0.5, 'Avg. daily return')

Ahora, defino una función de creación de cartera. La función creará un número definido de carteras con pesos asignados aleatoriamente por acción. El rendimiento esperado y la desviación estándar de los rendimientos diarios resultantes de esto se devuelven en forma de un marco de datos de Pandas:

# definir función para crear un número definido de carteras
def portfolioBuilder(n,tickersArr,start_date,end_date):
    muArr = []
    sigmaArr = []
    dailyreturnsArr = []
    weightedreturnsArr = []
    portfoliodailyreturnsArr = []
    # rellenar devoluciones diarias
    for i in range(0,len(tickersArr)):
        data = web.DataReader(tickersArr[i],"yahoo",start_date,end_date)
        dailyreturnsArr.append(returns(data))
    # crear n carteras diferentes
    for i in range(0,n):
        # restablecer la lista de cartera diaria
        portfoliodailyreturnsArr = []
        # crear peso de cartera
        weightsArr = [rnd.uniform(0,1) for i in range(0,len(tickersArr))]
        nweightsArr = [i/sum(weightsArr) for i in weightsArr]
        # ponderar los retornos diarios
        for j in range(0,len(dailyreturnsArr[0])):
            temp = 0
            for k in range(0,len(tickersArr)):
                temp = temp + float(dailyreturnsArr[k][j])*float(nweightsArr[k])
            portfoliodailyreturnsArr.append(temp)
        # calcular y agregar rendimientos de cartera ponderados diarios mavg
        muArr.append(stat.mean(portfoliodailyreturnsArr))
        # calcular y agregar la desviación estándar de los rendimientos diarios de la cartera ponderada
        sigmaArr.append(stat.stdev(portfoliodailyreturnsArr))
    # retorno de los rendimientos esperados y la desviación estándar de las carteras creadas
    return([sigmaArr,muArr])

Aplico la función de construcción de carteras a los tickers y trazo el resultado usando 500000 carteras aleatorias usando matplotlib.pyplot:

portfoliosArr = portfolioBuilder(500000,tickersArr,start_date,end_date)
plt.figure(figsize=(15,8))
muArr = [float(portfoliosArr[1][i]) for i in range(0,len(portfoliosArr[1]))]
sigmaArr = [float(portfoliosArr[0][i]) for i in range(0,len(portfoliosArr[0]))]
sharpeArr = [muArr[i]/sigmaArr[i] for i in range(0,len(muArr))]
plt.scatter(sigmaArr,muArr,c=sharpeArr,cmap="plasma")
plt.title("Historical avg. returns vs. standard deviations [single stocks]",size=22)
plt.colorbar(label="Sharpe ratio")
plt.xlabel("Standard deviation",size=14)
plt.ylabel("Avg. daily returns",size=14)
Text(0, 0.5, 'Avg. daily returns')

Este gráfico le permite validar si su elección actual de pesos para sus acciones de elección es actualmente eficiente o no. Las carteras eficientes se ubicarían a lo largo de la línea superior del diagrama de dispersión.

You May Also Like

Leave a Reply

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.