├── .gitignore
├── Base Buyer
├── main.py
└── research.ipynb
├── Breakout
├── indicators.py
├── main.py
└── research.ipynb
├── CryptoMomentum
├── main.py
└── research.ipynb
├── LongShortMeanReversion
├── main.py
└── research.ipynb
├── MABreakthroughETF
├── main.py
└── research.ipynb
├── MarketOnMarketOff
├── main.py
└── research.ipynb
├── MasterAlgo
├── base.py
├── constants.py
├── main.py
├── research.ipynb
└── turtle_trading.py
├── Mean Reversion Long
├── main.py
└── research.ipynb
├── Mean Reversion Short
├── main.py
└── research.ipynb
├── MeanReversionBBLong
├── main.py
└── research.ipynb
├── MeanReversionLongETF
├── main.py
└── research.ipynb
├── MeanReversionMaLong
├── main.py
└── research.ipynb
├── MomentumETF
├── main.py
└── research.ipynb
├── MonthlySectorRotation
├── main.py
└── research.ipynb
├── MultiNonCorrelatedAlphaStrategy
├── main.py
└── research.ipynb
├── MultiStrategyETF
├── main.py
└── research.ipynb
├── New High Breakout
├── main.py
└── research.ipynb
├── NewHighBreakoutIBD50
├── main.py
└── research.ipynb
├── Powertrend
├── main.py
└── research.ipynb
├── Rate Of Change Rotation
├── main.py
└── research.ipynb
├── RateOfChangeRotationETF
├── main.py
└── research.ipynb
├── TrendFollowingMonthly
├── main.py
└── research.ipynb
├── TurleTrading
├── main.py
└── research.ipynb
├── master algo framework idea.excalidraw
├── readme.md
├── scripts
├── __init__.py
└── analyze_orders.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /venv
2 | /data
3 | lean.json
4 | */backtests
5 | */.idea
6 | *.idea
7 | */.vscode
8 | *.pyc
9 | /backtest_reports
10 | *.csv
11 | */config.json
--------------------------------------------------------------------------------
/Base Buyer/main.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import *
2 | import datetime
3 |
4 | class BaseBuyer(QCAlgorithm):
5 |
6 | def Initialize(self):
7 | self.SetStartDate(2019, 1, 1)
8 | self.SetEndDate(2020, 1, 1)
9 | self.stocks_map = {}
10 | self.stocks_file_link = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vS_oVGJKqa6xhMcNKG3k5TkK_uXX_GSYvK6GZBqagd8hj1xqk0ONdavJrkl4KWYsomtFFMddD6hO2b5/pubhtml?gid=0&single=true'
11 | self.backtest_stocks_file_link = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vSujpOFMXOFM9pnjLa8-3kHJqYRSnCeh5ZWkR08HRFi5Xcf018-LQYCG6Pf_OwZFQ-rtTPtcP1Zu2sM/pub?gid=0&single=true&output=csv'
12 | self.EQUITY_RISK_PC = 0.01
13 | self.UniverseSettings.Resolution = Resolution.Hour
14 | # Order margin value has to have a minimum of 0.5% of Portfolio value, allows filtering out small trades and reduce fees.
15 | self.Settings.MinimumOrderMarginPortfolioPercentage = 0.005
16 | self.AddUniverse("my-dropbox-universe", self.universe_selector)
17 | self.csv_str = None
18 |
19 | def get_csv_str(self):
20 | if self.LiveMode:
21 | return self.Download(self.stocks_file_link)
22 | if self.csv_str:
23 | return self.csv_str
24 | self.csv_str = self.Download(self.backtest_stocks_file_link)
25 | return self.csv_str
26 |
27 | def universe_selector(self, date):
28 | csv_str = self.get_csv_str()
29 | for index, line in enumerate(csv_str.splitlines()):
30 | row = line.split(',')
31 | if index == 0 or len(row) < 3:
32 | continue
33 | try:
34 | symbol = row[0].strip()
35 | pivot = float(row[1])
36 | stop = float(row[2])
37 | if symbol and pivot and stop:
38 | self.stocks_map[symbol] = {
39 | 'pivot': pivot,
40 | 'stop': stop
41 | }
42 | except:
43 | continue
44 | return list(self.stocks_map.keys())
45 |
46 | def OnData(self, slice):
47 | if slice.Bars.Count == 0:
48 | return
49 | for symbol in self.ActiveSecurities.Keys:
50 | if symbol.Value not in self.stocks_map:
51 | continue
52 |
53 | # initialize volume indicator
54 | symbol_history = None
55 | if 'vol_ma' not in self.stocks_map[symbol.Value]:
56 | vol_ma = SimpleMovingAverage(300)
57 | for data in self.History(symbol, 300, Resolution.Hour).itertuples():
58 | vol_ma.Update(data.Index[1], data.volume)
59 | self.stocks_map[symbol.Value]['vol_ma'] = vol_ma
60 | self.stocks_map[symbol.Value]['vol_ma'].Update(self.Time, slice.Bars[symbol].Volume)
61 |
62 | # buy / sell logic
63 | if self.ActiveSecurities[symbol].Invested:
64 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.20:
65 | self.Liquidate(symbol)
66 | else:
67 | pivot = self.stocks_map[symbol.Value]['pivot']
68 | stock_in_buy_range = self.ActiveSecurities[symbol].Close > pivot < pivot * 1.05
69 | if not stock_in_buy_range:
70 | continue
71 | if not slice.Bars[symbol].Close > slice.Bars[symbol].Open:
72 | continue
73 | vol = self.stocks_map[symbol.Value]['vol_ma']
74 | if vol.IsReady and slice.Bars[symbol].Volume > vol.Current.Value:
75 | self.Debug(f"{symbol.Value} vol {vol.Current.Value}")
76 | atr = AverageTrueRange(21)
77 | symbol_history = symbol_history or self.History(symbol, 21, Resolution.Daily).itertuples()
78 | for data in symbol_history:
79 | atr.Update(
80 | TradeBar(data.Index[1], symbol, data.open, data.high, data.low, data.close, data.volume, datetime.timedelta(days=1)))
81 | position_size = self.calculate_position_size(atr.Current.Value)
82 | position_value = position_size * self.ActiveSecurities[symbol].Price
83 | if position_value < self.Portfolio.Cash:
84 | self.MarketOrder(symbol, position_size)
85 | self.StopMarketOrder(symbol, -1 * position_size, self.stocks_map[symbol.Value]['stop'])
86 |
87 | def calculate_position_size(self, atr):
88 | return round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
89 |
--------------------------------------------------------------------------------
/Base Buyer/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"venv","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)]"},"vscode":{"interpreter":{"hash":"21bbdd1cf262552499c2d15bf1cabff4190d2e483ac6e9c0500c115cdc18e2e9"}}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/Breakout/indicators.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import SimpleMovingAverage, AverageTrueRange, RollingWindow, TradeBar,\
2 | Resolution, Maximum
3 | from datetime import timedelta
4 |
5 |
6 | class SymbolIndicators:
7 | def __init__(self, algorithm, symbol) -> None:
8 | self.algorithm = algorithm
9 | self.sma = SimpleMovingAverage(50)
10 | self.sma_volume = SimpleMovingAverage(50)
11 | self.sma_200 = SimpleMovingAverage(200)
12 | self.atr = AverageTrueRange(21)
13 | self.trade_bar_window = RollingWindow[TradeBar](50)
14 | self.max_volume = Maximum(200)
15 | self.max_price = Maximum(200)
16 | self.sma_window = RollingWindow[float](2)
17 | self.breakout_window = RollingWindow[float](1)
18 |
19 | history = algorithm.History(symbol, 200, Resolution.Daily)
20 | for data in history.itertuples():
21 | trade_bar = TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
22 | self.update(trade_bar)
23 |
24 | def update(self, trade_bar):
25 | self.sma.Update(trade_bar.EndTime, trade_bar.Close)
26 | self.sma_volume.Update(trade_bar.EndTime, trade_bar.Volume)
27 | self.sma_200.Update(trade_bar.EndTime, trade_bar.Close)
28 | self.atr.Update(trade_bar)
29 | self.trade_bar_window.Add(trade_bar)
30 | self.max_volume.Update(trade_bar.EndTime, trade_bar.Volume)
31 | self.max_price.Update(trade_bar.EndTime, trade_bar.High)
32 | self.sma_window.Add(self.sma.Current.Value)
33 | if self.breakout_ready:
34 | level = self.is_breakout
35 | if level:
36 | self.breakout_window.Add(level)
37 |
38 | @property
39 | def ready(self):
40 | return all((
41 | self.sma.IsReady,
42 | self.sma_volume.IsReady,
43 | self.sma_200.IsReady,
44 | self.atr.IsReady,
45 | self.trade_bar_window.IsReady,
46 | self.max_volume.IsReady,
47 | self.max_price.IsReady,
48 | self.sma_window.IsReady,
49 | ))
50 |
51 | @property
52 | def breakout_ready(self):
53 | return all((
54 | self.sma_volume.IsReady,
55 | self.trade_bar_window.IsReady,
56 | ))
57 |
58 | @property
59 | def max_vol_on_down_day(self):
60 | max_vol = 0
61 | for i in range(0, 10):
62 | trade_bar = self.trade_bar_window[i]
63 | if trade_bar.Close < trade_bar.Open:
64 | max_vol = max(max_vol, trade_bar.Volume)
65 | return max_vol
66 |
67 | def atrp(self, close):
68 | return (self.atr.Current.Value / close) * 100
69 |
70 | @property
71 | def uptrending(self):
72 | """
73 | The stock is deemed to be uptrending if the 50 SMA is above 200 SMA
74 | and the latest close is above the 200 SMA.
75 |
76 | :return: True if uptrending.
77 | """
78 | trade_bar_lts = self.trade_bar_window[0]
79 | return self.sma.Current.Value > self.sma_200.Current.Value and trade_bar_lts.Close > self.sma_200.Current.Value
80 |
81 | @property
82 | def high_3_weeks_ago(self) -> bool:
83 | return self.max_price.PeriodsSinceMaximum > 5 * 3
84 |
85 | @property
86 | def high_7_weeks_ago(self) -> bool:
87 | return self.max_price.PeriodsSinceMaximum > 5 * 7
88 |
89 | def get_resistance_levels(self, range_filter: float = 0.005, peak_range: int = 3) -> list:
90 | """
91 | Finds major resistance levels for data in self.trade_bar_window.
92 | Resamples daily data to weekly to find weekly resistance levels.
93 |
94 | :param range_filter: Decides if two prices are part of the same resistance level.
95 | :param peak_range: Number of candles to check either side of peak candle.
96 | :return: set of price resistance levels.
97 | """
98 | df = self.algorithm.PandasConverter.GetDataFrame[TradeBar](list(self.trade_bar_window)[::-1]).reset_index()
99 | df.index = df.time
100 | df = df.resample('W-Fri')
101 | df = df.apply({
102 | 'open':'first',
103 | 'high':'max',
104 | 'low':'min',
105 | 'close':'last',
106 | 'volume':'sum'
107 | })
108 | peaks = []
109 | for i in range(peak_range, len(df) - peak_range):
110 | greater_than_prior_prices = df.iloc[i].high > df.iloc[i - peak_range].high
111 | greater_than_future_prices = df.iloc[i].high > df.iloc[i + peak_range].high
112 | if greater_than_prior_prices and greater_than_future_prices:
113 | peaks.append(df.iloc[i].high)
114 | del df
115 | levels = []
116 | peaks = sorted(peaks)
117 | for i, curr_peak in enumerate(peaks):
118 | level = None
119 | if i == 0:
120 | continue
121 | prev_peak_upper_range = peaks[i - 1] + (peaks[i - 1] * range_filter)
122 | if curr_peak < prev_peak_upper_range:
123 | level = curr_peak
124 | if level and levels:
125 | prev_level_upper_range = levels[-1] + (levels[-1] * range_filter)
126 | if level < prev_level_upper_range:
127 | levels.pop()
128 | if level:
129 | levels.append(level)
130 | return levels
131 |
132 | @property
133 | def is_breakout(self):
134 | """
135 | Determines if the current candle is a breakout.
136 | If so, returns the breakout price level.
137 | """
138 | trade_bar_lts = self.trade_bar_window[0]
139 | trade_bar_prev = self.trade_bar_window[1]
140 | for level in self.get_resistance_levels():
141 | if level > trade_bar_lts.High:
142 | # levels are ordered in ascending order.
143 | # no point in checking any more.
144 | break
145 | # require above average volume
146 | if not trade_bar_lts.Volume > self.sma_volume.Current.Value:
147 | continue
148 | daily_breakout = trade_bar_lts.Open < level and trade_bar_lts.Close > level
149 | gap_up_breakout = trade_bar_prev.Close < level and trade_bar_lts.Open > level
150 | if daily_breakout or gap_up_breakout:
151 | return level
152 |
153 | @property
154 | def close_range_pc(self):
155 | trade_bar_lts = self.trade_bar_window[0]
156 | high, low, close = trade_bar_lts.High, trade_bar_lts.Low, trade_bar_lts.Close
157 | candle_size = high - low
158 | close_size = close - low
159 | return (close_size / candle_size) * 100
--------------------------------------------------------------------------------
/Breakout/main.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import QCAlgorithm, Resolution, BrokerageName
2 | from indicators import SymbolIndicators
3 |
4 |
5 | HVC = 'high volume close'
6 | INSIDE_DAY = 'inside day'
7 | KMA_PULLBACK = 'key moving average pullback'
8 | POCKET_PIVOT = 'pocket pivot'
9 | BREAKOUT = 'breakout'
10 |
11 |
12 | class Breakout(QCAlgorithm):
13 | def Initialize(self):
14 | self.SetCash(10000)
15 | self.UniverseSettings.Resolution = Resolution.Daily
16 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
17 | self.EQUITY_RISK_PC = 0.0075
18 | self.AddUniverse(self.coarse_selection)
19 | self.symbol_map = {}
20 | self.AddEquity("SPY", Resolution.Daily)
21 | self.SL_RISK_PC = -0.05
22 | self.TP_TARGET = 0.20
23 | self.SYMBOLS_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRajMcf0SW61y_kCO9s1mhvCxGlGq9PgSRyQyNyQCx9ALfOF800f22Z0OKkL_-PU_jBWowdOBkM6FtM/pub?gid=0&single=true&output=csv'
24 | self.screened_symbols = []
25 | if not self.LiveMode:
26 | # backtest configuration
27 | self.screened_symbols = ["ASAN", "TSLA", "RBLX", "DOCN", "FTNT", "DDOG", "NET", "BILL", "NVDA", "AMBA", "INMD", "AMEH", "AEHR", "SITM", "CROX"]
28 | self.SetStartDate(2021, 1, 1)
29 | self.SetEndDate(2021, 12, 31)
30 |
31 |
32 | def live_log(self, msg):
33 | """
34 | Sends a log message when live trading, otherwise adds a debug message.
35 |
36 | :param msg: The message to be logged.
37 | """
38 | if self.LiveMode:
39 | self.Log(msg)
40 | else:
41 | self.Debug(msg)
42 |
43 | def coarse_selection(self, coarse):
44 | """
45 | This is the main universe filtering method.
46 | It returns a list of stock symbols which will be passed to self.OnData for processing.
47 |
48 | :param coarse: The initial coarse list of stocks.
49 | """
50 | if self.LiveMode:
51 | self.update_screened_symbols()
52 | return [stock.Symbol for stock in coarse if stock.Symbol.Value in self.screened_symbols]
53 |
54 | def OnData(self, data):
55 | """
56 | This method receives OHLC candle data and triggers buy / sell logic.
57 |
58 | :param data: A TradeBars object containing OHLC bars for each stock.
59 | """
60 |
61 | symbols = []
62 | for symbol in self.ActiveSecurities.Keys:
63 | if symbol.Value not in self.screened_symbols:
64 | continue
65 | if not data.Bars.ContainsKey(symbol):
66 | continue
67 | if symbol not in self.symbol_map:
68 | self.symbol_map[symbol] = SymbolIndicators(self, symbol)
69 | else:
70 | self.symbol_map[symbol].update(data.Bars[symbol])
71 | if not self.symbol_map[symbol].ready:
72 | continue
73 | if self.sell_signal(symbol, data):
74 | self.Liquidate(symbol)
75 | if self.symbol_map[symbol].uptrending and not self.ActiveSecurities[symbol].Invested:
76 | symbols.append(symbol)
77 | self.live_log("processing on data")
78 | if not symbols:
79 | self.live_log("no symbols")
80 | # sort stocks by lowest volatility
81 | for symbol in sorted(symbols, key=lambda symbol: self.symbol_map[symbol].atrp(data.Bars[symbol].Close)):
82 | if self.hvc(symbol):
83 | self.buy(symbol, order_tag=HVC)
84 | if self.inside_day(symbol):
85 | self.buy(symbol, order_tag=INSIDE_DAY)
86 | breakout = self.breakout(symbol)
87 | if breakout:
88 | self.buy(symbol, order_tag=f"{BREAKOUT}: {breakout}")
89 |
90 | def buy(self, symbol, order_tag=None, order_properties=None, price=None):
91 | """
92 | Generates a market order for a stock.
93 |
94 | :param symbol: The stock symbol being traded.
95 | :param order_tag: An order tag string which can be used for reporting.
96 | :param order_properties: Custom order properties which can be used for stop market orders.
97 | :param price: If defined, then a stop market order will be generated at the specified price.
98 | """
99 |
100 | position_size = self.get_position_size(symbol)
101 | position_value = position_size * self.ActiveSecurities[symbol].Price
102 | if position_value < self.Portfolio.Cash:
103 | if price:
104 | self.live_log(f"Limit order {symbol.Value} {position_value}: {order_tag or 'no tag'}: {str(price)}")
105 | self.StopMarketOrder(symbol, position_size, price, order_tag, order_properties)
106 | else:
107 | self.live_log(f"Market order {symbol.Value} {position_value}: {order_tag or 'no tag'}")
108 | self.MarketOrder(symbol, position_size, tag=order_tag)
109 | else:
110 | self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}")
111 |
112 | def get_position_size(self, symbol):
113 | """
114 | Gets the lowest risk position size based on either volatility or risk:
115 | volatility_size = ($total equity * portfolio risk %) / ATR(21)
116 | risk_size = ($total equity * portfolio risk %) / $value of risk on trade
117 | :param symbol: The stock symbol being traded.
118 | """
119 | volatility_size = (self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value
120 | risk_size = (self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / (self.ActiveSecurities[symbol].Price * (self.SL_RISK_PC * -1))
121 | return round(min(volatility_size, risk_size))
122 |
123 | def sell_signal(self, symbol, slice):
124 | """
125 | Returns a boolean signal confirming if a stock should be sold.
126 |
127 | :param symbol: The stock symbol.
128 | :param slice: A TradeBars slice object containing OHLC data for a single period.
129 | """
130 |
131 | profit = self.Portfolio[symbol].UnrealizedProfitPercent
132 | return profit >= self.TP_TARGET or profit <= self.SL_RISK_PC
133 |
134 | def hvc(self, symbol):
135 | """
136 | This pattern occurs after a stock gaps up on huge volume due to earnings or another major news event.
137 | The most important point for the stock going forwards is the gap up closing price.
138 | Highest Volume Ever (HVE)
139 | · Highest Volume in 1 Year (HVIPO)
140 | · Highest Volume Since IPO Week (HVIPO)
141 | · Higheset Volume Since Last EPS (HVLE)
142 | These characteristics show clear INSTITUTIONAL DEMAND.
143 | · 75% (or more) closing range on gap day
144 | · Gap to new highs or within 20% of prior highs
145 | The closing price of the gap up on day 1 is the High Volume Close (HVC).
146 | Exit: 3-5% hard stop below the HVC OR an end-of-day close below this level depending on the market environment.
147 |
148 | :param symbol: The stock symbol.
149 | """
150 | indicators: SymbolIndicators = self.symbol_map[symbol]
151 | trade_bar_lts = indicators.trade_bar_window[0]
152 | trade_bar_prev = indicators.trade_bar_window[1]
153 | # highest vol in 200 days
154 | if not indicators.max_volume.Current.Value == trade_bar_lts.Volume:
155 | return False
156 | # closing range PC above 75; formula = ((close - low) / ((high - low) / 100))
157 | if not indicators.close_range_pc >= 75:
158 | return False
159 | # ensure the stock hasn't gapped down previously
160 | if trade_bar_lts.Open < trade_bar_prev.Close and trade_bar_lts.Close < trade_bar_prev.Close:
161 | return False
162 | # must occur within an uptrend
163 | if not indicators.uptrending :
164 | return False
165 | return True
166 |
167 | def inside_day(self, symbol):
168 | """
169 | · Inside Day (today's whole price bar within yesterday's)
170 | Two day chart pattern.
171 | The body of the second candle must fit inside the first candle.
172 | Vol on the second day must be below average.
173 | Second day must have a positive close.
174 | Must occur within a general market uptrend.
175 | Entry:
176 | Price closes above the high of the first day after the pattern.
177 |
178 | :param symbol: The stock symbol.
179 | """
180 | indicators: SymbolIndicators = self.symbol_map[symbol]
181 | trade_bar_lts = indicators.trade_bar_window[0]
182 | pattern_day_2 = indicators.trade_bar_window[1]
183 | pattern_day_1 = indicators.trade_bar_window[2]
184 | # inside day
185 | if not ((pattern_day_2.High < pattern_day_1.High) and (pattern_day_2.Low > pattern_day_1.Low)):
186 | return False
187 | # below avg vol
188 | if pattern_day_2.Volume > indicators.sma_volume.Current.Value:
189 | return False
190 | # ensure positive close
191 | if pattern_day_2.Open > pattern_day_2.Close:
192 | return False
193 | # must occur within a base
194 | if not indicators.high_7_weeks_ago:
195 | return False
196 | # must occur within an uptrend
197 | if not indicators.uptrending :
198 | return False
199 | # closed above the inside day high
200 | return trade_bar_lts.Close > pattern_day_1.High
201 |
202 | def breakout(self, symbol):
203 | """
204 | Identifies if the stock is uptrending and is within 5% of a breakout level.
205 | Price must be above the breakout level.
206 | The breakout level must be within 10% of the 50 day high.
207 |
208 | :param symbol: The stock symbol.
209 | :return: The breakout level or None.
210 | """
211 | indicators: SymbolIndicators = self.symbol_map[symbol]
212 | trade_bar_lts = indicators.trade_bar_window[0]
213 | if not (indicators.uptrending and indicators.breakout_window.IsReady):
214 | return
215 | level = indicators.breakout_window[0]
216 | if not (level * 1.05 > trade_bar_lts.Close > level):
217 | return
218 | if indicators.max_price.Current.Value > level * 1.1:
219 | return
220 | return level
221 |
222 | def update_screened_symbols(self):
223 | """
224 | When in live mode, updates the stock list filter based on stocks in a google sheet.
225 | """
226 | self.screened_symbols = self.Download(self.SYMBOLS_URL).split("\r\n")
227 | self.live_log(f"symbols updated: {','.join(self.screened_symbols)}")
228 | for symbol in list(self.symbol_map.keys()):
229 | if symbol.Value not in self.screened_symbols:
230 | self.live_log(f"removed from indicators: {symbol.Value}")
231 | del self.symbol_map[symbol]
232 |
--------------------------------------------------------------------------------
/Breakout/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/CryptoMomentum/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 |
5 | class SymbolIndicators:
6 | def __init__(self) -> None:
7 | self.ma = SimpleMovingAverage(50)
8 | self.ma_long = SimpleMovingAverage(200)
9 | self.atr = AverageTrueRange(21)
10 | self.closed_below_window = RollingWindow[bool](2)
11 |
12 | def update(self, trade_bar):
13 | self.ma.Update(trade_bar.EndTime, trade_bar.Close)
14 | self.ma_long.Update(trade_bar.EndTime, trade_bar.Close)
15 | self.atr.Update(trade_bar)
16 | if self.ma.IsReady and self.ma.Current.Value > trade_bar.Close:
17 | self.closed_below_window.Add(True)
18 | else:
19 | self.closed_below_window.Add(False)
20 |
21 | @property
22 | def ready(self):
23 | return all((
24 | self.ma.IsReady,
25 | self.ma_long.IsReady,
26 | self.closed_below_window.IsReady,
27 | ))
28 |
29 | class CryptoMomentum(QCAlgorithm):
30 |
31 | def Initialize(self):
32 | resolution = Resolution.Daily
33 | self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash)
34 | self.SetStartDate(2018, 1, 1)
35 | self.SetCash('USDT', 9000)
36 | self.SetWarmUp(timedelta(200), resolution)
37 | self.EQUITY_RISK_PC = 0.01
38 | tickers = [
39 | "BTCUSDT",
40 | "ETHUSDT",
41 | ]
42 | for ticker in tickers:
43 | symbol = self.AddCrypto(ticker, resolution, Market.Binance).Symbol
44 | trade_plot = Chart(f'Trade Plot {symbol.Value}')
45 | trade_plot.AddSeries(Series('Longs', SeriesType.Scatter, "", Color.Green, ScatterMarkerSymbol.Triangle))
46 | self.AddChart(trade_plot)
47 | self.symbol_map = {}
48 |
49 | def OnData(self, data: Slice):
50 | for symbol in self.ActiveSecurities.Keys:
51 | if not data.Bars.ContainsKey(symbol):
52 | return
53 | if symbol not in self.symbol_map:
54 | self.symbol_map[symbol] = SymbolIndicators()
55 | self.symbol_map[symbol].update(data.Bars[symbol])
56 | if self.IsWarmingUp or not self.symbol_map[symbol].ready:
57 | continue
58 | close = data.Bars[symbol].Close
59 | ma = self.symbol_map[symbol].ma.Current.Value
60 | ma_long = self.symbol_map[symbol].ma_long.Current.Value
61 | prev_close_below_ma = self.symbol_map[symbol].closed_below_window[1]
62 | if not self.ActiveSecurities[symbol].Invested:
63 | if ma > ma_long:
64 | if prev_close_below_ma and close > ma:
65 | self.buy(symbol)
66 | self.Plot(f'Trade Plot {symbol.Value}', "Longs", close)
67 | elif close < ma or ma_long > ma or ma_long > close:
68 | self.Liquidate(symbol)
69 |
70 | def buy(self, symbol):
71 | position_size = (self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value
72 | position_value = position_size * self.ActiveSecurities[symbol].Price
73 | if position_value < self.Portfolio.Cash:
74 | self.MarketOrder(symbol, position_size)
75 |
--------------------------------------------------------------------------------
/CryptoMomentum/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"pycharm":{"name":"#%% md\n"}},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{"pycharm":{"name":"#%%\n"}},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', axis=1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{"pycharm":{"name":"#%%\n"}},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/LongShortMeanReversion/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from AlgorithmImports import Resolution, Time, Extensions, InsightDirection, Insight, InsightType,\
3 | AlphaModel, AverageDirectionalIndex, AverageTrueRange, SimpleMovingAverage, RelativeStrengthIndex,\
4 | BrokerageName, QCAlgorithm, QC500UniverseSelectionModel, CompositeAlphaModel, ConfidenceWeightedPortfolioConstructionModel,\
5 | ImmediateExecutionModel, MaximumDrawdownPercentPerSecurity, MaximumUnrealizedProfitPercentPerSecurity, \
6 | RateOfChangePercent
7 |
8 |
9 | class BaseAlpha(AlphaModel):
10 | def __init__(self, *args, **kwargs):
11 | self.resolution = Resolution.Daily
12 | self.prediction_interval = Time.Multiply(Extensions.ToTimeSpan(Resolution.Daily), 5)
13 | self.symbols = {}
14 | self.equity_risk_pc = kwargs['equity_risk_pc']
15 |
16 | def get_insight(self, algorithm, data, symbol):
17 | confidence = self.get_confidence_for_symbol(algorithm, data, symbol)
18 | # Insight(symbol, period, type, direction, magnitude=None, confidence=None, sourceModel=None, weight=None)
19 | return Insight(symbol, self.prediction_interval, InsightType.Price, self.direction, magnitude=None, confidence=confidence, sourceModel=None, weight=None)
20 |
21 | def get_confidence_for_symbol(self, algorithm, data, symbol):
22 | position_size = (algorithm.Portfolio.TotalPortfolioValue * self.equity_risk_pc) / self.symbols[symbol].atr.Current.Value
23 | position_value = position_size * data[symbol].Close
24 | return position_value / algorithm.Portfolio.TotalPortfolioValue
25 |
26 |
27 | class MeanReversionAlpha(BaseAlpha):
28 | def __init__(self, *args, **kwargs):
29 | super().__init__(*args, **kwargs)
30 | self.adx_lookback = kwargs['adx_lookback']
31 | self.atr_lookback = kwargs['atr_lookback']
32 | self.rsi_lookback = kwargs['rsi_lookback']
33 | self.sma_lookback = kwargs['sma_lookback']
34 | self.spy = kwargs['spy']
35 | self.direction = kwargs['direction']
36 |
37 | def Update(self, algorithm, data):
38 | securities = self.get_long_securities(algorithm, data) if self.direction == InsightDirection.Up\
39 | else self.get_short_securities(algorithm, data)
40 | return [self.get_insight(algorithm, data, symbol) for symbol in securities]
41 |
42 | def get_long_securities(self, algorithm, data):
43 | """
44 | RULES
45 | #1. close above 150 day: stage 2 uptrend
46 | #2. ADX greater than 45: strong uptrend
47 | #3. ATR greater than 4%: high volatility
48 | #4. RSI below 30: oversold
49 | #5. rank by most oversold RSI
50 | #6. filter out 10 stocks
51 | """
52 | securities = [symbol for symbol in self.symbols.keys() \
53 | if data.ContainsKey(symbol) and data[symbol] is not None \
54 | and self.symbols[symbol].sma.Current.Value < data[symbol].Close \
55 | and self.symbols[symbol].adx.Current.Value > 45 \
56 | and self.symbols[symbol].atrp(data[symbol].Close) > 4 \
57 | and self.symbols[symbol].rsi.Current.Value < 30]
58 | return sorted(
59 | securities,
60 | key=lambda symbol: self.symbols[symbol].rsi.Current.Value,
61 | )[:10]
62 |
63 | def get_short_securities(self, algorithm, data):
64 | """
65 | RULES
66 | #1. ADX greater than 50: strong uptrend
67 | #2. ATR greater than 5%: high volatility
68 | #3. RSI above 85: overbought
69 | #4. rank by most overbought RSI
70 | #5. filter out 10 stocks
71 | """
72 | if not self.get_spy_downtrending(data):
73 | return []
74 | securities = [symbol for symbol in self.symbols.keys() \
75 | if data.ContainsKey(symbol) and data[symbol] is not None \
76 | and self.symbols[symbol].adx.Current.Value > 50 \
77 | and self.symbols[symbol].atrp(data[symbol].Close) > 5 \
78 | and self.symbols[symbol].rsi.Current.Value > 85]
79 | return sorted(
80 | securities,
81 | key=lambda symbol: self.symbols[symbol].rsi.Current.Value,
82 | reverse=True,
83 | )[:10]
84 |
85 | def OnSecuritiesChanged(self, algorithm, changes):
86 | for added in changes.AddedSecurities:
87 | self.symbols[added.Symbol] = MeanReversionData(
88 | algorithm, added, self.resolution, adx_lookback=self.adx_lookback,
89 | atr_lookback=self.atr_lookback, rsi_lookback=self.rsi_lookback,
90 | sma_lookback=self.sma_lookback,
91 | )
92 |
93 | for removed in changes.RemovedSecurities:
94 | data = self.symbols.pop(removed.Symbol, None)
95 | if data is not None:
96 | algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
97 |
98 | def get_spy_downtrending(self, data):
99 | symbol = self.spy
100 | if data.ContainsKey(symbol) and data[symbol] is not None:
101 | return data[symbol].Close < self.symbols[symbol].sma.Current.Value
102 | return False
103 |
104 | class MeanReversionData:
105 | def __init__(self, algorithm, security, resolution, adx_lookback = 7, atr_lookback = 10, rsi_lookback = 3, sma_lookback = 150):
106 | self.security = security
107 | self.adx = AverageDirectionalIndex(adx_lookback)
108 | self.atr = AverageTrueRange(atr_lookback)
109 | self.rsi = RelativeStrengthIndex(rsi_lookback)
110 | self.sma = SimpleMovingAverage(sma_lookback)
111 | self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
112 | algorithm.RegisterIndicator(security.Symbol, self.adx, self.Consolidator)
113 | algorithm.RegisterIndicator(security.Symbol, self.atr, self.Consolidator)
114 | algorithm.RegisterIndicator(security.Symbol, self.rsi, self.Consolidator)
115 | algorithm.RegisterIndicator(security.Symbol, self.sma, self.Consolidator)
116 | algorithm.WarmUpIndicator(security.Symbol, self.adx, resolution)
117 | algorithm.WarmUpIndicator(security.Symbol, self.atr, resolution)
118 | algorithm.WarmUpIndicator(security.Symbol, self.rsi, resolution)
119 | algorithm.WarmUpIndicator(security.Symbol, self.sma, resolution)
120 |
121 | def atrp(self, close):
122 | return (self.atr.Current.Value / close) * 100
123 |
124 |
125 | class MeanReversionSelloffAlpha(BaseAlpha):
126 | def __init__(self, *args, **kwargs):
127 | super().__init__(*args, **kwargs)
128 | self.roc_lookback = kwargs['roc_lookback']
129 | self.atr_lookback = kwargs['atr_lookback']
130 | self.sma_lookback = kwargs['sma_lookback']
131 | self.direction = InsightDirection.Up
132 |
133 | def Update(self, algorithm, data):
134 | securities = [
135 | symbol for symbol in self.symbols.keys() \
136 | if data.ContainsKey(symbol) and data[symbol] is not None \
137 | and self.symbols[symbol].sma.Current.Value < data[symbol].Close \
138 | and self.symbols[symbol].is_sold_off
139 | ]
140 | securities = sorted(
141 | securities,
142 | key=lambda symbol: self.symbols[symbol].roc.Current.Value,
143 | )[:10]
144 | return [self.get_insight(algorithm, data, symbol) for symbol in securities]
145 |
146 | def OnSecuritiesChanged(self, algorithm, changes):
147 | for added in changes.AddedSecurities:
148 | self.symbols[added.Symbol] = MeanReversionSelloffData(
149 | algorithm, added, self.resolution, roc_lookback=self.roc_lookback,
150 | atr_lookback=self.atr_lookback, sma_lookback=self.sma_lookback,
151 | )
152 |
153 | for removed in changes.RemovedSecurities:
154 | data = self.symbols.pop(removed.Symbol, None)
155 | if data is not None:
156 | algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
157 |
158 |
159 | class MeanReversionSelloffData:
160 | def __init__(self, algorithm, security, resolution, roc_lookback = 3, atr_lookback = 10, sma_lookback = 150, sell_off_threshold = -12.5):
161 | self.security = security
162 | self.roc = RateOfChangePercent(roc_lookback)
163 | self.atr = AverageTrueRange(atr_lookback)
164 | self.sma = SimpleMovingAverage(sma_lookback)
165 | self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
166 | self.sell_off_threshold = sell_off_threshold
167 | for indicator in (self.roc, self.atr, self.sma):
168 | algorithm.RegisterIndicator(security.Symbol, indicator, self.Consolidator)
169 | algorithm.WarmUpIndicator(security.Symbol, indicator, resolution)
170 |
171 | @property
172 | def is_sold_off(self):
173 | return self.roc.Current.Value <= self.sell_off_threshold
174 |
175 |
176 | class LongShortMeanReversion(QCAlgorithm):
177 | def Initialize(self):
178 | self.SetStartDate(2008, 1, 1)
179 | self.SetCash(100000)
180 | self.SetWarmUp(datetime.timedelta(200), Resolution.Daily)
181 | self.UniverseSettings.Resolution = Resolution.Daily
182 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
183 | self.SetUniverseSelection(QC500UniverseSelectionModel())
184 | self.EQUITY_RISK_PC = 0.01
185 | self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
186 | self.SetAlpha(
187 | CompositeAlphaModel(
188 | # MeanReversionAlpha(direction=InsightDirection.Up, equity_risk_pc=self.EQUITY_RISK_PC, adx_lookback=7, atr_lookback=10, rsi_lookback=3, sma_lookback=150),
189 | MeanReversionAlpha(direction=InsightDirection.Down, equity_risk_pc=self.EQUITY_RISK_PC, adx_lookback=7, atr_lookback=10, rsi_lookback=3, sma_lookback=50, spy=self.spy),
190 | # MeanReversionSelloffAlpha(equity_risk_pc=self.EQUITY_RISK_PC, atr_lookback=21, roc_lookback=3, sma_lookback=150)
191 | )
192 | )
193 | # rebalance every Sunday & Wednesday
194 | self.SetPortfolioConstruction(ConfidenceWeightedPortfolioConstructionModel(self.DateRules.EveryDay("SPY")))
195 | self.SetExecution(ImmediateExecutionModel())
196 | self.Settings.RebalancePortfolioOnInsightChanges = False
197 | self.Settings.RebalancePortfolioOnSecurityChanges = False
198 | self.AddRiskManagement(MaximumDrawdownPercentPerSecurity(0.2))
199 | self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.05))
200 |
--------------------------------------------------------------------------------
/LongShortMeanReversion/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/MABreakthroughETF/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 |
5 | class SymbolIndicators:
6 | def __init__(self) -> None:
7 | self.ma = SimpleMovingAverage(50)
8 | self.ma_long = SimpleMovingAverage(200)
9 | self.atr = AverageTrueRange(21)
10 | self.closed_below_window = RollingWindow[bool](2)
11 |
12 | def update(self, trade_bar):
13 | self.ma.Update(trade_bar.EndTime, trade_bar.Close)
14 | self.ma_long.Update(trade_bar.EndTime, trade_bar.Close)
15 | self.atr.Update(trade_bar)
16 | if self.ma.IsReady and self.ma.Current.Value > trade_bar.Close:
17 | self.closed_below_window.Add(True)
18 | else:
19 | self.closed_below_window.Add(False)
20 |
21 | @property
22 | def ready(self):
23 | return all((
24 | self.ma.IsReady,
25 | self.ma_long.IsReady,
26 | self.closed_below_window.IsReady,
27 | ))
28 |
29 | @property
30 | def ma_above_ma_long(self):
31 | return 1 - self.ma_long.Current.Value/self.ma.Current.Value
32 |
33 | @property
34 | def ma_violated(self):
35 | return self.closed_below_window[1] and self.closed_below_window[0]
36 |
37 | class MABreakthroughETF(QCAlgorithm):
38 |
39 | def Initialize(self):
40 | self.SetStartDate(2002, 1, 1)
41 | self.SetEndDate(2022, 11, 1)
42 | self.SetCash(10000)
43 | self.SetWarmUp(timedelta(200), Resolution.Daily)
44 | self.UniverseSettings.Resolution = Resolution.Daily
45 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
46 | self.EQUITY_RISK_PC = 0.01
47 | tickers = [
48 | "GXLE",
49 | "GXLK",
50 | "GXLV",
51 | "GXLF",
52 | "SXLP",
53 | "SXLI",
54 | "GXLC",
55 | "SXLY",
56 | "SXLB",
57 | "SXLU",
58 | ] if self.LiveMode else [
59 | "XLE",
60 | "XLK",
61 | "XLV",
62 | "XLF",
63 | "XLP",
64 | "XLI",
65 | "XLC",
66 | "XLY",
67 | "XLB",
68 | "XLU",
69 | ]
70 | self.symbol_map = {}
71 | self.warm_up_buy_signals = set()
72 | for ticker in tickers:
73 | self.AddEquity(ticker, Resolution.Daily)
74 |
75 | def live_log(self, msg):
76 | if self.LiveMode:
77 | self.Log(msg)
78 |
79 | def OnData(self, data):
80 | uninvested = []
81 | self.Debug(f"{self.Time} - {','.join([symbol.Value for symbol in self.warm_up_buy_signals])}")
82 | for symbol in self.ActiveSecurities.Keys:
83 | if not data.Bars.ContainsKey(symbol):
84 | self.Debug("symbol not in data")
85 | continue
86 | if symbol not in self.symbol_map:
87 | self.symbol_map[symbol] = SymbolIndicators()
88 | self.symbol_map[symbol].update(data.Bars[symbol])
89 | if not self.symbol_map[symbol].ready:
90 | self.Debug("indicators not ready")
91 | continue
92 | if not self.ActiveSecurities[symbol].Invested:
93 | uninvested.append(symbol)
94 | if symbol in self.warm_up_buy_signals and self.sell_signal(symbol, data):
95 | self.Debug(f"removing warmed up buy signal: {symbol.Value}")
96 | self.warm_up_buy_signals.remove(symbol)
97 | elif self.sell_signal(symbol, data):
98 | self.Liquidate(symbol)
99 | uninvested = sorted(
100 | uninvested,
101 | key=lambda symbol: self.symbol_map[symbol].ma_above_ma_long,
102 | reverse=True,
103 | )
104 | for symbol in uninvested:
105 | if symbol in self.warm_up_buy_signals:
106 | self.Debug(f"buying warmed up symbol: {symbol.Value}")
107 | self.buy(symbol)
108 | continue
109 | close = data.Bars[symbol].Close
110 | ma = self.symbol_map[symbol].ma.Current.Value
111 | ma_long = self.symbol_map[symbol].ma_long.Current.Value
112 | prev_close_below_ma = self.symbol_map[symbol].closed_below_window[1]
113 | if not self.ActiveSecurities[symbol].Invested:
114 | if ma > ma_long:
115 | if prev_close_below_ma and close > ma:
116 | self.buy(symbol)
117 |
118 | def buy(self, symbol):
119 | if self.IsWarmingUp:
120 | self.Debug(f"adding symbol to warm up signals: {symbol.Value}")
121 | self.warm_up_buy_signals.add(symbol)
122 | else:
123 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value)
124 | position_value = position_size * self.ActiveSecurities[symbol].Price
125 | self.live_log(f"buying {symbol.Value}")
126 | if position_value < self.Portfolio.Cash:
127 | self.MarketOrder(symbol, position_size)
128 | if symbol in self.warm_up_buy_signals:
129 | self.warm_up_buy_signals.remove(symbol)
130 | self.Debug(f"symbol purchased and removed from warm up signals: {symbol.Value}")
131 | else:
132 | self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}")
133 |
134 | def sell_signal(self, symbol, data):
135 | ma = self.symbol_map[symbol].ma.Current.Value
136 | ma_long = self.symbol_map[symbol].ma_long.Current.Value
137 | return self.symbol_map[symbol].ma_violated or ma_long > ma or ma_long > data.Bars[symbol].Close
138 |
139 |
--------------------------------------------------------------------------------
/MABreakthroughETF/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/MarketOnMarketOff/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 |
5 | SIGNAL_BUY = 'buy'
6 | SIGNAL_SELL = 'sell'
7 | LONG_LOOKBACK = 200
8 | SHORT_LOOKBACK = 50
9 |
10 |
11 | class MarketOnMarketOff(QCAlgorithm):
12 | def Initialize(self):
13 | self.SetStartDate(2018, 1, 1)
14 | self.SetCash(100000)
15 | self.long_symbol = "SPY"
16 | self.short_symbol = "SH"
17 | self.long = self.AddEquity(self.long_symbol, Resolution.Daily)
18 | self.short = self.AddEquity(self.short_symbol, Resolution.Daily)
19 | self.data = None
20 |
21 | def OnData(self, slice: Slice):
22 | if not self.data:
23 | self.data = SymbolData(self.History(self.long.Symbol, LONG_LOOKBACK, Resolution.Daily))
24 | else:
25 | prices = slice.get(self.long.Symbol)
26 | if prices:
27 | self.data.update(self.Time, prices.Close, prices.Volume)
28 | self.data.low_window.Add(prices.Low)
29 | if not self.data.ready():
30 | return
31 | signal = self.data.get_signal()
32 | if not self.Portfolio.Invested:
33 | if signal == SIGNAL_BUY:
34 | self.SetHoldings(self.long_symbol, 1)
35 | elif signal == SIGNAL_SELL:
36 | self.SetHoldings(self.short_symbol, 1)
37 | else:
38 | if signal == SIGNAL_BUY and self.ActiveSecurities[self.short.Symbol].Invested:
39 | self.Liquidate()
40 | self.SetHoldings(self.long_symbol, 1)
41 | if signal == SIGNAL_SELL and self.ActiveSecurities[self.long.Symbol].Invested:
42 | self.Liquidate()
43 | self.SetHoldings(self.short_symbol, 1)
44 |
45 |
46 | class SymbolData:
47 | def __init__(self, history):
48 | self.dd_window = RollingWindow[int](SHORT_LOOKBACK)
49 | self.ftd_window = RollingWindow[int](LONG_LOOKBACK)
50 | self.rally_day_window = RollingWindow[int](LONG_LOOKBACK)
51 | self.low_window = RollingWindow[float](LONG_LOOKBACK)
52 | self.vol_ma = SimpleMovingAverage(SHORT_LOOKBACK)
53 | self.max = Maximum(LONG_LOOKBACK)
54 | self.min = Minimum(LONG_LOOKBACK)
55 | self.previous_vol = None
56 | self.previous_close = None
57 | self.signal = None
58 |
59 | for data in history.itertuples():
60 | self.update(data.Index[1], data.close, data.volume)
61 | self.low_window.Add(data.low)
62 |
63 | def update(self, time, close, volume):
64 | self.vol_ma.Update(time, volume)
65 | self.max.Update(time, close)
66 | self.min.Update(time, close)
67 | if self.previous_close and self.previous_vol:
68 | day_change = (close - self.previous_close) / close
69 | is_distribution_day = day_change < -0.02 and volume > self.previous_vol
70 | is_follow_through_day = day_change > 0.17 and volume > self.previous_vol and volume > self.vol_ma.Current.Value
71 | self.dd_window.Add(1 if is_distribution_day else 0)
72 | self.ftd_window.Add(1 if is_follow_through_day else 0)
73 | self.rally_day_window.Add(1 if day_change > 0 else 0)
74 | self.previous_close = close
75 | self.previous_vol = volume
76 |
77 | def get_signal(self):
78 | """
79 | There has been a rally day and a follow through day after a recent bottom.
80 | The market isn't in distribution
81 | :return: boolean
82 | """
83 | if self.signal in (SIGNAL_SELL, None):
84 | # find bottom, rally day, follow through day
85 | # rally day = loop through each day after min day. Look for up day that starts the rally attempt
86 | # follow through day = loop through each day after rally day. Check ftd conditions
87 | rally_day_index = None
88 | for day_index in reversed(range(self.min.PeriodsSinceMinimum)):
89 | if self.rally_day_window[day_index] == 1:
90 | rally_day_index = day_index
91 | break
92 | if rally_day_index is None:
93 | self.signal = SIGNAL_SELL
94 | return self.signal
95 | for ftd_index in reversed(range(rally_day_index - 1)):
96 | if self.ftd_window[ftd_index] == 1:
97 | # check if ftd failed: if the low of the ftd was taken out.
98 | # if so, move on to the next ftd_index
99 | # if not, exit loop and update buy signal
100 | ftd_low_taken_out = np.min(self.low_window[0:ftd_index - 1]) < self.low_window[ftd_index]
101 | if ftd_low_taken_out:
102 | continue
103 | self.signal = SIGNAL_BUY
104 | return self.signal
105 | if self.signal in (SIGNAL_BUY, None):
106 | market_in_distribution = sum(list(self.dd_window)) > 5
107 | if market_in_distribution:
108 | self.signal = SIGNAL_SELL
109 | else:
110 | self.signal = SIGNAL_BUY
111 | return self.signal
112 |
113 | def ready(self):
114 | return self.dd_window.IsReady and self.ftd_window.IsReady and self.rally_day_window.IsReady and\
115 | self.low_window.IsReady and self.vol_ma.IsReady and self.max.IsReady and self.min.IsReady
116 |
--------------------------------------------------------------------------------
/MarketOnMarketOff/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MasterAlgo/base.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import Resolution
2 |
3 | class BaseStrategy:
4 | def __init__(self, equity_risk_pc = 0.01, **kwargs) -> None:
5 | self.equity_risk_pc = equity_risk_pc
6 |
7 | def calculate_position_size(self, algorithm, symbol):
8 | atr = algorithm.symbols[symbol].atr.Current.Value
9 | return round((algorithm.Portfolio.TotalPortfolioValue * self.equity_risk_pc) / atr)
10 |
11 | def rebalance_due(self, algorithm):
12 | return algorithm.resolution == Resolution.Daily
13 |
14 | def handle_on_data(self, algorithm, data):
15 | raise NotImplementedError()
16 |
17 | def OnData(self, algorithm, data):
18 | if not self.rebalance_due(algorithm):
19 | return
20 | self.handle_on_data(algorithm, data)
21 |
22 | def get_indicator_configs(self):
23 | return []
24 |
25 | def get_manual_indicator_configs(self):
26 | return [indicator for indicator in self.get_indicator_configs() if indicator.get('manual')]
27 |
28 | def handle_manual_indicators(self, algorithm, data):
29 | manual_indicators = self.get_manual_indicator_configs()
30 | if manual_indicators:
31 | raise NotImplementedError("This class contains manual indicators but doesn't warm them up")
--------------------------------------------------------------------------------
/MasterAlgo/constants.py:
--------------------------------------------------------------------------------
1 | LONG = 'long'
2 | SHORT = 'short'
--------------------------------------------------------------------------------
/MasterAlgo/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from AlgorithmImports import Resolution, BrokerageName, QCAlgorithm, QC500UniverseSelectionModel, \
3 | ImmediateExecutionModel, RollingWindow, OrderEvent, OrderStatus, OrderTicket
4 | from turtle_trading import TurtleTrading
5 |
6 |
7 | class MyQC500(QC500UniverseSelectionModel):
8 | """
9 | Optimized to select the top 250 stocks by dollar volume
10 | """
11 | numberOfSymbolsCoarse = 250
12 |
13 |
14 | class MasterAlgo(QCAlgorithm):
15 | def Initialize(self):
16 | self.SetStartDate(2002, 1, 1)
17 | self.SetCash(100000)
18 | self.resolution = Resolution.Daily
19 | self.SetWarmUp(datetime.timedelta(200), self.resolution)
20 | self.UniverseSettings.Resolution = self.resolution
21 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
22 | self.SetUniverseSelection(MyQC500())
23 | self.SetExecution(ImmediateExecutionModel())
24 | self.symbols = {}
25 | self.EQUITY_RISK_PC = 0.02
26 | self.strategies = (
27 | # initialize strategy classes here
28 | TurtleTrading(high_lookback=40),
29 | )
30 | self.indicator_configs = []
31 | for strategy in self.strategies:
32 | self.indicator_configs += strategy.get_indicator_configs()
33 |
34 |
35 | def OnSecuritiesChanged(self, changes):
36 | for added in changes.AddedSecurities:
37 | self.symbols[added.Symbol] = SymbolData(
38 | self, added, self.resolution, self.indicator_configs
39 | )
40 |
41 | for removed in changes.RemovedSecurities:
42 | data = self.symbols.pop(removed.Symbol, None)
43 | if data is not None:
44 | self.SubscriptionManager.RemoveConsolidator(
45 | removed.Symbol, data.Consolidator
46 | )
47 | del data
48 |
49 | def OnData(self, data):
50 | for strategy in self.strategies:
51 | strategy.handle_manual_indicators(self, data)
52 | if self.IsWarmingUp:
53 | continue
54 | strategy.OnData(self, data)
55 |
56 | def OnOrderEvent(self, event: OrderEvent) -> None:
57 | if event.Status == OrderStatus.Filled:
58 | self.symbols[event.Symbol].confirm_position(event)
59 | elif event.Status in (OrderStatus.Canceled, OrderStatus.Invalid):
60 | strategy_name = self.get_strategy_name_for_order_id(event.OrderId)
61 | self.symbols[event.Symbol].delete_position(strategy_name)
62 |
63 | def buy(self, symbol, position_size, strategy_name):
64 | self.symbols[symbol].add_position(
65 | self.MarketOrder(symbol, position_size),
66 | strategy_name,
67 | )
68 |
69 | def liquidate(self, symbol, strategy_name):
70 | self.Liquidate(symbol)
71 | self.symbols[symbol].delete_position(strategy_name)
72 |
73 |
74 | class SymbolData:
75 | def __init__(self, algorithm, security, resolution, indicator_configs):
76 | self.security = security
77 | self.algorithm = algorithm
78 | self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
79 | # "strategy_name": {"order_id" 1, "created" datetime.datetime()}
80 | self.positions = {}
81 | self.indicators = []
82 | for config in indicator_configs:
83 | if config['class'] == RollingWindow:
84 | indicator_class = config['class'][config['window_type']](*config['args'])
85 | else:
86 | indicator_class = config['class'](*config['args'])
87 | setattr(self, config['name'], indicator_class)
88 | indicator = getattr(self, config['name'])
89 | self.indicators.append(indicator)
90 | if config.get('manual'):
91 | continue
92 | algorithm.RegisterIndicator(security.Symbol, indicator, self.Consolidator)
93 | algorithm.WarmUpIndicator(security.Symbol, indicator, resolution)
94 |
95 | def add_position(self, order_ticket: OrderTicket, strategy_name: str):
96 | self.positions[strategy_name] = {
97 | "order_id": order_ticket.OrderId,
98 | }
99 |
100 | def get_strategy_name_for_order_id(self, order_id):
101 | strategy_name, _ = [(strategy_name, position) for strategy_name, position in self.positions.items()\
102 | if position["order_id"] == order_id][0]
103 | return strategy_name
104 |
105 |
106 | def confirm_position(self, event: OrderEvent):
107 | strategy_name = self.get_strategy_name_for_order_id(event.OrderId)
108 | self.positions[strategy_name]["created"] = self.algorithm.Time
109 |
110 | def delete_position(self, strategy_name):
111 | del self.positions[strategy_name]
112 |
113 | def get_position(self, strategy_name):
114 | return self.positions[strategy_name]
115 |
116 | def get_position_age(self, strategy_name) -> datetime.timedelta:
117 | return self.get_position(strategy_name)['created'] - self.algorithm.Time
118 |
119 | @property
120 | def ready(self) -> bool:
121 | return all([indicator.IsReady for indicator in self.indicators])
122 |
--------------------------------------------------------------------------------
/MasterAlgo/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/MasterAlgo/turtle_trading.py:
--------------------------------------------------------------------------------
1 | from base import BaseStrategy
2 | from AlgorithmImports import AverageTrueRange, SimpleMovingAverage, RateOfChangePercent,\
3 | Maximum, Minimum, RollingWindow
4 |
5 |
6 | class TurtleTrading(BaseStrategy):
7 | def __init__(self, *args, **kwargs) -> None:
8 | super().__init__(*args, **kwargs)
9 | self.high_lookback = kwargs['high_lookback']
10 |
11 | def handle_on_data(self, algorithm, data):
12 | securities = [
13 | symbol for symbol in algorithm.symbols.keys() \
14 | if algorithm.ActiveSecurities.ContainsKey(symbol) \
15 | and data.ContainsKey(symbol) and data[symbol] is not None \
16 | and algorithm.symbols[symbol].ready \
17 | and algorithm.symbols[symbol].sma.Current.Value < data[symbol].Close \
18 | and algorithm.symbols[symbol].high_window[1] < data[symbol].Close \
19 | and algorithm.symbols[symbol].high_periods_since_window[1] >= self.high_lookback -1 \
20 | and not algorithm.ActiveSecurities[symbol].Invested
21 | ]
22 | securities = sorted(
23 | securities,
24 | key=lambda symbol: algorithm.symbols[symbol].roc.Current.Value,
25 | reverse=True,
26 | )[:10]
27 | for symbol in securities:
28 | position_size = self.calculate_position_size(algorithm, symbol)
29 | if position_size <= 0:
30 | continue
31 | position_value = position_size * algorithm.ActiveSecurities[symbol].Price
32 | if position_value < algorithm.Portfolio.Cash:
33 | algorithm.MarketOrder(symbol, position_size)
34 | self.handle_exit_strategy(algorithm)
35 |
36 | def handle_exit_strategy(self, algorithm):
37 | for symbol in algorithm.symbols.keys():
38 | if not algorithm.ActiveSecurities.ContainsKey(symbol) or not algorithm.ActiveSecurities[symbol].Invested:
39 | continue
40 | close = algorithm.ActiveSecurities[symbol].Close
41 | if close < algorithm.symbols[symbol].low_window[1]:
42 | algorithm.Liquidate(symbol)
43 | if algorithm.Portfolio[symbol].UnrealizedProfitPercent >= 0.20 or \
44 | algorithm.Portfolio[symbol].UnrealizedProfitPercent <= -0.08 or \
45 | close < algorithm.symbols[symbol].sma.Current.Value:
46 | algorithm.Liquidate(symbol)
47 |
48 | def get_indicator_configs(self):
49 | return [
50 | {
51 | "name": "atr",
52 | "class": AverageTrueRange,
53 | "args": [21],
54 | },
55 | {
56 | "name": "roc",
57 | "class": RateOfChangePercent,
58 | "args": [150],
59 | },
60 | {
61 | "name": "sma",
62 | "class": SimpleMovingAverage,
63 | "args": [150],
64 | },
65 | {
66 | "name": "high",
67 | "class": Maximum,
68 | "args": [40],
69 | },
70 | {
71 | "name": "low",
72 | "class": Minimum,
73 | "args": [20],
74 | },
75 | {
76 | "name": "high_window",
77 | "class": RollingWindow,
78 | "args": [2],
79 | "manual": True,
80 | "window_type": float,
81 | },
82 | {
83 | "name": "high_periods_since_window",
84 | "class": RollingWindow,
85 | "args": [2],
86 | "manual": True,
87 | "window_type": float,
88 | },
89 | {
90 | "name": "low_window",
91 | "class": RollingWindow,
92 | "args": [2],
93 | "manual": True,
94 | "window_type": float,
95 | },
96 | ]
97 |
98 | def handle_manual_indicators(self, algorithm, data):
99 | for symbol in algorithm.symbols.keys():
100 | algorithm.symbols[symbol].high_window.Add(algorithm.symbols[symbol].high.Current.Value)
101 | algorithm.symbols[symbol].high_periods_since_window.Add(algorithm.symbols[symbol].high.PeriodsSinceMaximum)
102 | algorithm.symbols[symbol].low_window.Add(algorithm.symbols[symbol].low.Current.Value)
103 |
--------------------------------------------------------------------------------
/Mean Reversion Long/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from datetime import timedelta
3 |
4 | from AlgorithmImports import *
5 | # endregion
6 |
7 |
8 | class MeanReversionLong(QCAlgorithm):
9 | def Initialize(self):
10 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
11 | self.SetStartDate(2012, 1, 1)
12 | self.SetEndDate(2022, 2, 1)
13 | self.SetCash(10000)
14 | self.UniverseSettings.Resolution = Resolution.Daily
15 | self.AddUniverse(self.coarse_selection)
16 | self.symbol_data = {}
17 | self.EQUITY_RISK_PC = 0.01
18 | self.STOP_LOSS_PC = 0.08
19 | self.open_positions = {}
20 |
21 | def coarse_selection(self, coarse):
22 | stocks = []
23 | coarse = sorted(
24 | [stock for stock in coarse if stock.DollarVolume > 2500000 and stock.Price > 10 and stock.Market == Market.USA and stock.HasFundamentalData],
25 | key=lambda x: x.DollarVolume, reverse=True
26 | )[:500]
27 | for stock in coarse:
28 | symbol = stock.Symbol
29 | if symbol not in self.symbol_data:
30 | self.symbol_data[symbol] = SymbolData(self.History(stock.Symbol, 200, Resolution.Daily))
31 | else:
32 | self.symbol_data[symbol].update(self.Time, stock.Price)
33 | # Rule #1: Trend template
34 | if not (stock.Price > self.symbol_data[symbol].ma.Current.Value >
35 | self.symbol_data[symbol].ma_long.Current.Value > self.symbol_data[symbol].ma_200.Current.Value):
36 | continue
37 | # Rule #2: 7 day ROC greater than 0
38 | if self.symbol_data[symbol].roc.Current.Value < 0:
39 | continue
40 | # Rule #3: 2 day RSI less than 30
41 | if self.symbol_data[symbol].rsi.Current.Value > 30:
42 | continue
43 | stocks.append(stock)
44 | # Rule #3: Rank by the highest ROC
45 | symbols = [stock.Symbol for stock in sorted(stocks, key=lambda x: self.symbol_data[x.Symbol].roc.Current.Value, reverse=True)]
46 | return symbols
47 |
48 | def position_outdated(self, symbol) -> bool:
49 | """
50 | Checks if the position is too old, or if it's time isn't stored
51 | """
52 | if self.open_positions.get(symbol):
53 | return (self.Time - self.open_positions.get(symbol)).days >= 4
54 | return True
55 |
56 | def OnData(self, slice) -> None:
57 | for symbol in self.ActiveSecurities.Keys:
58 | if self.ActiveSecurities[symbol].Invested:
59 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.03 or \
60 | self.Portfolio[symbol].UnrealizedProfitPercent <= self.STOP_LOSS_PC * -1 or \
61 | self.position_outdated(symbol):
62 | self.Liquidate(symbol)
63 | if self.open_positions.get(symbol):
64 | del self.open_positions[symbol]
65 | else:
66 | position_size, position_value = self.calculate_position(symbol)
67 | if position_size > 0 and self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Buy) > position_value:
68 | self.MarketOrder(symbol, position_size)
69 | self.open_positions[symbol] = self.Time
70 |
71 | def calculate_position(self, symbol):
72 | risk = self.ActiveSecurities[symbol].Price * self.STOP_LOSS_PC
73 | if risk <= 0:
74 | return 0, 0
75 | size = int((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / risk)
76 | return size, size * self.ActiveSecurities[symbol].Price
77 |
78 |
79 | class SymbolData:
80 | def __init__(self, history):
81 | self.rsi = RelativeStrengthIndex(2)
82 | self.ma = SimpleMovingAverage(50)
83 | self.ma_long = SimpleMovingAverage(150)
84 | self.ma_200 = SimpleMovingAverage(200)
85 | self.roc = RateOfChangePercent(7)
86 |
87 | for data in history.itertuples():
88 | self.update(data.Index[1], data.close)
89 |
90 | def update(self, time, price):
91 | self.rsi.Update(time, price)
92 | self.ma.Update(time, price)
93 | self.ma_long.Update(time, price)
94 | self.ma_200.Update(time, price)
95 | self.roc.Update(time, price)
96 |
--------------------------------------------------------------------------------
/Mean Reversion Long/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/Mean Reversion Short/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from datetime import timedelta
3 |
4 | from AlgorithmImports import *
5 | # endregion
6 | from dateutil.parser import parse
7 |
8 |
9 | class MeanReversionShort(QCAlgorithm):
10 |
11 | def Initialize(self):
12 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
13 | self.SetStartDate(2012, 1, 1)
14 | self.SetEndDate(2022, 1, 1)
15 | self.SetCash(10000)
16 | self.UniverseSettings.Resolution = Resolution.Daily
17 | self.AddUniverse(self.coarse_selection, self.fine_selection)
18 | self.fine_averages = {}
19 | self._changes = None
20 | self.EQUITY_RISK_PC = 0.01
21 |
22 | def coarse_selection(self, coarse):
23 | stocks = []
24 | stock_rsi_map = {}
25 | count = 0
26 | coarse = [stock for stock in coarse if stock.DollarVolume > 5000000 and stock.Price > 10 and stock.Market == Market.USA and stock.HasFundamentalData]
27 | for stock in sorted(coarse, key=lambda x: x.DollarVolume, reverse=True):
28 | if count == 500:
29 | break
30 | symbol = stock.Symbol
31 | data = CoarseData(self.History(symbol, 3, Resolution.Daily))
32 | # Rule #3: Last two days up
33 | # Rule #4: 3 day RSI above 85
34 | if data.rsi.Current.Value >= 85 and data.two_days_uptrend:
35 | stocks.append(symbol)
36 | stock_rsi_map[symbol] = data.rsi.Current.Value
37 | count += 1
38 | # rank stocks by RSI
39 | return sorted(stocks, key=lambda symbol: stock_rsi_map[symbol], reverse=True)
40 |
41 | def fine_selection(self, fine):
42 | stocks = []
43 | for stock in sorted(fine, key=lambda x: x.MarketCap, reverse=True):
44 | symbol = stock.Symbol
45 | self.fine_averages[symbol] = FineSelectionData(self.History(symbol, 10, Resolution.Daily))
46 | # Rule #1: ADX of past 7 days above 50
47 | if not self.fine_averages[symbol].adx.Current.Value > 50:
48 | continue
49 | natr = self.fine_averages[symbol].atr.Current.Value / stock.Price
50 | # Rule #2: ATR % of past 10 days above 5%
51 | if not natr > 0.05:
52 | continue
53 | stocks.append(symbol)
54 | if len(stocks) == 10:
55 | break
56 | return stocks
57 |
58 | def position_outdated(self, symbol) -> bool:
59 | """
60 | Checks if the position is too old, or if it's time isn't stored
61 | """
62 | if self.ObjectStore.ContainsKey(str(symbol)):
63 | return (self.Time - parse(self.ObjectStore.Read(str(symbol)))).days >= 2
64 | return True
65 |
66 | def OnData(self, slice) -> None:
67 | for symbol in self.ActiveSecurities.Keys:
68 | if self.ActiveSecurities[symbol].Invested:
69 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.04 or \
70 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.2 or \
71 | self.position_outdated(symbol):
72 | self.Liquidate(symbol)
73 | if self.ObjectStore.ContainsKey(str(symbol)):
74 | self.ObjectStore.Delete(str(symbol))
75 | else:
76 | position_size, position_value = self.calculate_position(symbol)
77 | if self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Sell) > position_value:
78 | self.MarketOrder(symbol, -position_size)
79 | self.ObjectStore.Save(str(symbol), str(self.Time))
80 |
81 | def calculate_position(self, symbol):
82 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.fine_averages[symbol].atr.Current.Value)
83 | return position_size, position_size * self.ActiveSecurities[symbol].Price
84 |
85 |
86 | class CoarseData():
87 | def __init__(self, history):
88 | self.rsi = RelativeStrengthIndex(3)
89 | self.close = RollingWindow[float](3)
90 |
91 | for data in history.itertuples():
92 | self.rsi.Update(data.Index[1], data.close)
93 | self.close.Add(data.close)
94 |
95 | @property
96 | def two_days_uptrend(self):
97 | try:
98 | return self.close[0] > self.close[1] > self.close[2]
99 | except:
100 | return False
101 |
102 |
103 | class FineSelectionData():
104 | def __init__(self, history):
105 | self.adx = AverageDirectionalIndex(7)
106 | self.atr = AverageTrueRange(10)
107 |
108 | for data in history.itertuples():
109 | self.update(data)
110 |
111 | def update(self, data):
112 | trade_bar = TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
113 | self.adx.Update(trade_bar)
114 | self.atr.Update(trade_bar)
115 |
--------------------------------------------------------------------------------
/Mean Reversion Short/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MeanReversionBBLong/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 | from dateutil.parser import parse
5 |
6 | NUM_OF_SYMBOLS = "number_of_symbols"
7 |
8 |
9 | class MeanReversionBBLong(QCAlgorithm):
10 | def Initialize(self):
11 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
12 | self.SetStartDate(1995, 1, 1)
13 | self.SetEndDate(2022, 1, 1)
14 | self.SetCash(10000)
15 | self.UniverseSettings.Resolution = Resolution.Daily
16 | self.AddUniverse(self.coarse_selection)
17 | self.symbol_data = {}
18 |
19 | def coarse_selection(self, coarse):
20 | stocks = []
21 | coarse = sorted(
22 | [stock for stock in coarse if stock.DollarVolume > 2500000 and stock.Price > 1 and stock.Market == Market.USA and stock.HasFundamentalData],
23 | key=lambda x: x.DollarVolume, reverse=True
24 | )[:500]
25 | for stock in coarse:
26 | symbol = stock.Symbol
27 | if symbol not in self.symbol_data:
28 | self.symbol_data[symbol] = SymbolData(self.History(stock.Symbol, 150, Resolution.Daily))
29 | else:
30 | self.symbol_data[symbol].update(self.Time, stock.Price)
31 | # Rule #1: Stock must be in long term uptrend (above 50 day)
32 | # Rule #2: 3 day RSI is below 30
33 | if not (self.symbol_data[symbol].rsi.Current.Value < 30 and stock.Price > self.symbol_data[symbol].ma.Current.Value >
34 | self.symbol_data[symbol].ma_long.Current.Value):
35 | continue
36 | # Rule #3: Stock must be at or below the lower bollinger band
37 | if not stock.Price <= self.symbol_data[symbol].bb.LowerBand.Current.Value:
38 | continue
39 | stocks.append(stock)
40 | # Rule #4: Rank by the lowest RSI = most oversold stocks
41 | symbols = [stock.Symbol for stock in sorted(stocks, key=lambda x: self.symbol_data[x.Symbol].rsi.Current.Value)]
42 | return symbols
43 |
44 | def position_outdated(self, symbol) -> bool:
45 | """
46 | Checks if the position is too old, or if it's time isn't stored
47 | """
48 | if self.ObjectStore.ContainsKey(str(symbol)):
49 | return (self.Time - parse(self.ObjectStore.Read(str(symbol)))).days >= 4
50 | return True
51 |
52 | def OnData(self, slice) -> None:
53 | for symbol in self.ActiveSecurities.Keys:
54 | if self.ActiveSecurities[symbol].Invested:
55 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.03 or \
56 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.2 or \
57 | self.position_outdated(symbol):
58 | self.Liquidate(symbol)
59 | if self.ObjectStore.ContainsKey(str(symbol)):
60 | self.ObjectStore.Delete(str(symbol))
61 | else:
62 | if self.ActiveSecurities[symbol].Price == 0:
63 | continue
64 | position_size, position_value = self.calculate_position(symbol)
65 | if self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Buy) > position_value:
66 | self.MarketOrder(symbol, position_size)
67 | self.ObjectStore.Save(str(symbol), str(self.Time))
68 |
69 | def calculate_position(self, symbol):
70 | position_value = self.Portfolio.TotalPortfolioValue / 10
71 | return round(position_value / self.ActiveSecurities[symbol].Price), position_value
72 |
73 |
74 | class SymbolData:
75 | def __init__(self, history):
76 | self.rsi = RelativeStrengthIndex(3)
77 | self.ma = SimpleMovingAverage(50)
78 | self.ma_long = SimpleMovingAverage(150)
79 | self.bb = BollingerBands(21, 2)
80 |
81 | for data in history.itertuples():
82 | self.update(data.Index[1], data.close)
83 |
84 | def update(self, time, price):
85 | self.rsi.Update(time, price)
86 | self.ma.Update(time, price)
87 | self.ma_long.Update(time, price)
88 | self.bb.Update(time, price)
89 |
--------------------------------------------------------------------------------
/MeanReversionBBLong/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MeanReversionLongETF/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from datetime import timedelta
3 |
4 | import pandas as pd
5 | from AlgorithmImports import *
6 | # endregion
7 |
8 |
9 | class MeanReversionLongETF(QCAlgorithm):
10 | def Initialize(self):
11 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
12 | self.SetStartDate(2012, 1, 1)
13 | self.SetEndDate(2022, 1, 1)
14 | self.SetCash(10000)
15 | self.UniverseSettings.Resolution = Resolution.Daily
16 | self.averages = {}
17 | self._changes = None
18 | self.EQUITY_RISK_PC = 0.01
19 | tickers = (
20 | "XLE", "XLF", "XLU", "XLI", "GDX", "XLK", "XLV", "XLY", "XLP", "XLB", "XOP", "IYR", "XHB", "ITB", "VNQ",
21 | "GDXJ", "IYE", "OIH", "XME", "XRT", "SMH", "IBB", "KBE", "KRE", "XTL",
22 | )
23 | symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in tickers]
24 | self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
25 | self.open_positions = {}
26 |
27 | def filter_symbol(self, symbol, data: pd.Series):
28 | price = self.ActiveSecurities[symbol].Price
29 | if symbol not in self.averages or self.averages[symbol].is_outdated(self.Time):
30 | self.averages[symbol] = SymbolData(self.History(symbol, 50, Resolution.Daily), symbol)
31 | else:
32 | self.averages[symbol].update(data, self.Time)
33 | # Rule #1: Stock must be in long term uptrend (above 50 day)
34 | # Rule #4: 3 day RSI is below 30
35 | if not (self.averages[symbol].rsi.Current.Value < 30 and price > self.averages[symbol].ma.Current.Value):
36 | return
37 | # Rule #2: 7 day ADX above 45 (strong short term trend)
38 | if not self.averages[symbol].adx.Current.Value > 45:
39 | return
40 | natr = self.averages[symbol].atr.Current.Value / self.ActiveSecurities[symbol].Price
41 | # Rule #3: ATR% above 4
42 | if natr < 0.04:
43 | return
44 | return symbol
45 |
46 | def position_outdated(self, symbol) -> bool:
47 | """
48 | Checks if the position is too old, or if it's time isn't stored
49 | """
50 | if self.open_positions.get(symbol):
51 | return (self.Time - self.open_positions.get(symbol)).days >= 4
52 | return True
53 |
54 | def OnData(self, slice) -> None:
55 | tradeable = []
56 | for symbol in self.ActiveSecurities.Keys:
57 | if self.ActiveSecurities[symbol].Invested:
58 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.03 or \
59 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.2 or \
60 | self.position_outdated(symbol):
61 | self.Liquidate(symbol)
62 | elif slice.Bars.ContainsKey(symbol) and self.filter_symbol(symbol, slice.Bars.get(symbol)):
63 | tradeable.append(symbol)
64 | for symbol in sorted(tradeable, key=lambda x: self.averages[x].rsi.Current.Value):
65 | position_size, position_value = self.calculate_position(symbol, self.averages[symbol].atr.Current.Value)
66 | if self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Buy) > position_value:
67 | self.MarketOrder(symbol, position_size)
68 | self.open_positions[symbol] = self.Time
69 |
70 | def calculate_position(self, symbol, atr):
71 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
72 | return position_size, position_size * self.ActiveSecurities[symbol].Price
73 |
74 |
75 | class SymbolData():
76 | def __init__(self, history, symbol):
77 | self.adx = AverageDirectionalIndex(7)
78 | self.atr = AverageTrueRange(10)
79 | self.ma = SimpleMovingAverage(50)
80 | self.rsi = RelativeStrengthIndex(3)
81 | self.symbol = symbol
82 | self.time = None
83 |
84 | for data in history.itertuples():
85 | self.update(data)
86 |
87 | def update(self, data, time=None):
88 | time = time or data.Index[1]
89 | trade_bar = TradeBar(time, self.symbol, data.open, data.high, data.low, data.close, data.volume, timedelta(1))
90 | self.adx.Update(trade_bar)
91 | self.atr.Update(trade_bar)
92 | self.rsi.Update(time, data.close)
93 | self.ma.Update(time, data.close)
94 | self.time = time
95 |
96 | def is_outdated(self, time):
97 | return (time - self.ma.Current.Time).days > 1
98 |
--------------------------------------------------------------------------------
/MeanReversionLongETF/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MeanReversionMaLong/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 | from dateutil.parser import parse
5 |
6 |
7 | class MeanReversionMaLong(QCAlgorithm):
8 | def Initialize(self):
9 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
10 | self.SetStartDate(2012, 1, 1)
11 | self.SetEndDate(2022, 1, 1)
12 | self.SetCash(10000)
13 | self.UniverseSettings.Resolution = Resolution.Daily
14 | self.AddUniverse(self.coarse_selection)
15 | self.symbol_data = {}
16 |
17 | def coarse_selection(self, coarse):
18 | stocks = []
19 | coarse = sorted(
20 | [stock for stock in coarse if
21 | stock.DollarVolume > 2500000 and stock.Price > 1 and stock.Market == Market.USA and stock.HasFundamentalData],
22 | key=lambda x: x.DollarVolume, reverse=True
23 | )[:500]
24 | for stock in coarse:
25 | symbol = stock.Symbol
26 | if symbol not in self.symbol_data:
27 | self.symbol_data[symbol] = SymbolData(self.History(stock.Symbol, 200, Resolution.Daily))
28 | else:
29 | self.symbol_data[symbol].update(self.Time, stock.Price)
30 | # Rule #1: Trend template
31 | if not (stock.Price > self.symbol_data[symbol].ma.Current.Value >
32 | self.symbol_data[symbol].ma_long.Current.Value > self.symbol_data[symbol].ma_200.Current.Value):
33 | continue
34 | ema = self.symbol_data[symbol].ema.Current.Value
35 | # Rule #2: 21 EMA must be above the 50 day
36 | if not ema > self.symbol_data[symbol].ma.Current.Value:
37 | continue
38 | # Rule #3: 7 EMA must be increasing
39 | if not self.symbol_data[symbol].strong_short_term_trend:
40 | continue
41 | # Rule #4: Price at or within 2% below EMA.
42 | if not ema >= stock.Price >= ema * 0.98:
43 | continue
44 | stocks.append(stock)
45 | symbols = [stock.Symbol for stock in sorted(stocks, key=lambda x: self.symbol_data[x.Symbol].roc.Current.Value)]
46 | return symbols
47 |
48 | def position_outdated(self, symbol) -> bool:
49 | """
50 | Checks if the position is too old, or if it's time isn't stored
51 | """
52 | if self.ObjectStore.ContainsKey(str(symbol)):
53 | return (self.Time - parse(self.ObjectStore.Read(str(symbol)))).days >= 4
54 | return True
55 |
56 | def OnData(self, slice) -> None:
57 | for symbol in self.ActiveSecurities.Keys:
58 | if self.ActiveSecurities[symbol].Invested:
59 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.3 or \
60 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.05 or \
61 | self.position_outdated(symbol):
62 | self.Liquidate(symbol)
63 | if self.ObjectStore.ContainsKey(str(symbol)):
64 | self.ObjectStore.Delete(str(symbol))
65 | else:
66 | if self.ActiveSecurities[symbol].Price == 0:
67 | continue
68 | position_size, position_value = self.calculate_position(symbol)
69 | if self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Buy) > position_value:
70 | self.MarketOrder(symbol, position_size)
71 | self.ObjectStore.Save(str(symbol), str(self.Time))
72 |
73 | def calculate_position(self, symbol):
74 | position_value = self.Portfolio.TotalPortfolioValue / 10
75 | return round(position_value / self.ActiveSecurities[symbol].Price), position_value
76 |
77 |
78 | class SymbolData:
79 | def __init__(self, history):
80 | self.ema = ExponentialMovingAverage(21)
81 | self.ema_short = ExponentialMovingAverage(7)
82 | self.ema_short_window = RollingWindow[float](3)
83 | self.ma = SimpleMovingAverage(50)
84 | self.ma_long = SimpleMovingAverage(150)
85 | self.ma_200 = SimpleMovingAverage(200)
86 | self.roc = RateOfChange(300)
87 |
88 | for data in history.itertuples():
89 | self.update(data.Index[1], data.close)
90 |
91 | def update(self, time, price):
92 | self.ema.Update(time, price)
93 | self.ema_short.Update(time, price)
94 | self.ema_short_window.Add(self.ema_short.Current.Value)
95 | self.ma.Update(time, price)
96 | self.ma_long.Update(time, price)
97 | self.ma_200.Update(time, price)
98 | self.roc.Update(time, price)
99 |
100 | @property
101 | def strong_short_term_trend(self):
102 | return self.ema_short.Current.Value > self.ema_short_window[2]
103 |
--------------------------------------------------------------------------------
/MeanReversionMaLong/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MomentumETF/main.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import *
2 | from datetime import timedelta
3 |
4 |
5 | class SymbolIndicators:
6 | def __init__(self, history) -> None:
7 | self.bollinger = BollingerBands(21, 2)
8 | self.keltner = KeltnerChannels(21, 2)
9 | self.donchian = DonchianChannel(21, 2)
10 | self.mfi = MoneyFlowIndex(5)
11 | self.atr = AverageTrueRange(21)
12 |
13 | for data in history.itertuples():
14 | trade_bar = TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
15 | self.update(trade_bar)
16 |
17 | def update(self, trade_bar):
18 | self.bollinger.Update(trade_bar.EndTime, trade_bar.Close)
19 | self.keltner.Update(trade_bar)
20 | self.donchian.Update(trade_bar)
21 | self.mfi.Update(trade_bar)
22 | self.atr.Update(trade_bar)
23 |
24 | @property
25 | def ready(self):
26 | return all((
27 | self.bollinger.IsReady,
28 | self.keltner.IsReady,
29 | self.donchian.IsReady,
30 | self.mfi.IsReady,
31 | self.atr.IsReady,
32 | ))
33 |
34 | @property
35 | def lowest_upper_band(self):
36 | return min((
37 | self.bollinger.UpperBand.Current.Value,
38 | self.keltner.UpperBand.Current.Value,
39 | self.donchian.UpperBand.Current.Value,
40 | ))
41 |
42 | @property
43 | def highest_lower_band(self):
44 | return max((
45 | self.bollinger.LowerBand.Current.Value,
46 | self.keltner.LowerBand.Current.Value,
47 | self.donchian.LowerBand.Current.Value,
48 | ))
49 |
50 |
51 | class MomentumETF(QCAlgorithm):
52 | def Initialize(self):
53 | self.SetStartDate(2002, 1, 1)
54 | self.SetEndDate(2022, 11, 1)
55 | self.SetCash(10000)
56 | self.SetWarmUp(timedelta(21), Resolution.Daily)
57 | self.UniverseSettings.Resolution = Resolution.Daily
58 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
59 | self.EQUITY_RISK_PC = 0.01
60 | tickers = [
61 | "GXLE",
62 | "GXLK",
63 | "GXLV",
64 | "GXLF",
65 | "SXLP",
66 | "SXLI",
67 | "GXLC",
68 | "SXLY",
69 | "SXLB",
70 | "SXLU",
71 | ] if self.LiveMode else [
72 | "XLE",
73 | "XLK",
74 | "XLV",
75 | "XLF",
76 | "XLP",
77 | "XLI",
78 | "XLC",
79 | "XLY",
80 | "XLB",
81 | "XLU",
82 | ]
83 | self.symbol_map = {}
84 | self.warm_up_buy_signals = set()
85 | for ticker in tickers:
86 | self.AddEquity(ticker, Resolution.Daily)
87 |
88 | def live_log(self, msg):
89 | if self.LiveMode:
90 | self.Log(msg)
91 |
92 | def OnData(self, data):
93 | uninvested = []
94 | self.Debug(f"{self.Time} - {','.join([symbol.Value for symbol in self.warm_up_buy_signals])}")
95 | for symbol in self.ActiveSecurities.Keys:
96 | if not data.Bars.ContainsKey(symbol):
97 | self.Debug("symbol not in data")
98 | continue
99 | if symbol not in self.symbol_map:
100 | self.symbol_map[symbol] = SymbolIndicators(
101 | self.History(symbol, 21, Resolution.Daily)
102 | )
103 | else:
104 | self.symbol_map[symbol].update(data.Bars[symbol])
105 | if not self.symbol_map[symbol].ready:
106 | self.Debug("indicators not ready")
107 | continue
108 | if not self.ActiveSecurities[symbol].Invested:
109 | uninvested.append(symbol)
110 | if symbol in self.warm_up_buy_signals and self.sell_signal(symbol, data):
111 | self.Debug(f"removing warmed up buy signal: {symbol.Value}")
112 | self.warm_up_buy_signals.remove(symbol)
113 | elif self.sell_signal(symbol, data):
114 | self.Liquidate(symbol)
115 | uninvested = sorted(
116 | uninvested,
117 | key=lambda symbol: self.symbol_map[symbol].mfi.Current.Value,
118 | reverse=True,
119 | )
120 | for symbol in uninvested:
121 | if symbol in self.warm_up_buy_signals:
122 | self.Debug(f"buying warmed up symbol: {symbol.Value}")
123 | self.buy(symbol)
124 | continue
125 | if not self.symbol_map[symbol].mfi.Current.Value >= 80:
126 | continue
127 | self.Debug(f"overbought: {self.ActiveSecurities[symbol].Price} {self.symbol_map[symbol].lowest_upper_band}")
128 | if self.ActiveSecurities[symbol].Price >= self.symbol_map[symbol].lowest_upper_band:
129 | self.buy(symbol)
130 |
131 | def buy(self, symbol):
132 | if self.IsWarmingUp:
133 | self.Debug(f"adding symbol to warm up signals: {symbol.Value}")
134 | self.warm_up_buy_signals.add(symbol)
135 | else:
136 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value)
137 | position_value = position_size * self.ActiveSecurities[symbol].Price
138 | self.live_log(f"buying {symbol.Value}")
139 | if position_value < self.Portfolio.Cash:
140 | self.MarketOrder(symbol, position_size)
141 | if symbol in self.warm_up_buy_signals:
142 | self.warm_up_buy_signals.remove(symbol)
143 | self.Debug(f"symbol purchased and removed from warm up signals: {symbol.Value}")
144 | else:
145 | self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}")
146 |
147 | def sell_signal(self, symbol, slice):
148 | highest_lower_band = self.symbol_map[symbol].highest_lower_band
149 | mfi_oversold = self.symbol_map[symbol].mfi.Current.Value <= 20
150 | return slice.Bars[symbol].Low <= highest_lower_band and mfi_oversold
151 |
--------------------------------------------------------------------------------
/MomentumETF/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool\n","# For more information see https://www.quantconnect.com/docs/research/overview\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n","ema.plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.8.13"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/MonthlySectorRotation/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from AlgorithmImports import RateOfChangePercent, SharpeRatio, AverageTrueRange, AlphaModel, Extensions, Resolution,\
3 | Time, InsightDirection, InsightType, Insight, SimpleMovingAverage, RollingWindow, QCAlgorithm, BrokerageName, \
4 | CompositeAlphaModel, ImmediateExecutionModel, NullRiskManagementModel, ConfidenceWeightedPortfolioConstructionModel, \
5 | Expiry
6 |
7 |
8 | class ATRIndicators:
9 | def __init__(self, *args, **kwargs) -> None:
10 | self.atr = AverageTrueRange(21)
11 |
12 | def update(self, trade_bar):
13 | self.atr.Update(trade_bar)
14 |
15 | @property
16 | def ready(self):
17 | return self.atr.IsReady
18 |
19 | def __str__(self) -> str:
20 | return str(self.atr.Current.Value)
21 |
22 |
23 | class RocIndicators(ATRIndicators):
24 | def __init__(self, *args, **kwargs) -> None:
25 | super().__init__(*args, **kwargs)
26 | self.roc_10 = RateOfChangePercent(10)
27 | self.roc_20 = RateOfChangePercent(20)
28 |
29 | def update(self, trade_bar):
30 | super().update(trade_bar)
31 | self.roc_10.Update(trade_bar.EndTime, trade_bar.Close)
32 | self.roc_20.Update(trade_bar.EndTime, trade_bar.Close)
33 |
34 | @property
35 | def ready(self):
36 | return super().ready and all((
37 | self.roc_10.IsReady,
38 | self.roc_20.IsReady,
39 | ))
40 |
41 | @property
42 | def monthly_performance(self) -> float:
43 | return self.roc_10.Current.Value + (-2 * self.roc_20.Current.Value)
44 |
45 |
46 | class SharpeIndicators(ATRIndicators):
47 | def __init__(self, *args, **kwargs) -> None:
48 | super().__init__(*args, **kwargs)
49 | self.sharpe = SharpeRatio(kwargs['look_back'])
50 |
51 | def update(self, trade_bar):
52 | super().update(trade_bar)
53 | self.sharpe.Update(trade_bar.EndTime, trade_bar.Close)
54 |
55 | @property
56 | def ready(self):
57 | return super().ready and self.sharpe.IsReady
58 |
59 |
60 | class BaseAlpha(AlphaModel):
61 | def __init__(self, *args, **kwargs) -> None:
62 | self.equity_risk_pc = kwargs['equity_risk_pc']
63 | self.spy = kwargs['spy']
64 | self.spy_ma = SimpleMovingAverage(150)
65 |
66 | def update_spy(self, data):
67 | if not data.ContainsKey(self.spy) or data[self.spy] is None:
68 | return
69 | self.spy_ma.Update(data[self.spy].EndTime, data[self.spy].Close)
70 |
71 | def get_risk_management_insights(self, data):
72 | if data.ContainsKey(self.spy) and data[self.spy] is not None:
73 | if data[self.spy].Close < self.spy_ma.Current.Value:
74 | return [Insight(symbol, self.get_insight_period(data), InsightType.Price, InsightDirection.Flat, None, None) for symbol in self.symbols]
75 |
76 | def get_confidence_for_symbol(self, algorithm, data, symbol):
77 | position_size = (algorithm.Portfolio.TotalPortfolioValue * self.equity_risk_pc) / self.indicators_map[symbol].atr.Current.Value
78 | position_value = position_size * data[symbol].Close
79 | algorithm.Debug(f"CONFIDENCE: {algorithm.Time} {str(symbol)} {self.indicators_map[symbol]} {algorithm.Portfolio.TotalPortfolioValue}")
80 | return position_value / algorithm.Portfolio.TotalPortfolioValue
81 |
82 | def get_insight_period(self, data):
83 | """
84 | Implementing monthly rebalancing logic defined in this forum post:
85 | https://www.quantconnect.com/forum/discussion/10677/algorithm-framework-questions/p1
86 | """
87 | return Expiry.EndOfMonth(data.Time) - datetime.timedelta(seconds=1)
88 |
89 | def get_insight(self, algorithm, data, symbol, direction = InsightDirection.Up):
90 | confidence = self.get_confidence_for_symbol(algorithm, data, symbol)
91 | period = self.get_insight_period(data)
92 | # Insight.Price(symbol, period, direction, magnitude=None, confidence=None, sourceModel=None, weight=None)
93 | return Insight.Price(symbol, period, direction, None, confidence)
94 |
95 |
96 | class MonthlyRotation(BaseAlpha):
97 | """
98 | A U.S. sector rotation Momentum Strategy with a long lookback period
99 | """
100 | def __init__(self, *args, **kwargs):
101 | super().__init__(*args, **kwargs)
102 | self.symbols = kwargs['symbols']
103 | self.look_back = kwargs['look_back']
104 | self.num = kwargs['num']
105 | self.indicators_map = {}
106 | self.month = None
107 |
108 | def Update(self, algorithm, data):
109 | symbols = []
110 | for symbol in self.symbols:
111 | if symbol not in self.indicators_map:
112 | self.indicators_map[symbol] = SharpeIndicators(look_back=self.look_back)
113 | if not data.ContainsKey(symbol) or data[symbol] is None:
114 | continue
115 | self.indicators_map[symbol].update(data[symbol])
116 | if self.indicators_map[symbol].ready:
117 | symbols.append(symbol)
118 | if algorithm.Time.month == self.month:
119 | return []
120 | self.month = algorithm.Time.month
121 | if not symbols:
122 | return []
123 | risk_management = self.get_risk_management_insights(data)
124 | if risk_management is not None:
125 | return risk_management
126 | highest_sharpe = sorted(
127 | symbols,
128 | key=lambda symbol: self.indicators_map[symbol].sharpe.Current.Value,
129 | reverse=True,
130 | )[:self.num]
131 | return [self.get_insight(algorithm, data, symbol) for symbol in highest_sharpe]
132 |
133 |
134 | class BuyTheWorstMeanReversion(BaseAlpha):
135 | """
136 | A U.S. sector rotation Mean Reversion Strategy
137 | """
138 | def __init__(self, *args, **kwargs):
139 | super().__init__(*args, **kwargs)
140 | self.symbols = kwargs['symbols']
141 | self.indicators_map = {}
142 | self.month = None
143 |
144 | def Update(self, algorithm, data):
145 | symbols = []
146 | for symbol in self.symbols:
147 | if symbol not in self.indicators_map:
148 | self.indicators_map[symbol] = RocIndicators()
149 | if not data.ContainsKey(symbol) or data[symbol] is None:
150 | continue
151 | self.indicators_map[symbol].update(data[symbol])
152 | if self.indicators_map[symbol].ready:
153 | symbols.append(symbol)
154 | if algorithm.Time.month == self.month:
155 | return []
156 | self.month = algorithm.Time.month
157 | if not symbols:
158 | return []
159 | risk_management = self.get_risk_management_insights(data)
160 | if risk_management is not None:
161 | return risk_management
162 | worst_performer = sorted(
163 | symbols,
164 | key=lambda symbol: self.indicators_map[symbol].monthly_performance,
165 | reverse=True,
166 | )[0]
167 | return [self.get_insight(algorithm, data, worst_performer)]
168 |
169 |
170 | class MonthlySectorRotation(QCAlgorithm):
171 | def Initialize(self):
172 | self.SetStartDate(2002, 1, 1)
173 | self.SetEndDate(2022, 11, 1)
174 | self.SetCash(10000)
175 | self.SetWarmUp(datetime.timedelta(200), Resolution.Daily)
176 | self.UniverseSettings.Resolution = Resolution.Daily
177 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
178 | # self.Settings.RebalancePortfolioOnInsightChanges = True
179 | # self.Settings.RebalancePortfolioOnSecurityChanges = False
180 | self.equity_risk_pc = 0.02
181 | tickers = [
182 | "GXLE",
183 | "GXLK",
184 | "GXLV",
185 | "GXLF",
186 | "SXLP",
187 | "SXLI",
188 | "GXLC",
189 | "SXLY",
190 | "SXLB",
191 | "SXLU",
192 | ] if self.LiveMode else [
193 | "XLE",
194 | "XLK",
195 | "XLV",
196 | "XLF",
197 | "XLP",
198 | "XLI",
199 | "XLC",
200 | "XLY",
201 | "XLB",
202 | "XLU",
203 | ]
204 | hedge_tickers = ["", ""] if self.LiveMode else ["SPY", "SH"]
205 | symbols = [self.AddEquity(ticker, Resolution.Daily).Symbol for ticker in tickers]
206 | spy = self.AddEquity(hedge_tickers[0], Resolution.Daily).Symbol
207 | self.SetAlpha(
208 | CompositeAlphaModel(
209 | MonthlyRotation(symbols=symbols, look_back=198, num=1, spy=spy, equity_risk_pc=self.equity_risk_pc),
210 | MonthlyRotation(symbols=symbols, look_back=7, num=1, spy=spy, equity_risk_pc=self.equity_risk_pc),
211 | BuyTheWorstMeanReversion(symbols=symbols, spy=spy, equity_risk_pc=self.equity_risk_pc),
212 | )
213 | )
214 | self.SetPortfolioConstruction(ConfidenceWeightedPortfolioConstructionModel(lambda time: None))
215 | self.SetExecution(ImmediateExecutionModel())
216 | self.SetRiskManagement(NullRiskManagementModel())
217 |
--------------------------------------------------------------------------------
/MonthlySectorRotation/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/MultiNonCorrelatedAlphaStrategy/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from AlgorithmImports import Resolution, Time, Extensions, InsightDirection, Insight, InsightType,\
3 | AlphaModel, AverageDirectionalIndex, AverageTrueRange, SimpleMovingAverage, RelativeStrengthIndex,\
4 | BrokerageName, QCAlgorithm, QC500UniverseSelectionModel, CompositeAlphaModel, EqualWeightingPortfolioConstructionModel,\
5 | ImmediateExecutionModel, MaximumDrawdownPercentPerSecurity, MaximumUnrealizedProfitPercentPerSecurity, RateOfChangePercent, \
6 | NullRiskManagementModel
7 |
8 |
9 | class BaseAlphaModel(AlphaModel):
10 | def __init__(self, *args, **kwargs):
11 | self.month = None
12 | self.prediction_interval = Time.Multiply(Extensions.ToTimeSpan(Resolution.Daily), 5)
13 | self.symbols = {}
14 | self.spy = kwargs['spy']
15 | self.equity_risk_pc = kwargs['equity_risk_pc']
16 | self.spy_data = None
17 | self.resolution = Resolution.Daily
18 | self.last_month = -1
19 |
20 | def get_confidence_for_symbol(self, algorithm, data, symbol):
21 | position_size = (algorithm.Portfolio.TotalPortfolioValue * self.equity_risk_pc) / self.symbols[symbol].atr.Current.Value
22 | position_value = position_size * data[symbol].Close
23 | return position_value / algorithm.Portfolio.TotalPortfolioValue
24 |
25 | def spy_downtrending(self, algorithm, data):
26 | if self.spy_data is None:
27 | self.spy_data = SpyData(algorithm, self.spy, self.resolution)
28 | if data.ContainsKey(self.spy) and data[self.spy] is not None:
29 | return data[self.spy].Close < self.spy_data.ma.Current.Value
30 | algorithm.Debug("SPY not in data")
31 | return True
32 |
33 |
34 | class MonthlyRateOfChangeTrendFollowingAlpha(BaseAlphaModel):
35 | def __init__(self, *args, **kwargs):
36 | super().__init__(*args, **kwargs)
37 | self.roc_lookback = kwargs['roc_lookback']
38 | self.atr_lookback = kwargs['atr_lookback']
39 |
40 | def Update(self, algorithm, data):
41 | if algorithm.Time.month == self.last_month:
42 | return []
43 | if self.spy_downtrending(algorithm, data):
44 | return []
45 | self.last_month = algorithm.Time.month
46 | securities = [symbol for symbol in self.symbols.keys() \
47 | if data.ContainsKey(symbol) and data[symbol] is not None \
48 | and self.symbols[symbol].atrp(data[symbol].Close) <= 5]
49 | top_performers = sorted(
50 | securities,
51 | key=lambda symbol: self.symbols[symbol].roc.Current.Value,
52 | reverse=True,
53 | )[:10]
54 | return [self.get_insight(algorithm, data, symbol) for symbol in top_performers]
55 |
56 | def OnSecuritiesChanged(self, algorithm, changes):
57 | for added in changes.AddedSecurities:
58 | self.symbols[added.Symbol] = MonthlyRateOfChangeTrendFollowingData(algorithm, added, self.resolution, roc_lookback=self.roc_lookback, atr_lookback=self.atr_lookback)
59 |
60 | for removed in changes.RemovedSecurities:
61 | data = self.symbols.pop(removed.Symbol, None)
62 | if data is not None:
63 | algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
64 |
65 | def get_insight(self, algorithm, data, symbol):
66 | confidence = self.get_confidence_for_symbol(algorithm, data, symbol)
67 | return Insight(symbol, self.prediction_interval, InsightType.Price, InsightDirection.Up, confidence, None)
68 |
69 |
70 | class MonthlyRateOfChangeTrendFollowingData:
71 | def __init__(self, algorithm, security, resolution, roc_lookback = 200, atr_lookback = 21):
72 | self.security = security
73 | self.roc = RateOfChangePercent(roc_lookback)
74 | self.atr = AverageTrueRange(atr_lookback)
75 | self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
76 | algorithm.RegisterIndicator(security.Symbol, self.roc, self.Consolidator)
77 | algorithm.RegisterIndicator(security.Symbol, self.atr, self.Consolidator)
78 | algorithm.WarmUpIndicator(security.Symbol, self.roc, resolution)
79 | algorithm.WarmUpIndicator(security.Symbol, self.atr, resolution)
80 |
81 | def atrp(self, close):
82 | return (self.atr.Current.Value / close) * 100
83 |
84 |
85 | class SpyData:
86 | def __init__(self, algorithm, symbol, resolution):
87 | self.ma = SimpleMovingAverage(200)
88 | self.Consolidator = algorithm.ResolveConsolidator(symbol, resolution)
89 | algorithm.RegisterIndicator(symbol, self.ma, self.Consolidator)
90 | algorithm.WarmUpIndicator(symbol, self.ma, resolution)
91 |
92 |
93 | class MyQC500(QC500UniverseSelectionModel):
94 | """
95 | Optimized to select the top 250 stocks by dollar volume
96 | """
97 | numberOfSymbolsCoarse = 250
98 |
99 |
100 | class MultiNonCorrelatedAlphaStrategy(QCAlgorithm):
101 | def Initialize(self):
102 | self.SetStartDate(2020, 1, 1)
103 | self.SetCash(100000)
104 | self.SetWarmUp(datetime.timedelta(200), Resolution.Daily)
105 | self.UniverseSettings.Resolution = Resolution.Daily
106 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
107 | self.SetUniverseSelection(MyQC500())
108 | self.symbols = {}
109 | self.EQUITY_RISK_PC = 0.01
110 | self.spy = self.AddEquity("SPY", Resolution.Daily)
111 | self.SetAlpha(
112 | CompositeAlphaModel(
113 | MonthlyRateOfChangeTrendFollowingAlpha(spy=self.spy.Symbol, equity_risk_pc=self.EQUITY_RISK_PC, roc_lookback=200, atr_lookback=21),
114 | )
115 | )
116 | self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(self.DateRules.MonthStart()))
117 | self.SetExecution(ImmediateExecutionModel())
118 | self.SetRiskManagement(NullRiskManagementModel())
119 | self.Settings.RebalancePortfolioOnInsightChanges = False
120 | self.Settings.RebalancePortfolioOnSecurityChanges = False
121 |
--------------------------------------------------------------------------------
/MultiNonCorrelatedAlphaStrategy/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/MultiStrategyETF/main.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import *
2 | import datetime
3 | from typing import Dict, List, Tuple
4 |
5 |
6 | SYMBOLS = 'symbols'
7 | POSITIONS = 'positions'
8 | REBALANCED_DATE = 'rebalanced_date'
9 |
10 |
11 | class SymbolIndicators:
12 | def __init__(self) -> None:
13 | self.sharpe_long = SharpeRatio(198)
14 | self.sharpe_short = SharpeRatio(7)
15 | self.atr = AverageTrueRange(21)
16 | self.roc_10 = RateOfChange(10)
17 | self.roc_20 = RateOfChange(20)
18 |
19 | def update(self, trade_bar):
20 | self.sharpe_long.Update(trade_bar.EndTime, trade_bar.High)
21 | self.atr.Update(trade_bar)
22 |
23 | @property
24 | def ready(self):
25 | return all((
26 | self.sharpe_long.IsReady,
27 | self.atr.IsReady,
28 | ))
29 |
30 | @property
31 | def monthly_performance(self) -> float:
32 | return self.roc_10.Current.Value + (-2 * self.roc_20.Current.Value)
33 |
34 |
35 | class BaseAlpha(object):
36 | EQUITY_RISK_PC = 0.01
37 |
38 | def __init__(self, algorithm: QCAlgorithm, indicators: Dict[Symbol, SymbolIndicators], bars, symbols: List[Symbol]) -> None:
39 | self.algorithm = algorithm
40 | self.indicators = indicators
41 | self.bars = bars
42 | self.symbols = symbols
43 |
44 | def get_signals(self) -> int:
45 | raise NotImplementedError()
46 |
47 | def calculate_position_size(self, atr):
48 | return round((self.algorithm.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
49 |
50 | @property
51 | def positions(self) -> Dict[Symbol, int]:
52 | return self.algorithm.alpha_map[self.__class__][POSITIONS]
53 |
54 | @property
55 | def rebalanced_date(self) -> Optional[datetime.datetime]:
56 | return self.algorithm.alpha_map[self.__class__][REBALANCED_DATE]
57 |
58 | def rebalancing_due(self) -> bool:
59 | """Only rebalance if nothing has changed in previous 30 days"""
60 | rebalanced: Optional[datetime.datetime] = self.rebalanced_date
61 | if not rebalanced:
62 | return True
63 | return (datetime.datetime.now() - rebalanced) > datetime.timedelta(days=30)
64 |
65 |
66 | class MonthlyRotation(BaseAlpha):
67 | """
68 | A U.S. sector rotation Momentum Strategy with a long lookback period
69 | """
70 | indicator_key = 'sharpe_long'
71 |
72 | def get_signals(self) -> List[Tuple[Symbol, int]]:
73 | signals = []
74 | if not self.rebalancing_due:
75 | return []
76 | highest_sharpe = sorted(
77 | self.symbols,
78 | key=lambda symbol: getattr(self.indicators[symbol], self.indicator_key),
79 | reverse=True,
80 | )[:3]
81 | for symbol in self.symbols:
82 | if symbol not in self.positions:
83 | if symbol in highest_sharpe:
84 | signals.append((symbol, self.calculate_position_size(self.indicators[symbol].atr.Current.Value)))
85 | elif symbol not in highest_sharpe:
86 | existing_position_size = self.positions[symbol]
87 | signals.append((symbol, -1 * existing_position_size))
88 | return signals
89 |
90 |
91 | class ShortMonthlyRotation(MonthlyRotation):
92 | indicator_key = 'sharpe_short'
93 |
94 |
95 | class BuyTheWorstMeanReversion(BaseAlpha):
96 |
97 | def get_signals(self) -> List[Tuple[Symbol, int]]:
98 | signals = []
99 | if not self.rebalancing_due or not self.symbols:
100 | return []
101 | worst_performer = sorted(
102 | self.symbols,
103 | key=lambda symbol: self.indicators[symbol].monthly_performance,
104 | reverse=True,
105 | )[0]
106 | for symbol in self.symbols:
107 | if symbol not in self.positions:
108 | if symbol == worst_performer:
109 | signals.append((symbol, self.calculate_position_size(self.indicators[symbol].atr.Current.Value)))
110 | elif symbol != worst_performer:
111 | existing_position_size = self.positions[symbol]
112 | signals.append((symbol, -1 * existing_position_size))
113 | return signals
114 |
115 |
116 | class MultiStrategyETF(QCAlgorithm):
117 | def Initialize(self):
118 | self.SetStartDate(2002, 1, 1)
119 | self.SetEndDate(2022, 11, 1)
120 | self.SetCash(10000)
121 | self.SetWarmUp(timedelta(200), Resolution.Daily)
122 | self.UniverseSettings.Resolution = Resolution.Daily
123 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
124 | tickers = [
125 | "GXLE",
126 | "GXLK",
127 | "GXLV",
128 | "GXLF",
129 | "SXLP",
130 | "SXLI",
131 | "GXLC",
132 | "SXLY",
133 | "SXLB",
134 | "SXLU",
135 | ] if self.LiveMode else [
136 | "XLE",
137 | "XLK",
138 | "XLV",
139 | "XLF",
140 | "XLP",
141 | "XLI",
142 | "XLC",
143 | "XLY",
144 | "XLB",
145 | "XLU",
146 | ]
147 | self.symbol_map = {}
148 | self.alpha_map = {
149 | MonthlyRotation: {
150 | SYMBOLS: tickers,
151 | POSITIONS: {},
152 | REBALANCED_DATE: None,
153 | },
154 | ShortMonthlyRotation: {
155 | SYMBOLS: tickers,
156 | POSITIONS: {},
157 | REBALANCED_DATE: None,
158 | },
159 | BuyTheWorstMeanReversion: {
160 | SYMBOLS: tickers,
161 | POSITIONS: {},
162 | REBALANCED_DATE: None,
163 | },
164 | }
165 | for ticker in tickers:
166 | self.AddEquity(ticker, Resolution.Daily)
167 |
168 | def update_indicators(self, data) -> Iterator[Symbol]:
169 | for symbol in self.ActiveSecurities.Keys:
170 | if not data.Bars.ContainsKey(symbol):
171 | continue
172 | if symbol not in self.symbol_map:
173 | self.symbol_map[symbol] = SymbolIndicators()
174 | self.symbol_map[symbol].update(data.Bars[symbol])
175 | if not self.symbol_map[symbol].ready:
176 | continue
177 | yield symbol
178 |
179 | def OnData(self, data):
180 | symbols = list(self.update_indicators(data))
181 | if not symbols:
182 | return
183 | for Alpha in self.alpha_map.keys():
184 | allowed_symbols = [symbol for symbol in symbols if str(symbol.Value) in self.alpha_map[Alpha][SYMBOLS]]
185 | for signal in Alpha(self, self.symbol_map, data.Bars, allowed_symbols).get_signals():
186 | if signal:
187 | symbol, size = signal
188 | if size > 0:
189 | value = size * self.ActiveSecurities[symbol].Price
190 | if value < self.Portfolio.Cash:
191 | self.MarketOrder(symbol, size)
192 | else:
193 | self.MarketOrder(symbol, size)
194 |
--------------------------------------------------------------------------------
/MultiStrategyETF/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/New High Breakout/main.py:
--------------------------------------------------------------------------------
1 | #region imports
2 | from datetime import timedelta
3 |
4 | from AlgorithmImports import *
5 | #endregion
6 |
7 |
8 | class NewHighBreakout(QCAlgorithm):
9 |
10 | def Initialize(self):
11 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
12 | self.SetStartDate(2012, 1, 1)
13 | self.SetEndDate(2022, 1, 1)
14 | self.SetCash(10000)
15 | self.UniverseSettings.Resolution = Resolution.Daily
16 | self.spy = self.AddEquity("SPY", Resolution.Daily)
17 | self.SetBenchmark("SPY")
18 | self.AddUniverse(self.coarse_selection)
19 | self.averages = {}
20 | self._changes = None
21 | self.EQUITY_RISK_PC = 0.01
22 | self.open_positions = {}
23 |
24 | def update_spy(self):
25 | if self.spy.Symbol not in self.averages:
26 | self.averages[self.spy.Symbol] = SPYSelectionData(self.History(self.spy.Symbol, 200, Resolution.Daily))
27 | else:
28 | self.averages[self.spy.Symbol].update(self.Time, self.spy.Price)
29 |
30 | def coarse_selection(self, coarse):
31 | self.update_spy()
32 | stocks = []
33 | coarse = [stock for stock in coarse if stock.Price > 10 and stock.Market == Market.USA and stock.HasFundamentalData]
34 | for stock in sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)[:100]:
35 | symbol = stock.Symbol
36 | if symbol == self.spy.Symbol:
37 | continue
38 | if symbol not in self.averages:
39 | self.averages[symbol] = SelectionData(self.History(symbol, 50, Resolution.Daily))
40 | self.averages[symbol].update(self.Time, stock.Price)
41 | if not (stock.Price > self.averages[symbol].ma.Current.Value):
42 | continue
43 | stocks.append(symbol)
44 | return stocks
45 |
46 | @property
47 | def spy_downtrending(self) -> bool:
48 | return self.averages[self.spy.Symbol].ma.Current.Value > self.spy.Price
49 |
50 | def position_outdated(self, symbol) -> bool:
51 | """
52 | Checks if the position is too old, or if it's time isn't stored
53 | """
54 | if self.open_positions.get(symbol):
55 | return (self.Time - self.open_positions.get(symbol)).days >= 120
56 | return True
57 |
58 | def OnData(self, slice) -> None:
59 | if self.spy_downtrending:
60 | for security in self.Portfolio.Securities.keys():
61 | self.Liquidate(self.Portfolio.Securities[security].Symbol)
62 | return
63 | for symbol in self.ActiveSecurities.Keys:
64 | if symbol == self.spy.Symbol:
65 | continue
66 | if self.ActiveSecurities[symbol].Invested:
67 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.20 or \
68 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.08 or \
69 | self.ActiveSecurities[symbol].Close < self.averages[symbol].ma.Current.Value or \
70 | self.position_outdated(symbol):
71 | self.Liquidate(symbol)
72 | else:
73 | high = Maximum(100)
74 | atr = AverageTrueRange(21)
75 | for data in self.History(symbol, 100, Resolution.Daily).itertuples():
76 | if self.Time != data.Index[1]:
77 | high.Update(data.Index[1], data.high)
78 | atr.Update(
79 | TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
80 | )
81 | if high.Current.Value * 1.05 > self.ActiveSecurities[symbol].Close >= high.Current.Value:
82 | if high.PeriodsSinceMaximum >= 25:
83 | position_size = self.calculate_position_size(atr.Current.Value)
84 | position_value = position_size * self.ActiveSecurities[symbol].Price
85 | if position_value < self.Portfolio.Cash:
86 | self.MarketOrder(symbol, position_size)
87 | self.open_positions[symbol] = self.Time
88 |
89 | def calculate_position_size(self, atr):
90 | return round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
91 |
92 |
93 | class SelectionData():
94 | def __init__(self, history):
95 | self.ma = SimpleMovingAverage(50)
96 |
97 | for data in history.itertuples():
98 | self.update(data.Index[1], data.close)
99 |
100 | def update(self, time, price):
101 | self.ma.Update(time, price)
102 |
103 |
104 | class SPYSelectionData():
105 | def __init__(self, history):
106 | self.ma = SimpleMovingAverage(200)
107 |
108 | for data in history.itertuples():
109 | self.ma.Update(data.Index[1], data.close)
110 |
111 | def update(self, time, price):
112 | self.ma.Update(time, price)
113 |
--------------------------------------------------------------------------------
/New High Breakout/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, datetime(2014,1,1), datetime(2015,1,1), Resolution.Daily)\n","\n","history['close'].plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/NewHighBreakoutIBD50/main.py:
--------------------------------------------------------------------------------
1 | #region imports
2 | from datetime import timedelta
3 |
4 | from AlgorithmImports import *
5 | #endregion
6 |
7 |
8 | class NewHighBreakoutIBD50(QCAlgorithm):
9 |
10 | def Initialize(self):
11 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
12 | self.SetStartDate(2022, 4, 1)
13 | self.SetEndDate(2022, 7, 1)
14 | self.SetCash(10000)
15 | self.UniverseSettings.Resolution = Resolution.Daily
16 | self.spy = self.AddEquity("SPY", Resolution.Daily)
17 | self.SetBenchmark("SPY")
18 | self.AddUniverse(self.coarse_selection)
19 | self.averages = {}
20 | self._changes = None
21 | self.EQUITY_RISK_PC = 0.01
22 | self.open_positions = {}
23 | self.holdings_symbols = []
24 |
25 | def update_holdings_symbols(self):
26 | dataframe = pd.read_csv('https://www.innovatoretfs.com/etf/xt_holdings.csv')
27 | if not dataframe.empty:
28 | dataframe = dataframe[(dataframe['Account'] == 'FFTY')]
29 | self.holdings_symbols = dataframe.StockTicker.values
30 | self.holdings_symbols = []
31 |
32 | def update_spy(self):
33 | if self.spy.Symbol not in self.averages:
34 | self.averages[self.spy.Symbol] = SPYSelectionData(self.History(self.spy.Symbol, 200, Resolution.Daily))
35 | else:
36 | self.averages[self.spy.Symbol].update(self.Time, self.spy.Price)
37 |
38 | @property
39 | def is_monday(self):
40 | return self.Time.weekday() == 0
41 |
42 | def coarse_selection(self, coarse):
43 | self.update_spy()
44 | if self.is_monday or not self.holdings_symbols:
45 | self.update_holdings_symbols()
46 | stocks = []
47 | for stock in coarse:
48 | symbol = stock.Symbol
49 | if symbol == self.spy.Symbol or symbol not in self.holdings_symbols:
50 | continue
51 | if symbol not in self.averages:
52 | self.averages[symbol] = SelectionData(self.History(symbol, 100, Resolution.Daily))
53 | self.averages[symbol].update(self.Time, stock.Price)
54 | if not (stock.Price > self.averages[symbol].ma.Current.Value):
55 | continue
56 | if not self.averages[symbol].roc.Current.Value > 0:
57 | continue
58 | stocks.append(symbol)
59 | return sorted(stocks, key=lambda x: self.averages[x].roc.Current.Value, reverse=True)
60 |
61 | @property
62 | def spy_downtrending(self) -> bool:
63 | return self.averages[self.spy.Symbol].ma.Current.Value > self.spy.Price
64 |
65 | def position_outdated(self, symbol) -> bool:
66 | """
67 | Checks if the position is too old, or if it's time isn't stored
68 | """
69 | if self.open_positions.get(symbol):
70 | return (self.Time - self.open_positions.get(symbol)).days >= 120
71 | return True
72 |
73 | def OnData(self, slice) -> None:
74 | if self.spy_downtrending:
75 | for security in self.Portfolio.Securities.keys():
76 | self.Liquidate(self.Portfolio.Securities[security].Symbol)
77 | return
78 | for symbol in self.ActiveSecurities.Keys:
79 | if symbol == self.spy.Symbol:
80 | continue
81 | if self.ActiveSecurities[symbol].Invested:
82 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.20 or \
83 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.08 or \
84 | self.ActiveSecurities[symbol].Close < self.averages[symbol].ma.Current.Value or \
85 | self.position_outdated(symbol):
86 | self.Liquidate(symbol)
87 | else:
88 | high = Maximum(100)
89 | atr = AverageTrueRange(21)
90 | for data in self.History(symbol, 100, Resolution.Daily).itertuples():
91 | if self.Time != data.Index[1]:
92 | high.Update(data.Index[1], data.high)
93 | atr.Update(
94 | TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
95 | )
96 | if high.Current.Value * 1.05 > self.ActiveSecurities[symbol].Close >= high.Current.Value:
97 | if high.PeriodsSinceMaximum >= 25:
98 | position_size = self.calculate_position_size(atr.Current.Value)
99 | position_value = position_size * self.ActiveSecurities[symbol].Price
100 | if position_value < self.Portfolio.Cash:
101 | self.MarketOrder(symbol, position_size)
102 | self.open_positions[symbol] = self.Time
103 |
104 | def calculate_position_size(self, atr):
105 | return round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
106 |
107 |
108 | class SelectionData():
109 | def __init__(self, history):
110 | self.ma = SimpleMovingAverage(50)
111 | self.roc = RateOfChangePercent(100)
112 |
113 | for data in history.itertuples():
114 | self.update(data.Index[1], data.close)
115 |
116 | def update(self, time, price):
117 | self.ma.Update(time, price)
118 | self.roc.Update(time, price)
119 |
120 |
121 | class SPYSelectionData():
122 | def __init__(self, history):
123 | self.ma = SimpleMovingAverage(200)
124 |
125 | for data in history.itertuples():
126 | self.ma.Update(data.Index[1], data.close)
127 |
128 | def update(self, time, price):
129 | self.ma.Update(time, price)
130 |
--------------------------------------------------------------------------------
/NewHighBreakoutIBD50/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, datetime(2014,1,1), datetime(2015,1,1), Resolution.Daily)\n","\n","history['close'].plot()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.8"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/Powertrend/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 |
5 | class SymbolIndicators:
6 | def __init__(self) -> None:
7 | self.ma = SimpleMovingAverage(50)
8 | self.ema = ExponentialMovingAverage(21)
9 | self.atr = AverageTrueRange(21)
10 | self.low_above_ema = RollingWindow[bool](10)
11 | self.ema_above_ma = RollingWindow[bool](5)
12 | self.ma_window = RollingWindow[float](2)
13 |
14 | def update(self, trade_bar):
15 | self.ma.Update(trade_bar.EndTime, trade_bar.Close)
16 | self.ema.Update(trade_bar.EndTime, trade_bar.Close)
17 | self.atr.Update(trade_bar)
18 | self.low_above_ema.Add(self.ema.IsReady and trade_bar.Low > self.ema.Current.Value)
19 | self.ema_above_ma.Add(self.ema.IsReady and self.ma.IsReady and self.ema.Current.Value > self.ma.Current.Value)
20 | if self.ma.IsReady:
21 | self.ma_window.Add(self.ma.Current.Value)
22 |
23 | @property
24 | def ready(self):
25 | return all((
26 | self.ma.IsReady,
27 | self.ema.IsReady,
28 | self.low_above_ema.IsReady,
29 | self.ema_above_ma.IsReady,
30 | self.ma_window.IsReady,
31 | ))
32 |
33 | @property
34 | def powertrend_on(self):
35 | return all(list(self.low_above_ema) + list(self.ema_above_ma)) and self.ma_window[0] > self.ma_window[1]
36 |
37 | @property
38 | def powertrend_off(self):
39 | return self.ema.Current.Value < self.ma.Current.Value
40 |
41 | class Powertrend(QCAlgorithm):
42 |
43 | def Initialize(self):
44 | self.SetStartDate(2018, 1, 1)
45 | self.SetCash(9000)
46 | self.SetWarmUp(timedelta(50), Resolution.Daily)
47 | self.EQUITY_RISK_PC = 0.01
48 | tickers = [
49 | "SPY",
50 | ]
51 | for ticker in tickers:
52 | self.AddEquity(ticker, Resolution.Daily)
53 | self.symbol_map = {}
54 |
55 | def OnData(self, data: Slice):
56 | for symbol in self.ActiveSecurities.Keys:
57 | if not data.Bars.ContainsKey(symbol):
58 | return
59 | if symbol not in self.symbol_map:
60 | self.symbol_map[symbol] = SymbolIndicators()
61 | self.symbol_map[symbol].update(data.Bars[symbol])
62 | if self.IsWarmingUp or not self.symbol_map[symbol].ready:
63 | continue
64 | if not self.Portfolio.Invested:
65 | if data.Bars[symbol].Close > data.Bars[symbol].Open and self.symbol_map[symbol].powertrend_on:
66 | self.buy(symbol)
67 | elif self.symbol_map[symbol].powertrend_off:
68 | self.Liquidate(symbol)
69 |
70 | def buy(self, symbol):
71 | position_size = (self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value
72 | position_value = position_size * self.ActiveSecurities[symbol].Price
73 | if position_value < self.Portfolio.Cash:
74 | self.MarketOrder(symbol, position_size)
75 |
--------------------------------------------------------------------------------
/Powertrend/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/Rate Of Change Rotation/main.py:
--------------------------------------------------------------------------------
1 | #region imports
2 | from AlgorithmImports import *
3 | #endregion
4 |
5 |
6 | class RocRotation(QCAlgorithm):
7 |
8 | def Initialize(self):
9 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
10 | self.SetStartDate(2012, 1, 1)
11 | self.SetEndDate(2022, 1, 1)
12 | self.SetCash(10000)
13 | self.UniverseSettings.Resolution = Resolution.Daily
14 | self.spy = self.AddEquity("SPY", Resolution.Daily)
15 | self.SetBenchmark("SPY")
16 | self.AddUniverse(self.coarse_selection)
17 | self.averages = {}
18 | self._changes = None
19 |
20 | def update_spy(self):
21 | if self.spy.Symbol not in self.averages:
22 | history = self.History(self.spy.Symbol, 200, Resolution.Daily)
23 | self.averages[self.spy.Symbol] = SPYSelectionData(history)
24 | self.averages[self.spy.Symbol].update(self.Time, self.spy.Price)
25 |
26 | def coarse_selection(self, coarse):
27 | self.update_spy()
28 | stocks = []
29 | for stock in sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)[:100]:
30 | symbol = stock.Symbol
31 | if symbol == self.spy.Symbol:
32 | continue
33 | if symbol not in self.averages:
34 | self.averages[symbol] = SelectionData(self.History(symbol, 200, Resolution.Daily))
35 | self.averages[symbol].update(self.Time, stock.Price)
36 | stocks.append(symbol)
37 | return sorted(stocks, key=lambda symbol: self.averages[symbol].roc, reverse=True)[:10]
38 |
39 | @property
40 | def spy_downtrending(self) -> bool:
41 | return self.averages[self.spy.Symbol].ma.Current.Value * .98 > self.spy.Price
42 |
43 | def OnData(self, data):
44 | if self.spy_downtrending:
45 | for security in self.Portfolio.Securities.keys():
46 | self.Liquidate(self.Portfolio.Securities[security].Symbol)
47 | return
48 | if not self.is_tuesday():
49 | return
50 | # if we have no changes, do nothing
51 | if self._changes is None: return
52 | # liquidate removed securities
53 | for security in self._changes.RemovedSecurities:
54 | if security.Invested:
55 | self.Liquidate(security.Symbol)
56 | for symbol in self.ActiveSecurities.Keys:
57 | if symbol == self.spy.Symbol:
58 | continue
59 | if self.ActiveSecurities[symbol].Invested:
60 | continue
61 | if not self.ActiveSecurities[symbol].Price > self.averages[symbol].ma.Current.Value:
62 | continue
63 | if not self.averages[symbol].rsi.Current.Value < 50:
64 | continue
65 | position_value = self.Portfolio.TotalPortfolioValue / 10
66 | if position_value < self.Portfolio.Cash:
67 | self.MarketOrder(symbol, int(position_value / self.ActiveSecurities[symbol].Price))
68 | self._changes = None
69 |
70 | def OnSecuritiesChanged(self, changes):
71 | self._changes = changes
72 |
73 | def is_tuesday(self):
74 | return self.Time.weekday() == 1
75 |
76 |
77 | class SelectionData():
78 | def __init__(self, history):
79 | self.rsi = RelativeStrengthIndex(3)
80 | self.roc = RateOfChangePercent(200)
81 | self.ma = SimpleMovingAverage(200)
82 |
83 | for data in history.itertuples():
84 | self.rsi.Update(data.Index[1], data.close)
85 | self.roc.Update(data.Index[1], data.close)
86 | self.ma.Update(data.Index[1], data.close)
87 |
88 | def is_ready(self):
89 | return self.rsi.IsReady and self.roc.IsReady and self.ma.IsReady
90 |
91 | def update(self, time, price):
92 | self.rsi.Update(time, price)
93 | self.roc.Update(time, price)
94 | self.ma.Update(time, price)
95 |
96 |
97 | class SPYSelectionData():
98 | def __init__(self, history):
99 | self.ma = SimpleMovingAverage(200)
100 |
101 | for data in history.itertuples():
102 | self.ma.Update(data.Index[1], data.close)
103 |
104 | def is_ready(self):
105 | return self.ma.IsReady
106 |
107 | def update(self, time, price):
108 | self.ma.Update(time, price)
109 |
--------------------------------------------------------------------------------
/Rate Of Change Rotation/research.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":["\n","
"]},{"cell_type":"code","execution_count":22,"metadata":{"vscode":{"languageId":"python"}},"outputs":[],"source":["# QuantBook Analysis Tool \n","# For more information see [https://www.quantconnect.com/docs/research/overview]\n","qb = QuantBook()\n","spy = qb.AddEquity(\"SPY\")\n","history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n","\n","# Indicator Analysis\n","bbdf = qb.Indicator(BollingerBands(30, 2), spy.Symbol, 360, Resolution.Daily)\n","bbdf.drop('standarddeviation', 1).plot()"]},{"cell_type":"code","execution_count":null,"metadata":{"vscode":{"languageId":"python"}},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"nbformat":4,"nbformat_minor":2}
2 |
--------------------------------------------------------------------------------
/RateOfChangeRotationETF/main.py:
--------------------------------------------------------------------------------
1 | # region imports
2 | from AlgorithmImports import *
3 | # endregion
4 |
5 | class SymbolIndicators:
6 | def __init__(self) -> None:
7 | self.roc = RateOfChangePercent(50)
8 | self.atr = AverageTrueRange(21)
9 | self.ma = SimpleMovingAverage(50)
10 |
11 | def update(self, trade_bar):
12 | self.roc.Update(trade_bar.EndTime, trade_bar.Close)
13 | self.atr.Update(trade_bar)
14 | self.ma.Update(trade_bar.EndTime, trade_bar.Close)
15 |
16 | @property
17 | def ready(self):
18 | return all((
19 | self.roc.IsReady,
20 | self.atr.IsReady,
21 | self.ma.IsReady,
22 | ))
23 |
24 |
25 | class RateOfChangeRotationETF(QCAlgorithm):
26 |
27 | def Initialize(self):
28 | self.SetStartDate(2002, 1, 1)
29 | self.SetEndDate(2022, 11, 1)
30 | self.SetCash(10000)
31 | self.SetWarmUp(timedelta(200), Resolution.Daily)
32 | self.UniverseSettings.Resolution = Resolution.Daily
33 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
34 | self.EQUITY_RISK_PC = 0.01
35 | tickers = [
36 | "GXLE",
37 | "GXLK",
38 | "GXLV",
39 | "GXLF",
40 | "SXLP",
41 | "SXLI",
42 | "GXLC",
43 | "SXLY",
44 | "SXLB",
45 | "SXLU",
46 | ] if self.LiveMode else [
47 | "XLE",
48 | "XLK",
49 | "XLV",
50 | "XLF",
51 | "XLP",
52 | "XLI",
53 | "XLC",
54 | "XLY",
55 | "XLB",
56 | "XLU",
57 | ]
58 | self.symbol_map = {}
59 | for ticker in tickers:
60 | self.AddEquity(ticker, Resolution.Daily)
61 |
62 | def live_log(self, msg):
63 | if self.LiveMode:
64 | self.Log(msg)
65 |
66 | def OnData(self, data):
67 | invested = []
68 | symbols = []
69 | for symbol in self.ActiveSecurities.Keys:
70 | if not data.Bars.ContainsKey(symbol):
71 | self.Debug("symbol not in data")
72 | continue
73 | if symbol not in self.symbol_map:
74 | self.symbol_map[symbol] = SymbolIndicators()
75 | self.symbol_map[symbol].update(data.Bars[symbol])
76 | if not self.symbol_map[symbol].ready or data.Bars[symbol].Close < self.symbol_map[symbol].ma.Current.Value:
77 | continue
78 | symbols.append(symbol)
79 | if self.ActiveSecurities[symbol].Invested:
80 | invested.append(symbol)
81 | if self.IsWarmingUp:
82 | return
83 | highest_roc = sorted(
84 | symbols,
85 | key=lambda symbol: self.symbol_map[symbol].roc,
86 | reverse=True,
87 | )[:5]
88 | for symbol in highest_roc:
89 | if not self.ActiveSecurities[symbol].Invested:
90 | self.buy(symbol)
91 | invested.append(symbol)
92 | for symbol in invested:
93 | if symbol not in highest_roc or data.Bars[symbol].Close < self.symbol_map[symbol].ma.Current.Value:
94 | self.Liquidate(symbol)
95 |
96 | def buy(self, symbol):
97 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbol_map[symbol].atr.Current.Value)
98 | position_value = position_size * self.ActiveSecurities[symbol].Price
99 | self.live_log(f"buying {symbol.Value}")
100 | if position_value < self.Portfolio.Cash:
101 | self.MarketOrder(symbol, position_size)
102 | else:
103 | self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}")
104 |
105 | def sell_signal(self, symbol, data):
106 | ma = self.symbol_map[symbol].ma.Current.Value
107 | ma_long = self.symbol_map[symbol].ma_long.Current.Value
108 | return self.symbol_map[symbol].ma_violated or ma_long > ma or ma_long > data.Bars[symbol].Close
109 |
110 |
--------------------------------------------------------------------------------
/RateOfChangeRotationETF/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/TrendFollowingMonthly/main.py:
--------------------------------------------------------------------------------
1 | from AlgorithmImports import *
2 |
3 |
4 | class MyQC500(QC500UniverseSelectionModel):
5 | """
6 | Optimized to select the top 250 stocks by dollar volume
7 | """
8 | numberOfSymbolsCoarse = 250
9 |
10 |
11 | class TrendFollowingMonthly(QCAlgorithm):
12 | def Initialize(self):
13 | self.UniverseSettings.Resolution = Resolution.Daily
14 | self.SetStartDate(2020, 1, 1)
15 | self.SetCash(100000)
16 | self.SetWarmUp(timedelta(200), Resolution.Daily)
17 | self.UniverseSettings.Resolution = Resolution.Daily
18 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
19 | # self.AddUniverseSelection(MyQC500()) # it might be this
20 | self.SetUniverseSelection(MyQC500())
21 | self.symbols = {}
22 | self.EQUITY_RISK_PC = 0.01
23 | self.spy = self.AddEquity("SPY", Resolution.Daily)
24 | self.last_month = -1
25 |
26 | @property
27 | def spy_downtrending(self) -> bool:
28 | spy_data = SPYSymbolData(self.History(self.spy.Symbol, 200, Resolution.Daily))
29 | return spy_data.ma.Current.Value > self.spy.Price
30 |
31 | def OnData(self, data):
32 | if self.Time.month == self.last_month:
33 | return
34 | self.last_month = self.Time.month
35 | self.Debug(str(self.Time))
36 | if self.spy_downtrending:
37 | for security in self.Portfolio.Securities.keys():
38 | self.Liquidate(self.Portfolio.Securities[security].Symbol)
39 | return
40 | securities = [symbol for symbol in self.ActiveSecurities.Keys if symbol in data.Bars]
41 | for symbol in securities:
42 | self.symbols[symbol] = SymbolData(self.History(symbol, 200, Resolution.Daily))
43 | securities = [symbol for symbol in securities if self.symbols[symbol].atrp(data.Bars[symbol].Close) <= 5]
44 | top_performers = sorted(
45 | securities,
46 | key=lambda symbol: self.symbols[symbol].roc.Current.Value,
47 | reverse=True,
48 | )[:10]
49 | for symbol in securities:
50 | if self.ActiveSecurities[symbol].Invested and symbol not in top_performers:
51 | self.Liquidate(symbol)
52 | for symbol in top_performers:
53 | if not self.ActiveSecurities[symbol].Invested:
54 | self.buy(symbol)
55 |
56 | def buy(self, symbol):
57 | position_size = round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / self.symbols[symbol].atr.Current.Value)
58 | position_value = position_size * self.ActiveSecurities[symbol].Price
59 | if position_value < self.Portfolio.Cash:
60 | self.MarketOrder(symbol, position_size)
61 |
62 |
63 | class SymbolData:
64 | def __init__(self, history):
65 | self.roc = RateOfChangePercent(200)
66 | self.atr = AverageTrueRange(21)
67 |
68 | for data in history.itertuples():
69 | self.update(
70 | TradeBar(data.Index[1], data.Index[0], data.open, data.high, data.low, data.close, data.volume, timedelta(1))
71 | )
72 |
73 | def update(self, trade_bar):
74 | self.atr.Update(trade_bar)
75 | self.roc.Update(trade_bar.EndTime, trade_bar.Close)
76 |
77 | def atrp(self, close):
78 | return (self.atr.Current.Value / close) * 100
79 |
80 |
81 | class SPYSymbolData():
82 | def __init__(self, history):
83 | self.ma = SimpleMovingAverage(200)
84 |
85 | for data in history.itertuples():
86 | self.ma.Update(data.Index[1], data.close)
87 |
88 | def update(self, time, price):
89 | self.ma.Update(time, price)
--------------------------------------------------------------------------------
/TrendFollowingMonthly/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/TurleTrading/main.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from AlgorithmImports import Resolution, AverageTrueRange, SimpleMovingAverage, MoneyFlowIndex,\
3 | BrokerageName, QCAlgorithm, QC500UniverseSelectionModel, ImmediateExecutionModel, Field
4 |
5 |
6 | class TurleTrading(QCAlgorithm):
7 | def Initialize(self):
8 | self.SetStartDate(2021, 1, 1)
9 | self.SetCash(100000)
10 | self.resolution = Resolution.Daily
11 | self.SetWarmUp(datetime.timedelta(200), self.resolution)
12 | self.UniverseSettings.Resolution = self.resolution
13 | self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
14 | self.SetUniverseSelection(QC500UniverseSelectionModel())
15 | self.SetExecution(ImmediateExecutionModel())
16 | self.symbols = {}
17 | self.EQUITY_RISK_PC = 0.02
18 | self.mfi_lookback = 21
19 | self.atr_lookback = 21
20 | self.sma_lookback = 150
21 | self.high_lookback = 40
22 | self.low_lookback = 20
23 |
24 |
25 | def OnSecuritiesChanged(self, changes):
26 | for added in changes.AddedSecurities:
27 | self.symbols[added.Symbol] = SymbolData(
28 | self, added, self.resolution, mfi_lookback=self.mfi_lookback,
29 | atr_lookback=self.atr_lookback, sma_lookback=self.sma_lookback,
30 | high_lookback=self.high_lookback, low_lookback=self.low_lookback,
31 | )
32 |
33 | for removed in changes.RemovedSecurities:
34 | data = self.symbols.pop(removed.Symbol, None)
35 | if data is not None:
36 | self.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
37 |
38 | def OnData(self, data):
39 | securities = [
40 | symbol for symbol in self.symbols.keys() \
41 | if self.symbols[symbol].sma.Current.Value < self.ActiveSecurities[symbol].Close \
42 | and self.symbols[symbol].high.PeriodsSinceMaximum >= self.high_lookback -1 \
43 | and not self.ActiveSecurities[symbol].Invested
44 | ]
45 | securities = sorted(
46 | securities,
47 | key=lambda symbol: self.symbols[symbol].mfi.Current.Value,
48 | reverse=True,
49 | )[:10]
50 | for symbol in securities:
51 | position_size = self.calculate_position_size(symbol)
52 | position_value = position_size * self.ActiveSecurities[symbol].Price
53 | if position_value < self.Portfolio.Cash:
54 | self.MarketOrder(symbol, position_size)
55 | self.handle_exit_strategy()
56 |
57 | def handle_exit_strategy(self):
58 | for symbol in self.symbols.keys():
59 | if not self.ActiveSecurities[symbol].Invested:
60 | continue
61 | if symbol not in self.symbols:
62 | self.Liquidate(symbol)
63 | close = self.ActiveSecurities[symbol].Close
64 | if close < self.symbols[symbol].low.Current.Value:
65 | self.Liquidate(symbol)
66 | if self.Portfolio[symbol].UnrealizedProfitPercent >= 0.20 or \
67 | self.Portfolio[symbol].UnrealizedProfitPercent <= -0.08 or \
68 | close < self.symbols[symbol].sma.Current.Value:
69 | self.Liquidate(symbol)
70 |
71 | def calculate_position_size(self, symbol):
72 | atr = self.symbols[symbol].atr.Current.Value
73 | return round((self.Portfolio.TotalPortfolioValue * self.EQUITY_RISK_PC) / atr)
74 |
75 |
76 | class SymbolData:
77 | def __init__(self, algorithm, security, resolution, mfi_lookback = 3, atr_lookback = 10, sma_lookback = 150, high_lookback = 40, low_lookback = 40):
78 | self.security = security
79 | self.mfi = MoneyFlowIndex(mfi_lookback)
80 | self.atr = AverageTrueRange(atr_lookback)
81 | self.sma = SimpleMovingAverage(sma_lookback)
82 | self.high = algorithm.MAX(self.security.Symbol, high_lookback, resolution, Field.High)
83 | self.low = algorithm.MIN(self.security.Symbol, low_lookback, resolution, Field.Low)
84 | self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
85 | for indicator in (self.mfi, self.atr, self.sma):
86 | algorithm.RegisterIndicator(security.Symbol, indicator, self.Consolidator)
87 | algorithm.WarmUpIndicator(security.Symbol, indicator, resolution)
88 |
--------------------------------------------------------------------------------
/TurleTrading/research.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "\n",
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "metadata": {},
15 | "outputs": [],
16 | "source": [
17 | "# QuantBook Analysis Tool\n",
18 | "# For more information see https://www.quantconnect.com/docs/research/overview\n",
19 | "qb = QuantBook()\n",
20 | "spy = qb.AddEquity(\"SPY\")\n",
21 | "history = qb.History(qb.Securities.Keys, 360, Resolution.Daily)\n",
22 | "\n",
23 | "# Indicator Analysis\n",
24 | "ema = qb.Indicator(ExponentialMovingAverage(10), spy.Symbol, 360, Resolution.Daily)\n",
25 | "ema.plot()"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": []
34 | }
35 | ],
36 | "metadata": {
37 | "kernelspec": {
38 | "display_name": "Python 3",
39 | "language": "python",
40 | "name": "python3"
41 | },
42 | "language_info": {
43 | "codemirror_mode": {
44 | "name": "ipython",
45 | "version": 3
46 | },
47 | "file_extension": ".py",
48 | "mimetype": "text/x-python",
49 | "name": "python",
50 | "nbconvert_exporter": "python",
51 | "pygments_lexer": "ipython3",
52 | "version": "3.8.13"
53 | }
54 | },
55 | "nbformat": 4,
56 | "nbformat_minor": 2
57 | }
58 |
--------------------------------------------------------------------------------
/master algo framework idea.excalidraw:
--------------------------------------------------------------------------------
1 | {
2 | "type": "excalidraw",
3 | "version": 2,
4 | "source": "https://excalidraw.com",
5 | "elements": [
6 | {
7 | "type": "text",
8 | "version": 2613,
9 | "versionNonce": 203197498,
10 | "isDeleted": false,
11 | "id": "6HIKs-uXCt8JgCVuRg8OA",
12 | "fillStyle": "hachure",
13 | "strokeWidth": 1,
14 | "strokeStyle": "solid",
15 | "roughness": 1,
16 | "opacity": 100,
17 | "angle": 0,
18 | "x": 391.6778946241693,
19 | "y": 217.89438236325503,
20 | "strokeColor": "#000000",
21 | "backgroundColor": "#4c6ef5",
22 | "width": 1115.87890625,
23 | "height": 1050,
24 | "seed": 1078021912,
25 | "groupIds": [],
26 | "roundness": null,
27 | "boundElements": [],
28 | "updated": 1681118724596,
29 | "link": null,
30 | "locked": false,
31 | "fontSize": 20,
32 | "fontFamily": 1,
33 | "text": "\nThe Algorithm Framework can't support multiple non correlated models \nwhich trade on different timeframes and possibly a different universe of symbols.\n\nFor an algorithm to contain multiple models, they need to share the following properties:\n- rebalance timeframe\n- universe\n- stop loss & profit taking logic\n- trade exit logic\n\nThe risk management piece of the framework knows nothing about which alpha generated\nan insight upon which a trade was made.\n\n\nSHORT TERM SOLUTION\n- find a long/short mean reversion strategy that can be deployed in the algo framework\n- this will have an alpha for generating long signals and one for short signals\n- the trading timeframe and stop loss mechanics will be identical\n\nLONG TERM SOLUTION\n- self host lean & run each algorithm in it's own virtual server (possibly using kubernetes to manage deployment)\n\n\nMASTER ALGO\n- a framework within the existing QC framework that can support multiple strategies\n\nHow to rebalance on different timeframes\n- algorithm will be set to run daily\n- each strategy must have a rebalance_period property (timedelta)\n- if daily, check that it's a valid trading day\n- if weekly, check that it's a Sunday\n- if monthly, check that it's the first trading day of the month\n\nHow to store positions for each strategy\n- when an order is submitted, update the symbol data object with a position for the strategy + the size\n- when an order is confirmed, find the position with the confirmed amount and update it as confirmed\n- when an order is liquidated, remove the position from symbol data\n\nHow to handle indicators for each strategy\n- one big symbol data class with all required indicators defined\n- try and reuse indicators across strategies: same ATR, SMA, ADX\n- if an indicator needs to be reused with a different lookback, use a name: sma_200 / ema_21",
34 | "textAlign": "left",
35 | "verticalAlign": "top",
36 | "containerId": null,
37 | "originalText": "\nThe Algorithm Framework can't support multiple non correlated models \nwhich trade on different timeframes and possibly a different universe of symbols.\n\nFor an algorithm to contain multiple models, they need to share the following properties:\n- rebalance timeframe\n- universe\n- stop loss & profit taking logic\n- trade exit logic\n\nThe risk management piece of the framework knows nothing about which alpha generated\nan insight upon which a trade was made.\n\n\nSHORT TERM SOLUTION\n- find a long/short mean reversion strategy that can be deployed in the algo framework\n- this will have an alpha for generating long signals and one for short signals\n- the trading timeframe and stop loss mechanics will be identical\n\nLONG TERM SOLUTION\n- self host lean & run each algorithm in it's own virtual server (possibly using kubernetes to manage deployment)\n\n\nMASTER ALGO\n- a framework within the existing QC framework that can support multiple strategies\n\nHow to rebalance on different timeframes\n- algorithm will be set to run daily\n- each strategy must have a rebalance_period property (timedelta)\n- if daily, check that it's a valid trading day\n- if weekly, check that it's a Sunday\n- if monthly, check that it's the first trading day of the month\n\nHow to store positions for each strategy\n- when an order is submitted, update the symbol data object with a position for the strategy + the size\n- when an order is confirmed, find the position with the confirmed amount and update it as confirmed\n- when an order is liquidated, remove the position from symbol data\n\nHow to handle indicators for each strategy\n- one big symbol data class with all required indicators defined\n- try and reuse indicators across strategies: same ATR, SMA, ADX\n- if an indicator needs to be reused with a different lookback, use a name: sma_200 / ema_21",
38 | "lineHeight": 1.25
39 | }
40 | ],
41 | "appState": {
42 | "gridSize": null,
43 | "viewBackgroundColor": "#ffffff"
44 | },
45 | "files": {}
46 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Quantconnect strategies
2 | This repo is a collection of strategies that I've developed across a year of tinkering with [quantconnect](https://www.quantconnect.com/).
3 | Most of them are not profitable, but exist as an implementation of an idea I had at one point and wished to try.
4 | The only strategy I actually live traded is in the [Breakout](/Breakout) directory.
5 | I no longer use quantconnect and so have decided to make this repo public.
6 |
7 | ## A note on code style and typing
8 | If you're an accomplished python developer you may have noticed that none of my strategy code uses the python typing system, and many class methods use CamelCase.
9 | This is because quantconnect's python implementation utilizes [PythonNet](https://github.com/pythonnet/pythonnet).
10 | If it was up to me I would implement PEP8 to the letter!
11 |
12 | ## Usage guide
13 |
14 | When running the strategies locally you can download stock data from qunatconnect API:
15 | ```sh
16 | $ lean data download --dataset "US Equities" --organization "" --data-type "Trade" --ticker "AAPL" --resolution "Daily" --start "20210101" --end "20211231"
17 | ```
18 |
19 | To push strategy updates to the cloud, do the following:
20 | ```sh
21 | $ lean cloud push --project
22 | ```
23 |
24 | To backtest a strategy on quantconnect, do the following:
25 | ```sh
26 | $ lean cloud backtest --push
27 | ```
28 | The `--push` flag will ensure that the latest local updates are pushed before the backtest starts.
29 | Initiating the backtest via the cli is also much quicker when compared with using the in browser GUI.
30 |
--------------------------------------------------------------------------------
/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oreilm49/quantconnect/5d7a167685326d2a921be6a4e7c55120fb02e3cf/scripts/__init__.py
--------------------------------------------------------------------------------
/scripts/analyze_orders.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import datetime
3 | import os
4 | from dataclasses import dataclass
5 | from pathlib import Path
6 | from typing import Optional
7 |
8 | import pandas as pd
9 |
10 |
11 | @dataclass
12 | class Position:
13 | start: datetime.datetime
14 | end: Optional[datetime.datetime]
15 | symbol: str
16 | price: float
17 | size: float
18 | value: float
19 | quantity_sold: float
20 | value_sold: float
21 |
22 | @property
23 | def liquidated(self) -> bool:
24 | return self.size == self.quantity_sold
25 |
26 | def sell(self, data: pd.Series) -> None:
27 | self.quantity_sold += (data.Quantity * -1)
28 | self.value_sold += (data.Value * -1)
29 |
30 | def add(self, data: pd.Series) -> None:
31 | self.value += data.Value
32 | self.size += data.Quantity
33 | self.price = self.value / self.size
34 |
35 | @property
36 | def profit(self) -> float:
37 | return self.value_sold - self.value
38 |
39 | @property
40 | def profit_pc(self) -> float:
41 | return (self.profit / self.value_sold) * 100 if self.value_sold else 0
42 |
43 | @property
44 | def days(self) -> Optional[int]:
45 | return (self.end - self.start).days if self.end else "-"
46 |
47 | def close(self, data: pd.Series) -> None:
48 | self.end = data.Time
49 |
50 | def get_values(self, names: list[str]):
51 | return [getattr(self, name) for name in names]
52 |
53 |
54 | def analyze_orders() -> None:
55 | folder_path = Path(Path().absolute(), 'backtest_reports')
56 | files = os.listdir(folder_path)
57 | for filename in files:
58 | name, ending = filename.split(".")
59 | if ending == 'csv' and '_analyzed' not in name:
60 | orders = pd.read_csv(Path(folder_path, filename), on_bad_lines='skip')
61 | orders['Time'] = pd.to_datetime(orders['Time'])
62 | open_positions: dict[str, Position] = {}
63 | closed_positions: list[Position] = []
64 | for index, data in orders[orders.Status == 'Filled'].iterrows():
65 | if data.Quantity < 0:
66 | open_positions[data.Symbol].sell(data)
67 | if open_positions[data.Symbol].liquidated:
68 | open_positions[data.Symbol].close(data)
69 | closed_positions.append(open_positions.pop(data.Symbol))
70 | else:
71 | if data.Symbol not in open_positions:
72 | open_positions[data.Symbol] = Position(
73 | start=data.Time,
74 | end=None,
75 | symbol=data.Symbol,
76 | price=data.Price,
77 | size=data.Quantity,
78 | value=data.Value,
79 | quantity_sold=0,
80 | value_sold=0,
81 | )
82 | else:
83 | open_positions[data.Symbol].add(data)
84 | positions = [*closed_positions, *open_positions.values()]
85 | if not positions:
86 | continue
87 | with open(Path(folder_path, f"{name}_analyzed.csv"), 'w', encoding='UTF8', newline='') as f:
88 | writer = csv.writer(f)
89 | headers = ['start', 'end', 'symbol', 'price', 'size', 'value', 'quantity_sold', 'value_sold', 'profit', 'profit_pc', 'days']
90 | writer.writerow(headers)
91 | position: Position
92 | for position in positions:
93 | writer.writerow(position.get_values(headers))
94 |
95 |
96 | if __name__ == "__main__":
97 | analyze_orders()
98 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import os, csv
2 |
3 |
4 | def process_orders(path) -> None:
5 | open_positions_map = {}
6 | positions = []
7 | with open(path, newline='') as csvfile:
8 | for row in csv.DictReader(csvfile):
9 | if row['Status'] == 'Invalid':
10 | continue
11 | if row['Symbol'] not in open_positions_map:
12 | open_positions_map[row['Symbol']] = {
13 | 'start': row['Date Time'],
14 | 'symbol': row['Symbol'],
15 | 'entry': row['Price'],
16 | 'size': row['Quantity'],
17 | }
18 | else:
19 | positions.append({
20 | **open_positions_map[row['Symbol']],
21 | 'exit': row['Price'],
22 | 'end': row['Date Time'],
23 | })
24 | open_positions_map.pop(row['Symbol'])
25 | with open(path.replace(".csv", "_processed.csv"), 'w', newline='') as csvfile:
26 | writer = csv.DictWriter(csvfile, fieldnames=('start', 'end', 'symbol', 'entry', 'exit', 'size'))
27 | writer.writeheader()
28 | for position in positions:
29 | writer.writerow(position)
30 |
--------------------------------------------------------------------------------