Investigating the effect of Company Announcements on their Share Price following COVID-19 (using the S&P 500)

Jonathan Legrand
Developer Advocate Developer Advocate

A lot of company valuation speculation has come about since the COrona-VIrus-Disease-2019 (COVID-19 or COVID for short) started to impact the stock market (estimated on the 20th of February 2020, 2020-02-20). Many investors tried to estimate the impact of the outbreak on businesses and trade accordingly as fast as possible. In this haste, it is possible that they miss-priced the effect of COVID on certain stocks.

This article lays out a framework to investigate whether the Announcement of Financial Statements after COVID (id est (i.e.): after 2020-02-20) impacted the price of stocks in any specific industry sector. It will proceed simply by producing a graph of the movement in average daily close prices for each industry - averaged from the time each company produced a Post COVID Announcement (i.e.: after they first produced a Financial Statement after 2020-02-20).

From there, one may stipulate that a profitable investment strategy could consist in going long in stocks of companies (i) that did not release an announcement since COVID yet (ii) within a sector that the framework below suggests will probably increase in price following from such an announcement.

 

 

Pre-Requisites

Required Python Packages: Refinitiv Eikon Python APINumpyPandas, and Matplotlib.

The Python built in modules datetime and dateutil are also required.

Supplementary

pickle: If one wishes to copy and manipulate this code, 'pickling' data along the way should aid in making sure no data is lost when / in case there are kernel issues.

 

Import libraries

First, we can use the library ' platform ' to show which version of Python we are using

    	
            # The ' from ... import ' structure here allows us to only import the module ' python_version ' from the library ' platform ':
from platform import python_version
print("This code runs on Python version " + python_version())

This code runs on Python version 3.7.7

We use Refinitiv's Eikon Python Application Programming Interface (API) to access financial data. We can access it via the Python library "eikon" that can be installed simply by using pip install.

    	
            import eikon as ek

# The key is placed in a text file so that it may be used in this code without showing it itself:
eikon_key = open("eikon.txt","r")
ek.set_app_key(str(eikon_key.read()))
# It is best to close the files we opened in order to make sure that we don't stop any other services/programs from accessing them if they need to:
eikon_key.close()

The following are Python-built-in modules/libraries, therefore they do not have specific version numbers.

    	
            

# datetime will allow us to manipulate Western World dates

import datetime

 

# dateutil will allow us to manipulate dates in equations

import dateutil

numpy is needed for datasets' statistical and mathematical manipulations

    	
            

import numpy

print("The numpy library imported in this code is version: " + numpy.__version__)

The numpy library imported in this code is version: 1.18.2

pandas will be needed to manipulate data sets

    	
            

import pandas

# This line will ensure that all columns of our dataframes are always shown:

pandas.set_option('display.max_columns', None)

print("The pandas library imported in this code is version: " + pandas.__version__)

The pandas library imported in this code is version: 1.0.3

matplotlib is needed to plot graphs of all kinds

    	
            

import matplotlib

# the use of ' as ... ' (specifically here: ' as plt ') allows us to create a shorthand for a module (here: ' matplotlib.pyplot ')

import matplotlib.pyplot as plt

print("The matplotlib library imported in this code is version: " + matplotlib.__version__)

The matplotlib library imported in this code is version: 3.2.1

Defining Functions

The cell below defines a function to plot data on one y axis (as opposed to two, one on the right and one on the left).

    	
            

# Using an implicitly registered datetime converter for a matplotlib plotting method is no longer supported by matplotlib. Current versions of pandas requires explicitly registering matplotlib converters:

pandas.plotting.register_matplotlib_converters()

 

