“投资宇宙包括138种加密货币。每小时根据前24小时的滞后1小时回报对其排名,做多回报最低的两种,加空回报最高的两种。该策略假设为等权重,并每小时重新平衡。”
资产类别:加密货币 | 区域:全球 | 频率:日内 | 市场:加密货币 | 关键词:加密货币
策略概述
投资宇宙由138种加密货币组成。每小时,投资者根据前24小时的滞后1小时回报对加密货币进行排名,每小时做多回报最低的两种加密货币,并做空回报最高的两种加密货币。该策略假设为等权重,并每小时重新平衡。
策略合理性
此前关于加密货币动量的研究结果不一致,可能是由于样本时间较短以及此类新资产存在的交易工具数量有限。作者提供了一个新的、独特的视角,并创造了“纯动量”这一新术语,他们将其描述为不受基本面影响的对回报变化的纯反应的衡量标准,简单来说就是过去的价格。由于计算回报的参考点不是固定的,而是随着回报窗口的不断移动而持续变化,投资者的感知也随之变化。提出的可能解释包括投资者对风险的反应、他们的偏见和信念,以及对信息的反应不足。
论文来源
Pure Momentum in Cryptocurrency Markets [点击浏览原文]
- Cesare Fracassi,德克萨斯大学奥斯汀分校,Reichman大学 – Arison商学院
- Shimon Kogan,宾夕法尼亚大学 – 沃顿商学院
<摘要>
动量现象是资产定价中最广泛、持久且令人困惑的现象之一。对于动量的普遍解释是,投资者对新信息反应不足,导致资产价格随时间漂移。我们利用了加密货币市场的一个独特特征:它们每天24小时都开放,并报告过去24小时的回报。因此,一天的回报基于滞后信息的消除而出现可预测的波动。我们表明,投资者对与任何新信息发布或资产基本面变化无关的回报变化作出正面反应。我们将这种行为异常称为“纯动量”。


回测表现
| 年化收益率 | 53.8% |
| 波动率 | 15.28% |
| Beta | 0.301 |
| 夏普比率 | 3.52 |
| 索提诺比率 | 0.949 |
| 最大回撤 | N/A |
| 胜率 | 19% |
完整python代码
from AlgorithmImports import *
from data_tools import CustomFeeModel, SymbolData
# endregion
class HourReversalInCryptocurrencies(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1)
self.SetCash(100000)
self.cryptos:list[str] = [
#"ANTUSD", # Aragon
#"BATUSD", # Basic Attention Token
"BTCUSD", # Bitcoin
#"DAIUSD", # Dai
#"DGBUSD", # Dogecoin
#"EOSUSD", # EOS
#"ETCUSD", # Ethereum Classic
"ETHUSD", # Ethereum
#"FUNUSD", # FUN Token
"LTCUSD", # Litecoin
#"MKRUSD", # Maker
#"NEOUSD", # Neo
#"OMGUSD", # OMG Network
#"SNTUSD", # Status
#"TRXUSD", # Tron
#"XLMUSD", # Stellar
#"XMRUSD", # Monero
"XRPUSD", # XRP
#"XTZUSD", # Tezos
#"XVGUSD", # Verge
#"ZECUSD", # Zcash
#"ZRXUSD" # Ox
]
self.data:dict[Symbol, SymbolData] = {}
self.lag_period:int = 24 + 1
self.max_missing_hours:int = 0
self.leg_count:int = 2
self.leverage:int = 5
self.portfolio_percentage:float = 0.1
self.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto in self.cryptos:
# GDAX is coinmarket, but it doesn't support this many cryptos, so we choose Bitfinex
data = self.AddCrypto(crypto, Resolution.Hour, Market.Bitfinex)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(self.leverage)
self.data[data.Symbol] = SymbolData(self.lag_period)
def OnData(self, data):
curr_time:datetime = self.Time
performance:dict[Symbol, float] = {}
for symbol, symbol_data in self.data.items():
if symbol in data and data[symbol]:
price:float = data.Bars[symbol].Value
if not symbol_data.data_still_coming(curr_time, self.max_missing_hours):
symbol_data.reset_data()
if symbol_data.prev_price_ready():
symbol_data.calculate_perf(price)
if symbol_data.performances_ready():
performance[symbol] = symbol_data.get_first_perf()
symbol_data.update_price(curr_time, price)
if len(performance) < (2 * self.leg_count):
self.Liquidate()
return
sorted_by_perf:list[Symbol] = [x[0] for x in sorted(performance.items(), key=lambda item: item[1])]
long_leg:list[Symbol] = sorted_by_perf[:self.leg_count]
short_leg:list[Symbol] = sorted_by_perf[-self.leg_count:]
# trade execution
invested:list[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long_leg + short_leg:
self.Liquidate(symbol)
for symbol in long_leg:
q:float = self.CalculateOrderQuantity(symbol, (1 / self.leg_count) * self.portfolio_percentage)
if q > self.Securities[symbol].SymbolProperties.MinimumOrderSize:
self.MarketOrder(symbol, q)
for symbol in short_leg:
q:float = self.CalculateOrderQuantity(symbol, (1 / self.leg_count) * self.portfolio_percentage)
if q > self.Securities[symbol].SymbolProperties.MinimumOrderSize:
self.MarketOrder(symbol, -q)
