The strategy trades 15 developed countries based on senior population percentages, leveraging demographic trends for growth opportunities, with annual rebalancing and customizable country selection for flexibility and alignment with investor goals.

I. STRATEGY IN A NUTSHELL

Invest in 15 developed countries based on senior population share: go long in countries with below-average elderly populations, short where it exceeds one standard deviation above the mean. Portfolios rebalance annually.

II. ECONOMIC RATIONALE

Market trends are influenced by generational asset supply and demand. Middle-aged investors buy more, driving prices up, while older populations sell to fund retirement, exerting downward pressure. Demographic shifts create predictable effects on equity returns.

III. SOURCE PAPER

Do Demographic Changes Affect Risk Premiums? Evidence from International Data [Click to Open PDF]

Ang, BlackRock, Inc; Maddaloni, European Central Bank (ECB)

<Abstract>

We examine the link between equity risk premiums and demographic changes using a very long sample over the twentieth century for the US, Japan, UK, Germany and France, and a shorter sample covering the last third of the twentieth century for fifteen countries. We find that demographic variables significantly predict excess returns internationally. However, the demographic predictability found in the US by past studies for the average age of the population does not extend to other countries. Pooling international data, we find that, on average, faster growth in the fraction of retired persons significantly decreases risk premiums. This demographic predictability of risk premiums is strongest in countries with well-developed social security systems and lesser-developed financial markets.

IV. BACKTEST PERFORMANCE

Annualised Return8.93%
VolatilityN/A
Beta0.022
Sharpe RatioN/A
Sortino RatioN/A
Maximum DrawdownN/A
Win Rate64%

V. FULL PYTHON CODE

from AlgorithmImports import *
import numpy as np
#endregion
class DemographicChanges(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        self.symbols = {'AU':'EWA', 'AT':'EWO', 'BE':'EWK', 'CA':'EWC', 'FR':'EWQ', 'DE':'EWG', 'IT':'EWI', 'JP':'EWJ', 'NL':'EWN', 'ES':'EWP', 'SE':'EWD', 'CH':'EWL', 'GB':'EWU', 'US':'SPY'}
        
        for symbol in self.symbols:
            data = self.AddEquity(self.symbols[symbol], Resolution.Daily)
            data.SetLeverage(10)
        
        self.demographic_data:Symbol = self.AddData(DemographicData, 'DemographicData').Symbol
        
        self.recent_month:int = -1
    def OnData(self, data:Slice) -> None:
        if self.recent_month == self.Time.month:
            return
        self.recent_month = self.Time.month
        # rebalance once a year
        if self.Time.month != 1: return
        age_data = {}
        if self.demographic_data in data and data[self.demographic_data]:
            for symbol in self.symbols:
                age_data[symbol] = data[self.demographic_data].GetProperty(symbol)
        if len(age_data) == 0: 
            self.Liquidate()
            return
        
        age_data_values = [x[1] for x in age_data.items()]
        age_data_mean = np.mean(age_data_values)
        age_data_std = np.std(age_data_values)
        
        long = []
        short = []
        for symbol in self.symbols:
            if age_data[symbol] < age_data_mean - age_data_std:
                long.append(self.symbols[symbol])
            elif age_data[symbol] > age_data_mean + age_data_std:
                short.append(self.symbols[symbol])
        
        # liquidate
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long + short:
                self.Liquidate(symbol)
        # trade exxecution
        long_count = len(long)
        short_count = len(short)
        
        for symbol in long:
            if symbol in data and data[symbol]:
                self.SetHoldings(symbol, 1 / long_count)
        for symbol in short:
            if symbol in data and data[symbol]:
                self.SetHoldings(symbol, -1 / short_count)
# Demographic data - population 65+ yo
# NOTE: IMPORTANT: Data order must be ascending (date-wise)
from dateutil.relativedelta import relativedelta
class DemographicData(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/population_65_over.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = DemographicData()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        # Data example:
        # year;AU;AT;BE;CA;FR;DE;IT;JP;NL;ES;SE;CH;GB;US
        # 2000;12.4;15.4;16.7;12.5;16.2;16.2;18.1;17.4;13.5;16.5;17.3;15.2;15.8;12.4
        #
        # YEARLY DATA
        
        data.Time = datetime.strptime(split[0], "%Y") + relativedelta(months=12)  # NOTE: Preventing of look ahaead bias. Add 12 months so this year we see last year's data.
        data['AU'] = float(split[1])
        data['AT'] = float(split[2])
        data['BE'] = float(split[3])
        data['CA'] = float(split[4])
        data['FR'] = float(split[5])
        data['DE'] = float(split[6])
        data['IT'] = float(split[7])
        data['JP'] = float(split[8])
        data['NL'] = float(split[9])
        data['ES'] = float(split[10])
        data['SE'] = float(split[11])
        data['CH'] = float(split[12])
        data['GB'] = float(split[13])
        data['US'] = float(split[14])
            
        data.Value = float(split[1])
        return data

Leave a Reply

Discover more from Quant Buffet

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

Continue reading