“该策略投资于CoinMarketCap上列出的加密货币,排除市值低于100万美元和交易历史少于60天的资产。利用Caldara和Iacoviello的地缘政治风险(GPR)指数,通过21天滚动时间序列回归计算地缘政治贝塔。然后,将加密货币按贝塔值分组,做多地缘政治贝塔最低的五分位组,做空贝塔最高的五分位组,每周重新平衡投资组合。”
资产类别:加密货币 | 地区:全球 | 频率:每周 | 市场:加密货币 | 关键词:地缘政治风险,加密货币
策略概述
投资范围包括所有在CoinMarketCap上列出的加密货币,但市值低于100万美元和交易历史少于60天的资产被排除在外。为了衡量地缘政治风险,使用Caldara和Iacoviello(2022)提出的地缘政治风险(GPR)指数,该指数基于主要报纸中与地缘政治事件相关的文章频率计算得出。
地缘政治贝塔通过滚动时间序列回归计算,回归模型的自变量为每日超额回报、GPR每日变化,控制变量包括市场超额回报、规模和动量因子。具体方程见论文第4页。估算期为21天,但对调整具有鲁棒性。然后根据地缘政治贝塔将加密货币按价值加权分为五个五分位组,做多地缘政治贝塔最低的五分位组,做空地缘政治贝塔最高的五分位组,并每周重新平衡投资组合。
策略合理性
Caldara和Iacoviello创建的GPR指数是全球地缘政治风险的衡量指标。通过基于该指数近似计算每种加密货币的地缘政治贝塔,可以衡量其对地缘政治事件的敏感性。研究结果支持这样的假设:投资者愿意为低地缘政治贝塔的资产支付溢价。因此,价格与地缘政治贝塔呈负相关,这也是该策略的基本理念。
论文来源
Is Geopolitical Risk Priced in the Cross-Section of Cryptocurrency Returns? [点击浏览原文]
- 华刚龙、Ender Demir、Barbara Bedowska-Sojka、Adam Zaremba、Syed Jawad Hussain Shahzad,浙江大学;浙江财经大学,伊斯坦布尔文明大学,波兹南经济与商业大学,蒙彼利埃商学院;波兹南经济与商业大学;开普敦大学,蒙彼利埃商学院
<摘要>
我们研究了地缘政治风险在加密货币定价横截面中的作用。我们计算了加密货币对地缘政治风险指数变化的敞口,并记录了地缘政治贝塔最低的加密货币相对于高地缘政治贝塔的加密货币表现更好。我们的研究结果表明,风险厌恶型投资者需要额外的补偿来激励其持有具有低或负地缘政治贝塔的加密货币,而他们愿意为具有高或正地缘政治贝塔的资产支付溢价。


