
“该策略根据比特币的过度反应日进行交易,在正向过度反应时买入,在负向过度反应时卖出,持仓在18:00或16:00开仓,并在00:00平仓。”
资产类别: 加密货币 | 地区: 全球 | 周期: 日内 | 市场: 加密货币 | 关键词: 比特币、动量
I. 策略概要
该策略投资于比特币加密货币,在正向过度反应日买入,在负向过度反应日卖出。过度反应日定义为加密货币回报超过平均回报加上两个标准差的日子。头寸在18:00(正向过度反应)或16:00(负向过度反应)开仓,并在00:00平仓。该策略利用显著的价格变动,旨在从市场过度反应中获利。
II. 策略合理性
过度反应日定义为资产回报超过平均回报加上两个标准差的日子。买入和卖出信号基于累积异常回报,正向过度反应日可在18:00前检测到,负向过度反应日可在16:00前检测到。这些过度反应产生的动量持续到当天结束。行为偏差,例如投资者羊群效应、过度反应、反应不足和确认偏差,是动量异常持续存在的常见解释。研究还表明,在散户交易比例较高的不发达市场中,动量效应更强,这使得这些市场更容易受到动量驱动的价格波动的影响。
III. 来源论文
Momentum Effects in the Cryptocurrency Market after One-Day Abnormal Returns [点击查看论文]
- 卡波拉莱(Caporale)、普拉斯顿(Plastun),布鲁内尔大学伦敦分校经济与金融系;伦敦南岸大学;CESifo(经济研究中心与IFO研究所);德国经济研究所(DIW Berlin);苏梅国立大学
<摘要>
本文研究了加密货币市场中单日异常回报后是否存在动量效应。为此,针对2017年1月1日至2019年9月1日期间比特币、以太坊和莱特币兑美元的汇率,检验了若干感兴趣的假设,具体包括:H1)过度反应日的小时回报的日内行为与正常日相比是否不同;H2)过度反应日是否存在动量效应;H3)单日异常回报后是否存在动量效应。分析方法包括多种统计方法以及交易模拟方法。结果表明,正/负过度反应日的小时回报显著高于/低于平均正/负日的小时回报。过度反应通常可以在一天结束前通过估计特定的时间参数来检测。价格在过度反应发生当天倾向于朝着过度反应的方向移动,直到当天结束,这意味着当天存在动量效应,从而产生可利用的盈利机会。这种效应(以及盈利机会)在第二天也观察到。在两种情况下(BTCUSD正向过度反应和ETHUSD负向过度反应),反而检测到反向效应。


IV. 回测表现
| 年化回报 | 28.62% |
| 波动率 | 30.26% |
| β值 | -0.052 |
| 夏普比率 | 0.95 |
| 索提诺比率 | 0.051 |
| 最大回撤 | N/A |
| 胜率 | 52% |
V. 完整的 Python 代码
from AlgorithmImports import *
from pandas.core.frame import dataframe
from math import floor
#endregion
class BitcoinIntradayMomentum(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash('USD', 100000)
self.closes:List[float] = []
self.period:int = 12 * 21
self.SetWarmup(self.period, Resolution.Daily)
self.percentage_traded:float = .9
self.std_threshold:float = 2.
self.signal_hours:List[int] = [16, 18]
self.symbol:Symbol = self.AddCrypto('BTCUSD', Resolution.Minute, Market.Bitfinex).Symbol
def OnData(self, data: Slice) -> None:
if not (self.symbol in data and data[self.symbol]):
return
current_price:float = data[self.symbol].Value
if self.Time.hour == 23 and self.Time.minute == 59:
# store daily price
self.closes.append(current_price)
if self.Portfolio[self.symbol].Invested:
self.Liquidate(self.symbol)
if self.IsWarmingUp: return
if len(self.closes) < self.period: return
if (self.Time.hour in self.signal_hours and self.Time.minute == 0):
# daily return return calculation
last_close:float = self.closes[-1]
performance:float = current_price / last_close - 1
# daily return average
closes:np.ndarray = np.array(self.closes)
daily_returns:np.ndarray = closes[1:] / closes[:-1] - 1
ret_mean:float = np.mean(daily_returns)
ret_std:float = np.std(daily_returns)
q:float = floor(self.Portfolio.TotalPortfolioValue * self.percentage_traded / current_price)
if q >= self.Securities[self.symbol].SymbolProperties.MinimumOrderSize:
# overreaction handling
if self.Time.hour == self.signal_hours[0] and self.Time.minute == 0:
if not self.Portfolio[self.symbol].Invested:
if performance < ret_mean - self.std_threshold * ret_std:
self.MarketOrder(self.symbol, -q)
elif self.Time.hour == self.signal_hours[1] and self.Time.minute == 0:
if not self.Portfolio[self.symbol].Invested:
if performance > ret_mean + self.std_threshold * ret_std:
self.MarketOrder(self.symbol, q)