Overall Statistics |
Total Trades 252 Average Win 1.27% Average Loss -1.12% Compounding Annual Return 26.953% Drawdown 9.600% Expectancy 0.253 Net Profit 40.218% Sharpe Ratio 1.401 Probabilistic Sharpe Ratio 64.728% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.13 Alpha 0.163 Beta 0.414 Annual Standard Deviation 0.136 Annual Variance 0.018 Information Ratio 0.849 Tracking Error 0.148 Treynor Ratio 0.459 Total Fees $466.20 Estimated Strategy Capacity $90000000.00 Lowest Capacity Asset VX XZBZTQ4K6PYH |
""" Steps: 1) Copy backtest files and run back tests for exact same - time periods - # contracts (1) / comparable contract size Resources: Extract portfolio NAV from strategy equity chart : https://www.quantconnect.com/forum/discussion/2817/how-to-get-backtest-results-into-research/p1 Compare live algo vs backtest : https://www.quantconnect.com/forum/discussion/7606/a-new-reconciliation-metric/p1 """
#region imports from AlgorithmImports import * #endregion import decimal import pandas as pd from datetime import datetime from io import StringIO from datetime import datetime, timedelta import pickle from math import floor # page w/ all Micro symbols - https://www.quantconnect.com/datasets/algoseek-us-futures """ Algo Overview Intraday momentum strategy that uses the momentum gauge with the following chars. - Uses the mg difference - postive or negative to decide whether to short or long - Uses overnight price gap to further qualify that the above is a continuing trend Algo Deployment Steps - Record previous prod code in file - Increment VERSION - Write RELEASE_NOTES or set to empty string "" - Ensure GO_LIVE_CLOSING_OVERWRITE_VALUE is set to most recent correct value - Deploy Algo """ # To Do: # Modify deployment steps if self.LiveMode works as expected. # Determine if we want to add back the old ratcheting system vs START_TAKE_PROFIT_POINTS_HIGHER # Modify text messages to indicate when a ratchet occurs when multiple ratches take place i.e. "You've made a lot of money x2" # Add custom rounding class for brokerage perecision - "To meet brokerage precision requirements, order StopPrice was rounded to 3670.00 from 3669.9165125" # Setup to use the continous contract # Can we switch from a hardcoded margin requirment to a dynamic self.initialMargin for holding daily? NOT overnight # Customized profit / loss day ?? Keep it generalized, likely not going to do # Adding tight stop loss after 2pm # --------------------------------------------------------- # # -- Release Configurations -- # RELEASE_NOTES = "RELEASE NOTES: Fixed bug, forgot to change log level to minimal" VERSION = 3.09 # -- User Configurations -- # RISK_PERCENTAGE = .08 LOG_LEVEL = "minimal" NUMBERS = ["+12035831419", "+15059771023", "+12035215903"] FIRST_NAME = "Alpha" # -- Global Configurations -- # # FUTURES_CONTRACT = Futures.Indices.SP500EMini FUTURES_CONTRACT = Futures.Indices.MicroSP500EMini EQUITY = "SPXL" BENCHMARK = "SPXL" GAP_MIN = .002 START_TAKE_PROFIT_POINTS = 25 PROFIT_POINTS = 10 START_TAKE_PROFIT_POINTS_HIGHER = 50 PROFIT_POINTS_HIGHER = 30 GO_LIVE_CLEAR = False GO_LIVE_CLOSING_OVERWRITE = False GO_LIVE_CLOSING_OVERWRITE_VALUE = 3821.25 GO_LIVE_CLOSING_VIEW = False MAX_LOSS_POINTS_AS_PERCENTAGE = .0087 MARGIN_REQ_PER_CONTRACT = 1800 # https://www.interactivebrokers.com/en/trading/margin-futures-fops.php PRICE_PER_POINT = 5 class gapMomentum(QCAlgorithm): closeObjectStoreKey = "close_object" def Initialize(self): """ Initialize -- note SP500 MG start date data is 2018-11-28 """ # -- Preset Times -- # # # Opto Set # self.SetStartDate(2021, 1, 1) # self.SetEndDate(2022, 3, 25) # # Long Set # self.SetStartDate(2021, 1, 1) # self.SetEndDate(2022, 5, 15) # self.SetStartDate(2021, 1, 1) # self.SetEndDate(2022, 4, 1) # 1 Year # self.SetStartDate(2021, 3, 25) # self.SetEndDate(2022, 3, 25) # Debug margin req # self.SetStartDate(2020, 3, 3) # self.SetEndDate(2020, 4, 25) # Current Set self.SetStartDate(2021, 1, 1) self.SetEndDate(2022, 6, 1) # -- Optimizations Used -- # start_take_profit_points = self.GetParameter("start_take_profit_points") self.start_take_profit_points = START_TAKE_PROFIT_POINTS if start_take_profit_points is None else float(start_take_profit_points) profit_points = self.GetParameter("profit_points") self.profit_points = PROFIT_POINTS if profit_points is None else float(profit_points) gap_min = self.GetParameter("gap_min") self.gapMin = GAP_MIN if gap_min is None else float(gap_min) risk_percentage = self.GetParameter("risk_percentage") self.risk_percentage = RISK_PERCENTAGE if risk_percentage is None else float(risk_percentage) max_loss_as_percentage = self.GetParameter("max_loss_as_percentage") self.max_loss_as_percentage = MAX_LOSS_POINTS_AS_PERCENTAGE if max_loss_as_percentage is None else float(max_loss_as_percentage) start_take_profit_points_higher = self.GetParameter("start_take_profit_points_higher") self.start_take_profit_points_higher = START_TAKE_PROFIT_POINTS_HIGHER if start_take_profit_points_higher is None else float(start_take_profit_points_higher) profit_points_higher = self.GetParameter("profit_points_higher") self.profit_points_higher = PROFIT_POINTS_HIGHER if profit_points_higher is None else float(profit_points_higher) # -- Futures & Equity Setup & Go Live Clear -- # if GO_LIVE_CLEAR or GO_LIVE_CLOSING_OVERWRITE or GO_LIVE_CLOSING_VIEW: self.SetStartDate(2022, 2, 14) self.SetEndDate(2022, 2, 16) self.SetCash(30000) # Setup Futures Contract self.futuresContract = self.AddFuture(FUTURES_CONTRACT) self.futuresContract.SetFilter(5, 150) self.contracts = {} self.oi_contract = None # Brokerage Model self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage) # Add SPXL for market hours self.equity = self.AddEquity(EQUITY, Resolution.Minute).Symbol # Initial dwonload for back-test url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"}) self.MG_df = pd.read_csv(StringIO(url)) self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModFromString) self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModToFormat) # Capture Closing price self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.BeforeMarketClose(self.equity, 0), self.captureClosingPrice) # Scheduled Entry self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.AfterMarketOpen(self.equity, 0), self.openPosition) # Initilization Vars self.closingPrice = None self.priceGap = None self.price = None self.positionType = None self.openingPrice = 0 self.security = None self.gapDown = None self.gapUp = None self.closePrice = None self.positionType = None self.boughtIn = False self.sitOut = False self.textProfit = True self.closePriceRH = None self.closePriceMP = None self.fillPrice = None self.stopLossTriggered = False self.stopLossTriggeredHigher = False self.SetWarmup(1, Resolution.Daily) if self.LiveMode: self.Log("Initialized Algorithim -- Version: " + str(VERSION)) for number in NUMBERS: self.Notify.Sms(number, "Initialized Algorithim -- Version: " + str(VERSION)) if RELEASE_NOTES != "": self.Notify.Sms(number, RELEASE_NOTES) # RELEASE_NOTES def OnData(self, data): """ Sorts Futures Contracts by OpenInterest """ self.data = data if GO_LIVE_CLOSING_VIEW: value = self.ObjectStore.Read(self.closeObjectStoreKey) deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey)) self.closingPrice = pickle.loads(deserialized_value) self.Debug("Current closingPrice stored: " + str(self.closingPrice)) return if GO_LIVE_CLOSING_OVERWRITE: self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(GO_LIVE_CLOSING_OVERWRITE_VALUE)) self.Debug("Manually stored closingPrice: " + str(GO_LIVE_CLOSING_OVERWRITE_VALUE)) return if GO_LIVE_CLEAR: self.ObjectStore.Delete(self.closeObjectStoreKey) return # Iterates through futures contracts for chain in self.data.FutureChains: # self.Log("Current Security At OnData: "+ str(self.security)) contracts = sorted([k for k in chain.Value if k.OpenInterest > 0], key=lambda k : k.OpenInterest, reverse=True) # self.Log("Contracts At Ondata: " + str(contracts)) if not contracts: # self.Log("Continuing at OnData, no contracts") continue # Chooses Futures Contract with most open interest self.oi_contract = contracts[0] # Uses Futures Contract with most Open Interest self.symbol = self.oi_contract.Symbol if self.symbol not in self.contracts: self.contracts[self.symbol] = SymbolData(self, self.symbol) self.contracts[self.symbol].consolidator.DataConsolidated += self.IntradayBars # self.Log("Setting Consolidator at Intraday") # self.Debug(" Debug for chain ") self.security = self.Securities[self.symbol] self.price = self.security.Price # self.Log("Current Security At OnData: " + str(self.security)) # self.Log("Current Price For Security At OnData: " + str(self.price)) self.initialMargin = self.security.BuyingPowerModel.InitialOvernightMarginRequirement # [RHINSEN] - do we need this? self.closePriceMP = self.price def openPosition(self): """ Opens Position Short or Long """ self.Log("At OpenPosition") # Return for hardcoding / clearing if GO_LIVE_CLEAR == True or GO_LIVE_CLOSING_OVERWRITE == True or GO_LIVE_CLOSING_VIEW == True: return # Catch if OnData didn't run first, if it hasn't, set security # if self.security is None: # Remove to set secruity evertytime if self.security is None: for chain in self.data.FutureChains: contracts = sorted([k for k in chain.Value if k.OpenInterest > 0], key=lambda k : k.OpenInterest, reverse=True) self.Log("Contracts: " + str(contracts)) if not contracts: self.Log("Continuing at openPosition -- No Contracts") continue # Chooses Futures Contract with most open interest self.oi_contract = contracts[0] # Uses Futures Contract with most Open Interest if self.symbol not in self.contracts: self.contracts[self.symbol] = SymbolData(self, self.symbol) self.contracts[self.symbol].consolidator.DataConsolidated += self.IntradayBars self.Log("Set Consolidator At Open") self.security = self.Securities[self.symbol] self.price = self.security.Price self.Log("Current Security At openPosition: " + str(self.security)) self.Log("Current Price For Security At openPosition: " + str(self.price)) # Read in stored value for closePrice to closingPrice when not warming up if not self.IsWarmingUp: if self.ObjectStore.ContainsKey(self.closeObjectStoreKey): value = self.ObjectStore.Read(self.closeObjectStoreKey) deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey)) self.closingPrice = pickle.loads(deserialized_value) self.Log("Set closingPrice At openPosition From Saved Value") else: if self.LiveMode: self.closingPrice = GO_LIVE_CLOSING_OVERWRITE_VALUE self.Log("Set closingPrice At openPosition From Global Value") # Calculate price gap for MG if self.price is not None and self.closingPrice is not None and self.priceGap is None: self.Log("Setting PriceGap at openPosition") # Needed for a new day of trading self.startInvestment = self.Portfolio.TotalPortfolioValue self.boughtIn = False self.sitOut = False self.textProfit = True self.fillPrice = None self.keepPrice = self.price self.openingPrice = self.price # Price Gap Logic self.priceGap = self.price - self.closingPrice self.gapUp = self.closingPrice * (1+self.gapMin) self.gapDown = self.closingPrice * (1-self.gapMin) self.Log("PriceGap set at openPosition- Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "Set at openPosition- Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown)) # Cause a reinitilization of the file download every open position if LIVE if self.LiveMode: url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"}) self.MG_df = pd.read_csv(StringIO(url)) # Grab last known entry, which should be yesterday self.dayUsed = self.MG_df.iloc[-1][0] self.MG_positive = float(self.MG_df.iloc[-1][1]) self.MG_negative = float(self.MG_df.iloc[-1][2]) self.MG_difference = float(self.MG_df.iloc[-1][3]) self.MG_daily_change = float(self.MG_df.iloc[-1][4]) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change)) # self.Debug(str(self.Time) + " opening Portfolio Margin: " + str(self.Portfolio.MarginRemaining)) def captureClosingPrice(self): """ Capture closing price """ # Closing Price information [MPURCELL] if not self.IsWarmingUp: self.closePriceRH = self.price if self.closePriceRH is not None: self.closePrice = self.closePriceRH self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(self.closePrice)) self.priceGap = None self.Log('Used RH close price of: ' + str(self.closePrice)) if LOG_LEVEL == "info": for number in NUMBERS: self.Notify.Sms(number, "Closing Price: " + str(self.closePrice)) elif self.closePriceMP is not None: self.closePrice = self.closePriceMP self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(self.closePrice)) self.priceGap = None self.Log('Used MP close price of: ' + str(self.closePrice)) if LOG_LEVEL == "info": for number in NUMBERS: self.Notify.Sms(number, "Closing Price: " + str(self.closePrice)) else: self.closePrice = GO_LIVE_CLOSING_OVERWRITE_VALUE self.Log('Used Global Close Price: ' + str(self.closePrice)) self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(self.closePrice)) self.priceGap = None def IntradayBars(self, sender, bar): """ Creates IntraDay Bars """ if not self.oi_contract or self.oi_contract.Symbol != bar.Symbol: return # Check if Today's date in MG File for back-testing try: current_time = f'{self.Time.year}-{self.Time.month}-{self.Time.day}' todayidxList = self.MG_df.index[self.MG_df['Date']==current_time].tolist() todayIDX = todayidxList[0] self.todaysValues = True except: self.todaysValues = False # Use Yesterday's numbers to prevent look-ahead bias if self.todaysValues == True: self.dayUsed = self.MG_df.loc[todayIDX-1][0] self.MG_positive = float(self.MG_df.loc[todayIDX-1][1]) self.MG_negative = float(self.MG_df.loc[todayIDX-1][2]) self.MG_difference = float(self.MG_df.loc[todayIDX-1][3]) self.MG_daily_change = float(self.MG_df.loc[todayIDX-1][4]) # Time Logic current_time = self.Time.strftime("%H:%M") current_time_as_date = datetime.strptime(current_time, '%H:%M').time() # This is a back-up if open position somehow doesn't get the priceGap, an OK use of a `==` if current_time_as_date >= datetime.strptime('9:30', '%H:%M').time() and current_time_as_date <= datetime.strptime('10:00', '%H:%M').time(): if self.priceGap is None and not self.IsWarmingUp: self.Log("Setting PriceGap at Intraday") if self.ObjectStore.ContainsKey(self.closeObjectStoreKey): value = self.ObjectStore.Read(self.closeObjectStoreKey) deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey)) self.closingPrice = pickle.loads(deserialized_value) self.Log("Set closingPrice At Intraday From Saved Value") else: if self.LiveMode: self.closingPrice = GO_LIVE_CLOSING_OVERWRITE_VALUE self.Log("Set closingPrice At Intraday From Overwrite Value") # Needed for a new day of trading self.startInvestment = self.Portfolio.TotalPortfolioValue self.boughtIn = False self.sitOut = False self.textProfit = True self.fillPrice = None self.keepPrice = self.price self.openingPrice = self.price # Price Gap Logic self.priceGap = self.price - self.closingPrice self.gapUp = self.closingPrice * (1+self.gapMin) self.gapDown = self.closingPrice * (1-self.gapMin) self.Log("PriceGap Set at Intraday Bars == - Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "Set at == - Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown)) if self.LiveMode: url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"}) self.MG_df = pd.read_csv(StringIO(url)) # Grab last known entry, which should be yesterday self.dayUsed = self.MG_df.iloc[-1][0] self.MG_positive = float(self.MG_df.iloc[-1][1]) self.MG_negative = float(self.MG_df.iloc[-1][2]) self.MG_difference = float(self.MG_df.iloc[-1][3]) self.MG_daily_change = float(self.MG_df.iloc[-1][4]) self.Log(str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change)) # Begin to allow buy in process when price gap is exceeded if current_time_as_date >= datetime.strptime('9:30', '%H:%M').time() and current_time_as_date <= datetime.strptime('16:55', '%H:%M').time() and self.sitOut == False: # For Initial BuyIn if not self.security.Invested and self.boughtIn == False and self.sitOut == False and self.priceGap and self.Securities[EQUITY].Exchange.ExchangeOpen == True and not self.IsWarmingUp: """ It's not enough to wish, dream, hope. Even children know this. We must set sail into the sea of uncertainty. We must meet fear face-to-face. We must take our dreams as maps for a greater journey. Dreams, to come true, need a good story. So go live one. — Vironika Tugaleva """ # -- Long -- # if float(self.MG_daily_change) > 0: self.positionType = 'Long' # Calculate cutOffPrice using percentage instead of static number self.max_loss_points = self.price * self.max_loss_as_percentage self.cutOffPrice = self.price - self.max_loss_points if self.price > self.gapUp: self.Log("Investing Long at Intraday") self.contractsToTradeMaxByRisk = floor( (self.Portfolio.TotalPortfolioValue * self.risk_percentage) / (self.max_loss_points * PRICE_PER_POINT)) self.contractsToTradeMaxByEquity = floor( self.Portfolio.TotalPortfolioValue / MARGIN_REQ_PER_CONTRACT ) if self.contractsToTradeMaxByEquity < self.contractsToTradeMaxByRisk: self.contractsToTrade = self.contractsToTradeMaxByEquity else: self.contractsToTrade = self.contractsToTradeMaxByRisk self.MarketOrder(self.symbol, 1) # self.Debug(str(self.Time) + " Buy price: " + str(self.price) + " long contractsToTrade: " + str(self.contractsToTrade) + " portfolio value: " + str(self.Portfolio.TotalPortfolioValue) ) # self.Debug(str(self.Time) + " risk%: " + str(self.risk_percentage) + " risk $: " + str(self.contractsToTrade * self.max_loss_points * 5) + " max_loss_pts: " +str(self.max_loss_points)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, str(FIRST_NAME) + ", IntraDay Gap Momentum Playing Long Today at algo price: " + str(self.price)) elif LOG_LEVEL == "minimal": self.Notify.Sms(number, str(FIRST_NAME) + ", IntraDay Gap Momentum Playing Long Today") self.boughtIn = True self.stopLossTriggered = False self.stopLossTriggeredHigher = False self.keepPrice = self.price ## Global SL self.stopMarketTicket = self.StopMarketOrder(self.symbol, -1, self.cutOffPrice) # -- Short -- # elif float(self.MG_daily_change) < 0: self.positionType = 'Short' # Calculate cutOffPrice using percentage instead of static number self.max_loss_points = self.price * self.max_loss_as_percentage self.cutOffPrice = self.price + self.max_loss_points if self.price < self.gapDown: self.Log("Investing Short at Intraday") self.contractsToTradeMaxByRisk = floor( (self.Portfolio.TotalPortfolioValue * self.risk_percentage) / (self.max_loss_points * PRICE_PER_POINT)) self.contractsToTradeMaxByEquity = floor( self.Portfolio.TotalPortfolioValue / MARGIN_REQ_PER_CONTRACT ) if self.contractsToTradeMaxByEquity < self.contractsToTradeMaxByRisk: self.contractsToTrade = self.contractsToTradeMaxByEquity else: self.contractsToTrade = self.contractsToTradeMaxByRisk self.MarketOrder(self.symbol, -1) # self.Debug(str(self.Time) + " Sell price: " + str(self.price) + " short contractsToTrade: " + str(self.contractsToTrade) + " portfolio value: " + str(self.Portfolio.TotalPortfolioValue)) # self.Debug(str(self.Time) + " risk%: " + str(self.risk_percentage) + " risk $: " + str(self.contractsToTrade * self.max_loss_points * 5) + " max_loss_pts: " +str(self.max_loss_points)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, str(FIRST_NAME) + ", IntraDay Gap Momentum Playing Short Today at algo price: " + str(self.price)) elif LOG_LEVEL == "minimal": self.Notify.Sms(number, str(FIRST_NAME) + ", IntraDay Gap Momentum Playing Short Today") self.boughtIn = True self.stopLossTriggered = False self.stopLossTriggeredHigher = False self.keepPrice = self.price ## Global SL self.stopMarketTicket = self.StopMarketOrder(self.symbol, 1, self.cutOffPrice) # -- Dynamic Exit -- # if self.security.Invested and self.stopLossTriggered == False: # For Long, set a take profit after PROFIT_BUFFER_POINTS exceeded if self.positionType == 'Long': if self.price >= (self.keepPrice + self.start_take_profit_points): # Update Global SL self.stopLossTriggered = True self.keepPoints = self.keepPrice + self.profit_points updateSettings = UpdateOrderFields() updateSettings.StopPrice = decimal.Decimal(round(self.keepPoints, 2)) response = self.stopMarketTicket.Update(updateSettings) # Capture Profit Estimation self.estimatedProfit = int(abs((round(self.keepPoints, 2) - self.fillPrice) * self.contractsToTrade * PRICE_PER_POINT)) self.Debug("Estimated profit Long: " + str(self.estimatedProfit)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "TP StopLoss Triggered At: " + str(self.price)) self.Notify.Sms(number, "You will be making money today") elif LOG_LEVEL == "minimal": self.Notify.Sms(number, "You have safely locked in an estimated $" + str(self.estimatedProfit) + " in profit") # For Short, set a take profit after PROFIT_BUFFER_POINTS exceeded if self.positionType == 'Short': if self.price <= (self.keepPrice - self.start_take_profit_points): # Update Global SL self.stopLossTriggered = True self.keepPoints = self.keepPrice - self.profit_points updateSettings = UpdateOrderFields() updateSettings.StopPrice = decimal.Decimal(round(self.keepPoints, 2)) response = self.stopMarketTicket.Update(updateSettings) # Capture Profit Estimation self.estimatedProfit = int(abs((round(self.keepPoints, 2) - self.fillPrice) * self.contractsToTrade * PRICE_PER_POINT)) self.Debug("Estimated profit Short: " + str(self.estimatedProfit)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "TP StopLoss Triggered At: " + str(self.price)) self.Notify.Sms(number, "You will be making money today") elif LOG_LEVEL == "minimal": self.Notify.Sms(number, "You have safely locked in an estimated $" + str(self.estimatedProfit) + " in profit") # Check if higher take profit threshold is met & raise the dynamic Exit if self.security.Invested and self.stopLossTriggeredHigher == False: if self.positionType == 'Long': if self.price >= (self.keepPrice + self.start_take_profit_points_higher): # Update Global SL self.stopLossTriggeredHigher = True self.keepPoints = self.keepPrice + self.profit_points_higher updateSettings = UpdateOrderFields() updateSettings.StopPrice = decimal.Decimal(round(self.keepPoints, 2)) response = self.stopMarketTicket.Update(updateSettings) # Capture Profit Estimation self.estimatedProfit = int(abs((round(self.keepPoints, 2) - self.fillPrice) * self.contractsToTrade * PRICE_PER_POINT)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "Higher TP StopLoss Triggered At: " + str(self.price)) self.Notify.Sms(number, "You will be making ALOT of money today, " + str(FIRST_NAME)) elif LOG_LEVEL == "minimal": self.Notify.Sms(number, "You have safely locked in an estimated $" + str(self.estimatedProfit) + " in profit") # self.Debug(str(self.Time) + " Higher TP start - short algo price: " + str(self.price) + " keepPoints higher: " + str(self.keepPoints) + " keepPrice: " +str(self.keepPrice)) if self.positionType == 'Short': if self.price <= (self.keepPrice - self.start_take_profit_points_higher): # Update Global SL self.stopLossTriggeredHigher = True self.keepPoints = self.keepPrice - self.profit_points_higher updateSettings = UpdateOrderFields() updateSettings.StopPrice = decimal.Decimal(round(self.keepPoints, 2)) response = self.stopMarketTicket.Update(updateSettings) # Capture Profit Estimation self.estimatedProfit = int(abs((round(self.keepPoints, 2) - self.fillPrice) * self.contractsToTrade * PRICE_PER_POINT)) for number in NUMBERS: if LOG_LEVEL == "info": self.Notify.Sms(number, "Higher TP StopLoss Triggered At: " + str(self.price)) self.Notify.Sms(number, "You will be making ALOT of money today, " + str(FIRST_NAME)) elif LOG_LEVEL == "minimal": self.Notify.Sms(number, "You have safely locked in an estimated $" + str(self.estimatedProfit) + " in profit") # Check whether max_loss_stop is exceeded, if so sit out if self.positionType == 'Long': if self.price <= self.cutOffPrice: self.sitOut = True if self.security.Invested: gapMomentum.closePosition(self) if self.positionType == 'Short': if self.price >= self.cutOffPrice: self.sitOut = True if self.security.Invested: gapMomentum.closePosition(self) # Liquadate always before end of day if self.security.Invested and current_time_as_date >= datetime.strptime('16:55', '%H:%M').time(): # self.Debug('Liqudated Close of Day ' + str(self.positionType) + ': ' + str(self.Time)) gapMomentum.closePosition(self) # Bought in, and have closed position if not self.security.Invested and self.boughtIn == True and self.textProfit == True: self.textProfit = False # Get Daily Profit profit = round((self.Portfolio.TotalPortfolioValue - self.startInvestment), 2) # self.Debug('Daily Profit: '+str(profit)) if profit >= 0: gains = 'Profit' else: gains = 'Loss' # Msg if LOG_LEVEL in ['minimal', 'info']: for number in NUMBERS: self.Notify.Sms(number, "IntraDay Gap Momentum Sold " + str(self.positionType) + " Today for " + str(gains) + ": $" + str(profit)) def closePosition(self): """ close position """ ### Liquadate and Text self.Liquidate() # -- Clean up Stop Losses -- # # Gets Open Tickets related to stop loss open_tickets = self.Transactions.GetOpenOrderTickets() # Parses through open tickets looking for stop loss stop_tickets = [ticket for ticket in open_tickets if ticket.OrderType == OrderType.StopMarket] # If More then one active stop loss ticket, cancel current stop losses, create ticket if len(stop_tickets) > 1 : for ticket in stop_tickets: ticket.Cancel('Canceled stop loss tickets') create_ticket = True def OnSecuritiesChanged(self, changes): """ QC function for when futures contract changes """ for security in changes.RemovedSecurities: self.symbol = security.Symbol symbolData = self.contracts.get(self.symbol, None) def OnOrderEvent(self, orderevent): """ Grabs price from initial fill event """ order = self.Transactions.GetOrderById(orderevent.OrderId) if orderevent.Status == OrderStatus.Filled: self.fillPrice = orderevent.FillPrice class SymbolData: """ For data consolidation with 5 minute bars""" def __init__(self, algorithm, symbol): periods = { '1D': timedelta(1), 'INTRADAY': timedelta(minutes=5) } self.consolidator = TradeBarConsolidator(periods['INTRADAY']) # if algorithm.LiveMode: message = str(type(self.consolidator)) algorithm.Log(message) # Used for Intraday Function self.atr = AverageTrueRange(14, MovingAverageType.Simple) algorithm.RegisterIndicator(symbol, self.atr, self.consolidator) class Utils: """ Utility Functions """ @staticmethod def dateModFromString(x): return datetime.strptime(x, '%Y-%m-%d') @staticmethod def dateModToFormat(x): return x.strftime("%Y-%-m-%-d")
""" Algo Objective: Trade VIX futures intraday using previous day's direction and MG for direction. According to VIX Research: Days up Avg: 1.67 Days up median: 1.0 Days down Avg: 2.53 Days down median: 2.0 MG_dailyChange predicts VIX updays after MG_daily_change < 0: 44% VIX downdays after MG_daily_change > 0: 68% Both conclude that predicting down days is easier than predicting up days. try adding TP pts similar to Gap Mo. PROD """ """ Notes / Inferences: Shorting vix works much better than going long (as predicted). Going Long only has positive return if using MG_daily_change only MG_daily_change adds a lot of benefit to the short side - only a little to the long side. MG_difference does the worst Very little difference in CAGR between MG_daily_change only and MG_daily_change with days in a row (in_a_row has better PSR, info ratio, but/and trades 23% less) I could put $ into shorting now. - Long does better when the previously day was a down day for the VIX. """ # Next Steps # Add TP pts # add SL """ From 2021 - June 2022 VX Range Avg: 0.63 VX Range Median: .40 """ # region imports from AlgorithmImports import * import statistics from datetime import datetime from io import StringIO from datetime import datetime, timedelta # endregion FUTURES_CONTRACT = Futures.Indices.VIX EQUITY = "SPY" class CryingFluorescentYellowBuffalo(QCAlgorithm): def Initialize(self): # Debug Set # self.SetStartDate(2022,3,1) # self.SetEndDate(2022,6,10) # Long Set self.SetStartDate(2021,1,1) self.SetEndDate(2022,6,1) self.SetCash(50000) self.equity = self.AddEquity(EQUITY, Resolution.Minute) self.vix = self.AddData(CBOE, "VIX").Symbol # Setup Futures Contract self.futuresContract = self.AddFuture(FUTURES_CONTRACT, dataNormalizationMode = DataNormalizationMode.Raw, dataMappingMode = DataMappingMode.LastTradingDay, contractDepthOffset = 0) # Initial dwonload for back-test url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"}) self.MG_df = pd.read_csv(StringIO(url)) self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModFromString) self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModToFormat) self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.AfterMarketOpen(EQUITY, 1), self.dayStart) self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.BeforeMarketClose(EQUITY, 0), self.dayEnd) # Iniatialzed Vars self.MG_daily_change = None self.previouslyUp = False def OnData(self, data: Slice): self.data = data if data.ContainsKey("VIX"): self.vixPriceOpen = float(data['VIX'].Open) self.vixPriceClose = float(data['VIX'].Close) self.futuresPrice = self.futuresContract.Price def dayStart(self): # Check if Today's date in MG File for back-testing try: current_time = f'{self.Time.year}-{self.Time.month}-{self.Time.day}' todayidxList = self.MG_df.index[self.MG_df['Date']==current_time].tolist() todayIDX = todayidxList[0] self.todaysValues = True except: self.todaysValues = False # Use Yesterday's numbers to prevent look-ahead bias if self.todaysValues == True: self.dayUsed = self.MG_df.loc[todayIDX-1][0] self.MG_positive = float(self.MG_df.loc[todayIDX-1][1]) self.MG_negative = float(self.MG_df.loc[todayIDX-1][2]) self.MG_difference = float(self.MG_df.loc[todayIDX-1][3]) self.MG_daily_change = float(self.MG_df.loc[todayIDX-1][4]) if self.MG_daily_change is not None and self.vixPriceOpen is not None and self.vixPriceClose is not None: # self.Debug(str(self.Time) + " Vix prev Open: " + str(self.vixPriceOpen) + " Vix prev Close: " + str(self.vixPriceClose) + " MG_daily_change: " + str(self.MG_daily_change)) # -- Short -- if self.MG_daily_change > 0 and self.vixPriceOpen > self.vixPriceClose: self.MarketOrder(self.futuresContract.Mapped, -1) self.Debug(str(self.Time) + " sell price: " + str(self.futuresPrice)) # -- Long -- # if self.MG_daily_change < 0 and self.previouslyUp == False: # self.MarketOrder(self.futuresContract.Mapped, 1) # self.Debug(str(self.Time) + " sell price: " + str(self.futuresPrice)) def dayEnd(self): if self.Portfolio.Invested: self.Liquidate() self.Debug(str(self.Time) + " close price: " + str(self.futuresPrice)) if self.vixPriceOpen < self.vixPriceClose: self.previouslyUp = True self.Debug(str(self.Time) + " self.previouslyUp: True" ) else: self.previouslyUp = False self.Debug(str(self.Time) + " self.previouslyUp: False" ) class Utils: """ Utility Functions """ @staticmethod def dateModFromString(x): return datetime.strptime(x, '%Y-%m-%d') @staticmethod def dateModToFormat(x): return x.strftime("%Y-%-m-%-d")