| Overall Statistics |
|
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0.164 Tracking Error 0.18 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% |
# region imports
from AlgorithmImports import *
# endregion
class HyperActiveYellowGreenFalcon(QCAlgorithm):
# Daily Trade Volume Threshold
VolumeThreshold = 2e6
# Daily Minutes Traded Threshold
MinuteThreshold = 180
# Timedelta To Create Consolidator
AvergCosolidatorTime = timedelta(minutes=30)
# NOTE: Check the notebook, I selected these ranges based on the dataframe at the bottom
# Tresholds For Leverage
Leverage_Lower_Threshold = 0.00018/100
Leverage_Upper_Threshold = 0.0002/100
# No leverage thresholds
Lower_Threshold = 0.00014/100
Upper_Threshold = 0.00016/100
# Leverages
MyLeverage = 2
DefaultLeverage = 1
# Maximum number of tickers to select
NumberOfTickers = 20
# Ratios Top and Low Top:
# Take 50% of number of tickers with the greater Close Ratio
# and the other 50% with the lowest
NTopRatio = 0.5
NLowRatio = 0.5
def Initialize(self) -> None:
self.SetStartDate(2021, 9, 17) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
## Universe Settings
# This allow a minute data feed resolution
self.UniverseSettings.Resolution = Resolution.Minute
# Add coarse universe selection model
self.AddUniverseSelection(VolumeUniverseSelectionModel(volume_th=self.VolumeThreshold))
# Custom Alpha Model
self.AddAlpha(CloseRatioAlphaModel(NumberOfTickers=20,
AvergCosolidatorTime = self.AvergCosolidatorTime, MinuteThreshold=self.MinuteThreshold,
Leverage_Lower_Threshold = self.Leverage_Lower_Threshold, Leverage_Upper_Threshold = self.Leverage_Upper_Threshold,
Lower_Threshold = self.Lower_Threshold, Upper_Threshold = self.Upper_Threshold,
MyLeverage = self.MyLeverage, DefaultLeverage = self.DefaultLeverage,
NTopRatio = self.NTopRatio, NLowRatio = self.NLowRatio
)
)
# EqualWeightingPortfolioConstructionModel
# Set the rebalance to the same period that we are consolidating bars
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(rebalance = self.AvergCosolidatorTime))
# Keeps record of the SelctedData instances assigned
self.SecuritiesTracker = dict()
## SECURITIES LOGIC: CREATION, INDICATORS, UPDATE, TACKING
def ManageAdded(self, added:list):
'''
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] = None
def ManageRemoved(self,removed:list):
'''
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 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.
self.ManageAdded(changes.AddedSecurities)
# The removed securities are liquidated and removed from the security tracker.
self.ManageRemoved(changes.RemovedSecurities)
## POSITION MANAGEMENT
def OnData(self, data:Slice):
for symbol, tracker in self.SecuritiesTracker.items():
exchange_hours = self.Securities[symbol].Exchange.Hours
within_half = (exchange_hours.GetNextMarketClose(self.Time, extendedMarket=False) - self.Time).seconds <= 60*30
if data.ContainsKey(symbol) and exchange_hours.IsOpen(self.Time, extendedMarket=False) and within_half: # Market in last 30 minutes
if self.Portfolio[symbol].Invested:
self.Liquidate(symbol)
## -------------------------------------------------------------------------- UNIVERSE SELECTION MODEL --------------------------------------------------------------------- ##
## Due to the required minute data resolution for security selection
## It was necesary to implement the Number of traded minutes on the main algorithm
class VolumeUniverseSelectionModel(FineFundamentalUniverseSelectionModel):
# Reference to FineFundamental Universes ->: https://www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/universe-selection/fundamental-universes
def __init__(self, volume_th:float,
universe_settings: UniverseSettings = None) -> None:
super().__init__(self.SelectCoarse, self.SelectFine, universe_settings)
# Volume threshold
self.volume_th = volume_th
# Store the securities with SelectionData objects created
self.Windows = dict()
def SelectCoarse(self, coarse: List[CoarseFundamental]) -> List[Symbol]:
# Reference to Coarse filters ->: https://www.quantconnect.com/docs/v2/writing-algorithms/universes/equity
#1. Filt to securities with fundamental data
tickers = [c for c in coarse if c.HasFundamentalData]
#2. Filt securities with trade volume greater than <volume_th>
by_volume = filter(lambda x: x.Volume > self.volume_th, tickers)
return [c.Symbol for c in by_volume]
def SelectFine(self, fine: List[FineFundamental]) -> List[Symbol]:
# Reference to Fine filters ->: https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/morningstar/us-fundamental-data#01-Introduction
#1. Filt markets: Docs reference ->: https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/morningstar/us-fundamental-data#06-Data-Point-Attributes
return [x.Symbol for x in fine if x.SecurityReference.ExchangeId in ["NAS", "ASE"]]
## -------------------------------------------------------------------------- ALPHA MODEL -------------------------------------------------------------------------- ##
class CloseRatioAlphaModel(AlphaModel):
'''Base de insights on the Close prices ratio between time/(time-1)'''
def __init__(self, NumberOfTickers: int,
AvergCosolidatorTime: timedelta, MinuteThreshold:float,
Leverage_Lower_Threshold:float, Leverage_Upper_Threshold: float,
Lower_Threshold:float, Upper_Threshold: float,
MyLeverage:float = 2, DefaultLeverage:int=1,
NTopRatio:float= 0.5, NLowRatio:float=0.5):
'''
Inputs:
- NumberOfTickers: Maximum number of tickers to select. The real selected amount depends of the filters as well.
- NTopRatio and NLowRatio: Should sum up to 1 as a percentage of Number of tickers.
Example: If NumberOfTickers = 10 and NTopRatio, NLowRatio equals to 0.5 each,
5 tickers will be selected for long and 5 for short
- AvergCosolidatorTime: Timedelta To Create Consolidator
- MinuteThreshold: Daily Minutes Traded Threshold
- Leverage_Lower_Threshold - Leverage_Upper_Threshold: Create the range for leveraged insights.
- Lower_Threshold - Upper_Threshold: Create the range for normal insights.
- MyLeverage: Leverage for leveraged insights.
'''
# NumberOfTickers
assert NTopRatio + NLowRatio <= 1, 'NTopRatio and NLowRatio should sum up to 1 as a percentage of Number of tickers'
self.NTopRatio = int(NumberOfTickers * NTopRatio)
self.NLowRatio = int(NumberOfTickers * NLowRatio)
# For securities selection
self.AvergCosolidatorTime = AvergCosolidatorTime
self.MinuteThreshold = MinuteThreshold
# Set ranges
self.Leverage_Lower_Threshold = Leverage_Lower_Threshold
self.Leverage_Upper_Threshold = Leverage_Upper_Threshold
self.Lower_Threshold = Lower_Threshold
self.Upper_Threshold = Upper_Threshold
# Leverages
self.MyLeverage = MyLeverage
self.DefaultLeverage = DefaultLeverage
# Keeps record of the SelctedData instances assigned
self.SecuritiesTracker = dict()
## SECURITIES LOGIC: CREATION, INDICATORS, UPDATE, TACKING
def ManageAdded(self, algorithm:QCAlgorithm, 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] = SelectionData(algorithm,
security, algorithm.get_Time,
self.AvergCosolidatorTime,
self.MinuteThreshold)
def ManageRemoved(self, algorithm:QCAlgorithm, removed):
'''
Logic for securities removed. Remove the SymbolData objects.
Inputs:
removed [list]: List of removed securities
'''
for security in removed: # Don't track anymore
algorithm.SubscriptionManager.RemoveConsolidator(security.Symbol, self.SecuritiesTracker[security.Symbol].consolidator)
self.SecuritiesTracker.pop(security.Symbol, None)
def OnSecuritiesChanged(self, algorithm:QCAlgorithm, 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.
self.ManageAdded(algorithm, changes.AddedSecurities)
# The removed securities are liquidated and removed from the security tracker.
self.ManageRemoved(algorithm, changes.RemovedSecurities)
## POSITION MANAGEMENT
def CheckLeveraged(self,value:float, sign:int):
return sign*self.Leverage_Lower_Threshold < value < sign*self.Leverage_Upper_Threshold
def CheckTreshold(self, value:float, sign:int):
return sign*self.Lower_Threshold < value < sign*self.Upper_Threshold
def UpdateWindow(self, algorithm: QCAlgorithm, data:Slice):
for symbol, tracker in self.SecuritiesTracker.items():
exchange_hours = algorithm.Securities[symbol].Exchange.Hours
within_half = (exchange_hours.GetNextMarketClose(self.Time, extendedMarket=False) - self.Time).seconds <= 60*30
if data.ContainsKey(symbol) and exchange_hours.IsOpen(self.Time, extendedMarket=False) and not(within_half): # Market in last 30 minutes
# Update Tracked Windows
tracker.UpdateWindow(data.Bars[symbol])
def UpdateState(self, algorithm: QCAlgorithm, data:Slice):
rank = {}
for symbol, tracker in self.SecuritiesTracker.items():
exchange_hours = algorithm.Securities[symbol].Exchange.Hours
within_half = (exchange_hours.GetNextMarketClose(self.Time, extendedMarket=False) - self.Time).seconds <= 60*30
if data.ContainsKey(symbol) and exchange_hours.IsOpen(self.Time, extendedMarket=False) and not(within_half): # Market in last 30 minutes
# Filt by the Daily Minutes Traded Threshold
if tracker.IsReady and tracker.IsSelected(exchange_hours.GetPreviousTradingDay(self.Time).day):
rank[symbol] = tracker.LastRatio
return sorted(rank.items(), key=lambda item: item[1],reverse=True)
def TopRanked(self,algorithm: QCAlgorithm, rank:list):
insights = []
for symbol,ratio in rank:
if self.CheckLeveraged(ratio, 1) or (((self.Time.today().weekday() == 0) or (self.Time.hour == 11)) and self.CheckTreshold(ratio, 1)):
insights.append(Insight.Price(symbol, self.AvergCosolidatorTime,
direction=InsightDirection.Up, magnitude=ratio, confidence=1))
# Change Leverage
algorithm.Securities[symbol].SetLeverage(self.MyLeverage)
elif self.CheckTreshold(ratio, 1):
insights.append(Insight.Price(symbol, self.AvergCosolidatorTime,
direction=InsightDirection.Up, magnitude=ratio, confidence=0.8))
# Change Leverage
algorithm.Securities[symbol].SetLeverage(self.DefaultLeverage)
return insights
def TopLowRanked(self, algorithm: QCAlgorithm, rank:list):
insights = []
for symbol,ratio in rank:
if self.CheckLeveraged(ratio, -1) or (((self.Time.today().weekday() == 0) or (self.Time.hour == 11)) and self.CheckTreshold(ratio, -1)):
insights.append(Insight.Price(symbol, self.AvergCosolidatorTime,
direction=InsightDirection.Down, magnitude=ratio, confidence=1))
# Change Leverage
algorithm.Securities[symbol].SetLeverage(self.MyLeverage)
elif self.CheckTreshold(ratio, -1):
insights.append(Insight.Price(symbol, self.AvergCosolidatorTime,
direction=InsightDirection.Down, magnitude=ratio, confidence=0.8))
# Change Leverage
algorithm.Securities[symbol].SetLeverage(self.DefaultLeverage)
return insights
def Update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]:
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
self.Time = algorithm.Time
insights =[]
rank = self.UpdateWindow(algorithm,data)
if self.Time.minute % 30 == 0: # Perform Only every half hour
rank = self.UpdateState(algorithm,data)
lenght = len(rank)
# NOTE: The minimum between the target amount and the half of tickers will be selected
if lenght >= 2:
top = rank[:min(self.NTopRatio,lenght//2)]
topLow = rank[::-1][:min(self.NLowRatio,lenght//2)]
# Create Insights
insights += self.TopRanked(algorithm,rank)
insights += self.TopLowRanked(algorithm,rank)
# NOTE: If there is only one ticket both conditions will be evaluated.
else:
insights += self.TopRanked(algorithm, rank)
if not(insights):
insights += self.TopLowRanked(algorithm,rank)
return insights
## -------------------------------------------------------------------------- SYMBOL DATA: HELPER --------------------------------------------------------------------- ##
class SelectionData:
# Reference to RollingWindows ->: https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/rolling-window
# Reference to Type of Consolidator used ->: https://www.quantconnect.com/docs/v2/writing-algorithms/consolidating-data/consolidator-types/time-period-consolidators
## INITIALIZATION:
def __init__(self, algorithm: QCAlgorithm,
security, time: QCAlgorithm.get_Time,
consolidator_time:timedelta,
minute_th:int):
self.Security = security
self.Symbol = security.Symbol
# Reference to main algorithm time
self.get_Time = time
# Minutes traded threshold
self.minute_th = minute_th
# Window: Average Trading Hours assumed as six
# Two days are stored due to the possible multiple calls intra-day
self.Window = RollingWindow[TradeBar](12*60)
self.WarmUpWindow(algorithm,12*60)
# Create Average Close Ratio
self.HalfHourWindow = RollingWindow[float](2)
self.CloseRatioWindow = RollingWindow[float](4)
# Create a 30 Minutes consolidated bar
self.InitConsolidator(algorithm, consolidator_time)
# DayTracked will help us reduce compute overhead
self.DayTracked = {}
@property
def Time(self)-> datetime:
# Allow access to the Time object directly
return self.get_Time()
def WarmUpWindow(self, algorithm: QCAlgorithm, period:int):
# Manually WarmUp Rolling Window: This is not efficiente
# but allows a faster response time to an addes security
history_trade_bar = algorithm.History[TradeBar](self.Symbol, period, Resolution.Minute)
for trade_bar in history_trade_bar:
self.Window.Add(trade_bar)
def InitConsolidator(self, algorithm: QCAlgorithm, consolidator_time:timedelta):
self.consolidator = TradeBarConsolidator(consolidator_time)
self.consolidator.DataConsolidated += self.UpdateIndicators
algorithm.SubscriptionManager.AddConsolidator(self.Symbol, self.consolidator)
def UpdateIndicators(self, sender: object, consolidated_bar: TradeBar) -> None:
# Record two half hour cosolidated bars
self.HalfHourWindow.Add(consolidated_bar.Close)
# Create the ratio
if self.HalfHourWindow.IsReady:
# Bar[t]/Bar[t-1]
self.CloseRatioWindow.Add(self.HalfHourWindow[0]/self.HalfHourWindow[1])
## POSITION MANAGEMENT
def UpdateWindow(self, bar):
self.Window.Add(bar)
@property
def IsReady(self):
return self.Window.IsReady and self.HalfHourWindow.IsReady and self.CloseRatioWindow.IsReady
@property
def LastRatio(self):
if not(self.IsReady):
return 0
return self.CloseRatioWindow[0]
def IsSelected(self, past_day) -> bool:
if not self.IsReady:
return False
tracked = self.DayTracked.get(past_day)
if tracked is not None:
return tracked
else:
# Reset
self.DayTracked = {}
# If past day meets the minutes traded threshold requirment
# Filt to the past trade day and Trade Volume > 0
my_filter = lambda bar: (bar.EndTime.day == past_day) and (bar.Volume > 0)
# Number of bars with at least some trade in the las day
self.DayTracked[past_day] = len(list(filter(my_filter, self.Window))) >= self.minute_th
return self.DayTracked[past_day]