Quant BuffetRelax, Not Over Thinking

Benchmarks Portfolios with Decreasing Carbon Footprints

Log in to collect

Academic paper

Building Benchmarks Portfolios with Decreasing Carbon Footprints

AuthorsÉric Jondeau; Benoı̂t Mojon; Luiz A. Pereira da Silva

Institute
  • CHUniversity of Lausanne
  • CHSwiss Finance Institute
  • ?University of Lausanne - Faculty of Business and Economics (HEC Lausanne)
  • CHBank for International Settlements
  • ?Bank for International Settlements (BIS)

Strategy in a nutshell

The strategy selects ACWI stocks with low carbon intensity, calculated from Scope 1–3 emissions per revenue. It keeps a target percentage of total market cap, excludes the highest polluters, adjusts weights proportionally, and rebalances annually.

Economic rationale

A few firms account for extreme emissions. Removing these outliers greatly improves the portfolio’s carbon profile while barely affecting performance, allowing environmental impact reduction without sacrificing returns.

Backtest performance

Annualised return8.2%
Volatility16.5%
Beta0.924
Sharpe ratio0.5
Sortino ratio0.545
Win rate90%

Full Python code

from AlgorithmImports import *
from typing import List, Dict
# endregion

class BenchmarksPortfolioswithDecreasingCarbonFootprints(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100_000)

self.leverage: int = 3
self.quantile: int = 3
self.portfolio_threshold: float = 0.75

self.data: Dict[int, Dict[str, float]] = {}

# DJI stocks
self.tickers: List[str] = [
    'AXP', 'AMGN', 'AAPL', 'BA', 'CAT', 'CSCO', 'CVX', 'GS', 'HD', 'HON', 
    'IBM', 'INTC', 'JNJ', 'KO', 'JPM', 'MCD', 'MMM', 'MRK', 'MSFT', 'NKE', 
    'PG', 'TRV', 'UNH', 'CRM', 'VZ', 'V', 'WBA', 'WMT', 'DIS', 'DOW'
]

for ticker in self.tickers:
    data: Equity = self.AddEquity(ticker, Resolution.Daily)
    data.SetLeverage(self.leverage)

# Source: https://esg.exerica.com/Company?Name=Apple
ghg_emissions: str = self.Download('data.quantpedia.com/backtesting_data/economic/GHG_emissions.csv')
lines: List[str] = ghg_emissions.split('\r\n')

for line in lines[1:]:  # skip first comment lines
    if line == '':
        continue
    
    line_split: List[str] = line.split(';')
    year: str = line_split[0]
    if year not in self.data:
        self.data[year] = {}
 
    # N stocks -> n*4 properties
    for i in range(1, len(line_split), 4):
        # parse GHG emissions info
        if line_split[i] not in self.data[year]:
            self.data[year][line_split[i]] = sum( float(line_split[x]) for x in range(i+1, i+4) if line_split[x] != '' )

self.rebalance_month: int = 7
self.current_month: int = -1

def OnData(self, slice: Slice) -> None:
if self.Time.month == self.current_month:
    return
self.current_month = self.Time.month

if self.Time.month != self.rebalance_month:
    return

if str(self.Time.year - 1) not in list(self.data.keys()):
    return

# calculate emission intensity
emission_intensity: Dict[Symbol, float] = { 
    self.Symbol(ticker) : self.data[str(self.Time.year - 1)][ticker] / self.Securities[ticker].Fundamentals.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths \
    for ticker in self.tickers if self.data[str(self.Time.year - 1)][ticker] != 0 and self.Securities[ticker].Fundamentals.HasFundamentalData\
    and not np.isnan(self.Securities[ticker].Fundamentals.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths) \
    and self.Securities[ticker].Fundamentals.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths != 0 
}

weight: Dict[Symbol, float] = {}
final_portfolio: List[Symbol] = []
portfolio_percentage: float = 0.

# sort and divide portfolio
if len(emission_intensity) != 0:
    sorted_emissions:List[Symbol] = sorted(emission_intensity, key=emission_intensity.get)

    # calculate weights based on marketcap
    mc_sum:float = sum(list(map(lambda symbol: self.Securities[symbol].Fundamentals.MarketCap, sorted_emissions)))
    for symbol in sorted_emissions:
        w: float = self.Securities[symbol].Fundamentals.MarketCap / mc_sum

        portfolio_percentage += w
        if portfolio_percentage <= self.portfolio_threshold:
            final_portfolio.append(symbol)

if len(final_portfolio) != 0:
    mc_sum: float = sum(list(map(lambda symbol: self.Securities[symbol].Fundamentals.MarketCap, final_portfolio)))
    for symbol in final_portfolio:
        weight[symbol] = self.Securities[symbol].Fundamentals.MarketCap / mc_sum

# trade execution
portfolio:List[PortfolioTarget] = [PortfolioTarget(symbol, w) for symbol, w in weight.items() if symbol in slice and slice[symbol]]
self.SetHoldings(portfolio, True)