Quant Buffet放轻松,别过度思虑

人口变化预测股市回报策略

登录后收藏

学术论文

Do Demographic Changes Affect Risk Premiums? Evidence from International Data

作者作者: Ang

机构
  • ?Maddaloni, BlackRock, Inc, 欧洲央行(ECB)
论文摘要

我们研究了股票风险溢价与人口变化之间的关系,使用了美国、日本、英国、德国和法国涵盖整个20世纪的长期样本,以及覆盖20世纪后三分之一时间的15个国家的短期样本。研究发现,人口变量能够显著预测国际市场的超额回报。然而,之前研究中发现的美国人口平均年龄对风险溢价的预测能力并未扩展到其他国家。通过汇总国际数据,我们发现退休人口比例的快速增长平均显著降低了风险溢价。这种风险溢价的可预测性在社会保障体系完善但金融市场较不发达的国家中最为强烈。

策略概要

该策略通过分析15个发达国家65岁及以上老年人口的比例进行交易。对老年人口比例低于平均值一个标准差的国家建立多头头寸,对高于平均值一个标准差的国家建立空头头寸。通过利用人口趋势,策略旨在发现增长和投资机会。投资组合每年根据更新的人口数据进行重新平衡,同时允许根据投资者的目标灵活选择或排除特定国家,以实现定制化的投资配置,同时保持对人口与经济研究的基础依托。

策略合理性

这一现象源于不同世代在资产供需上的差异,这由活跃的市场参与者推动。中年人通常是主要的资产投资者,而老年人则出售资产以支持退休资金需求。当中年人口占主导时,对资产的需求会推动市场上涨;而当老年人口增长时,供给的增加会对市场形成下行压力。这些人口结构的变化决定了市场的动态:较大的中年群体推动需求,而较大的老年群体增加供给。由此产生的股市波动模式是可预测的,反映了经济中代际构成和生命周期投资行为。

回测表现

年化收益8.93%
贝塔0.022
胜率64%

完整 Python 代码

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