Overall Statistics |
Total Trades 1706 Average Win 0.09% Average Loss -0.18% Compounding Annual Return -59.419% Drawdown 87.200% Expectancy -0.971 Net Profit -87.212% Sharpe Ratio -1.395 Probabilistic Sharpe Ratio 0% Loss Rate 98% Win Rate 2% Profit-Loss Ratio 0.49 Alpha -0.433 Beta 0.001 Annual Standard Deviation 0.311 Annual Variance 0.096 Information Ratio -1.371 Tracking Error 0.348 Treynor Ratio -474.469 Total Fees $1706.00 Estimated Strategy Capacity $14000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 186.14% |
#region imports from AlgorithmImports import * import datetime as dt #endregion ## Percentage of investment according to Total Portfolio Value FUTURE1_ORDER_QUANTITY = 2 FUTURE2_ORDER_QUANTITY = 3 EQUITY1_ORDER_QUANTITY = 100 EQUITY2_ORDER_QUANTITY = 300 # Limit Order margin # https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-types/limit-orders LIMIT_ORDER = 0.005 # If positive Marketable, unmarketable otherwise ## Retracement levels K_AVERAGE = 6 K_STD = 16 ## LOGIC B1 = -0.75 B2 = 0.55 B3 = 0.61 B4 = -0.65
#region imports from AlgorithmImports import * from collections import deque #endregion class CustomMainIndicator(PythonIndicator): ## INITIALIZATION def __init__(self,name,period,resolution=None): """ The parameters accompanied by a * must exist in every custom indicator. Inputs: -- Name* (String): A characteristic name for the definition of the indicator. -- Period [int, float,timedelta]: Number of timeframes to account. When time delta, the resolution is necessary. -- Resolution [QC Attr Resolution or timdelta]: The resolution parameter allow the period computation. Initialization Parameters: -- Time* (datetime.datetime object): Inherits the time of the creation and, on the update method, the moment of update. -- Value* (float non-NonTypeObject): Actual value of the indicator. Where the arrows will be created. """ self.Name = name # Number of periods to make the computation if isinstance(period, (int,float)): self.Period = int(period) else: assert isinstance(period, timedelta), 'Period recibe Int, float or timedelta.' assert isinstance(resolution, (int,timedelta)), 'If period is timedelta type, a resolution should be passed\ to compute the periods. resolution can be the Resolution attribute\ from QC or a timedelta.' self.Period = period//self.SetResolution(resolution) self.Time = datetime.min # The following parametters are necessary for the creation of the figures self.Value = 0 self.Date = datetime.min def SetResolution(self,resolution): '''Return a resolution that the model undestand for the period computation.''' if resolution == 1: # Resolution.Second: return timedelta(seconds=1) elif resolution == 2: # Resolution.Minute: return timedelta(minutes=1) elif resolution == 3: # Resolution.Hour: return timedelta(hours=1) elif resolution == 4: # Resolution.Daily: return timedelta(days=1) else: assert isinstance(resolution, timedelta), 'resolution can be the Resolution attribute\ from QC or a timedelta.' return resolution class ModZIndicator(CustomMainIndicator): ## INITIALIZATION def __init__(self, name, k_average,k_std, **kwargs): ''' Parameters: - ''' super().__init__(name, max(k_average,k_std),**kwargs) self.WarmUpPeriod = self.Period self.K_mean = k_average self.K_std = k_std self.window = deque(maxlen=self.Period) @property def IsReady(self): return len(self.window) >= self.window.maxlen ## INDICATOR COMPUTATION def Update(self, time, inputA, inputB) -> bool: '''QC required function to get the data and update the indicator''' self.Time = time self.EndTime = time self.Ratio = inputA.Close/(inputB.Close + 1e-18) self.window.append(self.Ratio) if len(self.window) < self.window.maxlen: return False window = np.array(self.window) self.Average = np.mean(window[-self.K_mean]) self.STD = np.std(window[-self.K_std:]) + 1e-18 self.Value = (self.Ratio-self.Average)/self.STD return len(self.window) >= self.window.maxlen ##
#region imports from AlgorithmImports import * #endregion from Selection.FutureUniverseSelectionModel import FutureUniverseSelectionModel # Your New Python File class FrontMonthFutureUniverseSelectionModel(FutureUniverseSelectionModel): def __init__(self,) -> None: super().__init__(timedelta(1), self.select_future_chain_symbols) def select_future_chain_symbols(self, utcTime: datetime) -> List[Symbol]: return [ Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME), Symbol.Create(Futures.Indices.NASDAQ100EMini, SecurityType.Future, Market.CME), ] def Filter(self, filter: FutureFilterUniverse) -> FutureFilterUniverse: return filter.FrontMonth().OnlyApplyFilterAtMarketOpen()
#region imports from AlgorithmImports import * from collections import deque import CustomIndicators as ci from ControlParameters import * #endregion class SymbolData: # Object to Keep track of the securities ## INITIALIZATION def __init__(self,symbol, time): ''' Inputs: - Symbol [QC Symbol]: Reference to the underlying security. - time [Main Algo function]: This function returns the time of the main algorithm. ''' self.__Symbol = symbol self.get_Time = time self.__Order = None @property def Time(self): # Allow access to the Time object directly return self.get_Time() ## MANAGEMENT @property def Symbol(self): return self.__Symbol @Symbol.setter def Symbol(self,NewSymbol): self.__Order = None self.__Symbol = NewSymbol @property def Order(self): return self.__Order @Order.setter def Order(self, order): self.__Order = order def GetOrderQuantity(self, portfolio, target): return target - portfolio # --------------------------------------------------------------------------- class PairData: # Object to Keep track of the pairs ## INITIALIZATION def __init__(self,future1, future2, underlying1, underlying2, time ): ''' Inputs: - Symbol [QC Symbol]: Reference to the underlying security. - Security [QC Security]: Reference to the security object to access data. - time [Main Algo function]: This function returns the time of the main algorithm. - Indicator objects: MACD, STOCH, Fast_MA, Slow_MA and BB ''' self.Future1 = future1 self.Future2 = future2 self.Underlying1 = underlying1 self.Underlying2 = underlying2 # Helper dictionaries self.Futures = {self.Future1.Symbol: self.Future1, self.Future2.Symbol: self.Future2} self.Underlyings = {self.Underlying1.Symbol : self.Underlying1, self.Underlying2.Symbol : self.Underlying2} self.MyItems = {**self.Futures,**self.Underlyings} self.ModZ = self.InitIndicators() self.get_Time = time def InitIndicators(self): ''' Create the required indicators. ''' # This procets repeat itself per security modz = ci.ModZIndicator('MyModZ',K_AVERAGE, K_STD)#timedelta(days=10), resolution=Resolution.Daily) '''The following line make the indicator update automatically, but because we use a ratio we have to update it manually''' # main.RegisterIndicator(contract, modz, Resolution.Minite) # Associate the indicator to a ticker and a update resolution # (resolution has to be equal or lower than security resolution) return modz @property def Time(self): # Allow access to the Time object directly return self.get_Time() @property def IsReady(self): # Tells if all the indicators assciated are ready return self.ModZ.IsReady ## STATUS def UpdateFuture(self, old, new, security): ''' In case of rollover or change in the symbol data. Input: old[QC object]: QC Symbol object used within the PairData object. new[QC object]: QC current Symbol object. security [QC object]: Current Security object from QC. ''' self.Futures[new] = security self.Futures.pop(old,None) if old == self.Future1.Symbol: self.Future1 = security elif old == self.Future2.Symbol: self.Future2 = security else: assert self.Future1.Symbol in self.Futures.keys() and\ self.Future2.Symbol in self.Futures.keys(), 'One future did not updated.' def UpdateStatus(self): self.ModZ.Update(self.Time, self.Future1, self.Future2) if self.ModZ.IsReady: return self.MyChecks() return ## MANAGEMENT POSITION LOGIC: Logic specified for the position management def MyChecks(self): targets = {} if self.ModZ.Value > B1 and self.ModZ.Value < B2: targets[self.Future1.Symbol] = FUTURE1_ORDER_QUANTITY targets[self.Underlying1.Symbol] = EQUITY1_ORDER_QUANTITY targets[self.Future2.Symbol] = 0 targets[self.Underlying2.Symbol] = 0 elif self.ModZ.Value < B3 and self.ModZ.Value > B4: targets[self.Future2.Symbol] = FUTURE2_ORDER_QUANTITY targets[self.Underlying2.Symbol] = EQUITY2_ORDER_QUANTITY targets[self.Future1.Symbol] = 0 targets[self.Underlying1.Symbol] = 0 else: targets[self.Future2.Symbol] = 0 targets[self.Underlying2.Symbol] = 0 targets[self.Future1.Symbol] = 0 targets[self.Underlying1.Symbol] = 0 return targets
# region imports from AlgorithmImports import * from collections import deque from Selection.FutureUniverseSelectionModel import FutureUniverseSelectionModel import SymbolData import CustomIndicators as ci from ControlParameters import * from MyUniverseSelectionModel import * # endregion class AdaptableRedOrangePanda(QCAlgorithm): ## INITIALIZATION def Initialize(self): self.SetStartDate(2020, 12, 5) # Set Start Date self.SetCash(2000) # Set Strategy Cash self.SetBrokerageModel(BrokerageName.QuantConnectBrokerage, AccountType.Margin) self.MyUniverseInitializer() self.SetSecurityInitializer(self.MySecurityInitializer) # Add custom modification every time a security is initialized. # (i.e. every time the model starts to feed a security data) self.InitCharts() def InitCharts(self): '''Create a chart and series visualize the indicator''' chart = Chart("MyIndicator") self.AddChart(chart) s_b1 = Series('B1',SeriesType.Line) chart.AddSeries(s_b1) s_b1.AddPoint(datetime(2020,12,5),B1) s_b1.AddPoint(datetime(2021,12,5),B1) s_b2 = Series('B2',SeriesType.Line) chart.AddSeries(s_b2) s_b2.AddPoint(datetime(2020,12,5),B2) s_b2.AddPoint(datetime(2021,12,5),B2) s_b3 = Series('B3',SeriesType.Line) chart.AddSeries(s_b3) s_b3.AddPoint(datetime(2020,12,5),B3) s_b3.AddPoint(datetime(2021,12,5),B3) s_b4 = Series('B4',SeriesType.Line) chart.AddSeries(s_b4) s_b4.AddPoint(datetime(2020,12,5),B4) s_b4.AddPoint(datetime(2021,12,5),B4) self.s_modzor = Series('ModZ OR',SeriesType.Scatter) chart.AddSeries(self.s_modzor) self.s_modzir = Series('ModZ IR',SeriesType.Scatter) chart.AddSeries(self.s_modzir) def MyUniverseInitializer(self): self.SecuritiesTracker = {} # Initilize tracker parameters self.PairTracker = {} # self.addedContracts = set() # Set the resolution for universal use # Even though this is a daily trading strategy, we set the resolution to # minute to have a constant flow of data. We feed the data to the algorithm with # a minute resolution if not, the update of the state would be too long. self.UniverseSettings.Resolution = Resolution.Minute # Adding securities # https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/algoseek/us-futures#05-Supported-Assets # We add the securities as a Universe Selection model so future implementation # can have the adaptability to any security that gets into the Universe. equities = [Symbol.Create('SPY', SecurityType.Equity, Market.USA), Symbol.Create('QQQ', SecurityType.Equity, Market.USA)] # Store desired futures self.__Futures = [Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME), Symbol.Create(Futures.Indices.NASDAQ100EMini, SecurityType.Future, Market.CME)] # Store current futures chain symbol self.__Contracts = {} # State the pairs to follow self.Pairs = {'Futures':[[1,0]], 'Equities':[[equities[1],equities[0]]]} # Universe selector for equities self.AddUniverseSelection(ManualUniverseSelectionModel(equities)) # Universe selector for futures self.AddUniverseSelection(FrontMonthFutureUniverseSelectionModel()) @property def FutureValues(self): '''Return current futures chain symbol''' return [self.__Contracts.get(i) for i in self.__Futures] def MySecurityInitializer(self, security): # When adding an future we could get 0 as Ask/Bid prices since the data has not been feed. # This solves that issue bar = self.GetLastKnownPrice(security) security.SetMarketPrice(bar) ## SECURITIES LOGIC: CREATION, INDICATORS, UPDATE, TACKING def ManageAdded(self, added): ''' Logic for securities added. Create the SymbolData objects that track the added securities. Inputs: added [list]: List of added securities ''' for security in added: if self.SecuritiesTracker.get(security.Symbol) is None: # Create SymbolData object self.SecuritiesTracker[security.Symbol] = SymbolData.SymbolData(security.Symbol, self.get_Time) if security.Type == SecurityType.Future: # Manage futures for i,v in self.PairTracker.items(): try: # If the future has already been added and the Symbol change, update It indx = self.Pairs['Futures'][i].index(self.__Contracts[security.Symbol.Canonical]) self.Pairs['Futures'][i][indx] = security.Symbol except ValueError: pass # If the future has already been added and the Symbol change, update It if self.__Contracts[security.Symbol.Canonical] in v.Futures.keys(): self.PairTracker[i].UpdateFuture(self.__Contracts[security.Symbol.Canonical], security.Symbol, security) # Create it if added for first time or update it self.__Contracts[security.Symbol.Canonical] = security.Symbol def ManageRemoved(self, removed): ''' Logic for securities removed. Remove the SymbolData objects. Inputs: removed [list]: List of removed securities ''' for security in removed: # Don't track anymore if self.Portfolio[security.Symbol].Invested: self.Liquidate(security.Symbol) self.SecuritiesTracker.pop(security.Symbol, None) def ReplacePending(self, pair): ''' The self.Pair["Futures"] created in "MyUniverseInitializer" method initially stores the position of the self.__Futures added, these positions will be replaced by the numbers once the current future chain symbols are added to the universe. Inputs: pair [list]: List of Symbols or integers, in case it was already replaced or is needed respectively. ''' ft_value = self.FutureValues if isinstance(pair[0], int) and ft_value[pair[0]] is not None: pair[0] = ft_value[pair[0]] if isinstance(pair[1], int) and ft_value[pair[1]] is not None: pair[1] = ft_value[pair[1]] return pair def CheckPairs(self): ''' Create a PairData Object once all the securities needed for the pair have been added. ''' for i in range(len(self.Pairs['Futures'])): self.Pairs['Futures'][i] = self.ReplacePending(self.Pairs['Futures'][i]) if isinstance(self.Pairs['Futures'][i][0], int) or isinstance(self.Pairs['Futures'][i][1], int): continue f1 = self.Securities.get(self.Pairs['Futures'][i][0]) f2 = self.Securities.get(self.Pairs['Futures'][i][-1]) e1 = self.Securities.get(self.Pairs['Equities'][i][0]) e2 = self.Securities.get(self.Pairs['Equities'][i][-1]) if not(None in [f1,f2,e1,e2]): self.PairTracker[i] = SymbolData.PairData(f1, f2, e1,e2, self.get_Time) def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: # Gets an object with the changes in the universe # For the added securities we create a SymbolData object that allows us to track the orders associated and the indicators created for it. # We do this per equity due to the fact that we can have more than one contract per underlying security. self.ManageAdded(changes.AddedSecurities) # The removed securities are liquidated and removed from the security tracker. self.ManageRemoved(changes.RemovedSecurities) # Create pairs objects self.CheckPairs() ## CHECK FOR BUYING POWER def CheckBuyingPower(self, symbol, quantity): ''' Check for enough buying power. If the buying power for the target quantity is not enough, It will return the quantity for which the buying power is enough. ''' if quantity < 0: order_direction = OrderDirection.Sell else: order_direction = OrderDirection.Buy # Get the buying power depending of the order direction and symbol buy_power = self.Portfolio.GetBuyingPower(symbol, order_direction) # Compute possible quantity q_t = abs(buy_power) / self.Securities[symbol].Price # Select minimum quantity return round(min(abs(quantity),q_t),8)*np.sign(quantity) def CheckOrdeQuatity(self, symbol, quantity): '''Check that the quantity of shares computed meets the minimum requirments''' q = abs(quantity) # There are requirements for the minimum or maximum that can be purchased per security. if q > self.Settings.MinAbsolutePortfolioTargetPercentage and q < self.Settings.MaxAbsolutePortfolioTargetPercentage: symbol_properties = self.Securities[symbol].SymbolProperties if symbol_properties.MinimumOrderSize is None or q > symbol_properties.MinimumOrderSize: return True return False ## POSITION MANAGEMENT def CheckOpenMarket(self, symbol): '''Check if the market is open for symbol''' last_open = self.Securities[symbol].Exchange.Hours.GetPreviousMarketOpen(self.Time, False) next_close = self.Securities[symbol].Exchange.Hours.GetNextMarketClose(self.Time, False) return (last_open < self.Time and next_close > self.Time) and \ (last_open.day == self.Time.day and next_close.day == self.Time.day) def OnData(self, data: Slice): # The OnData method is called every time the data is feed into the algorithm. for i,pair in self.PairTracker.items(): # Iterate Over the pairs on track targets = pair.UpdateStatus() # Update ModZ Indicator and get target quantities per security # Time between market hours and same day check if self.CheckOpenMarket(pair.Future1.Symbol) and self.CheckOpenMarket(pair.Underlying1.Symbol) and\ self.CheckOpenMarket(pair.Future2.Symbol) and self.CheckOpenMarket(pair.Underlying2.Symbol) and pair.IsReady: # Sort It; First sell and then buy targets = {v:pair.MyItems[k] for k,v in sorted(targets.items(), key=lambda item: item[1])} if np.sum(list(targets.keys())) == 0: # Plot indicator self.s_modzor.AddPoint(pair.ModZ.EndTime ,pair.ModZ.Value) else: self.s_modzir.AddPoint(pair.ModZ.EndTime ,pair.ModZ.Value) for target,s in targets.items(): # Check Roll Over if s.Type == SecurityType.Future: symbol = self.CheckRollOver(data, s.Symbol, pair) else: symbol = s.Symbol # Check current holdings and get the quantity to send the order tracker = self.SecuritiesTracker.get(symbol) quantity = self.Portfolio[symbol].Quantity quantity = tracker.GetOrderQuantity(quantity,target) if quantity == 0: continue if s.Type == SecurityType.Future: self.FutureOrders(data, symbol, tracker, quantity) else: self.EquityOrders(data, symbol, tracker, quantity) # Check for Buying logic and perform create positions If meets the requirements. # self.CreatePositions(data,symbol,tracker) def CheckRollOver(self, data, symbol, pair): '''In case of roll over, this function will update the data in the security tracker''' roll = data.SymbolChangedEvents.get(symbol) if roll is not None and roll.OldSymbol in self.SecuritiesTracker.keys(): # Update Security Tracker self.SecuritiesTracker[roll.NewSymbol] = self.SecuritiesTracker[roll.OldSymbol] self.SecuritiesTracker[roll.NewSymbol].Symbol(roll.NewSymbol) self.SecuritiesTracker.pop(roll.OldSymbol) # Update Pairs pair.UpdateFuture(roll.OldSymbol, roll.NewSymbol, self.Securities[roll.NewSymbol]) return roll.NewSymbol return symbol def FutureOrders(self, data, symbol, tracker, quantity): ''' Implement the logic to purchase futures. Inputs: - symbol [Symbol QC object]: QC Symbol identifier of the securities. - tracker [SymbolData object]: Tracker created for the specific symbol. ''' if data.ContainsKey(symbol) and data[symbol] is not None: # The indicators are ready and the security has been feeded with data chain = data.FuturesChains.get(symbol.Canonical) contract = self.FuturesFilter(symbol, chain) # Call the Future Filter funtion and get the better contract if contract: # A suitable has been found quantity = self.CheckBuyingPower(contract.Symbol, quantity) # Check for buying power if self.CheckOrdeQuatity(contract.Symbol, quantity): # Check for the minimum quantity buy = self.LimitOrder(contract.Symbol,# Create an order for a call contract quantity, round(self.Securities[contract.Symbol].AskPrice * (1 + np.sign(quantity)*LIMIT_ORDER),2)) tracker.Order = buy # Save in the security tracker the contract and order def EquityOrders(self, data, symbol, tracker,quantity): ''' Implement the logic to purchase futures. Inputs: - symbol [Symbol QC object]: QC Symbol identifier of the securities. - tracker [SymbolData object]: Tracker created for the specific symbol. ''' if data.ContainsKey(symbol) and data[symbol] is not None: # The indicators are ready and the security has been feeded with data quantity = self.CheckBuyingPower(symbol, quantity) # Check for buying power if self.CheckOrdeQuatity(symbol, quantity): # Check for the minimum quantity buy = self.MarketOrder(symbol, quantity) # Create an order tracker.Order = buy # Save in the security tracker the contract and order ## OPTIONS: Chain Provider def FuturesFilter(self, symbol, chain): ''' Inputs: - symbol [Symbol QC object]: QC Symbol identifier of the future. - chain [Symbol QC object]: Chain of contracts. Return: If a contract meets the requirements, Future Contract object, otherwise None. ''' if not chain: return False if len(chain.Contracts) > 0: # Filter Contract list contract = sorted(chain.Contracts.values(), key=lambda k : k.OpenInterest, reverse=True)[0] # Order by most popular if contract not in self.addedContracts: self.addedContracts.add(contract.Symbol) # Use AddFutureContract() to subscribe the data for a specified contract future_cont = self.AddFutureContract(contract.Symbol) return contract return None