def plot1ax(dataset, ylabel = "", title = "", xlabel = "Year",

            datasubset = [0], # datasubset needs to be a list of the number of each column within the dtaset that needs to be labelled on the left

            datarange = False, # If wanting to plot graph from and to a specific point, make datarange a list of start and end date

            linescolor = False, # This needs to be a list of the color of each vector to be plotted, in order they are shown in their dataframe from left to right

            figuresize = (12,4), # This can be changed to give graphs of different proportions. It is defaulted to a 12 by 4 (ratioed) graph

            facecolor="0.25",# This allows the user to change the background color as needed

            grid = True,  # This allows us to decide whether or not to include a grid in our graphs

            time_index = [], time_index_step = 48, # These two variables allow us to dictate the frequency of the ticks on the x-axis of our graph

            legend = True): 

    

    # The if statement below allows for manipulation of the date range that we would like to graph:

    if datarange == False:

        start_date = str(dataset.iloc[:,datasubset].index[0])

        end_date = str(dataset.iloc[:,datasubset].index[-1])

    else:

        start_date = str(datarange[0])

        

        # The if statement below allows us to graph to the end of the dataframe if wanted, whatever date that may be:

        if datarange[-1] == -1:

            end_date = str(dataset.iloc[:,datasubset].index[-1])

        else:

            end_date = str(datarange[-1])

    

    fig, ax1 = plt.subplots(figsize = figuresize, facecolor = facecolor)

    ax1.tick_params(axis = 'both', colors = 'w')

    ax1.set_facecolor(facecolor)

    fig.autofmt_xdate()

    plt.ylabel(ylabel, color ='w')

    ax1.set_xlabel(str(xlabel), color = 'w')

    

    if linescolor == False:

        for i in datasubset: # This is to label all the lines in order to allow matplot lib to create a legend

            ax1.plot(dataset.iloc[:, i].loc[start_date : end_date],

                     label = str(dataset.columns[i]))

    else:

        for i in datasubset: # This is to label all the lines in order to allow matplot lib to create a legend

            ax1.plot(dataset.iloc[:, i].loc[start_date : end_date],

                     label = str(dataset.columns[i]),

                     color = linescolor)

    

    ax1.tick_params(axis='y')

    

    if grid == True:

        ax1.grid()

    else:

        pass

    

    if len(time_index) != 0:

        # locs, labels = plt.xticks()

        plt.xticks(numpy.arange(len(dataset.iloc[:,datasubset]), step = time_index_step), [i for i in time_index[0::time_index_step]])

    else:

        pass

    

    ax1.set_title(str(title) + " \n", color='w')

 

    

    if legend == True:

        plt.legend()

    elif legend == "underneath":

        ax1.legend(loc = 'upper center', bbox_to_anchor = (0.5, -0.3), fancybox = True, shadow = True, ncol = 5)

    elif legend != False:

        plt.legend().get_texts()[0].set_text(legend)

    

    

    plt.show()

The cell below defines a function that adds a series of daily close prices to the dataframe named 'daily_df' and plots it.

    	
            

# Defining the ' daily_df ' variable before the ' Get_Daily_Close ' function

daily_df = pandas.DataFrame()

 

def Get_Daily_Close(instrument, # Name of the instrument in a list.

                    days_back, # Number of days from which to collect the data.

                    plot_title = False, # If ' = True ', then a graph of the data will be shown.

                    plot_time_index_step = 30 * 3, # This line dictates the index frequency on the graph/plot's x axis.

                    col = ""): # This can be changed to name the column of the merged dataframe.

    

    # This instructs the function to use a pre-defined ' daily_df ' variable:

    global daily_df

    

    if col == "":

        # If ' col ' is not defined, then the column name of the data will be replaced with its instrument abbreviated name followed by " Close Price".

        col = str(instrument) + " Close Price"

    else:

        pass

    

    # This allows for the function to programmatically ensure that all instruments' data are collected - regardless of potential server Timeout Errors.

    worked = False

    while worked != True:

        try:

            instrument, err = ek.get_data(instruments = instrument,

                                          fields = [str("TR.CLOSEPRICE(SDate=-" + str(days_back) + ",EDate=0,Frq=D,CALCMETHOD=CLOSE).timestamp"),

                                                    str("TR.CLOSEPRICE(SDate=-" + str(days_back) + ",EDate=0,Frq=D,CALCMETHOD=CLOSE)")])

            instrument.dropna()

            worked = True

        except:

            # Note that this ' except ' is necessary

            pass

            

    instrument = pandas.DataFrame(list(instrument.iloc[:,2]), index = list(instrument.iloc[:,1]), columns = [col])

    instrument.index = pandas.to_datetime(instrument.index, format = "%Y-%m-%d")

    

    if plot_title != False:

            plot1ax(dataset = instrument.dropna(), ylabel = "Close Price", title = str(plot_title), xlabel = "Year", # legend ="Close Price",

                    linescolor = "#ff9900", time_index_step = plot_time_index_step, time_index = instrument.dropna().index)

    

    daily_df = pandas.merge(daily_df, instrument, how = "outer", left_index = True, right_index = True)

