Overall Statistics
Total Trades
9
Average Win
0.09%
Average Loss
-0.78%
Compounding Annual Return
-1.662%
Drawdown
3.400%
Expectancy
-0.629
Net Profit
-1.049%
Sharpe Ratio
-0.434
Probabilistic Sharpe Ratio
12.912%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
0.11
Alpha
-0.019
Beta
0.011
Annual Standard Deviation
0.037
Annual Variance
0.001
Information Ratio
-2.459
Tracking Error
0.133
Treynor Ratio
-1.425
Total Fees
$47.33
Estimated Strategy Capacity
$9900000.00
Lowest Capacity Asset
IEF SGNKIKYGE9NP
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import AlphaModel, Insight, InsightType, InsightDirection

import numpy as np

class LongShortMovingAverageCrossoverAlphaCreationModel(AlphaModel):
    
    '''
    * Refer to the research notebook for a visual explanation of this alpha logic
    Description:
        This Alpha model creates InsightDirection.Up to go Long when a Short Moving Average crosses above a Long Moving Average,
        and InsightDirection.Down to go Short when it crosses below
    Details:
        The important things to understand here are:
            - We can retrieve historical data by calling algorith.History(symbol, bar_count, resolution)
            - We can easily orginise the code in Python with a class to store calculations for indicators for each symbol
            - We can use InsightDirection.Up/InsightDirection.Down to go Long/Short
    '''

    def __init__(self, algorithm, name, tickers, shortPeriodSMA = 50, longPeriodSMA = 200, resolution = Resolution.Daily):
        
        self.algo = algorithm
        self.Name = name
        self.tickers = tickers
        
        self.shortPeriodSMA = shortPeriodSMA # period for short moving average
        self.longPeriodSMA = longPeriodSMA # period for long moving average
        self.resolution = resolution # resolution for historical data
        
        self.securities = [] # list to store securities to consider
        self.calculations = {} # store calculations
        
        self.weightsDict = {}
        self.scheduleSymbol = None
        self.scheduledEvent = None
        self.timeToEmitInsights = False
        
    def Update(self, algorithm, data):
        
        insights = []
        if not self.timeToEmitInsights:
            return insights
        
        insightExpiry = Expiry.EndOfDay(self.algo.Time) + timedelta(days=1)
        
        # loop through insights and send portfolio targets
        for symbol, weight in self.weightsDict.items():
            if weight > 0:
                insightDirection = InsightDirection.Up
            elif weight < 0:
                insightDirection = InsightDirection.Down
            else:
                insightDirection = InsightDirection.Flat
                
            insights.append(Insight.Price(symbol, insightExpiry, insightDirection, None, None, None, abs(weight) * 0.2))
                            
        self.timeToEmitInsights = False
        
        return insights
        
    def EveryDayAtOpen(self):
        
        """ Create new signals """
        
        self.weightsDict = {}
        
        # get the symbols for which we have already calculate indicators to simply add last data point to update them
        # we separate this from new symbols to avoid calling full history for all securities every time
        currentSymbols = [x.Symbol for x in self.securities if x.Symbol in self.calculations.keys()]
        if len(currentSymbols) > 0:
            historyCurrentSymbols = self.algo.History(currentSymbols, 1, self.resolution)
        
        # get the new symbols for which we need to warm up indicators from scratch
        newSymbols = [x.Symbol for x in self.securities if x.Symbol not in self.calculations.keys()]
        if len(newSymbols) > 0:
            historyNewSymbols = self.algo.History(newSymbols, self.longPeriodSMA + 1, self.resolution)
        
        # now loop through securities to create/update indicators
        for security in self.securities:
            if security.Symbol in newSymbols:
                self.calculations[security.Symbol] = SymbolData(security.Symbol, self.shortPeriodSMA, self.longPeriodSMA)
                history = historyNewSymbols
            else:
                history = historyCurrentSymbols
            try:
                self.calculations[security.Symbol].UpdateIndicators(history)
            except Exception as e:
                self.algo.Log('removing from calculations due to ' + str(e))
                self.calculations.pop(security.Symbol, None)
        
        # loop through active securities and generate insights
        for symbol, symbolData in self.calculations.items():
            # if short sma just crossed above long sma, we go long with an InsightDirection.Up
            # or if no cross happened but we are currently Long, update the InsightDirection.Up to stay Long for another bar
            if symbolData.crossAbove or self.algo.Portfolio[symbol].IsLong:
                self.weightsDict[symbol] = 1
            
            # if short sma just crossed below long sma, we go short with an InsightDirection.Down
            # or if no cross happened but we are currently Short, update the InsightDirection.Down to stay Short for another bar
            elif symbolData.crossBelow or self.algo.Portfolio[symbol].IsShort:
                self.weightsDict[symbol] = -1
                
            # if no cross has happened and we are not invested, emit an InsightDirection.Flat to stay in cash for another bar
            else:
                self.weightsDict[symbol] = 0
                
        self.timeToEmitInsights = True
                
    def OnSecuritiesChanged(self, algorithm, changes):

        """ Actions to take every time the universe changes """

        for security in changes.RemovedSecurities:
            self.securities.remove(security)
            self.calculations.pop(security.Symbol, None)
                
            symbol = security.Symbol
            if symbol == self.scheduleSymbol:
                # remove scheduled event
                algorithm.Schedule.Remove(self.scheduledEvent)
                self.scheduleSymbol = None
                self.scheduledEvent = None

        for security in changes.AddedSecurities:
            if security.Symbol.Value in self.tickers:
                self.securities.append(security)
                
                symbol = security.Symbol
                if self.scheduleSymbol is None:
                    # add scheduled event
                    self.scheduleSymbol = symbol
                    self.scheduledEvent = algorithm.Schedule.On(algorithm.DateRules.EveryDay(symbol),
                                                                algorithm.TimeRules.AfterMarketOpen(symbol, 1),
                                                                self.EveryDayAtOpen)