回测表现
| 年化收益率 | 187.05% |
| 波动率 | 39.8% |
| Beta | -0.001 |
| 夏普比率 | 4.7 |
| 索提诺比率 | 0.425 |
| 最大回撤 | N/A |
| 胜率 | 51% |
完整python代码
from AlgorithmImports import *
import data_tools
from typing import List, Dict
# endregion
class CrawlingYellowBarracuda(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2015, 1, 1)
self.SetCash(1000000)
self.period: int = 21 + 1 # need n of daily data
self.quantile: int = 5
self.leverage: int = 5
self.portfolio_percentage: float = .5
cryptos: Dict[str, str] = {
"ANTUSD": "ANT", # Aragon
"BATUSD": "BAT", # Basic Attention Token
"BTCUSD": "BTC", # Bitcoin
"BTGUSD": "BTG", # Bitcoin Gold
"DAIUSD": "DAI", # Dai
"DGBUSD": "DGB", # Dogecoin
"EOSUSD": "EOS", # EOS
"ETCUSD": "ETC", # Ethereum Classic
"ETHUSD": "ETH", # Ethereum
"FUNUSD": "FUN", # FUNToken
"LTCUSD": "LTC", # Litecoin
"MKRUSD": "MKR", # Maker
"NEOUSD": "NEO", # Neo
"OMGUSD": "OMG", # OMG Network
"SNTUSD": "SNT", # Status
"TRXUSD": "TRX", # Tron
"XLMUSD": "XLM", # Stellar
"XMRUSD": "XMR", # Monero
"XRPUSD": "XRP", # XRP
"XTZUSD": "XTZ", # Tezos
"XVGUSD": "XVG", # Verge
"ZECUSD": "ZEC", # Zcash
"ZRXUSD": "ZRX", # Ox
}
self.data: Dict[str, data_tools.SymbolData] = {}
self.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto, ticker in cryptos.items():
# GDAX is coinmarket, but it doesn't support this many cryptos, so we choose Bitfinex
data: Securities = self.AddCrypto(crypto, Resolution.Daily, Market.Bitfinex)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(self.leverage)
network_symbol: Symbol = self.AddData(data_tools.CryptoNetworkData, ticker, Resolution.Daily).Symbol
self.data[crypto] = data_tools.SymbolData(network_symbol, self.period)
self.geo_risk_index: Symbol = self.AddData(data_tools.QuantpediaGeopoliticalRisk, 'GeopoliticalRiskIndex', Resolution.Daily).Symbol
self.geo_risk_index_values: RollingWindow = RollingWindow[float](self.period)
self.geo_risk_beta_value_index: int = 1
self.value_weighted: bool = True
self.selection_flag: bool = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.WeekStart('BTCUSD'), self.TimeRules.At(9, 30), self.Selection)
def OnData(self, data: Slice) -> None:
curr_date: datetime.date = self.Time.date()
crypto_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.CryptoNetworkData.get_last_update_date()
qp_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.QuantpediaGeopoliticalRisk.get_last_update_date()
# daily updating of crypto prices and market capitalization(CapMrktCurUSD)
if self.Securities[self.geo_risk_index].GetLastData() and self.Time.date() <= qp_data_last_update_date[self.geo_risk_index]:
for crypto, symbol_obj in self.data.items():
# if self.geo_risk_index in data and data[self.geo_risk_index]:
network_symbol:Symbol = symbol_obj.network_symbol
if data.ContainsKey(crypto):
price: float = data[crypto].Value
self.data[crypto].update_prices(price)
# GPR_value:float = data[self.geo_risk_index].Value
GPR_value:float = self.Securities[self.geo_risk_index].Price
self.geo_risk_index_values.Add(GPR_value)
if data.ContainsKey(network_symbol):
cap_mrkt_cur_usd: float = data[network_symbol].Value
self.data[crypto].update_cap(cap_mrkt_cur_usd)
else:
self.geo_risk_index_values.Reset()
# weekly rebalance
if not self.selection_flag:
return
self.selection_flag = False
if not self.geo_risk_index_values.IsReady:
self.Liquidate()
return
# calculate monthly performance series
monthly_returns_by_symbol: Dict[str, float] = {}
for crypto, symbol_obj in self.data.items():
network_symbol: Symbol = symbol_obj.network_symbol
if self.Securities[network_symbol].GetLastData() and self.Time.date() > crypto_data_last_update_date[network_symbol]:
self.Liquidate()
return
if not symbol_obj.is_ready():
continue
monthly_returns_by_symbol[crypto] = symbol_obj.get_daily_returns()
if len(monthly_returns_by_symbol) < self.quantile:
self.Liquidate()
return
# create market factor
crypto_c: int = len(monthly_returns_by_symbol)
weights: np.ndarray = np.array([1/crypto_c] * crypto_c)
market_factor: np.ndarray = np.matmul(np.array(list(monthly_returns_by_symbol.values())).T, weights)
# create GPR factor
GPR_index_values: np.ndarray = np.array(list(self.geo_risk_index_values))
GPR_factor: np.ndarray = (GPR_index_values[:-1] - GPR_index_values[1:]) / GPR_index_values[1:]
regression_x: List[np.ndarray] = [
GPR_factor,
market_factor
]
beta_by_ticker: Dict[str, float] = {}
for crypto, monthly_return in monthly_returns_by_symbol.items():
regression_y: np.ndarray = monthly_return
regression_model: RegressionResultsWrapper = data_tools.MultipleLinearRegression(regression_x, regression_y)
geo_risk_beat: float = regression_model.params[self.geo_risk_beta_value_index]
beta_by_ticker[crypto] = geo_risk_beat
if len(beta_by_ticker) < self.quantile:
self.Liquidate()
return
# long and short selection
quantile: int = int(len(beta_by_ticker) / self.quantile)
sorted_by_beta: List[list[str, float]] = [x[0] for x in sorted(beta_by_ticker.items(), key=lambda item: item[1])]
long_leg: List[List[str, float]] = sorted_by_beta[:quantile]
short_leg: List[List[str, float]] = sorted_by_beta[-quantile:]
# weights calculation
weights: Dict[str, float] = {}
if self.value_weighted:
for i, portfolio in enumerate([long_leg, short_leg]):
mc_sum: float = sum(list(map(lambda ticker: self.data[ticker].cap_mrkt_cur_usd, portfolio)))
for ticker in portfolio:
weights[ticker] = ((-1)**i) * self.data[ticker].cap_mrkt_cur_usd / mc_sum
else:
for i, portfolio in enumerate([long_leg, short_leg]):
for ticker in portfolio:
weights[ticker] = ((-1) ** i) / len(portfolio)
# trade execution
portfolio: List[PortfolioTarget] = [PortfolioTarget(ticker, self.portfolio_percentage * w) for ticker, w in weights.items() if ticker in data and data[ticker]]
self.SetHoldings(portfolio, True)
def Selection(self) -> None:
self.selection_flag = True