The cell below sets up a function that gets Eikon recorded Company Announcement Data through time for any index (or instrument)

    	
            

def Get_Announcement_For_Index(index_instrument, periods_back, show_df = False, show_list = False):

    

    # This allows the function to collect a list of all constituents of the index

    index_issuer_rating, err = ek.get_data(index_instrument, ["TR.IssuerRating"])

    index_Announcement_list = []

    

    for i in range(len(index_issuer_rating)):

        

        # This allows for the function to programmatically ensure that all instruments' data are collected - regardless of potential server Timeout Errors.

        worked = False

        while worked != True:

            try: # The ' u ' in ' index_issuer_rating_u ' is for 'unique' as it will be for each unique instrument

                index_Announcement_u, err = ek.get_data(index_issuer_rating.iloc[i,0],

                                                        ["TR.JPINCOriginalAnnouncementDate(SDate=-" + str(periods_back) + ",EDate=0,,Period=FI0,Frq=FI)",

                                                         "TR.JPCASOriginalAnnouncementDate(SDate=-" + str(periods_back) + ",EDate=0,,Period=FI0,Frq=FI)",

                                                         "TR.JPBALOriginalAnnouncementDate(SDate=-" + str(periods_back) + ",EDate=0,,Period=FI0,Frq=FI)"])

                worked = True

            except:

                # Note that this ' except ' is necessary

                pass

        

        index_Announcement_list.append(index_Announcement_u)

    

    index_Instrument = []

    index_Income_Announcement = []

    index_Cash_Announcement = []

    index_Balance_Announcement = []

    for i in range(len(index_Announcement_list)):

        for j in range(len(index_Announcement_list[i])):

            index_Instrument.append(index_Announcement_list[i].iloc[j,0])

            index_Income_Announcement.append(index_Announcement_list[i].iloc[j,1])

            index_Cash_Announcement.append(index_Announcement_list[i].iloc[j,2])

            index_Balance_Announcement.append(index_Announcement_list[i].iloc[j,3])

            

    index_Announcement_df = pandas.DataFrame(columns = ["Instrument",

                                                        "Income Statement Announcement Date",

                                                        "Cash Flos Statement Announcement Date",

                                                        "Balance Sheet Announcement Date"])

    index_Announcement_df.iloc[:,0] = index_Instrument

    index_Announcement_df.iloc[:,1] = pandas.to_datetime(index_Income_Announcement)

    index_Announcement_df.iloc[:,2] = pandas.to_datetime(index_Cash_Announcement)

    index_Announcement_df.iloc[:,3] = pandas.to_datetime(index_Balance_Announcement)

 

    if show_df == True:

        display(index_Announcement_df)

    else:

        pass

    

    if show_list == True:

        for i in range(len(index_Announcement_list)):

            display(index_Announcement_list[i])

    else:

        pass

    

    return index_Announcement_df, index_Announcement_list

Setting Up Dates

Before starting to investigate data pre- or post-COVID, we need to define the specific time when COVID affected stock markets: In this instance we chose "2020-02-20"

    	
            

COVID_start_date = datetime.datetime.strptime("2020-02-20", '%Y-%m-%d').date()

days_since_COVID = (datetime.date.today() - COVID_start_date).days

Announcements

The below collects announcements of companies within the index of choice for the past 3 financial periods. In this article, the Standard & Poor's 500 Index (S&P500 or SPX for short) is used as an example. It can be used with indices such as FTSE or DJI instead of the SPX.

    	
            

index_Announcement_df, index_Announcement_list = Get_Announcement_For_Index(index_instrument = ["0#.SPX"],

                                                                            periods_back = 3,

                                                                            show_df = False,

                                                                            show_list = False)

Now we can choose only announcements post COVID.

    	
            

Announcement_COVID_date = []

for k in (1,2,3):

    index_Instruments_COVID_date = []

    index_Announcement_post_COVID_list = []

    for i in range(len(index_Announcement_list)):

        index_Instrument_COVID_date = []

        for j in reversed(index_Announcement_list[i].iloc[:,1]):

            try: # Note that ' if (index_Announcement_list[i].iloc[1,1] - COVID_start_date).days >= 0: ' would not work

                if (datetime.datetime.strptime(index_Announcement_list[i].iloc[:,1].iloc[-1], '%Y-%m-%d').date() - COVID_start_date).days >= 0:

                    while len(index_Instrument_COVID_date) == 0:

                        if (datetime.datetime.strptime(j, '%Y-%m-%d').date() - datetime.datetime.strptime("2020-02-20", '%Y-%m-%d').date()).days >= 0:

                            index_Instrument_COVID_date.append(j)

                else:

                    index_Instrument_COVID_date.append("NaT")

            except:

                index_Instrument_COVID_date.append("NaT")

        index_Instruments_COVID_date.append(index_Instrument_COVID_date[0])

    Instruments_Announcement_COVID_date = pandas.DataFrame(index_Instruments_COVID_date, index = index_Announcement_df.Instrument.unique(), columns = ["Date"])

    Instruments_Announcement_COVID_date.Date = pandas.to_datetime(Instruments_Announcement_COVID_date.Date)

    Announcement_COVID_date.append(Instruments_Announcement_COVID_date)

    	
            

Instruments_Income_Statement_Announcement_COVID_date = Announcement_COVID_date[0]

Instruments_Income_Statement_Announcement_COVID_date.columns = ["Date of the First Income Statement Announced after COVID"]

    	
            

Instruments_Cash_Flow_Statement_Announcement_COVID_date = Announcement_COVID_date[1]

Instruments_Cash_Flow_Statement_Announcement_COVID_date.columns = ["Date of the First Cash Flow Statement Announced after COVID"]

    	
            

Instruments_Balance_Sheet_COVID_date = Announcement_COVID_date[2]

Instruments_Balance_Sheet_COVID_date.columns = ["Date of the First Balance Sheet Announced after COVID"]

Daily Price

Post COVID

The cell below collects Daily Close Prices for all relevant instruments in the index chosen.

    	
            

for i in index_Announcement_df.iloc[:,0].unique():

    Get_Daily_Close(i, days_back = days_since_COVID)

Some instruments might have been added to the index midway during our time period of choice. They are the ones below:

    	
            

removing = [i.split()[0]  + " Close Price" for i in daily_df.iloc[0,:][daily_df.iloc[0,:].isna() == True].index]

print("We will be removing " + removing + " from our dataframe")

['CARR.N Close Price', 'OTIS.N Close Price']

The cell below will remove them to make sure that they do not skew our statistics later on in the code.

    	
            

# This line removes instruments that wera added midway to the index

daily_df_no_na = daily_df.drop(removing, axis = 1).dropna()

Now we can focus on stock price movements alone.

    	
            

daily_df_trend = pandas.DataFrame(columns = daily_df_no_na.columns)

for i in range(len(pandas.DataFrame.transpose(daily_df_no_na))):

    daily_df_trend.iloc[:,i] = daily_df_no_na.iloc[:,i] - daily_df_no_na.iloc[0,i]

The following 3 cells display plots to visualise our data this far.

    	
            

datasubset_list = []

for i in range(len(daily_df_no_na.columns)):

    datasubset_list.append(i)

    	
            

plot1ax(dataset = daily_df_no_na,

        ylabel = "Close Price",

        title = "Index Constituents' Close Prices",

        xlabel = "Date",

        legend = False,

        datasubset = datasubset_list)

    	
            

plot1ax(dataset = daily_df_trend, legend = False,

        ylabel = "Normalised Close Price",

        title = "Index Constituents' Change in Close Prices",

        datasubset = datasubset_list, xlabel = "Date",)

The graph above shows the change in constituent companies' close prices since COVID.

Saving our data

The cell below saves variables to a 'pickle' file to quicken subsequent runs of this code if they are seen as necessary.

    	
            

# pip install pickle-mixin

import pickle

 

pickle_out = open("SPX.pickle","wb")

pickl = (COVID_start_date, days_since_COVID,

         index_Announcement_df, index_Announcement_list,

         Announcement_COVID_date,

         Instruments_Income_Statement_Announcement_COVID_date,

         Instruments_Cash_Flow_Statement_Announcement_COVID_date,

         Instruments_Balance_Sheet_COVID_date,

         daily_df, daily_df_no_na,

         daily_df_trend, datasubset_list)

pickle.dump(pickl, pickle_out)

pickle_out.close()

The cell below can be run to load these variables back into the kernel

    	
            

# pickle_in = open("pickl.pickle","rb")