# this class is coming from the research nothebook (check its logic there)
class SymbolData:
    
    '''
    make all the calculations needed for each symbol including
    all the indicators and whether the ticker meets the criteria
    '''
    
    def __init__(self, symbol, shortPeriodSMA, longPeriodSMA):
        self.Symbol = symbol
        self.shortPeriod = shortPeriodSMA
        self.longPeriod = longPeriodSMA
        self.closePrices = RollingWindow[float](longPeriodSMA + 1)
    
    # method to update the rolling window
    def UpdateIndicators(self, history):
        if str(self.Symbol) in history.index:
            for index, row in history.loc[str(self.Symbol)].iterrows():
                if 'close' in row:
                    self.closePrices.Add(row['close'])
                else:
                    raise Exception('missing some close prices for: ' + str(self.Symbol.Value))
        else:
            raise Exception('symbol not in history index: ' + str(self.Symbol.Value))
    
    # convert the rolling window to list for easier manipulation
    @property
    def listClosePrices(self):
        if self.closePrices.IsReady:
            return [float(x) for x in self.closePrices]
        else:
            return [0]
    
    # update short and long current SMA
    @property
    def currentShortSMA(self):
        return np.mean(self.listClosePrices[:self.shortPeriod])
    @property
    def currentLongSMA(self):
        return np.mean(self.listClosePrices[:self.longPeriod])
    
    # update short and long before SMA (the SMA from the previous trading bar)
    @property
    def beforeShortSMA(self):
        return np.mean(self.listClosePrices[1:][:self.shortPeriod])
    @property
    def beforeLongSMA(self):
        return np.mean(self.listClosePrices[1:][:self.longPeriod])
    
    # update boolean for cross above/below of moving averages
    @property
    def crossAbove(self):
        return (self.currentShortSMA > self.currentLongSMA) and (self.beforeShortSMA < self.beforeLongSMA)
    @property
    def crossBelow(self):
        return (self.currentShortSMA < self.currentLongSMA) and (self.beforeShortSMA > self.beforeLongSMA)
from datetime import datetime
import pandas as pd
import numpy as np
from io import StringIO

class DropboxUniverseModule:
    
    '''
    Description:
        Provide an implementation of a DropboxUniverseModule that extracts the components, at each point in history,
        of the index SP500 or NASDAQ100 from a file in a Dropbox location.
        It then sorts the tickers by MarketCap in desired order (descending/ascending) and returns a list with the first n tickers.
    '''
    
    def __init__(self, getIndex = 'sp500', sortMarketCap = 'descending', keepSortedMarketCap = 100):
        
        self.keepSortedMarketCap = keepSortedMarketCap
        self.downloadCheck = True
        
        if getIndex == 'sp500':
            self.relevantLink = 'https://www.dropbox.com/s/gjq97ts801692hw/SP500.csv?dl=1' # sp500
        elif getIndex == 'nasdaq100':
            self.relevantLink = 'https://www.dropbox.com/s/7d36djet5gghwej/NASDAQ.csv?dl=1' # nasdaq100
        else:
            raise ValueError('parameter getIndex must be either sp500 or nasdaq100')
            
        if sortMarketCap == 'descending':
            self.sortingRule = False
        elif sortMarketCap == 'ascending':
            self.sortingRule = True
        else:
            raise ValueError('parameter sortMarketCap must be either descending or ascending')

    def GetFileDf(self, algorithm):
        
        ''' Read external Dropbox file and create a dataframe '''
        
        # if Backtesting we will only read it once
        # if Live we will read it at every iteration (we will need to adjust this for higher frequencies later)
        if self.downloadCheck or algorithm.LiveMode:
            try:
                # download the file from the dropbox link (this link has to stay the same)
                strFile = algorithm.Download(self.relevantLink)
                
                # read the strFile as csv
                self.fileDf = pd.read_csv(StringIO(strFile), sep = ',')
                
                # convert date column to datetime
                self.fileDf['Date'] = pd.to_datetime(self.fileDf['Date'])
                
                algorithm.Log('file successfully imported!')
                
            except:
                algorithm.Log('file import failed!')
                
            self.downloadCheck = False
            
        return self.fileDf
            
    def GetCurrentDf(self, algorithm):
        
        ''' Filter the fileDf by the most recent available date '''
        
        # get the fileDf dataframe
        fileDf = self.GetFileDf(algorithm)
        
        # get today datetime
        todayDateTime = datetime(algorithm.Time.year, algorithm.Time.month, algorithm.Time.day, 0, 0, 0)
        # find the most recent date in the file
        lastDateForIndex = fileDf[fileDf['Date'] <= todayDateTime].iloc[-1]['Date']
            
        # filter the fileDf by lastDateForIndex
        currentDf = fileDf[fileDf['Date'] == lastDateForIndex]
        
        return currentDf
        
    def GetSortedMarketCapTickersList(self, algorithm):
        
        ''' Sort the currentDf in desired order (parameter sortMarketCap) by MarketCap and return the first n (parameter keepSortedMarketCap) tickers in a list '''
        
        # get the currentDf
        currentDf = self.GetCurrentDf(algorithm)
        
        # sort by MarketCap and keep first n tickers
        sortedMarketCapDf = currentDf.sort_values(by = ['MarketCap'], inplace = False, ascending = self.sortingRule)[:self.keepSortedMarketCap]
        
        # convert into a list
        sortedMarketCapTickersList = list(sortedMarketCapDf['Symbol'])
        
        return sortedMarketCapTickersList
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data.UniverseSelection import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel

import pandas as pd
import numpy as np
from itertools import groupby

from DropboxUniverseClass import DropboxUniverseModule

