| 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