投资范围包括在纽约证券交易所(NYSE)交易的所有股票。主要关注的变量是 CEO 薪酬过高的排名。这可以通过使用 “As You Sow” 等服务找到。基于此,当前年度 CEO 薪酬过高的前100家公司可以被筛选出来。这些公司所雇用的 CEO 所在的公司股票将被做空,并在市场指数(如 S&P500)中建立多头头寸作为对冲。所有股票等权重配置,投资组合每年重新平衡。

策略概述

投资范围包括在纽约证券交易所(NYSE)交易的所有股票。主要关注的变量是 CEO 薪酬过高的排名。这可以通过使用 “As You Sow” 等服务找到。基于此,当前年度 CEO 薪酬过高的前100家公司可以被筛选出来。这些公司所雇用的 CEO 所在的公司股票将被做空,并在市场指数(如 S&P500)中建立多头头寸作为对冲。所有股票等权重配置,投资组合每年重新平衡。

策略合理性

该策略的功能性基于实证观察:当基础公司被认为存在治理不善时,股票表现往往不佳,其中之一的表现就是 CEO 薪酬过高。

一个原因可能是,股东会认为这些公司倾向于不善于利用资源,在做决策时不重视公司治理和责任。此外,另一个可能性是,由于员工发现他们的 CEO 薪酬过高,公司内部士气下降,进而导致生产力下降,最终导致公司表现不佳,股票表现不佳。因此,由于这些原因,我们可以验证并有信心执行这一策略,即做空那些在实证和逻辑上必然表现不佳的股票。

论文来源

Evidence from the 100 most overpaid CEOs

<摘要>

我研究了负面 ESG 新闻如何扭曲股东结果。2015 年,As You Sow 开始每年发布“薪酬过高的 100 位 CEO”名单,导致这些公司出现负面的 ESG 新闻。过高支付 CEO 是一个治理问题,因为它表明适当控制的失败;同时也是一个社会问题,因为它会在股东和员工待遇方面为公司带来负面形象。被列入名单的公司构建的等权重(价值权重)投资组合获得的年化六因素 Alpha 分别为 -3.60% 和 -2.64%。对于那些支付 CEO 超过平均工资的公司,表现尤其糟糕。

回测表现

年化收益率5.41%
波动率8.55%
Beta0.047
夏普比率0.63
索提诺比率N/A
最大回撤N/A
胜率24%

完整python代码

from AlgorithmImports import *
from io import StringIO
from pandas.core.frame import DataFrame
from typing import List, Dict
import pandas as pd
# endregion

class ShortingCompaniesWiththeMostOverpaidCEOs(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        self.market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol

        self.leverage:int = 3
        self.selection_month:int = 3

        self.short:List[Symbol] = []

        # source: https://www.asyousow.org/reports/the-100-most-overpaid-ceos-2022
        overpaid_CEO:str = self.Download('data.quantpedia.com/backtesting_data/economic/overpaid_CEO.csv')
        self.overpaid_CEO_df:DataFrame = pd.read_csv(StringIO(overpaid_CEO), delimiter=';')

        self.selection_flag:bool = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.Schedule.On(self.DateRules.MonthStart(self.market), self.TimeRules.AfterMarketOpen(self.market), self.Selection)
        
    def OnSecuritiesChanged(self, changes:SecurityChanges) -> None:
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())
            security.SetLeverage(self.leverage)
    
    def CoarseSelectionFunction(self, coarse:List[CoarseFundamental]) -> List[Symbol]:
        # selection on start of March
        if not self.selection_flag:
            return Universe.Unchanged

        selected:List[Symbol] = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
        
        return selected

    def FineSelectionFunction(self, fine:List[FineFundamental]) -> List[Symbol]:
        fine:List[FineFundamental] = [x for x in fine if x.MarketCap != 0 and \
            (x.SecurityReference.ExchangeId == 'NYS')]

        fine:Dict[str, Symbol] = {x.Symbol.Value: x.Symbol for x in fine}

        if str(self.Time.year) in list(self.overpaid_CEO_df.columns):
            self.short = [fine[x] for x in self.overpaid_CEO_df[str(self.Time.year)].values if x in fine]
        else:
            self.Liquidate()

        return self.short

    def OnData(self, data: Slice) -> None:
        # yearly rebalance
        if not self.selection_flag:
            return
        self.selection_flag = False

        invested:List[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
        for price_symbol in invested:
            if price_symbol not in self.short + [self.market]:
                self.Liquidate(price_symbol)

        # trade execution
        if len(self.short) != 0:
            if self.market in data and data[self.market]:
                self.SetHoldings(self.market, 1)
        
        for symbol in self.short:
            if symbol in data and data[symbol]:
                self.SetHoldings(symbol, -1 / len(self.short))
            
        self.short.clear()

    def Selection(self) -> None:
        if self.Time.month == self.selection_month:
            self.selection_flag = True
    
# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading