Overall Statistics |
Total Trades
14305
Average Win
0.02%
Average Loss
-0.01%
Compounding Annual Return
9.815%
Drawdown
21.900%
Expectancy
2.153
Net Profit
250.993%
Sharpe Ratio
0.746
Probabilistic Sharpe Ratio
11.282%
Loss Rate
16%
Win Rate
84%
Profit-Loss Ratio
2.76
Alpha
0.008
Beta
0.635
Annual Standard Deviation
0.096
Annual Variance
0.009
Information Ratio
-0.475
Tracking Error
0.06
Treynor Ratio
0.113
Total Fees
$168.91
Estimated Strategy Capacity
$800000000.00
Lowest Capacity Asset
WY R735QTJ8XC9X
|
# https://quantpedia.com/strategies/esg-factor-investing-strategy/ # # As we have previously mentioned, the choice of the database of ESG scores can alter results. This paper uses for the assessments of # environment, social, and governance performance of single firms database provided by Asset4. Scores are updated every year, therefore # to obtain monthly ESG data, the scores remain unchanged until the next assessment. # The investment universe consists of stocks of the North America region (Canada and the United States) that have ESG scores available. # Stocks with a price of less than one USD are excluded. Paper examines the returns as abnormal returns according to the methodology of # Daniel et al. (1997). Such methodology controls for risk factors such as size, book-to-market ratio, and momentum. The idea is to match # a stock along with the mentioned factors to a benchmark portfolio that contains stocks with similar characteristics. Therefore, for the # North America region, we have 4×4 benchmark portfolios. The abnormal return is calculated as the return of stock minus the return of # stock´s matching benchmark portfolio return (equation 1, page 13). # Finally, each month stocks are ranked according to their E, S and G scores. Long top 20% stocks of each score and short the bottom 20% # stocks of each score. Therefore, we have one complex strategy that consists of three individual strategies (for representative purposes, # the paper examines each strategy individually). The strategy is equally-weighted: both stocks in the quintiles and individual strategies. # The strategy is rebalanced yearly. # # QC implementation: # - Universe consists of ~700 stocks with ESG score data. #region imports from AlgorithmImports import * from numpy import floor #endregion class ESGFactorInvestingStrategy(QCAlgorithm): def Initialize(self): self.SetStartDate(2009, 6, 1) self.SetCash(100000) # Decile weighting. # True - Value weighted # False - Equally weighted self.value_weighting = True self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.esg_data = self.AddData(ESGData, 'ESG', Resolution.Daily) # All tickers from ESG database. self.tickers = [] self.ticker_deciles = {} self.holding_period = 12 self.managed_queue = [] self.latest_price = {} self.selection_flag = False self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(10) def CoarseSelectionFunction(self, coarse): if not self.selection_flag: return Universe.Unchanged self.latest_price.clear() selected = [x for x in coarse if (x.Symbol.Value).lower() in self.tickers] for stock in selected: symbol = stock.Symbol self.latest_price[symbol] = stock.AdjustedPrice return [x.Symbol for x in selected] def FineSelectionFunction(self, fine): fine = [x for x in fine if x.MarketCap != 0] # Store symbol/market cap pair. long = [x for x in fine if (x.Symbol.Value in self.ticker_deciles) and \ (self.ticker_deciles[x.Symbol.Value] is not None) and \ (self.ticker_deciles[x.Symbol.Value] >= 0.8)] short = [x for x in fine if (x.Symbol.Value in self.ticker_deciles) and \ (self.ticker_deciles[x.Symbol.Value] is not None) and \ (self.ticker_deciles[x.Symbol.Value] <= 0.2)] long_symbol_q = [] short_symbol_q = [] # ew if not self.value_weighting: if len(long) != 0: long_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(long) long_symbol_q = [(x.Symbol, floor(long_w / self.latest_price[x.Symbol])) for x in long] if len(short) != 0: short_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(short) short_symbol_q = [(x.Symbol, -floor(short_w / self.latest_price[x.Symbol])) for x in short] # vw else: if len(long) != 0: total_market_cap_long = sum([x.MarketCap for x in long]) long_w = self.Portfolio.TotalPortfolioValue / self.holding_period long_symbol_q = [(x.Symbol, floor((long_w * (x.MarketCap / total_market_cap_long))) / self.latest_price[x.Symbol]) for x in long] short_symbol_q = [] if len(short) != 0: total_market_cap_short = sum([x.MarketCap for x in short]) short_w = self.Portfolio.TotalPortfolioValue / self.holding_period short_symbol_q = [(x.Symbol, -floor((short_w * (x.MarketCap / total_market_cap_short))) / self.latest_price[x.Symbol]) for x in short] self.managed_queue.append(RebalanceQueueItem(long_symbol_q + short_symbol_q)) self.ticker_deciles.clear() return [x.Symbol for x in long + short] def OnData(self, data): new_data_arrived = False if 'ESG' in data and data['ESG']: # Store universe tickers. if len(self.tickers) == 0: # TODO '_typename' in storage dictionary? self.tickers = [x.Key for x in self.esg_data.GetLastData().GetStorageDictionary()][:-1] # Store history for every ticker. for ticker in self.tickers: ticker_u = ticker.upper() if ticker_u not in self.ticker_deciles: self.ticker_deciles[ticker_u] = None decile = self.esg_data.GetLastData()[ticker] self.ticker_deciles[ticker_u] = decile # trigger selection after new esg data arrived. if not self.selection_flag: new_data_arrived = True if new_data_arrived: self.selection_flag = True return if not self.selection_flag: return self.selection_flag = False # Trade execution remove_item = None # Rebalance portfolio for item in self.managed_queue: if item.holding_period == self.holding_period: for symbol, quantity in item.symbol_q: self.MarketOrder(symbol, -quantity) remove_item = item elif item.holding_period == 0: open_symbol_q = [] for symbol, quantity in item.symbol_q: if symbol in data and data[symbol]: if quantity >= 1: self.MarketOrder(symbol, quantity) open_symbol_q.append((symbol, quantity)) # Only opened orders will be closed item.symbol_q = open_symbol_q item.holding_period += 1 if remove_item: self.managed_queue.remove(remove_item) class RebalanceQueueItem(): def __init__(self, symbol_q): # symbol/quantity collections self.symbol_q = symbol_q self.holding_period = 0 # ESG data. class ESGData(PythonData): def __init__(self): self.tickers = [] def GetSource(self, config, date, isLiveMode): return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/esg_deciles_data.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config, line, date, isLiveMode): data = ESGData() data.Symbol = config.Symbol if not line[0].isdigit(): self.tickers = [x for x in line.split(';')][1:] return None split = line.split(';') data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1) index = 1 for ticker in self.tickers: data[ticker] = float(split[index]) index += 1 data.Value = float(split[1]) return data # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))