# COVID_start_date, days_since_COVID, index_Announcement_df, index_Announcement_list, Announcement_COVID_date, Instruments_Income_Statement_Announcement_COVID_date, Instruments_Cash_Flow_Statement_Announcement_COVID_date, Instruments_Balance_Sheet_COVID_date, daily_df, daily_df_no_na, daily_df_trend, datasubset_list = pickle.load(pickle_in)

Post-COVID-Announcement Price Insight

Now we can start investigating price changes after the first Post-COVID-Announcement of each company in our dataset.

    	
            

# This is just to delimitate between the code before and after this point

daily_df2 = daily_df_no_na

The cell below formats the date-type of our data to enable us to apply them to simple algebra.

    	
            

date_in_date_format = []

for k in range(len(daily_df2)):

    date_in_date_format.append(daily_df2.index[k].date())

daily_df2.index = date_in_date_format

The cell below renames the columns of our dataset.

    	
            

daily_df2_instruments = []

for i in daily_df2.columns:

    daily_df2_instruments.append(str.split(i)[0])

Now: we collect daily prices only for dates after the first Post-COVID-Announcement of each instrument of interest

    	
            

daily_df2_post_COVID_announcement = pandas.DataFrame()

for i,j in zip(daily_df2.columns, daily_df2_instruments):

    daily_df2_post_COVID_announcement = pandas.merge(daily_df2_post_COVID_announcement,

                                                     daily_df2[i][daily_df2.index >= Instruments_Income_Statement_Announcement_COVID_date.loc[j].iloc[0].date()],

                                                     how = "outer", left_index = True, right_index = True) # Note that the following would not work: ' daily_df2_post_COVID_announcement[i] = daily_df2[i][daily_df2.index >= Instruments_Income_Statement_Announcement_COVID_date.loc[j].iloc[0].date()] '

Now we can focus on the trend/change in those prices

    	
            

daily_df2_post_COVID_announcement_trend = pandas.DataFrame()

for i in daily_df2.columns:

    try:

        daily_df2_post_COVID_announcement_trend = pandas.merge(daily_df2_post_COVID_announcement_trend,

                                                               daily_df2_post_COVID_announcement.reset_index()[i].dropna().reset_index()[i] - daily_df2_post_COVID_announcement.reset_index()[i].dropna().iloc[0],

                                                               how = "outer", left_index = True, right_index = True)

    except:

        daily_df2_post_COVID_announcement_trend[i] = numpy.nan

And plot them

    	
            

plot1ax(dataset = daily_df2_post_COVID_announcement_trend,

        ylabel = "Normalised Close Price",

        title = "Index Constituents' Trend In Close Prices From There First Income Statement Announcement Since COVID\n" + 

                "Only companies that announced an Income Statement since the start of COVID (i.e.:" + str(COVID_start_date) + ") will show",

        xlabel = "Days since first Post-COVID-Announcement",

        legend = False, # change to "underneath" to see list of all instruments and their respective colors as per this graph's legend.

        datasubset = datasubset_list)

Some companies have lost and gained a great deal following from their first Post-COVID-Announcement, but most seem to have changed by less than 50 United States of america Dollars (USD).

Post COVID Announcement Price Change

The cell below simply gathers all stocks that decreased, increased or did not change in price since their first Post-COVID-Announcement in an easy to digest pandas table. Note that is they haven't had a Post-COVID-Announcement yet, they will show as unchanged.

    	
            

COVID_priced_in = [[],[],[]]

for i in daily_df2_post_COVID_announcement_trend.columns:

    if str(sum(daily_df2_post_COVID_announcement_trend[i].dropna())) != "nan":

        if numpy.mean(daily_df2_post_COVID_announcement_trend[i].dropna()) < 0:

            COVID_priced_in[0].append(str.split(i)[0])

        if numpy.mean(daily_df2_post_COVID_announcement_trend[i].dropna()) == 0:

            COVID_priced_in[1].append(str.split(i)[0])

        if numpy.mean(daily_df2_post_COVID_announcement_trend[i].dropna()) > 0:

            COVID_priced_in[2].append(str.split(i)[0])

COVID_priced_in = pandas.DataFrame(COVID_priced_in, index = ["Did not have the negative impact of COVID priced in enough",

                                                             "Had the effects of COVID priced in (or didn't have time to react to new company announcements)",

                                                             "Had a price that overcompensated the negative impact of COVID"])