class MomentumWithDropboxUniverseSelectionModel(FundamentalUniverseSelectionModel):
    
    '''
    Description:
        
    Details:
    
    '''

    def __init__(self,
                getIndex = 'sp500',
                sortMarketCap = 'descending',
                keepSortedMarketCap = 100,
                updateUniverse = 'monthly',
                filterFineData = False,
                universeSettings = None,
                securityInitializer = None):
        
        super().__init__(filterFineData, universeSettings, securityInitializer)

        self.updateUniverse = updateUniverse
        
        self.periodCheck = -1 # initialize a variable to check when the period changes
        self.currentPeriod = -1 # initialize a variable to track the current period
        
        self.dropboxUniverseModule = DropboxUniverseModule(getIndex = getIndex, sortMarketCap = sortMarketCap, keepSortedMarketCap = keepSortedMarketCap)

    def SelectCoarse(self, algorithm, coarse):
        
        # check the current period
        if self.updateUniverse == 'monthly':
            self.currentPeriod = algorithm.Time.month
        elif self.updateUniverse == 'quarterly':
            self.currentPeriod = ((algorithm.Time.month - 1) // 3) + 1
        elif self.updateUniverse == 'yearly':
            self.currentPeriod = algorithm.Time.year
        else:
            raise ValueError('updateUniverse has to be one of either: monthly, quarterly or yearly')
            
        # this ensures the universe selection only runs once a period
        if self.currentPeriod == self.periodCheck:
            return Universe.Unchanged
        self.periodCheck = self.currentPeriod
        
        dropboxUniverseTickersList = self.dropboxUniverseModule.GetSortedMarketCapTickersList(algorithm)
        relevantSymbols = [x.Symbol for x in coarse if x.HasFundamentalData and x.Symbol.Value in dropboxUniverseTickersList]
        
        return relevantSymbols
from clr import AddReference

AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import AlphaModel, Insight, InsightType, InsightDirection

from OptimizerClass import PortfolioOptimizer

import numpy as np
import general_utils as utils


class RotationalOptimizerAlphaCreationModel(AlphaModel):

    def __init__(self, algorithm, name,
                lookbackOptimization = 252, objFunction = 'minVariance',
                tickerCash = None, dictParameters = None):

        self.algo = algorithm
        self.Name = name

        self.lookbackOptimization = lookbackOptimization
        self.objFunction = objFunction
        self.tickerCash = tickerCash
        self.dictParameters = dictParameters
        
        # initialize the optimizer
        self.optimizer = PortfolioOptimizer(minWeight=0, maxWeight=1)
            
        # get all the parameters for the indicators
        valuesList = []
        for ticker in dictParameters.keys():
            if dictParameters[ticker]['addTicker'][0]:
                valuesList.append( dictParameters[ticker]['sma'][0] )
                valuesList.append( sum(dictParameters[ticker]['macd'][0]) )
        
        # keep the highest parameter provided to call history
        self.lookbackHistory = max(lookbackOptimization, max(valuesList))

        self.weightsDict = {}
        self.scheduledEvent = None
        self.scheduleSymbol = None
        self.timeToEmitInsights = False

    def Update(self, algorithm, data):
        
        insights = []
        if not self.timeToEmitInsights:
            return insights
        
        insightExpiry = Expiry.EndOfMonth(self.algo.Time) + timedelta(days=10)
        
        # loop through insights and send portfolio targets
        for symbol, weight in self.weightsDict.items():
            insightDirection = InsightDirection.Up if weight > 0 else InsightDirection.Flat
            insights.append(Insight.Price(symbol, insightExpiry, insightDirection, None, None, None, abs(weight)))
                            
        self.timeToEmitInsights = False
        
        return insights

    def EveryMonth(self):

        """ Create new signals """
        
        # get active symbols for this alpha
        symbols = [x.Symbol for x in self.algo.ActiveSecurities.Values if x.Symbol.Value in self.dictParameters.keys() or x.Symbol.Value == self.tickerCash]

        # determine target percent for the given insights (check function DetermineTargetPercent for details)
        self.weightsDict = self.DetermineTargetPercent(self.algo, symbols)
        
        self.timeToEmitInsights = True

    def OnSecuritiesChanged(self, algorithm, changes):

        """ Actions to take every time the universe changes """

        for security in changes.RemovedSecurities:
            symbol = security.Symbol
            if symbol == self.scheduleSymbol:
                # remove scheduled event
                algorithm.Schedule.Remove(self.scheduledEvent)
                self.scheduleSymbol = None
                self.scheduledEvent = None

        for security in changes.AddedSecurities:
            symbol = security.Symbol
            if self.scheduleSymbol is None:
                # add scheduled event
                self.scheduleSymbol = symbol
                self.scheduledEvent = algorithm.Schedule.On(algorithm.DateRules.MonthStart(symbol),
                                                            algorithm.TimeRules.AfterMarketOpen(symbol, 1),
                                                            self.EveryMonth)
                                                             
    def DetermineTargetPercent(self, algorithm, symbols):
        
        """
        Description:
            Determine the target percent for each insight
        Args:
            algorithm: The algorithm instance
            symbols: The symbols to generate an insight for
        """
        
        # empty dictionary to store portfolio targets by symbol
        finalWeights = {}
        
        # get symbols for calculations
        calculationSymbols = [x for x in symbols if x.Value != self.tickerCash]
        
        # get historical data for calculationSymbols for the last n trading days
        history = algorithm.History(calculationSymbols, self.lookbackHistory, Resolution.Daily)
        
        # empty dictionary for calculations
        calculations = {}
        
        # iterate over all symbols and perform calculations
        for symbol in calculationSymbols:
            # check if we have enough historical data, otherwise just skip this security
            if not utils.CheckHistory(symbol, history):
                algorithm.Log(str(symbol.Value) + ': skipping security due to no/not enough historical data')
                continue
            else:
                # add symbol to calculations
                calculations[symbol] = SymbolData(symbol, dictParameters=self.dictParameters)
                try:
                    # get series of daily returns
                    calculations[symbol].CalculateDailyReturnSeries(history, self.lookbackOptimization)
                    # update technical indicators
                    calculations[symbol].UpdateIndicators(history)
                except:
                    algorithm.Log('returning empty weights for now due to calculations failing for: '
                    + str(symbol.Value) + '; we will try again at the next iteration')
                    return finalWeights
        
        # calculate optimal weights
        optWeights = self.CalculateOptimalWeights(algorithm, calculations)
        algorithm.Log('optimal weights for the period: ' + str(optWeights))
        
        if optWeights is None:
            algorithm.Log('returning empty weights due to optWeights being None; we will try again at the next iteration')
            return finalWeights
        
        # modify optimal weights using specific criteria
        finalWeights = self.FilterOptimalWeights(algorithm, calculations, optWeights)
        algorithm.Log('filtered optimal weights for the period: ' + str({symbol.Value: weight for symbol, weight in finalWeights.items()}))
            
        # check how much we have allocated so far to risky assets
        totalWeight = sum(finalWeights.values())
        if totalWeight >= 1:
            totalWeight = 1
        algorithm.Log('total allocation after weight filtering: ' + str(totalWeight))
        
        # allocate remaining cash to tickerCash
        for symbol in symbols:
            if symbol.Value == self.tickerCash:
                if symbol in finalWeights.keys():
                    finalWeights[symbol] = finalWeights[symbol] + (1 - totalWeight)
                else:
                    finalWeights[symbol] = 1 - totalWeight
                    
                algorithm.Log(str(self.tickerCash) + '; final allocation for tickerCash: ' + str(finalWeights[symbol]))
                
        # avoid very small numbers and make them 0
        for symbol, weight in finalWeights.items():
            if weight <= 1e-10:
                finalWeights[symbol] = 0
            
        return finalWeights
        
    def CalculateOptimalWeights(self, algorithm, calculations):
        
        """
        Description:
            Calculate the individual weights for each symbol that optimize some given objective function
        Args:
            algorithm: The algorithm instance
            calculations: Dictionary containing calculations for each symbol
        """
                
        # create a dictionary keyed by the symbols in calculations with a pandas.Series as value to create a dataframe of returns
        dailyReturnsDict = { symbol.Value: symbolData.dailyReturnsSeries for symbol, symbolData in calculations.items() }
        dailyReturnsDf = pd.DataFrame(dailyReturnsDict)
        listTickers = list(dailyReturnsDf.columns)
        
        try:
            # portfolio optimizer finds the optimal weights for the given data
            listOptWeights = self.optimizer.Optimize(objFunction=self.objFunction, dailyReturnsDf=dailyReturnsDf)
        except:
            algorithm.Log('optimization failed')
            return None
        
        # create dictionary with the optimal weights by symbol
        weights = {listTickers[i]: listOptWeights[i] for i in range(len(listTickers))}
        
        # avoid very small numbers and make them 0
        for ticker, weight in weights.items():
            if weight <= 1e-10:
                weights[ticker] = 0
            
        return weights
        
    def FilterOptimalWeights(self, algorithm, calculations, optWeights):
        
        """
        Description:
            Filter and modify the optimal weights using a combination of technical indicators
        Args:
            algorithm: The algorithm instance
            calculations: Dictionary containing calculations for each symbol
            optWeights: Dictionary with the optimal weights by symbol
        """
        
        # empty dicitonary to store weights
        weights = {}
        
        # loop through calculations and check conditions for weight filtering ------------------------
        for symbol, symbolData in calculations.items():
            if symbolData.SMA.IsReady and symbolData.MACD.IsReady:
                currentPrice = algorithm.ActiveSecurities[symbol].Price

                # check if sma condition is met and act accordingly ----------------------------------
                smaLowerBoundCondition = self.dictParameters[symbol.Value]['sma'][1][0]
                smaUpperBoundCondition = self.dictParameters[symbol.Value]['sma'][1][1]
                smaConditionWeight = self.dictParameters[symbol.Value]['sma'][2]
                
                algorithm.Log(str(symbol.Value)
                + '; current price: ' + str(round(currentPrice, 2))
                + '; SMA: ' + str(round(symbolData.SMA.Current.Value, 2))
                + '; Price vs SMA: ' + str(round((currentPrice / symbolData.SMA.Current.Value) - 1, 2)))
                
                if (currentPrice <= symbolData.SMA.Current.Value * (1 + smaLowerBoundCondition)
                or currentPrice >= symbolData.SMA.Current.Value * (1 + smaUpperBoundCondition)):
                    weights[symbol] = min(optWeights[symbol.Value], smaConditionWeight)
                    algorithm.Log(str(symbol.Value)
                    + '; modifying weight due to sma filtering from '
                    + str(optWeights[symbol.Value]) + ' to ' + str(weights[symbol]))
                else:
                    weights[symbol] = optWeights[symbol.Value]
                    
                smaModifiedWeight = weights[symbol]
                
                # check if macd condition is met and act accordingly ----------------------------------
                macdCondition = self.dictParameters[symbol.Value]['macd'][1]
                macdConditionWeight = self.dictParameters[symbol.Value]['macd'][2]
                # calculate our macd vs signal score between -1 and 1
                macdMinusSignal = symbolData.MACD.Current.Value - symbolData.MACD.Signal.Current.Value
                macdVsSignalScore = macdMinusSignal / (1 + abs(macdMinusSignal))
                
                algorithm.Log(str(symbol.Value)
                + '; MACD: ' + str(round(symbolData.MACD.Current.Value, 2))
                + '; MACD Signal: ' + str(round(symbolData.MACD.Signal.Current.Value, 2))
                + '; MACD vs Signal Score: ' + str(round(macdVsSignalScore, 2)))
                
                if macdVsSignalScore <= macdCondition:
                    weights[symbol] = min(smaModifiedWeight, macdConditionWeight)
                    algorithm.Log(str(symbol.Value)
                    + '; modifying weight due to macd filtering from '
                    + str(smaModifiedWeight) + ' to ' + str(weights[symbol]))
                else:
                    weights[symbol] = smaModifiedWeight
                    
                macdModifiedWeight = weights[symbol]
                    
            else:
                weights[symbol] = 0
                algorithm.Log(str(symbol.Value) + '; indicators are not ready so assign zero weight')

        return weights


class SymbolData:
    
    """ Contain data specific to a symbol required by this model """
    
    def __init__(self, symbol, dictParameters):
        
        self.Symbol = symbol
        self.dailyReturnsSeries = None
        
        smaPeriod = dictParameters[symbol.Value]['sma'][0]
        self.SMA = SimpleMovingAverage(smaPeriod)
        
        macdFastPeriod = dictParameters[self.Symbol.Value]['macd'][0][0]
        macdSlowPeriod = dictParameters[self.Symbol.Value]['macd'][0][1]
        macdSignalPeriod = dictParameters[self.Symbol.Value]['macd'][0][2]
        self.MACD = MovingAverageConvergenceDivergence(macdFastPeriod, macdSlowPeriod, macdSignalPeriod, MovingAverageType.Exponential)
    
    def CalculateDailyReturnSeries(self, history, lookbackOptimization):
        
        ''' Calculate the daily returns series for each security '''
        
        tempReturnsSeries = history.loc[str(self.Symbol)]['close'].pct_change(periods=1).dropna() # 1-day returns
        self.dailyReturnsSeries = tempReturnsSeries[-lookbackOptimization:]
        
    def UpdateIndicators(self, history):
        
        ''' Update the indicators with historical data '''
        
        for index, row in history.loc[str(self.Symbol)].iterrows():
            self.SMA.Update(index, row['close'])
            self.MACD.Update(index, row['close'])
from clr import AddReference

AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import Resolution, Extensions
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Portfolio import *

from itertools import groupby
from datetime import datetime, timedelta

from pytz import utc

UTCMIN = datetime.min.replace(tzinfo=utc)

import numpy as np
import pandas as pd
import general_utils as utils
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_colwidth', -1)

from OptimizerClass import PortfolioOptimizer


class CompositePortfolioConstructionModel(PortfolioConstructionModel):
    
    """
    Description:
        Implementation of CompositePortfolioConstructionModel
    """

    def __init__(self, algorithm, alphaModels, alphaPortfolioOptLookback=21, alphaPortfolioOptObjFunction='minVariance'):

        self.algo = algorithm
        
        self.modelsInsightReturns = {key: {} for key in alphaModels}  # nested dict for insight returns
        self.modelsDailyReturns = {key: {} for key in alphaModels}  # nested dict for daily returns
        self.modelsCumReturns = {key: 0.0 for key in alphaModels}  # store models cumulative returns
        self.modelsAllocation = {key: 0.0 for key in alphaModels}  # store models daily optimal allocations
        self.modelsAccuracy = {key: 0.0 for key in alphaModels}  # store models accuracy

        self.alphaPortfolioOptLookback = alphaPortfolioOptLookback  # number of days for rolling window of returns
        self.alphaPortfolioOptObjFunction = alphaPortfolioOptObjFunction
        self.optAlphaAllocationReady = False

        self.optimizer = PortfolioOptimizer(minWeight=0.05, maxWeight=1)

        self.insightCollection = InsightCollection()
        self.removedSymbols = []
        self.nextExpiryTime = UTCMIN

        self.entryDirection = {}  # hold entry direction for each symbol
        self.previousPrice = {}
        
    def CreateTargets(self, algorithm, insights):

        """
        Description:
            Create portfolio targets from the specified insights
        Args:
            algorithm: The algorithm instance
            insights: The insights to create portfolio targets from
        Returns:
            An enumerable of portfolio targets to be sent to the execution model
        """

        targets = []

        # create flatten target for each security that was removed from the universe
        if self.removedSymbols is not None:
            universeDeselectionTargets = [PortfolioTarget(symbol, 0) for symbol in self.removedSymbols]
            algorithm.Log('liquidate any open positions for removed symbols: ' + str([x.Value for x in self.removedSymbols]))
            targets.extend(universeDeselectionTargets)
            self.removedSymbols = None

        # get insights that haven't expired of each symbol that is still in the universe
        self.insightCollection.AddRange(insights)
        activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime)

        # more than one alpha model can generate insights for the same symbol and even at the same time
        # we will prioritise the insights coming from the model with the largest optimal allocation and cumulative returns (in case of same allocation)
        # and then we need to sort by GeneratedTimeUtc to get the latest generated insight for the symbol
        # note we're sorting by model at the end to break ties at the start of the backtest when all the above things could be the same
        self.lastActiveInsights = []
        for symbol, g in groupby(activeInsights, lambda x: x.Symbol):
            self.lastActiveInsights.append(sorted(g, key=lambda x: (self.modelsAllocation[x.SourceModel],
                                                                    self.modelsCumReturns[x.SourceModel],
                                                                    x.GeneratedTimeUtc,
                                                                    x.SourceModel), reverse=True)[0])

        errorSymbols = {}
        if self.ShouldCreateTargets(algorithm, self.lastActiveInsights):
            # calculate targets ------------------------------------------------
            for insight in self.lastActiveInsights:
                if insight.Symbol not in algorithm.ActiveSecurities.Keys:
                    continue

                # check if we want to do optimal alpha allocation and if we have enough data for it
                if self.optAlphaAllocationReady:
                    # insight weight * the optimal allocation for the model
                    weight = insight.Weight * self.modelsAllocation[insight.SourceModel]
                else:
                    weight = insight.Weight * 1 / len(self.modelsInsightReturns.keys())

                if insight.Direction == InsightDirection.Up:
                    self.entryDirection[insight.Symbol] = insight.Direction
                    self.previousPrice[insight.Symbol] = algorithm.ActiveSecurities[insight.Symbol].Price
                    tradeType = "long"
                    
                elif insight.Direction == InsightDirection.Down:
                    self.entryDirection[insight.Symbol] = insight.Direction
                    self.previousPrice[insight.Symbol] = algorithm.ActiveSecurities[insight.Symbol].Price
                    tradeType = "short"
                    
                # if direction is flat, we close the position due to risk management triggering and assign 0 weight
                elif insight.Direction == InsightDirection.Flat:
                    tradeType = "closing"
                    weight = 0

                    # update returns if we closed position with flat insight
                    if algorithm.ActiveSecurities[insight.Symbol].Invested:
                        self.UpdateReturnsDictionaries(algorithm, insight)

                # calculate the percent target
                target = PortfolioTarget.Percent(algorithm, insight.Symbol, insight.Direction * weight)

                if target is not None:
                    targets.append(target)

                    algorithm.Log(str(insight.SourceModel) + ': '
                                    + str(tradeType) + ' position for ' + str(insight.Symbol.Value)
                                    + '; total target: ' + str(round(weight, 2)))
                else:
                    errorSymbols[insight.Symbol] = insight.Symbol

        # get expired insights and create flatten targets for each symbol ----------------------------------------------
        collectionExpiredInsights = self.insightCollection.RemoveExpiredInsights(algorithm.UtcTime)

        expiredInsights = []
        for symbol, g in groupby(collectionExpiredInsights, lambda x: x.Symbol):
            expiredInsights.append(sorted(g, key=lambda x: (self.modelsAllocation[x.SourceModel],
                                                            self.modelsCumReturns[x.SourceModel],
                                                            x.GeneratedTimeUtc,
                                                            x.SourceModel), reverse=True)[0])

        expiredTargets = []
        for insight in expiredInsights:
            if (not self.insightCollection.HasActiveInsights(insight.Symbol, algorithm.UtcTime)
                    and insight.Symbol not in errorSymbols and insight.Symbol in algorithm.ActiveSecurities.Keys):
                expiredTargets.append(PortfolioTarget(insight.Symbol, 0))

                algorithm.Log(str(insight.SourceModel) + ': end of day closing position for ' + str(insight.Symbol.Value))

                # update returns if we closed position after insight expired
                if algorithm.ActiveSecurities[insight.Symbol].Invested:
                    self.UpdateReturnsDictionaries(algorithm, insight)

        targets.extend(expiredTargets)

        # update next expiry time for each active insight
        self.nextExpiryTime = self.insightCollection.GetNextExpiryTime()
        if self.nextExpiryTime is None:
            self.nextExpiryTime = UTCMIN

        return targets
    
    def EveryDayAtOpen(self):

        """ Update dictionaries every day at the open """

        self.todayDate = date(self.algo.Time.year, self.algo.Time.month, self.algo.Time.day)

        # calculate the optimal weights ------------------------------------
        # create a dataframe of returns of last n days from the dictionary modelsDailyReturns
        dailyReturnsDf = pd.DataFrame(self.modelsDailyReturns)[-self.alphaPortfolioOptLookback:]
        dailyReturnsDf = dailyReturnsDf.dropna()

        # wait until we have a week of returns
        optWeights = pd.Series(0)
        if len(dailyReturnsDf) >= 5:
            try:
                optWeights = self.optimizer.Optimize(objFunction=self.alphaPortfolioOptObjFunction, dailyReturnsDf=dailyReturnsDf)
                optWeights = pd.Series(optWeights, index=dailyReturnsDf.columns)
                self.algo.Log('optimal weights for alpha models: ' + '\n' + str(optWeights))
            except BaseException as e:
                self.algo.Log('Optimize failed due to ' + str(e))

        # for each alpha model ---------------------------------------------
        for key in self.modelsInsightReturns:
            # initialize today empty list for models insight returns and daily returns
            self.modelsInsightReturns[key][self.todayDate] = []
            self.modelsDailyReturns[key][self.todayDate] = np.nan

            # update the cumulative returns for each alpha model and plot it
            filteredDailyReturns = pd.DataFrame.from_dict({key: value for key, value in self.modelsDailyReturns[key].items() if not np.isnan(value)}, orient='index')
            self.modelsCumReturns[key] = filteredDailyReturns.add(1).cumprod().iloc[-1] if not filteredDailyReturns.empty else 1
            self.algo.Plot('Alphas Cumulative Returns', key, float(self.modelsCumReturns[key]))

            # get the optimal allocation per alpha and plot it
            self.optAlphaAllocationReady = False
            if all(x > 0 for x in optWeights):
                self.optAlphaAllocationReady = True
                self.modelsAllocation[key] = optWeights[key]
                self.algo.Plot('Alphas Allocation', key, float(self.modelsAllocation[key]))

            # calculate the accuracy per model for last n insights and plot it
            # these are the individual returns of last n insights (we're using lookback times X, assuming X insights per day)
            flatReturns = [val for sublist in self.modelsInsightReturns[key].values() for val in sublist][-(self.alphaPortfolioOptLookback * 5):]
            if len(flatReturns) > 0:
                self.modelsAccuracy[key] = sum(x > 0 for x in flatReturns) / len(flatReturns)
                self.algo.Plot('Alphas Accuracy', key, float(self.modelsAccuracy[key]))
        
    def EveryDayAtClose(self):

        """ Update dictionaries every day at the close """
        
        for insight in self.lastActiveInsights:
            if insight.Symbol not in self.algo.ActiveSecurities.Keys or not self.algo.ActiveSecurities[insight.Symbol].Invested:
                continue

            # update returns if we closed position with flat insight
            self.UpdateReturnsDictionaries(self.algo, insight)
            
            self.previousPrice[insight.Symbol] = self.algo.ActiveSecurities[insight.Symbol].Price
        
    def OnSecuritiesChanged(self, algorithm, changes):

        """ Actions to take every time the universe changes """
        
        # get removed symbol and invalidate them in the insight collection
        self.removedSymbols = [x.Symbol for x in changes.RemovedSecurities]
        self.insightCollection.Clear(self.removedSymbols)

        for security in changes.AddedSecurities:
            symbol = security.Symbol
            if symbol.Value == 'SPY':
                algorithm.Schedule.On(algorithm.DateRules.EveryDay(symbol),
                                        algorithm.TimeRules.AfterMarketOpen(symbol, 1),
                                                            self.EveryDayAtOpen)
                algorithm.Schedule.On(algorithm.DateRules.EveryDay(symbol),
                                        algorithm.TimeRules.BeforeMarketClose(symbol, 1),
                                                            self.EveryDayAtClose)

    def ShouldCreateTargets(self, algorithm, lastActiveInsights):

        """
        Description:
            Determine whether we should rebalance the portfolio when:
                - We want to include some new security in the portfolio
                - We want to modify the direction of some existing security
        Args:
            lastActiveInsights: The last active insights to check
        """

        for insight in lastActiveInsights:
            # if there is an insight for a new security that's not invested, then rebalance
            if not algorithm.Portfolio[insight.Symbol].Invested and insight.Direction != InsightDirection.Flat:
                return True
            # if there is an insight to close a long position, then rebalance
            elif algorithm.Portfolio[insight.Symbol].IsLong and insight.Direction != InsightDirection.Up:
                return True
            # if there is an insight to close a short position, then rebalance
            elif algorithm.Portfolio[insight.Symbol].IsShort and insight.Direction != InsightDirection.Down:
                return True

        return False

    def CalculateInsightReturn(self, algorithm, insight):

        """ Calculate the returns from the insights of each model as if 100% was invested in the model """
        
        insightReturn = 0

        if (algorithm.ActiveSecurities[insight.Symbol].Price != 0
            and self.entryDirection.get(insight.Symbol) is not None
            and self.previousPrice.get(insight.Symbol) is not None):

            previousPrice = self.previousPrice[insight.Symbol]
            currentPrice = algorithm.ActiveSecurities[insight.Symbol].Price
            entryDirection = self.entryDirection[insight.Symbol]
            weight = insight.Weight

            # return the insight return (weight-adjusted)
            insightReturn = entryDirection * (currentPrice / previousPrice -1) * weight
            
        return insightReturn

    def UpdateReturnsDictionaries(self, algorithm, insight):

        """ Update modelsInsightReturns and modelsDailyReturns """

        # calculate the insight return
        insightReturn = self.CalculateInsightReturn(algorithm, insight)
        # append the dictionary of insight returns by model
        self.modelsInsightReturns[insight.SourceModel][self.todayDate].append(insightReturn)
        # update the dictionary of daily returns by model
        self.modelsDailyReturns[insight.SourceModel][self.todayDate] = sum(self.modelsInsightReturns[insight.SourceModel][self.todayDate])
        
