Opbygning af effektive aktieporteføljer indenfor fragtbranchen i Python

I tidligere indlæg har jeg demonstreret, hvordan du kan få adgang til aktiekursdata med f.eks. pandas_datareader i Python. I dette indlæg vil jeg præsentere en algoritme, som du kan konstruere en effektiv portefølje af aktier med. Algoritmen bestemmer den optimale andel af hver aktie i din portefølje baseret på det ønskede risikoniveau.

Mere præcist vil jeg præsentere en metode til visualisering af den effektive grænse for teoretisk mulige aktieporteføljer bestående af et specifikt sæt aktier. I dette eksempel vil jeg arbejde med følgende aktier fra fragtbranchen:

  • 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)

Risiko måles i dette indlæg som standardafvigelse af historisk afkast. Afkast måles som det gennemsnitlige historiske daglige aktieafkast (ved hjælp af sammenligning af daglige aktieslutkurser).

Jeg starter med at importere relevante moduler i Python:

# importer relevante moduler
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

Jeg ønsker at indsamle historiske aktiekursdata for de foregående 6 måneder. Nedenfor angiver jeg start- og slutdato for den relevante periode, hvortil data så indsamles:

# specificer relevant start- og slutdato for aktieprisdataindsamlingsperioden
start_date = datetime.datetime(2020,4,1)
end_date = datetime.datetime(2020,9,30)

Dernæst definerer jeg en hjælpefunktion, som indsamler en dataramme for aktiekurser fra Yahoo Finance via pandas_datareader. Funktionen oversætter de indsamlede aktiekursdata til daglige afkast:

# definer funktion, der returnerer en liste med daglige returneringer
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)

Nu definerer jeg en anden hjælpefunktion, som indsamler og opsætter en liste over aktienavne og beregner deres gennemsnitlige daglige afkast og deres standardafvigelse af daglige afkast over en bestemt periode. Også denne funktion gør brug af pandas_datareader til at indsamle aktiekursdata fra Yahoo Finance:

# definer funktion, der kan konstruere dataramme med standardafvigelser og gennemsnitlige daglige afkast
def analyzeStocks(tickersArr,start_date,end_date):
    # Opret tom datarammeskabelon
    index = ["ticker","return","stdev"]
    muArr = []
    sigmaArr = []
    # løb gennem alle tickers
    for i in range(0,len(tickersArr)):
        # tilføj ticker til tabellen
        tick = tickersArr[i]
        # få aktiekursdata
        data = web.DataReader(tickersArr[i],"yahoo",start_date,end_date)
        # beregne gennemsnitligt dagligt afkast
        muArr.append(stat.mean(returns(data)))
        # beregne standardafvigelse
        sigmaArr.append(stat.stdev(returns(data)))
    # returner en dataramme
    return(pd.DataFrame(np.array([tickersArr, muArr, sigmaArr]),index=index,columns=tickersArr))

De følgende aktier, med de følgende aktiemærker, undersøges i dette indlæg:

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

Ved hjælp af ovenstående aktiemærker (tickers) udfører jeg analyseStocks for at trække aktiekursdata og beregne de gennemsnitlige daglige afkast og dertilhørende standardafvigelse:

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

Ved hjælp af matplotlib laver jeg et simpelt spredningsdiagram over de enkelte aktiers historiske afkast og standardafvigelse:

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')

Nu definerer jeg en funktion til porteføljeopbygning. Funktionen opretter et defineret antal porteføljer med tilfældigt tildelt aktievægtning. Det forventede afkast og standardafvigelsen for daglige afkast som følge heraf returneres i form af en Pandas-dataramme:

# definer funktion til oprettelse af defineret antal porteføljer
def portfolioBuilder(n,tickersArr,start_date,end_date):
    muArr = []
    sigmaArr = []
    dailyreturnsArr = []
    weightedreturnsArr = []
    portfoliodailyreturnsArr = []
    # udfyld daglige afkast
    for i in range(0,len(tickersArr)):
        data = web.DataReader(tickersArr[i],"yahoo",start_date,end_date)
        dailyreturnsArr.append(returns(data))
    # Opret n forskellige porteføljer
    for i in range(0,n):
        # nulstil daglig porteføljeliste
        portfoliodailyreturnsArr = []
        # Opret porteføljevægt
        weightsArr = [rnd.uniform(0,1) for i in range(0,len(tickersArr))]
        nweightsArr = [i/sum(weightsArr) for i in weightsArr]
        # vægt det daglige afkast
        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)
        # beregne og tilføje mavg daglige vægtede porteføljeafkast
        muArr.append(stat.mean(portfoliodailyreturnsArr))
        # beregn standardafvigelse for den vægtede porteføljes daglige afkast
        sigmaArr.append(stat.stdev(portfoliodailyreturnsArr))
    # returnerer forventet afkast og standardafvigelse for de oprettede porteføljer
    return([sigmaArr,muArr])

Jeg anvender porteføljebygningsfunktionen til tickerne og plotter resultatet ved hjælp af 500.000 tilfældige porteføljer ved hjælp af 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')

Dette diagram giver dig mulighed for at validere om din nuværende aktievægtning er effektivt. Effektivt vægtede porteføljer vil ligger placeret langs den øverste linje i spredningsdiagrammet.

Leave a Reply

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Close

Meta