Overall Statistics
import datetime

class MorningBreakOut(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        self.SetTimeZone("Europe/Berlin")
        
        self.SetBrokerageModel(BrokerageName.OandaBrokerage)
        
        # self.SetBenchmark("SPY")
        
        symbols = []
        symbols.append(Symbol.Create("DE30EUR", SecurityType.Cfd, Market.Oanda))
        
        #3. Set a universe using self.SetUniverseSelection(), and pass in a ManualUniverseSelectionModel() 
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        
        #2. Set the resolution of the universe assets to daily resolution
        self.UniverseSettings.Resolution = Resolution.Minute
        
        # holds {"symbol": symbolData} instance for each symbol
        self.symbolDataBySymbol = {}
        
        # Call the MOMAlphaModel Class
        self.SetAlpha( OMRAlphaModel(self.symbolDataBySymbol) )
        
        self.SetPortfolioConstruction( AllInOrSplitEqually() )
        
        self.SetRiskManagement( OpenPositionRiskManagement(self.symbolDataBySymbol) )
        
        self.SetExecution( ImmediateExecutionModel(self.symbolDataBySymbol) )
        
        
        
    def OnDataConsolidated(self, sender, quoteBar):
        self.Log("OnDataConsolidated called on " + str(self.Time))
        self.Log(str(quoteBar))

        
    def OnSecuritiesChanged(self, changes):
        '''We should use this event to setup any state required for our alpha calculations. 
        The method provides the algorithm object, and a list of securities changes. For example, 
        we could generate indicators for each symbol in this method and save the symbols and indicators to a dictionary.
        '''
        
        # Initialize SymbolData for each symbol
        for added in changes.AddedSecurities:
            self.Debug("securities change")
            symbolData = self.symbolDataBySymbol.get(added.Symbol)
            if symbolData is None:
                symbolData = SymbolData(added)
                symbolData.RegisterConsolidator(self, minutes=60)
                self.symbolDataBySymbol[added.Symbol] = symbolData

class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, security):
        self.Security = security
        self.Symbol = security.Symbol
        self._openingBar = None
        self.oneR = None
    
    def OnDataConsolidated(self, quoteBar):
        if quoteBar.Time.hour == 9:
            self.openingBar = quoteBar
            self.oneR = abs(self.openingBar.High - self.openingBar.Low)
            self.Debug(f"EndTime: {quoteBar.EndTime}, High: {quoteBar.High}, Low: {quoteBar.Low}, oneR: {abs(quoteBar.High-quoteBar.Low)}")
    
    def RegisterConsolidator(self, algorithm, minutes=60):
        openRangeCons = algorithm.Consolidate(self.Symbol, timedelta(minutes), self.OnDataConsolidated)
        algorithm.SubscriptionManager.AddConsolidator(self.Symbol, openRangeCons)
    
    @property
    def openingBar(self):
        return self._openingBar
        
    @openingBar.setter
    def openingBar(self, value):
        self._openingBar = value



class OMRAlphaModel(AlphaModel):
    
    def __init__(self, symbolDataBySymbol):
        self.symbolDataBySymbol = symbolDataBySymbol


    def Update(self, algorithm, data):
        
        insights = []
        for symbol, symbolData in self.symbolDataBySymbol.items():
            
            if symbolData.openingBar is None: continue
            
            algorithm.Debug("no openingBar")
            
            direction = InsightDirection.Flat
            
            if not algorithm.Portfolio[symbol].Invested:
                # Entry
                if data[symbol].Close >= symbolData.openingBar.High: direction = InsightDirection.Up
                if data[symbol].Close <= symbolData.openingBar.Low: direction = InsightDirection.Down
                insights.append(Insight(symbolData.Symbol, timedelta(1), InsightType.Price, direction, "Entry"))
            
            # Take Profit
            if algorithm.Portfolio[symbol].Invested:
                if data[symbol].Close >= symbolData.openingBar.High + symbolData.oneR: InsightDirection.Down
                if data[symbol].Close <= symbolData.openingBar.Low - symbolData.oneR: InsightDirection.Up
                insights.append(Insight(symbolData.Symbol, timedelta(1), InsightType.Price, direction, "TakeProfit"))
                
            
        return insights


class AllInOrSplitEqually(PortfolioConstructionModel):
    
    def __init__(self):
        pass
    
    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}

        # give equal weighting to each security
        count = sum(x.Direction != InsightDirection.Flat for x in activeInsights)
        percent = 0 if count == 0 else 1.0 / count
        
        for insight in activeInsights:
            result[insight] = insight.Direction * percent
        
        return result
        
class OpenPositionRiskManagement(RiskManagementModel):

    def __init__(self, symbolDataBySymbol):
        self.symbolDataBySymbol = symbolDataBySymbol
        self.trailingStop = None

    def ManageRisk(self, algorithm, targets):

        targets = []
        
        for symbol in self.symbolDataBySymbol:
            
            # Time Exit if necessary
            if algorithm.Time.time() >= datetime.time(21,15):
                targets.append(PortfolioTarget(symbol, 0))
                continue
            
            
            # Trailing StopLoss
            if algorithm.Portfolio[symbol].Quantity > 0:
                self.trailingStop = round(self.symbolDataBySymbol[symbol].openingBar.High - self.oneR / 2, 1)
                if algorithm.Securities[symbol].Close <= self.trailingStop:
                    targets.append(PortfolioTarget(symbol, 0))
                
            if algorithm.Portfolio[symbol].Quantity < 0:
                self.trailingStop = round(self.symbolDataBySymbol[symbol].openingBar.Low + self.oneR / 2, 1)
                if algorithm.Securities[symbol].Close >= self.trailingStop:
                    targets.append(PortfolioTarget(symbol, 0))
            
        return targets

class ImmediateExecutionModel(ExecutionModel):
    '''Provides an implementation of IExecutionModel that immediately submits market orders to achieve the desired portfolio targets'''

    def __init__(self, symbolDataBySymbol):
        '''Initializes a new instance of the ImmediateExecutionModel class'''
        self.targetsCollection = PortfolioTargetCollection()
        self.symbolDataBySymbol = symbolDataBySymbol

    def Execute(self, algorithm, targets):
        '''Immediately submits orders for the specified portfolio targets.
        Args:
            algorithm: The algorithm instance
            targets: The portfolio targets to be ordered'''

        # for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
        self.targetsCollection.AddRange(targets)
        if self.targetsCollection.Count > 0:
            for target in self.targetsCollection.OrderByMarginImpact(algorithm):
                # calculate remaining quantity to be ordered
                quantity = OrderSizing.GetUnorderedQuantity(algorithm, target)
                if quantity != 0:
                    algorithm.MarketOrder(target.Symbol, quantity)
                    self.symbolDataBySymbol.openingBar = None

            self.targetsCollection.ClearFulfilled(algorithm)