import pandas as pd
import numpy as np
from scipy.optimize import minimize

class PortfolioOptimizer:
    
    '''
    Description:
        Implementation of a custom optimizer that calculates the weights for each asset to optimize a given objective function
    Details:
        Optimization can be:
            - Equal Weighting
            - Maximize Portfolio Return
            - Minimize Portfolio Standard Deviation
            - Mean-Variance (minimize Standard Deviation given a target return)
            - Maximize Portfolio Sharpe Ratio
            - Maximize Portfolio Sortino Ratio
            - Risk Parity Portfolio
        Constraints:
            - Weights must be between some given boundaries
            - Weights must sum to 1
    '''
    
    def __init__(self, 
                 minWeight = 0,
                 maxWeight = 1):
                     
        '''
        Description:
            Initialize the CustomPortfolioOptimizer
        Args:
            minWeight(float): The lower bound on portfolio weights
            maxWeight(float): The upper bound on portfolio weights
        '''
        
        self.minWeight = minWeight
        self.maxWeight = maxWeight

    def Optimize(self, objFunction, dailyReturnsDf, targetReturn = None):
        
        '''
        Description:
            Perform portfolio optimization given a series of returns
        Args:
            objFunction: The objective function to optimize (equalWeighting, maxReturn, minVariance, meanVariance, maxSharpe, maxSortino, riskParity)
            dailyReturnsDf: DataFrame of historical daily arithmetic returns
        Returns:
            Array of double with the portfolio weights (size: K x 1)
        '''
        
        # initial weights: equally weighted
        size = dailyReturnsDf.columns.size # K x 1
        self.initWeights = np.array(size * [1. / size])

        # get sample covariance matrix
        covariance = dailyReturnsDf.cov()
        # get the sample covariance matrix of only negative returns for sortino ratio
        negativeReturnsDf = dailyReturnsDf[dailyReturnsDf < 0]
        covarianceNegativeReturns = negativeReturnsDf.cov()
        
        if objFunction == 'equalWeighting':
            return self.initWeights
        
        bounds = tuple((self.minWeight, self.maxWeight) for x in range(size))
        constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0}]
        
        if objFunction == 'meanVariance':
            # if no target return is provided, use the resulting from equal weighting
            if targetReturn is None:
                targetReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, self.initWeights)
            constraints.append( {'type': 'eq', 'fun': lambda weights:
                                self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights) - targetReturn} )

        opt = minimize(lambda weights: self.ObjectiveFunction(objFunction, dailyReturnsDf,
                                                                covariance, covarianceNegativeReturns,
                                                                weights),
                        x0 = self.initWeights,
                        bounds = bounds,
                        constraints = constraints,
                        method = 'SLSQP')

        return opt['x']

    def ObjectiveFunction(self, objFunction, dailyReturnsDf, covariance, covarianceNegativeReturns, weights):
        
        '''
        Description:
            Compute the objective function
        Args:
            objFunction: The objective function to optimize (equalWeighting, maxReturn, minVariance, meanVariance,
                                                                maxSharpe, maxSortino, riskParity)
            dailyReturnsDf: DataFrame of historical daily returns
            covariance: Sample covariance
            covarianceNegativeReturns: Sample covariance matrix of only negative returns
            weights: Portfolio weights
        '''
    
        if objFunction == 'maxReturn':
            f = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
            return -f # convert to negative to be minimized
        elif objFunction == 'minVariance':
            f = self.CalculateAnnualizedPortfolioStd(covariance, weights)
            return f
        elif objFunction == 'meanVariance':
            f = self.CalculateAnnualizedPortfolioStd(covariance, weights)
            return f
        elif objFunction == 'maxSharpe':
            f = self.CalculateAnnualizedPortfolioSharpeRatio(dailyReturnsDf, covariance, weights)
            return -f # convert to negative to be minimized
        elif objFunction == 'maxSortino':
            f = self.CalculateAnnualizedPortfolioSortinoRatio(dailyReturnsDf, covarianceNegativeReturns, weights)
            return -f # convert to negative to be minimized
        elif objFunction == 'riskParity':
            f = self.CalculateRiskParityFunction(covariance, weights)
            return f
        else:
            raise ValueError(f'PortfolioOptimizer.ObjectiveFunction: objFunction input has to be one of equalWeighting,'
             + ' maxReturn, minVariance, meanVariance, maxSharpe, maxSortino or riskParity')
        
    def CalculateAnnualizedPortfolioReturn(self, dailyReturnsDf, weights):
        
        annualizedPortfolioReturns = np.sum( ((1 + dailyReturnsDf.mean())**252 - 1) * weights )
        
        return annualizedPortfolioReturns
            
    def CalculateAnnualizedPortfolioStd(self, covariance, weights):
        
        annualizedPortfolioStd = np.sqrt( np.dot(weights.T, np.dot(covariance * 252, weights)) )
        
        if annualizedPortfolioStd == 0:
            raise ValueError(f'PortfolioOptimizer.CalculateAnnualizedPortfolioStd: annualizedPortfolioStd cannot be zero. Weights: {weights}')
            
        return annualizedPortfolioStd
    
    def CalculateAnnualizedPortfolioNegativeStd(self, covarianceNegativeReturns, weights):
    
        annualizedPortfolioNegativeStd = np.sqrt( np.dot(weights.T, np.dot(covarianceNegativeReturns * 252, weights)) )        
    
        if annualizedPortfolioNegativeStd == 0:
            raise ValueError(f'PortfolioOptimizer.CalculateAnnualizedPortfolioNegativeStd: annualizedPortfolioNegativeStd cannot be zero. Weights: {weights}')
        
        return annualizedPortfolioNegativeStd
        
    def CalculateAnnualizedPortfolioSharpeRatio(self, dailyReturnsDf, covariance, weights):
        
        annualizedPortfolioReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
        annualizedPortfolioStd = self.CalculateAnnualizedPortfolioStd(covariance, weights)
        annualizedPortfolioSharpeRatio = annualizedPortfolioReturn / annualizedPortfolioStd
            
        return annualizedPortfolioSharpeRatio
    
    def CalculateAnnualizedPortfolioSortinoRatio(self, dailyReturnsDf, covarianceNegativeReturns, weights):
        
        annualizedPortfolioReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
        annualizedPortfolioNegativeStd = self.CalculateAnnualizedPortfolioNegativeStd(covarianceNegativeReturns, weights)
        annualizedPortfolioSortinoRatio = annualizedPortfolioReturn / annualizedPortfolioNegativeStd
            
        return annualizedPortfolioSortinoRatio
    
    def CalculateRiskParityFunction(self, covariance, weights):
        
        ''' Spinu formulation for risk parity portfolio '''
        
        assetsRiskBudget = self.initWeights
        portfolioVolatility = self.CalculateAnnualizedPortfolioStd(covariance, weights)
        
        x = weights / portfolioVolatility
        riskParity = (np.dot(x.T, np.dot(covariance, x)) / 2) - np.dot(assetsRiskBudget.T, np.log(x))
            
        return riskParity
