├── .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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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":["![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\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 | --------------------------------------------------------------------------------