-
Notifications
You must be signed in to change notification settings - Fork 625
/
market-sentiment-and-an-overnight-anomaly.py
120 lines (96 loc) · 4.26 KB
/
market-sentiment-and-an-overnight-anomaly.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# https://quantpedia.com/strategies/market-sentiment-and-an-overnight-anomaly/
#
# The investment universe consists of SPY ETF, and the price of SPY, price of VIX and Brain Market Sentiment (BMS) indicator
# are used to identify the market sentiment. The investor buys SPY ETF and holds it overnight; when the price of SPY is above its 20-day moving average,
# the price of VIX is below its moving average, and the value of the BMS indicator is greater than its 20-day moving average.
# Note that the authors suggest using this strategy as an overlay when deciding whether to make a trade rather than using this system on its own.
#
# QC Implementation:
# region imports
from AlgorithmImports import *
# endregion
class MarketSentimentAndAnOvernightAnomaly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.period: int = 20 # sma period
self.weight: float = 0
self.price_data: dict = {}
self.spy_symbol: Symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
self.vix_symbol: Symbol = self.AddData(
QuandlVix, "CBOE/VIX", Resolution.Daily
).Symbol # starts in 2004
self.bms_symbol: Symbol = self.AddData(
QuantpediaBMS, "BMS_GLOBAL", Resolution.Daily
).Symbol # starts in 2018
for symbol in [self.spy_symbol, self.vix_symbol, self.bms_symbol]:
self.price_data[symbol] = RollingWindow[float](self.period)
def OnData(self, data: Slice):
# calculate signal from SPY 16 minutes before close
if (
self.spy_symbol in data
and data[self.spy_symbol]
and self.Time.hour == 15
and self.Time.minute == 44
):
weight: float = 0.0
for symbol in [self.spy_symbol, self.vix_symbol, self.bms_symbol]:
# trade only sub-strategies with underlying data available
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
<= 3
):
price: float = self.Securities[symbol].GetLastData().Price
rolling_window: RollingWindow = self.price_data[symbol]
if rolling_window.IsReady and self.GetSignal(
price,
rolling_window,
True if symbol != self.vix_symbol else False,
):
weight += 1 / 3
rolling_window.Add(price)
q: int = int(
(self.Portfolio.TotalPortfolioValue * weight)
/ data[self.spy_symbol].Value
)
if q != 0:
self.MarketOnCloseOrder(self.spy_symbol, q)
self.MarketOnOpenOrder(self.spy_symbol, -q)
def GetSignal(
self, curr_value: float, rolling_window: RollingWindow, signal_above_sma: bool
) -> bool:
prices: list[float] = [x for x in rolling_window]
moving_average: float = sum(prices) / len(prices)
result: bool = False
if signal_above_sma and (curr_value > moving_average):
result = True
elif not signal_above_sma and (curr_value < moving_average):
result = True
return result
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBMS(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/index/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data: QuantpediaBMS = QuantpediaBMS()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split: list = line.split(",")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data.Value = float(split[2])
return data
class QuandlVix(PythonQuandl):
def __init__(self):
self.ValueColumnName = "VIX Close"