from clr import AddReference

AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel


class PriceVolumeUniverseSelectionModel(FundamentalUniverseSelectionModel):

    def __init__(self,
                 minPrice=20,
                 nStocks=500,
                 filterFineData=False,
                 universeSettings=None):

        super().__init__(filterFineData, universeSettings)

        self.minPrice = minPrice  # min price for stock selection
        self.nStocks = nStocks  # number of stocks to select in universe

    def SelectCoarse(self, algorithm, coarse):

        """ Perform coarse selection based on price and dollar volume """

        # filter universe to select only stocks with price above
        preFiltered = [x for x in coarse if x.HasFundamentalData and x.Price > self.minPrice]

        # sort the tickers by criteria and take the top candidates by dollar volume
        topDollarVolume = sorted(preFiltered, key=lambda x: x.DollarVolume, reverse=True)[:self.nStocks]
        topDollarVolumeSymbols = [x.Symbol for x in topDollarVolume]

        return topDollarVolumeSymbols
from PriceVolumeUniverseSelection import PriceVolumeUniverseSelectionModel

from RotationalOptimizerAlphaCreation import RotationalOptimizerAlphaCreationModel
from LongShortMovingAverageCrossoverAlphaCreation import LongShortMovingAverageCrossoverAlphaCreationModel