COVID_priced_in

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
Did not have the negative impact of COVID priced in enough CHRW.OQ PRGO.N BA.N MCD.N HD.N COG.N AIZ.N COST.OQ AMD.OQ REG.OQ TRV.N MOS.N WDC.OQ VTR.N STX.OQ VRSN.OQ LB.N LOW.N BSX.N MAS.N BEN.N RJF.N CE.N LLY.N NWL.OQ AVB.N TPR.N CINF.OQ SEE.N HON.N NBL.OQ EA.OQ CB.N MDLZ.OQ BLL.N JPM.N CDW.OQ COO.N UAL.OQ KHC.OQ PNR.N KSS.N DPZ.N HPQ.N TGT.N OXY.N EXC.OQ AZO.N TEL.N MXIM.OQ AAL.OQ VLO.N LH.N SHW.N GD.N SBAC.OQ COP.N GRMN.OQ TXT.N WELL.N PLD.N ROST.OQ MRK.N WEC.N TMO.N F.N LYB.N CERN.OQ PEP.OQ HP.N ABMD.OQ PH.N NSC.N BAX.N GILD.OQ JWN.N NOC.N PNW.N BFb.N DE.N HSY.N FLT.N IT.N ECL.N BXP.N GE.N ED.N WFC.N FTV.N PRU.N DLTR.OQ NEE.N ILMN.OQ XLNX.OQ CMS.N HPE.N ALGN.OQ SRE.N REGN.OQ DHR.N CME.OQ ADM.N FANG.OQ WRB.N FLS.N TROW.OQ DRE.N MLM.N TWTR.N L.N QCOM.OQ ANSS.OQ ULTA.OQ HRB.N FISV.OQ XRX.N ANET.N HLT.N NFLX.OQ AMGN.OQ KIM.N XRAY.OQ URI.N CNC.N ANTM.N K.N LEG.N PSA.N NLSN.N HOG.N BK.N MO.N HST.N BBY.N LHX.N KSU.N FE.N VZ.N NEM.N GIS.N CMCSA.OQ PFE.N EIX.N UPS.N IP.N MMM.N INTU.OQ OKE.N ADP.OQ EQIX.OQ MTD.N PEG.N CTSH.OQ NCLH.N HIG.N UHS.N INCY.OQ UNM.N LRCX.OQ PPG.N LKQ.OQ TJX.N VMC.N EW.N ALL.N MHK.N CAT.N PG.N ZTS.N AFL.N CPB.N MSI.N ITW.N GPS.N ALK.N PGR.N DISCA.OQ KMB.N EL.N SPGI.N ADSK.OQ WHR.N AKAM.OQ LUV.N AMZN.OQ FTI.N BRKb.N KR.N BKNG.OQ MA.N SWK.N DISCK.OQ OMC.N GLW.N CRM.N SBUX.OQ TAP.N ABT.N JNPR.N IRM.N PEAK.N FIS.N ROK.N HBI.N DOW.N PPL.N ETN.N CI.N XYL.N HAS.OQ SO.N VNO.N CTL.N DLR.N AVY.N None None None None None None None None None None None None None None None None None None None None None None None None None None None
Had the effects of COVID priced in (or didn't have time to react to new company announcements) AMCR.N SPG.N COTY.N UAA.N MYL.OQ CAH.N ZBH.N AEE.N IFF.N MAR.OQ ETR.N UA.N None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None None
Had a price that overcompensated the negative impact of COVID AJG.N CNP.N WM.N FOX.OQ WY.N HBAN.OQ QRVO.OQ LVS.N AIG.N MCO.N DIS.N PAYX.OQ DHI.N BWA.N IVZ.N ZBRA.OQ SYK.N NVR.N SYY.N FCX.N EVRG.N EXPD.OQ PAYC.N AIV.N CL.N UNH.N ARE.N TIF.N ISRG.OQ PVH.N ADS.N CBRE.N WMB.N TMUS.OQ TXN.OQ PFG.OQ KEYS.N INFO.N RMD.N LNT.OQ NOV.N CMG.N PCAR.OQ CHTR.OQ PWR.N CCL.N PM.N SNA.N ESS.N JBHT.OQ LW.N IR.N WAB.N FBHS.N NOW.N BR.N EMN.N FRT.N DVN.N MKC.N IEX.N DRI.N MAA.N JNJ.N ABC.N ORCL.N AMP.N ALXN.OQ T.N NDAQ.OQ FFIV.OQ SLG.N LEN.N CDNS.OQ MSCI.N IBM.N VRTX.OQ DXCM.OQ AOS.N BLK.N HII.N CVS.N MSFT.OQ HUM.N CMA.N GL.N SLB.N AWK.N PKG.N MMC.N BKR.N ALLE.N CTVA.N CF.N AEP.N FMC.N KLAC.OQ AME.N NUE.N WU.N D.N SJM.N EMR.N DVA.N RHI.N BDX.N MGM.N EFX.N USB.N HCA.N NWSA.OQ AAPL.OQ MRO.N APD.N VAR.N AXP.N CLX.N DOV.N FDX.N JKHY.OQ O.N PKI.N IPG.N PHM.N PBCT.OQ KO.N WLTW.OQ ES.N CTAS.OQ GPN.N RSG.N RF.N PXD.N DG.N EQR.N TDG.N NKE.N AES.N GWW.N GOOGL.OQ GM.N CCI.N COF.N ROP.N CXO.N C.N ODFL.OQ GS.N MET.N WYNN.OQ FAST.OQ LDOS.N ORLY.OQ CSX.OQ CFG.N NI.N DD.N HOLX.OQ STT.N JCI.N PNC.N ROL.N KEY.N NWS.OQ MU.OQ UNP.N BAC.N KMX.N VIAC.OQ APA.N CHD.N EBAY.OQ BIIB.OQ ALB.N UDR.N DGX.N CBOE.Z ETFC.OQ VRSK.OQ AMT.N PYPL.OQ CAG.N TFX.N SYF.N WAT.N IDXX.OQ HES.N EOG.N MNST.OQ FRC.N BMY.N APH.N GPC.N MCHP.OQ FITB.OQ XEL.OQ HSIC.OQ DFS.N APTV.N MPC.N ZION.OQ ICE.N SWKS.OQ EXR.N IPGP.OQ ADBE.OQ WRK.N FOXA.OQ TSN.N TSCO.OQ AON.N MS.N GOOG.OQ STZ.N ABBV.N XOM.N HRL.N INTC.OQ WBA.OQ ATO.N HAL.N TFC.N ATVI.OQ V.N LYV.N RE.N FTNT.OQ DAL.N NTRS.OQ CVX.N LNC.N ACN.N CTXS.OQ DISH.OQ NRG.N MKTX.OQ LMT.N PSX.N FLIR.OQ SCHW.N J.N SIVB.OQ

Informative Powers of Announcements Per Sector

We will now investigate the insight behind our analysis per industry sector.

The 2 cells below allow us to see the movement in daily price of companies with Post-COVID-Announcements per sector

    	
            

ESector, err = ek.get_data(instruments = [i.split()[0] for i in daily_df2_post_COVID_announcement_trend.dropna(axis = "columns", how = "all").columns],

                           fields = ["TR.TRBCEconSectorCode",

                                     "TR.TRBCBusinessSectorCode",

                                     "TR.TRBCIndustryGroupCode",

                                     "TR.TRBCIndustryCode",

                                     "TR.TRBCActivityCode"])

 

ESector["TRBC Economic Sector"] = numpy.nan

ESector_list = [[],[],[],[],[],[],[],[],[],[]]

Sectors_list = ["Energy", "Basic Materials", "Industrials", "Consumer Cyclicals",

                "Consumer Non-Cyclicals", "Financials", "Healthcare",

                "Technology", "Telecommunication Services", "Utilities"]

 

for i in range(len(ESector["TRBC Economic Sector Code"])):

    for j,k in zip(range(0, 10), Sectors_list):

        if ESector.iloc[i,1] == (50 + j):

            ESector.iloc[i,6] = k

            ESector_list[j].append(ESector.iloc[i,0])

 

ESector_df = numpy.transpose(pandas.DataFrame(data = [ESector_list[i] for i in range(len(ESector_list))],

                                              index = Sectors_list))

 

ESector_df_by_Sector = []

for k in Sectors_list:

    ESector_df_by_Sector.append(numpy.average([numpy.average(daily_df2_post_COVID_announcement_trend[i + " Close Price"].dropna()) for i in [j for j in ESector_df[k].dropna()]]))

 

ESector_average = pandas.DataFrame(data = ESector_df_by_Sector,

                                   columns = ["Average of Close Prices Post COVID Announcement"],

                                   index = Sectors_list)

    	
            ESector_average
        
        
    
Average of Close Prices Post COVID Announcement
Energy 0.510261
Basic Materials -0.113836
Industrials 0.557313
Consumer Cyclicals 1.606089
Consumer Non-Cyclicals 0.763417
Financials 1.065371
Healthcare 0.070465
Technology 5.372840
Telecommunication Services 1.167946
Utilities -0.540518

The 'ESector_average' table above shows the Close Prices Post COVID-Announcement for each company averaged per sector

The cells below now allow us to visualise this trend in a graph on an industry sector basis

    	
            

Sector_Average = []

for k in ESector_average.index:

    Sector_Average1 = []

    for j in range(len(pandas.DataFrame([daily_df2_post_COVID_announcement_trend[i + " Close Price"].dropna() for i in ESector_df[k].dropna()]).columns)):

        Sector_Average1.append(numpy.average(pandas.DataFrame([daily_df2_post_COVID_announcement_trend[i + " Close Price"].dropna() for i in ESector_df[k].dropna()]).iloc[:,j].dropna()))

    Sector_Average.append(Sector_Average1)

    	
            Sector_Average = numpy.transpose(pandas.DataFrame(Sector_Average, index = ESector_average.index))
        
        
    

This cell below in particular allows us to collect and save our data before continuing so that we don't have to ask for data from Eikon again were we to manipulate the same content later (just in case)

    	
            

pickle_out = open("SPX2.pickle","wb")

pickl = (COVID_start_date, days_since_COVID,

         index_Announcement_df, index_Announcement_list,

         Announcement_COVID_date,

         Instruments_Income_Statement_Announcement_COVID_date,

         Instruments_Cash_Flow_Statement_Announcement_COVID_date,

         Instruments_Balance_Sheet_COVID_date,

         daily_df, daily_df_no_na,

         daily_df_trend, datasubset_list)

pickle.dump(pickl, pickle_out)

pickle_out.close()

    	
            

plot1ax(dataset = Sector_Average, ylabel = "Price Movement",

        title = "Index Constituents' Trend In Close Prices From There First Income Statement Announcement Since COVID Sorted By Sector\n" + 

                "Only companies that announced an Income Statement since the start of COVID (i.e.:" + str(COVID_start_date) + ") will show",

        xlabel = "Trading Day", legend = "underneath",

        datasubset = [i for i in range(len(Sector_Average.columns))])

Conclusion

Using S&P 500 (i.e.: SPX) data, this last graph can provide a wholesome picture of industries in the United States of America (USA). We can see a great negative change in instruments’ daily close prices for stocks in the Consumer Cyclical, Utilities, Healthcare and Industrial markets. This is actually surprising because they are the industries that were suggested to be most hindered by COVID in the media before their financial statement announcements; investors thus ought to have priced the negative effects of the Disease on these market sectors appropriately.

The graph suggests that it may be profitable to short companies within these sectors just before they are due to release their first post-COVID Financial Statements - but naturally does not account for future changes, trade costs or other such variants external to this investigation. Companies in the Financial sector seem to have performed adequately. Reasons for movements in this sector can be complex and numerous due to their exposure to all other sectors. Tech companies seem to have had the impact of COVID priced in prior to the release of their financial statements. One may postulate the impact of COVID on their share price was actually positive as people rush to online infrastructures they support during confinement. Companies dealing with Basic Material have performed relatively well. This may be an indication that investors are losing confidence in all but sectors that offer physical goods in supply chains (rather than in consumer goods) - a retreat to fundamentals in a time of uncertainty.

BUT one must use both the ESector_average table and the last graph before coming to any conclusion. The ESector_average - though simple - can provide more depth to our analysis. Take the Healthcare sector for example: One may assume – based on the last graph alone – that this sector is performing badly when revealing information via Announcements; but the ESector_average shows a positive ‘Average of Close Prices Post COVID Announcement’. This is because only very few companies within the Healthcare sector published Announcements before May 2020, and the only ones that did performed badly, skewing the data negatively on the graph.

References

You can find more detail regarding the Eikon Data API and related technologies for this notebook from the following resources:

For any question related to this example or Eikon Data API, please use the Developers Community Q&A Forum.

 

Webinar

  • Login
  • Please Login
Contact Us MyRefinitiv