from CompositePortfolioConstruction import CompositePortfolioConstructionModel


class MultiAlphaFrameworkAlgorithm(QCAlgorithmFramework):

    def Initialize(self):

        ### user-defined inputs ---------------------------------------------------------------------------

        self.SetStartDate(2021, 1, 1)  # set start date
        # self.SetEndDate(2016, 1, 1)  # set end date
        self.SetCash(1000000)  # set strategy cash

        # UNIVERSE -------------------------------------------------------------

        #minPrice = 10  # minimum price for stocks (last day)
        #nStocks = 100  # number of stocks to select in universe

        # ALPHAS ---------------------------------------------------------------
        
        # select alphas to apply
        alphaModelsDict = {
            'alpha1': 'RotationalOptimizer',
            'alpha2': 'LongShortMovingAverage'
        }
        
        # ROTATIONAL OPTIMIZER -------------------------------------------------
        
        # select tickers to create the Universe and add indicators and parameters for weight filtering
        dictParameters = {
                        'SPY':
                                {'addTicker':
                                    [True, 'SPY'], # [boolean to add/not add the ticker, ticker to actually trade]
                                'sma':
                                    [200, (-0.1, 0.1), 0], # [period, (lower % threshold, upper % threshold; price vs sma), weight if condition met]
                                'macd':
                                    [(252, 504, 126), 0, 0.3], # [(fast, slow, signal), score macd vs signal (-1 to 1), weight if condition met]
                                'atrTrailStop':
                                    [True, (10, 63, 0.5), 5, 1]}, # [activate, (recentAtrPeriod, pastAtrPeriod, % above), atrMultiple, emergencyAtrMultiple]
                        'QQQ':
                                {'addTicker':
                                    [True, 'QQQ'], # [boolean to add/not add the ticker, ticker to actually trade]
                                'sma':
                                    [200, (-0.1, 0.3), 0], # [period, (lower % threshold, upper % threshold; price vs sma), weight if condition met]
                                'macd':
                                    [(252, 504, 126), 0, 0.3], # [(fast, slow, signal), score macd vs signal (-1 to 1), weight if condition met]
                                'atrTrailStop':
                                    [True, (10, 63, 0.5), 5, 1]}, # [activate, (recentAtrPeriod, pastAtrPeriod, % above), atrMultiple, emergencyAtrMultiple]
                        'TLT':
                                {'addTicker':
                                    [True, 'TLT'], # [boolean to add/not add the ticker, ticker to actually trade]
                                'sma':
                                    [700, (-0.1, 0.3), 0], # [period, (lower % threshold, upper % threshold; price vs sma), weight if condition met]
                                'macd':
                                    [(63, 126, 42), 0, 0], # [(fast, slow, signal), score macd vs signal (-1 to 1), weight if condition met]
                                'atrTrailStop':
                                    [True, (10, 63, 0.5), 5, 1]}, # [activate, (recentAtrPeriod, pastAtrPeriod, % above), atrMultiple, emergencyAtrMultiple]
                        'SLV':
                                {'addTicker':
                                    [True, 'SLV'], # [boolean to add/not add the ticker, ticker to actually trade]
                                'sma':
                                    [200, (-0.15, 0.15), 0], # [period, (lower % threshold, upper % threshold; price vs sma), weight if condition met]
                                'macd':
                                    [(63, 126, 42), 0, 0.3], # [(fast, slow, signal), score macd vs signal (-1 to 1), weight if condition met]
                                'atrTrailStop':
                                    [True, (10, 63, 0.5), 5, 1]}, # [activate, (recentAtrPeriod, pastAtrPeriod, % above), atrMultiple, emergencyAtrMultiple]
                        'GLD':
                                {'addTicker':
                                    [True, 'GLD'], # [boolean to add/not add the ticker, ticker to actually trade]
                                'sma':
                                    [200, (-0.15, 0.15), 0], # [period, (lower % threshold, upper % threshold; price vs sma), weight if condition met]
                                'macd':
                                    [(63, 126, 42), 0, 0.3], # [(fast, slow, signal), score macd vs signal (-1 to 1), weight if condition met]
                                'atrTrailStop':
                                    [True, (10, 63, 0.5), 5, 1]} # [activate, (recentAtrPeriod, pastAtrPeriod, % above), atrMultiple, emergencyAtrMultiple]
                        }
        
        # weights optimization
        
        # select number of lookback days for optimization
        lookbackOptimization = 63
        
        # select the objective function to optimize the portfolio weights
        # options are: maxReturn, minVariance, meanVariance, maxSharpe, maxSortino, riskParity
        objFunction = 'minVariance'
        
        # ticker to allocate remaining cash after filtering (enter None to just stay in Cash)
        tickerCash = 'IEF'
        
        # LONG SHORT MOVING AVERAGE CROSSOVER ----------------------------------
        
        tickersLongShortMovingAverageCrossover = ['TSLA']
        
        # PORTFOLIO ------------------------------------------------------------
        
        # number of days for rolling window of returns
        alphaPortfolioOptLookback = 21
        # options are: maxReturn, minVariance, meanVariance, maxSharpe, maxSortino, riskParity
        alphaPortfolioOptObjFunction = 'riskParity'

        ### --------------------------------------------------------------------------------------------------
        
        # list with alpha model names
        alphaModels = [value for key, value in sorted(alphaModelsDict.items())]

        # set the brokerage model
        #self.SetBrokerageModel(AlphaStreamsBrokerageModel())

        # set requested data resolution
        self.UniverseSettings.Resolution = Resolution.Minute
        
        # Universe Selection ---------------------------------------------------
        tickers = []
        symbols = []
        # loop through the tickers and create symbols for RotationalOptimizer
        for ticker in dictParameters.keys():
            if dictParameters[ticker]['addTicker'][0]:
                symbols.append(Symbol.Create(ticker, SecurityType.Equity, Market.USA))
                tickers.append(ticker)
                
                tradableTicker = dictParameters[ticker]['addTicker'][1]
                if tradableTicker not in tickers:
                    symbols.append(Symbol.Create(tradableTicker, SecurityType.Equity, Market.USA))
                    tickers.append(tradableTicker)
        # add tickerCash as well
        if tickerCash is not None and tickerCash not in tickers:
            symbols.append(Symbol.Create(tickerCash, SecurityType.Equity, Market.USA))
        
        # add tickers for LongShortMovingAverageCrossover
        for ticker in tickersLongShortMovingAverageCrossover:
            symbols.append(Symbol.Create(ticker, SecurityType.Equity, Market.USA))
        
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        #self.SetUniverseSelection(PriceVolumeUniverseSelectionModel(minPrice=minPrice, nStocks=nStocks))

        self.SetAlpha(
            CompositeAlphaModel(
                RotationalOptimizerAlphaCreationModel(self, name=alphaModels[0], lookbackOptimization=lookbackOptimization,
                                                        objFunction=objFunction, tickerCash=tickerCash, dictParameters=dictParameters),
                                                        
                LongShortMovingAverageCrossoverAlphaCreationModel(self, name=alphaModels[1], tickers=tickersLongShortMovingAverageCrossover)
            )
        )

        self.SetPortfolioConstruction(
            CompositePortfolioConstructionModel(self, alphaModels=alphaModels, alphaPortfolioOptLookback=alphaPortfolioOptLookback,
                                                alphaPortfolioOptObjFunction=alphaPortfolioOptObjFunction))

        self.SetExecution(ImmediateExecutionModel())

        self.SetRiskManagement(NullRiskManagementModel())
def CheckHistory(symbol, history):

    """ Check if the history dataframe is valid """

    if (str(symbol) not in history.index
            or history.loc[str(symbol)].get('open') is None
            or history.loc[str(symbol)].get('open').isna().any()
            or history.loc[str(symbol)].get('high') is None
            or history.loc[str(symbol)].get('high').isna().any()
            or history.loc[str(symbol)].get('low') is None
            or history.loc[str(symbol)].get('low').isna().any()
            or history.loc[str(symbol)].get('close') is None
            or history.loc[str(symbol)].get('close').isna().any()):
        return False

    else:
        return True


def ShouldEmitInsight(self, algorithm, symbol):

    """ Check if we should emit new insights for a symbol """

    tradingDay = algorithm.Time.day
    generatedInsight = self.generatedInsightBySymbol.get(symbol)

    if generatedInsight is not None:
        if self.generatedInsightBySymbol[symbol] == tradingDay:
            return False
        else:
            self.generatedInsightBySymbol[symbol] = tradingDay
            return True
    else:
        self.generatedInsightBySymbol[symbol] = tradingDay
        return True